nix-update/nix_update/eval.py

227 lines
6.5 KiB
Python
Raw Normal View History

2020-03-23 13:38:04 +03:00
import json
import os
from dataclasses import InitVar, dataclass, field
2023-01-08 10:41:48 +03:00
from textwrap import dedent, indent
2023-08-25 09:53:31 +03:00
from typing import Any, Literal
2022-11-24 20:33:58 +03:00
from urllib.parse import ParseResult, urlparse
2020-03-23 13:38:04 +03:00
from .errors import UpdateError
2020-04-21 12:06:55 +03:00
from .options import Options
from .utils import run
2022-11-21 23:07:18 +03:00
from .version.version import Version, VersionPreference
2020-03-23 13:38:04 +03:00
2020-12-01 11:30:17 +03:00
@dataclass
class Position:
file: str
line: int
column: int
class CargoLock:
pass
class NoCargoLock(CargoLock):
pass
class CargoLockInSource(CargoLock):
def __init__(self, path: str) -> None:
self.path = path
class CargoLockInStore(CargoLock):
pass
2020-03-23 13:38:04 +03:00
@dataclass
class Package:
attribute: str
import_path: InitVar[str]
2020-03-23 13:38:04 +03:00
name: str
2020-03-24 16:25:17 +03:00
old_version: str
2020-03-23 13:38:04 +03:00
filename: str
line: int
2023-08-25 09:53:31 +03:00
urls: list[str] | None
url: str | None
src_homepage: str | None
changelog: str | None
2024-01-25 09:59:00 +03:00
maintainers: list[dict[str, str]] | None
2020-03-23 13:38:04 +03:00
rev: str
2023-08-25 09:53:31 +03:00
hash: str | None
go_modules: str | None
go_modules_old: str | None
cargo_deps: str | None
npm_deps: str | None
yarn_deps: str | None
composer_deps: str | None
maven_deps: str | None
2023-08-25 09:53:31 +03:00
tests: list[str]
has_update_script: bool
2020-03-23 13:38:04 +03:00
2023-08-25 09:53:31 +03:00
raw_version_position: InitVar[dict[str, Any] | None]
raw_cargo_lock: InitVar[Literal[False] | str | None]
2020-12-01 11:30:17 +03:00
2023-08-25 09:53:31 +03:00
parsed_url: ParseResult | None = None
new_version: Version | None = None
version_position: Position | None = field(init=False)
cargo_lock: CargoLock = field(init=False)
2023-08-25 09:53:31 +03:00
diff_url: str | None = None
2020-12-01 11:30:17 +03:00
def __post_init__(
self,
import_path: str,
2023-08-25 09:53:31 +03:00
raw_version_position: dict[str, Any] | None,
raw_cargo_lock: Literal[False] | str | None,
) -> None:
2022-11-24 20:33:58 +03:00
url = self.url or (self.urls[0] if self.urls else None)
if url:
self.parsed_url = urlparse(url)
2020-12-01 11:30:17 +03:00
if raw_version_position is None:
self.version_position = None
else:
self.version_position = Position(**raw_version_position)
2023-04-20 20:33:36 +03:00
if raw_cargo_lock is None:
self.cargo_lock = NoCargoLock()
elif raw_cargo_lock is False:
self.cargo_lock = CargoLockInStore()
elif not os.path.realpath(raw_cargo_lock).startswith(import_path):
self.cargo_lock = CargoLockInStore()
else:
self.cargo_lock = CargoLockInSource(raw_cargo_lock)
2020-03-24 16:25:17 +03:00
2020-03-23 13:38:04 +03:00
2023-01-08 18:30:03 +03:00
def eval_expression(
2024-04-01 21:53:33 +03:00
escaped_import_path: str,
attr: str,
flake: bool,
system: str | None,
override_filename: str | None,
2023-01-08 18:30:03 +03:00
) -> str:
system = f'"{system}"' if system else "builtins.currentSystem"
2023-01-08 10:41:48 +03:00
if flake:
sanitize_position = (
f"""
sanitizePosition = {{ file, ... }}@pos:
assert substring 0 outPathLen file != outPath
-> throw "${{file}} is not in ${{outPath}}";
pos // {{ file = {escaped_import_path} + substring outPathLen (stringLength file - outPathLen) file; }};
"""
if override_filename is None
else """
sanitizePosition = x: x;
"""
).strip()
2023-01-08 10:41:48 +03:00
let_bindings = f"""
inherit (builtins) getFlake stringLength substring;
currentSystem = {system};
flake = getFlake {escaped_import_path};
2023-01-08 10:41:48 +03:00
pkg = flake.packages.${{currentSystem}}.{attr} or flake.{attr};
inherit (flake) outPath;
outPathLen = stringLength outPath;
{sanitize_position}
2023-01-08 10:41:48 +03:00
"""
else:
let_bindings = f"""
pkgs = import {escaped_import_path};
2023-01-08 20:32:46 +03:00
args = builtins.functionArgs pkgs;
inputs = (if args ? system then {{ system = {system}; }} else {{}}) //
(if args ? overlays then {{ overlays = [ ]; }} else {{}});
pkg = (pkgs inputs).{attr};
2023-01-08 10:41:48 +03:00
sanitizePosition = x: x;
"""
2022-12-08 20:11:00 +03:00
has_update_script = (
"false" if flake else "pkg.passthru.updateScript or null != null"
)
return f"""
let
2023-01-08 10:41:48 +03:00
{indent(dedent(let_bindings), " ")}
positionFromMeta = pkg: let
parts = builtins.match "(.*):([0-9]+)" pkg.meta.position;
in {{
file = builtins.elemAt parts 0;
line = builtins.fromJSON (builtins.elemAt parts 1);
}};
2022-12-08 20:11:00 +03:00
raw_version_position = sanitizePosition (builtins.unsafeGetAttrPos "version" pkg);
position = if pkg ? meta.position then
sanitizePosition (positionFromMeta pkg)
else if pkg ? isRubyGem then
2022-12-08 20:11:00 +03:00
raw_version_position
else if pkg ? isPhpExtension then
raw_version_position
else
2022-12-08 20:11:00 +03:00
sanitizePosition (builtins.unsafeGetAttrPos "src" pkg);
in {{
name = pkg.name;
old_version = pkg.version or (builtins.parseDrvName pkg.name).version;
inherit raw_version_position;
filename = position.file;
line = position.line;
urls = pkg.src.urls or null;
url = pkg.src.url or null;
rev = pkg.src.rev or null;
hash = pkg.src.outputHash or null;
go_modules = pkg.goModules.outputHash or null;
go_modules_old = pkg.go-modules.outputHash or null;
2022-12-08 20:11:00 +03:00
cargo_deps = pkg.cargoDeps.outputHash or null;
raw_cargo_lock =
if pkg ? cargoDeps.lockFile then
let
inherit (pkg.cargoDeps) lockFile;
res = builtins.tryEval (sanitizePosition {{
2023-04-27 05:36:17 +03:00
file = toString lockFile;
}});
in
if res.success then res.value.file else false
else
null;
composer_deps = pkg.composerRepository.outputHash or null;
2022-12-08 20:11:00 +03:00
npm_deps = pkg.npmDeps.outputHash or null;
yarn_deps = pkg.offlineCache.outputHash or null;
maven_deps = pkg.fetchedMavenDeps.outputHash or null;
2022-12-08 20:11:00 +03:00
tests = builtins.attrNames (pkg.passthru.tests or {{}});
has_update_script = {has_update_script};
src_homepage = pkg.src.meta.homepage or null;
changelog = pkg.meta.changelog or null;
2024-01-25 09:59:00 +03:00
maintainers = pkg.meta.maintainers or null;
2022-12-08 20:11:00 +03:00
}}"""
2020-03-23 13:38:04 +03:00
def eval_attr(opts: Options) -> Package:
2023-08-02 16:54:08 +03:00
expr = eval_expression(
2024-04-01 21:53:33 +03:00
opts.escaped_import_path,
opts.escaped_attribute,
opts.flake,
opts.system,
opts.override_filename,
2023-08-02 16:54:08 +03:00
)
cmd = [
"nix",
"eval",
"--json",
"--impure",
"--expr",
expr,
] + opts.extra_flags
res = run(cmd)
2020-03-23 13:38:04 +03:00
out = json.loads(res.stdout)
package = Package(attribute=opts.attribute, import_path=opts.import_path, **out)
if opts.override_filename is not None:
package.filename = opts.override_filename
if opts.url is not None:
package.parsed_url = urlparse(opts.url)
if opts.version_preference != VersionPreference.SKIP and package.old_version == "":
2020-03-23 13:38:04 +03:00
raise UpdateError(
f"Nix's builtins.parseDrvName could not parse the version from {package.name}"
)
return package