mirror of
https://github.com/4cuck/teapawt.git
synced 2026-10-27 04:01:55 +00:00
tack: redeem
This commit is contained in:
1 parent
b75d379642
commit
07f3ed76b2
6 files changed
+369
-70
No files matched your search
+5
-1
@@ -7,6 +7,7 @@
|
||||
!.gitignore
|
||||
!.rustfmt.toml
|
||||
!.taplo.toml
|
||||
!flake.lock
|
||||
!Cargo.lock
|
||||
!Cargo.toml
|
||||
!README.md
|
||||
@@ -27,7 +28,10 @@
|
||||
!public/**
|
||||
|
||||
# Nix
|
||||
!flake.lock
|
||||
!.tack/
|
||||
!.tack/default.nix
|
||||
!.tack/pins.toml
|
||||
!.tack/pins.lock.json
|
||||
!flake.nix
|
||||
!nix/
|
||||
!nix/package.nix
|
||||
@@ -0,0 +1,331 @@
|
||||
# SPDX-License-Identifier: EUPL-1.2
|
||||
# tack-managed resolver. delete this line to take ownership; tack will leave it alone afterwards.
|
||||
|
||||
let
|
||||
inherit (builtins)
|
||||
attrNames
|
||||
attrValues
|
||||
concatMap
|
||||
elem
|
||||
elemAt
|
||||
filter
|
||||
foldl'
|
||||
fromJSON
|
||||
head
|
||||
intersectAttrs
|
||||
isList
|
||||
isString
|
||||
listToAttrs
|
||||
mapAttrs
|
||||
match
|
||||
pathExists
|
||||
readFile
|
||||
substring
|
||||
tail
|
||||
trace
|
||||
;
|
||||
|
||||
call =
|
||||
{
|
||||
overrides ? { },
|
||||
}:
|
||||
let
|
||||
pins = fromTOML (readFile ./pins.toml);
|
||||
lock = fromJSON (readFile ./pins.lock.json);
|
||||
all_follow_raw = pins.all_follow or { };
|
||||
|
||||
# flatten `target = [aliases]` rows alongside `alias = "target"` rows
|
||||
all_follow = foldl' (
|
||||
acc: key:
|
||||
let
|
||||
val = all_follow_raw.${key};
|
||||
in
|
||||
if isList val then
|
||||
acc
|
||||
// {
|
||||
${key} = key;
|
||||
}
|
||||
// listToAttrs (
|
||||
map (a: {
|
||||
name = a;
|
||||
value = key;
|
||||
}) val
|
||||
)
|
||||
else if isString val then
|
||||
acc // { ${key} = val; }
|
||||
else
|
||||
acc
|
||||
) { } (attrNames all_follow_raw);
|
||||
|
||||
knownTypes = [
|
||||
"github"
|
||||
"gitlab"
|
||||
"git"
|
||||
"tarball"
|
||||
"path"
|
||||
"indirect"
|
||||
];
|
||||
|
||||
# path nodes are convenience pins, so return the live local path directly
|
||||
# because fetchTree rejects unlocked paths in pure eval
|
||||
fetchPin =
|
||||
name:
|
||||
if !(lock ? ${name}) then
|
||||
throw "tack: pin '${name}' has no lock entry; run tack update"
|
||||
else
|
||||
let
|
||||
node = lock.${name};
|
||||
in
|
||||
if (node.type or "") == "path" then
|
||||
{
|
||||
outPath = if substring 0 1 node.path == "/" then node.path else ./. + ("/" + node.path);
|
||||
lastModified = node.lastModified or 0;
|
||||
}
|
||||
// (if node ? narHash then { inherit (node) narHash; } else { })
|
||||
else if !(elem (node.type or "") knownTypes) then
|
||||
throw "tack: unknown lock type '${node.type or "?"}' for pin '${name}'"
|
||||
else
|
||||
fetchTree node;
|
||||
|
||||
fetchFixed =
|
||||
name: entry:
|
||||
let
|
||||
raw = derivation {
|
||||
inherit name;
|
||||
inherit (entry) url;
|
||||
builder = "builtin:fetchurl";
|
||||
system = "builtin";
|
||||
outputHash = entry.sha256;
|
||||
outputHashAlgo = "sha256";
|
||||
outputHashMode = "flat";
|
||||
};
|
||||
unpacked = derivation {
|
||||
inherit name;
|
||||
builder = "builtin:unpack-channel";
|
||||
system = "builtin";
|
||||
src = raw;
|
||||
channelName = name;
|
||||
};
|
||||
in
|
||||
if (entry.unpack or "file") == "tarball" then unpacked.outPath + "/" + name else raw.outPath;
|
||||
|
||||
resolveSpec = upLock: spec: if isList spec then walkPath upLock upLock.root spec else spec;
|
||||
|
||||
walkPath =
|
||||
upLock: nodeName: path:
|
||||
if path == [ ] then
|
||||
nodeName
|
||||
else if !(upLock.nodes ? ${nodeName}) then
|
||||
throw "tack: follows path dead-end: no node '${nodeName}' in flake.lock"
|
||||
else
|
||||
let
|
||||
key = head path;
|
||||
inputs = upLock.nodes.${nodeName}.inputs or { };
|
||||
in
|
||||
if !(inputs ? ${key}) then
|
||||
throw "tack: follows path dead-end: node '${nodeName}' has no input '${key}'"
|
||||
else
|
||||
walkPath upLock (resolveSpec upLock inputs.${key}) (tail path);
|
||||
|
||||
followsFor =
|
||||
pin:
|
||||
let
|
||||
rules = removeAttrs all_follow (pin.exclude_follow or [ ]);
|
||||
in
|
||||
{
|
||||
level = rules // (pin.follows or { });
|
||||
deep = rules;
|
||||
};
|
||||
|
||||
resolveFollows = mapAttrs (
|
||||
_: target: self.${target} or (throw "tack: follows target '${target}' is not a pin")
|
||||
);
|
||||
|
||||
# follows key is `flake:name`, `tack:name`, or bare `name`
|
||||
# project onto one side, rekeyed to bare names
|
||||
followsForSide =
|
||||
side: follows:
|
||||
listToAttrs (
|
||||
concatMap (
|
||||
key:
|
||||
let
|
||||
m = match "(flake|tack):(.*)" key;
|
||||
in
|
||||
if m == null then
|
||||
[
|
||||
{
|
||||
name = key;
|
||||
value = follows.${key};
|
||||
}
|
||||
]
|
||||
else if head m == side then
|
||||
[
|
||||
{
|
||||
name = elemAt m 1;
|
||||
value = follows.${key};
|
||||
}
|
||||
]
|
||||
else
|
||||
[ ]
|
||||
) (attrNames follows)
|
||||
);
|
||||
|
||||
mkCallerInputs =
|
||||
upLock: nodeName: rawInputs: levelFollows: deepFollows:
|
||||
let
|
||||
resolved = resolveFollows levelFollows;
|
||||
in
|
||||
mapAttrs (
|
||||
n: _decl:
|
||||
resolved.${n} or (
|
||||
if upLock != null then
|
||||
let
|
||||
ref =
|
||||
(upLock.nodes.${nodeName}.inputs or { }).${n}
|
||||
or (throw "tack: input '${n}' declared but not in flake.lock node '${nodeName}'");
|
||||
childName = resolveSpec upLock ref;
|
||||
childNode = upLock.nodes.${childName};
|
||||
childSrc = fetchTree childNode.locked;
|
||||
in
|
||||
if childNode.flake or true then evalTransitive upLock childName childSrc deepFollows else childSrc
|
||||
else
|
||||
throw "tack: no flake.lock; cannot resolve input '${n}'"
|
||||
)
|
||||
) rawInputs;
|
||||
|
||||
mkFlakeResult =
|
||||
sourceInfo: flakeDir: callerInputs: outputs:
|
||||
outputs
|
||||
// sourceInfo
|
||||
// {
|
||||
outPath = flakeDir;
|
||||
inputs = callerInputs;
|
||||
inherit outputs sourceInfo;
|
||||
_type = "flake";
|
||||
};
|
||||
|
||||
evalFlake =
|
||||
sourceInfo: flakeDir: upLock: nodeName: levelFollows: deepFollows:
|
||||
let
|
||||
raw = import (flakeDir + "/flake.nix");
|
||||
|
||||
tackPinsPath = flakeDir + "/.tack/pins.toml";
|
||||
hasTack = pathExists tackPinsPath;
|
||||
upPins = if hasTack then fromTOML (readFile tackPinsPath) else { };
|
||||
|
||||
# project follows onto each side, keep only names that side has
|
||||
# bare follow reaches both; `flake:`/`tack:` reaches just one
|
||||
tackOverrides = resolveFollows (
|
||||
intersectAttrs (upPins.inputs or { }) (followsForSide "tack" levelFollows)
|
||||
);
|
||||
flakeLevel = intersectAttrs (raw.inputs or { }) (followsForSide "flake" levelFollows);
|
||||
|
||||
# deep follows pass down raw, so each descendant re-projects per side
|
||||
callerInputs = mkCallerInputs upLock nodeName (raw.inputs or { }) flakeLevel deepFollows;
|
||||
|
||||
# upstream declares its outputs forward tackOverrides; a closed `{ self }:`
|
||||
# would throw on the extra kwarg, so forward only when declared
|
||||
supportsOverrides = (upPins.tack or { }).recomposable or false;
|
||||
|
||||
extraArgs = if supportsOverrides && tackOverrides != { } then { inherit tackOverrides; } else { };
|
||||
|
||||
outputs = raw.outputs (callerInputs // extraArgs // { self = result; });
|
||||
|
||||
result =
|
||||
let
|
||||
base = mkFlakeResult sourceInfo flakeDir callerInputs outputs;
|
||||
in
|
||||
if hasTack && tackOverrides != { } && !supportsOverrides then
|
||||
trace "tack: ${flakeDir}: not marked recomposable (set [tack] recomposable = true); overrides will not reach upstream" base
|
||||
else
|
||||
base;
|
||||
in
|
||||
result;
|
||||
|
||||
evalTransitive =
|
||||
upLock: nodeName: sourceInfo: follows:
|
||||
evalFlake sourceInfo sourceInfo.outPath upLock nodeName follows follows;
|
||||
|
||||
evalTopFlake =
|
||||
sourceInfo: pin:
|
||||
let
|
||||
flakeDir = sourceInfo.outPath + (if pin ? dir then "/" + pin.dir else "");
|
||||
upLockPath = flakeDir + "/flake.lock";
|
||||
upLock = if pathExists upLockPath then fromJSON (readFile upLockPath) else null;
|
||||
rootNode = if upLock != null then upLock.root else null;
|
||||
f = followsFor pin;
|
||||
in
|
||||
evalFlake sourceInfo flakeDir upLock rootNode f.level f.deep;
|
||||
|
||||
evalFetch =
|
||||
sourceInfo: pin: subdir:
|
||||
let
|
||||
path = sourceInfo.outPath + subdir;
|
||||
tackPinsPath = path + "/.tack/pins.toml";
|
||||
hasTack = pathExists tackPinsPath;
|
||||
upPins = if hasTack then fromTOML (readFile tackPinsPath) else { };
|
||||
f = followsFor pin;
|
||||
# a fetch drill-in is tack-only
|
||||
tackOverrides = resolveFollows (
|
||||
intersectAttrs (upPins.inputs or { }) (followsForSide "tack" f.level)
|
||||
);
|
||||
in
|
||||
# a fetch pin is a source tree (path); hand back resolved inputs only when
|
||||
# there are overrides to push into the upstream's .tack
|
||||
if hasTack && tackOverrides != { } then
|
||||
let
|
||||
upstream = import (path + "/.tack");
|
||||
in
|
||||
# old resolvers return a plain attrset, not a callable functor
|
||||
if upstream ? __functor then
|
||||
(upstream { overrides = tackOverrides; }) // { outPath = path; }
|
||||
else
|
||||
trace "tack: ${path}: upstream .tack predates override support; overrides will not reach it" path
|
||||
else
|
||||
path;
|
||||
|
||||
loadPin =
|
||||
name: pin:
|
||||
let
|
||||
pinType = pin.type or (if pin.flake or true then "flake" else "fetch");
|
||||
subdir = if pin ? dir then "/" + pin.dir else "";
|
||||
in
|
||||
if pinType == "fixed" then
|
||||
fetchFixed name lock.${name}
|
||||
else
|
||||
let
|
||||
sourceInfo = fetchPin name;
|
||||
in
|
||||
if pinType == "flake" then evalTopFlake sourceInfo pin else evalFetch sourceInfo pin subdir;
|
||||
|
||||
declared = pins.inputs or { };
|
||||
|
||||
# undeclared lock entries are auto-dedup synthetics only when referenced as
|
||||
# [all_follow] targets; stale locks from hand-edits are ignored (tack rm to clean)
|
||||
autoTargets = listToAttrs (
|
||||
map (target: {
|
||||
name = target;
|
||||
value = true;
|
||||
}) (attrValues all_follow)
|
||||
);
|
||||
autoNames = filter (n: !(declared ? ${n}) && autoTargets ? ${n}) (attrNames lock);
|
||||
autoPin =
|
||||
name:
|
||||
let
|
||||
sourceInfo = fetchPin name;
|
||||
in
|
||||
if pathExists (sourceInfo.outPath + "/flake.nix") then evalTopFlake sourceInfo { } else sourceInfo;
|
||||
|
||||
self =
|
||||
(mapAttrs loadPin declared)
|
||||
// listToAttrs (
|
||||
map (name: {
|
||||
inherit name;
|
||||
value = autoPin name;
|
||||
}) autoNames
|
||||
)
|
||||
// overrides;
|
||||
in
|
||||
self // { __functor = _: call; };
|
||||
in
|
||||
call { }
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"fenix": {
|
||||
"type": "github",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "fa2a0be0f712d7147d1677d33d15ea7b0589cdb4",
|
||||
"narHash": "sha256-49WO85egjNtN1vMgJ3zUjDh5IqO+ou4zZY80WQ6EKGg=",
|
||||
"lastModified": 1784017020
|
||||
},
|
||||
"nixpkgs": {
|
||||
"type": "github",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "6cdc7fc76e8bf7fde9fa43a849fcaaa70e230dee",
|
||||
"narHash": "sha256-FmieJB8/OUvNxbkboi7+IGfIuSXY3nF/hZQm8kD0r50=",
|
||||
"lastModified": 1783915482
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
[all_follow]
|
||||
nixpkgs = "nixpkgs"
|
||||
|
||||
[tack]
|
||||
recomposable = true
|
||||
|
||||
[inputs.fenix]
|
||||
url = "github:nix-community/fenix"
|
||||
|
||||
[inputs.nixpkgs]
|
||||
url = "github:NixOS/nixpkgs/nixpkgs-unstable"
|
||||
Generated
+1
-60
@@ -1,65 +1,6 @@
|
||||
{
|
||||
"nodes": {
|
||||
"fenix": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"rust-analyzer-src": "rust-analyzer-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1773646590,
|
||||
"narHash": "sha256-qwnecNC3DB0hSu6MvU27xh/Mg9uPbmmg7d1wBOtO7ds=",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "350a4df2afc34c1ae115173e0509cec7067a06c9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1773597492,
|
||||
"narHash": "sha256-hQ284SkIeNaeyud+LS0WVLX+WL2rxcVZLFEaK0e03zg=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "a07d4ce6bee67d7c838a8a5796e75dff9caa21ef",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"fenix": "fenix",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1773543526,
|
||||
"narHash": "sha256-CKmkYqUi2pI1uDGDfpK0mkZbRLyjUKCpYDU3eMHtmks=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "90c8906e6443e7cee18cece9c2621a8b1c10794c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "rust-lang",
|
||||
"ref": "nightly",
|
||||
"repo": "rust-analyzer",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
"root": {}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
{
|
||||
description = "A privacy-focused Twitter/X frontend written in Rust";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||
fenix = {
|
||||
url = "github:nix-community/fenix";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
|
||||
outputs =
|
||||
{ nixpkgs, fenix, ... }:
|
||||
{ self, ... }@args:
|
||||
let
|
||||
inputs = (import ./.tack) { overrides = args.tackOverrides or { }; };
|
||||
inherit (inputs) nixpkgs fenix;
|
||||
inherit (nixpkgs) lib;
|
||||
forAllSystems = lib.genAttrs (lib.systems.doubles.linux ++ lib.systems.doubles.darwin);
|
||||
pkgsFor = system: nixpkgs.legacyPackages.${system} or (import nixpkgs { inherit system; });
|
||||
|
||||
Reference in new issue
Block a user