Compare commits
3 Commits
39b552c831
...
dffb461f94
Author | SHA1 | Date |
---|---|---|
Ehmry - | dffb461f94 | |
Ehmry - | f978c53a29 | |
Ehmry - | 2e2129e873 |
21
README.md
21
README.md
|
@ -12,13 +12,16 @@ nim_lk > lock.json
|
||||||
These lock files contain Nix FOD store paths that can be converted to `nim.cfg` files.
|
These lock files contain Nix FOD store paths that can be converted to `nim.cfg` files.
|
||||||
|
|
||||||
```nix
|
```nix
|
||||||
{ pkgs ? import <nixpkgs> { }, lockPath }:
|
{ pkgs ? import <nixpkgs> { }, lockPath, excludes ? [ ] }:
|
||||||
|
|
||||||
let inherit (pkgs) lib;
|
let inherit (pkgs) lib;
|
||||||
in lib.pipe lockPath [
|
in with builtins;
|
||||||
builtins.readFile
|
lib.pipe lockPath [
|
||||||
builtins.fromJSON
|
readFile
|
||||||
(builtins.getAttr "depends")
|
fromJSON
|
||||||
(map ({ path, srcDir, ... }: ''path:"${path}/${srcDir}"''))
|
(getAttr "depends")
|
||||||
|
(filter ({ packages, ... }: !(any (pkg: elem pkg excludes) packages)))
|
||||||
|
(map ({ path, srcDir, ... }: ''path:"${storePath path}/${srcDir}"''))
|
||||||
lib.strings.concatLines
|
lib.strings.concatLines
|
||||||
(pkgs.writeText "nim.cfg")
|
(pkgs.writeText "nim.cfg")
|
||||||
]
|
]
|
||||||
|
@ -28,15 +31,17 @@ I manage all this with [Tup](https://gittup.org/tup).
|
||||||
|
|
||||||
```
|
```
|
||||||
# Tuprules.tup above my Nim projects
|
# Tuprules.tup above my Nim projects
|
||||||
!nim_lk = |> nim_lk > %o |> lock.json
|
!nim_lk = |> nim_lk | jq --compact-output --sort-keys > %o |> lock.json
|
||||||
|
|
||||||
NIXEXPRS_DIR = $(TUP_CWD)/nixexprs
|
NIXEXPRS_DIR = $(TUP_CWD)/nixexprs
|
||||||
!nim_cfg = |> nix build --file $(NIXEXPRS_DIR)/configure.nix --argstr lockPath `pwd`/%f --out-link %o |> nim.cfg
|
!nim_cfg = |> nix build --file $(NIXEXPRS_DIR)/configure.nix --out-link %o --arg lockPath `pwd`/%f --arg excludes '[$(NIM_LOCK_EXCLUDES)]' |> nim.cfg
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
```
|
||||||
# Tupfile in a Nim project
|
# Tupfile in a Nim project
|
||||||
include_rules
|
include_rules
|
||||||
|
# Omit the foobar library from nim.cfg.
|
||||||
|
NIM_LOCK_EXCLUDES += "foobar"
|
||||||
: |> !nim_lk |> | ./<lock>
|
: |> !nim_lk |> | ./<lock>
|
||||||
: lock.json |> !nim_cfg |> | ./<lock>
|
: lock.json |> !nim_cfg |> | ./<lock>
|
||||||
```
|
```
|
||||||
|
|
|
@ -3,6 +3,6 @@ bin = @["nim_lk"]
|
||||||
description = "Tool for generating Nim lockfiles"
|
description = "Tool for generating Nim lockfiles"
|
||||||
license = "BSD-3-Clause"
|
license = "BSD-3-Clause"
|
||||||
srcDir = "src"
|
srcDir = "src"
|
||||||
version = "20231004"
|
version = "20231008"
|
||||||
|
|
||||||
requires "nim >= 2.0.0"
|
requires "nim >= 2.0.0"
|
||||||
|
|
169
src/nim_lk.nim
169
src/nim_lk.nim
|
@ -1,14 +1,13 @@
|
||||||
import private/nix
|
# SPDX-FileCopyrightText: ☭ Emery Hemingway
|
||||||
|
# SPDX-License-Identifier: Unlicense
|
||||||
|
|
||||||
import nimblepkg/common,
|
import nimblepkg/common,
|
||||||
nimblepkg/download,
|
|
||||||
nimblepkg/packageinfo,
|
|
||||||
nimblepkg/options,
|
nimblepkg/options,
|
||||||
nimblepkg/version,
|
nimblepkg/packageinfo,
|
||||||
nimblepkg/packageparser,
|
nimblepkg/packageparser,
|
||||||
nimblepkg/cli
|
nimblepkg/version
|
||||||
|
|
||||||
import std/[algorithm, deques, httpclient, json, monotimes, os, osproc, parseutils, random, sequtils, streams, strutils, tables, times, uri]
|
import std/[algorithm, deques, httpclient, json, os, osproc, parseutils, streams, strutils, uri]
|
||||||
|
|
||||||
const githubPackagesUrl =
|
const githubPackagesUrl =
|
||||||
"https://raw.githubusercontent.com/nim-lang/packages/master/packages.json"
|
"https://raw.githubusercontent.com/nim-lang/packages/master/packages.json"
|
||||||
|
@ -28,23 +27,44 @@ func isGitUrl(uri: Uri): bool =
|
||||||
uri.scheme == "git" or
|
uri.scheme == "git" or
|
||||||
uri.scheme.startsWith("git+")
|
uri.scheme.startsWith("git+")
|
||||||
|
|
||||||
proc gitLsRemote(url: string): seq[tuple[tag: string, rev: string]] =
|
proc isWebGitForgeUrl(uri: Uri): bool =
|
||||||
var lines = execProcess(
|
result =
|
||||||
"git",
|
case uri.hostname
|
||||||
args = ["ls-remote", "--tags", url],
|
of "github.com", "git.sr.ht", "codeberg.org": true
|
||||||
options = {poUsePath},
|
else: false
|
||||||
)
|
if not result:
|
||||||
result.setLen(lines.countLines.pred)
|
stderr.writeLine "not a web forge hostname: '", uri.hostname, "'"
|
||||||
var off = 0
|
|
||||||
for i in 0..result.high:
|
proc startProcess(cmd: string; cmdArgs: varargs[string]): Process =
|
||||||
off.inc parseUntil(lines, result[i].rev, {'\x09'}, off)
|
startProcess(cmd, args = cmdArgs, options = {poUsePath})
|
||||||
off.inc skipWhiteSpace(lines, off)
|
|
||||||
off.inc skipUntil(lines, '/', off).succ
|
proc gitLsRemote(url: string; withTags: bool): seq[tuple[tag: string, rev: string]] =
|
||||||
off.inc skipUntil(lines, '/', off).succ
|
var line, rev, refer: string
|
||||||
off.inc parseUntil(lines, result[i].tag, {'\x0a'}, off).succ
|
var process =
|
||||||
|
if withTags:
|
||||||
|
startProcess("git", "ls-remote", "--tags", url)
|
||||||
|
else:
|
||||||
|
startProcess("git", "ls-remote", url)
|
||||||
|
while process.running:
|
||||||
|
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)
|
||||||
|
elif refer.startsWith(headsTags):
|
||||||
|
refer.removePrefix(headsTags)
|
||||||
|
result.add((refer, rev,))
|
||||||
|
stderr.write(process.errorStream.readAll)
|
||||||
|
close(process)
|
||||||
|
|
||||||
proc matchRev(url: string; wanted: VersionRange): tuple[tag: string, rev: string] =
|
proc matchRev(url: string; wanted: VersionRange): tuple[tag: string, rev: string] =
|
||||||
let pairs = gitLsRemote(url)
|
let withTags = wanted.kind != verAny
|
||||||
|
let pairs = gitLsRemote(url, withTags)
|
||||||
var resultVersion: Version
|
var resultVersion: Version
|
||||||
for (tag, rev) in pairs:
|
for (tag, rev) in pairs:
|
||||||
var tagVer = Version(tag)
|
var tagVer = Version(tag)
|
||||||
|
@ -53,23 +73,25 @@ proc matchRev(url: string; wanted: VersionRange): tuple[tag: string, rev: string
|
||||||
result = (tag, rev)
|
result = (tag, rev)
|
||||||
if result.rev == "" and pairs.len > 0:
|
if result.rev == "" and pairs.len > 0:
|
||||||
result = pairs[pairs.high]
|
result = pairs[pairs.high]
|
||||||
|
doAssert result.rev != ""
|
||||||
|
|
||||||
proc collectMetadata(data: JsonNode; options: Options) =
|
proc collectMetadata(data: JsonNode) =
|
||||||
let storePath = data["path"].getStr
|
let storePath = data["path"].getStr
|
||||||
var packageNames = newJArray()
|
var packageNames = newSeq[string]()
|
||||||
for (kind, path) in walkDir(storePath):
|
for (kind, path) in walkDir(storePath):
|
||||||
if kind in {pcFile, pcLinkToFile} and path.endsWith(".nimble"):
|
if kind in {pcFile, pcLinkToFile} and path.endsWith(".nimble"):
|
||||||
var (dir, name, ext) = splitFile(path)
|
var (_, name, _) = splitFile(path)
|
||||||
packageNames.add %name
|
packageNames.add name
|
||||||
if packageNames.len == 0:
|
if packageNames.len == 0:
|
||||||
quit("no .nimble files found in " & storePath)
|
quit("no .nimble files found in " & storePath)
|
||||||
data["packages"] = packageNames
|
sort(packageNames)
|
||||||
|
data["packages"] = %packageNames
|
||||||
var
|
var
|
||||||
nimbleFilePath = findNimbleFile(storePath, true)
|
nimbleFilePath = findNimbleFile(storePath, true)
|
||||||
pkg = readPackageInfo(nimbleFilePath, options)
|
pkg = readPackageInfo(nimbleFilePath, parseCmdLine())
|
||||||
data["srcDir"] = %pkg.srcDir
|
data["srcDir"] = %pkg.srcDir
|
||||||
|
|
||||||
proc prefetchGit(uri: Uri; version: VersionRange; options: Options): JsonNode =
|
proc prefetchGit(uri: Uri; version: VersionRange): JsonNode =
|
||||||
var
|
var
|
||||||
uri = uri
|
uri = uri
|
||||||
subdir = ""
|
subdir = ""
|
||||||
|
@ -80,10 +102,8 @@ proc prefetchGit(uri: Uri; version: VersionRange; options: Options): JsonNode =
|
||||||
uri.query = ""
|
uri.query = ""
|
||||||
let url = $uri
|
let url = $uri
|
||||||
let (tag, rev) = matchRev(url, version)
|
let (tag, rev) = matchRev(url, version)
|
||||||
var args = @["--quiet", "--fetch-submodules", "--url", url]
|
var args = @["--quiet", "--fetch-submodules", "--url", url, "--rev", rev]
|
||||||
if rev != "":
|
stderr.writeLine "prefetch ", url
|
||||||
args.add "--rev"
|
|
||||||
args.add rev
|
|
||||||
let dump = execProcess(
|
let dump = execProcess(
|
||||||
"nix-prefetch-git",
|
"nix-prefetch-git",
|
||||||
args = args,
|
args = args,
|
||||||
|
@ -97,7 +117,50 @@ proc prefetchGit(uri: Uri; version: VersionRange; options: Options): JsonNode =
|
||||||
result["method"] = %"git"
|
result["method"] = %"git"
|
||||||
if tag != "":
|
if tag != "":
|
||||||
result["ref"] = %tag
|
result["ref"] = %tag
|
||||||
collectMetadata(result, options)
|
collectMetadata(result)
|
||||||
|
|
||||||
|
proc prefetchGitForge(uri: Uri; version: VersionRange): JsonNode =
|
||||||
|
result = newJObject()
|
||||||
|
var
|
||||||
|
uri = uri
|
||||||
|
subdir = ""
|
||||||
|
uri.scheme.removePrefix("git+")
|
||||||
|
if uri.query != "":
|
||||||
|
if uri.query.startsWith("subdir="):
|
||||||
|
subdir = uri.query[7 .. ^1]
|
||||||
|
uri.query = ""
|
||||||
|
var url = $uri
|
||||||
|
let (tag, rev) = matchRev(url, version)
|
||||||
|
|
||||||
|
uri.scheme = "https"
|
||||||
|
uri.path.removeSuffix(".git")
|
||||||
|
uri.path.add("/archive/")
|
||||||
|
uri.path.add(rev)
|
||||||
|
uri.path.add(".tar.gz")
|
||||||
|
url = $uri
|
||||||
|
|
||||||
|
stderr.writeLine "prefetch ", url
|
||||||
|
var lines = execProcess(
|
||||||
|
"nix-prefetch-url",
|
||||||
|
args = @[url, "--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["method"] = %"fetchurl"
|
||||||
|
result["path"] = %storePath
|
||||||
|
result["rev"] = %rev
|
||||||
|
result["sha256"] = %hash
|
||||||
|
result["url"] = %url
|
||||||
|
if subdir != "":
|
||||||
|
result["subdir"] = %* subdir
|
||||||
|
if tag != "":
|
||||||
|
result["ref"] = %tag
|
||||||
|
collectMetadata(result)
|
||||||
|
|
||||||
proc containsPackageUri(lockAttrs: JsonNode; pkgUri: string): bool =
|
proc containsPackageUri(lockAttrs: JsonNode; pkgUri: string): bool =
|
||||||
for e in lockAttrs.items:
|
for e in lockAttrs.items:
|
||||||
|
@ -110,10 +173,10 @@ proc containsPackage(lockAttrs: JsonNode; pkgName: string): bool =
|
||||||
if pkgName == other.getStr:
|
if pkgName == other.getStr:
|
||||||
return true
|
return true
|
||||||
|
|
||||||
proc collectRequires(pending: var Deque[PkgTuple]; options: Options; pkgPath: string) =
|
proc collectRequires(pending: var Deque[PkgTuple]; pkgPath: string) =
|
||||||
var
|
var
|
||||||
nimbleFilePath = findNimbleFile(pkgPath, true)
|
nimbleFilePath = findNimbleFile(pkgPath, true)
|
||||||
pkg = readPackageInfo(nimbleFilePath, options)
|
pkg = readPackageInfo(nimbleFilePath, parseCmdLine())
|
||||||
for pair in pkg.requires:
|
for pair in pkg.requires:
|
||||||
if pair.name != "nim" and pair.name != "compiler":
|
if pair.name != "nim" and pair.name != "compiler":
|
||||||
pending.addLast(pair)
|
pending.addLast(pair)
|
||||||
|
@ -149,12 +212,12 @@ proc getPackgeUri(name: string): tuple[uri: string, meth: string] =
|
||||||
quit("Failed to parse shit JSON " & $e)
|
quit("Failed to parse shit JSON " & $e)
|
||||||
inc i
|
inc i
|
||||||
|
|
||||||
proc generateLockfile(options: Options): JsonNode =
|
proc generateLockfile(): JsonNode =
|
||||||
result = newJObject()
|
result = newJObject()
|
||||||
var
|
var
|
||||||
deps = newJArray()
|
deps = newJArray()
|
||||||
pending: Deque[PkgTuple]
|
pending: Deque[PkgTuple]
|
||||||
collectRequires(pending, options, getCurrentDir())
|
collectRequires(pending, getCurrentDir())
|
||||||
while pending.len > 0:
|
while pending.len > 0:
|
||||||
let batchLen = pending.len
|
let batchLen = pending.len
|
||||||
for i in 1..batchLen:
|
for i in 1..batchLen:
|
||||||
|
@ -167,34 +230,36 @@ proc generateLockfile(options: Options): JsonNode =
|
||||||
if not deps.containsPackage(pkg.name):
|
if not deps.containsPackage(pkg.name):
|
||||||
pending.addLast(pkg)
|
pending.addLast(pkg)
|
||||||
elif not deps.containsPackageUri(pkg.name):
|
elif not deps.containsPackageUri(pkg.name):
|
||||||
if uri.isGitUrl:
|
if uri.isWebGitForgeUrl:
|
||||||
pkgData = prefetchGit(uri, pkg.ver, options)
|
pkgData = prefetchGitForge(uri, pkg.ver)
|
||||||
|
elif uri.isGitUrl:
|
||||||
|
pkgData = prefetchGit(uri, pkg.ver)
|
||||||
else:
|
else:
|
||||||
quit("unhandled URI " & $uri)
|
quit("unhandled URI " & $uri)
|
||||||
collectRequires(pending, options, pkgData["path"].getStr)
|
collectRequires(pending, pkgData["path"].getStr)
|
||||||
deps.add pkgData
|
deps.add pkgData
|
||||||
|
|
||||||
if batchLen == pending.len:
|
if batchLen == pending.len:
|
||||||
var
|
var
|
||||||
|
pkgData: JsonNode
|
||||||
pkg = pending.popFirst()
|
pkg = pending.popFirst()
|
||||||
info = getPackgeUri(pkg.name)
|
info = getPackgeUri(pkg.name)
|
||||||
case info.meth
|
uri = parseUri info.uri
|
||||||
of "git":
|
if uri.isWebGitForgeUrl:
|
||||||
stderr.writeLine "prefetch ", info.uri
|
pkgData = prefetchGitForge(uri, pkg.ver)
|
||||||
var pkgData = prefetchGit(parseUri info.uri, pkg.ver, options)
|
|
||||||
collectRequires(pending, options, pkgData["path"].getStr)
|
|
||||||
deps.add pkgData
|
|
||||||
else:
|
else:
|
||||||
quit("unhandled fetch method " & $info.meth & " for " & 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)
|
sort(deps.elems)
|
||||||
result["depends"] = deps
|
result["depends"] = deps
|
||||||
|
|
||||||
proc main =
|
proc main =
|
||||||
var options = parseCmdLine()
|
var lockInfo = generateLockfile()
|
||||||
# parse nimble options, not recommended
|
|
||||||
if options.action.typ != actionCustom:
|
|
||||||
options.action = Action(typ: actionCustom)
|
|
||||||
var lockInfo = generateLockfile(options)
|
|
||||||
stdout.writeLine lockInfo
|
stdout.writeLine lockInfo
|
||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
|
@ -1,324 +0,0 @@
|
||||||
# Copyright (C) Dominik Picheta. All rights reserved.
|
|
||||||
# BSD License. Look at license.txt for more info.
|
|
||||||
|
|
||||||
import parseutils, os, osproc, strutils, tables, pegs, uri, json
|
|
||||||
|
|
||||||
import packageinfo, packageparser, version, tools, common, options, cli
|
|
||||||
from algorithm import SortOrder, sorted
|
|
||||||
from sequtils import toSeq, filterIt, map
|
|
||||||
|
|
||||||
type
|
|
||||||
DownloadMethod* {.pure.} = enum
|
|
||||||
git = "git", hg = "hg"
|
|
||||||
|
|
||||||
proc getSpecificDir(meth: DownloadMethod): string {.used.} =
|
|
||||||
case meth
|
|
||||||
of DownloadMethod.git:
|
|
||||||
".git"
|
|
||||||
of DownloadMethod.hg:
|
|
||||||
".hg"
|
|
||||||
|
|
||||||
proc doCheckout(meth: DownloadMethod, downloadDir, branch: string) =
|
|
||||||
case meth
|
|
||||||
of DownloadMethod.git:
|
|
||||||
cd downloadDir:
|
|
||||||
# Force is used here because local changes may appear straight after a
|
|
||||||
# clone has happened. Like in the case of git on Windows where it
|
|
||||||
# messes up the damn line endings.
|
|
||||||
doCmd("git checkout --force " & branch)
|
|
||||||
doCmd("git submodule update --recursive")
|
|
||||||
of DownloadMethod.hg:
|
|
||||||
cd downloadDir:
|
|
||||||
doCmd("hg checkout " & branch)
|
|
||||||
|
|
||||||
proc doPull(meth: DownloadMethod, downloadDir: string) {.used.} =
|
|
||||||
case meth
|
|
||||||
of DownloadMethod.git:
|
|
||||||
doCheckout(meth, downloadDir, "")
|
|
||||||
cd downloadDir:
|
|
||||||
doCmd("git pull")
|
|
||||||
if existsFile(".gitmodules"):
|
|
||||||
doCmd("git submodule update")
|
|
||||||
of DownloadMethod.hg:
|
|
||||||
doCheckout(meth, downloadDir, "default")
|
|
||||||
cd downloadDir:
|
|
||||||
doCmd("hg pull")
|
|
||||||
|
|
||||||
proc doClone(meth: DownloadMethod, url, downloadDir: string, branch = "",
|
|
||||||
onlyTip = true) =
|
|
||||||
case meth
|
|
||||||
of DownloadMethod.git:
|
|
||||||
let
|
|
||||||
depthArg = if onlyTip: "--depth 1 " else: ""
|
|
||||||
branchArg = if branch == "": "" else: "-b " & branch & " "
|
|
||||||
doCmd("git clone --recursive " & depthArg & branchArg & url &
|
|
||||||
" " & downloadDir)
|
|
||||||
of DownloadMethod.hg:
|
|
||||||
let
|
|
||||||
tipArg = if onlyTip: "-r tip " else: ""
|
|
||||||
branchArg = if branch == "": "" else: "-b " & branch & " "
|
|
||||||
doCmd("hg clone " & tipArg & branchArg & url & " " & downloadDir)
|
|
||||||
|
|
||||||
proc getTagsList(dir: string, meth: DownloadMethod): seq[string] =
|
|
||||||
cd dir:
|
|
||||||
var output = execProcess("git tag")
|
|
||||||
case meth
|
|
||||||
of DownloadMethod.git:
|
|
||||||
output = execProcess("git tag")
|
|
||||||
of DownloadMethod.hg:
|
|
||||||
output = execProcess("hg tags")
|
|
||||||
if output.len > 0:
|
|
||||||
case meth
|
|
||||||
of DownloadMethod.git:
|
|
||||||
result = @[]
|
|
||||||
for i in output.splitLines():
|
|
||||||
if i == "": continue
|
|
||||||
result.add(i)
|
|
||||||
of DownloadMethod.hg:
|
|
||||||
result = @[]
|
|
||||||
for i in output.splitLines():
|
|
||||||
if i == "": continue
|
|
||||||
var tag = ""
|
|
||||||
discard parseUntil(i, tag, ' ')
|
|
||||||
if tag != "tip":
|
|
||||||
result.add(tag)
|
|
||||||
else:
|
|
||||||
result = @[]
|
|
||||||
|
|
||||||
proc getTagsListRemote*(url: string, meth: DownloadMethod): seq[string] =
|
|
||||||
var
|
|
||||||
url = url
|
|
||||||
uri = parseUri url
|
|
||||||
if uri.query != "":
|
|
||||||
uri.query = ""
|
|
||||||
url = $uri
|
|
||||||
result = @[]
|
|
||||||
case meth
|
|
||||||
of DownloadMethod.git:
|
|
||||||
var (output, exitCode) = doCmdEx("git ls-remote --tags " & url)
|
|
||||||
if exitCode != QuitSuccess:
|
|
||||||
raise newException(OSError, "Unable to query remote tags for " & url &
|
|
||||||
". Git returned: " & output)
|
|
||||||
for i in output.splitLines():
|
|
||||||
let refStart = i.find("refs/tags/")
|
|
||||||
# git outputs warnings, empty lines, etc
|
|
||||||
if refStart == -1: continue
|
|
||||||
let start = refStart+"refs/tags/".len
|
|
||||||
let tag = i[start .. i.len-1]
|
|
||||||
if not tag.endswith("^{}"): result.add(tag)
|
|
||||||
|
|
||||||
of DownloadMethod.hg:
|
|
||||||
# http://stackoverflow.com/questions/2039150/show-tags-for-remote-hg-repository
|
|
||||||
raise newException(ValueError, "Hg doesn't support remote tag querying.")
|
|
||||||
|
|
||||||
proc getVersionList*(tags: seq[string]): OrderedTable[Version, string] =
|
|
||||||
## Return an ordered table of Version -> git tag label. Ordering is
|
|
||||||
## in descending order with the most recent version first.
|
|
||||||
let taggedVers: seq[tuple[ver: Version, tag: string]] =
|
|
||||||
tags
|
|
||||||
.filterIt(it != "")
|
|
||||||
.map(proc(s: string): tuple[ver: Version, tag: string] =
|
|
||||||
# skip any chars before the version
|
|
||||||
let i = skipUntil(s, Digits)
|
|
||||||
# TODO: Better checking, tags can have any
|
|
||||||
# names. Add warnings and such.
|
|
||||||
result = (newVersion(s[i .. s.len-1]), s))
|
|
||||||
.sorted(proc(a, b: (Version, string)): int = cmp(a[0], b[0]),
|
|
||||||
SortOrder.Descending)
|
|
||||||
result = toOrderedTable[Version, string](taggedVers)
|
|
||||||
|
|
||||||
proc getDownloadMethod*(meth: string): DownloadMethod =
|
|
||||||
case meth
|
|
||||||
of "git": return DownloadMethod.git
|
|
||||||
of "hg", "mercurial": return DownloadMethod.hg
|
|
||||||
else:
|
|
||||||
raise newException(NimbleError, "Invalid download method: " & meth)
|
|
||||||
|
|
||||||
proc getHeadName*(meth: DownloadMethod): Version =
|
|
||||||
## Returns the name of the download method specific head. i.e. for git
|
|
||||||
## it's ``head`` for hg it's ``tip``.
|
|
||||||
case meth
|
|
||||||
of DownloadMethod.git: newVersion("#head")
|
|
||||||
of DownloadMethod.hg: newVersion("#tip")
|
|
||||||
|
|
||||||
proc checkUrlType*(url: string): DownloadMethod =
|
|
||||||
## Determines the download method based on the URL.
|
|
||||||
if doCmdEx("git ls-remote " & url).exitCode == QuitSuccess:
|
|
||||||
return DownloadMethod.git
|
|
||||||
elif doCmdEx("hg identify " & url).exitCode == QuitSuccess:
|
|
||||||
return DownloadMethod.hg
|
|
||||||
else:
|
|
||||||
raise newException(NimbleError, "Unable to identify url: " & url)
|
|
||||||
|
|
||||||
proc getUrlData*(url: string): (string, Table[string, string]) =
|
|
||||||
var uri = parseUri(url)
|
|
||||||
# TODO: use uri.parseQuery once it lands... this code is quick and dirty.
|
|
||||||
var subdir = ""
|
|
||||||
if uri.query.startsWith("subdir="):
|
|
||||||
subdir = uri.query[7 .. ^1]
|
|
||||||
|
|
||||||
uri.query = ""
|
|
||||||
return ($uri, {"subdir": subdir}.toTable())
|
|
||||||
|
|
||||||
proc isURL*(name: string): bool =
|
|
||||||
name.startsWith(peg" @'://' ")
|
|
||||||
|
|
||||||
proc doDownload(url: string, downloadDir: string, verRange: VersionRange,
|
|
||||||
downMethod: DownloadMethod,
|
|
||||||
options: Options): Version =
|
|
||||||
## Downloads the repository specified by ``url`` using the specified download
|
|
||||||
## method.
|
|
||||||
##
|
|
||||||
## Returns the version of the repository which has been downloaded.
|
|
||||||
template getLatestByTag(meth: untyped) {.dirty.} =
|
|
||||||
# Find latest version that fits our ``verRange``.
|
|
||||||
var latest = findLatest(verRange, versions)
|
|
||||||
## Note: HEAD is not used when verRange.kind is verAny. This is
|
|
||||||
## intended behaviour, the latest tagged version will be used in this case.
|
|
||||||
|
|
||||||
# If no tagged versions satisfy our range latest.tag will be "".
|
|
||||||
# We still clone in that scenario because we want to try HEAD in that case.
|
|
||||||
# https://github.com/nim-lang/nimble/issues/22
|
|
||||||
meth
|
|
||||||
if $latest.ver != "":
|
|
||||||
result = latest.ver
|
|
||||||
|
|
||||||
removeDir(downloadDir)
|
|
||||||
if verRange.kind == verSpecial:
|
|
||||||
# We want a specific commit/branch/tag here.
|
|
||||||
if verRange.spe == getHeadName(downMethod):
|
|
||||||
# Grab HEAD.
|
|
||||||
doClone(downMethod, url, downloadDir, onlyTip = not options.forceFullClone)
|
|
||||||
else:
|
|
||||||
# Grab the full repo.
|
|
||||||
doClone(downMethod, url, downloadDir, onlyTip = false)
|
|
||||||
# Then perform a checkout operation to get the specified branch/commit.
|
|
||||||
# `spe` starts with '#', trim it.
|
|
||||||
doAssert(($verRange.spe)[0] == '#')
|
|
||||||
doCheckout(downMethod, downloadDir, substr($verRange.spe, 1))
|
|
||||||
result = verRange.spe
|
|
||||||
else:
|
|
||||||
case downMethod
|
|
||||||
of DownloadMethod.git:
|
|
||||||
# For Git we have to query the repo remotely for its tags. This is
|
|
||||||
# necessary as cloning with a --depth of 1 removes all tag info.
|
|
||||||
result = getHeadName(downMethod)
|
|
||||||
let versions = getTagsListRemote(url, downMethod).getVersionList()
|
|
||||||
if versions.len > 0:
|
|
||||||
getLatestByTag:
|
|
||||||
display("Cloning", "latest tagged version: " & latest.tag,
|
|
||||||
priority = MediumPriority)
|
|
||||||
doClone(downMethod, url, downloadDir, latest.tag,
|
|
||||||
onlyTip = not options.forceFullClone)
|
|
||||||
else:
|
|
||||||
# If no commits have been tagged on the repo we just clone HEAD.
|
|
||||||
doClone(downMethod, url, downloadDir) # Grab HEAD.
|
|
||||||
of DownloadMethod.hg:
|
|
||||||
doClone(downMethod, url, downloadDir, onlyTip = not options.forceFullClone)
|
|
||||||
result = getHeadName(downMethod)
|
|
||||||
let versions = getTagsList(downloadDir, downMethod).getVersionList()
|
|
||||||
|
|
||||||
if versions.len > 0:
|
|
||||||
getLatestByTag:
|
|
||||||
display("Switching", "to latest tagged version: " & latest.tag,
|
|
||||||
priority = MediumPriority)
|
|
||||||
doCheckout(downMethod, downloadDir, latest.tag)
|
|
||||||
|
|
||||||
proc downloadPkg*(url: string, verRange: VersionRange,
|
|
||||||
downMethod: DownloadMethod,
|
|
||||||
subdir: string,
|
|
||||||
options: Options,
|
|
||||||
downloadPath = ""): (string, Version) =
|
|
||||||
## Downloads the repository as specified by ``url`` and ``verRange`` using
|
|
||||||
## the download method specified.
|
|
||||||
##
|
|
||||||
## If `downloadPath` isn't specified a location in /tmp/ will be used.
|
|
||||||
##
|
|
||||||
## Returns the directory where it was downloaded (subdir is appended) and
|
|
||||||
## the concrete version which was downloaded.
|
|
||||||
let downloadDir =
|
|
||||||
if downloadPath == "":
|
|
||||||
(getNimbleTempDir() / getDownloadDirName(url, verRange))
|
|
||||||
else:
|
|
||||||
downloadPath
|
|
||||||
|
|
||||||
createDir(downloadDir)
|
|
||||||
var modUrl =
|
|
||||||
if url.startsWith("git://") and options.config.cloneUsingHttps:
|
|
||||||
"https://" & url[6 .. ^1]
|
|
||||||
else: url
|
|
||||||
|
|
||||||
# Fixes issue #204
|
|
||||||
# github + https + trailing url slash causes a
|
|
||||||
# checkout/ls-remote to fail with Repository not found
|
|
||||||
if modUrl.contains("github.com") and modUrl.endswith("/"):
|
|
||||||
modUrl = modUrl[0 .. ^2]
|
|
||||||
|
|
||||||
if subdir.len > 0:
|
|
||||||
display("Downloading", "$1 using $2 (subdir is '$3')" %
|
|
||||||
[modUrl, $downMethod, subdir],
|
|
||||||
priority = HighPriority)
|
|
||||||
else:
|
|
||||||
display("Downloading", "$1 using $2" % [modUrl, $downMethod],
|
|
||||||
priority = HighPriority)
|
|
||||||
result = (
|
|
||||||
downloadDir / subdir,
|
|
||||||
doDownload(modUrl, downloadDir, verRange, downMethod, options)
|
|
||||||
)
|
|
||||||
|
|
||||||
if verRange.kind != verSpecial:
|
|
||||||
## Makes sure that the downloaded package's version satisfies the requested
|
|
||||||
## version range.
|
|
||||||
let pkginfo = getPkgInfo(result[0], options)
|
|
||||||
if pkginfo.version.newVersion notin verRange:
|
|
||||||
raise newException(NimbleError,
|
|
||||||
"Downloaded package's version does not satisfy requested version " &
|
|
||||||
"range: wanted $1 got $2." %
|
|
||||||
[$verRange, $pkginfo.version])
|
|
||||||
|
|
||||||
proc echoPackageVersions*(pkg: Package) =
|
|
||||||
let downMethod = pkg.downloadMethod.getDownloadMethod()
|
|
||||||
case downMethod
|
|
||||||
of DownloadMethod.git:
|
|
||||||
try:
|
|
||||||
let versions = getTagsListRemote(pkg.url, downMethod).getVersionList()
|
|
||||||
if versions.len > 0:
|
|
||||||
let sortedVersions = toSeq(values(versions))
|
|
||||||
echo(" versions: " & join(sortedVersions, ", "))
|
|
||||||
else:
|
|
||||||
echo(" versions: (No versions tagged in the remote repository)")
|
|
||||||
except OSError:
|
|
||||||
echo(getCurrentExceptionMsg())
|
|
||||||
of DownloadMethod.hg:
|
|
||||||
echo(" versions: (Remote tag retrieval not supported by " &
|
|
||||||
pkg.downloadMethod & ")")
|
|
||||||
|
|
||||||
proc packageVersionsJson*(pkg: Package): JsonNode =
|
|
||||||
result = newJArray()
|
|
||||||
let downMethod = pkg.downloadMethod.getDownloadMethod()
|
|
||||||
try:
|
|
||||||
case downMethod
|
|
||||||
of DownloadMethod.git:
|
|
||||||
let versions = getTagsListRemote(pkg.url, downMethod).getVersionList()
|
|
||||||
for v in values(versions):
|
|
||||||
result.add %v
|
|
||||||
of DownloadMethod.hg:
|
|
||||||
discard
|
|
||||||
except:
|
|
||||||
result = %* { "exception": getCurrentExceptionMsg() }
|
|
||||||
|
|
||||||
when isMainModule:
|
|
||||||
# Test version sorting
|
|
||||||
block:
|
|
||||||
let data = @["v9.0.0-taeyeon", "v9.0.1-jessica", "v9.2.0-sunny",
|
|
||||||
"v9.4.0-tiffany", "v9.4.2-hyoyeon"]
|
|
||||||
let expected = toOrderedTable[Version, string]({
|
|
||||||
newVersion("9.4.2-hyoyeon"): "v9.4.2-hyoyeon",
|
|
||||||
newVersion("9.4.0-tiffany"): "v9.4.0-tiffany",
|
|
||||||
newVersion("9.2.0-sunny"): "v9.2.0-sunny",
|
|
||||||
newVersion("9.0.1-jessica"): "v9.0.1-jessica",
|
|
||||||
newVersion("9.0.0-taeyeon"): "v9.0.0-taeyeon"
|
|
||||||
})
|
|
||||||
doAssert expected == getVersionList(data)
|
|
||||||
|
|
||||||
echo("Everything works!")
|
|
|
@ -1,120 +0,0 @@
|
||||||
import std/sequtils, std/strutils, std/tables
|
|
||||||
|
|
||||||
type
|
|
||||||
ValueKind* = enum
|
|
||||||
tInt,
|
|
||||||
tBool,
|
|
||||||
tString,
|
|
||||||
tPath,
|
|
||||||
tNull,
|
|
||||||
tAttrs,
|
|
||||||
tList,
|
|
||||||
tThunk,
|
|
||||||
tApp,
|
|
||||||
tLambda,
|
|
||||||
tBlackhole,
|
|
||||||
tPrimOp,
|
|
||||||
tPrimOpApp,
|
|
||||||
tExternal,
|
|
||||||
tFloat
|
|
||||||
Value* = ref object
|
|
||||||
case kind: ValueKind
|
|
||||||
of tInt:
|
|
||||||
num*: BiggestInt
|
|
||||||
of tBool:
|
|
||||||
boolean*: bool
|
|
||||||
of tString:
|
|
||||||
str*: string
|
|
||||||
of tPath:
|
|
||||||
path*: string
|
|
||||||
of tNull:
|
|
||||||
discard
|
|
||||||
of tAttrs:
|
|
||||||
attrs*: Table[string, Value]
|
|
||||||
of tList:
|
|
||||||
list*: seq[Value]
|
|
||||||
of tThunk: discard
|
|
||||||
of tApp: discard
|
|
||||||
of tLambda: discard
|
|
||||||
of tBlackhole: discard
|
|
||||||
of tPrimOp: discard
|
|
||||||
of tPrimOpApp: discard
|
|
||||||
of tExternal: discard
|
|
||||||
of tFloat:
|
|
||||||
fnum*: float
|
|
||||||
|
|
||||||
func `$`*(v: Value): string =
|
|
||||||
case v.kind:
|
|
||||||
of tInt:
|
|
||||||
result = $v.num
|
|
||||||
of tBool:
|
|
||||||
result = if v.boolean: "True" else: "False"
|
|
||||||
of tString:
|
|
||||||
result = "\"$1\"" % (v.str.replace("\"", "\\\""))
|
|
||||||
of tPath:
|
|
||||||
result = v.path
|
|
||||||
of tNull:
|
|
||||||
result = "null"
|
|
||||||
of tAttrs:
|
|
||||||
result = "{"
|
|
||||||
for key, val in v.attrs.pairs:
|
|
||||||
let key = if key.validIdentifier: key else: key.escape
|
|
||||||
result.add("$1=$2;" % [key, $val])
|
|
||||||
result.add "}"
|
|
||||||
of tList:
|
|
||||||
result = "[ "
|
|
||||||
for e in v.list:
|
|
||||||
result.add $e
|
|
||||||
result.add " "
|
|
||||||
result.add "]"
|
|
||||||
of tFloat:
|
|
||||||
result = $v.fnum
|
|
||||||
else:
|
|
||||||
result = $v.kind
|
|
||||||
|
|
||||||
func toNix*(x: Value): Value = x
|
|
||||||
|
|
||||||
func toNix*(x: SomeInteger): Value =
|
|
||||||
Value(kind: tInt, num: x)
|
|
||||||
|
|
||||||
func toNix*(x: bool): Value =
|
|
||||||
Value(kind: tBool, boolean: x)
|
|
||||||
|
|
||||||
func toNix*(x: string): Value =
|
|
||||||
Value(kind: tString, str: x)
|
|
||||||
|
|
||||||
func toPath*(x: string): Value =
|
|
||||||
Value(kind: tPath, path: x)
|
|
||||||
|
|
||||||
template toNix*(pairs: openArray[(string, Value)]): Value =
|
|
||||||
Value(kind: tAttrs, attrs: toTable pairs)
|
|
||||||
|
|
||||||
func toNix*(x: seq[Value]): Value =
|
|
||||||
Value(kind: tList, list: x)
|
|
||||||
|
|
||||||
template toNix*(x: seq[untyped]): Value =
|
|
||||||
map(x, toNix).toNix
|
|
||||||
|
|
||||||
func toNix*(x: openarray[Value]): Value =
|
|
||||||
Value(kind: tList, list: x[x.low .. x.high])
|
|
||||||
|
|
||||||
func toNix*(x: float): Value =
|
|
||||||
Value(kind: tFloat, fnum: x)
|
|
||||||
|
|
||||||
template `[]=`*(result: Value; key: string; val: untyped) =
|
|
||||||
result.attrs[key] = val.toNix
|
|
||||||
|
|
||||||
proc `[]`*(attrs: Value; key: string): Value =
|
|
||||||
attrs.attrs[key]
|
|
||||||
|
|
||||||
proc newNull*(): Value =
|
|
||||||
Value(kind: tNull)
|
|
||||||
|
|
||||||
proc newAttrs*(): Value =
|
|
||||||
Value(kind: tAttrs, attrs: initTable[string, Value]())
|
|
||||||
|
|
||||||
func newList*(): Value =
|
|
||||||
Value(kind: tList)
|
|
||||||
|
|
||||||
proc add*(x, y: Value) =
|
|
||||||
x.list.add y
|
|
Loading…
Reference in New Issue