blob: ced5525f6ab86966000b4b1f98b546c49e7cc2d7 [file] [log] [blame]
#!/usr/bin/env fuchsia-vendored-python
# Copyright 2020 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 os
import subprocess
import sys
from pathlib import Path
from multiprocessing.dummy import Pool
FUCHSIA_DIR = Path(os.environ["FUCHSIA_DIR"])
def main(args):
"""Converts as many FIDL files as possible by invoking `fx build`, then
running through a number of strategies to match the converted file output.
This script assumes that the fidlc has been instrumented such that it
produces a `.fidl.new` type output for each input `.fidl` file.
"""
if args.dryrun:
print("\nDRY RUN!")
else:
print("\nREAL RUN!")
if args.rebuild:
# Note: This includes all of the "kitchen_sink" and "buildbot/core", but
# unfortunately there are still some build targets that are not included
# in these catch-all targets. Those are manually listed at th marked
# point, but will likely change in the future (ie, the targets could be
# added to the catch-alls, or removed completely. Additionally, other
# targets that are missed by the catch-alls could be added as well).
print("\nSETTING ENVIRONMENT...")
subprocess.check_call(
[
"fx",
"--dir=out/default",
"set",
"workstation_eng.x64",
"--with=//bundles/fidl:tests",
"--with=//bundles/kitchen_sink",
"--with=//bundles/tests",
"--with=//bundles/buildbot/core",
"--with=//sdk/fidl/fuchsia.firebase.messaging:fuchsia.firebase.messaging",
"--with=//sdk/fidl/fuchsia.metricbroker:fuchsia.metricbroker",
"--with=//sdk/fidl/fuchsia.process:fuchsia.process",
"--with=//sdk/fidl/fuchsia.process.init:fuchsia.process.init",
"--with=//sdk/fidl/fuchsia.terminal:fuchsia.terminal",
"--with=//src/camera/bin/factory:fuchsia.factory.camera",
"--with=//src/media/vnext/fidl/fuchsia.audio:fuchsia.audio",
"--with=//src/media/vnext/fidl/fuchsia.media2:fuchsia.media2",
"--with=//src/media/vnext/fidl/fuchsia.mediastreams:fuchsia.mediastreams",
"--with=//src/media/vnext/fidl/fuchsia.mem2:fuchsia.mem2",
"--cargo-toml-gen",
],
stdout=sys.stdout,
)
print("\nBUILDING...")
subprocess.check_call(["fx", "build"], stdout=sys.stdout)
print("\nMATCHING... (takes a few min)")
converted, unconverted = match_converted_files()
ready = len(converted)
total = ready + len(unconverted)
print("\nCOULD NOT MATCH THE FOLLOWING FILES:")
for u in unconverted:
print(u)
if args.dryrun:
print("MATCHED %s OF %d FIDL FILES" % (ready, total))
else:
for old, new in converted.items():
try:
subprocess.check_call(["cp", "-fT", new, old])
except subprocess.CalledProcessError as e:
print(e.output)
print("SUCCESSFULLY CONVERTED %s OF %d FIDL FILES" % (ready, total))
def match_converted_files():
"""Attempts to match each source FIDL file with its converted `.fidl.new`
counterpart in the `out/default` directory. Returns a tuple, with the first
value being a map of old_file -> converted_file, and the second being a list
of old files that could not be converted.
"""
converted = {}
unconverted = []
# Match each source file with its converted copy.
for path in FUCHSIA_DIR.rglob("*.fidl"):
if not path.is_file():
continue
old_syntax_path = path.relative_to(FUCHSIA_DIR)
# These are not fuchsia.git source files - ignore them.
if str(old_syntax_path).startswith("out/"):
continue
if str(old_syntax_path).startswith("prebuilt/"):
continue
if str(old_syntax_path).startswith("third_party/"):
continue
if str(old_syntax_path).startswith("vendor/"):
continue
new_syntax_path = (
FUCHSIA_DIR / "out/default/fidling/gen" / old_syntax_path
)
new_syntax_path = new_syntax_path.with_suffix(".fidl.new")
# The default case: the file's output location matches its location in
# the source, save a slight redirection to the output directory.
if new_syntax_path.exists():
converted[str(old_syntax_path)] = str(new_syntax_path)
continue
# Sometimes, a file stored in `foo/fidl/my.fidl` is output into
# a directory called `foo` rather than `foo/fidl`. Check for such
# cases.
new_syntax_path = "/".join(str(new_syntax_path).rsplit("/fidl/", 1))
if Path(new_syntax_path).exists():
converted[str(old_syntax_path)] = str(new_syntax_path)
continue
unconverted.append(str(old_syntax_path))
# But wait! There's still a couple of things we can try for all of the
# old syntax FIDL files we've not been able to successfully pair with a
# converted copy. First, we'll try using the `fx gn outputs` command on
# each of the old files, in order to find outputs written to unorthodox
# output directory paths. Because this command can take a long time, we'll
# use a thread pool to parallelize the invocations.
pool = Pool(16)
for matched in pool.map(match_converted_file, unconverted):
if matched is not None:
converted[matched[0]] = matched[1]
unconverted.remove(matched[0])
# One more thing we can try: just attempt to run fidlc on each of the
# remaining files. This will fail for FIDL libraries that import
# dependencies via the `using` command, or are defined over multiple source
# files, but it will at least cover us for simple test files (DIFL, etc).
for matched in pool.map(try_convert_fidl_file, unconverted):
if matched is not None:
converted[matched[0]] = matched[1]
unconverted.remove(matched[0])
# Whatever is left at this point truly, really needs to be converted by
# hand.
return converted, unconverted
def match_converted_file(old_syntax_path_str):
"""This function uses `fx gn outputs` to search for any output files located
in unconventionally named outputs directories. It will return None on
failure, and a (old_file_path, converted_file_path) tuple on success.
"""
new_syntax_path = Path(old_syntax_path_str).with_suffix(".fidl.new")
new_file_name = str(new_syntax_path.name)
result = subprocess.run(
["fx", "gn", "outputs", "out/default", old_syntax_path_str],
capture_output=True,
)
for line in result.stdout.decode("utf-8").splitlines():
stripped = str(line.strip())
if stripped.endswith(new_file_name):
# print(' * ' + old_syntax_path_str + ' [MATCHED]')
return old_syntax_path_str, "out/default/" + stripped
# print(' * ' + old_syntax_path_str + ' [NOT FOUND]')
return None
def try_convert_fidl_file(old_syntax_path_str):
"""This function will successfully convert any FIDL library that does not
have any `using` imports and is defined in a single source file. It will
return None on failure, and a (old_file_path, converted_file_path) tuple on
success.
"""
tmpdir = Path.home() / "tmp/fidl-migration"
new_syntax_path = tmpdir / old_syntax_path_str
new_syntax_path = new_syntax_path.with_suffix(".fidl.new")
new_syntax_dir = new_syntax_path.parent
subprocess.check_call(["mkdir", "-p", new_syntax_dir], stdout=sys.stdout)
# Try to convert the file assuming that it is a standalone library with no
# imports. If it works, great. If not, return None, so that we may record
# this file as unconvertable.
fidlc = FUCHSIA_DIR / "out/default/host_x64/exe.unstripped/fidlc"
old_file = FUCHSIA_DIR / Path(old_syntax_path_str)
try:
subprocess.check_call(
[
fidlc,
"--experimental",
"old_syntax_only",
"--convert-syntax",
new_syntax_dir,
"--files",
old_file,
],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
except subprocess.CalledProcessError as e:
# print(' * ' + old_syntax_path_str + ' [ERRORED]')
return None
# print(' * ' + old_syntax_path_str + ' [MATCHED]')
return old_syntax_path_str, str(new_syntax_path)
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Copies files converted by fidlc via a flag-enabled mode back into the source tree."
)
parser.add_argument(
"--dry-run",
dest="dryrun",
action=argparse.BooleanOptionalAction,
default=False,
)
parser.set_defaults(dryrun=True)
parser.add_argument(
"--rebuild",
dest="rebuild",
action=argparse.BooleanOptionalAction,
default=False,
)
parser.set_defaults(rebuild=True)
main(parser.parse_args())