|  | #!/usr/bin/env python2.7 | 
|  | # | 
|  | # 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 os | 
|  | import argparse | 
|  | import hashlib | 
|  | import shutil | 
|  | import re | 
|  | import sys | 
|  | import json | 
|  | import datetime | 
|  |  | 
|  | ROOT_PATH = os.path.abspath(__file__ + "/../..") | 
|  | sys.path += [os.path.join(ROOT_PATH, "third_party", "pytoml")] | 
|  | import pytoml as toml | 
|  |  | 
|  | CARGO_PACKAGE_CONTENTS = """\ | 
|  | # Copyright %(year)s 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. | 
|  |  | 
|  | # source GN: %(target)s" | 
|  |  | 
|  | [package] | 
|  | name = "%(package_name)s" | 
|  | version = "%(version)s" | 
|  | license = "BSD-3-Clause" | 
|  | authors = ["rust-fuchsia@fuchsia.com"] | 
|  | description = "Rust crate for Fuchsia OS" | 
|  | repository = "https://fuchsia.googlesource.com" | 
|  | edition = "%(edition)s" | 
|  |  | 
|  | %(bin_or_lib)s | 
|  | %(is_proc_macro)s | 
|  | name = "%(crate_name)s" | 
|  | path = "%(source_root)s" | 
|  | """ | 
|  |  | 
|  | CARGO_PACKAGE_DEP = """\ | 
|  | [dependencies.%(crate_name)s] | 
|  | version = "0.0.1" | 
|  | path = "%(crate_path)s" | 
|  |  | 
|  | """ | 
|  |  | 
|  |  | 
|  | def strip_toolchain(target): | 
|  | return re.search("[^(]*", target)[0] | 
|  |  | 
|  |  | 
|  | def lookup_gn_pkg_name(project, target): | 
|  | metadata = project.targets[target] | 
|  | return metadata["output_name"] | 
|  |  | 
|  |  | 
|  | def rebase_gn_path(root_path, location, directory=False): | 
|  | assert location[0:2] == "//" | 
|  | # remove the prefix // | 
|  | path = location[2:] | 
|  | target = os.path.dirname(path) if directory else path | 
|  | return os.path.join(root_path, target) | 
|  |  | 
|  |  | 
|  | class FeatureSpec(object): | 
|  |  | 
|  | def __init__(self, features, default_features): | 
|  | self.features = features | 
|  | self.default_features = default_features | 
|  |  | 
|  |  | 
|  | class Project(object): | 
|  |  | 
|  | def __init__(self, project_json): | 
|  | self.targets = project_json | 
|  | self.patches = None | 
|  | self.third_party_features = {} | 
|  |  | 
|  | def rust_targets(self): | 
|  | for target in self.targets.keys(): | 
|  | if "crate_root" in self.targets[target]: | 
|  | yield target | 
|  |  | 
|  | def dereference_group(self, target): | 
|  | """Dereference proc macro shims. | 
|  |  | 
|  | If the target happens to be a group which just redirects you to a | 
|  | different target, returns the real target label. Otherwise, returns | 
|  | target. | 
|  | """ | 
|  | meta = self.targets[target] | 
|  | if meta["type"] == "group": | 
|  | if len(meta["deps"]) == 1: | 
|  | dep = meta["deps"][0] | 
|  | dep_meta = self.targets[dep] | 
|  | return dep | 
|  | return target | 
|  |  | 
|  | def expand_source_set(self, target): | 
|  | """Returns a list of dependencies if the target is a source_set. | 
|  |  | 
|  | Returns dependencies as a list of strings if the target is a | 
|  | source_set, or None otherwise. | 
|  | """ | 
|  | meta = self.targets[target] | 
|  | if meta["type"] == "source_set": | 
|  | return meta["deps"] | 
|  |  | 
|  |  | 
|  | def write_toml_file( | 
|  | fout, metadata, project, target, lookup, root_path, root_build_dir): | 
|  | rust_crates_path = os.path.join(root_path, "third_party/rust_crates") | 
|  |  | 
|  | edition = "2018" if "--edition=2018" in metadata["rustflags"] else "2015" | 
|  |  | 
|  | if metadata["type"] in ["rust_library", "rust_proc_macro", | 
|  | "static_library"]: | 
|  | target_type = "[lib]" | 
|  | else: | 
|  | if "--test" in metadata["rustflags"]: | 
|  | target_type = "[[test]]" | 
|  | else: | 
|  | target_type = "[[bin]]" | 
|  |  | 
|  | if metadata["type"] == "rust_proc_macro": | 
|  | is_proc_macro = "proc-macro = true" | 
|  | else: | 
|  | is_proc_macro = "" | 
|  |  | 
|  | features = [] | 
|  | feature_pat = re.compile(r"--cfg=feature=\"(.*)\"$") | 
|  | for flag in metadata["rustflags"]: | 
|  | match = feature_pat.match(flag) | 
|  | if match: | 
|  | features.append(match.group(1)) | 
|  |  | 
|  | crate_type = "rlib" | 
|  | package_name = lookup_gn_pkg_name(project, target) | 
|  |  | 
|  | fout.write( | 
|  | CARGO_PACKAGE_CONTENTS % { | 
|  | "target": target, | 
|  | "package_name": package_name, | 
|  | "crate_name": metadata["crate_name"], | 
|  | "version": "0.0.1", | 
|  | "year": datetime.datetime.now().year, | 
|  | "bin_or_lib": target_type, | 
|  | "is_proc_macro": is_proc_macro, | 
|  | "lib_crate_type": crate_type, | 
|  | "edition": edition, | 
|  | "source_root": rebase_gn_path(root_path, metadata["crate_root"]), | 
|  | "crate_name": metadata["crate_name"], | 
|  | "rust_crates_path": rust_crates_path, | 
|  | }) | 
|  |  | 
|  | if features: | 
|  | fout.write("\n[features]\n") | 
|  | # Filter 'default' feature out to avoid generating a duplicated entry. | 
|  | features = filter(lambda x: x != "default", features) | 
|  | fout.write("default = %s\n" % json.dumps(features)) | 
|  | for feature in features: | 
|  | fout.write("%s = []\n" % feature) | 
|  |  | 
|  | fout.write("\n[patch.crates-io]\n") | 
|  | for patch in project.patches: | 
|  | path = project.patches[patch]["path"] | 
|  | fout.write( | 
|  | "%s = { path = \"%s/%s\" }\n" % (patch, rust_crates_path, path)) | 
|  | fout.write("\n") | 
|  |  | 
|  | # collect all dependencies | 
|  | deps = metadata["deps"] | 
|  | while deps: | 
|  | dep = deps.pop() | 
|  | # handle proc macro shims: | 
|  | dep = project.dereference_group(dep) | 
|  |  | 
|  | # If a dependency points to a source set, expand it into a list | 
|  | # of its deps, and append them to the deps list. Finally, continue | 
|  | # to the next item, since a source set itself is not considered a | 
|  | # dependency for our purposes. | 
|  | expanded_deps = project.expand_source_set(dep) | 
|  | if expanded_deps: | 
|  | deps.extend(expanded_deps) | 
|  | continue | 
|  |  | 
|  | # this is a third-party dependency | 
|  | # TODO remove this when all things use GN. temporary hack? | 
|  | if "third_party/rust_crates:" in dep: | 
|  | has_third_party_deps = True | 
|  | match = re.search("rust_crates:([\w-]*)", dep) | 
|  | crate_name, version = str(match.group(1)).rsplit("-v", 1) | 
|  | version = version.replace("_", ".") | 
|  | feature_spec = project.third_party_features.get(crate_name) | 
|  | fout.write("[dependencies.\"%s\"]\n" % crate_name) | 
|  | fout.write("version = \"%s\"\n" % version) | 
|  | if feature_spec: | 
|  | fout.write( | 
|  | "features = %s\n" % json.dumps(feature_spec.features)) | 
|  | if feature_spec.default_features is False: | 
|  | fout.write("default-features = false\n") | 
|  | # this is a in-tree rust target | 
|  | elif "crate_name" in project.targets[dep]: | 
|  | crate_name = lookup_gn_pkg_name(project, dep) | 
|  | output_name = project.targets[dep]["crate_name"] | 
|  | dep_dir = os.path.join(root_build_dir, "cargo", str(lookup[dep])) | 
|  | fout.write( | 
|  | CARGO_PACKAGE_DEP % { | 
|  | "crate_path": dep_dir, | 
|  | "crate_name": crate_name, | 
|  | }) | 
|  |  | 
|  |  | 
|  | def main(): | 
|  | # TODO(tmandry): Remove all hardcoded paths and replace with args. | 
|  | parser = argparse.ArgumentParser() | 
|  | parser.add_argument("--root_build_dir", required=True) | 
|  | parser.add_argument("--fuchsia_dir", required=True) | 
|  | parser.add_argument("json_path") | 
|  | args = parser.parse_args() | 
|  | json_path = args.json_path | 
|  |  | 
|  | project = None | 
|  | try: | 
|  | with open(json_path, "r") as json_file: | 
|  | project = json.loads(json_file.read()) | 
|  | except IOError: | 
|  | print("Failed to generate Cargo.toml files") | 
|  | print("No project.json in the root of your out directory!") | 
|  | print("Run gn with the --ide=json flag set") | 
|  | # returns 0 so that CQ doesn't fail if this isn't set properly | 
|  | return 0 | 
|  |  | 
|  | project = Project(project) | 
|  | root_path = os.path.abspath(args.fuchsia_dir) | 
|  | root_build_dir = os.path.abspath(args.root_build_dir) | 
|  |  | 
|  | rust_crates_path = os.path.join(root_path, "third_party/rust_crates") | 
|  |  | 
|  | # this will be removed eventually? | 
|  | with open(rust_crates_path + "/Cargo.toml", "r") as f: | 
|  | cargo_toml = toml.load(f) | 
|  | project.patches = cargo_toml["patch"]["crates-io"] | 
|  |  | 
|  | # Map from crate name to FeatureSpec. We don't include the version because we don't directly | 
|  | # depend on more than one version of the same crate. | 
|  | def collect_features(deps): | 
|  | for dep, info in deps.iteritems(): | 
|  | if isinstance(info, str) or isinstance(info, unicode): | 
|  | continue | 
|  | project.third_party_features[dep] = FeatureSpec( | 
|  | info.get("features", []), info.get("default-features", True)) | 
|  |  | 
|  | collect_features(cargo_toml["dependencies"]) | 
|  | for target_info in cargo_toml["target"].itervalues(): | 
|  | collect_features(target_info.get("dependencies", {})) | 
|  |  | 
|  | host_binaries = [] | 
|  | target_binaries = [] | 
|  |  | 
|  | lookup = {} | 
|  | for idx, target in enumerate(project.rust_targets()): | 
|  | # hash is the GN target name without the prefixed // | 
|  | lookup[target] = hashlib.sha1(target[2:].encode("utf-8")).hexdigest() | 
|  |  | 
|  | # remove the priorly generated rust crates | 
|  | gn_cargo_dir = os.path.join(root_build_dir, "cargo") | 
|  | shutil.rmtree(gn_cargo_dir, ignore_errors=True) | 
|  | os.makedirs(gn_cargo_dir) | 
|  | # Write a stamp file with a predictable name so the build system knows the | 
|  | # step ran successfully. | 
|  | with open(os.path.join(gn_cargo_dir, "generate_cargo.stamp"), "w") as f: | 
|  | f.truncate() | 
|  |  | 
|  | for target in project.rust_targets(): | 
|  | cargo_toml_dir = os.path.join(gn_cargo_dir, str(lookup[target])) | 
|  | try: | 
|  | os.makedirs(cargo_toml_dir) | 
|  | except OSError: | 
|  | print("Failed to create directory for Cargo: %s" % cargo_toml_dir) | 
|  |  | 
|  | metadata = project.targets[target] | 
|  | with open(os.path.join(cargo_toml_dir, "Cargo.toml"), "w") as fout: | 
|  | write_toml_file( | 
|  | fout, metadata, project, target, lookup, root_path, | 
|  | root_build_dir) | 
|  | return 0 | 
|  |  | 
|  |  | 
|  | if __name__ == "__main__": | 
|  | sys.exit(main()) |