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' |
(...skipping 19 matching lines...) Expand all Loading... | |
62 def DebugPrint(message): | 62 def DebugPrint(message): |
63 if _debug_mode: | 63 if _debug_mode: |
64 sys.stderr.write('%s\n' % message) | 64 sys.stderr.write('%s\n' % message) |
65 sys.stderr.flush() | 65 sys.stderr.flush() |
66 | 66 |
67 | 67 |
68 class Error(Exception): | 68 class Error(Exception): |
69 '''Local Error class for this file.''' | 69 '''Local Error class for this file.''' |
70 pass | 70 pass |
71 | 71 |
72 | |
73 class ArchFile(object): | 72 class ArchFile(object): |
74 '''Simple structure containing information about | 73 '''Simple structure containing information about |
75 | 74 |
76 Attributes: | 75 Attributes: |
77 arch: Architecture of this file (e.g., x86-32) | 76 arch: Architecture of this file (e.g., x86-32) |
78 filename: name of this file | 77 filename: name of this file |
79 path: Full path to this file on the build system | 78 path: Full path to this file on the build system |
80 url: Relative path to file in the staged web directory. | 79 url: Relative path to file in the staged web directory. |
81 Used for specifying the "url" attribute in the nmf file.''' | 80 Used for specifying the "url" attribute in the nmf file.''' |
82 def __init__(self, arch, name, path='', url=None): | 81 def __init__(self, arch, name, path='', url=None): |
83 self.arch = arch | 82 self.arch = arch |
84 self.name = name | 83 self.name = name |
85 self.path = path | 84 self.path = path |
86 self.url = url or '/'.join([arch, name]) | 85 self.url = url or '/'.join([arch, name]) |
87 | 86 |
87 def __repr__(self): | |
88 return "ArchFile: %s" % self.path | |
binji
2012/08/01 04:06:46
repr(foo) is usually "<foo ...>"
Sam Clegg
2012/08/01 17:21:03
Done.
| |
89 | |
88 def __str__(self): | 90 def __str__(self): |
89 '''Return the file path when invoked with the str() function''' | 91 '''Return the file path when invoked with the str() function''' |
90 return self.path | 92 return self.path |
91 | 93 |
92 | 94 |
93 class NmfUtils(object): | 95 class NmfUtils(object): |
94 '''Helper class for creating and managing nmf files | 96 '''Helper class for creating and managing nmf files |
95 | 97 |
96 Attributes: | 98 Attributes: |
97 manifest: A JSON-structured dict containing the nmf structure | 99 manifest: A JSON-structured dict containing the nmf structure |
98 needed: A dict with key=filename and value=ArchFile (see GetNeeded) | 100 needed: A dict with key=filename and value=ArchFile (see GetNeeded) |
99 ''' | 101 ''' |
100 | 102 |
101 def __init__(self, main_files=None, objdump='x86_64-nacl-objdump', | 103 def __init__(self, main_files=None, objdump='x86_64-nacl-objdump', |
102 lib_path=None, extra_files=None, lib_prefix=None, | 104 lib_path=None, extra_files=None, lib_prefix=None, |
103 toolchain=None, remap={}): | 105 toolchain=None, remap={}): |
104 ''' Constructor | 106 '''Constructor |
105 | 107 |
106 Args: | 108 Args: |
107 main_files: List of main entry program files. These will be named | 109 main_files: List of main entry program files. These will be named |
108 files->main.nexe for dynamic nexes, and program for static nexes | 110 files->main.nexe for dynamic nexes, and program for static nexes |
109 objdump: path to x86_64-nacl-objdump tool (or Linux equivalent) | 111 objdump: path to x86_64-nacl-objdump tool (or Linux equivalent) |
110 lib_path: List of paths to library directories | 112 lib_path: List of paths to library directories |
111 extra_files: List of extra files to include in the nmf | 113 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, | 114 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. | 115 both for staging the libraries and for inclusion into the nmf file. |
114 Examples: ['..'], ['lib_dir'] | 116 Examples: ['..'], ['lib_dir'] |
(...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
199 Returns: | 201 Returns: |
200 A dict with key=filename and value=ArchFile of input files. | 202 A dict with key=filename and value=ArchFile of input files. |
201 Includes the input files as well, with arch filled in if absent. | 203 Includes the input files as well, with arch filled in if absent. |
202 Example: { '/path/to/my.nexe': ArchFile(my.nexe), | 204 Example: { '/path/to/my.nexe': ArchFile(my.nexe), |
203 '/path/to/libfoo.so': ArchFile(libfoo.so) }''' | 205 '/path/to/libfoo.so': ArchFile(libfoo.so) }''' |
204 if self.needed: | 206 if self.needed: |
205 return self.needed | 207 return self.needed |
206 | 208 |
207 runnable = (self.toolchain != 'newlib' and self.toolchain != 'pnacl') | 209 runnable = (self.toolchain != 'newlib' and self.toolchain != 'pnacl') |
208 DebugPrint('GetNeeded(%s)' % self.main_files) | 210 DebugPrint('GetNeeded(%s)' % self.main_files) |
209 if runnable: | 211 if runnable: |
210 examined = set() | 212 examined = set() |
211 all_files, unexamined = self.GleanFromObjdump( | 213 all_files, unexamined = self.GleanFromObjdump( |
212 dict([(file, None) for file in self.main_files])) | 214 dict([(file, None) for file in self.main_files])) |
213 for name, arch_file in all_files.items(): | 215 for name, arch_file in all_files.items(): |
214 arch_file.url = name | 216 arch_file.url = name |
215 if unexamined: | 217 if unexamined: |
216 unexamined.add('/'.join([arch_file.arch, RUNNABLE_LD])) | 218 unexamined.add('/'.join([arch_file.arch, RUNNABLE_LD])) |
217 while unexamined: | 219 while unexamined: |
218 files_to_examine = {} | 220 files_to_examine = {} |
219 for arch_name in unexamined: | 221 for arch_name in unexamined: |
(...skipping 12 matching lines...) Expand all Loading... | |
232 del all_files[name] | 234 del all_files[name] |
233 self.needed = all_files | 235 self.needed = all_files |
234 else: | 236 else: |
235 need = {} | 237 need = {} |
236 for filename in self.main_files: | 238 for filename in self.main_files: |
237 arch = filename.split('_')[-1] | 239 arch = filename.split('_')[-1] |
238 arch = NAME_ARCH_MAP[arch] | 240 arch = NAME_ARCH_MAP[arch] |
239 url = os.path.split(filename)[1] | 241 url = os.path.split(filename)[1] |
240 need[filename] = ArchFile(arch=arch, name=os.path.basename(filename), | 242 need[filename] = ArchFile(arch=arch, name=os.path.basename(filename), |
241 path=filename, url=url) | 243 path=filename, url=url) |
242 self.needed = need | 244 self.needed = need |
245 | |
243 return self.needed | 246 return self.needed |
244 | 247 |
245 def StageDependencies(self, destination_dir): | 248 def StageDependencies(self, destination_dir): |
246 '''Copies over the dependencies into a given destination directory | 249 '''Copies over the dependencies into a given destination directory |
247 | 250 |
248 Each library will be put into a subdirectory that corresponds to the arch. | 251 Each library will be put into a subdirectory that corresponds to the arch. |
249 | 252 |
250 Args: | 253 Args: |
251 destination_dir: The destination directory for staging the dependencies | 254 destination_dir: The destination directory for staging the dependencies |
252 ''' | 255 ''' |
253 needed = self.GetNeeded() | 256 needed = self.GetNeeded() |
254 for source, arch_file in needed.items(): | 257 for source, arch_file in needed.items(): |
255 destination = os.path.join(destination_dir, | 258 urldest = urllib.url2pathname(arch_file.url) |
256 urllib.url2pathname(arch_file.url)) | 259 if source.endswith('.nexe') and source in self.main_files: |
binji
2012/08/01 04:06:46
I don't think we should make any determination bas
Sam Clegg
2012/08/01 17:21:03
I'm only mimicking what _GenerateManifest does. T
binji
2012/08/01 18:05:20
ok
| |
257 try: | 260 urldest = os.path.basename(urldest) |
258 os.makedirs(os.path.dirname(destination)) | 261 |
259 except OSError as exception_info: | 262 destination = os.path.join(destination_dir, urldest) |
260 if exception_info.errno != errno.EEXIST: | 263 |
261 raise | 264 # make sure target dir exists |
265 dirname = os.path.dirname(destination) | |
266 if not os.path.exists(dirname): | |
binji
2012/08/01 04:06:46
IMO, catching the exception is better. Testing for
binji
2012/08/01 18:05:20
opinion on this?
Sam Clegg
2012/08/01 18:26:39
Well.. it pretty much guarantees that it won't fai
| |
267 Trace("mkdir: %s" % dirname) | |
268 os.makedirs(dirname) | |
269 | |
262 if (os.path.normcase(os.path.abspath(source)) != | 270 if (os.path.normcase(os.path.abspath(source)) != |
263 os.path.normcase(os.path.abspath(destination))): | 271 os.path.normcase(os.path.abspath(destination))): |
272 Trace("copy: %s -> %s" % (source, destination)) | |
264 shutil.copy2(source, destination) | 273 shutil.copy2(source, destination) |
265 | 274 |
266 def _GenerateManifest(self): | 275 def _GenerateManifest(self): |
267 '''Create a JSON formatted dict containing the files | 276 '''Create a JSON formatted dict containing the files |
268 | 277 |
269 NaCl will map url requests based on architecture. The startup NEXE | 278 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 | 279 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 | 280 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 | 281 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 | 282 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 | 283 FILES key mapped as 'main.exe' instead of it's original name so that the |
275 loader can find it.''' | 284 loader can find it.''' |
276 manifest = { FILES_KEY: {}, PROGRAM_KEY: {} } | 285 manifest = { FILES_KEY: {}, PROGRAM_KEY: {} } |
277 runnable = (self.toolchain != 'newlib' and self.toolchain != 'pnacl') | 286 runnable = (self.toolchain != 'newlib' and self.toolchain != 'pnacl') |
278 | 287 |
279 needed = self.GetNeeded() | 288 needed = self.GetNeeded() |
280 for need in needed: | 289 for need, archinfo in needed.items(): |
281 archinfo = needed[need] | |
282 urlinfo = { URL_KEY: archinfo.url } | 290 urlinfo = { URL_KEY: archinfo.url } |
283 name = archinfo.name | 291 name = archinfo.name |
284 | 292 |
285 # If starting with runnable-ld.so, make that the main executable. | 293 # If starting with runnable-ld.so, make that the main executable. |
286 if runnable: | 294 if runnable: |
287 if need.endswith(RUNNABLE_LD): | 295 if need.endswith(RUNNABLE_LD): |
288 manifest[PROGRAM_KEY][archinfo.arch] = urlinfo | 296 manifest[PROGRAM_KEY][archinfo.arch] = urlinfo |
289 continue | 297 continue |
290 | 298 |
291 # For the main nexes: | 299 # For the main nexes: |
(...skipping 24 matching lines...) Expand all Loading... | |
316 | 324 |
317 def GetJson(self): | 325 def GetJson(self): |
318 '''Returns the Manifest as a JSON-formatted string''' | 326 '''Returns the Manifest as a JSON-formatted string''' |
319 pretty_string = json.dumps(self.GetManifest(), indent=2) | 327 pretty_string = json.dumps(self.GetManifest(), indent=2) |
320 # json.dumps sometimes returns trailing whitespace and does not put | 328 # json.dumps sometimes returns trailing whitespace and does not put |
321 # a newline at the end. This code fixes these problems. | 329 # a newline at the end. This code fixes these problems. |
322 pretty_lines = pretty_string.split('\n') | 330 pretty_lines = pretty_string.split('\n') |
323 return '\n'.join([line.rstrip() for line in pretty_lines]) + '\n' | 331 return '\n'.join([line.rstrip() for line in pretty_lines]) + '\n' |
324 | 332 |
325 | 333 |
326 def ErrorOut(text): | |
327 sys.stderr.write(text + '\n') | |
328 sys.exit(1) | |
329 | |
330 | |
331 def DetermineToolchain(objdump): | 334 def DetermineToolchain(objdump): |
332 objdump = objdump.replace('\\', '/') | 335 objdump = objdump.replace('\\', '/') |
333 paths = objdump.split('/') | 336 paths = objdump.split('/') |
334 count = len(paths) | 337 count = len(paths) |
335 for index in range(count - 2, 0, -1): | 338 for index in range(count - 2, 0, -1): |
336 if paths[index] == 'toolchain': | 339 if paths[index] == 'toolchain': |
337 if paths[index + 1].endswith('newlib'): | 340 if paths[index + 1].endswith('newlib'): |
338 return 'newlib' | 341 return 'newlib' |
339 if paths[index + 1].endswith('glibc'): | 342 if paths[index + 1].endswith('glibc'): |
340 return 'glibc' | 343 return 'glibc' |
341 ErrorOut('Could not deternime which toolchain to use.') | 344 raise Error('Could not deternime which toolchain to use.') |
342 | 345 |
binji
2012/08/01 04:06:46
style: Two newlines between top-level definitions.
Sam Clegg
2012/08/01 17:21:03
Done.
| |
346 def Trace(msg): | |
347 if Trace.verbose: | |
348 sys.stderr.write(str(msg) + '\n') | |
349 | |
350 Trace.verbose = False | |
343 | 351 |
344 def Main(argv): | 352 def Main(argv): |
345 parser = optparse.OptionParser( | 353 parser = optparse.OptionParser( |
346 usage='Usage: %prog [options] nexe [extra_libs...]') | 354 usage='Usage: %prog [options] nexe [extra_libs...]') |
347 parser.add_option('-o', '--output', dest='output', | 355 parser.add_option('-o', '--output', dest='output', |
348 help='Write manifest file to FILE (default is stdout)', | 356 help='Write manifest file to FILE (default is stdout)', |
349 metavar='FILE') | 357 metavar='FILE') |
350 parser.add_option('-D', '--objdump', dest='objdump', default='objdump', | 358 parser.add_option('-D', '--objdump', dest='objdump', default='objdump', |
351 help='Use TOOL as the "objdump" tool to run', | 359 help='Use TOOL as the "objdump" tool to run', |
352 metavar='TOOL') | 360 metavar='TOOL') |
353 parser.add_option('-L', '--library-path', dest='lib_path', | 361 parser.add_option('-L', '--library-path', dest='lib_path', |
354 action='append', default=[], | 362 action='append', default=[], |
355 help='Add DIRECTORY to library search path', | 363 help='Add DIRECTORY to library search path', |
356 metavar='DIRECTORY') | 364 metavar='DIRECTORY') |
357 parser.add_option('-s', '--stage-dependencies', dest='stage_dependencies', | 365 parser.add_option('-s', '--stage-dependencies', dest='stage_dependencies', |
358 help='Destination directory for staging libraries', | 366 help='Destination directory for staging libraries', |
359 metavar='DIRECTORY') | 367 metavar='DIRECTORY') |
360 parser.add_option('-r', '--remove', dest='remove', | 368 parser.add_option('-r', '--remove', dest='remove', |
361 help='Remove the prefix from the files.', | 369 help='Remove the prefix from the files.', |
362 metavar='PATH') | 370 metavar='PATH') |
363 parser.add_option('-t', '--toolchain', dest='toolchain', | 371 parser.add_option('-t', '--toolchain', dest='toolchain', |
364 help='Add DIRECTORY to library search path', | 372 help='Add DIRECTORY to library search path', |
365 default=None, metavar='TOOLCHAIN') | 373 default=None, metavar='TOOLCHAIN') |
366 parser.add_option('-n', '--name', dest='name', | 374 parser.add_option('-n', '--name', dest='name', |
367 help='Rename FOO as BAR', | 375 help='Rename FOO as BAR', |
368 action='append', default=[], metavar='FOO,BAR') | 376 action='append', default=[], metavar='FOO,BAR') |
377 parser.add_option('-v', '--verbose', | |
378 help='Verbose output', action='store_true') | |
369 (options, args) = parser.parse_args(argv) | 379 (options, args) = parser.parse_args(argv) |
370 | 380 if options.verbose: |
381 Trace.verbose = True | |
382 | |
383 if len(args) < 1: | |
384 raise Error("No nexe files specified. See --help for more info") | |
binji
2012/08/01 04:06:46
maybe just display help in this case?
Sam Clegg
2012/08/01 17:21:03
The convention seems to be to tell the user what h
binji
2012/08/01 18:05:20
ok
| |
385 | |
386 remap = {} | |
binji
2012/08/01 04:06:46
I prefer initialization closer to first use.
Sam Clegg
2012/08/01 17:21:03
Ooops. Didn't mean to move that.
Done.
| |
371 if not options.toolchain: | 387 if not options.toolchain: |
372 options.toolchain = DetermineToolchain(os.path.abspath(options.objdump)) | 388 options.toolchain = DetermineToolchain(os.path.abspath(options.objdump)) |
373 | 389 |
374 if options.toolchain not in ['newlib', 'glibc', 'pnacl']: | 390 if options.toolchain not in ['newlib', 'glibc', 'pnacl']: |
375 ErrorOut('Unknown toolchain: ' + str(options.toolchain)) | 391 raise Error('Unknown toolchain: ' + str(options.toolchain)) |
376 | 392 |
377 if len(args) < 1: | |
378 parser.print_usage() | |
379 sys.exit(1) | |
380 | |
381 remap = {} | |
382 for ren in options.name: | 393 for ren in options.name: |
383 parts = ren.split(',') | 394 parts = ren.split(',') |
384 if len(parts) != 2: | 395 if len(parts) != 2: |
385 ErrorOut('Expecting --name=<orig_arch.so>,<new_name.so>') | 396 raise Error('Expecting --name=<orig_arch.so>,<new_name.so>') |
386 remap[parts[0]] = parts[1] | 397 remap[parts[0]] = parts[1] |
387 | 398 |
388 nmf = NmfUtils(objdump=options.objdump, | 399 nmf = NmfUtils(objdump=options.objdump, |
389 main_files=args, | 400 main_files=args, |
390 lib_path=options.lib_path, | 401 lib_path=options.lib_path, |
391 toolchain=options.toolchain, | 402 toolchain=options.toolchain, |
392 remap=remap) | 403 remap=remap) |
393 | 404 |
394 manifest = nmf.GetManifest() | 405 manifest = nmf.GetManifest() |
395 if options.output is None: | 406 if options.output is None: |
396 sys.stdout.write(nmf.GetJson()) | 407 sys.stdout.write(nmf.GetJson()) |
397 else: | 408 else: |
398 with open(options.output, 'w') as output: | 409 with open(options.output, 'w') as output: |
399 output.write(nmf.GetJson()) | 410 output.write(nmf.GetJson()) |
400 | 411 |
401 if options.stage_dependencies: | 412 if options.stage_dependencies: |
413 Trace("Staging dependancies...") | |
binji
2012/08/01 04:06:46
sp: dependencies
Sam Clegg
2012/08/01 17:21:03
Done.
| |
402 nmf.StageDependencies(options.stage_dependencies) | 414 nmf.StageDependencies(options.stage_dependencies) |
403 | 415 |
416 return 0 | |
417 | |
404 | 418 |
405 # Invoke this file directly for simple testing. | 419 # Invoke this file directly for simple testing. |
406 if __name__ == '__main__': | 420 if __name__ == '__main__': |
407 sys.exit(Main(sys.argv[1:])) | 421 try: |
422 rtn = Main(sys.argv[1:]) | |
423 except Error, e: | |
binji
2012/08/01 04:06:46
How does this output differ from the default outpu
Sam Clegg
2012/08/01 17:21:03
When an Error (local class) is thrown it represent
binji
2012/08/01 18:05:20
sgtm
| |
424 print "%s: %s" % (os.path.basename(__file__), e) | |
425 rtn = 1 | |
426 sys.exit(rtn) | |
OLD | NEW |