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.
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.
10 This script depends on and pulls in the translator nexes and libraries
11 from the toolchain directory (so that must be downloaded first) and
12 it depends on the pnacl_irt_shim.
26 ######################################################################
27 # Target arch and build arch junk to convert between all the
28 # silly conventions between SCons, Chrome and PNaCl.
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
35 def CanonicalArch(arch):
36 if arch in ('x86_64', 'x86-64', 'x64', 'amd64'):
38 # TODO(jvoung): be more specific about the arm architecture version?
39 if arch in ('arm', 'armv7'):
41 if re.match('^i.86$', arch) or arch in ('x86_32', 'x86-32', 'ia32', 'x86'):
46 arch = platform.machine()
47 return CanonicalArch(arch)
49 BUILD_ARCH = GetBuildArch()
50 ARCHES = ['x86-32', 'x86-64', 'arm']
52 def IsValidArch(arch):
55 # The version of the arch used by configure and pnacl's build.sh.
56 def StandardArch(arch):
57 return {'x86-32': 'i686',
59 'arm' : 'armv7'}[arch]
62 ######################################################################
65 """ Find the native_client path, relative to this script.
66 This script is in ppapi/... and native_client is a sibling of ppapi.
68 script_file = os.path.abspath(__file__)
69 def SearchForNaCl(cur_dir):
70 if cur_dir.endswith('ppapi'):
71 parent = os.path.dirname(cur_dir)
72 sibling = os.path.join(parent, 'native_client')
73 if not os.path.isdir(sibling):
74 raise Exception('Could not find native_client relative to %s' %
77 # Detect when we've the root (linux is /, but windows is not...)
78 next_dir = os.path.dirname(cur_dir)
79 if cur_dir == next_dir:
80 raise Exception('Could not find native_client relative to %s' %
82 return SearchForNaCl(next_dir)
84 return SearchForNaCl(script_file)
87 NACL_ROOT = GetNaClRoot()
90 ######################################################################
92 # Normalize the platform name to be the way SCons finds chrome binaries.
93 # This is based on the platform "building" the extension.
95 def GetBuildPlatform():
96 if sys.platform == 'darwin':
98 elif sys.platform.startswith('linux'):
100 elif sys.platform in ('cygwin', 'win32'):
103 raise Exception('Unknown platform: %s' % sys.platform)
105 BUILD_PLATFORM = GetBuildPlatform()
108 def DetermineInstallerArches(target_arch):
109 arch = CanonicalArch(target_arch)
110 if not IsValidArch(arch):
111 raise Exception('Unknown target_arch %s' % target_arch)
112 # On windows, we need x86-32 and x86-64 (assuming non-windows RT).
113 if BUILD_PLATFORM == 'windows':
114 if arch.startswith('x86'):
115 return ['x86-32', 'x86-64']
117 raise Exception('Unknown target_arch on windows w/ target_arch == %s' %
123 ######################################################################
125 class PnaclPackaging(object):
127 package_base = os.path.dirname(__file__)
129 # File paths that are set from the command line.
130 pnacl_template = None
131 tool_revisions = None
133 # Agreed-upon name for pnacl-specific info.
134 pnacl_json = 'pnacl.json'
137 def SetPnaclInfoTemplatePath(path):
138 PnaclPackaging.pnacl_template = path
141 def SetToolsRevisionPath(path):
142 PnaclPackaging.tool_revisions = path
145 def PnaclToolsRevision():
146 with open(PnaclPackaging.tool_revisions, 'r') as f:
147 for line in f.read().splitlines():
148 if line.startswith('PNACL_VERSION'):
149 _, version = line.split('=')
150 # CWS happens to use version quads, so make it a quad too.
151 # However, each component of the quad is limited to 64K max.
152 # Try to handle a bit more.
153 max_version = 2 ** 16
154 version = int(version)
155 version_more = version / max_version
156 version = version % max_version
157 return '0.1.%d.%d' % (version_more, version)
158 raise Exception('Cannot find PNACL_VERSION in TOOL_REVISIONS file: %s' %
159 PnaclPackaging.tool_revisions)
162 def GeneratePnaclInfo(target_dir, abi_version, arch):
163 # A note on versions: pnacl_version is the version of translator built
164 # by the NaCl repo, while abi_version is bumped when the NaCl sandbox
166 pnacl_version = PnaclPackaging.PnaclToolsRevision()
167 with open(PnaclPackaging.pnacl_template, 'r') as pnacl_template_fd:
168 pnacl_template = json.load(pnacl_template_fd)
169 out_name = J(target_dir, UseWhitelistedChars(PnaclPackaging.pnacl_json,
171 with open(out_name, 'w') as output_fd:
172 pnacl_template['pnacl-arch'] = arch
173 pnacl_template['pnacl-version'] = pnacl_version
174 json.dump(pnacl_template, output_fd, sort_keys=True, indent=4)
177 ######################################################################
179 class PnaclDirs(object):
180 toolchain_dir = J(NACL_ROOT, 'toolchain')
181 output_dir = J(toolchain_dir, 'pnacl-package')
184 def TranslatorRoot():
185 return J(PnaclDirs.toolchain_dir, 'pnacl_translator')
188 def LibDir(target_arch):
189 return J(PnaclDirs.TranslatorRoot(), 'lib-%s' % target_arch)
192 def SandboxedCompilerDir(target_arch):
193 return J(PnaclDirs.toolchain_dir,
194 'pnacl_translator', StandardArch(target_arch), 'bin')
198 PnaclDirs.output_dir = d
202 return PnaclDirs.output_dir
205 def OutputAllDir(version_quad):
206 return J(PnaclDirs.OutputDir(), version_quad)
209 def OutputArchBase(arch):
213 def OutputArchDir(arch):
214 # Nest this in another directory so that the layout will be the same
215 # as the "all"/universal version.
216 parent_dir = J(PnaclDirs.OutputDir(), PnaclDirs.OutputArchBase(arch))
217 return (parent_dir, J(parent_dir, PnaclDirs.OutputArchBase(arch)))
220 ######################################################################
222 def StepBanner(short_desc, long_desc):
223 logging.info("**** %s\t%s", short_desc, long_desc)
227 out_dir = PnaclDirs.OutputDir()
228 StepBanner('CLEAN', 'Cleaning out old packaging: %s' % out_dir)
229 if os.path.isdir(out_dir):
230 shutil.rmtree(out_dir)
232 logging.info('Clean skipped -- no previous output directory!')
234 ######################################################################
236 def UseWhitelistedChars(orig_basename, arch):
237 """ Make the filename match the pattern expected by nacl_file_host.
239 Currently, this assumes there is prefix "pnacl_public_" and
240 that the allowed chars are in the set [a-zA-Z0-9_].
243 target_basename = 'pnacl_public_%s_%s' % (arch, orig_basename)
245 target_basename = 'pnacl_public_%s' % orig_basename
246 result = re.sub(r'[^a-zA-Z0-9_]', '_', target_basename)
247 logging.info('UseWhitelistedChars using: %s' % result)
250 def CopyFlattenDirsAndPrefix(src_dir, arch, dest_dir):
251 """ Copy files from src_dir to dest_dir.
253 When copying, also rename the files such that they match the white-listing
254 pattern in chrome/browser/nacl_host/nacl_file_host.cc.
256 for (root, dirs, files) in os.walk(src_dir, followlinks=True):
258 # Assume a flat directory.
259 assert (f == os.path.basename(f))
260 full_name = J(root, f)
261 target_name = UseWhitelistedChars(f, arch)
262 shutil.copy(full_name, J(dest_dir, target_name))
265 def BuildArchForInstaller(version_quad, arch, lib_overrides):
266 """ Build an architecture specific version for the chrome installer.
268 target_dir = PnaclDirs.OutputDir()
270 StepBanner('BUILD INSTALLER',
271 'Packaging for arch %s in %s' % (arch, target_dir))
273 # Copy llc.nexe and ld.nexe, but with some renaming and directory flattening.
274 CopyFlattenDirsAndPrefix(PnaclDirs.SandboxedCompilerDir(arch),
278 # Copy native libraries, also with renaming and directory flattening.
279 CopyFlattenDirsAndPrefix(PnaclDirs.LibDir(arch), arch, target_dir)
281 # Also copy files from the list of overrides.
282 # This needs the arch tagged onto the name too, like the other files.
283 if arch in lib_overrides:
284 for override in lib_overrides[arch]:
285 override_base = os.path.basename(override)
286 target_name = UseWhitelistedChars(override_base, arch)
287 shutil.copy(override, J(target_dir, target_name))
290 def BuildInstallerStyle(version_quad, lib_overrides, arches):
291 """ Package the pnacl component for use within the chrome installer
292 infrastructure. These files need to be named in a special way
293 so that white-listing of files is easy.
295 StepBanner("BUILD_ALL", "Packaging installer for version: %s" % version_quad)
297 BuildArchForInstaller(version_quad, arch, lib_overrides)
298 # Generate pnacl info manifest.
299 # Hack around the fact that there may be more than one arch, on Windows.
302 PnaclPackaging.GeneratePnaclInfo(PnaclDirs.OutputDir(), version_quad, arches)
305 ######################################################################
309 usage = 'usage: %prog [options] version_arg'
310 parser = optparse.OptionParser(usage)
311 # We may want to accept a target directory to dump it in the usual
312 # output directory (e.g., scons-out).
313 parser.add_option('-c', '--clean', dest='clean',
314 action='store_true', default=False,
315 help='Clean out destination directory first.')
316 parser.add_option('-d', '--dest', dest='dest',
317 help='The destination root for laying out the extension')
318 parser.add_option('-L', '--lib_override',
319 dest='lib_overrides', action='append', default=[],
320 help='Specify path to a fresher native library ' +
321 'that overrides the tarball library with ' +
322 '(arch:libfile) tuple.')
323 parser.add_option('-t', '--target_arch',
324 dest='target_arch', default=None,
325 help='Only generate the chrome installer version for arch')
326 parser.add_option('--info_template_path',
327 dest='info_template_path', default=None,
328 help='Path of the info template file')
329 parser.add_option('--tool_revisions_path', dest='tool_revisions_path',
330 default=None, help='Location of NaCl TOOL_REVISIONS file.')
331 parser.add_option('-v', '--verbose', dest='verbose', default=False,
333 help='Print verbose debug messages.')
335 (options, args) = parser.parse_args()
337 logging.getLogger().setLevel(logging.DEBUG)
339 logging.getLogger().setLevel(logging.ERROR)
340 logging.info('pnacl_component_crx_gen w/ options %s and args %s\n'
343 # Set destination directory before doing any cleaning, etc.
345 PnaclDirs.SetOutputDir(options.dest)
350 if options.info_template_path:
351 PnaclPackaging.SetPnaclInfoTemplatePath(options.info_template_path)
353 if options.tool_revisions_path:
354 PnaclPackaging.SetToolsRevisionPath(options.tool_revisions_path)
357 for o in options.lib_overrides:
358 arch, override_lib = o.split(',')
359 arch = CanonicalArch(arch)
360 if not IsValidArch(arch):
361 raise Exception('Unknown arch for -L: %s (from %s)' % (arch, o))
362 if not os.path.isfile(override_lib):
363 raise Exception('Override native lib not a file for -L: %s (from %s)' %
365 override_list = lib_overrides.get(arch, [])
366 override_list.append(override_lib)
367 lib_overrides[arch] = override_list
371 parser.error('Incorrect number of arguments')
373 abi_version = int(args[0])
375 arches = DetermineInstallerArches(options.target_arch)
376 BuildInstallerStyle(abi_version, lib_overrides, arches)
380 if __name__ == '__main__':