1#!/usr/bin/python 2# Copyright (c) 2012 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"""This script lays out the PNaCl translator files for a 7 normal Chrome installer, for one platform. Once run num-of-arches times, 8 the result can then be packed into a multi-CRX zip file. 9 10 This script depends on and pulls in the translator nexes and libraries 11 from the PNaCl translator. It also depends on the pnacl_irt_shim. 12""" 13 14import json 15import logging 16import optparse 17import os 18import platform 19import re 20import shutil 21import subprocess 22import sys 23 24J = os.path.join 25 26###################################################################### 27# Target arch and build arch junk to convert between all the 28# silly conventions between SCons, Chrome and PNaCl. 29 30# The version of the arch used by NaCl manifest files. 31# This is based on the machine "building" this extension. 32# We also used this to identify the arch-specific different versions of 33# this extension. 34 35def CanonicalArch(arch): 36 if arch in ('x86_64', 'x86-64', 'x64', 'amd64'): 37 return 'x86-64' 38 # TODO(jvoung): be more specific about the arm architecture version? 39 if arch in ('arm', 'armv7'): 40 return 'arm' 41 if arch in ('mipsel'): 42 return 'mips32' 43 if re.match('^i.86$', arch) or arch in ('x86_32', 'x86-32', 'ia32', 'x86'): 44 return 'x86-32' 45 return None 46 47def GetBuildArch(): 48 arch = platform.machine() 49 return CanonicalArch(arch) 50 51BUILD_ARCH = GetBuildArch() 52ARCHES = ['x86-32', 'x86-64', 'arm', 'mips32'] 53 54def IsValidArch(arch): 55 return arch in ARCHES 56 57 58###################################################################### 59 60# Normalize the platform name to be the way SCons finds chrome binaries. 61# This is based on the platform "building" the extension. 62 63def GetBuildPlatform(): 64 if sys.platform == 'darwin': 65 platform = 'mac' 66 elif sys.platform.startswith('linux'): 67 platform = 'linux' 68 elif sys.platform in ('cygwin', 'win32'): 69 platform = 'windows' 70 else: 71 raise Exception('Unknown platform: %s' % sys.platform) 72 return platform 73BUILD_PLATFORM = GetBuildPlatform() 74 75 76def DetermineInstallerArches(target_arch): 77 arch = CanonicalArch(target_arch) 78 if not IsValidArch(arch): 79 raise Exception('Unknown target_arch %s' % target_arch) 80 # On windows, we need x86-32 and x86-64 (assuming non-windows RT). 81 if BUILD_PLATFORM == 'windows': 82 if arch.startswith('x86'): 83 return ['x86-32', 'x86-64'] 84 else: 85 raise Exception('Unknown target_arch on windows w/ target_arch == %s' % 86 target_arch) 87 else: 88 return [arch] 89 90 91###################################################################### 92 93class PnaclPackaging(object): 94 95 package_base = os.path.dirname(__file__) 96 97 # File paths that are set from the command line. 98 pnacl_template = None 99 package_version_path = None 100 pnacl_package = 'pnacl_newlib' 101 102 # Agreed-upon name for pnacl-specific info. 103 pnacl_json = 'pnacl.json' 104 105 @staticmethod 106 def SetPnaclInfoTemplatePath(path): 107 PnaclPackaging.pnacl_template = path 108 109 @staticmethod 110 def SetPackageVersionPath(path): 111 PnaclPackaging.package_version_path = path 112 113 @staticmethod 114 def SetPnaclPackageName(name): 115 PnaclPackaging.pnacl_package = name 116 117 @staticmethod 118 def PnaclToolsRevision(): 119 pkg_ver_cmd = [sys.executable, PnaclPackaging.package_version_path, 120 'getrevision', 121 '--revision-package', PnaclPackaging.pnacl_package] 122 123 return subprocess.check_output(pkg_ver_cmd).strip() 124 125 @staticmethod 126 def GeneratePnaclInfo(target_dir, abi_version, arch): 127 # A note on versions: pnacl_version is the version of translator built 128 # by the NaCl repo, while abi_version is bumped when the NaCl sandbox 129 # actually changes. 130 pnacl_version = PnaclPackaging.PnaclToolsRevision() 131 with open(PnaclPackaging.pnacl_template, 'r') as pnacl_template_fd: 132 pnacl_template = json.load(pnacl_template_fd) 133 out_name = J(target_dir, UseWhitelistedChars(PnaclPackaging.pnacl_json, 134 None)) 135 with open(out_name, 'w') as output_fd: 136 pnacl_template['pnacl-arch'] = arch 137 pnacl_template['pnacl-version'] = pnacl_version 138 json.dump(pnacl_template, output_fd, sort_keys=True, indent=4) 139 140 141###################################################################### 142 143class PnaclDirs(object): 144 translator_dir = None 145 output_dir = None 146 147 @staticmethod 148 def SetTranslatorRoot(d): 149 PnaclDirs.translator_dir = d 150 151 @staticmethod 152 def TranslatorRoot(): 153 return PnaclDirs.translator_dir 154 155 @staticmethod 156 def LibDir(target_arch): 157 return J(PnaclDirs.TranslatorRoot(), 'translator', '%s' % target_arch) 158 159 @staticmethod 160 def SandboxedCompilerDir(target_arch): 161 return J(PnaclDirs.TranslatorRoot(), 'translator', target_arch, 'bin') 162 163 @staticmethod 164 def SetOutputDir(d): 165 PnaclDirs.output_dir = d 166 167 @staticmethod 168 def OutputDir(): 169 return PnaclDirs.output_dir 170 171 @staticmethod 172 def OutputAllDir(version_quad): 173 return J(PnaclDirs.OutputDir(), version_quad) 174 175 @staticmethod 176 def OutputArchBase(arch): 177 return '%s' % arch 178 179 @staticmethod 180 def OutputArchDir(arch): 181 # Nest this in another directory so that the layout will be the same 182 # as the "all"/universal version. 183 parent_dir = J(PnaclDirs.OutputDir(), PnaclDirs.OutputArchBase(arch)) 184 return (parent_dir, J(parent_dir, PnaclDirs.OutputArchBase(arch))) 185 186 187###################################################################### 188 189def StepBanner(short_desc, long_desc): 190 logging.info("**** %s\t%s", short_desc, long_desc) 191 192 193def Clean(): 194 out_dir = PnaclDirs.OutputDir() 195 StepBanner('CLEAN', 'Cleaning out old packaging: %s' % out_dir) 196 if os.path.isdir(out_dir): 197 shutil.rmtree(out_dir) 198 else: 199 logging.info('Clean skipped -- no previous output directory!') 200 201###################################################################### 202 203def UseWhitelistedChars(orig_basename, arch): 204 """ Make the filename match the pattern expected by nacl_file_host. 205 206 Currently, this assumes there is prefix "pnacl_public_" and 207 that the allowed chars are in the set [a-zA-Z0-9_]. 208 """ 209 if arch: 210 target_basename = 'pnacl_public_%s_%s' % (arch, orig_basename) 211 else: 212 target_basename = 'pnacl_public_%s' % orig_basename 213 result = re.sub(r'[^a-zA-Z0-9_]', '_', target_basename) 214 logging.info('UseWhitelistedChars using: %s' % result) 215 return result 216 217def CopyFlattenDirsAndPrefix(src_dir, arch, dest_dir): 218 """ Copy files from src_dir to dest_dir. 219 220 When copying, also rename the files such that they match the white-listing 221 pattern in chrome/browser/nacl_host/nacl_file_host.cc. 222 """ 223 if not os.path.isdir(src_dir): 224 raise Exception('Copy dir failed, directory does not exist: %s' % src_dir) 225 226 for (root, dirs, files) in os.walk(src_dir, followlinks=True): 227 for f in files: 228 # Assume a flat directory. 229 assert (f == os.path.basename(f)) 230 full_name = J(root, f) 231 target_name = UseWhitelistedChars(f, arch) 232 shutil.copy(full_name, J(dest_dir, target_name)) 233 234 235def BuildArchForInstaller(version_quad, arch, lib_overrides): 236 """ Build an architecture specific version for the chrome installer. 237 """ 238 target_dir = PnaclDirs.OutputDir() 239 240 StepBanner('BUILD INSTALLER', 241 'Packaging for arch %s in %s' % (arch, target_dir)) 242 243 # Copy llc.nexe and ld.nexe, but with some renaming and directory flattening. 244 CopyFlattenDirsAndPrefix(PnaclDirs.SandboxedCompilerDir(arch), 245 arch, 246 target_dir) 247 248 # Copy native libraries, also with renaming and directory flattening. 249 CopyFlattenDirsAndPrefix(PnaclDirs.LibDir(arch), arch, target_dir) 250 251 # Also copy files from the list of overrides. 252 # This needs the arch tagged onto the name too, like the other files. 253 if arch in lib_overrides: 254 for (override_lib, desired_name) in lib_overrides[arch]: 255 target_name = UseWhitelistedChars(desired_name, arch) 256 shutil.copy(override_lib, J(target_dir, target_name)) 257 258 259def BuildInstallerStyle(version_quad, lib_overrides, arches): 260 """ Package the pnacl component for use within the chrome installer 261 infrastructure. These files need to be named in a special way 262 so that white-listing of files is easy. 263 """ 264 StepBanner("BUILD_ALL", "Packaging installer for version: %s" % version_quad) 265 for arch in arches: 266 BuildArchForInstaller(version_quad, arch, lib_overrides) 267 # Generate pnacl info manifest. 268 # Hack around the fact that there may be more than one arch, on Windows. 269 if len(arches) == 1: 270 arches = arches[0] 271 PnaclPackaging.GeneratePnaclInfo(PnaclDirs.OutputDir(), version_quad, arches) 272 273 274###################################################################### 275 276 277def Main(): 278 usage = 'usage: %prog [options] version_arg' 279 parser = optparse.OptionParser(usage) 280 # We may want to accept a target directory to dump it in the usual 281 # output directory (e.g., scons-out). 282 parser.add_option('-c', '--clean', dest='clean', 283 action='store_true', default=False, 284 help='Clean out destination directory first.') 285 parser.add_option('-d', '--dest', dest='dest', 286 help='The destination root for laying out the extension') 287 parser.add_option('-L', '--lib_override', 288 dest='lib_overrides', action='append', default=[], 289 help='Specify path to a fresher native library ' + 290 'that overrides the tarball library with ' + 291 '(arch,libfile,librenamed) tuple.') 292 parser.add_option('-t', '--target_arch', 293 dest='target_arch', default=None, 294 help='Only generate the chrome installer version for arch') 295 parser.add_option('--info_template_path', 296 dest='info_template_path', default=None, 297 help='Path of the info template file') 298 parser.add_option('--package_version_path', dest='package_version_path', 299 default=None, help='Path to package_version.py script.') 300 parser.add_option('--pnacl_package_name', dest='pnacl_package_name', 301 default=None, help='Name of PNaCl package.') 302 parser.add_option('--pnacl_translator_path', dest='pnacl_translator_path', 303 default=None, help='Location of PNaCl translator.') 304 parser.add_option('-v', '--verbose', dest='verbose', default=False, 305 action='store_true', 306 help='Print verbose debug messages.') 307 308 (options, args) = parser.parse_args() 309 if options.verbose: 310 logging.getLogger().setLevel(logging.DEBUG) 311 else: 312 logging.getLogger().setLevel(logging.ERROR) 313 logging.info('pnacl_component_crx_gen w/ options %s and args %s\n' 314 % (options, args)) 315 316 # Set destination directory before doing any cleaning, etc. 317 if options.dest is None: 318 raise Exception('Destination path must be set.') 319 PnaclDirs.SetOutputDir(options.dest) 320 321 if options.clean: 322 Clean() 323 324 if options.pnacl_translator_path is None: 325 raise Exception('PNaCl translator path must be set.') 326 PnaclDirs.SetTranslatorRoot(options.pnacl_translator_path) 327 328 if options.info_template_path: 329 PnaclPackaging.SetPnaclInfoTemplatePath(options.info_template_path) 330 331 if options.package_version_path: 332 PnaclPackaging.SetPackageVersionPath(options.package_version_path) 333 else: 334 raise Exception('Package verison script must be specified.') 335 336 if options.pnacl_package_name: 337 PnaclPackaging.SetPnaclPackageName(options.pnacl_package_name) 338 339 lib_overrides = {} 340 for o in options.lib_overrides: 341 arch, override_lib, desired_name = o.split(',') 342 arch = CanonicalArch(arch) 343 if not IsValidArch(arch): 344 raise Exception('Unknown arch for -L: %s (from %s)' % (arch, o)) 345 if not os.path.isfile(override_lib): 346 raise Exception('Override native lib not a file for -L: %s (from %s)' % 347 (override_lib, o)) 348 override_list = lib_overrides.get(arch, []) 349 override_list.append((override_lib, desired_name)) 350 lib_overrides[arch] = override_list 351 352 if len(args) != 1: 353 parser.print_help() 354 parser.error('Incorrect number of arguments') 355 356 abi_version = int(args[0]) 357 358 arches = DetermineInstallerArches(options.target_arch) 359 BuildInstallerStyle(abi_version, lib_overrides, arches) 360 return 0 361 362 363if __name__ == '__main__': 364 sys.exit(Main()) 365