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