Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions local/commands/link.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import
percy,
basecli,
lib/links,
lib/lockfile

type
LinkCommand = ref object of BaseCommand

begin LinkCommand:
method execute(console: Console): int =
result = super.execute(console)

let
name = console.getArg("name")
path = console.getArg("path")
repository = this.settings.getRepository(name)
url = repository.url
targetDir = getVendorDir(this.settings.getWorkDir(url))
absPath = expandTilde(path).absolutePath()

let lockFile = LockFile.init(fmt "{percy.name}.lock")
if not lockFile.exists() or not lockFile.commits().anyIt(it.repository.url == url):
fail fmt "'{name}' is not a dependency of this project. Run 'percy install' first if needed."
return 1
elif not dirExists(absPath):
fail fmt "Path does not exist or is not a directory: '{absPath}'"
return 2
elif symLinkExists(targetDir):
fail fmt "Already linked. Run `percy unlink {name}` first."
return 3

if dirExists(targetDir):
removeDir(targetDir)

createDir(targetDir.parentDir())
createSymlink(absPath, targetDir)

var links = readLinks()
links.links[url] = absPath
writeLinks(links)

print fmt "Linked '{name}' ({url}) → {absPath}"

shape LinkCommand: @[
Command(
name: "link",
description: "Link a local workspace directory as a vendored package",
opts: @[CommandConfigOpt, CommandVerbosityOpt],
args: @[
Arg(name: "name", description: "Package alias or name to link"),
Arg(name: "path", description: "Local filesystem path to the workspace directory")
]
)
]
48 changes: 48 additions & 0 deletions local/commands/unlink.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import
percy,
basecli,
lib/links

type
UnlinkCommand = ref object of BaseCommand

begin UnlinkCommand:
method execute(console: Console): int =
result = super.execute(console)

let
name = console.getArg("name")
repository = this.settings.getRepository(name)
url = repository.url
workDir = this.settings.getWorkDir(url)
targetDir = getVendorDir(workDir)

var links = readLinks()

if links.links.len == 0:
fail fmt "No linked packages found ('{linksFile}' does not exist)"
return 1
elif url notin links:
fail fmt "Package '{name}' is not linked"
info fmt "> Hint: Run `percy link {name} <path>` to link it"
return 2

if symLinkExists(targetDir):
removeFile(targetDir)

links.links.del(url)
writeLinks(links)

print fmt "Unlinked '{name}'"
info fmt "> Hint: Run `percy install` to restore the vendored version"

shape UnlinkCommand: @[
Command(
name: "unlink",
description: "Unlink a workspace directory, restoring normal vendor management",
opts: @[CommandConfigOpt, CommandVerbosityOpt],
args: @[
Arg(name: "name", description: "Package alias or name to unlink")
]
)
]
46 changes: 41 additions & 5 deletions local/lib/depgraph.nim
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import
semver,
lib/settings,
lib/repository,
lib/links,
mininim/cli

export
Expand Down Expand Up @@ -46,6 +47,7 @@ type
tracking: OrderedTable[Repository, OrderedSet[Commit]]
requirements: Table[Commit, seq[Requirement]]
settings: Settings
links: Links

DecisionLevel = int

Expand Down Expand Up @@ -137,9 +139,12 @@ begin DepGraph:
method init*(settings: Settings, quiet: bool = true): void {. base .} =
this.quiet = quiet
this.settings = settings
this.links = readLinks()

method checkConstraint*(requirement: Requirement, commit: Commit): bool {. base .} =
if requirement.constraint.check(commit.version):
if requirement.repository.url in this.links:
result = true
elif requirement.constraint.check(commit.version):
result = true
elif requirement.constraint.check(ver(commit.id)):
result = true
Expand Down Expand Up @@ -328,8 +333,14 @@ begin DepGraph:
print fmt "> Source: {commit.repository.url} @ {commit.version}"

