Exemple #1
0
def main():
    parser = argparse.ArgumentParser(
        description=
        'Removes references to `migrated_manifest` from BUILD.gn files')
    parser.add_argument('--root',
                        help='Path to the directory to inspect',
                        default=FUCHSIA_ROOT)
    args = parser.parse_args()

    build_files = []
    for base, _, files in os.walk(args.root):
        for file in files:
            if file == 'BUILD.gn':
                build_files.append(os.path.join(base, file))

    for build_path in build_files:
        # Number of currently open curly brackets while processing a target to
        # remove.
        # A lesser than or equal to 0 number means no target is currently being
        # erased.
        curly_bracket_depth = 0
        modified = False
        for line in fileinput.FileInput(build_path, inplace=True):
            if '//build/unification/images/migrated_manifest.gni' in line:
                continue
            target_match = re.match('\s*migrated_manifest\(', line)
            if target_match:
                curly_bracket_depth = 1
                modified = True
                continue
            if curly_bracket_depth > 0:
                curly_bracket_depth += line.count('{') - line.count('}')
                if curly_bracket_depth >= 0:
                    # Keep erasing.
                    continue
            sys.stdout.write(line)
        if modified:
            fx_format(build_path)

    return 0
def main():
    parser = argparse.ArgumentParser(
        description='Moves a C/C++ library from //zircon to //sdk')
    parser.add_argument('--lib',
                        help='Name of the library folder to migrate, e.g. '
                        'ulib/foo or dev/lib/bar',
                        action='append')
    args = parser.parse_args()

    if not args.lib:
        print('Need to specify at least one library.')
        return 1

    # Check that the fuchsia.git tree is clean.
    if not is_tree_clean():
        return 1

    movable_libs = []
    for lib in args.lib:
        # Verify that the library may be migrated at this time.
        library = Library(lib)
        if not os.path.exists(library.build_path):
            print('Could not locate library ' + lib + ', ignoring')
            continue

        # No kernel!
        if library.has_kernel:
            print('Some libraries are used in the kernel and may not be '
                  'migrated at the moment, ignoring ' + lib)
            continue

        # Only libraries that have been exported to the GN build already!
        if library.has_unexported:
            print(
                'Can only convert libraries already exported to the GN build,'
                ' ignoring ' + lib)
            continue

        # No SDK libraries for now.
        if library.has_sdk and library.has_static:
            print('Cannot convert static SDK libraries for now, ignoring ' +
                  lib)
            continue

        # Gather build files with references to the library.
        for base, _, files in os.walk(FUCHSIA_ROOT):
            for file in files:
                if file != 'BUILD.gn':
                    continue
                file_path = os.path.join(base, file)
                with open(file_path, 'r') as build_file:
                    content = build_file.read()
                    for name in [s.name for s in library.stats]:
                        reference = '"//zircon/public/lib/' + name + '"'
                        if reference in content:
                            library.build_files.append(file_path)
                            if not is_in_fuchsia_project(file_path):
                                library.cross_project = True
                            break

        movable_libs.append(library)

    if not movable_libs:
        print('Could not find any library to convert, aborting')
        return 1

    solo_libs = [
        l.name for l in movable_libs if l.cross_project or l.has_shared
    ]
    if solo_libs and len(movable_libs) > 1:
        print('These libraries may only be moved in a dedicated change: ' +
              ', '.join(solo_libs))
        return 1

    for library in movable_libs:
        # Rewrite the library's build file.
        import_added = False
        for line in fileinput.FileInput(library.build_path, inplace=True):
            # 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 known configs.
            line = line.replace(
                '$zx_build/public/gn/config:static-libc++',
                '//build/config/fuchsia:static_cpp_standard_library')
            # Update references to Zircon in general.
            line = line.replace('$zx', '//zircon')
            # Update deps on libdriver.
            line = line.replace('//zircon/public/lib/driver',
                                '//src/devices/lib/driver')
            # Remove header target specifier.
            line = line.replace('.headers', '')
            line = line.replace(':headers', '')
            sys.stdout.write(line)

            if not line.strip() and not import_added:
                import_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/36548.                         #\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')
                sys.stdout.write(
                    'import("//build/unification/zx_library.gni")\n')
                sys.stdout.write('\n')
        fx_format(library.build_path)

        # Edit references to the library.
        for file_path in library.build_files:
            for line in fileinput.FileInput(file_path, inplace=True):
                for name in [s.name for s in library.stats]:
                    new_label = '"' + library.base_label
                    if os.path.basename(new_label) != name:
                        new_label = new_label + ':' + name
                    new_label = new_label + '"'
                    line = re.sub('"//zircon/public/lib/' + name + '"',
                                  new_label, line)
                sys.stdout.write(line)
            fx_format(file_path)

        # Remove references to the library in the unification scaffolding if
        # necessary.
        if library.has_shared:
            unification_path = os.path.join(FUCHSIA_ROOT, 'build',
                                            'unification', 'images',
                                            'BUILD.gn')

            def unification_replacer(line):
                for name in [s.name for s in library.stats]:
                    if re.match('^\s*"' + name + '",$', line):
                        # Remove the line.
                        return ''

            replace_lines(unification_path, unification_replacer)

        # Generate an alias for the library under //zircon/public/lib if a soft
        # transition is necessary.
        if library.cross_project:
            alias_path = os.path.join(FUCHSIA_ROOT, 'build', 'unification',
                                      'zircon_library_mappings.json')
            with open(alias_path, 'r') as alias_file:
                data = json.load(alias_file)
            for s in library.stats:
                data.append({
                    'name': s.name,
                    'sdk': s.sdk_publishable,
                    'label': library.base_label + ":" + s.name,
                })
            data = sorted(data, key=lambda item: item['name'])
            with open(alias_path, 'w') as alias_file:
                json.dump(data,
                          alias_file,
                          indent=2,
                          sort_keys=True,
                          separators=(',', ': '))

        # Update references to the library if it belongs to any SDK.
        if library.has_sdk:
            sdk_path = os.path.join(FUCHSIA_ROOT, 'sdk', 'BUILD.gn')
            folder = os.path.basename(library.name)

            def sdk_replacer(line):
                for name in [s.name for s in library.stats]:
                    if '"//zircon/public/lib/' + folder + ':' + name + '_sdk' + '"' in line:
                        return line.replace('public/lib', 'system/ulib')

            replace_lines(sdk_path, sdk_replacer)
            fx_format(sdk_path)

        # Remove the reference in the ZN aggregation target.
        aggregation_path = os.path.join(FUCHSIA_ROOT, 'zircon', 'system',
                                        os.path.dirname(library.name),
                                        'BUILD.gn')
        if os.path.exists(aggregation_path):
            folder = os.path.basename(library.name)

            def aggregation_replacer(line):
                for name in [s.name for s in library.stats]:
                    if ('"' + folder + ':' + name + '"' in line
                            or '"' + folder + '"' in line):
                        return ''

            replace_lines(aggregation_path, aggregation_replacer)
        else:
            print('Warning: some references to ' + lib + ' might still exist '
                  'in the ZN build, please remove them manually')

    # Create a commit.
    libs = sorted([l.name for l in movable_libs])
    under_libs = [l.replace('/', '_') for l in libs]
    branch_name = 'lib-move-' + under_libs[0]
    lib_name = '//zircon/system/' + libs[0]
    if len(libs) > 1:
        branch_name += '-and-co'
        lib_name += ' and others'
    run_command(['git', 'checkout', '-b', branch_name, 'JIRI_HEAD'])
    run_command(['git', 'add', FUCHSIA_ROOT])
    message = [
        '[unification] Move ' + lib_name + ' to the GN build', '',
        'Affected libraries:'
    ] + ['//zircon/system/' + l for l in libs]
    if any(l.has_shared for l in movable_libs):
        message += [
            '',
            'scripts/unification/verify_element_move.py --reference local/initial.json:',
            '',
            'TODO PASTE VERIFICATION RESULT HERE',
        ]
    message += ['', 'Generated with ' + SCRIPT_LABEL, '', 'Bug: 36548']
    fd, message_path = tempfile.mkstemp()
    with open(message_path, 'w') as message_file:
        message_file.write('\n'.join(message))
    commit_command = ['git', 'commit', '-a', '-F', message_path]
    run_command(commit_command)
    os.close(fd)
    os.remove(message_path)

    if any(l.cross_project for l in movable_libs):
        print('*** Warning: multiple Git projects were affected by this move!')
        print('Run jiri status to view affected projects.')
        print('Staging procedure:')
        print(
            ' - use "jiri upload" to start the review process on the fuchsia.git change;'
        )
        print(
            ' - prepare dependent CLs for each affected project and get them review;'
        )
        print(
            ' - when the fuchsia.git change has rolled into GI, get the other CLs submitted;'
        )
        print(
            ' - when these CLs have rolled into GI, prepare a change to remove the forwarding'
        )
        print(
            '   targets under //build/unification/zircon_library_mappings.json'
        )
    else:
        print(
            'Change is ready, use "jiri upload" to start the review process.')

    return 0
