Skip to content

Instantly share code, notes, and snippets.

@rmcgibbo
Created March 1, 2021 00:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rmcgibbo/6e6ed0d69d864ca220f54369f2bb3828 to your computer and use it in GitHub Desktop.
Save rmcgibbo/6e6ed0d69d864ca220f54369f2bb3828 to your computer and use it in GitHub Desktop.
import json
import os
import subprocess
import sys
from typing import Any, Dict, List
from .nix import Attr
# https://github.com/rmcgibbo/nixpkgs-review-bot/issues/26
IGNORE_SUGGESTIONS_FOR_FILES = {
"pkgs/misc/vim-plugins/generated.nix",
"pkgs/misc/vim-plugins/overrides.nix",
"pkgs/misc/tmux-plugins/default.nix",
}
# https://github.com/jtojnar/nixpkgs-hammering/issues/73
# https://github.com/jtojnar/nixpkgs-hammering/issues/77#issuecomment-786193493
# https://github.com/jtojnar/nixpkgs-hammering/pull/78#pullrequestreview-599072677
ATTRS_THAT_BREAK_NIXPKGS_HAMMER = {
"acl",
"attr",
"bash",
"binutils-unwrapped",
"bzip2",
"coreutils",
"coreutils-full",
"coreutils-prefixed",
"datadog-agent",
"diffutils",
"findutils",
"gawkInteractive",
"gcc-unwrapped",
"glibc",
"gnugrep",
"gnupatch",
"gnused",
"gnutar",
"gzip",
"holochain-go",
"javaPackages.junit_4_12",
"javaPackages.mavenHello_1_0",
"javaPackages.mavenHello_1_1",
"libgccjit",
"zfsbackup",
}
def nixpkgs_hammer(attrs: List[Attr], modified_files: List[str] = None) -> List[Attr]:
"""Run nixpkgs-hammer on each attr, saves the results into the
'check_report' field on the attr.
Only save checks if the 'location' of the bug flagged by the check is within
the set of files modified by the pr, which should be passed in `modified_files`.
"""
cmd = [
"nixpkgs-hammer",
"-f",
".",
"--json",
"--exclude",
"attribute-ordering",
"--exclude",
"explicit-phases",
# Discussion here: https://github.com/jtojnar/nixpkgs-hammering/pull/38#issuecomment-778381992
"--exclude",
"attribute-typo",
] + [
a.name
for a in attrs
if (a.name not in ATTRS_THAT_BREAK_NIXPKGS_HAMMER)
and (not a.skipped)
and (not a.blacklisted)
and (not a.broken)
]
print("$" + " ".join(cmd), file=sys.stderr)
try:
proc = subprocess.run(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=True,
text=True,
)
except subprocess.CalledProcessError as e:
print(os.path.abspath(os.getcwd()), file=sys.stderr)
print(e.stderr, file=sys.stderr)
print(e.stderr, file=sys.stderr)
print(e.returncode, file=sys.stderr)
raise
hammer_report = json.loads(proc.stdout)
for name, data in hammer_report.items():
for msg in data:
for location in msg.get("locations", []):
if "file" in location and isinstance(location["file"], str):
location["file"] = os.path.relpath(location["file"], ".")
attrs_by_name = {a.name: a for a in attrs}
for name, data in hammer_report.items():
for msg in (m for m in data if is_acceptable_hammer_message(m, modified_files)):
attrs_by_name[name].check_report.append(stringify_message(**msg))
return attrs
def stringify_location(file: str, line: int, column: int):
with open(file, "r") as opened_file:
all_lines = opened_file.read().splitlines()
line_contents = all_lines[line - 1]
line_spaces = " " * len(str(line))
pointer = " " * (column - 1) + "^"
location_lines = [
"Near " + file + ":" + str(line) + ":" + str(column) + ":",
"```",
line_spaces + " |",
str(line) + " | " + line_contents,
line_spaces + " | " + pointer,
"```",
"",
]
return "\n".join(location_lines)
def stringify_message(
name: str,
msg: str,
locations: List[Dict[str, Any]] = [],
cond: bool = True,
link: bool = True,
severity: str = "warning",
) -> str:
if link:
linked_name = f'<a href="https://github.com/jtojnar/nixpkgs-hammering/blob/master/explanations/{name}.md">{name}</a>'
else:
linked_name = name
message_lines = [
f"{severity}: {linked_name}",
"",
msg,
] + list(map(lambda loc: stringify_location(**loc), locations))
return "\n".join(message_lines)
def is_acceptable_hammer_message(
msg: Dict[str, Any], modified_files: List[str] = None
) -> bool:
predicates = [
lambda: is_modified_location(msg, modified_files=modified_files),
lambda: not is_ignored_file(msg),
lambda: not is_ignored_golang_buildFlagsArray_msg(msg),
lambda: msg["name"] not in ("no-build-output",),
]
return all(pred() for pred in predicates)
def is_modified_location(msg: Dict[str, Any], modified_files: List[str] = None) -> bool:
if modified_files is None:
return True
for loc in msg.get("locations", []):
if loc["file"] in set(modified_files):
return True
return False
def is_ignored_file(msg: Dict[str, Any]) -> bool:
return any(
loc["file"] in IGNORE_SUGGESTIONS_FOR_FILES for loc in msg.get("locations", [])
)
def is_ignored_golang_buildFlagsArray_msg(msg: Dict[str, Any]) -> bool:
for loc in msg["locations"]:
is_golang = False
with open(loc["file"]) as f:
content = f.read()
is_golang = "buildGoModule" in content or "buildGoPackage" in content
if is_golang and msg["name"] == "no-flags-array":
return True
return False
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment