152 lines
4.3 KiB
Python
Executable file
152 lines
4.3 KiB
Python
Executable file
#!/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:]))
|