|  | #!/usr/bin/env python | 
|  | # 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. | 
|  |  | 
|  | from __future__ import print_function | 
|  |  | 
|  | import argparse | 
|  | import os.path | 
|  | import sys | 
|  |  | 
|  | # Exempt targets with these prefixes. | 
|  | EXEMPTION_PREFIXES = [ | 
|  | # TODO(fxbug.dev/56885): cargo-gnaw should generate sources files for third party crates. | 
|  | '//third_party/rust_crates:', | 
|  | ] | 
|  |  | 
|  |  | 
|  | def parse_depfile(depfile_path): | 
|  | with open(depfile_path) as f: | 
|  | # Only the first line contains important information. | 
|  | line = f.readline().strip() | 
|  |  | 
|  | # The depfile format looks like `target: dep1 dep2 dep3...` | 
|  | # We assume the target is the one we care about and that dep1, dep2, dep3, etc. are | 
|  | # source files. | 
|  | # | 
|  | # We use `os.path.relpath` to convert paths like '../../out/default/foo/bar' to the | 
|  | # canonical 'foo/bar', which is how the expected inputs are expressed. | 
|  | return set( | 
|  | os.path.relpath(os.path.normpath(source)) | 
|  | for source in line[line.find(':') + 1:].split(' ') | 
|  | if source.strip()) | 
|  |  | 
|  |  | 
|  | def build_file_source_path(target, source_path): | 
|  | '''Returns a source path suitable for listing in a BUILD.gn file. | 
|  |  | 
|  | The returned path is relative to the `target` GN label, or source absolute if the | 
|  | `source_path` is not a descendent of the `target`. | 
|  |  | 
|  | Eg. for `target` of '//src/sys/component_manager:bin': | 
|  | when source_path='../../src/sys/component_manager/src/main.rs' | 
|  | return 'src/main.rs' | 
|  | when source_path='../../prebuilts/assets/font.ttf' | 
|  | return '//prebuilts/assets/font.ttf' | 
|  | ''' | 
|  | while source_path.startswith('../'): | 
|  | source_path = source_path[3:] | 
|  | target_dir = target[2:].split(':')[0] | 
|  | if source_path.startswith(target_dir): | 
|  | return os.path.relpath(source_path, start=target_dir) | 
|  | return '//{}'.format(source_path) | 
|  |  | 
|  |  | 
|  | def print_suggested_sources(varname, sources): | 
|  | '''Prints a GN list variable assignment with the variable name `varname`. | 
|  |  | 
|  | Eg. | 
|  |  | 
|  | sources = [ | 
|  | "src/main.rs", | 
|  | "src/foo.rs", | 
|  | ] | 
|  | ''' | 
|  | print('  {} = ['.format(varname), file=sys.stderr) | 
|  | for source in sources: | 
|  | print('    "{}",'.format(source), file=sys.stderr) | 
|  | print('  ]', file=sys.stderr) | 
|  |  | 
|  |  | 
|  | def main(): | 
|  | parser = argparse.ArgumentParser( | 
|  | description= | 
|  | 'Verifies that the compiler-emitted depfile strictly contains the expected source files' | 
|  | ) | 
|  | parser.add_argument( | 
|  | '-t', | 
|  | '--target_label', | 
|  | required=True, | 
|  | help='GN target label being checked') | 
|  | parser.add_argument( | 
|  | '-d', | 
|  | '--depfile', | 
|  | required=True, | 
|  | help='path to compiler emitted depfile') | 
|  | parser.add_argument( | 
|  | 'expected_sources', | 
|  | nargs='*', | 
|  | help='path to the expected list of source files') | 
|  | args = parser.parse_args() | 
|  |  | 
|  | # Check for opt-out. | 
|  | if args.expected_sources and args.expected_sources[0].endswith( | 
|  | '/build/rust/__SKIP_ENFORCEMENT__.rs'): | 
|  | return 0 | 
|  |  | 
|  | # Ignore specific target exemptions. | 
|  | for prefix in EXEMPTION_PREFIXES: | 
|  | if args.target_label.startswith(prefix): | 
|  | return 0 | 
|  |  | 
|  | expected_sources = set(args.expected_sources) | 
|  | actual_sources = parse_depfile(args.depfile) | 
|  |  | 
|  | unlisted_sources = actual_sources.difference(expected_sources) | 
|  | if unlisted_sources: | 
|  | # There is a mismatch in expected sources and actual sources used by the compiler. | 
|  | # We don't treat overly-specified sources as an error. Ninja will still complain | 
|  | # if those source files don't exist. | 
|  | for source in unlisted_sources: | 
|  | print( | 
|  | 'error: source file `{}` was used during compilation but not listed in BUILD.gn' | 
|  | .format(source), | 
|  | file=sys.stderr) | 
|  |  | 
|  | print( | 
|  | 'note: the BUILD.gn file for {} should have the following:\n'. | 
|  | format(args.target_label), | 
|  | file=sys.stderr) | 
|  |  | 
|  | rust_sources = [ | 
|  | build_file_source_path(args.target_label, source) | 
|  | for source in actual_sources | 
|  | if source.endswith('.rs') | 
|  | ] | 
|  | if rust_sources: | 
|  | print_suggested_sources('sources', rust_sources) | 
|  |  | 
|  | non_rust_sources = [ | 
|  | build_file_source_path(args.target_label, source) | 
|  | for source in actual_sources | 
|  | if not source.endswith('.rs') | 
|  | ] | 
|  | if non_rust_sources: | 
|  | print_suggested_sources('inputs', non_rust_sources) | 
|  |  | 
|  | return 1 | 
|  | return 0 | 
|  |  | 
|  |  | 
|  | if __name__ == '__main__': | 
|  | sys.exit(main()) |