2022-05-24 17:07:41 +02:00
|
|
|
import std/[asyncdispatch, deques, json, os, osproc, streams, strutils, tables]
|
|
|
|
import dhall/[render, terms]
|
2021-02-08 11:37:04 +01:00
|
|
|
import eris
|
|
|
|
|
2022-05-24 17:07:41 +02:00
|
|
|
proc toDhall(js: JsonNode): Value =
|
|
|
|
case js.kind
|
|
|
|
of JString:
|
|
|
|
result = newValue(js.str)
|
|
|
|
of JObject:
|
|
|
|
result = newRecordLiteral(js.fields.len)
|
|
|
|
for key, val in js.fields.pairs:
|
|
|
|
result.table[key] = val.toDhall
|
|
|
|
else:
|
|
|
|
raiseAssert "unhandled JSON value"
|
2021-02-08 11:37:04 +01:00
|
|
|
|
|
|
|
if getEnv("dontErisPatch") != "": quit 0
|
|
|
|
|
|
|
|
let
|
|
|
|
patchelf = getEnv("ERIS_PATCHELF", "patchelf")
|
|
|
|
nixStore = getEnv("NIX_STORE", "/nix/store")
|
2022-05-24 17:07:41 +02:00
|
|
|
jsonManifestSubPath = "nix-support" / "eris-manifest.json"
|
|
|
|
dhallManifestSubPath = "nix-support" / "eris-manifest.dhall"
|
2021-02-08 11:37:04 +01:00
|
|
|
|
|
|
|
proc isElf(path: string): bool =
|
|
|
|
var magic: array[4, char]
|
|
|
|
let file = open(path)
|
2022-05-03 23:46:17 +02:00
|
|
|
discard readChars(file, magic)
|
2021-02-08 11:37:04 +01:00
|
|
|
close(file)
|
|
|
|
magic == [0x7f.char, 'E', 'L', 'F']
|
|
|
|
|
|
|
|
type PendingFile = ref object
|
|
|
|
outputRoot, filePath: string
|
|
|
|
replacements: Table[string, string]
|
|
|
|
|
|
|
|
var
|
|
|
|
outputManifests = initTable[string, JsonNode]()
|
|
|
|
pendingFiles = initDeque[PendingFile]()
|
|
|
|
failed = false
|
|
|
|
for outputName in getEnv("outputs").splitWhitespace:
|
|
|
|
let outputRoot = getEnv(outputName)
|
2022-05-24 17:07:41 +02:00
|
|
|
if fileExists(outputRoot / jsonManifestSubPath):
|
2021-02-08 11:37:04 +01:00
|
|
|
echo "Not running ERIS patch hook again"
|
|
|
|
quit 0
|
|
|
|
outputManifests[outputRoot] = newJObject()
|
|
|
|
|
|
|
|
let buildInputs = getEnv("buildInputs").splitWhitespace
|
|
|
|
|
|
|
|
proc resolveNeed(rpath: seq[string]; need: string): string =
|
|
|
|
if need.isAbsolute:
|
|
|
|
return need
|
|
|
|
for libdir in rpath:
|
|
|
|
let absNeed = libdir / need
|
|
|
|
if fileExists(absNeed):
|
|
|
|
return absNeed
|
|
|
|
for outputRoot in outputManifests.keys:
|
|
|
|
for relPath in [need, "lib" / need]:
|
|
|
|
let absNeed = outputRoot / relPath
|
|
|
|
if fileExists(absNeed):
|
|
|
|
return absNeed
|
|
|
|
for buildInput in buildInputs:
|
|
|
|
for relPath in [need, "lib" / need]:
|
|
|
|
let absNeed = buildInput / relPath
|
|
|
|
if fileExists(absNeed):
|
|
|
|
return absNeed
|
|
|
|
|
|
|
|
proc resolveFile(outputRoot, filePath: string): PendingFile =
|
|
|
|
result = PendingFile(
|
|
|
|
outputRoot: outputRoot,
|
|
|
|
filePath: filePath,
|
|
|
|
replacements: initTable[string, string](8))
|
|
|
|
let needs = splitWhitespace(execProcess(
|
|
|
|
patchelf, args = ["--print-needed", filePath], options = {poUsePath}))
|
|
|
|
let rpath = splitWhitespace(execProcess(
|
|
|
|
patchelf, args = ["--print-rpath", filePath], options = {poUsePath}))
|
|
|
|
for need in needs:
|
|
|
|
if need == "ld.lib.so" or need.startsWith("urn:"): continue
|
|
|
|
result.replacements[need] = resolveNeed(rpath, need)
|
|
|
|
|
2022-05-03 23:46:17 +02:00
|
|
|
var capCache = initTable[string, ErisCap]()
|
2021-02-08 11:37:04 +01:00
|
|
|
|
2022-05-03 23:46:17 +02:00
|
|
|
proc fileUrn(filePath: string): string =
|
2021-02-08 11:37:04 +01:00
|
|
|
## Determine the ERIS URN for ``filePath``.
|
2022-05-03 23:46:17 +02:00
|
|
|
var cap: ErisCap
|
2021-02-08 11:37:04 +01:00
|
|
|
if capCache.hasKey(filePath):
|
|
|
|
cap = capCache[filePath]
|
|
|
|
else:
|
|
|
|
try:
|
2022-05-03 23:46:17 +02:00
|
|
|
let blockSize = if getFileSize(filePath) < (16 shl 10):
|
|
|
|
bs1k
|
|
|
|
else:
|
|
|
|
bs32k
|
|
|
|
let str = openFileStream(filePath)
|
2021-02-08 11:37:04 +01:00
|
|
|
cap = waitFor encode(newDiscardStore(), blockSize, str)
|
|
|
|
capCache["filePath"] = cap
|
|
|
|
close(str)
|
|
|
|
except:
|
|
|
|
stderr.writeLine("failed to read \"", filePath, "\"")
|
|
|
|
quit 1
|
|
|
|
$cap # & "#" & encodeUrl(extractFilename(filePath), usePlus = false)
|
|
|
|
|
|
|
|
var closureCache = initTable[string, TableRef[string, string]]()
|
|
|
|
|
|
|
|
proc fileClosure(filePath: string): TableRef[string, string] =
|
|
|
|
## Recusively find the dependency closure of ``filePath``.
|
|
|
|
let filePath = expandFilename filePath
|
|
|
|
if closureCache.hasKey(filePath):
|
|
|
|
result = closureCache[filePath]
|
|
|
|
else:
|
|
|
|
result = newTable[string, string]()
|
|
|
|
var storePath = filePath
|
|
|
|
for p in parentDirs(filePath):
|
|
|
|
# find the top directory of the ``filePath`` derivation
|
|
|
|
if p == nixStore: break
|
|
|
|
storePath = p
|
|
|
|
if storePath.startsWith nixStore:
|
|
|
|
# read the closure manifest of the dependency
|
2022-05-24 17:07:41 +02:00
|
|
|
let manifestPath = storePath / jsonManifestSubPath
|
2021-02-08 11:37:04 +01:00
|
|
|
if fileExists(manifestPath):
|
|
|
|
let
|
|
|
|
manifest = parseFile(manifestPath)
|
2022-05-25 06:06:57 +02:00
|
|
|
entry = manifest[filePath.extractFilename]
|
2021-02-08 11:37:04 +01:00
|
|
|
for path, cap in entry["closure"].pairs:
|
|
|
|
result[path] = cap.getStr
|
|
|
|
let otherClosure = fileClosure(path)
|
|
|
|
for otherPath, otherCap in otherClosure.pairs:
|
|
|
|
# merge the closure of the dependency
|
|
|
|
result[otherPath] = otherCap
|
|
|
|
closureCache[filePath] = result
|
|
|
|
|
|
|
|
for outputRoot in outputManifests.keys:
|
2022-05-24 17:07:41 +02:00
|
|
|
let manifestPath = outputRoot / jsonManifestSubPath
|
2021-02-08 11:37:04 +01:00
|
|
|
if fileExists manifestPath: continue
|
|
|
|
for filePath in walkDirRec(outputRoot, relative = false):
|
|
|
|
# Populate the queue of files to patch
|
|
|
|
if filePath.isElf:
|
|
|
|
pendingFiles.addLast(resolveFile(outputRoot, filePath))
|
|
|
|
|
|
|
|
var
|
|
|
|
prevLen = pendingFiles.len
|
|
|
|
prevPrevLen = prevLen.succ
|
|
|
|
# used to detect reference cycles
|
|
|
|
while pendingFiles.len != 0:
|
|
|
|
block selfReferenceCheck:
|
|
|
|
# process the files that have been collected
|
|
|
|
# taking care not to take a the URN of an
|
|
|
|
# unprocessed file
|
|
|
|
let
|
|
|
|
pendingFile = pendingFiles.popFirst()
|
|
|
|
filePath = pendingFile.filePath
|
|
|
|
for need, replacementPath in pendingFile.replacements.pairs:
|
|
|
|
# search for self-references
|
|
|
|
if replacementPath == "":
|
|
|
|
echo need, " not found for ", filePath
|
|
|
|
failed = true
|
|
|
|
continue
|
|
|
|
for outputRoot in outputManifests.keys:
|
|
|
|
if replacementPath.startsWith(outputRoot):
|
|
|
|
for other in pendingFiles.items:
|
|
|
|
echo "compare for self-reference:"
|
|
|
|
echo '\t', replacementPath
|
|
|
|
echo '\t', other.filePath
|
|
|
|
if replacementPath == other.filePath:
|
|
|
|
echo "defering patch of ", filePath, " with reference to ", other.filePath
|
|
|
|
pendingFiles.addLast(pendingFile)
|
|
|
|
break selfReferenceCheck
|
|
|
|
var
|
|
|
|
closure = newJObject()
|
|
|
|
replaceCmd = patchelf & " --set-rpath '' " & filePath
|
|
|
|
for need, replacementPath in pendingFile.replacements.pairs:
|
|
|
|
if replacementPath == "": continue
|
2022-05-03 23:46:17 +02:00
|
|
|
let urn = fileUrn(replacementPath)
|
2021-02-08 11:37:04 +01:00
|
|
|
echo "replace reference to ", need, " with ", urn
|
|
|
|
replaceCmd.add(" --replace-needed $# $#" % [need, urn])
|
|
|
|
closure[replacementPath] = %urn
|
|
|
|
for path, urn in fileClosure(replacementPath).pairs:
|
|
|
|
closure[path] = %urn
|
|
|
|
if pendingFile.replacements.len != 0:
|
2022-05-03 23:46:17 +02:00
|
|
|
let exitCode = execCmd(replaceCmd)
|
2021-02-08 11:37:04 +01:00
|
|
|
if exitCode != 0:
|
2022-05-03 23:46:17 +02:00
|
|
|
echo "Patchelf failed - ", replaceCmd
|
2021-02-08 11:37:04 +01:00
|
|
|
quit exitCode
|
2022-05-25 06:06:57 +02:00
|
|
|
outputManifests[pendingFile.outputRoot][filePath.extractFilename] = %* {
|
2022-05-03 23:46:17 +02:00
|
|
|
"cap": fileUrn(filePath),
|
2021-02-08 11:37:04 +01:00
|
|
|
"closure": closure,
|
2022-05-25 06:06:57 +02:00
|
|
|
"path": filePath
|
2021-02-08 11:37:04 +01:00
|
|
|
}
|
|
|
|
if pendingFiles.len == prevPrevLen:
|
|
|
|
failed = true
|
|
|
|
echo "reference cycle detected in the following:"
|
|
|
|
for remain in pendingFiles.items:
|
|
|
|
echo '\t', " ", remain.filePath
|
|
|
|
break
|
|
|
|
prevPrevLen = prevLen
|
|
|
|
prevLen = pendingFiles.len
|
|
|
|
|
|
|
|
if failed:
|
|
|
|
quit -1
|
|
|
|
|
|
|
|
for outputRoot, manifest in outputManifests:
|
2022-05-24 17:07:41 +02:00
|
|
|
let supportDir = outputRoot / "nix-support"
|
|
|
|
createDir(supportDir)
|
|
|
|
writeFile(outputRoot / jsonManifestSubPath, $manifest)
|
2022-05-25 06:06:57 +02:00
|
|
|
writeFile(outputRoot / dhallManifestSubPath, $(manifest.toDhall))
|