Skip to content

Commit ff281e6

Browse files
committed
feat: main prefix validation
1 parent 62d3c00 commit ff281e6

4 files changed

Lines changed: 79 additions & 2 deletions

File tree

ffi.nimble

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ task genbindings_rust, "Generate Rust bindings for the timer example":
226226
" -o:/dev/null examples/timer/timer.nim"
227227

228228
task genbindings_cddl, "Generate CDDL schema for the timer example":
229-
exec "nim c " & nimFlagsOrc & " --app:lib --noMain --nimMainPrefix:libtimer" &
229+
exec "nim c " & nimFlagsOrc & " --app:lib --noMain --nimMainPrefix:libmy_timer" &
230230
" -d:ffiGenBindings -d:targetLang=cddl" &
231231
" -d:ffiOutputDir=examples/timer/cddl_bindings" & " -d:ffiSrcPath=../timer.nim" &
232232
" -o:/dev/null examples/timer/timer.nim"

ffi/internal/ffi_library.nim

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,59 @@
1-
import std/[macros, atomics, sysatomics], strformat, chronicles, chronos
1+
import
2+
std/[macros, atomics, sysatomics, compilesettings], strformat, chronicles, chronos
3+
import strutils
24
import ../codegen/meta
35

6+
func nimMainPrefixOnCmdLine(cmdLine: string): tuple[found: bool, value: string] =
7+
## Scan the compiler command line for `--nimMainPrefix:X` (last one wins) and
8+
## return its value. Switch names in Nim are style-insensitive, so the name
9+
## is matched lowercased and with underscores stripped; the separator may be
10+
## `:` or `=`. Returns `(false, "")` when the flag is absent — note config.nims
11+
## switches may not surface here, so absence is not proof it was never set.
12+
var found = false
13+
var value = ""
14+
for tok in cmdLine.splitWhitespace():
15+
let body = tok.strip(trailing = false, chars = {'-'})
16+
let sep = body.find({':', '='})
17+
if sep < 0:
18+
continue
19+
if body[0 ..< sep].toLowerAscii().replace("_", "") == "nimmainprefix":
20+
found = true
21+
value = body[sep + 1 .. ^1]
22+
(found, value)
23+
24+
proc validateNimMainPrefix(libraryName: string) {.compileTime.} =
25+
## The Nim runtime init symbol is importc'd as `lib{libraryName}NimMain`, so
26+
## the build must pass `--nimMainPrefix:lib{libraryName}`; a mismatch otherwise
27+
## surfaces only at link time as an obscure undefined-symbol error. Absence
28+
## can't be an error — config.nims may set the prefix without it showing on
29+
## `commandLine` — so it only warrants a hint, and only for the `--app:lib`
30+
## build where the prefix actually matters.
31+
let expectedPrefix = "lib" & libraryName
32+
let (prefixFound, prefixValue) =
33+
nimMainPrefixOnCmdLine(querySetting(SingleValueSetting.commandLine))
34+
if prefixFound and prefixValue != expectedPrefix:
35+
error(
36+
"declareLibrary(\"" & libraryName &
37+
"\"): the Nim runtime init symbol is importc'd as " & expectedPrefix &
38+
"NimMain, so the build needs --nimMainPrefix:" & expectedPrefix &
39+
", but the command line passes --nimMainPrefix:" & prefixValue &
40+
". Change the flag to --nimMainPrefix:" & expectedPrefix &
41+
" (it must be \"lib\" followed by the declareLibrary name)."
42+
)
43+
elif not prefixFound and compileOption("app", "lib"):
44+
hint(
45+
"declareLibrary(\"" & libraryName & "\"): pass --nimMainPrefix:" & expectedPrefix &
46+
" so the Nim runtime init symbol " & expectedPrefix &
47+
"NimMain resolves; without it the build may fail with an undefined-symbol" &
48+
" link error (ignore this hint if the prefix is set in config.nims)."
49+
)
50+
451
macro declareLibraryBase*(libraryName: static[string]): untyped =
552
# Record the library name for binding generation
653
currentLibName = libraryName
754

55+
validateNimMainPrefix(libraryName)
56+
857
var res = newStmtList()
958

1059
## Generate {.pragma: exported, exportc, cdecl, raises: [].}

tests/unit/mainprefix_fixture.nim

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import ffi
2+
3+
type MpFixture = object
4+
5+
declareLibrary("mpfixture", MpFixture)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import std/[os, strutils]
2+
import unittest2
3+
4+
# The validation only fires when --nimMainPrefix is on the command line, so we
5+
# capture a real `nim check` of the fixture at this test's compile time.
6+
# `mainprefix_fixture.nim` deliberately lacks the `test_` prefix so the nimble
7+
# runner never compiles it standalone.
8+
const
9+
nimExe = getCurrentCompilerExe()
10+
fixture = currentSourcePath.parentDir / "mainprefix_fixture.nim"
11+
checkCmd = nimExe & " check --hints:off --colors:off "
12+
wrongPrefixOutput = staticExec(checkCmd & "--nimMainPrefix:libWRONG " & fixture)
13+
rightPrefixOutput = staticExec(checkCmd & "--nimMainPrefix:libmpfixture " & fixture)
14+
15+
suite "compile-time --nimMainPrefix validation":
16+
test "a mismatched prefix errors and names the expected flag":
17+
check "Error:" in wrongPrefixOutput
18+
# naming the expected flag is what distinguishes our error from any other
19+
# compile failure, so assert on it rather than the bare "Error:".
20+
check "needs --nimMainPrefix:libmpfixture" in wrongPrefixOutput
21+
22+
test "the matching prefix compiles without error":
23+
check "Error:" notin rightPrefixOutput

0 commit comments

Comments
 (0)