199 lines
5.9 KiB
Nix
Executable File
199 lines
5.9 KiB
Nix
Executable File
{
|
|
lib,
|
|
}:
|
|
let
|
|
inherit (builtins)
|
|
isAttrs
|
|
isList
|
|
isString
|
|
hasAttr
|
|
getAttr
|
|
attrNames
|
|
replaceStrings
|
|
;
|
|
inherit (lib) mapAttrs recursiveUpdate;
|
|
|
|
# Deep-merge attrsets (right-biased).
|
|
deepMerge = a: b: recursiveUpdate a b;
|
|
|
|
# Merge component sources: base.sources overlaid by overrides (component-wise deep merge).
|
|
mergeSources =
|
|
baseSources: overrides:
|
|
baseSources
|
|
// mapAttrs (
|
|
name: ov: if hasAttr name baseSources then deepMerge (getAttr name baseSources) ov else ov
|
|
) overrides;
|
|
|
|
# Apply a single variant overlay (variables + sources).
|
|
applyVariantOnce =
|
|
selected: variant:
|
|
let
|
|
vVars = variant.variables or { };
|
|
vSrcs = variant.sources or { };
|
|
in
|
|
{
|
|
variables = selected.variables // vVars;
|
|
sources = mergeSources selected.sources vSrcs;
|
|
};
|
|
|
|
# Apply platform-specific overrides if present for the given system.
|
|
applyPlatforms =
|
|
selected: variant: system:
|
|
if system == null || !(variant ? platforms) || !(hasAttr system variant.platforms) then
|
|
selected
|
|
else
|
|
let
|
|
p = variant.platforms.${system};
|
|
pVars = p.variables or { };
|
|
pSrcs = p.sources or { };
|
|
in
|
|
{
|
|
variables = selected.variables // pVars;
|
|
sources = mergeSources selected.sources pSrcs;
|
|
};
|
|
|
|
# Resolve variant chain via inherits (ancestor first), then apply platforms.
|
|
resolveVariant =
|
|
spec: baseSelected: variantName: system:
|
|
if variantName == null || !(spec ? variants) || !(hasAttr variantName spec.variants) then
|
|
baseSelected
|
|
else
|
|
let
|
|
v = spec.variants.${variantName};
|
|
parentSelected =
|
|
if v ? inherits then resolveVariant spec baseSelected v.inherits system else baseSelected;
|
|
withVariant = applyVariantOnce parentSelected v;
|
|
in
|
|
applyPlatforms withVariant v system;
|
|
|
|
# Render ${var} substitutions in any string within attrs/lists.
|
|
renderValue =
|
|
value: vars:
|
|
if isString value then
|
|
let
|
|
keys = attrNames vars;
|
|
patterns = map (k: "\${" + k + "}") keys;
|
|
replacements = map (k: toString (getAttr k vars)) keys;
|
|
in
|
|
replaceStrings patterns replacements value
|
|
else if isAttrs value then
|
|
mapAttrs (_: v: renderValue v vars) value
|
|
else if isList value then
|
|
map (v: renderValue v vars) value
|
|
else
|
|
value;
|
|
|
|
# Decide fetcher for URL type based on optional extra.unpack hint.
|
|
useFetchZip = comp: comp ? extra && comp.extra ? unpack && comp.extra.unpack == "zip";
|
|
|
|
# Build a single src from a rendered component spec, using the given pkgs for fetchers.
|
|
mkSrcFromRendered' =
|
|
pkgs': comp:
|
|
let
|
|
fetcher = comp.fetcher or "none";
|
|
in
|
|
if fetcher == "github" then
|
|
pkgs'.fetchFromGitHub (
|
|
{
|
|
inherit (comp) owner repo hash;
|
|
# Allow tag as rev (ignore null/empty tag)
|
|
rev = if comp ? tag && comp.tag != null && comp.tag != "" then comp.tag else comp.rev;
|
|
fetchSubmodules = comp.submodules or false;
|
|
}
|
|
// lib.optionalAttrs (comp ? name) { inherit (comp) name; }
|
|
)
|
|
else if fetcher == "git" then
|
|
pkgs'.fetchgit {
|
|
inherit (comp) url rev hash;
|
|
fetchSubmodules = comp.submodules or false;
|
|
}
|
|
else if fetcher == "url" then
|
|
let
|
|
url = comp.url or comp.urlTemplate;
|
|
in
|
|
if useFetchZip comp then
|
|
pkgs'.fetchzip (
|
|
{
|
|
inherit (comp) hash;
|
|
inherit url;
|
|
}
|
|
// lib.optionalAttrs (comp ? extra && comp.extra ? stripRoot) { inherit (comp.extra) stripRoot; }
|
|
)
|
|
else
|
|
pkgs'.fetchurl {
|
|
inherit (comp) hash;
|
|
inherit url;
|
|
}
|
|
else if fetcher == "pypi" then
|
|
pkgs'.python3Packages.fetchPypi {
|
|
inherit (comp) version hash;
|
|
pname = comp.name;
|
|
}
|
|
else
|
|
# fetcher == "none": pass-through (e.g., linux version/hash consumed by custom logic)
|
|
comp;
|
|
|
|
in
|
|
rec {
|
|
/*
|
|
Select a variant from a loaded version.json specification.
|
|
|
|
Usage:
|
|
let selected = versioning.selectVariant spec variantName system;
|
|
- spec: attrset from lib.importJSON ./version.json
|
|
- variantName: string or null (when null, uses spec.defaultVariant if present)
|
|
- system: string like "x86_64-linux" or null (to apply platforms overrides)
|
|
*/
|
|
selectVariant =
|
|
spec: variantName: system:
|
|
let
|
|
chosen = if variantName != null then variantName else (spec.defaultVariant or null);
|
|
baseSelected = {
|
|
variables = spec.variables or { };
|
|
sources = spec.sources or { };
|
|
};
|
|
in
|
|
resolveVariant spec baseSelected chosen system;
|
|
|
|
/*
|
|
Render ${var} template substitutions across any value using provided variables.
|
|
Strings, attrsets, and lists are traversed.
|
|
*/
|
|
render = value: variables: renderValue value variables;
|
|
|
|
/*
|
|
Render a component with variables and then build its src (or pass-through for fetcher "none").
|
|
Prefer using mkAllSources, which handles rendering for all components.
|
|
|
|
pkgs: the nixpkgs instance to use for fetchers (must match the target system).
|
|
*/
|
|
mkSrc =
|
|
pkgs': comp: variables:
|
|
let
|
|
rendered = renderValue comp variables;
|
|
in
|
|
mkSrcFromRendered' pkgs' rendered;
|
|
|
|
/*
|
|
Produce an attrset of all sources for a selected spec:
|
|
mkAllSources pkgs selected
|
|
Where:
|
|
pkgs: the nixpkgs instance to use for fetchers (must match the target system).
|
|
selected = selectVariant spec variantName system
|
|
Returns:
|
|
{ componentName = src | renderedComp (for "none"); ... }
|
|
*/
|
|
mkAllSources =
|
|
pkgs': selected:
|
|
mapAttrs (
|
|
_name: comp:
|
|
if comp ? fetcher && comp.fetcher == "none" then
|
|
renderValue comp selected.variables
|
|
else
|
|
mkSrc pkgs' (renderValue comp selected.variables) selected.variables
|
|
) selected.sources;
|
|
|
|
# Expose deepMerge for convenience (right-biased).
|
|
inherit deepMerge;
|
|
}
|