1 # nix-pin-deps: gc-pin dependencies of a partially-built derivation
3 # This program is free software: you can redistribute it and/or modify it
4 # under the terms of the GNU General Public License as published by the
5 # Free Software Foundation, version 3.
8 from contextlib
import contextmanager
14 import xml
.sax
.handler
16 from typing
import Any
, Dict
, Iterator
, List
, Set
20 def log(msg
: str) -> Iterator
[None]:
21 print(msg
, file=sys
.stderr
, end
='', flush
=True)
25 print('\r', file=sys
.stderr
, end
='', flush
=True)
28 class ParseNixStoreQueryGraphML(xml
.sax
.handler
.ContentHandler
):
29 def __init__(self
) -> None:
31 self
.roots
: Set
[str] = set()
32 self
.non_roots
: Set
[str] = set()
33 self
.deps
: Dict
[str, List
[str]] = {}
35 def startElement(self
, name
: str, attrs
: Any
) -> None:
37 source
= attrs
.getValue("source")
38 target
= attrs
.getValue("target")
39 self
.non_roots
.add(target
)
40 self
.deps
.setdefault(source
, []).append(target
)
41 if target
in self
.roots
:
42 self
.roots
.remove(target
)
43 if source
not in self
.non_roots
:
44 self
.roots
.add(source
)
47 def getDeps(drv
: str) -> ParseNixStoreQueryGraphML
:
48 with log("Loading dependency tree..."):
49 with subprocess
.Popen(
50 ["nix-store", "--query", "--graphml", drv
], stdout
=subprocess
.PIPE
) as process
:
51 parser
= ParseNixStoreQueryGraphML()
53 xml
.sax
.parse(process
.stdout
, parser
)
54 assert process
.wait() == 0
58 def getDrvInfo(drv
: str) -> Any
:
59 with log(f
"Loading {drv}..."):
60 with subprocess
.Popen(
61 ["nix", "--experimental-features", "nix-command",
62 "show-derivation", "/nix/store/" + drv
],
63 stdout
=subprocess
.PIPE
) as process
:
65 info
= json
.load(process
.stdout
)
66 assert process
.wait() == 0
70 def allBuilt(drv
: str) -> bool:
71 # TODO: How to pin outputs one at a time?
72 # Currently, we only pin when all outputs are available.
73 # It would be better to pin the outputs we've got.
74 return all(os
.path
.exists(o
['path']) for d
in getDrvInfo(
75 drv
).values() for o
in d
['outputs'].values())
78 def isDrv(thing
: str) -> bool:
79 return thing
.endswith(".drv")
82 def removesuffix(s
: str, suf
: str) -> str:
83 return s
[:-len(suf
)] if s
.endswith(suf
) else s
86 def pin(drv
: str) -> None:
87 outPath
= os
.path
.join(sys
.argv
[2], removesuffix(drv
, ".drv"))
88 if not os
.path
.exists(outPath
):
89 subprocess
.run(["nix-store", "--realise", "--add-root",
90 outPath
, "/nix/store/" + drv
], check
=True)
93 def pinBuiltThings(thing
: str,
95 deps
: Dict
[str, List
[str]]) -> None:
99 if not isDrv(thing
) or allBuilt(thing
):
102 for dep
in deps
.get(thing
, []):
103 pinBuiltThings(dep
, done
, deps
)
107 dep_graph
= getDeps(sys
.argv
[1])
108 for root
in dep_graph
.roots
:
109 pinBuiltThings(root
, set(), dep_graph
.deps
)
112 if __name__
== '__main__':