#!/usr/bin/env python # Copyright 2014 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Adaptor script called through build/isolate.gypi. Creates a wrapping .isolate which 'includes' the original one, that can be consumed by tools/swarming_client/isolate.py. Path variables are determined based on the current working directory. The relative_cwd in the .isolated file is determined based on *the .isolate file that declare the 'command' variable to be used* so the wrapping .isolate doesn't affect this value. It packages all the dynamic libraries found in this wrapping .isolate. This is inefficient and non-deterministic. In the very near future, it will parse build.ninja, find back the root target and find all the dynamic libraries that are marked as a dependency to this target. """ import glob import os import posixpath import subprocess import sys import time TOOLS_DIR = os.path.dirname(os.path.abspath(__file__)) SWARMING_CLIENT_DIR = os.path.join(TOOLS_DIR, 'swarming_client') SRC_DIR = os.path.dirname(TOOLS_DIR) sys.path.insert(0, SWARMING_CLIENT_DIR) import isolate_format # Location to grab binaries based on the OS. DYNAMIC_LIBRARIES = { 'darwin': '*.dylib', 'linux2': 'lib/*.so', 'win32': '*.dll', } def get_dynamic_libs(build_dir): """Finds all the dynamic libs to map. Returns: list of relative path, e.g. [../out/Debug/lib/libuser_prefs.so]. """ items = set() root = os.path.join(build_dir, DYNAMIC_LIBRARIES[sys.platform]) for i in glob.iglob(root): try: # Will throw on Windows if another process is writing to this file. open(i).close() items.add((i, os.stat(i).st_size)) except IOError: continue # The following sleep value was carefully selected via random guessing. The # goal is to detect files that are being linked by checking their file size # after a small delay. # # This happens as other build targets can be built simultaneously. For # example, base_unittests.isolated is being processed but dynamic libraries # for chrome are currently being linked. # # TODO(maruel): Obviously, this must go away and be replaced with a proper # ninja parser but we need something now. http://crbug.com/333473 time.sleep(10) for item in sorted(items): file_name, file_size = item try: open(file_name).close() if os.stat(file_name).st_size != file_size: items.remove(item) except IOError: items.remove(item) continue return [i[0].replace(os.path.sep, '/') for i in items] def create_wrapper(args, isolate_index, isolated_index): """Creates a wrapper .isolate that add dynamic libs. The original .isolate is not modified. """ cwd = os.getcwd() isolate = args[isolate_index] # The code assumes the .isolate file is always specified path-less in cwd. Fix # if this assumption doesn't hold true. assert os.path.basename(isolate) == isolate, isolate # This will look like ../out/Debug. This is based against cwd. Note that this # must equal the value provided as PRODUCT_DIR. build_dir = os.path.dirname(args[isolated_index]) # This will look like chrome/unit_tests.isolate. It is based against SRC_DIR. # It's used to calculate temp_isolate. src_isolate = os.path.relpath(os.path.join(cwd, isolate), SRC_DIR) # The wrapping .isolate. This will look like # ../out/Debug/gen/chrome/unit_tests.isolate. temp_isolate = os.path.join(build_dir, 'gen', src_isolate) temp_isolate_dir = os.path.dirname(temp_isolate) # Relative path between the new and old .isolate file. isolate_relpath = os.path.relpath( '.', temp_isolate_dir).replace(os.path.sep, '/') # Will look like ['<(PRODUCT_DIR)/lib/flibuser_prefs.so']. rebased_libs = [ '<(PRODUCT_DIR)/%s' % i[len(build_dir)+1:] for i in get_dynamic_libs(build_dir) ] # Now do actual wrapping .isolate. out = { 'includes': [ posixpath.join(isolate_relpath, isolate), ], 'variables': { isolate_format.KEY_TRACKED: rebased_libs, }, } if not os.path.isdir(temp_isolate_dir): os.makedirs(temp_isolate_dir) comment = ( '# Warning: this file was AUTOGENERATED.\n' '# DO NO EDIT.\n') with open(temp_isolate, 'wb') as f: isolate_format.print_all(comment, out, f) if '--verbose' in args: print('Added %d dynamic libs' % len(dynamic_libs)) args[isolate_index] = temp_isolate def main(): args = sys.argv[1:] isolate = None isolated = None is_component = False for i, arg in enumerate(args): if arg == '--isolate': isolate = i + 1 if arg == '--isolated': isolated = i + 1 if arg == 'component=shared_library': is_component = True if isolate is None or isolated is None: print >> sys.stderr, 'Internal failure' return 1 # Implement a ninja parser. # http://crbug.com/360223 if is_component and False: create_wrapper(args, isolate, isolated) swarming_client = os.path.join(SRC_DIR, 'tools', 'swarming_client') sys.stdout.flush() result = subprocess.call( [sys.executable, os.path.join(swarming_client, 'isolate.py')] + args) return result if __name__ == '__main__': sys.exit(main())