| #!/usr/bin/env python3 |
| # Copyright (C) 2025 The Android Open Source Project |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| """Helper tool to check various metadata (e.g. licensing) in source files.""" |
| |
| import argparse |
| from pathlib import Path |
| import re |
| import sys |
| |
| import util |
| |
| |
| _FILE_HEADER_RE = re.compile( |
| r"""# Copyright \(C\) 20[0-9]{2} The Android Open Source Project |
| # |
| # Licensed under the Apache License, Version 2\.0 \(the "License"\); |
| # you may not use this file except in compliance with the License\. |
| # You may obtain a copy of the License at |
| # |
| # http://www\.apache\.org/licenses/LICENSE-2\.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied\. |
| # See the License for the specific language governing permissions and |
| # limitations under the License\. |
| """ |
| ) |
| |
| |
| def check_license(path: Path, lines: list[str]) -> bool: |
| """Check license header.""" |
| # Enforce licensing on configs & scripts. |
| if not ( |
| path.suffix in (".bash", ".cfg", ".ini", ".py", ".toml") |
| or lines[0] in ("#!/bin/bash", "#!/bin/sh", "#!/usr/bin/env python3") |
| ): |
| return True |
| |
| # Extract the file header. |
| header_lines = [] |
| for line in lines: |
| if line.startswith("#"): |
| header_lines.append(line) |
| else: |
| break |
| if not header_lines: |
| print( |
| f"error: {path.relative_to(util.TOPDIR)}: " |
| "missing file header (copyright+licensing)", |
| file=sys.stderr, |
| ) |
| return False |
| |
| # Skip the shebang. |
| if header_lines[0].startswith("#!"): |
| header_lines.pop(0) |
| |
| # If this file is imported into the tree, then leave it be. |
| if header_lines[0] == "# DO NOT EDIT THIS FILE": |
| return True |
| |
| header = "".join(f"{x}\n" for x in header_lines) |
| if not _FILE_HEADER_RE.match(header): |
| print( |
| f"error: {path.relative_to(util.TOPDIR)}: " |
| "file header incorrectly formatted", |
| file=sys.stderr, |
| ) |
| print( |
| "".join(f"> {x}\n" for x in header_lines), end="", file=sys.stderr |
| ) |
| return False |
| |
| return True |
| |
| |
| def check_path(opts: argparse.Namespace, path: Path) -> bool: |
| """Check a single path.""" |
| data = path.read_text(encoding="utf-8") |
| lines = data.splitlines() |
| # NB: Use list comprehension and not a generator so we run all the checks. |
| return all( |
| [ |
| check_license(path, lines), |
| ] |
| ) |
| |
| |
| def check_paths(opts: argparse.Namespace, paths: list[Path]) -> bool: |
| """Check all the paths.""" |
| # NB: Use list comprehension and not a generator so we check all paths. |
| return all([check_path(opts, x) for x in paths]) |
| |
| |
| def find_files(opts: argparse.Namespace) -> list[Path]: |
| """Find all the files in the source tree.""" |
| result = util.run( |
| opts, |
| ["git", "ls-tree", "-r", "-z", "--name-only", "HEAD"], |
| cwd=util.TOPDIR, |
| capture_output=True, |
| encoding="utf-8", |
| ) |
| return [util.TOPDIR / x for x in result.stdout.split("\0")[:-1]] |
| |
| |
| def get_parser() -> argparse.ArgumentParser: |
| """Get a CLI parser.""" |
| parser = argparse.ArgumentParser(description=__doc__) |
| parser.add_argument( |
| "-n", |
| "--dry-run", |
| dest="dryrun", |
| action="store_true", |
| help="show everything that would be done", |
| ) |
| parser.add_argument( |
| "paths", |
| nargs="*", |
| help="the paths to scan", |
| ) |
| return parser |
| |
| |
| def main(argv: list[str]) -> int: |
| """The main func!""" |
| parser = get_parser() |
| opts = parser.parse_args(argv) |
| |
| paths = opts.paths |
| if not opts.paths: |
| paths = find_files(opts) |
| |
| return 0 if check_paths(opts, paths) else 1 |
| |
| |
| if __name__ == "__main__": |
| sys.exit(main(sys.argv[1:])) |