| # 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 hashlib |
| import os |
| import platform |
| import re |
| import sys |
| import subprocess |
| |
| ROOT_PATH = os.environ["FUCHSIA_DIR"] |
| FX_PATH = os.path.join(ROOT_PATH, "scripts", "fx") |
| FUCHSIA_BUILD_DIR = os.environ["FUCHSIA_BUILD_DIR"] |
| PREBUILT_DIR = os.path.join(ROOT_PATH, "prebuilt") |
| PREBUILT_THIRD_PARTY_DIR = os.path.join(PREBUILT_DIR, "third_party") |
| HOST_PLATFORM = "%s-%s" % ( |
| platform.system().lower().replace("darwin", "mac"), |
| { |
| "x86_64": "x64", |
| "aarch64": "arm64", |
| }[platform.machine()], |
| ) |
| |
| |
| class GnTarget: |
| |
| def __init__(self, gn_target): |
| # [\w-] is a valid GN name. We also accept '/' and '.' in paths. |
| # For the toolchain suffix, we take the whole label name at once, so we allow ':'. |
| match = re.match( |
| r"([\w/.-]*)" + r"(:([\w-]+))?" + r"(\(([\w/:-]+)\))?$", gn_target) |
| if match is None: |
| print(f"Invalid GN label '{gn_target}'") |
| raise ValueError(gn_target) |
| path, name, toolchain = match.group(1, 3, 5) |
| |
| if path.startswith("//"): |
| path = path[2:] |
| else: |
| abs_path = os.path.join(os.path.abspath("."), path) |
| path = os.path.relpath(abs_path, ROOT_PATH) |
| |
| if name is None: |
| name = os.path.basename(path) |
| |
| self.label_path = path |
| self.label_name = name |
| self.explicit_toolchain = toolchain |
| |
| def __str__(self): |
| return self.gn_target |
| |
| @property |
| def ninja_target(self): |
| """The canonical GN label of this target, minus the leading '//'.""" |
| return '%s:%s%s' % ( |
| self.label_path, self.label_name, self.toolchain_suffix) |
| |
| @property |
| def gn_target(self): |
| """The canonical GN label of this target, including the leading '//'.""" |
| return '//%s' % self.ninja_target |
| |
| @property |
| def toolchain_suffix(self): |
| """The GN path suffix for this target's toolchain, if it is not the default.""" |
| if self.explicit_toolchain is None: |
| return '' |
| if 'fuchsia' in self.explicit_toolchain: |
| # Default toolchain. |
| return '' |
| return '(%s)' % self.explicit_toolchain |
| |
| @property |
| def src_path(self): |
| """The absolute path to the directory containing this target's BUILD.gn file.""" |
| return os.path.join(ROOT_PATH, self.label_path) |
| |
| def manifest_path(self, build_dir=None): |
| """The path to Cargo.toml for this target.""" |
| if build_dir is None: |
| build_dir = FUCHSIA_BUILD_DIR |
| |
| hashed_gn_path = hashlib.sha1(str( |
| self.ninja_target).encode("utf-8")).hexdigest() |
| return os.path.join(build_dir, "cargo", hashed_gn_path, "Cargo.toml") |
| |
| |
| def get_rust_target_from_file(file): |
| """Given a Rust file, return a GN target that references it. Raises ValueError if the file |
| cannot be converted to a target.""" |
| if not file.endswith(".rs"): |
| return None, "Not a Rust file." |
| # Query ninja to find the output file. |
| ninja_query_args = [ |
| os.path.join(PREBUILT_THIRD_PARTY_DIR, "ninja", HOST_PLATFORM, "ninja"), |
| "-C", |
| FUCHSIA_BUILD_DIR, |
| "-t", |
| "query", |
| os.path.relpath(file, FUCHSIA_BUILD_DIR), |
| ] |
| |
| p = subprocess.Popen( |
| ninja_query_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
| out, err = p.communicate() |
| if p.returncode: |
| print(err) |
| raise None |
| |
| # Expected Ninja query output is: |
| # ../../filename.rs: |
| # outputs: |
| # rust_crates/binary |
| lines = out.splitlines() |
| if len(lines) < 3: |
| print(f"Unexpected Ninja output: {out}") |
| return None |
| |
| output_files = [ |
| os.path.join(FUCHSIA_BUILD_DIR, l.strip()) for l in lines[2:] |
| ] |
| |
| # For each output file in Ninja, check to see if it's produced by a Rust build |
| # target. If so, return the base target name. |
| for output_file in output_files: |
| # Query GN to get the target that produced that output. |
| gn_refs_args = [ |
| os.path.join(PREBUILT_THIRD_PARTY_DIR, "gn", HOST_PLATFORM, "gn"), |
| "refs", |
| FUCHSIA_BUILD_DIR, |
| output_file, |
| ] |
| |
| p = subprocess.Popen( |
| gn_refs_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
| out, err = p.communicate() |
| if p.returncode: |
| print(err) |
| raise None |
| |
| # Expected GN refs output is: |
| # //path/to/target:bin_build |
| # //path/to/target:bin_copy |
| lines = out.splitlines() |
| for line in lines: |
| line = line.strip() |
| if line.endswith("_build"): |
| return GnTarget(line.rstrip("_build")) |
| |
| print(f"Unable to find Rust build target for {file}") |
| return None |