254 lines
7.8 KiB
Nim
254 lines
7.8 KiB
Nim
# SPDX-FileCopyrightText: ☭ Emery Hemingway
|
|
# SPDX-License-Identifier: Unlicense
|
|
|
|
import nimblepkg/common,
|
|
nimblepkg/options,
|
|
nimblepkg/packageinfo,
|
|
nimblepkg/packageparser,
|
|
nimblepkg/version
|
|
|
|
import std/[algorithm, deques, httpclient, json, os, osproc, parseutils, streams, strutils, uri]
|
|
|
|
const githubPackagesUrl =
|
|
"https://raw.githubusercontent.com/nim-lang/packages/master/packages.json"
|
|
|
|
proc registryCachePath: string =
|
|
result = getEnv("XDG_CACHE_HOME")
|
|
if result == "":
|
|
let home = getEnv("HOME")
|
|
if home == "":
|
|
result = getEnv("TMPDIR", "/tmp")
|
|
else:
|
|
result = home / ".cache"
|
|
result.add "/packages.json"
|
|
|
|
proc isGitUrl(uri: Uri): bool =
|
|
uri.scheme == "git" or
|
|
uri.scheme.startsWith("git+") or
|
|
uri.path.endsWith(".git") or
|
|
uri.hostname.contains("git")
|
|
|
|
proc startProcess(cmd: string; cmdArgs: varargs[string]): Process =
|
|
# stderr.writeLine(cmd, " ", join(cmdArgs, " "))
|
|
startProcess(cmd, args = cmdArgs, options = {poUsePath})
|
|
|
|
proc gitLsRemote(url: string; withTags: bool): seq[tuple[tag: string, rev: string]] =
|
|
var line, rev, refer: string
|
|
var process =
|
|
if withTags:
|
|
startProcess("git", "ls-remote", "--tags", url)
|
|
else:
|
|
startProcess("git", "ls-remote", url)
|
|
while process.outputStream.readLine(line):
|
|
var off = 0
|
|
off.inc parseUntil(line, rev, Whitespace, off)
|
|
off.inc skipWhile(line, Whitespace, off)
|
|
refer = line[off..line.high]
|
|
const
|
|
refsTags = "refs/tags/"
|
|
headsTags = "refs/heads/"
|
|
if refer.startsWith(refsTags):
|
|
refer.removePrefix(refsTags)
|
|
result.add((refer, rev,))
|
|
elif refer.startsWith(headsTags):
|
|
refer.removePrefix(headsTags)
|
|
result.add((refer, rev,))
|
|
stderr.write(process.errorStream.readAll)
|
|
close(process)
|
|
if withTags and result.len == 0:
|
|
result = gitLsRemote(url, not withTags)
|
|
|
|
proc matchRev(url: string; wanted: VersionRange): tuple[tag: string, rev: string] =
|
|
if wanted.kind == verSpecial:
|
|
let special = string(wanted.spe)
|
|
if special.len == 41 and special[0] == '#':
|
|
result[1] = special[1..39]
|
|
else:
|
|
quit("unhandled version " & url & " " & $wanted)
|
|
else:
|
|
let withTags = wanted.kind != verAny
|
|
let pairs = gitLsRemote(url, withTags)
|
|
var resultVersion: Version
|
|
for (tag, rev) in pairs:
|
|
var tagVer = Version(tag)
|
|
if tagVer.withinRange(wanted) and resultVersion < tagVer:
|
|
resultVersion = tagVer
|
|
result = (tag, rev)
|
|
if result.rev == "" and pairs.len > 0:
|
|
result = pairs[pairs.high]
|
|
doAssert result.rev != "", url
|
|
|
|
proc collectMetadata(data: JsonNode) =
|
|
let storePath = data["path"].getStr
|
|
var packageNames = newSeq[string]()
|
|
for (kind, path) in walkDir(storePath):
|
|
if kind in {pcFile, pcLinkToFile} and path.endsWith(".nimble"):
|
|
var (_, name, _) = splitFile(path)
|
|
packageNames.add name
|
|
if packageNames.len == 0:
|
|
quit("no .nimble files found in " & storePath)
|
|
sort(packageNames)
|
|
data["packages"] = %packageNames
|
|
var
|
|
nimbleFilePath = findNimbleFile(storePath, true)
|
|
pkg = readPackageInfo(nimbleFilePath, parseCmdLine())
|
|
data["srcDir"] = %pkg.srcDir
|
|
|
|
proc prefetchGit(uri: Uri; version: VersionRange): JsonNode =
|
|
var
|
|
uri = uri
|
|
subdir = ""
|
|
uri.scheme.removePrefix("git+")
|
|
if uri.query != "":
|
|
if uri.query.startsWith("subdir="):
|
|
subdir = uri.query[7 .. ^1]
|
|
uri.query = ""
|
|
let cloneUrl = $uri
|
|
let (tag, rev) = matchRev(cloneUrl, version)
|
|
var archiveUri = uri
|
|
archiveUri.scheme = "https"
|
|
archiveUri.path.removeSuffix ".git"
|
|
archiveUri.path.add "/archive/"
|
|
archiveUri.path.add rev
|
|
archiveUri.path.add ".tar.gz"
|
|
let client = newHttpClient()
|
|
defer: close(client)
|
|
let
|
|
archiveUrl = $archiveUri
|
|
resp = head(client, archiveUrl)
|
|
if resp.code in {Http200, Http302}:
|
|
stderr.writeLine "prefetch ", archiveUrl
|
|
var lines = execProcess(
|
|
"nix-prefetch-url",
|
|
args = @[archiveUrl, "--type", "sha256", "--print-path", "--unpack", "--name", "source"],
|
|
options = {poUsePath})
|
|
var
|
|
hash, storePath: string
|
|
off: int
|
|
off.inc parseUntil(lines, hash, {'\n'}, off).succ
|
|
off.inc parseUntil(lines, storePath, {'\n'}, off).succ
|
|
doAssert off == lines.len, "unrecognized nix-prefetch-url output:\n" & lines
|
|
result = newJObject()
|
|
result["method"] = %"fetchzip"
|
|
result["path"] = %storePath
|
|
result["rev"] = %rev
|
|
result["sha256"] = %hash
|
|
result["url"] = %archiveUrl
|
|
if subdir != "":
|
|
result["subdir"] = %* subdir
|
|
else:
|
|
stderr.writeLine "fetch of ", archiveUrl, " returned ", resp.code
|
|
var args = @["--quiet", "--fetch-submodules", "--url", cloneUrl, "--rev", rev]
|
|
stderr.writeLine "prefetch ", cloneUrl
|
|
let dump = execProcess(
|
|
"nix-prefetch-git",
|
|
args = args,
|
|
options = {poUsePath})
|
|
try: result = parseJson dump
|
|
except JsonParsingError:
|
|
stderr.writeLine "failed to parse output of nix-prefetch-git ", join(args, " ")
|
|
quit(dump)
|
|
if subdir != "":
|
|
result["subdir"] = %* subdir
|
|
result["method"] = %"git"
|
|
if tag != "":
|
|
result["ref"] = %tag
|
|
collectMetadata(result)
|
|
|
|
proc containsPackageUri(lockAttrs: JsonNode; pkgUri: string): bool =
|
|
for e in lockAttrs.items:
|
|
if e["url"].getStr == pkgUri:
|
|
return true
|
|
|
|
proc containsPackage(lockAttrs: JsonNode; pkgName: string): bool =
|
|
for e in lockAttrs.items:
|
|
for other in e["packages"].items:
|
|
if pkgName == other.getStr:
|
|
return true
|
|
|
|
proc collectRequires(pending: var Deque[PkgTuple]; pkgPath: string) =
|
|
var
|
|
nimbleFilePath = findNimbleFile(pkgPath, true)
|
|
pkg = readPackageInfo(nimbleFilePath, parseCmdLine())
|
|
for pair in pkg.requires:
|
|
if pair.name != "nim" and pair.name != "compiler":
|
|
pending.addLast(pair)
|
|
|
|
var globalRegistry: JsonNode
|
|
|
|
proc getPackgeUri(name: string): tuple[uri: string, meth: string] =
|
|
if globalRegistry.isNil:
|
|
let registryPath = registryCachePath()
|
|
if fileExists(registryPath):
|
|
globalRegistry = parseFile(registryPath)
|
|
else:
|
|
let client = newHttpClient()
|
|
var raw = client.getContent(githubPackagesUrl)
|
|
close(client)
|
|
writeFile(registryPath, raw)
|
|
globalRegistry = parseJson(raw)
|
|
var
|
|
name = name
|
|
i = 0
|
|
while i < globalRegistry.len:
|
|
var e = globalRegistry[i]
|
|
if e["name"].getStr == name:
|
|
if e.hasKey "alias":
|
|
var alias = e["alias"].getStr
|
|
doAssert alias != name
|
|
name = alias
|
|
i = 0
|
|
else:
|
|
try:
|
|
return (e["url"].getStr, e["method"].getStr,)
|
|
except CatchableError:
|
|
quit("Failed to parse shit JSON " & $e)
|
|
inc i
|
|
|
|
proc generateLockfile(): JsonNode =
|
|
result = newJObject()
|
|
var
|
|
deps = newJArray()
|
|
pending: Deque[PkgTuple]
|
|
collectRequires(pending, getCurrentDir())
|
|
while pending.len > 0:
|
|
let batchLen = pending.len
|
|
for i in 1..batchLen:
|
|
var pkgData: JsonNode
|
|
let pkg = pending.popFirst()
|
|
if pkg.name == "nim" or pkg.name == "compiler":
|
|
continue
|
|
var uri = parseUri(pkg.name)
|
|
if uri.scheme == "":
|
|
if not deps.containsPackage(pkg.name):
|
|
pending.addLast(pkg)
|
|
elif not deps.containsPackageUri(pkg.name):
|
|
if uri.isGitUrl:
|
|
pkgData = prefetchGit(uri, pkg.ver)
|
|
else:
|
|
quit("unhandled URI " & $uri)
|
|
collectRequires(pending, pkgData["path"].getStr)
|
|
deps.add pkgData
|
|
|
|
if batchLen == pending.len:
|
|
var
|
|
pkgData: JsonNode
|
|
pkg = pending.popFirst()
|
|
info = getPackgeUri(pkg.name)
|
|
uri = parseUri info.uri
|
|
case info.meth
|
|
of "git":
|
|
pkgData = prefetchGit(uri, pkg.ver)
|
|
else:
|
|
quit("unhandled fetch method " & $info.meth & " for " & info.uri)
|
|
collectRequires(pending, pkgData["path"].getStr)
|
|
deps.add pkgData
|
|
sort(deps.elems)
|
|
result["depends"] = deps
|
|
|
|
proc main =
|
|
var lockInfo = generateLockfile()
|
|
stdout.writeLine lockInfo
|
|
|
|
main()
|