try:
for file in commit.repository.listDir("/", commit.id):
if file.endsWith(".nimble"):
if commit.repository.url in this.links:
for file in walkDir(this.links[commit.repository.url]):
if file.path.endsWith(".nimble"):
commit.info = parser.parse(readFile(file.path))
break
else:
for file in commit.repository.listDir("/", commit.id):
if file.endsWith(".nimble"):
let
contents = commit.repository.readFile(file, commit.id)
when debugging(3):
Expand Down Expand Up @@ -377,6 +388,29 @@ begin DepGraph:

]#
method expandCommits*(requirement: Requirement): void {. base .} =
if requirement.repository.url in this.links:
if not this.commits.hasKey(requirement.repository):
if not this.quiet:
print fmt "Graph: Adding Repository (Using Linked Directory)"
print fmt "> Repository URL: {requirement.repository.url}"
print fmt "> Repository Hash: {requirement.repository.shaHash}"

var output: string
percy.execIn(
ExecHook as (
block:
discard percy.execCmdCaptureAll(output, @["git rev-parse HEAD"])
),
this.links[requirement.repository.url]
)
this.commits[requirement.repository] = initOrderedSet[Commit]()
let head = output.strip()
if head.len == 40:
this.commits[requirement.repository].incl(
Commit(id: head, version: ver("head"), repository: requirement.repository)
)
return

if not this.commits.hasKey(requirement.repository):
if requirement.repository.exists:
if not this.quiet:
Expand Down Expand Up @@ -428,15 +462,17 @@ begin DepGraph:
toResolve = HashSet[Commit]()
toRemove = Table[Commit, string]()

let isLinked = requirement.repository.url in this.links

for commit in this.commits[requirement.repository]:
if not this.requirements.hasKey(commit):
if depth == 0:
if not this.checkConstraint(requirement, commit):
if not isLinked and not this.checkConstraint(requirement, commit):
toRemove[commit] = "Not Usable At Top-Level"
else:
toResolve.incl(commit)
else:
if this.checkConstraint(requirement, commit):
if isLinked or this.checkConstraint(requirement, commit):
toResolve.incl(commit)

for commit, reason in toRemove:
Expand Down
31 changes: 31 additions & 0 deletions local/lib/links.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import
percy

const
linksFile* = "vendor/links.json"

type
Links* = ref object of Class
links*: Table[string, string]

proc `[]`*(links: Links, url: string): string =
links.links[url]

proc contains*(links: Links, url: string): bool =
links.links.hasKey(url)

proc readLinks*(): Links =
result = Links()
if fileExists(linksFile):
for k, v in json.parseFile(linksFile)["links"].pairs:
result.links[k] = v.getStr()

proc writeLinks*(links: Links) =
if links.links.len == 0:
if fileExists(linksFile):
removeFile(linksFile)
else:
var inner = newJObject()
for k, v in links.links:
inner[k] = %v
writeFile(linksFile, pretty(%* { "links": inner }))
19 changes: 19 additions & 0 deletions local/lib/loader.nim
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import
lib/settings,
lib/depgraph,
lib/repository,
lib/links,
pkg/checksums/sha1

type
Expand Down Expand Up @@ -199,6 +200,22 @@ begin Loader:
if not this.map[relPath]["subs"].contains(%repository.shaHash):
this.map[relPath]["subs"].add(%repository.shaHash)

method restoreLinks() {. base .} =
let links = readLinks()
for url, absPath in links.links:
let
workDir = this.settings.getWorkDir(url)
targetDir = getVendorDir(workDir)
if symLinkExists(targetDir):
discard # already fine
elif dirExists(absPath):
createDir(targetDir.parentDir())
createSymlink(absPath, targetDir)
if not this.quiet:
info fmt "> Restored link: {targetDir} → {absPath}"
elif not this.quiet:
warn fmt "> Broken link for '{url}': '{absPath}' not found (run `percy unlink` to clean up)"

method loadSolution*(solution: Solution, preserve: bool = false, force: bool = false): seq[Checkout] {. base .} =
var
error: int
Expand All @@ -209,6 +226,8 @@ begin Loader:
createDirs: OrderedSet[string]
targetCommits: Table[string, Commit]

this.restoreLinks()

if not this.quiet:
print "Loading Solution"

Expand Down