1#!/usr/bin/python 2# Copyright 2016 The Chromium Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6""" 7 Convert the ASCII download_file_types.asciipb proto into a binary resource. 8 9 We generate a separate variant of the binary proto for each platform, 10 each which contains only the values that platform needs. 11""" 12 13import os 14import re 15import sys 16 17# Import the binary proto generator. Walks up to the root of the source tree 18# which is five directories above, and the finds the protobufs directory from 19# there. 20proto_generator_path = os.path.normpath(os.path.join(os.path.abspath(__file__), 21 *[os.path.pardir] * 5 + ['components/resources/protobufs'])) 22sys.path.insert(0, proto_generator_path) 23from binary_proto_generator import BinaryProtoGenerator 24 25# Map of platforms for which we can generate binary protos. 26# This must be run after the custom imports. 27# key: type-name 28# value: proto-platform_type (int) 29def PlatformTypes(): 30 return { 31 "android": download_file_types_pb2.DownloadFileType.PLATFORM_ANDROID, 32 "chromeos": download_file_types_pb2.DownloadFileType.PLATFORM_CHROME_OS, 33 "linux": download_file_types_pb2.DownloadFileType.PLATFORM_LINUX, 34 "bsd": download_file_types_pb2.DownloadFileType.PLATFORM_LINUX, 35 "mac": download_file_types_pb2.DownloadFileType.PLATFORM_MAC, 36 "win": download_file_types_pb2.DownloadFileType.PLATFORM_WINDOWS, 37 } 38 39 40def PrunePlatformSettings(file_type, default_settings, platform_type): 41 # Modify this file_type's platform_settings by keeping the only the 42 # best one for this platform_type. In order of preference: 43 # * Exact match to platform_type 44 # * PLATFORM_ANY entry 45 # * or copy from the default file type. 46 47 last_platform = -1 48 setting_match = None 49 for s in file_type.platform_settings: 50 # Enforce: sorted and no dups (signs of mistakes). 51 assert last_platform < s.platform, ( 52 "Extension '%s' has duplicate or out of order platform: '%s'" % 53 (file_type.extension, s.platform)) 54 last_platform = s.platform 55 56 # Pick the most specific match. 57 if ((s.platform == platform_type) or 58 (s.platform == download_file_types_pb2.DownloadFileType.PLATFORM_ANY and 59 setting_match is None)): 60 setting_match = s 61 62 # If platform_settings was empty, we'll fill in from the default 63 if setting_match is None: 64 assert default_settings is not None, ( 65 "Missing default settings for platform %d" % platform_type) 66 setting_match = default_settings 67 68 # Now clear out the full list and replace it with 1 entry. 69 del file_type.platform_settings[:] 70 new_setting = file_type.platform_settings.add() 71 new_setting.CopyFrom(setting_match) 72 new_setting.ClearField('platform') 73 74 75def FilterPbForPlatform(full_pb, platform_type): 76 """ Return a filtered protobuf for this platform_type """ 77 assert type(platform_type) is int, "Bad platform_type type" 78 79 new_pb = download_file_types_pb2.DownloadFileTypeConfig(); 80 new_pb.CopyFrom(full_pb) 81 82 # Ensure there's only one platform_settings for the default. 83 PrunePlatformSettings(new_pb.default_file_type, None, platform_type) 84 85 # This can be extended if we want to match weird extensions. 86 # Just no dots, non-UTF8, or uppercase chars. 87 invalid_char_re = re.compile('[^a-z0-9_-]') 88 89 # Filter platform_settings for each type. 90 uma_values_used = set() 91 extensions_used = set() 92 for file_type in new_pb.file_types: 93 assert not invalid_char_re.search(file_type.extension), ( 94 "File extension '%s' contains non alpha-num-dash chars" % ( 95 file_type.extension)) 96 assert file_type.extension not in extensions_used, ( 97 "Duplicate extension '%s'" % file_type.extension) 98 extensions_used.add(file_type.extension) 99 100 assert file_type.uma_value not in uma_values_used, ( 101 "Extension '%s' reused UMA value %d." % ( 102 file_type.extension, file_type.uma_value)) 103 uma_values_used.add(file_type.uma_value) 104 105 # Modify file_type to include only the best match platform_setting. 106 PrunePlatformSettings( 107 file_type, new_pb.default_file_type.platform_settings[0], platform_type) 108 109 return new_pb 110 111 112def FilterForPlatformAndWrite(full_pb, platform_type, outfile): 113 """ Filter and write out a file for this platform """ 114 # Filter it 115 filtered_pb = FilterPbForPlatform(full_pb, platform_type); 116 # Serialize it 117 binary_pb_str = filtered_pb.SerializeToString() 118 # Write it to disk 119 open(outfile, 'wb').write(binary_pb_str) 120 121 122def MakeSubDirs(outfile): 123 """ Make the subdirectories needed to create file |outfile| """ 124 dirname = os.path.dirname(outfile) 125 if not os.path.exists(dirname): 126 os.makedirs(dirname) 127 128 129class DownloadFileTypeProtoGenerator(BinaryProtoGenerator): 130 131 def ImportProtoModule(self): 132 import download_file_types_pb2 133 globals()['download_file_types_pb2'] = download_file_types_pb2 134 135 def EmptyProtoInstance(self): 136 return download_file_types_pb2.DownloadFileTypeConfig() 137 138 def ValidatePb(self, opts, pb): 139 """ Validate the basic values of the protobuf. The 140 file_type_policies_unittest.cc will also validate it by platform, 141 but this will catch errors earlier. 142 """ 143 assert pb.version_id > 0; 144 assert pb.sampled_ping_probability >= 0.0; 145 assert pb.sampled_ping_probability <= 1.0; 146 assert len(pb.default_file_type.platform_settings) >= 1; 147 assert len(pb.file_types) > 1; 148 149 def ProcessPb(self, opts, pb): 150 """ Generate one or more binary protos using the parsed proto. """ 151 if opts.type is not None: 152 # Just one platform type 153 platform_enum = PlatformTypes()[opts.type] 154 outfile = os.path.join(opts.outdir, opts.outbasename) 155 FilterForPlatformAndWrite(pb, platform_enum, outfile) 156 else: 157 # Make a separate file for each platform 158 for platform_type, platform_enum in PlatformTypes().iteritems(): 159 # e.g. .../all/77/chromeos/download_file_types.pb 160 outfile = os.path.join(opts.outdir, 161 str(pb.version_id), 162 platform_type, 163 opts.outbasename) 164 MakeSubDirs(outfile) 165 FilterForPlatformAndWrite(pb, platform_enum, outfile) 166 167 def AddCommandLineOptions(self, parser): 168 parser.add_option('-a', '--all', action="store_true", default=False, 169 help='Write a separate file for every platform. ' 170 'Outfile must have a %d for version and %s for platform.') 171 parser.add_option('-t', '--type', 172 help='The platform type. One of android, chromeos, ' + 173 'linux, bsd, mac, win') 174 175 def AddExtraCommandLineArgsForVirtualEnvRun(self, opts, command): 176 if opts.type is not None: 177 command += ['-t', opts.type] 178 if opts.all: 179 command += ['-a'] 180 181 def VerifyArgs(self, opts): 182 if (not opts.all and opts.type not in PlatformTypes()): 183 print "ERROR: Unknown platform type '%s'" % opts.type 184 self.opt_parser.print_help() 185 return False 186 187 if (bool(opts.all) == bool(opts.type)): 188 print "ERROR: Need exactly one of --type or --all" 189 self.opt_parser.print_help() 190 return False 191 return True 192 193def main(): 194 return DownloadFileTypeProtoGenerator().Run() 195 196if __name__ == '__main__': 197 sys.exit(main()) 198