import argparse
import pathlib
import subprocess
import sys
import typing as T
WORKDIR = pathlib.Path("/tmp")
OLD_SUFFIX = "-old"
NEW_SUFFIX = "-new"
EXTENSIONS = {
"tree-sitter-mozjs": ["*.js", "*.js2", "*.jsm"],
"tree-sitter-javascript": ["*.js", "*.js2"],
"tree-sitter-tsx": ["*.tsx"],
"tree-sitter-typescript": ["*.ts", "*.jsw", "*.jsmw"],
"tree-sitter-java": ["*.java"],
"tree-sitter-rust": ["*.rs"],
"tree-sitter-cpp": [
"*.cpp",
"*.cx",
"*.cxx",
"*.cc",
"*.hxx",
"*.hpp",
"*.c",
"*.h",
"*.hh",
"*.inc",
"*.mm",
"*.m",
],
"tree-sitter-python": ["*.py"],
}
def run_subprocess(cmd: str, *args: T.Union[str, pathlib.Path]) -> None:
subprocess.run([cmd, *args])
def run_rca(
repo_dir: pathlib.Path,
output_dir: pathlib.Path,
manifest_path: T.Optional[pathlib.Path],
include_languages: T.List[str],
) -> None:
run_subprocess(
"cargo",
"run",
"--manifest-path",
manifest_path / "Cargo.toml" if manifest_path else "Cargo.toml",
"--release",
"--package",
"rust-code-analysis-cli",
"--",
"--metrics",
"--output-format=json",
"--pr",
"-I",
*include_languages,
"-p",
repo_dir,
"-o",
output_dir,
)
def compute_ci_metrics(args: argparse.Namespace) -> None:
if args.language not in EXTENSIONS.keys():
print(args.language, "is not a valid tree-sitter-language")
sys.exit(1)
repo_dir = pathlib.Path(args.path)
rca_path = WORKDIR / "rust-code-analysis"
old_dir = WORKDIR / (args.language + OLD_SUFFIX)
new_dir = WORKDIR / (args.language + NEW_SUFFIX)
old_dir.mkdir(parents=True, exist_ok=True)
new_dir.mkdir(parents=True, exist_ok=True)
print(f"Cloning rust-code-analysis master branch into /tmp")
run_subprocess(
"git",
"clone",
"--depth=1",
"--recurse-submodules",
"-j8",
"https://github.com/mozilla/rust-code-analysis",
rca_path,
)
print("\nComputing metrics before the update and saving them in", old_dir)
run_rca(repo_dir, old_dir, rca_path, EXTENSIONS[args.language])
print("\nUpdate", args.language)
run_subprocess("./update-language-bindings.sh")
print("\nComputing metrics after the update and saving them in", new_dir)
run_rca(repo_dir, new_dir, None, EXTENSIONS[args.language])
def compute_metrics(args: argparse.Namespace) -> None:
if args.language not in EXTENSIONS.keys():
print(args.language, "is not a valid tree-sitter-language")
sys.exit(1)
repo_dir = WORKDIR / args.path
old_dir = WORKDIR / (args.language + OLD_SUFFIX)
new_dir = WORKDIR / (args.language + NEW_SUFFIX)
old_dir.mkdir(parents=True, exist_ok=True)
new_dir.mkdir(parents=True, exist_ok=True)
if not args.only_new:
print(f"Cloning {args.url} into {repo_dir}")
run_subprocess("git", "clone", "--depth=1", args.url, repo_dir)
print("\nComputing metrics before the update and saving them in", old_dir)
run_rca(repo_dir, old_dir, None, EXTENSIONS[args.language])
print("\nCreate a new branch called", args.language)
run_subprocess("git", "checkout", "-B", args.language)
print("\nUpdate", args.language)
run_subprocess("./update-submodule.sh", args.language)
print("\nComputing metrics after the update and saving them in", new_dir)
run_rca(repo_dir, new_dir, None, EXTENSIONS[args.language])
def compare_metrics(args: argparse.Namespace) -> None:
old_dir = WORKDIR / (args.language + OLD_SUFFIX)
new_dir = WORKDIR / (args.language + NEW_SUFFIX)
compare_dir = WORKDIR / (args.language + "-compare")
compare_dir.mkdir(parents=True, exist_ok=True)
print("\nSave JSON of differences in", compare_dir)
run_subprocess("json-diff-cli", "--raw-json", "-o", compare_dir, old_dir, new_dir)
print("\nSave minimal tests in", compare_dir)
run_subprocess("json-minimal-tests", "-o", compare_dir, old_dir, new_dir)
def main() -> None:
parser = argparse.ArgumentParser(
prog="check-submodule",
description="This tool computes the metrics of a chosen repository "
"before and after a tree-sitter-language update.",
epilog="The source code of this program can be found on "
"GitHub at https://github.com/mozilla/rust-code-analysis",
)
commands = parser.add_subparsers(help="Sub-command help")
compute_metrics_cmd = commands.add_parser(
"compute-metrics",
help="Computes the metrics of a chosen repository before and after "
"a tree-sitter-language update.",
)
compute_metrics_cmd.add_argument(
"--only-new",
"-n",
action="store_true",
help="Only compute the metrics after the tree-sitter-language update",
)
compute_metrics_cmd.add_argument(
"-u",
"--url",
type=str,
required=True,
help="URL of the repository used to compute the metrics",
)
compute_metrics_cmd.add_argument(
"-p",
"--path",
type=str,
required=True,
help="Path where the repository will be saved locally",
)
compute_metrics_cmd.add_argument(
"-l",
"--language",
type=str,
required=True,
help="tree-sitter-language to be updated",
)
compute_metrics_cmd.set_defaults(func=compute_metrics)
compute_ci_metrics_cmd = commands.add_parser(
"compute-ci-metrics",
help="Computes the metrics of a chosen repository before and after "
"a tree-sitter-language update on a continuous integration system.",
)
compute_ci_metrics_cmd.add_argument(
"-p",
"--path",
type=str,
required=True,
help="Path where the rust-code-analysis repository is saved on the "
"continuous integration system",
)
compute_ci_metrics_cmd.add_argument(
"-l",
"--language",
type=str,
required=True,
help="tree-sitter-language to be updated",
)
compute_ci_metrics_cmd.set_defaults(func=compute_ci_metrics)
compare_metrics_cmd = commands.add_parser(
"compare-metrics",
help="Compares the metrics before and after "
"a tree-sitter-language update in order to discover whether "
"there are differences.",
)
compare_metrics_cmd.add_argument(
"-l",
"--language",
type=str,
required=True,
help="tree-sitter-language used to compare the metrics",
)
compare_metrics_cmd.set_defaults(func=compare_metrics)
args = parser.parse_args()
args.func(args)
if __name__ == "__main__":
main()