| #!/usr/bin/env python |
| # Copyright 2018 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 json |
| import os |
| import subprocess |
| import sys |
| |
| ROOT_PATH = os.path.abspath(__file__ + "/../../..") |
| sys.path += [os.path.join(ROOT_PATH, "third_party", "pytoml")] |
| import pytoml |
| |
| # "foo" from "foo 0.1.0 (//path/to/crate)" |
| def package_name_from_crate_id(crate_id): |
| return crate_id.split(" ")[0] |
| |
| # Removes the (//path/to/crate) from the crate id "foo 0.1.0 (//path/to/crate)" |
| # This is necessary in order to support locally-patched (mirrored) crates |
| def pathless_crate_id(crate_id): |
| split_id = crate_id.split(" ") |
| return split_id[0] + " " + split_id[1] |
| |
| # Creates the directory containing the given file. |
| def create_base_directory(file): |
| path = os.path.dirname(file) |
| try: |
| os.makedirs(path) |
| except os.error: |
| # Already existed. |
| pass |
| |
| # Runs the given command and returns its return code and output. |
| def run_command(args, env, cwd): |
| job = subprocess.Popen(args, env=env, cwd=cwd, stdout=subprocess.PIPE, |
| stderr=subprocess.PIPE) |
| stdout, stderr = job.communicate() |
| return (job.returncode, stdout, stderr) |
| |
| def main(): |
| parser = argparse.ArgumentParser("Compiles all third-party Rust crates") |
| parser.add_argument("--rustc", |
| help="Path to rustc", |
| required=True) |
| parser.add_argument("--cargo", |
| help="Path to the cargo tool", |
| required=True) |
| parser.add_argument("--crate-root", |
| help="Path to the crate root", |
| required=True) |
| parser.add_argument("--opt-level", |
| help="Optimization level", |
| required=True, |
| choices=["0", "1", "2", "3", "s", "z"]) |
| parser.add_argument("--symbol-level", |
| help="Symbols to include (0=none, 1=minimal, 2=full)", |
| choices=["0", "1", "2"], |
| required=True) |
| parser.add_argument("--out-dir", |
| help="Path at which the output libraries should be stored", |
| required=True) |
| parser.add_argument("--out-deps-data", |
| help="File in which data about the dependencies should be stored", |
| required=True) |
| parser.add_argument("--target", |
| help="Target for which this crate is being compiled", |
| required=True) |
| parser.add_argument("--cmake-dir", |
| help="Path to the directory containing cmake", |
| required=True) |
| parser.add_argument("--clang_prefix", |
| help="Path to the clang prefix", |
| required=True) |
| parser.add_argument("--clang-resource-dir", |
| help="Path to the clang resource dir", |
| required=True) |
| parser.add_argument("--mmacosx-version-min", |
| help="Select macosx framework version", |
| required=False) |
| # TODO(cramertj) make these args required when the third_party/rust_crates change lands |
| parser.add_argument("--sysroot", |
| help="Path to the sysroot", |
| required=False) |
| parser.add_argument("--lib-dir", |
| help="Path to the location of shared libraries", |
| action='append', default=[]) |
| parser.add_argument("--cipd-version", |
| help="CIPD version of Rust toolchain", |
| required=False) |
| parser.add_argument |
| args = parser.parse_args() |
| |
| env = os.environ.copy() |
| create_base_directory(args.out_dir) |
| |
| clang_c_compiler = os.path.join(args.clang_prefix, "clang") |
| |
| rustflags = [ |
| "-Copt-level=" + args.opt_level, |
| "-Cdebuginfo=" + args.symbol_level, |
| ] |
| |
| if args.target.endswith("fuchsia"): |
| if args.target.startswith("aarch64"): |
| rustflags += ["-Clink-arg=--fix-cortex-a53-843419"] |
| rustflags += [ |
| "-L", os.path.join(args.sysroot, "lib"), |
| "-Clink-arg=--pack-dyn-relocs=relr", |
| "-Clink-arg=--threads", |
| "-Clink-arg=-L%s" % os.path.join(args.sysroot, "lib"), |
| "-Clink-arg=-L%s" % os.path.join(args.clang_resource_dir, args.target, "lib"), |
| ] |
| if args.sysroot: |
| rustflags.append("-Clink-arg=--sysroot=" + args.sysroot) |
| for dir in args.lib_dir: |
| rustflags.append("-Lnative=" + dir) |
| else: |
| if args.target.startswith("aarch64"): |
| rustflags += ["-Clink-arg=-Wl,--fix-cortex-a53-843419"] |
| if args.target.endswith("linux-gnu"): |
| rustflags += ["-Clink-arg=-Wl,--build-id"] |
| if not args.target.endswith("darwin"): |
| rustflags += ["-Clink-arg=-Wl,--threads"] |
| env["CARGO_TARGET_LINKER"] = clang_c_compiler |
| env["CARGO_TARGET_X86_64_APPLE_DARWIN_LINKER"] = clang_c_compiler |
| env["CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER"] = clang_c_compiler |
| env["CARGO_TARGET_%s_LINKER" % args.target.replace("-", "_").upper()] = clang_c_compiler |
| rustflags += ["-Clink-arg=--target=" + args.target] |
| |
| if args.mmacosx_version_min: |
| rustflags += ["-Clink-arg=-mmacosx-version-min=%s" % args.mmacosx_version_min] |
| env["CARGO_TARGET_%s_RUSTFLAGS" % args.target.replace("-", "_").upper()] = ( |
| ' '.join(rustflags) |
| ) |
| env["CARGO_TARGET_DIR"] = args.out_dir |
| env["CARGO_BUILD_DEP_INFO_BASEDIR"] = args.out_dir |
| env["RUSTC"] = args.rustc |
| env["RUST_BACKTRACE"] = "1" |
| env["CC"] = clang_c_compiler |
| if args.sysroot: |
| env["CFLAGS"] = " ".join(["--sysroot=" + args.sysroot] + |
| ["-L" + dir for dir in args.lib_dir]) |
| env["CXX"] = os.path.join(args.clang_prefix, "clang++") |
| env["AR"] = os.path.join(args.clang_prefix, "llvm-ar") |
| env["RANLIB"] = os.path.join(args.clang_prefix, "llvm-ranlib") |
| env["PATH"] = "%s:%s" % (env["PATH"], args.cmake_dir) |
| |
| call_args = [ |
| args.cargo, |
| "build", |
| "--color=always", |
| "--target=%s" % args.target, |
| "--frozen", |
| ] |
| |
| call_args.append("--message-format=json") |
| |
| retcode, stdout, stderr = run_command(call_args, env, args.crate_root) |
| if retcode != 0: |
| # The output is not particularly useful as it is formatted in JSON. |
| # Re-run the command with a user-friendly format instead. |
| del call_args[-1] |
| _, stdout, stderr = run_command(call_args, env, args.crate_root) |
| print(stdout + stderr) |
| return retcode |
| |
| cargo_toml_path = os.path.join(args.crate_root, "Cargo.toml") |
| with open(cargo_toml_path, "r") as file: |
| cargo_toml = pytoml.load(file) |
| |
| crate_id_to_info = {} |
| deps_folders = set() |
| for line in stdout.splitlines(): |
| data = json.loads(line) |
| if "filenames" not in data: |
| continue |
| crate_id = pathless_crate_id(data["package_id"]) |
| assert len(data["filenames"]) == 1 |
| lib_path = data["filenames"][0] |
| |
| # For libraries built for both the host and the target, pick |
| # the one being built for the target. |
| # If we've already seen a lib with the given ID and the new one doesn't |
| # contain our target, discard it and keep the current entry. |
| if (crate_id in crate_id_to_info) and (args.target not in lib_path): |
| continue |
| |
| crate_name = data["target"]["name"] |
| |
| # Build scripts built for our dependencies unfortunately have the same |
| # package ID as the crates themselves. In order to distinguish them, |
| # we look for the crate name that cargo uses for these artifacts, |
| # "build-script-build". |
| if crate_name == "build-script-build": |
| continue |
| |
| if crate_name != "fuchsia-third-party": |
| # go from e.g. target/debug/deps/libfoo.rlib to target/debug/deps |
| deps_folders.add(os.path.dirname(lib_path)) |
| |
| crate_id_to_info[crate_id] = { |
| "crate_name": crate_name, |
| "lib_path": lib_path, |
| } |
| |
| cargo_lock_path = os.path.join(args.crate_root, "Cargo.lock") |
| with open(cargo_lock_path, "r") as file: |
| cargo_lock_toml = pytoml.load(file) |
| |
| # output the info for fuchsia-third-party's direct dependencies |
| crates = {} |
| cargo_dependencies = cargo_toml["dependencies"] |
| fuchsia_target_spec = 'cfg(target_os = "fuchsia")' |
| non_fuchsia_target_spec = 'cfg(not(target_os = "fuchsia"))' |
| if "fuchsia" in args.target: |
| target_spec = fuchsia_target_spec |
| non_target_spec = non_fuchsia_target_spec |
| else: |
| target_spec = non_fuchsia_target_spec |
| non_target_spec = fuchsia_target_spec |
| target_only_deps = cargo_toml \ |
| .get("target", {}) \ |
| .get(target_spec, {}) \ |
| .get("dependencies", []) |
| cargo_dependencies.update(target_only_deps) |
| other_target_deps = cargo_toml \ |
| .get("target", {}) \ |
| .get(non_target_spec, {}) \ |
| .get("dependencies", []) |
| for package in cargo_lock_toml["package"]: |
| if package["name"] == "fuchsia-third-party": |
| for crate_id in package["dependencies"]: |
| crate_id = pathless_crate_id(crate_id) |
| if crate_id in crate_id_to_info: |
| crate_info = crate_id_to_info[crate_id] |
| crate_name = crate_info["crate_name"] |
| package_name = package_name_from_crate_id(crate_id) |
| if package_name in cargo_dependencies: |
| crate_info["cargo_dependency_toml"] = cargo_dependencies[package_name] |
| crates[package_name] = crate_info |
| elif package_name not in other_target_deps: |
| print (package_name + " not found in Cargo.toml dependencies section") |
| return 1 |
| |
| # normalize paths for patches |
| patches = cargo_toml["patch"]["crates-io"] |
| for patch in patches: |
| path = patches[patch]["path"] |
| path = os.path.join(args.crate_root, path) |
| patches[patch] = { "path": path } |
| |
| create_base_directory(args.out_deps_data) |
| with open(args.out_deps_data, "w") as file: |
| file.write(json.dumps({ |
| "crates": crates, |
| "deps_folders": list(deps_folders), |
| "patches": patches, |
| }, sort_keys=True, indent=4, separators=(",", ": "))) # for humans |
| |
| if __name__ == '__main__': |
| sys.exit(main()) |