Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

--moduleoverride:mod1:mod2:prefix: generalized patchFile to allow non-global effect; works on cmdline / config #18496

Closed
wants to merge 5 commits into from
Closed
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
18 changes: 18 additions & 0 deletions compiler/commands.nim
Original file line number Diff line number Diff line change
Expand Up @@ -931,6 +931,24 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
else: conf.spellSuggestMax = parseInt(arg)
of "declaredlocs":
processOnOffSwitchG(conf, {optDeclaredLocs}, arg, pass, info)
of "moduleoverride":
# --moduleoverride:std/sequtils:pkg/foo/sequtils2:prefix1,prefix2/sub
proc validate(path: string): string =
if path.isAbsolute or path.isCanonicalPath: result = path
else: localError(conf, info, "module path must be canonical or absolute, got: $1" % path)
let args = arg.split(":")
if args.len notin {2, 3}:
localError(conf, info, "invalid arg: $1" % arg)
else:
let lhs = args[0].validate
let rhs = args[1].validate
if args.len == 2:
conf.localOverrides.add LocalOverride(lhs: lhs, rhs: rhs, prefix: "/")
elif args.len == 3:
let prefixes = args[2].split(",")
for a in prefixes:
let prefix = a.validate
conf.localOverrides.add LocalOverride(lhs: lhs, rhs: rhs, prefix: prefix)
of "dynliboverride":
dynlibOverride(conf, switch, arg, pass, info)
of "dynliboverrideall":
Expand Down
44 changes: 43 additions & 1 deletion compiler/options.nim
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,12 @@ type
foLegacyRelProj # legacy, shortest of (foAbs, foRelProject)
foName # lastPathPart, e.g.: foo.nim
foStacktrace # if optExcessiveStackTrace: foAbs else: foName
LocalOverrideAtom* = object