Exemple #3
0
def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument(
        '--base',
        help='Path to the directory in which files will be fixed',
        required=True)
    args = parser.parse_args()

    # Fix manifest files.
    manifest_paths = []
    for root, _, files in os.walk(args.base):
        for file in files:
            name, extension = os.path.splitext(file)
            if extension != '.cmx':
                continue
            file_path = os.path.join(root, file)
            with open(file_path, 'r') as manifest_file:
                contents = manifest_file.read()
            if '"bin/app"' not in contents:
                continue
            contents = contents.replace('"bin/app"', '"bin/' + name + '"')
            with open(file_path, 'w') as manifest_file:
                manifest_file.write(contents)
            manifest_paths.append(file_path)

    # Fix build files.
    build_changes = {}  # file path --> list of affected binaries
    for root, _, files in os.walk(args.base):
        for file in files:
            if file != 'BUILD.gn':
                continue
            file_path = os.path.join(root, file)
            found_reference = False
            for line in fileinput.FileInput(file_path, inplace=True):
                match = re.match('^(\s*)binary = "([^"]+)"\s*$', line)
                if match:
                    found_reference = True
                    line = match.group(
                        1) + 'binaries = [{ name = "' + match.group(2) + '" }]'
                    build_changes.setdefault(file_path,
                                             []).append(match.group(2))
                sys.stdout.write(line)
            if found_reference:
                fx_format(file_path)

    # Cross-reference the two lists of modified files and attempt to identify
    # issues.
    print('-----------------------------')
    print('Updated %d component manifest files' % len(manifest_paths))
    print(
        'Updated %d references in %d build files' % (
            sum([len(v) for v in build_changes.values()],
                0), len(build_changes.keys())))
    print('')

    # First off, build a list of manifest files we expect to see given the
    # changes made to build files.
    expected_manifest_paths = []
    matched_manifest_paths = []
    for build_file, binaries in sorted(build_changes.iteritems()):
        base_dir = os.path.dirname(build_file)
        for binary in binaries:
            manifest = os.path.join(base_dir, 'meta', binary + '.cmx')
            if manifest in manifest_paths:
                # We found a manifest exactly where we expected it: great!
                manifest_paths.remove(manifest)
                matched_manifest_paths.append(manifest)
                continue
            if '_' in binary:
                # Since '_' is technically not allowed in package URIs, some
                # manifest files are renamed by turning '_' into '-'.
                alternate_manifest = os.path.join(
                    base_dir, 'meta',
                    binary.replace('_', '-') + '.cmx')
                if alternate_manifest in manifest_paths:
                    manifest_paths.remove(alternate_manifest)
                    matched_manifest_paths.append(alternate_manifest)
                    continue
            expected_manifest_paths.append(manifest)
    print('-----------------------------')
    print(
        'After exact matches: %d references, %d manifest files unmatched' %
        (len(expected_manifest_paths), len(manifest_paths)))
    print('Matches:')
    for path in matched_manifest_paths:
        print(path)
    print('')

    # Second step is to look at manifests with the same file name as what we
    # would expect AND under the same directory.
    print('-----------------------------')
    print('Likely matches')
    for potential_path in list(expected_manifest_paths):
        base_dir = os.path.dirname(os.path.dirname(potential_path))
        name = os.path.basename(potential_path)
        for path in list(manifest_paths):
            if os.path.commonprefix([
                    base_dir, path
            ]) == base_dir and os.path.basename(path) == name:
                expected_manifest_paths.remove(potential_path)
                manifest_paths.remove(path)
                print('[---] ' + potential_path)
                print('[+++] ' + path)
                break
    print('')

    print('-----------------------------')
    print(
        'Leftovers: %d references, %d manifest files' %
        (len(expected_manifest_paths), len(manifest_paths)))
    combined = [(p, '---') for p in expected_manifest_paths] + [
        (p, '+++') for p in manifest_paths
    ]
    for path, type in sorted(combined):
        print('[%s] %s' % (type, path))

    return 0
