mirror of
https://github.com/Mic92/nix-update.git
synced 2024-09-11 02:25:32 +03:00
344 lines
9.9 KiB
Python
344 lines
9.9 KiB
Python
import argparse
|
|
import os
|
|
import shutil
|
|
import sys
|
|
import tempfile
|
|
from typing import NoReturn
|
|
|
|
from .eval import CargoLockInSource, Package, eval_attr
|
|
from .options import Options
|
|
from .update import update
|
|
from .utils import run
|
|
from .version.version import VersionPreference
|
|
|
|
|
|
def die(msg: str) -> NoReturn:
|
|
print(msg, file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
|
|
def parse_args(args: list[str]) -> Options:
|
|
parser = argparse.ArgumentParser()
|
|
help = "File to import rather than default.nix. Examples, ./release.nix"
|
|
parser.add_argument("-f", "--file", default="./.", help=help)
|
|
parser.add_argument(
|
|
"-F", "--flake", action="store_true", help="Update a flake attribute instead"
|
|
)
|
|
parser.add_argument("--build", action="store_true", help="build the package")
|
|
parser.add_argument(
|
|
"--test", action="store_true", help="Run package's `passthru.tests`"
|
|
)
|
|
parser.add_argument(
|
|
"--review", action="store_true", help="Run `nixpkgs-review wip`"
|
|
)
|
|
parser.add_argument("--format", action="store_true", help="Run `nixfmt`")
|
|
parser.add_argument(
|
|
"--commit", action="store_true", help="Commit the updated package"
|
|
)
|
|
parser.add_argument(
|
|
"-u",
|
|
"--use-update-script",
|
|
action="store_true",
|
|
help="Use passthru.updateScript instead if possible",
|
|
)
|
|
parser.add_argument(
|
|
"--url",
|
|
help="URL to the repository to check for a release instead of using the URL in the src attribute of the package",
|
|
)
|
|
parser.add_argument(
|
|
"--write-commit-message",
|
|
metavar="FILE",
|
|
help="Write commit message to FILE",
|
|
)
|
|
parser.add_argument(
|
|
"-vr",
|
|
"--version-regex",
|
|
help="Regex to extract version with, i.e. 'jq-(.*)'",
|
|
default="(.*)",
|
|
)
|
|
parser.add_argument(
|
|
"--run",
|
|
action="store_true",
|
|
help="provide a shell based on `nix run` with the package in $PATH",
|
|
)
|
|
parser.add_argument(
|
|
"--shell", action="store_true", help="provide a shell with the package"
|
|
)
|
|
parser.add_argument(
|
|
"--version",
|
|
nargs="?",
|
|
default=VersionPreference.STABLE,
|
|
help="Version to update to. Possible values are: "
|
|
+ ", ".join(VersionPreference),
|
|
)
|
|
parser.add_argument(
|
|
"--override-filename",
|
|
nargs="?",
|
|
help="Set filename where nix-update will update version/hash",
|
|
default=None,
|
|
)
|
|
parser.add_argument(
|
|
"--system",
|
|
help="The system used to to calculate the hash and run other nix commands",
|
|
default=None,
|
|
)
|
|
|
|
default_attribute = os.getenv("UPDATE_NIX_ATTR_PATH")
|
|
parser.add_argument(
|
|
"attribute",
|
|
default=default_attribute,
|
|
nargs="?" if default_attribute else None, # type: ignore
|
|
help="""Attribute name within the file evaluated (defaults to environment variable "UPDATE_NIX_ATTR_PATH")""",
|
|
)
|
|
|
|
a = parser.parse_args(args)
|
|
return Options(
|
|
import_path=os.path.realpath(a.file),
|
|
flake=a.flake,
|
|
build=a.build,
|
|
commit=a.commit,
|
|
use_update_script=a.use_update_script,
|
|
url=a.url,
|
|
write_commit_message=a.write_commit_message,
|
|
run=a.run,
|
|
shell=a.shell,
|
|
version=a.version,
|
|
version_preference=VersionPreference.from_str(a.version),
|
|
attribute=a.attribute,
|
|
test=a.test,
|
|
version_regex=a.version_regex,
|
|
review=a.review,
|
|
format=a.format,
|
|
override_filename=a.override_filename,
|
|
system=a.system,
|
|
extra_flags=(["--system", a.system] if a.system else [])
|
|
+ ["--extra-experimental-features", "flakes nix-command"],
|
|
)
|
|
|
|
|
|
def nix_shell(options: Options) -> None:
|
|
if options.flake:
|
|
run(
|
|
[
|
|
"nix",
|
|
"shell",
|
|
f"{options.import_path}#{options.attribute}",
|
|
]
|
|
+ options.extra_flags,
|
|
stdout=None,
|
|
check=False,
|
|
)
|
|
else:
|
|
expr = f"let pkgs = import {options.escaped_import_path} {{}}; in pkgs.mkShell {{ buildInputs = [ pkgs.{options.escaped_attribute} ]; }}"
|
|
with tempfile.TemporaryDirectory() as d:
|
|
path = os.path.join(d, "default.nix")
|
|
with open(path, "w") as f:
|
|
f.write(expr)
|
|
run(["nix-shell", path] + options.extra_flags, stdout=None, check=False)
|
|
|
|
|
|
def git_has_diff(git_dir: str, package: Package) -> bool:
|
|
diff = run(["git", "-C", git_dir, "diff", "--", package.filename])
|
|
return len(diff.stdout) > 0
|
|
|
|
|
|
def format_commit_message(package: Package) -> str:
|
|
new_version = getattr(package.new_version, "number", None)
|
|
if (
|
|
new_version
|
|
and package.old_version != new_version
|
|
and new_version.startswith("v")
|
|
):
|
|
new_version = new_version[1:]
|
|
msg = f"{package.attribute}: {package.old_version} -> {new_version}"
|
|
if package.diff_url:
|
|
msg += f"\n\nDiff: {package.diff_url}"
|
|
if package.changelog:
|
|
msg += f"\n\nChangelog: {package.changelog}"
|
|
return msg
|
|
|
|
|
|
def git_commit(git_dir: str, package: Package) -> None:
|
|
msg = format_commit_message(package)
|
|
new_version = package.new_version
|
|
files_changed = [package.filename]
|
|
if isinstance(package.cargo_lock, CargoLockInSource):
|
|
files_changed.append(package.cargo_lock.path)
|
|
if new_version and (
|
|
package.old_version != new_version.number
|
|
or (new_version.rev and new_version.rev != package.rev)
|
|
):
|
|
run(
|
|
["git", "-C", git_dir, "commit", "--verbose", "--message", msg]
|
|
+ files_changed,
|
|
stdout=None,
|
|
)
|
|
else:
|
|
with tempfile.NamedTemporaryFile(mode="w") as f:
|
|
f.write(msg)
|
|
f.flush()
|
|
run(
|
|
["git", "-C", git_dir, "commit", "--verbose", "--template", f.name]
|
|
+ files_changed,
|
|
stdout=None,
|
|
)
|
|
|
|
|
|
def write_commit_message(path: str, package: Package) -> None:
|
|
with open(path, "w") as f:
|
|
f.write(format_commit_message(package))
|
|
f.write("\n")
|
|
|
|
|
|
def find_git_root(path: str) -> str | None:
|
|
prefix = [path]
|
|
release_nix = [".git"]
|
|
while True:
|
|
root_path = os.path.join(*prefix)
|
|
release_nix_path = os.path.join(root_path, *release_nix)
|
|
if os.path.exists(release_nix_path):
|
|
return root_path
|
|
if os.path.abspath(root_path) == "/":
|
|
return None
|
|
prefix.append("..")
|
|
|
|
|
|
def validate_git_dir(import_path: str) -> str:
|
|
if os.path.isdir(import_path):
|
|
git_dir = find_git_root(import_path)
|
|
else:
|
|
git_dir = find_git_root(os.path.dirname(import_path))
|
|
|
|
if git_dir is None:
|
|
die(f"Could not find a git repository relative to {import_path}")
|
|
|
|
return git_dir
|
|
|
|
|
|
def nix_run(options: Options) -> None:
|
|
cmd = [
|
|
"nix",
|
|
"shell",
|
|
"-L",
|
|
] + options.extra_flags
|
|
|
|
if options.flake:
|
|
cmd.append(f"{options.import_path}#{options.attribute}")
|
|
else:
|
|
cmd.extend(["-f", options.import_path, options.attribute])
|
|
run(
|
|
cmd,
|
|
stdout=None,
|
|
check=False,
|
|
)
|
|
|
|
|
|
def nix_build_tool() -> str:
|
|
"Return `nom` if found in $PATH"
|
|
if shutil.which("nom"):
|
|
return "nom"
|
|
else:
|
|
return "nix"
|
|
|
|
|
|
def nix_build(options: Options) -> None:
|
|
cmd = [
|
|
nix_build_tool(),
|
|
"build",
|
|
"-L",
|
|
] + options.extra_flags
|
|
if options.flake:
|
|
cmd.append(f"{options.import_path}#{options.attribute}")
|
|
else:
|
|
cmd.extend(["-f", options.import_path, options.attribute])
|
|
run(cmd, stdout=None)
|
|
|
|
|
|
def nix_test(opts: Options, package: Package) -> None:
|
|
if not package.tests:
|
|
die(f"Package '{package.name}' does not define any tests")
|
|
|
|
cmd = [nix_build_tool(), "build", "-L"] + opts.extra_flags
|
|
|
|
if opts.flake:
|
|
for t in package.tests:
|
|
cmd.append(f"{opts.import_path}#{package.attribute}.tests.{t}")
|
|
else:
|
|
cmd.extend(["-f", opts.import_path])
|
|
for t in package.tests:
|
|
cmd.append("-A")
|
|
cmd.append(f"{package.attribute}.tests.{t}")
|
|
run(cmd, stdout=None)
|
|
|
|
|
|
def nixpkgs_review() -> None:
|
|
cmd = [
|
|
"nixpkgs-review",
|
|
"wip",
|
|
]
|
|
run(cmd, stdout=None)
|
|
|
|
|
|
def main(args: list[str] = sys.argv[1:]) -> None:
|
|
options = parse_args(args)
|
|
if not os.path.exists(options.import_path):
|
|
die(f"path {options.import_path} does not exist")
|
|
|
|
git_dir = None
|
|
if options.commit or options.review:
|
|
git_dir = validate_git_dir(options.import_path)
|
|
|
|
package = update(options)
|
|
|
|
if package.maintainers:
|
|
print("Package maintainers:")
|
|
for maintainer in package.maintainers:
|
|
print(
|
|
f" - {maintainer['name']}"
|
|
+ (f" (@{maintainer['github']})" if "github" in maintainer else "")
|
|
)
|
|
|
|
if options.build:
|
|
nix_build(options)
|
|
|
|
if options.run:
|
|
nix_run(options)
|
|
|
|
if options.shell:
|
|
nix_shell(options)
|
|
|
|
if not git_dir:
|
|
git_dir = find_git_root(options.import_path)
|
|
|
|
changes_detected = not git_dir or git_has_diff(git_dir, package)
|
|
|
|
if not changes_detected:
|
|
print("No changes detected, skipping remaining steps")
|
|
return
|
|
|
|
if options.test:
|
|
nix_test(options, package)
|
|
|
|
if options.review:
|
|
if options.flake:
|
|
print("--review is unsupported with --flake")
|
|
else:
|
|
nixpkgs_review()
|
|
|
|
if options.format:
|
|
run(["nixfmt", package.filename], stdout=None)
|
|
|
|
if options.commit:
|
|
assert git_dir is not None
|
|
if package.changelog:
|
|
# If we have a changelog we will re-eval the package in case it has changed
|
|
package.changelog = eval_attr(options).changelog
|
|
git_commit(git_dir, package)
|
|
|
|
if options.write_commit_message is not None:
|
|
write_commit_message(options.write_commit_message, package)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|