blob: d7e8fb30b2693d9090560175a175f9dbe34a48a6 [file] [log] [blame]
#!/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
# 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"
name = "%(package_name)s"
version = "%(version)s"
license = "BSD-3-Clause"
authors = [""]
description = "Rust crate for Fuchsia OS"
repository = ""
edition = "%(edition)s"
name = "%(crate_name)s"
path = "%(source_root)s"
version = "0.0.1"
path = "%(crate_path)s"
def strip_toolchain(target):
return"[^(]*", 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["targets"]
self.build_settings = project_json["build_settings"]
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
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 write_toml_file(fout, metadata, project, target, lookup):
root_path = project.build_settings["root_path"]
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",
target_type = "[lib]"
if "--test" in metadata["rustflags"]:
target_type = "[[test]]"
target_type = "[[bin]]"
if metadata["type"] == "rust_proc_macro":
is_proc_macro = "proc-macro = true"
is_proc_macro = ""
features = []
feature_pat = re.compile(r"--cfg=feature=\"(.*)\"$")
for flag in metadata["rustflags"]:
match = feature_pat.match(flag)
if match:
crate_type = "rlib"
package_name = lookup_gn_pkg_name(project, target)
"target": target,
"package_name": package_name,
"crate_name": metadata["crate_name"],
"version": "0.0.1",
"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:
# 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)
for patch in project.patches:
path = project.patches[patch]["path"]
"%s = { path = \"%s/%s\" }\n" % (patch, rust_crates_path, path))
# collect all dependencies
deps = []
for dep in metadata["deps"]:
# handle proc macro shims:
dep = project.dereference_group(dep)
# 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 ="rust_crates:([\w-]*)", dep)
crate_name, version = str("-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:
"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 = rebase_gn_path(
root_path, project.build_settings["build_dir"] + "cargo/" +
"crate_path": dep_dir,
"crate_name": crate_name,
def main():
# TODO(tmandry): Remove all hardcoded paths and replace with args.
parser = argparse.ArgumentParser()
args = parser.parse_args()
json_path = args.json_path
project = None
with open(json_path, "r") as json_file:
project = json.loads(
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 = project.build_settings["root_path"]
build_dir = project.build_settings["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):
project.third_party_features[dep] = FeatureSpec(
info.get("features", []), info.get("default-features", True))
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 = rebase_gn_path(
root_path, project.build_settings["build_dir"] + "cargo/")
shutil.rmtree(gn_cargo_dir, ignore_errors=True)
# 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:
# And a depfile so GN knows to run this script again when the json file
# changes. Dependencies on the third-party build are tracked within GN.
with open(os.path.join(gn_cargo_dir, "generate_cargo.stamp.d"), "w") as f:
f.write("cargo/generate_cargo.stamp: %s\n" % json_path)
for target in project.rust_targets():
cargo_toml_dir = rebase_gn_path(
root_path, project.build_settings["build_dir"] + "cargo/" +
except OSError:
print("Failed to create directory for Cargo: %s" % cargo_toml_dir)
metadata = project.targets[target]
with open(cargo_toml_dir + "/Cargo.toml", "w") as fout:
write_toml_file(fout, metadata, project, target, lookup)
return 0
if __name__ == "__main__":