def main():
    parser = argparse.ArgumentParser(
        description='Moves a Banjo library from //zircon to //sdk')
    parser.add_argument('lib', help='Name of the library to migrate')
    args = parser.parse_args()
    lib = args.lib

    # Check that the fuchsia.git tree is clean.
    if not is_tree_clean():
        return 1

    sdk_base = os.path.join(FUCHSIA_ROOT, 'sdk', 'banjo')
    sdk_dir = os.path.join(sdk_base, lib)
    banjo_base = os.path.join(FUCHSIA_ROOT, 'zircon', 'system', 'banjo')
    source_dir = os.path.join(banjo_base, lib)

    # Move the sources.
    shutil.move(source_dir, sdk_base)

    # Edit the build file.
    is_dummy = not (lib.startswith('ddk.protocol') or lib.startswith('ddk.hw'))
    build_path = os.path.join(sdk_dir, 'BUILD.gn')
    for line in fileinput.FileInput(build_path, inplace=True):
        line = line.replace('$zx_build/public/gn/banjo.gni',
                            '//build/banjo/banjo.gni')
        line = line.replace('banjo_library',
                            'banjo_dummy' if is_dummy else 'banjo')
        line = line.replace('public_deps', 'deps')
        line = line.replace('$zx/system/banjo', '//zircon/system/banjo')
        sys.stdout.write(line)
    fx_format(build_path)

    # Edit references to the library.
    for base, _, files in os.walk(FUCHSIA_ROOT):
        for file in files:
            if file != 'BUILD.gn':
                continue
            file_path = os.path.join(base, file)
            has_changes = False
            for original_line in fileinput.FileInput(file_path, inplace=True):
                # Make sure that only exact matches are replaced, as some
                # library names are prefix of other names.
                # Examples:
                # //zircon/s/b/ddk.foo.bar" --> "//sdk/b/foo.bar"
                # //zircon/s/b/ddk.foo.bar:boop" --> "//sdk/b/foo.bar:boop"
                line = re.sub('"(//zircon/system/banjo/' + lib + ')([:"])',
                              '"//sdk/banjo/' + lib + '\\2', original_line)
                if line != original_line:
                    has_changes = True
                sys.stdout.write(line)
            if has_changes:
                fx_format(file_path)

    for line in fileinput.FileInput(os.path.join(banjo_base, 'BUILD.gn'),
                                    inplace=True):
        if not '"' + lib + '"' in line:
            sys.stdout.write(line)

    # Create a commit.
    run_command(['git', 'checkout', '-b', 'banjo-move-' + lib, 'JIRI_HEAD'])
    run_command(['git', 'add', sdk_dir])
    message = [
        '[unification] Move ' + lib + ' to //sdk/banjo', '',
        'Generated with: //scripts/unification/move_banjo_library.py ' + lib,
        '', 'Bug: 36540'
    ]
    commit_command = ['git', 'commit', '-a']
    for line in message:
        commit_command += ['-m', line]
    run_command(commit_command)

    print('Change is ready, use "jiri upload" to start the review process.')

    return 0
