feat: main prefix validation (#116)

This commit is contained in:
Gabriel Cruz 2026-07-10 16:49:07 -03:00 committed by GitHub
parent 62d3c00b61
commit c0f6f2e802
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 79 additions and 2 deletions

View File

@ -226,7 +226,7 @@ task genbindings_rust, "Generate Rust bindings for the timer example":
" -o:/dev/null examples/timer/timer.nim"
task genbindings_cddl, "Generate CDDL schema for the timer example":
exec "nim c " & nimFlagsOrc & " --app:lib --noMain --nimMainPrefix:libtimer" &
exec "nim c " & nimFlagsOrc & " --app:lib --noMain --nimMainPrefix:libmy_timer" &
" -d:ffiGenBindings -d:targetLang=cddl" &
" -d:ffiOutputDir=examples/timer/cddl_bindings" & " -d:ffiSrcPath=../timer.nim" &
" -o:/dev/null examples/timer/timer.nim"

View File

@ -1,10 +1,59 @@
import std/[macros, atomics, sysatomics], strformat, chronicles, chronos
import
std/[macros, atomics, sysatomics, compilesettings], strformat, chronicles, chronos
import strutils
import ../codegen/meta
func nimMainPrefixOnCmdLine(cmdLine: string): tuple[found: bool, value: string] =
## Scan the compiler command line for `--nimMainPrefix:X` (last one wins) and
## return its value. Switch names in Nim are style-insensitive, so the name
## is matched lowercased and with underscores stripped; the separator may be
## `:` or `=`. Returns `(false, "")` when the flag is absent — note config.nims
## switches may not surface here, so absence is not proof it was never set.
var found = false
var value = ""
for tok in cmdLine.splitWhitespace():
let body = tok.strip(trailing = false, chars = {'-'})
let sep = body.find({':', '='})
if sep < 0:
continue
if body[0 ..< sep].toLowerAscii().replace("_", "") == "nimmainprefix":
found = true
value = body[sep + 1 .. ^1]
(found, value)
proc validateNimMainPrefix(libraryName: string) {.compileTime.} =
## The Nim runtime init symbol is importc'd as `lib{libraryName}NimMain`, so
## the build must pass `--nimMainPrefix:lib{libraryName}`; a mismatch otherwise
## surfaces only at link time as an obscure undefined-symbol error. Absence
## can't be an error — config.nims may set the prefix without it showing on
## `commandLine` — so it only warrants a hint, and only for the `--app:lib`
## build where the prefix actually matters.
let expectedPrefix = "lib" & libraryName
let (prefixFound, prefixValue) =
nimMainPrefixOnCmdLine(querySetting(SingleValueSetting.commandLine))
if prefixFound and prefixValue != expectedPrefix:
error(
"declareLibrary(\"" & libraryName &
"\"): the Nim runtime init symbol is importc'd as " & expectedPrefix &
"NimMain, so the build needs --nimMainPrefix:" & expectedPrefix &
", but the command line passes --nimMainPrefix:" & prefixValue &
". Change the flag to --nimMainPrefix:" & expectedPrefix &
" (it must be \"lib\" followed by the declareLibrary name)."
)
elif not prefixFound and compileOption("app", "lib"):
hint(
"declareLibrary(\"" & libraryName & "\"): pass --nimMainPrefix:" & expectedPrefix &
" so the Nim runtime init symbol " & expectedPrefix &
"NimMain resolves; without it the build may fail with an undefined-symbol" &
" link error (ignore this hint if the prefix is set in config.nims)."
)
macro declareLibraryBase*(libraryName: static[string]): untyped =
# Record the library name for binding generation
currentLibName = libraryName
validateNimMainPrefix(libraryName)
var res = newStmtList()
## Generate {.pragma: exported, exportc, cdecl, raises: [].}

View File

@ -0,0 +1,5 @@
import ffi
type MpFixture = object
declareLibrary("mpfixture", MpFixture)

View File

@ -0,0 +1,23 @@
import std/[os, strutils]
import unittest2
# The validation only fires when --nimMainPrefix is on the command line, so we
# capture a real `nim check` of the fixture at this test's compile time.
# `mainprefix_fixture.nim` deliberately lacks the `test_` prefix so the nimble
# runner never compiles it standalone.
const
nimExe = getCurrentCompilerExe()
fixture = currentSourcePath.parentDir / "mainprefix_fixture.nim"
checkCmd = nimExe & " check --hints:off --colors:off "
wrongPrefixOutput = staticExec(checkCmd & "--nimMainPrefix:libWRONG " & fixture)
rightPrefixOutput = staticExec(checkCmd & "--nimMainPrefix:libmpfixture " & fixture)
suite "compile-time --nimMainPrefix validation":
test "a mismatched prefix errors and names the expected flag":
check "Error:" in wrongPrefixOutput
# naming the expected flag is what distinguishes our error from any other
# compile failure, so assert on it rather than the bare "Error:".
check "needs --nimMainPrefix:libmpfixture" in wrongPrefixOutput
test "the matching prefix compiles without error":
check "Error:" notin rightPrefixOutput