1 #!/usr/bin/env 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 """Tool for automatically creating .nmf files from .nexe/.pexe executables. 7 8 As well as creating the nmf file this tool can also find and stage 9 any shared libraries dependancies that the executables might have. 10 """ 11 12 import errno 13 import json 14 import optparse 15 import os 16 import re 17 import shutil 18 import struct 19 import subprocess 20 import sys 21 22 import getos 23 import quote 24 25 if sys.version_info < (2, 6, 0): 26 sys.stderr.write("python 2.6 or later is required run this script\n") 27 sys.exit(1) 28 29 NeededMatcher = re.compile('^ *NEEDED *([^ ]+)\n$') 30 FormatMatcher = re.compile('^(.+):\\s*file format (.+)\n$') 31 32 SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) 33 34 OBJDUMP_ARCH_MAP = { 35 # Names returned by Linux's objdump: 36 'elf64-x86-64': 'x86-64', 37 'elf32-i386': 'x86-32', 38 'elf32-little': 'arm', 39 'elf32-littlearm': 'arm', 40 # Names returned by old x86_64-nacl-objdump: 41 'elf64-nacl': 'x86-64', 42 'elf32-nacl': 'x86-32', 43 # Names returned by new x86_64-nacl-objdump: 44 'elf64-x86-64-nacl': 'x86-64', 45 'elf32-x86-64-nacl': 'x86-64', 46 'elf32-i386-nacl': 'x86-32', 47 } 48 49 ARCH_LOCATION = { 50 'x86-32': 'lib32', 51 'x86-64': 'lib64', 52 'arm': 'lib', 53 } 54 55 56 # These constants are used within nmf files. 57 RUNNABLE_LD = 'runnable-ld.so' # Name of the dynamic loader 58 MAIN_NEXE = 'main.nexe' # Name of entry point for execution 59 PROGRAM_KEY = 'program' # Key of the program section in an nmf file 60 URL_KEY = 'url' # Key of the url field for a particular file in an nmf file 61 FILES_KEY = 'files' # Key of the files section in an nmf file 62 PNACL_OPTLEVEL_KEY = 'optlevel' # key for PNaCl optimization level 63 PORTABLE_KEY = 'portable' # key for portable section of manifest 64 TRANSLATE_KEY = 'pnacl-translate' # key for translatable objects 65 66 67 # The proper name of the dynamic linker, as kept in the IRT. This is 68 # excluded from the nmf file by convention. 69 LD_NACL_MAP = { 70 'x86-32': 'ld-nacl-x86-32.so.1', 71 'x86-64': 'ld-nacl-x86-64.so.1', 72 'arm': None, 73 } 74 75 76 def DebugPrint(message): 77 if DebugPrint.debug_mode: 78 sys.stderr.write('%s\n' % message) 79 80 81 DebugPrint.debug_mode = False # Set to True to enable extra debug prints 82 83 84 def MakeDir(dirname): 85 """Just like os.makedirs but doesn't generate errors when dirname 86 already exists. 87 """ 88 if os.path.isdir(dirname): 89 return 90 91 Trace("mkdir: %s" % dirname) 92 try: 93 os.makedirs(dirname) 94 except OSError as exception_info: 95 if exception_info.errno != errno.EEXIST: 96 raise 97 98 99 class Error(Exception): 100 '''Local Error class for this file.''' 101 pass 102 103 104 def ParseElfHeader(path): 105 """Determine properties of a nexe by parsing elf header. 106 Return tuple of architecture and boolean signalling whether 107 the executable is dynamic (has INTERP header) or static. 108 """ 109 # From elf.h: 110 # typedef struct 111 # { 112 # unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */ 113 # Elf64_Half e_type; /* Object file type */ 114 # Elf64_Half e_machine; /* Architecture */ 115 # ... 116 # } Elf32_Ehdr; 117 elf_header_format = '16s2H' 118 elf_header_size = struct.calcsize(elf_header_format) 119 120 with open(path, 'rb') as f: 121 header = f.read(elf_header_size) 122 123 try: 124 header = struct.unpack(elf_header_format, header) 125 except struct.error: 126 raise Error("error parsing elf header: %s" % path) 127 e_ident, _, e_machine = header[:3] 128 129 elf_magic = '\x7fELF' 130 if e_ident[:4] != elf_magic: 131 raise Error('Not a valid NaCl executable: %s' % path) 132 133 e_machine_mapping = { 134 3 : 'x86-32', 135 40 : 'arm', 136 62 : 'x86-64' 137 } 138 if e_machine not in e_machine_mapping: 139 raise Error('Unknown machine type: %s' % e_machine) 140 141 # Set arch based on the machine type in the elf header 142 arch = e_machine_mapping[e_machine] 143 144 # Now read the full header in either 64bit or 32bit mode 145 dynamic = IsDynamicElf(path, arch == 'x86-64') 146 return arch, dynamic 147 148 149 def IsDynamicElf(path, is64bit): 150 """Examine an elf file to determine if it is dynamically 151 linked or not. 152 This is determined by searching the program headers for 153 a header of type PT_INTERP. 154 """ 155 if is64bit: 156 elf_header_format = '16s2HI3QI3H' 157 else: 158 elf_header_format = '16s2HI3II3H' 159 160 elf_header_size = struct.calcsize(elf_header_format) 161 162 with open(path, 'rb') as f: 163 header = f.read(elf_header_size) 164 header = struct.unpack(elf_header_format, header) 165 p_header_offset = header[5] 166 p_header_entry_size = header[9] 167 num_p_header = header[10] 168 f.seek(p_header_offset) 169 p_headers = f.read(p_header_entry_size*num_p_header) 170 171 # Read the first word of each Phdr to find out its type. 172 # 173 # typedef struct 174 # { 175 # Elf32_Word p_type; /* Segment type */ 176 # ... 177 # } Elf32_Phdr; 178 elf_phdr_format = 'I' 179 PT_INTERP = 3 180 181 while p_headers: 182 p_header = p_headers[:p_header_entry_size] 183 p_headers = p_headers[p_header_entry_size:] 184 phdr_type = struct.unpack(elf_phdr_format, p_header[:4])[0] 185 if phdr_type == PT_INTERP: 186 return True 187 188 return False 189 190 191 class ArchFile(object): 192 '''Simple structure containing information about 193 194 Attributes: 195 name: Name of this file 196 path: Full path to this file on the build system 197 arch: Architecture of this file (e.g., x86-32) 198 url: Relative path to file in the staged web directory. 199 Used for specifying the "url" attribute in the nmf file.''' 200 201 def __init__(self, name, path, url, arch=None): 202 self.name = name 203 self.path = path 204 self.url = url 205 self.arch = arch 206 if not arch: 207 self.arch = ParseElfHeader(path)[0] 208 209 def __repr__(self): 210 return '<ArchFile %s>' % self.path 211 212 def __str__(self): 213 '''Return the file path when invoked with the str() function''' 214 return self.path 215 216 217 class NmfUtils(object): 218 '''Helper class for creating and managing nmf files 219 220 Attributes: 221 manifest: A JSON-structured dict containing the nmf structure 222 needed: A dict with key=filename and value=ArchFile (see GetNeeded) 223 ''' 224 225 def __init__(self, main_files=None, objdump=None, 226 lib_path=None, extra_files=None, lib_prefix=None, 227 remap=None, pnacl_optlevel=None): 228 '''Constructor 229 230 Args: 231 main_files: List of main entry program files. These will be named 232 files->main.nexe for dynamic nexes, and program for static nexes 233 objdump: path to x86_64-nacl-objdump tool (or Linux equivalent) 234 lib_path: List of paths to library directories 235 extra_files: List of extra files to include in the nmf 236 lib_prefix: A list of path components to prepend to the library paths, 237 both for staging the libraries and for inclusion into the nmf file. 238 Examples: ['..'], ['lib_dir'] 239 remap: Remaps the library name in the manifest. 240 pnacl_optlevel: Optimization level for PNaCl translation. 241 ''' 242 self.objdump = objdump 243 self.main_files = main_files or [] 244 self.extra_files = extra_files or [] 245 self.lib_path = lib_path or [] 246 self.manifest = None 247 self.needed = {} 248 self.lib_prefix = lib_prefix or [] 249 self.remap = remap or {} 250 self.pnacl = main_files and main_files[0].endswith('pexe') 251 self.pnacl_optlevel = pnacl_optlevel 252 253 for filename in self.main_files: 254 if not os.path.exists(filename): 255 raise Error('Input file not found: %s' % filename) 256 if not os.path.isfile(filename): 257 raise Error('Input is not a file: %s' % filename) 258 259 def GleanFromObjdump(self, files, arch): 260 '''Get architecture and dependency information for given files 261 262 Args: 263 files: A list of files to examine. 264 [ '/path/to/my.nexe', 265 '/path/to/lib64/libmy.so', 266 '/path/to/mydata.so', 267 '/path/to/my.data' ] 268 arch: The architecure we are looking for, or None to accept any 269 architecture. 270 271 Returns: A tuple with the following members: 272 input_info: A dict with key=filename and value=ArchFile of input files. 273 Includes the input files as well, with arch filled in if absent. 274 Example: { '/path/to/my.nexe': ArchFile(my.nexe), 275 '/path/to/libfoo.so': ArchFile(libfoo.so) } 276 needed: A set of strings formatted as "arch/name". Example: 277 set(['x86-32/libc.so', 'x86-64/libgcc.so']) 278 ''' 279 if not self.objdump: 280 self.objdump = FindObjdumpExecutable() 281 if not self.objdump: 282 raise Error('No objdump executable found (see --help for more info)') 283 284 full_paths = set() 285 for filename in files: 286 if os.path.exists(filename): 287 full_paths.add(filename) 288 else: 289 for path in self.FindLibsInPath(filename): 290 full_paths.add(path) 291 292 cmd = [self.objdump, '-p'] + list(full_paths) 293 DebugPrint('GleanFromObjdump[%s](%s)' % (arch, cmd)) 294 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, 295 stderr=subprocess.PIPE, bufsize=-1) 296 297 input_info = {} 298 found_basenames = set() 299 needed = set() 300 output, err_output = proc.communicate() 301 if proc.returncode: 302 raise Error('%s\nStdError=%s\nobjdump failed with error code: %d' % 303 (output, err_output, proc.returncode)) 304 305 for line in output.splitlines(True): 306 # Objdump should display the architecture first and then the dependencies 307 # second for each file in the list. 308 matched = FormatMatcher.match(line) 309 if matched: 310 filename = matched.group(1) 311 file_arch = OBJDUMP_ARCH_MAP[matched.group(2)] 312 if arch and file_arch != arch: 313 continue 314 name = os.path.basename(filename) 315 found_basenames.add(name) 316 input_info[filename] = ArchFile( 317 arch=file_arch, 318 name=name, 319 path=filename, 320 url='/'.join(self.lib_prefix + [ARCH_LOCATION[file_arch], name])) 321 matched = NeededMatcher.match(line) 322 if matched: 323 match = '/'.join([file_arch, matched.group(1)]) 324 needed.add(match) 325 Trace("NEEDED: %s" % match) 326 327 for filename in files: 328 if os.path.basename(filename) not in found_basenames: 329 raise Error('Library not found [%s]: %s' % (arch, filename)) 330 331 return input_info, needed 332 333 def FindLibsInPath(self, name): 334 '''Finds the set of libraries matching |name| within lib_path 335 336 Args: 337 name: name of library to find 338 339 Returns: 340 A list of system paths that match the given name within the lib_path''' 341 files = [] 342 for dirname in self.lib_path: 343 filename = os.path.join(dirname, name) 344 if os.path.exists(filename): 345 files.append(filename) 346 if not files: 347 raise Error('cannot find library %s' % name) 348 return files 349 350 def GetNeeded(self): 351 '''Collect the list of dependencies for the main_files 352 353 Returns: 354 A dict with key=filename and value=ArchFile of input files. 355 Includes the input files as well, with arch filled in if absent. 356 Example: { '/path/to/my.nexe': ArchFile(my.nexe), 357 '/path/to/libfoo.so': ArchFile(libfoo.so) }''' 358 359 if self.needed: 360 return self.needed 361 362 DebugPrint('GetNeeded(%s)' % self.main_files) 363 364 dynamic = any(ParseElfHeader(f)[1] for f in self.main_files) 365 366 if dynamic: 367 examined = set() 368 all_files, unexamined = self.GleanFromObjdump(self.main_files, None) 369 for arch_file in all_files.itervalues(): 370 arch_file.url = arch_file.path 371 if unexamined: 372 unexamined.add('/'.join([arch_file.arch, RUNNABLE_LD])) 373 374 while unexamined: 375 files_to_examine = {} 376 377 # Take all the currently unexamined files and group them 378 # by architecture. 379 for arch_name in unexamined: 380 arch, name = arch_name.split('/') 381 files_to_examine.setdefault(arch, []).append(name) 382 383 # Call GleanFromObjdump() for each architecture. 384 needed = set() 385 for arch, files in files_to_examine.iteritems(): 386 new_files, new_needed = self.GleanFromObjdump(files, arch) 387 all_files.update(new_files) 388 needed |= new_needed 389 390 examined |= unexamined 391 unexamined = needed - examined 392 393 # With the runnable-ld.so scheme we have today, the proper name of 394 # the dynamic linker should be excluded from the list of files. 395 ldso = [LD_NACL_MAP[arch] for arch in set(OBJDUMP_ARCH_MAP.values())] 396 for name, arch_file in all_files.items(): 397 if arch_file.name in ldso: 398 del all_files[name] 399 400 self.needed = all_files 401 else: 402 for filename in self.main_files: 403 url = os.path.split(filename)[1] 404 archfile = ArchFile(name=os.path.basename(filename), 405 path=filename, url=url) 406 self.needed[filename] = archfile 407 408 return self.needed 409 410 def StageDependencies(self, destination_dir): 411 '''Copies over the dependencies into a given destination directory 412 413 Each library will be put into a subdirectory that corresponds to the arch. 414 415 Args: 416 destination_dir: The destination directory for staging the dependencies 417 ''' 418 nexe_root = os.path.dirname(os.path.abspath(self.main_files[0])) 419 nexe_root = os.path.normcase(nexe_root) 420 421 needed = self.GetNeeded() 422 for arch_file in needed.itervalues(): 423 urldest = arch_file.url 424 source = arch_file.path 425 426 # for .nexe and .so files specified on the command line stage 427 # them in paths relative to the .nexe (with the .nexe always 428 # being staged at the root). 429 if source in self.main_files: 430 absdest = os.path.normcase(os.path.abspath(urldest)) 431 if absdest.startswith(nexe_root): 432 urldest = os.path.relpath(urldest, nexe_root) 433 434 destination = os.path.join(destination_dir, urldest) 435 436 if (os.path.normcase(os.path.abspath(source)) == 437 os.path.normcase(os.path.abspath(destination))): 438 continue 439 440 # make sure target dir exists 441 MakeDir(os.path.dirname(destination)) 442 443 Trace('copy: %s -> %s' % (source, destination)) 444 shutil.copy2(source, destination) 445 446 def _GeneratePNaClManifest(self): 447 manifest = {} 448 manifest[PROGRAM_KEY] = {} 449 manifest[PROGRAM_KEY][PORTABLE_KEY] = {} 450 translate_dict = { 451 "url": os.path.basename(self.main_files[0]), 452 } 453 if self.pnacl_optlevel is not None: 454 translate_dict[PNACL_OPTLEVEL_KEY] = self.pnacl_optlevel 455 manifest[PROGRAM_KEY][PORTABLE_KEY][TRANSLATE_KEY] = translate_dict 456 self.manifest = manifest 457 458 def _GenerateManifest(self): 459 '''Create a JSON formatted dict containing the files 460 461 NaCl will map url requests based on architecture. The startup NEXE 462 can always be found under the top key PROGRAM. Additional files are under 463 the FILES key further mapped by file name. In the case of 'runnable' the 464 PROGRAM key is populated with urls pointing the runnable-ld.so which acts 465 as the startup nexe. The application itself is then placed under the 466 FILES key mapped as 'main.exe' instead of the original name so that the 467 loader can find it. ''' 468 manifest = { FILES_KEY: {}, PROGRAM_KEY: {} } 469 470 needed = self.GetNeeded() 471 472 runnable = any(n.endswith(RUNNABLE_LD) for n in needed) 473 474 extra_files_kv = [(key, ArchFile(name=key, 475 arch=arch, 476 path=url, 477 url=url)) 478 for key, arch, url in self.extra_files] 479 480 nexe_root = os.path.dirname(os.path.abspath(self.main_files[0])) 481 482 for need, archinfo in needed.items() + extra_files_kv: 483 urlinfo = { URL_KEY: archinfo.url } 484 name = archinfo.name 485 486 # If starting with runnable-ld.so, make that the main executable. 487 if runnable: 488 if need.endswith(RUNNABLE_LD): 489 manifest[PROGRAM_KEY][archinfo.arch] = urlinfo 490 continue 491 492 if need in self.main_files: 493 # Ensure that the .nexe and .so names are relative to the root 494 # of where the .nexe lives. 495 if os.path.abspath(urlinfo[URL_KEY]).startswith(nexe_root): 496 urlinfo[URL_KEY] = os.path.relpath(urlinfo[URL_KEY], nexe_root) 497 498 if need.endswith(".nexe"): 499 # Place it under program if we aren't using the runnable-ld.so. 500 if not runnable: 501 manifest[PROGRAM_KEY][archinfo.arch] = urlinfo 502 continue 503 # Otherwise, treat it like another another file named main.nexe. 504 name = MAIN_NEXE 505 506 name = self.remap.get(name, name) 507 fileinfo = manifest[FILES_KEY].get(name, {}) 508 fileinfo[archinfo.arch] = urlinfo 509 manifest[FILES_KEY][name] = fileinfo 510 self.manifest = manifest 511 512 def GetManifest(self): 513 '''Returns a JSON-formatted dict containing the NaCl dependencies''' 514 if not self.manifest: 515 if self.pnacl: 516 self._GeneratePNaClManifest() 517 else: 518 self._GenerateManifest() 519 return self.manifest 520 521 def GetJson(self): 522 '''Returns the Manifest as a JSON-formatted string''' 523 pretty_string = json.dumps(self.GetManifest(), indent=2) 524 # json.dumps sometimes returns trailing whitespace and does not put 525 # a newline at the end. This code fixes these problems. 526 pretty_lines = pretty_string.split('\n') 527 return '\n'.join([line.rstrip() for line in pretty_lines]) + '\n' 528 529 530 def Trace(msg): 531 if Trace.verbose: 532 sys.stderr.write(str(msg) + '\n') 533 534 Trace.verbose = False 535 536 537 def ParseExtraFiles(encoded_list, err): 538 """Parse the extra-files list and return a canonicalized list of 539 [key, arch, url] triples. The |encoded_list| should be a list of 540 strings of the form 'key:url' or 'key:arch:url', where an omitted 541 'arch' is taken to mean 'portable'. 542 543 All entries in |encoded_list| are checked for syntax errors before 544 returning. Error messages are written to |err| (typically 545 sys.stderr) so that the user has actionable feedback for fixing all 546 errors, rather than one at a time. If there are any errors, None is 547 returned instead of a list, since an empty list is a valid return 548 value. 549 """ 550 seen_error = False 551 canonicalized = [] 552 for ix in range(len(encoded_list)): 553 kv = encoded_list[ix] 554 unquoted = quote.unquote(kv, ':') 555 if len(unquoted) == 3: 556 if unquoted[1] != ':': 557 err.write('Syntax error for key:value tuple ' + 558 'for --extra-files argument: ' + kv + '\n') 559 seen_error = True 560 else: 561 canonicalized.append([unquoted[0], 'portable', unquoted[2]]) 562 elif len(unquoted) == 5: 563 if unquoted[1] != ':' or unquoted[3] != ':': 564 err.write('Syntax error for key:arch:url tuple ' + 565 'for --extra-files argument: ' + 566 kv + '\n') 567 seen_error = True 568 else: 569 canonicalized.append([unquoted[0], unquoted[2], unquoted[4]]) 570 else: 571 err.write('Bad key:arch:url tuple for --extra-files: ' + kv + '\n') 572 if seen_error: 573 return None 574 return canonicalized 575 576 577 def GetSDKRoot(): 578 """Determine current NACL_SDK_ROOT, either via the environment variable 579 itself, or by attempting to derive it from the location of this script. 580 """ 581 sdk_root = os.environ.get('NACL_SDK_ROOT') 582 if not sdk_root: 583 sdk_root = os.path.dirname(SCRIPT_DIR) 584 if not os.path.exists(os.path.join(sdk_root, 'toolchain')): 585 return None 586 587 return sdk_root 588 589 590 def FindObjdumpExecutable(): 591 """Derive path to objdump executable to use for determining shared 592 object dependencies. 593 """ 594 sdk_root = GetSDKRoot() 595 if not sdk_root: 596 return None 597 598 osname = getos.GetPlatform() 599 toolchain = os.path.join(sdk_root, 'toolchain', '%s_x86_glibc' % osname) 600 objdump = os.path.join(toolchain, 'bin', 'x86_64-nacl-objdump') 601 if osname == 'win': 602 objdump += '.exe' 603 604 if not os.path.exists(objdump): 605 sys.stderr.write('WARNING: failed to find objdump in default ' 606 'location: %s' % objdump) 607 return None 608 609 return objdump 610 611 612 def GetDefaultLibPath(config): 613 """Derive default library path to use when searching for shared 614 objects. This currently include the toolchain library folders 615 as well as the top level SDK lib folder and the naclports lib 616 folder. We include both 32-bit and 64-bit library paths. 617 """ 618 assert(config in ('Debug', 'Release')) 619 sdk_root = GetSDKRoot() 620 if not sdk_root: 621 # TOOD(sbc): output a warning here? We would also need to suppress 622 # the warning when run from the chromium build. 623 return [] 624 625 osname = getos.GetPlatform() 626 libpath = [ 627 # Core toolchain libraries 628 'toolchain/%s_x86_glibc/x86_64-nacl/lib' % osname, 629 'toolchain/%s_x86_glibc/x86_64-nacl/lib32' % osname, 630 # naclports installed libraries 631 'toolchain/%s_x86_glibc/x86_64-nacl/usr/lib' % osname, 632 'toolchain/%s_x86_glibc/i686-nacl/usr/lib' % osname, 633 # SDK bundle libraries 634 'lib/glibc_x86_32/%s' % config, 635 'lib/glibc_x86_64/%s' % config, 636 # naclports bundle libraries 637 'ports/lib/glibc_x86_32/%s' % config, 638 'ports/lib/glibc_x86_64/%s' % config, 639 ] 640 libpath = [os.path.normpath(p) for p in libpath] 641 libpath = [os.path.join(sdk_root, p) for p in libpath] 642 return libpath 643 644 645 def main(argv): 646 parser = optparse.OptionParser( 647 usage='Usage: %prog [options] nexe [extra_libs...]', description=__doc__) 648 parser.add_option('-o', '--output', dest='output', 649 help='Write manifest file to FILE (default is stdout)', 650 metavar='FILE') 651 parser.add_option('-D', '--objdump', dest='objdump', 652 help='Override the default "objdump" tool used to find ' 653 'shared object dependencies', 654 metavar='TOOL') 655 parser.add_option('--no-default-libpath', action='store_true', 656 help="Don't include the SDK default library paths") 657 parser.add_option('--debug-libs', action='store_true', 658 help='Use debug library paths when constructing default ' 659 'library path.') 660 parser.add_option('-L', '--library-path', dest='lib_path', 661 action='append', default=[], 662 help='Add DIRECTORY to library search path', 663 metavar='DIRECTORY') 664 parser.add_option('-P', '--path-prefix', dest='path_prefix', default='', 665 help='A path to prepend to shared libraries in the .nmf', 666 metavar='DIRECTORY') 667 parser.add_option('-s', '--stage-dependencies', dest='stage_dependencies', 668 help='Destination directory for staging libraries', 669 metavar='DIRECTORY') 670 parser.add_option('-t', '--toolchain', help='Legacy option, do not use') 671 parser.add_option('-n', '--name', dest='name', 672 help='Rename FOO as BAR', 673 action='append', default=[], metavar='FOO,BAR') 674 parser.add_option('-x', '--extra-files', 675 help=('Add extra key:file tuple to the "files"' + 676 ' section of the .nmf'), 677 action='append', default=[], metavar='FILE') 678 parser.add_option('-O', '--pnacl-optlevel', 679 help='Set the optimization level to N in PNaCl manifests', 680 metavar='N') 681 parser.add_option('-v', '--verbose', 682 help='Verbose output', action='store_true') 683 parser.add_option('-d', '--debug-mode', 684 help='Debug mode', action='store_true') 685 686 # To enable bash completion for this command first install optcomplete 687 # and then add this line to your .bashrc: 688 # complete -F _optcomplete create_nmf.py 689 try: 690 import optcomplete 691 optcomplete.autocomplete(parser) 692 except ImportError: 693 pass 694 695 options, args = parser.parse_args(argv) 696 if options.verbose: 697 Trace.verbose = True 698 if options.debug_mode: 699 DebugPrint.debug_mode = True 700 701 if options.toolchain is not None: 702 sys.stderr.write('warning: option -t/--toolchain is deprecated.\n') 703 704 if len(args) < 1: 705 parser.error('No nexe files specified. See --help for more info') 706 707 canonicalized = ParseExtraFiles(options.extra_files, sys.stderr) 708 if canonicalized is None: 709 parser.error('Bad --extra-files (-x) argument syntax') 710 711 remap = {} 712 for ren in options.name: 713 parts = ren.split(',') 714 if len(parts) != 2: 715 parser.error('Expecting --name=<orig_arch.so>,<new_name.so>') 716 remap[parts[0]] = parts[1] 717 718 if options.path_prefix: 719 path_prefix = options.path_prefix.split('/') 720 else: 721 path_prefix = [] 722 723 for libpath in options.lib_path: 724 if not os.path.exists(libpath): 725 sys.stderr.write('Specified library path does not exist: %s\n' % libpath) 726 elif not os.path.isdir(libpath): 727 sys.stderr.write('Specified library is not a directory: %s\n' % libpath) 728 729 if not options.no_default_libpath: 730 # Add default libraries paths to the end of the search path. 731 config = options.debug_libs and 'Debug' or 'Release' 732 options.lib_path += GetDefaultLibPath(config) 733 734 pnacl_optlevel = None 735 if options.pnacl_optlevel is not None: 736 pnacl_optlevel = int(options.pnacl_optlevel) 737 if pnacl_optlevel < 0 or pnacl_optlevel > 3: 738 sys.stderr.write( 739 'warning: PNaCl optlevel %d is unsupported (< 0 or > 3)\n' % 740 pnacl_optlevel) 741 742 nmf = NmfUtils(objdump=options.objdump, 743 main_files=args, 744 lib_path=options.lib_path, 745 extra_files=canonicalized, 746 lib_prefix=path_prefix, 747 remap=remap, 748 pnacl_optlevel=pnacl_optlevel) 749 750 nmf.GetManifest() 751 if not options.output: 752 sys.stdout.write(nmf.GetJson()) 753 else: 754 with open(options.output, 'w') as output: 755 output.write(nmf.GetJson()) 756 757 if options.stage_dependencies and not nmf.pnacl: 758 Trace('Staging dependencies...') 759 nmf.StageDependencies(options.stage_dependencies) 760 761 return 0 762 763 764 if __name__ == '__main__': 765 try: 766 rtn = main(sys.argv[1:]) 767 except Error, e: 768 sys.stderr.write('%s: %s\n' % (os.path.basename(__file__), e)) 769 rtn = 1 770 except KeyboardInterrupt: 771 sys.stderr.write('%s: interrupted\n' % os.path.basename(__file__)) 772 rtn = 1 773 sys.exit(rtn) 774