LocalOverride* = object
lhs*: string
rhs*: string
prefix*: string
ConfigRef* {.acyclic.} = ref object ## every global configuration
## fields marked with '*' are subject to
## the incremental compilation mechanisms
Expand Down Expand Up @@ -344,6 +349,7 @@ type
jsonBuildFile*: AbsoluteFile
prefixDir*, libpath*, nimcacheDir*: AbsoluteDir
dllOverrides, moduleOverrides*, cfileSpecificOptions*: StringTableRef
localOverrides*: seq[LocalOverride]
projectName*: string # holds a name like 'nim'
projectPath*: AbsoluteDir # holds a path like /home/alice/projects/nim/compiler/
projectFull*: AbsoluteFile # projectPath/projectName
Expand Down Expand Up @@ -823,6 +829,12 @@ const stdlibDirs = [
const
pkgPrefix = "pkg/"
stdPrefix = "std/"
systemPrefix = "system/"

proc isCanonicalPath*(path: string): bool =
# in future work we can support also `this/` for current package.
let path = path & '/'
result = path.startsWith(stdPrefix) or path.startsWith(systemPrefix) or path.startsWith(pkgPrefix)

proc getRelativePathFromConfigPath*(conf: ConfigRef; f: AbsoluteFile, isTitle = false): RelativeFile =
let f = $f
Expand Down Expand Up @@ -852,7 +864,24 @@ proc findFile*(conf: ConfigRef; f: string; suppressStdlib = false): AbsoluteFile
result = rawFindFile2(conf, RelativeFile f.toLowerAscii)
patchModule(conf)

proc findModule*(conf: ConfigRef; modulename, currentModule: string): AbsoluteFile =
proc canonicalImport*(conf: ConfigRef, file: AbsoluteFile): string

proc canonicalImportPkg*(conf: ConfigRef, file: AbsoluteFile): string =
result = canonicalImport(conf, file)
let tmp = result & '/'
if not (tmp.startsWith(stdPrefix) or tmp.startsWith(systemPrefix)):
result = pkgPrefix & result

proc pathMatchesPrefix(conf: ConfigRef, path: string, prefix: string): bool =
# see also `prefixmatches.prefixMatch`
if prefix == "/": result = true
elif prefix.isAbsolute:
result = path.isRelativeTo(prefix)
else: # canonical, eg std/foo or pkg/fusion/bar
let canon = canonicalImportPkg(conf, AbsoluteFile(path)) & '/'
result = canon.startsWith(prefix & '/')

proc findModule*(conf: ConfigRef; modulename, currentModule: string, depth = 0): AbsoluteFile =
# returns path to module
var m = addFileExt(modulename, NimExt)
if m.startsWith(pkgPrefix):
Expand All @@ -870,6 +899,19 @@ proc findModule*(conf: ConfigRef; modulename, currentModule: string): AbsoluteFi
result = AbsoluteFile currentPath / m
if not fileExists(result):
result = findFile(conf, m)
if not result.isEmpty:
let canon = canonicalImportPkg(conf, result)
for ai in conf.localOverrides:
if ai.lhs == canon:
if pathMatchesPrefix(conf, currentModule, ai.prefix):
if depth > 10:
# can happen with: `--moduleoverride:std/foo2:std/foo2` or `--moduleoverride:std/foo2:/pathto/std/foo2`
# or more complex cases with cycles. Future work could improve things but this is a rare edge case.
# `localError` not defined in scope, alternative is some simple refactoring.
stderr.write "module resolution too deep, possible cyclic overrides detected\n"
return AbsoluteFile""
result = findModule(conf, ai.rhs, currentModule, depth + 1)
break
patchModule(conf)

proc findProjectNimFile*(conf: ConfigRef; pkg: string): string =
Expand Down
65 changes: 65 additions & 0 deletions tests/misc/trunner.nim
Original file line number Diff line number Diff line change
Expand Up @@ -377,4 +377,69 @@ mused3.nim(75, 10) Hint: duplicate import of 'mused3a'; previous import here: mu
"""

else:
block: # moduleoverride
proc fn(opt: string, expected: string) =
let output = runNimCmdChk("nimble/mmoduleoverride.nim", fmt"--warnings:off --hints:off {opt}")
doAssert output == expected, opt & "\noutput:\n" & output & "expected:\n" & expected
fn(""): """
in pkgA.module2
in pkgC.module2
in pkgB.module2
"""
fn("--moduleoverride:pkg/pkgC/module2:pkg/pkgC/module2b"): """
in pkgA.module2
in pkgC.module2b
in pkgB.module2
"""
fn("--moduleoverride:pkg/pkgC/module2:pkg/pkgC/module2b:pkg/pkgA"): """
in pkgA.module2
in pkgC.module2b
in pkgB.module2
in pkgC.module2
"""
fn("--moduleoverride:pkg/pkgC/module2:pkg/pkgC/module2b:pkg/pkgB"): """
in pkgA.module2
in pkgC.module2
in pkgB.module2
in pkgC.module2b
"""
fn("--moduleoverride:pkg/pkgC/module2:pkg/pkgC/module2b:pkg"): """
in pkgA.module2
in pkgC.module2b
in pkgB.module2
"""
fn("--moduleoverride:pkg/pkgC/module2:pkg/pkgC/module2b:pkg/pkgA/module2"): """
in pkgA.module2
in pkgC.module2b
in pkgB.module2
in pkgC.module2
"""
fn("--moduleoverride:pkg/pkgC/module2:pkg/pkgC/module2b:pkg/pkgA/module"): """
in pkgA.module2
in pkgC.module2
in pkgB.module2
"""
fn("--moduleoverride:std/strutils:pkg/pkgC/module2b"): """
in pkgC.module2b
in pkgA.module2
in pkgC.module2
in pkgB.module2
"""
let path = testsDir / "nimble/nimbleDir/simplePkgs/pkgA-0.1.0/pkgA/module2.nim"
fn(fmt"--moduleoverride:pkg/pkgC/module2:pkg/pkgC/module2b:{path}"): """
in pkgA.module2
in pkgC.module2b
in pkgB.module2
in pkgC.module2
"""
# edge case, whether to consider `pkg/tests/nimble/mmoduleoverride` as canonical
fn("--moduleoverride:std/strutils:pkg/pkgC/module2b:pkg/tests/nimble/mmoduleoverride"): """
in pkgC.module2b
in pkgA.module2
in pkgC.module2
in pkgB.module2
"""
doAssertRaises(AssertionDefect): fn("--moduleoverride:pkgC/module2:pkg/pkgC/module2b:pkg/pkgA/module", "") # pkg/ missing
doAssertRaises(AssertionDefect): fn("--moduleoverride:pkg/pkgC/module2:pkg/pkgC/nonexistent", "") # pkg/pkgC/nonexistent is nonexistent

discard # only during debugging, tests added here will run with `-d:nimTestsTrunnerDebugging` enabled
8 changes: 8 additions & 0 deletions tests/nimble/mmoduleoverride.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
discard """
matrix: "--moduleoverride:pkgC/module2:std/foo2b:$nim_prs_D/lib/"
# matrix: "--moduleoverride:std/foo2:std/foo2b:$nim_prs_D/lib/"
"""
import std/strutils
import pkgA/module2 as A
import pkgB/module2 as B
import pkgC/module2 as C
5 changes: 5 additions & 0 deletions tests/nimble/mmoduleoverride.nims
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
switch("clearNimblePath")
switch("nimblePath", "$projectdir/nimbleDir/simplePkgs")
switch("path", "$nimblepath/pkgA-0.1.0")
switch("path", "$nimblepath/pkgB-#head")
switch("path", "$nimblepath/pkgC-#head")
2 changes: 2 additions & 0 deletions tests/nimble/nimbleDir/simplePkgs/pkgA-0.1.0/pkgA/module2.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
static: echo "in pkgA.module2"
import pkgC/module2
2 changes: 2 additions & 0 deletions tests/nimble/nimbleDir/simplePkgs/pkgB-#head/pkgB/module2.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
static: echo "in pkgB.module2"
import pkgC/module2
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
static: echo "in pkgC.module2"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
static: echo "in pkgC.module2b"