Exemple #5
0
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
Exemple #6
0
def main():
    parser = argparse.ArgumentParser(
        description='Moves a C/C++ library from //zircon to //sdk')
    parser.add_argument('lib',
                        help='Name of the library folder to migrate, e.g. '
                        'ulib/foo or dev/lib/bar')
    args = parser.parse_args()

    # Check that the fuchsia.git tree is clean.
    if not is_tree_clean():
        return 1

    # Verify that the library may be migrated at this time.
    build_path = os.path.join(FUCHSIA_ROOT, 'zircon', 'system', args.lib,
                              'BUILD.gn')
    base_label = '//zircon/system/' + args.lib
    stats = get_library_stats(build_path)

    # No kernel!
    has_kernel = len([s for s in stats if s.kernel]) != 0
    if has_kernel:
        print('Some libraries in the given folder are used in the kernel and '
              'may not be migrated at the moment')
        return 1

    # Only source libraries!
    non_source_sdk = len([s for s in stats if s.sdk != Sdk.SOURCE]) != 0
    if non_source_sdk:
        print('Can only convert libraries exported as "sources" for now')
        return 1

    # Rewrite the library's build file.
    import_added = False
    for line in fileinput.FileInput(build_path, inplace=True):
        # 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 known configs.
        line = line.replace(
            '$zx_build/public/gn/config:static-libc++',
            '//build/config/fuchsia:static_cpp_standard_library')
        # Update references to Zircon in general.
        line = line.replace('$zx', '//zircon')
        # Update deps on libdriver.
        line = line.replace('//zircon/public/lib/driver',
                            '//src/devices/lib/driver')
        # Remove header target specifier.
        line = line.replace('.headers', '')
        line = line.replace(':headers', '')
        sys.stdout.write(line)

        if not line.strip() and not import_added:
            import_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/36548.                         #\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')
            sys.stdout.write('import("//build/unification/zx_library.gni")\n')
            sys.stdout.write('\n')
    fx_format(build_path)

    # Track whether fuchsia.git was the only affected project.
    multiple_projects_affected = False

    # Edit references to the library.
    for base, _, files in os.walk(FUCHSIA_ROOT):
        for file in files:
            if file != 'BUILD.gn':
                continue
            has_matches = False
            file_path = os.path.join(base, file)
            for line in fileinput.FileInput(file_path, inplace=True):
                for name in [s.name for s in stats]:
                    new_label = '"' + base_label
                    if os.path.basename(new_label) != name:
                        new_label = new_label + ':' + name
                    new_label = new_label + '"'
                    line, count = re.subn('"//zircon/public/lib/' + name + '"',
                                          new_label, line)
                    if count:
                        has_matches = True
                sys.stdout.write(line)
            if has_matches:
                fx_format(file_path)
                if not is_in_fuchsia_project(file_path):
                    multiple_projects_affected = True

    # Generate an alias for the library under //zircon/public/lib if a soft
    # transition is necessary.
    if multiple_projects_affected:
        alias_path = os.path.join(FUCHSIA_ROOT, 'build', 'unification',
                                  'zircon_library_mappings.json')
        with open(alias_path, 'r') as alias_file:
            data = json.load(alias_file)
        for s in stats:
            data.append({
                'name': s.name,
                'sdk': s.sdk_publishable,
                'label': base_label + ":" + s.name,
            })
        data = sorted(data, key=lambda item: item['name'])
        with open(alias_path, 'w') as alias_file:
            json.dump(data,
                      alias_file,
                      indent=2,
                      sort_keys=True,
                      separators=(',', ': '))

    # Remove the reference in the ZN aggregation target.
    aggregation_path = os.path.join(FUCHSIA_ROOT, 'zircon', 'system',
                                    os.path.dirname(args.lib), 'BUILD.gn')
    folder = os.path.basename(args.lib)
    for line in fileinput.FileInput(aggregation_path, inplace=True):
        for s in stats:
            if (not '"' + folder + ':' + name + '"' in line
                    and not '"' + folder + '"' in line):
                sys.stdout.write(line)

    # Create a commit.
    lib = args.lib.replace('/', '_')
    run_command(['git', 'checkout', '-b', 'lib-move-' + lib, 'JIRI_HEAD'])
    run_command(['git', 'add', FUCHSIA_ROOT])
    message = [
        '[unification] Move //zircon/system/' + args.lib + ' to the GN build',
        '', 'Generated with: ' + SCRIPT_LABEL + ' ' + args.lib, '',
        'Bug: 36548'
    ]
    commit_command = ['git', 'commit', '-a']
    for line in message:
        commit_command += ['-m', line]
    run_command(commit_command)

    if multiple_projects_affected:
        print('*** Warning: multiple Git projects were affected by this move!')
        print('Run jiri status to view affected projects.')
        print('Staging procedure:')
        print(
            ' - use "jiri upload" to start the review process on the fuchsia.git change;'
        )
        print(
            ' - prepare dependent CLs for each affected project and get them review;'
        )
        print(
            ' - when the fuchsia.git change has rolled into GI, get the other CLs submitted;'
        )
        print(
            ' - when these CLs have rolled into GI, prepare a change to remove the forwarding'
        )
        print(
            '   targets under //build/unification/zircon_library_mappings.json'
        )
    else:
        print(
            'Change is ready, use "jiri upload" to start the review process.')

    return 0
