- add sources.
[platform/framework/web/crosswalk.git] / src / ppapi / native_client / src / untrusted / pnacl_support_extension / pnacl_component_crx_gen.py
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 toolchain directory (so that must be downloaded first) and
12    it depends on the pnacl_irt_shim.
13 """
14
15 import json
16 import logging
17 import optparse
18 import os
19 import platform
20 import re
21 import shutil
22 import sys
23
24 J = 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
35 def 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 re.match('^i.86$', arch) or arch in ('x86_32', 'x86-32', 'ia32', 'x86'):
42     return 'x86-32'
43   return None
44
45 def GetBuildArch():
46   arch = platform.machine()
47   return CanonicalArch(arch)
48
49 BUILD_ARCH = GetBuildArch()
50 ARCHES = ['x86-32', 'x86-64', 'arm']
51
52 def IsValidArch(arch):
53   return arch in ARCHES
54
55 # The version of the arch used by configure and pnacl's build.sh.
56 def StandardArch(arch):
57   return {'x86-32': 'i686',
58           'x86-64': 'x86_64',
59           'arm'   : 'armv7'}[arch]
60
61
62 ######################################################################
63
64 def GetNaClRoot():
65   """ Find the native_client path, relative to this script.
66       This script is in ppapi/... and native_client is a sibling of ppapi.
67   """
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' %
75                         script_file)
76       return sibling
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' %
81                       script_file)
82     return SearchForNaCl(next_dir)
83
84   return SearchForNaCl(script_file)
85
86
87 NACL_ROOT = GetNaClRoot()
88
89
90 ######################################################################
91
92 # Normalize the platform name to be the way SCons finds chrome binaries.
93 # This is based on the platform "building" the extension.
94
95 def GetBuildPlatform():
96   if sys.platform == 'darwin':
97     platform = 'mac'
98   elif sys.platform.startswith('linux'):
99     platform = 'linux'
100   elif sys.platform in ('cygwin', 'win32'):
101     platform = 'windows'
102   else:
103     raise Exception('Unknown platform: %s' % sys.platform)
104   return platform
105 BUILD_PLATFORM = GetBuildPlatform()
106
107
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']
116     else:
117       raise Exception('Unknown target_arch on windows w/ target_arch == %s' %
118                       target_arch)
119   else:
120     return [arch]
121
122
123 ######################################################################
124
125 class PnaclPackaging(object):
126
127   package_base = os.path.dirname(__file__)
128
129   # File paths that are set from the command line.
130   pnacl_template = None
131   tool_revisions = None
132
133   # Agreed-upon name for pnacl-specific info.
134   pnacl_json = 'pnacl.json'
135
136   @staticmethod
137   def SetPnaclInfoTemplatePath(path):
138     PnaclPackaging.pnacl_template = path
139
140   @staticmethod
141   def SetToolsRevisionPath(path):
142     PnaclPackaging.tool_revisions = path
143
144   @staticmethod
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)
160
161   @staticmethod
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
165     # actually changes.
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,
170                                                    None))
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)
175
176
177 ######################################################################
178
179 class PnaclDirs(object):
180   toolchain_dir = J(NACL_ROOT, 'toolchain')
181   output_dir = J(toolchain_dir, 'pnacl-package')
182
183   @staticmethod
184   def TranslatorRoot():
185     return J(PnaclDirs.toolchain_dir, 'pnacl_translator')
186
187   @staticmethod
188   def LibDir(target_arch):
189     return J(PnaclDirs.TranslatorRoot(), 'lib-%s' % target_arch)
190
191   @staticmethod
192   def SandboxedCompilerDir(target_arch):
193     return J(PnaclDirs.toolchain_dir,
194              'pnacl_translator', StandardArch(target_arch), 'bin')
195
196   @staticmethod
197   def SetOutputDir(d):
198     PnaclDirs.output_dir = d
199
200   @staticmethod
201   def OutputDir():
202     return PnaclDirs.output_dir
203
204   @staticmethod
205   def OutputAllDir(version_quad):
206     return J(PnaclDirs.OutputDir(), version_quad)
207
208   @staticmethod
209   def OutputArchBase(arch):
210     return '%s' % arch
211
212   @staticmethod
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)))
218
219
220 ######################################################################
221
222 def StepBanner(short_desc, long_desc):
223   logging.info("**** %s\t%s", short_desc, long_desc)
224
225
226 def Clean():
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)
231   else:
232     logging.info('Clean skipped -- no previous output directory!')
233
234 ######################################################################
235
236 def UseWhitelistedChars(orig_basename, arch):
237   """ Make the filename match the pattern expected by nacl_file_host.
238
239   Currently, this assumes there is prefix "pnacl_public_" and
240   that the allowed chars are in the set [a-zA-Z0-9_].
241   """
242   if arch:
243     target_basename = 'pnacl_public_%s_%s' % (arch, orig_basename)
244   else:
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)
248   return result
249
250 def CopyFlattenDirsAndPrefix(src_dir, arch, dest_dir):
251   """ Copy files from src_dir to dest_dir.
252
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.
255   """
256   for (root, dirs, files) in os.walk(src_dir, followlinks=True):
257     for f in files:
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))
263
264
265 def BuildArchForInstaller(version_quad, arch, lib_overrides):
266   """ Build an architecture specific version for the chrome installer.
267   """
268   target_dir = PnaclDirs.OutputDir()
269
270   StepBanner('BUILD INSTALLER',
271              'Packaging for arch %s in %s' % (arch, target_dir))
272
273   # Copy llc.nexe and ld.nexe, but with some renaming and directory flattening.
274   CopyFlattenDirsAndPrefix(PnaclDirs.SandboxedCompilerDir(arch),
275                            arch,
276                            target_dir)
277
278   # Copy native libraries, also with renaming and directory flattening.
279   CopyFlattenDirsAndPrefix(PnaclDirs.LibDir(arch), arch, target_dir)
280
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))
288
289
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.
294   """
295   StepBanner("BUILD_ALL", "Packaging installer for version: %s" % version_quad)
296   for arch in arches:
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.
300   if len(arches) == 1:
301     arches = arches[0]
302   PnaclPackaging.GeneratePnaclInfo(PnaclDirs.OutputDir(), version_quad, arches)
303
304
305 ######################################################################
306
307
308 def Main():
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,
332                     action='store_true',
333                     help='Print verbose debug messages.')
334
335   (options, args) = parser.parse_args()
336   if options.verbose:
337     logging.getLogger().setLevel(logging.DEBUG)
338   else:
339     logging.getLogger().setLevel(logging.ERROR)
340   logging.info('pnacl_component_crx_gen w/ options %s and args %s\n'
341                % (options, args))
342
343   # Set destination directory before doing any cleaning, etc.
344   if options.dest:
345     PnaclDirs.SetOutputDir(options.dest)
346
347   if options.clean:
348     Clean()
349
350   if options.info_template_path:
351     PnaclPackaging.SetPnaclInfoTemplatePath(options.info_template_path)
352
353   if options.tool_revisions_path:
354     PnaclPackaging.SetToolsRevisionPath(options.tool_revisions_path)
355
356   lib_overrides = {}
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)' %
364                       (override_lib, o))
365     override_list = lib_overrides.get(arch, [])
366     override_list.append(override_lib)
367     lib_overrides[arch] = override_list
368
369   if len(args) != 1:
370     parser.print_help()
371     parser.error('Incorrect number of arguments')
372
373   abi_version = int(args[0])
374
375   arches = DetermineInstallerArches(options.target_arch)
376   BuildInstallerStyle(abi_version, lib_overrides, arches)
377   return 0
378
379
380 if __name__ == '__main__':
381   sys.exit(Main())