blob: 46573f33a03bc77262957b111ef47986d817e1ad [file] [log] [blame]
#!/usr/bin/env python
# Copyright 2017 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 paths
import platform
import re
import shutil
import subprocess
import sys
import tempfile
sys.path += [os.path.join(paths.FUCHSIA_ROOT, "third_party", "pytoml")]
import pytoml as toml
from check_rust_licenses import check_licenses
CONFIGS = [
"apps/xi/modules/xi-core",
"lib/fidl/rust/fidl",
"rust/magenta-rs",
"rust/magenta-rs/magenta-sys",
"rust/rust_sample_module",
"third_party/xi-editor/rust/core-lib",
]
NATIVE_LIBS = {
"cairo": "//third_party/cairo",
}
def get_cargo_bin():
host_os = platform.system()
if host_os == "Darwin":
host_triple = "x86_64-apple-darwin"
elif host_os == "Linux":
host_triple = "x86_64-unknown-linux-gnu"
else:
raise Exception("Platform not supported: %s" % host_os)
return os.path.join(paths.FUCHSIA_ROOT, "buildtools", "rust",
"rust-%s" % host_triple, "bin", "cargo")
def parse_dependencies(lock_path):
"""Extracts the crate dependency tree from a lockfile."""
result = []
with open(lock_path, "r") as lock_file:
content = toml.load(lock_file)
dep_matcher = re.compile("^([^\s]+)\s([^\s]+)")
for package in content["package"]:
deps = []
if "dependencies" in package:
for dep in package["dependencies"]:
match = dep_matcher.match(dep)
if match:
deps.append("%s-%s" % (match.group(1), match.group(2)))
label = "%s-%s" % (package["name"], package["version"])
result.append({
"name": package["name"],
"label": label,
"deps": deps,
})
return result
def filter_non_vendor_crates(crates, vendor_dir):
"""Removes crates that are not vendored from the given list."""
def is_3p(c): return os.path.isdir(os.path.join(vendor_dir, c["label"]))
return filter(is_3p, crates)
def add_native_libraries(crates, vendor_dir):
"""Returns true if all native libraries could be identified and added to
the given crate metadata."""
result = True
for crate in crates:
config_path = os.path.join(vendor_dir, crate["label"], "Cargo.toml")
with open(config_path, "r") as config_file:
config = toml.load(config_file)
if "links" in config["package"]:
library = config["package"]["links"]
if library not in NATIVE_LIBS:
print("Unknown native library: %s" % library)
result = False
continue
crate["native_lib"] = library
return result
def generate_build_file(build_path, crates):
"""Creates a BUILD.gn file for the given crates."""
crates.sort(key=lambda c: c["label"])
with open(build_path, "w") as build_file:
build_file.write("""# Generated by //scripts/update_rust_crates.py.
import("//build/rust/rust_info.gni")
""")
for info in crates:
build_file.write("""
rust_info("%s") {
name = "%s"
deps = [
""" % (info["label"], info["name"]))
for dep in info["deps"]:
build_file.write(" \":%s\",\n" % dep)
build_file.write(" ]\n")
if "native_lib" in info:
lib = info["native_lib"]
build_file.write("""
native_lib = \"%s\"
non_rust_deps = [
\"%s\",
]
""" % (lib, NATIVE_LIBS[lib]))
build_file.write("}\n")
def call_or_exit(args, dir):
if subprocess.call(args, cwd=dir) != 0:
raise Exception("Command failed in %s: %s" % (dir, " ".join(args)))
def main():
parser = argparse.ArgumentParser("Updates third-party Rust crates")
parser.add_argument("--cargo-vendor",
help="Path to the cargo-vendor command",
required=True)
parser.add_argument("--debug",
help="Debug mode",
action="store_true")
args = parser.parse_args()
# Use the root of the tree as the working directory. Ideally a temporary
# directory would be used, but unfortunately this would break the flow as
# the configs used to seed the vendor directory must be under a common
# parent directory.
base_dir = paths.FUCHSIA_ROOT
toml_path = os.path.join(base_dir, "Cargo.toml")
lock_path = os.path.join(base_dir, "Cargo.lock")
try:
print("Downloading dependencies for:")
for config in CONFIGS:
print(" - %s" % config)
# Create Cargo.toml.
def mapper(p): return os.path.join(paths.FUCHSIA_ROOT, p)
config = {
"workspace": {
"members": list(map(mapper, CONFIGS))
}
}
with open(toml_path, "w") as config_file:
toml.dump(config, config_file)
cargo_bin = get_cargo_bin()
# Generate Cargo.lock.
lockfile_args = [
cargo_bin,
"generate-lockfile",
]
call_or_exit(lockfile_args, base_dir)
crates = parse_dependencies(lock_path)
# Populate the vendor directory.
vendor_args = [
args.cargo_vendor,
"-x",
"--sync",
lock_path,
"vendor",
]
call_or_exit(vendor_args, base_dir)
finally:
if not args.debug:
os.remove(toml_path)
os.remove(lock_path)
crates_dir = os.path.join(paths.FUCHSIA_ROOT, "third_party", "rust-crates")
vendor_dir = os.path.join(crates_dir, "vendor")
shutil.rmtree(vendor_dir)
shutil.move(os.path.join(paths.FUCHSIA_ROOT, "vendor"), vendor_dir)
# Remove members of the workspace from the list of crates.
crates = filter_non_vendor_crates(crates, vendor_dir)
if not add_native_libraries(crates, vendor_dir):
print("Unable to identify all required native libraries.")
return 1
build_path = os.path.join(crates_dir, "BUILD.gn")
generate_build_file(build_path, crates)
print("Verifying licenses...")
if not check_licenses(vendor_dir):
print("Some licenses are missing!")
return 1
update_file = os.path.join(crates_dir, ".vendor-update.stamp")
# Write the timestamp file.
# This file is necessary in order to trigger rebuilds of Rust artifacts
# whenever third-party dependencies are updated.
with open(update_file, "a"):
os.utime(update_file, None)
print("Vendor directory updated at %s" % vendor_dir)
if __name__ == '__main__':
sys.exit(main())