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