def main():
    parser = argparse.ArgumentParser(
        description='Moves a FIDL library from //zircon to //sdk')
    parser.add_argument('lib', help='Name of the library to migrate')
    args = parser.parse_args()

    # Accept library names with dots or dashes.
    lib = args.lib.replace('-', '.')
    lib_with_dash = args.lib.replace('.', '-')

    # Check that the fuchsia.git tree is clean.
    if not is_tree_clean():
        return 1

    sdk_base = os.path.join(FUCHSIA_ROOT, 'sdk', 'fidl')
    sdk_dir = os.path.join(sdk_base, lib)
    fidl_base = os.path.join(FUCHSIA_ROOT, 'zircon', 'system', 'fidl')
    source_dir = os.path.join(fidl_base, lib_with_dash)

    # Move the sources.
    # The destination directory sometimes already exists.
    if not os.path.isdir(sdk_dir):
        os.mkdir(sdk_dir)
    for _, _, files in os.walk(source_dir):
        for file in files:
            shutil.move(os.path.join(source_dir, file), sdk_dir)
            dest_file = os.path.join(sdk_dir, file)
            fx_format(dest_file)
        break

    # Edit the build file in its new location.
    in_sdk = False
    build_path = os.path.join(sdk_dir, 'BUILD.gn')
    for line in fileinput.FileInput(build_path, inplace=True):
        if 'sdk = false' in line:
            continue
        if 'sdk = true' in line:
            in_sdk = True
            print('  sdk_category = "partner"')
            print('  api = "' + lib + '.api"')
            continue
        line = line.replace('$zx_build/public/gn/fidl.gni',
                            '//build/fidl/fidl.gni')
        line = line.replace('fidl_library', 'fidl')
        line = line.replace(lib_with_dash, lib)
        line = line.replace('$zx/system/fidl', '//zircon/system/fidl')
        sys.stdout.write(line)
    fx_format(build_path)

    # Track whether fuchsia.git was the only affected project.
    multiple_projects_affected = False

    # Edit references to the library.
    for base, _, files in os.walk(FUCHSIA_ROOT):
        for file in files:
            if file != 'BUILD.gn':
                continue
            has_matches = False
            file_path = os.path.join(base, file)
            for line in fileinput.FileInput(file_path, inplace=True):
                match = re.search(
                    '"//zircon/system/fidl/' + lib_with_dash +
                    '(?::(?P<target>[^"]+))?"', line)
                if match:
                    has_matches = True
                    original_target = match.group('target')
                    if original_target:
                        if original_target == 'c':
                            target = lib + '_c'
                        elif original_target == 'c.headers':
                            target = lib + '_c'
                        elif original_target == 'llcpp':
                            target = lib + '_llcpp'
                        elif original_target == 'llcpp.headers':
                            target = lib + '_llcpp'
                        else:
                            target = original_target.replace(
                                lib_with_dash, lib)
                        line = line.replace(
                            '"//zircon/system/fidl/' + lib_with_dash + ':' +
                            original_target + '"',
                            '"//sdk/fidl/' + lib + ':' + target + '"')
                    else:
                        line = line.replace(
                            '"//zircon/system/fidl/' + lib_with_dash + '"',
                            '"//sdk/fidl/' + lib + '"')
                sys.stdout.write(line)
            if has_matches:
                # Format the file.
                fx_format(file_path)
                if not is_in_fuchsia_project(file_path):
                    multiple_projects_affected = True

    if multiple_projects_affected:
        # Set up an alias in the old location.
        with open(os.path.join(source_dir, 'BUILD.gn'), 'w') as build_file:
            build_file.writelines([
                '# Copyright 2020 The Fuchsia Authors. All rights reserved.\n',
                '# Use of this source code is governed by a BSD-style license that can be\n',
                '# found in the LICENSE file.\n',
                '\n',
                'import("//build/unification/fidl_alias.gni")\n',
                '\n',
                'fidl_alias("%s") {\n' % lib_with_dash,
                '  sdk_category = "partner"\n' if in_sdk else '\n',
                '}\n',
            ])

    # Edit references to the library.
    # Only editing the ZN file listing all FIDL libraries.
    for line in fileinput.FileInput(os.path.join(fidl_base, 'BUILD.gn'),
                                    inplace=True):
        if not '"' + lib_with_dash + '"' in line:
            sys.stdout.write(line)

    # Create a commit.
    run_command(['git', 'checkout', '-b', 'fidl-move-' + lib, 'JIRI_HEAD'])
    run_command(['git', 'add', sdk_dir])
    message = [
        '[unification] Move ' + lib + ' to //sdk/fidl', '',
        'Generated with: //scripts/unification/move_fidl_library.py ' + lib,
        '', 'Bug: 36547'
    ]
    commit_command = ['git', 'commit', '-a']
    for line in message:
        commit_command += ['-m', line]
    run_command(commit_command)

    if multiple_projects_affected:
        print('*** Warning: multiple Git projects were affected by this move!')
        print('Run jiri status to view affected projects.')
        print('Staging procedure:')
        print(
            ' - use "jiri upload" to start the review process on the fuchsia.git change;'
        )
        print(
            ' - prepare dependent CLs for each affected project and get them review;'
        )
        print(
            ' - when the fuchsia.git change has rolled into GI, get the other CLs submitted;'
        )
        print(
            ' - when these CLs have rolled into GI, prepare a change to remove the forwarding'
        )
        print('   target under //zircon/system/fidl/' + lib_with_dash)
    else:
        print(
            'Change is ready, use "jiri upload" to start the review process.')

    return 0