OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 from __future__ import with_statement | 6 from __future__ import with_statement |
7 | 7 |
8 import errno | 8 import errno |
9 import optparse | 9 import optparse |
10 import os | 10 import os |
(...skipping 11 matching lines...) Expand all Loading... |
22 NeededMatcher = re.compile('^ *NEEDED *([^ ]+)\n$') | 22 NeededMatcher = re.compile('^ *NEEDED *([^ ]+)\n$') |
23 FormatMatcher = re.compile('^(.+):\\s*file format (.+)\n$') | 23 FormatMatcher = re.compile('^(.+):\\s*file format (.+)\n$') |
24 | 24 |
25 FORMAT_ARCH_MAP = { | 25 FORMAT_ARCH_MAP = { |
26 # Names returned by Linux's objdump: | 26 # Names returned by Linux's objdump: |
27 'elf64-x86-64': 'x86-64', | 27 'elf64-x86-64': 'x86-64', |
28 'elf32-i386': 'x86-32', | 28 'elf32-i386': 'x86-32', |
29 # Names returned by x86_64-nacl-objdump: | 29 # Names returned by x86_64-nacl-objdump: |
30 'elf64-nacl': 'x86-64', | 30 'elf64-nacl': 'x86-64', |
31 'elf32-nacl': 'x86-32', | 31 'elf32-nacl': 'x86-32', |
32 } | 32 } |
33 | 33 |
34 ARCH_LOCATION = { | 34 ARCH_LOCATION = { |
35 'x86-32': 'lib32', | 35 'x86-32': 'lib32', |
36 'x86-64': 'lib64', | 36 'x86-64': 'lib64', |
37 } | 37 } |
38 | 38 |
39 NAME_ARCH_MAP = { | 39 NAME_ARCH_MAP = { |
40 '32.nexe': 'x86-32', | 40 '32.nexe': 'x86-32', |
41 '64.nexe': 'x86-64', | 41 '64.nexe': 'x86-64', |
42 'arm.nexe': 'arm' | 42 'arm.nexe': 'arm' |
43 } | 43 } |
44 | 44 |
45 # These constants are used within nmf files. | 45 # These constants are used within nmf files. |
46 RUNNABLE_LD = 'runnable-ld.so' # Name of the dynamic loader | 46 RUNNABLE_LD = 'runnable-ld.so' # Name of the dynamic loader |
47 MAIN_NEXE = 'main.nexe' # Name of entry point for execution | 47 MAIN_NEXE = 'main.nexe' # Name of entry point for execution |
48 PROGRAM_KEY = 'program' # Key of the program section in an nmf file | 48 PROGRAM_KEY = 'program' # Key of the program section in an nmf file |
49 URL_KEY = 'url' # Key of the url field for a particular file in an nmf file | 49 URL_KEY = 'url' # Key of the url field for a particular file in an nmf file |
50 FILES_KEY = 'files' # Key of the files section in an nmf file | 50 FILES_KEY = 'files' # Key of the files section in an nmf file |
51 | 51 |
52 # The proper name of the dynamic linker, as kept in the IRT. This is | 52 # The proper name of the dynamic linker, as kept in the IRT. This is |
53 # excluded from the nmf file by convention. | 53 # excluded from the nmf file by convention. |
54 LD_NACL_MAP = { | 54 LD_NACL_MAP = { |
55 'x86-32': 'ld-nacl-x86-32.so.1', | 55 'x86-32': 'ld-nacl-x86-32.so.1', |
56 'x86-64': 'ld-nacl-x86-64.so.1', | 56 'x86-64': 'ld-nacl-x86-64.so.1', |
57 } | 57 } |
58 | 58 |
59 _debug_mode = False # Set to True to enable extra debug prints | 59 |
| 60 def DebugPrint(message): |
| 61 if DebugPrint.debug_mode: |
| 62 sys.stderr.write('%s\n' % message) |
60 | 63 |
61 | 64 |
62 def DebugPrint(message): | 65 DebugPrint.debug_mode = False # Set to True to enable extra debug prints |
63 if _debug_mode: | 66 |
64 sys.stderr.write('%s\n' % message) | 67 |
65 sys.stderr.flush() | 68 def MakeDir(dirname): |
| 69 """Just like os.makedirs but doesn't generate errors when dirname |
| 70 already exists. |
| 71 """ |
| 72 if os.path.isdir(dirname): |
| 73 return |
| 74 |
| 75 Trace("mkdir: %s" % dirname) |
| 76 try: |
| 77 os.makedirs(dirname) |
| 78 except OSError as exception_info: |
| 79 if exception_info.errno != errno.EEXIST: |
| 80 raise |
66 | 81 |
67 | 82 |
68 class Error(Exception): | 83 class Error(Exception): |
69 '''Local Error class for this file.''' | 84 '''Local Error class for this file.''' |
70 pass | 85 pass |
71 | 86 |
72 | 87 |
73 class ArchFile(object): | 88 class ArchFile(object): |
74 '''Simple structure containing information about | 89 '''Simple structure containing information about |
75 | 90 |
76 Attributes: | 91 Attributes: |
77 arch: Architecture of this file (e.g., x86-32) | 92 arch: Architecture of this file (e.g., x86-32) |
78 filename: name of this file | 93 filename: name of this file |
79 path: Full path to this file on the build system | 94 path: Full path to this file on the build system |
80 url: Relative path to file in the staged web directory. | 95 url: Relative path to file in the staged web directory. |
81 Used for specifying the "url" attribute in the nmf file.''' | 96 Used for specifying the "url" attribute in the nmf file.''' |
82 def __init__(self, arch, name, path='', url=None): | 97 def __init__(self, arch, name, path='', url=None): |
83 self.arch = arch | 98 self.arch = arch |
84 self.name = name | 99 self.name = name |
85 self.path = path | 100 self.path = path |
86 self.url = url or '/'.join([arch, name]) | 101 self.url = url or '/'.join([arch, name]) |
87 | 102 |
| 103 def __repr__(self): |
| 104 return "<ArchFile %s>" % self.path |
| 105 |
88 def __str__(self): | 106 def __str__(self): |
89 '''Return the file path when invoked with the str() function''' | 107 '''Return the file path when invoked with the str() function''' |
90 return self.path | 108 return self.path |
91 | 109 |
92 | 110 |
93 class NmfUtils(object): | 111 class NmfUtils(object): |
94 '''Helper class for creating and managing nmf files | 112 '''Helper class for creating and managing nmf files |
95 | 113 |
96 Attributes: | 114 Attributes: |
97 manifest: A JSON-structured dict containing the nmf structure | 115 manifest: A JSON-structured dict containing the nmf structure |
98 needed: A dict with key=filename and value=ArchFile (see GetNeeded) | 116 needed: A dict with key=filename and value=ArchFile (see GetNeeded) |
99 ''' | 117 ''' |
100 | 118 |
101 def __init__(self, main_files=None, objdump='x86_64-nacl-objdump', | 119 def __init__(self, main_files=None, objdump='x86_64-nacl-objdump', |
102 lib_path=None, extra_files=None, lib_prefix=None, | 120 lib_path=None, extra_files=None, lib_prefix=None, |
103 toolchain=None, remap={}): | 121 toolchain=None, remap={}): |
104 ''' Constructor | 122 '''Constructor |
105 | 123 |
106 Args: | 124 Args: |
107 main_files: List of main entry program files. These will be named | 125 main_files: List of main entry program files. These will be named |
108 files->main.nexe for dynamic nexes, and program for static nexes | 126 files->main.nexe for dynamic nexes, and program for static nexes |
109 objdump: path to x86_64-nacl-objdump tool (or Linux equivalent) | 127 objdump: path to x86_64-nacl-objdump tool (or Linux equivalent) |
110 lib_path: List of paths to library directories | 128 lib_path: List of paths to library directories |
111 extra_files: List of extra files to include in the nmf | 129 extra_files: List of extra files to include in the nmf |
112 lib_prefix: A list of path components to prepend to the library paths, | 130 lib_prefix: A list of path components to prepend to the library paths, |
113 both for staging the libraries and for inclusion into the nmf file. | 131 both for staging the libraries and for inclusion into the nmf file. |
114 Examples: ['..'], ['lib_dir'] | 132 Examples: ['..'], ['lib_dir'] |
(...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
199 Returns: | 217 Returns: |
200 A dict with key=filename and value=ArchFile of input files. | 218 A dict with key=filename and value=ArchFile of input files. |
201 Includes the input files as well, with arch filled in if absent. | 219 Includes the input files as well, with arch filled in if absent. |
202 Example: { '/path/to/my.nexe': ArchFile(my.nexe), | 220 Example: { '/path/to/my.nexe': ArchFile(my.nexe), |
203 '/path/to/libfoo.so': ArchFile(libfoo.so) }''' | 221 '/path/to/libfoo.so': ArchFile(libfoo.so) }''' |
204 if self.needed: | 222 if self.needed: |
205 return self.needed | 223 return self.needed |
206 | 224 |
207 runnable = (self.toolchain != 'newlib' and self.toolchain != 'pnacl') | 225 runnable = (self.toolchain != 'newlib' and self.toolchain != 'pnacl') |
208 DebugPrint('GetNeeded(%s)' % self.main_files) | 226 DebugPrint('GetNeeded(%s)' % self.main_files) |
209 if runnable: | 227 if runnable: |
210 examined = set() | 228 examined = set() |
211 all_files, unexamined = self.GleanFromObjdump( | 229 all_files, unexamined = self.GleanFromObjdump( |
212 dict([(file, None) for file in self.main_files])) | 230 dict([(file, None) for file in self.main_files])) |
213 for name, arch_file in all_files.items(): | 231 for name, arch_file in all_files.items(): |
214 arch_file.url = name | 232 arch_file.url = name |
215 if unexamined: | 233 if unexamined: |
216 unexamined.add('/'.join([arch_file.arch, RUNNABLE_LD])) | 234 unexamined.add('/'.join([arch_file.arch, RUNNABLE_LD])) |
217 while unexamined: | 235 while unexamined: |
218 files_to_examine = {} | 236 files_to_examine = {} |
219 for arch_name in unexamined: | 237 for arch_name in unexamined: |
(...skipping 12 matching lines...) Expand all Loading... |
232 del all_files[name] | 250 del all_files[name] |
233 self.needed = all_files | 251 self.needed = all_files |
234 else: | 252 else: |
235 need = {} | 253 need = {} |
236 for filename in self.main_files: | 254 for filename in self.main_files: |
237 arch = filename.split('_')[-1] | 255 arch = filename.split('_')[-1] |
238 arch = NAME_ARCH_MAP[arch] | 256 arch = NAME_ARCH_MAP[arch] |
239 url = os.path.split(filename)[1] | 257 url = os.path.split(filename)[1] |
240 need[filename] = ArchFile(arch=arch, name=os.path.basename(filename), | 258 need[filename] = ArchFile(arch=arch, name=os.path.basename(filename), |
241 path=filename, url=url) | 259 path=filename, url=url) |
242 self.needed = need | 260 self.needed = need |
| 261 |
243 return self.needed | 262 return self.needed |
244 | 263 |
245 def StageDependencies(self, destination_dir): | 264 def StageDependencies(self, destination_dir): |
246 '''Copies over the dependencies into a given destination directory | 265 '''Copies over the dependencies into a given destination directory |
247 | 266 |
248 Each library will be put into a subdirectory that corresponds to the arch. | 267 Each library will be put into a subdirectory that corresponds to the arch. |
249 | 268 |
250 Args: | 269 Args: |
251 destination_dir: The destination directory for staging the dependencies | 270 destination_dir: The destination directory for staging the dependencies |
252 ''' | 271 ''' |
253 needed = self.GetNeeded() | 272 needed = self.GetNeeded() |
254 for source, arch_file in needed.items(): | 273 for source, arch_file in needed.items(): |
255 destination = os.path.join(destination_dir, | 274 urldest = urllib.url2pathname(arch_file.url) |
256 urllib.url2pathname(arch_file.url)) | 275 if source.endswith('.nexe') and source in self.main_files: |
257 try: | 276 urldest = os.path.basename(urldest) |
258 os.makedirs(os.path.dirname(destination)) | 277 |
259 except OSError as exception_info: | 278 destination = os.path.join(destination_dir, urldest) |
260 if exception_info.errno != errno.EEXIST: | 279 |
261 raise | |
262 if (os.path.normcase(os.path.abspath(source)) != | 280 if (os.path.normcase(os.path.abspath(source)) != |
263 os.path.normcase(os.path.abspath(destination))): | 281 os.path.normcase(os.path.abspath(destination))): |
| 282 # make sure target dir exists |
| 283 MakeDir(os.path.dirname(destination)) |
| 284 |
| 285 Trace("copy: %s -> %s" % (source, destination)) |
264 shutil.copy2(source, destination) | 286 shutil.copy2(source, destination) |
265 | 287 |
266 def _GenerateManifest(self): | 288 def _GenerateManifest(self): |
267 '''Create a JSON formatted dict containing the files | 289 '''Create a JSON formatted dict containing the files |
268 | 290 |
269 NaCl will map url requests based on architecture. The startup NEXE | 291 NaCl will map url requests based on architecture. The startup NEXE |
270 can always be found under the top key PROGRAM. Additional files are under | 292 can always be found under the top key PROGRAM. Additional files are under |
271 the FILES key further mapped by file name. In the case of 'runnable' the | 293 the FILES key further mapped by file name. In the case of 'runnable' the |
272 PROGRAM key is populated with urls pointing the runnable-ld.so which acts | 294 PROGRAM key is populated with urls pointing the runnable-ld.so which acts |
273 as the startup nexe. The application itself, is then placed under the | 295 as the startup nexe. The application itself, is then placed under the |
274 FILES key mapped as 'main.exe' instead of it's original name so that the | 296 FILES key mapped as 'main.exe' instead of it's original name so that the |
275 loader can find it.''' | 297 loader can find it.''' |
276 manifest = { FILES_KEY: {}, PROGRAM_KEY: {} } | 298 manifest = { FILES_KEY: {}, PROGRAM_KEY: {} } |
277 runnable = (self.toolchain != 'newlib' and self.toolchain != 'pnacl') | 299 runnable = (self.toolchain != 'newlib' and self.toolchain != 'pnacl') |
278 | 300 |
279 needed = self.GetNeeded() | 301 needed = self.GetNeeded() |
280 for need in needed: | 302 for need, archinfo in needed.items(): |
281 archinfo = needed[need] | |
282 urlinfo = { URL_KEY: archinfo.url } | 303 urlinfo = { URL_KEY: archinfo.url } |
283 name = archinfo.name | 304 name = archinfo.name |
284 | 305 |
285 # If starting with runnable-ld.so, make that the main executable. | 306 # If starting with runnable-ld.so, make that the main executable. |
286 if runnable: | 307 if runnable: |
287 if need.endswith(RUNNABLE_LD): | 308 if need.endswith(RUNNABLE_LD): |
288 manifest[PROGRAM_KEY][archinfo.arch] = urlinfo | 309 manifest[PROGRAM_KEY][archinfo.arch] = urlinfo |
289 continue | 310 continue |
290 | 311 |
291 # For the main nexes: | 312 # For the main nexes: |
(...skipping 24 matching lines...) Expand all Loading... |
316 | 337 |
317 def GetJson(self): | 338 def GetJson(self): |
318 '''Returns the Manifest as a JSON-formatted string''' | 339 '''Returns the Manifest as a JSON-formatted string''' |
319 pretty_string = json.dumps(self.GetManifest(), indent=2) | 340 pretty_string = json.dumps(self.GetManifest(), indent=2) |
320 # json.dumps sometimes returns trailing whitespace and does not put | 341 # json.dumps sometimes returns trailing whitespace and does not put |
321 # a newline at the end. This code fixes these problems. | 342 # a newline at the end. This code fixes these problems. |
322 pretty_lines = pretty_string.split('\n') | 343 pretty_lines = pretty_string.split('\n') |
323 return '\n'.join([line.rstrip() for line in pretty_lines]) + '\n' | 344 return '\n'.join([line.rstrip() for line in pretty_lines]) + '\n' |
324 | 345 |
325 | 346 |
326 def ErrorOut(text): | |
327 sys.stderr.write(text + '\n') | |
328 sys.exit(1) | |
329 | |
330 | |
331 def DetermineToolchain(objdump): | 347 def DetermineToolchain(objdump): |
332 objdump = objdump.replace('\\', '/') | 348 objdump = objdump.replace('\\', '/') |
333 paths = objdump.split('/') | 349 paths = objdump.split('/') |
334 count = len(paths) | 350 count = len(paths) |
335 for index in range(count - 2, 0, -1): | 351 for index in range(count - 2, 0, -1): |
336 if paths[index] == 'toolchain': | 352 if paths[index] == 'toolchain': |
337 if paths[index + 1].endswith('newlib'): | 353 if paths[index + 1].endswith('newlib'): |
338 return 'newlib' | 354 return 'newlib' |
339 if paths[index + 1].endswith('glibc'): | 355 if paths[index + 1].endswith('glibc'): |
340 return 'glibc' | 356 return 'glibc' |
341 ErrorOut('Could not deternime which toolchain to use.') | 357 raise Error('Could not deternime which toolchain to use.') |
| 358 |
| 359 |
| 360 def Trace(msg): |
| 361 if Trace.verbose: |
| 362 sys.stderr.write(str(msg) + '\n') |
| 363 |
| 364 Trace.verbose = False |
342 | 365 |
343 | 366 |
344 def Main(argv): | 367 def Main(argv): |
345 parser = optparse.OptionParser( | 368 parser = optparse.OptionParser( |
346 usage='Usage: %prog [options] nexe [extra_libs...]') | 369 usage='Usage: %prog [options] nexe [extra_libs...]') |
347 parser.add_option('-o', '--output', dest='output', | 370 parser.add_option('-o', '--output', dest='output', |
348 help='Write manifest file to FILE (default is stdout)', | 371 help='Write manifest file to FILE (default is stdout)', |
349 metavar='FILE') | 372 metavar='FILE') |
350 parser.add_option('-D', '--objdump', dest='objdump', default='objdump', | 373 parser.add_option('-D', '--objdump', dest='objdump', default='objdump', |
351 help='Use TOOL as the "objdump" tool to run', | 374 help='Use TOOL as the "objdump" tool to run', |
352 metavar='TOOL') | 375 metavar='TOOL') |
353 parser.add_option('-L', '--library-path', dest='lib_path', | 376 parser.add_option('-L', '--library-path', dest='lib_path', |
354 action='append', default=[], | 377 action='append', default=[], |
355 help='Add DIRECTORY to library search path', | 378 help='Add DIRECTORY to library search path', |
356 metavar='DIRECTORY') | 379 metavar='DIRECTORY') |
357 parser.add_option('-s', '--stage-dependencies', dest='stage_dependencies', | 380 parser.add_option('-s', '--stage-dependencies', dest='stage_dependencies', |
358 help='Destination directory for staging libraries', | 381 help='Destination directory for staging libraries', |
359 metavar='DIRECTORY') | 382 metavar='DIRECTORY') |
360 parser.add_option('-r', '--remove', dest='remove', | 383 parser.add_option('-r', '--remove', dest='remove', |
361 help='Remove the prefix from the files.', | 384 help='Remove the prefix from the files.', |
362 metavar='PATH') | 385 metavar='PATH') |
363 parser.add_option('-t', '--toolchain', dest='toolchain', | 386 parser.add_option('-t', '--toolchain', dest='toolchain', |
364 help='Add DIRECTORY to library search path', | 387 help='Add DIRECTORY to library search path', |
365 default=None, metavar='TOOLCHAIN') | 388 default=None, metavar='TOOLCHAIN') |
366 parser.add_option('-n', '--name', dest='name', | 389 parser.add_option('-n', '--name', dest='name', |
367 help='Rename FOO as BAR', | 390 help='Rename FOO as BAR', |
368 action='append', default=[], metavar='FOO,BAR') | 391 action='append', default=[], metavar='FOO,BAR') |
| 392 parser.add_option('-v', '--verbose', |
| 393 help='Verbose output', action='store_true') |
| 394 parser.add_option('-d', '--debug-mode', |
| 395 help='Debug mode', action='store_true') |
369 (options, args) = parser.parse_args(argv) | 396 (options, args) = parser.parse_args(argv) |
370 | 397 if options.verbose: |
| 398 Trace.verbose = True |
| 399 if options.debug_mode: |
| 400 DebugPrint.debug_mode = True |
| 401 |
| 402 if len(args) < 1: |
| 403 raise Error("No nexe files specified. See --help for more info") |
| 404 |
371 if not options.toolchain: | 405 if not options.toolchain: |
372 options.toolchain = DetermineToolchain(os.path.abspath(options.objdump)) | 406 options.toolchain = DetermineToolchain(os.path.abspath(options.objdump)) |
373 | 407 |
374 if options.toolchain not in ['newlib', 'glibc', 'pnacl']: | 408 if options.toolchain not in ['newlib', 'glibc', 'pnacl']: |
375 ErrorOut('Unknown toolchain: ' + str(options.toolchain)) | 409 raise Error('Unknown toolchain: ' + str(options.toolchain)) |
376 | |
377 if len(args) < 1: | |
378 parser.print_usage() | |
379 sys.exit(1) | |
380 | 410 |
381 remap = {} | 411 remap = {} |
382 for ren in options.name: | 412 for ren in options.name: |
383 parts = ren.split(',') | 413 parts = ren.split(',') |
384 if len(parts) != 2: | 414 if len(parts) != 2: |
385 ErrorOut('Expecting --name=<orig_arch.so>,<new_name.so>') | 415 raise Error('Expecting --name=<orig_arch.so>,<new_name.so>') |
386 remap[parts[0]] = parts[1] | 416 remap[parts[0]] = parts[1] |
387 | 417 |
388 nmf = NmfUtils(objdump=options.objdump, | 418 nmf = NmfUtils(objdump=options.objdump, |
389 main_files=args, | 419 main_files=args, |
390 lib_path=options.lib_path, | 420 lib_path=options.lib_path, |
391 toolchain=options.toolchain, | 421 toolchain=options.toolchain, |
392 remap=remap) | 422 remap=remap) |
393 | 423 |
394 manifest = nmf.GetManifest() | 424 manifest = nmf.GetManifest() |
395 if options.output is None: | 425 if options.output is None: |
396 sys.stdout.write(nmf.GetJson()) | 426 sys.stdout.write(nmf.GetJson()) |
397 else: | 427 else: |
398 with open(options.output, 'w') as output: | 428 with open(options.output, 'w') as output: |
399 output.write(nmf.GetJson()) | 429 output.write(nmf.GetJson()) |
400 | 430 |
401 if options.stage_dependencies: | 431 if options.stage_dependencies: |
| 432 Trace("Staging dependencies...") |
402 nmf.StageDependencies(options.stage_dependencies) | 433 nmf.StageDependencies(options.stage_dependencies) |
403 | 434 |
| 435 return 0 |
| 436 |
404 | 437 |
405 # Invoke this file directly for simple testing. | 438 # Invoke this file directly for simple testing. |
406 if __name__ == '__main__': | 439 if __name__ == '__main__': |
407 sys.exit(Main(sys.argv[1:])) | 440 try: |
| 441 rtn = Main(sys.argv[1:]) |
| 442 except Error, e: |
| 443 print "%s: %s" % (os.path.basename(__file__), e) |
| 444 rtn = 1 |
| 445 sys.exit(rtn) |
OLD | NEW |