| #!/usr/bin/env python2.7 |
| # Copyright 2019 The Fuchsia Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import argparse |
| import fileinput |
| import json |
| import operator |
| import os |
| import re |
| import subprocess |
| import sys |
| |
| from common import (FUCHSIA_ROOT, run_command, is_tree_clean, fx_format) |
| |
| SCRIPT_LABEL = '//' + os.path.relpath(os.path.abspath(__file__), |
| start=FUCHSIA_ROOT) |
| |
| BRANCH_PREFIX = 'gn-move-' |
| |
| |
| class Type(object): |
| DRIVER = 'zx_driver' |
| EXECUTABLE = 'zx_executable' |
| TEST = 'zx_test' |
| TEST_DRIVER = 'zx_test_driver' |
| @classmethod |
| def all(cls): return [cls.DRIVER, cls.EXECUTABLE, cls.TEST, cls.TEST_DRIVER] |
| |
| |
| BINARY_TYPES = { |
| Type.DRIVER: 'driver_module', |
| Type.EXECUTABLE: 'executable', |
| Type.TEST: 'test', |
| Type.TEST_DRIVER: 'driver_module', |
| } |
| |
| TARGET_SCORES = { |
| Type.DRIVER: 1, |
| Type.EXECUTABLE: 1, |
| Type.TEST: 1, |
| Type.TEST_DRIVER: 1, |
| 'banjo_library': 100, |
| 'fidl_library': 100, |
| 'config': 1000, |
| 'fuzzer': 1000, |
| 'zx_library': 1000, |
| 'zx_host_tool': 1000, |
| } |
| |
| |
| def run_command(command): |
| return subprocess.check_output(command, cwd=FUCHSIA_ROOT) |
| |
| |
| def locate_build_files(base): |
| result = [] |
| for root, dirs, files in os.walk(os.path.join(FUCHSIA_ROOT, 'zircon', |
| 'system', base)): |
| for file in files: |
| if file == 'BUILD.gn': |
| result.append(os.path.join(root, file)) |
| return result |
| |
| |
| def transform_build_file(build): |
| # First pass: identify contents of the build file. |
| binaries = [] |
| has_test_binaries = False |
| has_drivers = False |
| binary_types = BINARY_TYPES.keys() |
| unclear_types = ['library'] |
| n_lines = 0 |
| with open(build, 'r') as build_file: |
| lines = build_file.readlines() |
| n_lines = len(lines) |
| for line in lines: |
| match = re.match(r'\A([^\(]+)\("([^"]+)"\)', line) |
| if match: |
| type, name = match.groups() |
| if type in binary_types: |
| binaries.append(name) |
| if type == Type.TEST: |
| has_test_binaries = True |
| if type == Type.DRIVER or type == Type.TEST_DRIVER: |
| has_drivers = True |
| if type in unclear_types: |
| print('Warning: target ' + name + ' of type ' + type + ' ' |
| 'needs to be manually converted.') |
| |
| # Second pass: rewrite contents to match GN build standards. |
| imports_added = False |
| for line in fileinput.FileInput(build, inplace=True): |
| # Apply required edits. |
| # Update target types. |
| starting_type = '' |
| for type in binary_types: |
| new_type_line = line.replace(type, BINARY_TYPES[type]) |
| if new_type_line != line: |
| starting_type = type |
| line = new_type_line |
| break |
| # Remove references to libzircon. |
| if '$zx/system/ulib/zircon' in line and not 'zircon-internal' in line: |
| line = '' |
| # Update references to libraries. |
| line = line.replace('$zx/system/ulib', '//zircon/public/lib') |
| line = line.replace('$zx/system/dev/lib', '//zircon/public/lib') |
| # Update references to Zircon in general. |
| line = line.replace('$zx', '//zircon') |
| # Update import for fuzzers. |
| line = line.replace('//zircon/public/gn/fuzzer.gni', '//build/fuzzing/fuzzer.gni') |
| # Update references to firmware. |
| line = line.replace('//zircon/public/gn/firmware.gni', '//build/unification/firmware.gni') |
| line = line.replace('$firmware_dir/', '') |
| # Update deps on libdriver. |
| line = line.replace('//zircon/public/lib/driver', '//src/devices/lib/driver') |
| # Print the line, if any content is left. |
| if line: |
| sys.stdout.write(line) |
| |
| # Insert required imports at the start of the file. |
| if not line.strip() and not imports_added: |
| imports_added = True |
| sys.stdout.write('##########################################\n') |
| sys.stdout.write('# Though under //zircon, this build file #\n') |
| sys.stdout.write('# is meant to be used in the Fuchsia GN #\n') |
| sys.stdout.write('# build. #\n') |
| sys.stdout.write('# See fxb/36139. #\n') |
| sys.stdout.write('##########################################\n') |
| sys.stdout.write('\n') |
| sys.stdout.write('assert(!defined(zx) || zx != "/", "This file can only be used in the Fuchsia GN build.")\n') |
| sys.stdout.write('\n') |
| if has_drivers: |
| sys.stdout.write('import("//build/config/fuchsia/rules.gni")\n') |
| if has_test_binaries: |
| sys.stdout.write('import("//build/test.gni")\n') |
| sys.stdout.write('import("//build/unification/images/migrated_manifest.gni")\n') |
| sys.stdout.write('\n') |
| |
| # Add extra parameters to tests. |
| if starting_type == Type.TEST: |
| sys.stdout.write(' # Dependent manifests unfortunately cannot be marked as `testonly`.\n') |
| sys.stdout.write(' # TODO(44278): Remove when converting this file to proper GN build idioms.\n') |
| sys.stdout.write(' if (is_fuchsia) {\n') |
| sys.stdout.write(' testonly = false\n') |
| sys.stdout.write(' }\n') |
| |
| if starting_type == Type.TEST_DRIVER: |
| sys.stdout.write(' test = true\n') |
| |
| if starting_type in [Type.DRIVER, Type.TEST_DRIVER]: |
| sys.stdout.write(' defines = [ "_ALL_SOURCE" ]\n') |
| sys.stdout.write(' configs += [ "//build/config/fuchsia:enable_zircon_asserts" ]\n') |
| sys.stdout.write(' configs -= [ "//build/config/fuchsia:no_cpp_standard_library" ]\n') |
| sys.stdout.write(' configs += [ "//build/config/fuchsia:static_cpp_standard_library" ]\n') |
| |
| if starting_type in Type.all(): |
| sys.stdout.write(' if (is_fuchsia) {\n') |
| sys.stdout.write(' configs += [ "//build/unification/config:zircon-migrated" ]\n') |
| sys.stdout.write(' }\n') |
| |
| if starting_type in [Type.EXECUTABLE, Type.TEST]: |
| sys.stdout.write(' if (is_fuchsia) {\n') |
| sys.stdout.write(' fdio_config = [ "//build/config/fuchsia:fdio_config" ]\n') |
| sys.stdout.write(' if (configs + fdio_config - fdio_config != configs) {\n') |
| sys.stdout.write(' configs -= fdio_config\n') |
| sys.stdout.write(' }\n') |
| sys.stdout.write(' }\n') |
| |
| # Third pass: add manifest targets at the end of the file. |
| with open(build, 'a') as build_file: |
| for binary in binaries: |
| build_file.write('\n') |
| build_file.write('migrated_manifest("' + binary + '-manifest") {\n') |
| build_file.write(' deps = [\n') |
| build_file.write(' ":' + binary + '",\n') |
| build_file.write(' ]\n') |
| build_file.write('}\n') |
| |
| # Format the file. |
| fx_format(build) |
| |
| return 0 |
| |
| |
| def score_file(path): |
| total = 0 |
| with open(os.path.join(FUCHSIA_ROOT, 'zircon', 'system', path, 'BUILD.gn'), 'r') as build_file: |
| content = build_file.read() |
| for word, score in TARGET_SCORES.iteritems(): |
| instances = len(re.findall(r'^\s*' + word + '\(', content, re.MULTILINE)) |
| total += instances * score |
| return total |
| |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser( |
| description='Moves a binary from ZN to GN.') |
| commands = parser.add_subparsers() |
| |
| convert_parser = commands.add_parser('convert', |
| help='Migrate from ZN to GN') |
| convert_parser.add_argument('binary', |
| help='The binary under //zircon/system to migrate, ' |
| 'e.g. uapp/audio, utest/fit, dev/bus/pci') |
| convert_parser.add_argument('--from-current-branch', |
| help='use the current branch as parent rather than JIRI_HEAD', |
| action="store_true") |
| convert_parser.set_defaults(func=run_convert) |
| |
| list_parser = commands.add_parser('list', |
| help='List available binaries') |
| list_parser.add_argument('--build-dir', |
| help='path to the ZN build dir', |
| default=os.path.join(FUCHSIA_ROOT, 'out', 'default.zircon')) |
| list_parser.add_argument('--sorted', |
| help='whether to sort the list by decreasing difficulty', |
| action='store_true') |
| list_parser.set_defaults(func=run_list) |
| |
| args = parser.parse_args() |
| args.func(args) |
| |
| |
| def run_convert(args): |
| # Check that the fuchsia.git tree is clean. |
| if not is_tree_clean(): |
| return 1 |
| |
| # Identify the affected build files. |
| build_files = locate_build_files(args.binary) |
| if not build_files: |
| print('Error: could not find any files for ' + args.binary) |
| return 1 |
| |
| # Confirm with the user that these are the files they want to convert. |
| print('The following build file(s) will be converted:') |
| for file in build_files: |
| print(' - ' + os.path.relpath(file, FUCHSIA_ROOT)) |
| go_ahead = raw_input('Proceed? (Y/n) ').lower().strip() |
| if go_ahead != 'y' and go_ahead != '': |
| print('User disagrees, exiting') |
| return 0 |
| |
| # Determine what branch to start from. |
| branch_args = ['JIRI_HEAD'] |
| if args.from_current_branch: |
| head = run_command(['git', 'symbolic-ref', '--short', '-q', 'HEAD']).strip() |
| if not head.startswith(BRANCH_PREFIX): |
| print('Error: should start from a migration branch (i.e. named "%sfoo").' % |
| BRANCH_PREFIX) |
| return 1 |
| branch_args = ['-t', head] |
| |
| # Convert the build files. |
| for file in build_files: |
| transform_build_file(file) |
| |
| # Create a commit. |
| id = args.binary.replace('/', '_') |
| run_command(['git', 'checkout', '-b', BRANCH_PREFIX + id] + branch_args) |
| |
| run_command(['git', 'add', '.']) |
| message = [ |
| '[unification] Move //zircon/system/' + args.binary + ' to the GN build', |
| '', |
| 'Generated with: ' + SCRIPT_LABEL, |
| '', |
| 'scripts/unification/verify_element_move.py --reference local/initial.json:', |
| 'TODO PASTE VERIFICATION RESULT HERE', |
| '', |
| 'Bug: 36139' |
| ] |
| commit_command = ['git', 'commit', '-a'] |
| for line in message: |
| commit_command += ['-m', line] |
| run_command(commit_command) |
| |
| print('Base change is ready. Please attempt to build a full system to ' |
| 'identify further required changes.') |
| |
| return 0 |
| |
| |
| def run_list(args): |
| scores = {} |
| for arch in ['arm64', 'x64']: |
| manifest_path = os.path.join(args.build_dir, |
| 'legacy_unification-%s.json' % arch) |
| with open(manifest_path, 'r') as manifest_file: |
| data = json.load(manifest_file) |
| for item in data: |
| if item['name'].startswith('lib.'): |
| # Libraries will be migrated through a different process. |
| continue |
| label = item['label'] |
| # Labels are always full, i.e. "//foo/bar:blah(//toolchain)". |
| label = label[0:label.index('(')] |
| label = label[0:label.index(':')] |
| if not label.startswith('//system'): |
| continue |
| label = label[len('//system/'):] |
| score = score_file(label) if args.sorted else 0 |
| scores.setdefault(score, set()).add(label) |
| for score, labels in sorted(scores.items(), |
| key=operator.itemgetter(0), |
| reverse=True): |
| for label in sorted(labels): |
| print(label) |
| if args.sorted: |
| print('-------------------------------------') |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main()) |