fix Hardcoded Nim build flags in generated build files. build.rs and CMakeLists.txt both bake in --mm:orc and -d:chronicles_log_level=WARN (rust.nim:121–122, cpp.nim:463–464).

The whole point of a generic generator is decoupling — this couples every consumer of nim-ffi to one specific project's Nim build conventions. Plumb these through
  genBindings() parameters or a config block.
This commit is contained in:
Ivan FB 2026-05-11 22:11:25 +02:00
parent 6920f22ab8
commit 1eb8f2bdf8
No known key found for this signature in database
GPG Key ID: DF0C67A04C543270
6 changed files with 69 additions and 14 deletions

View File

@ -11,6 +11,8 @@ requires "taskpools"
requires "https://github.com/logos-messaging/nim-ffi >= 0.1.3"
const nimFlags = "--mm:orc -d:chronicles_log_level=WARN"
# Flags baked into the generated build.rs / CMakeLists.txt -- see ffi.nimble.
const genBakedFlags = "--mm:orc -d:chronicles_log_level=WARN"
task build, "Compile the nimtimer library":
exec "nim c " & nimFlags &
@ -19,9 +21,13 @@ task build, "Compile the nimtimer library":
task genbindings_rust, "Generate Rust bindings for the nimtimer example":
exec "nim c " & nimFlags & " --app:lib --noMain --nimMainPrefix:libnimtimer" &
" -d:ffiGenBindings -d:targetLang=rust" & " -d:ffiOutputDir=rust_bindings" &
" -d:ffiNimSrcRelPath=nim_timer.nim" & " -o:/dev/null nim_timer.nim"
" -d:ffiNimSrcRelPath=nim_timer.nim" &
" -d:ffiNimBuildFlags=\"" & genBakedFlags & "\"" &
" -o:/dev/null nim_timer.nim"
task genbindings_cpp, "Generate C++ bindings for the nimtimer example":
exec "nim c " & nimFlags & " --app:lib --noMain --nimMainPrefix:libnimtimer" &
" -d:ffiGenBindings -d:targetLang=cpp" & " -d:ffiOutputDir=cpp_bindings" &
" -d:ffiNimSrcRelPath=nim_timer.nim" & " -o:/dev/null nim_timer.nim"
" -d:ffiNimSrcRelPath=nim_timer.nim" &
" -d:ffiNimBuildFlags=\"" & genBakedFlags & "\"" &
" -o:/dev/null nim_timer.nim"

View File

@ -41,18 +41,26 @@ task genbindings_example, "Generate Rust bindings for the nim_timer example":
exec "nim c " & nimFlagsOrc & " --app:lib --noMain --nimMainPrefix:libnimtimer -d:ffiGenBindings -o:/dev/null examples/nim_timer/nim_timer.nim"
exec "nim c " & nimFlagsRefc & " --app:lib --noMain --nimMainPrefix:libnimtimer -d:ffiGenBindings -o:/dev/null examples/nim_timer/nim_timer.nim"
# Flags baked into the generated build.rs / CMakeLists.txt so downstream
# consumers compile the Nim library with the same conventions this example
# uses. Anything that should NOT be hardcoded into the published bindings
# goes here, not in the generator.
const genBakedFlags = "--mm:orc -d:chronicles_log_level=WARN"
task genbindings_rust, "Generate Rust bindings for the nim_timer example":
exec "nim c " & nimFlagsOrc &
" --app:lib --noMain --nimMainPrefix:libnimtimer" &
" -d:ffiGenBindings -d:targetLang=rust" &
" -d:ffiOutputDir=examples/nim_timer/rust_bindings" &
" -d:ffiNimSrcRelPath=../nim_timer.nim" &
" -d:ffiNimBuildFlags=\"" & genBakedFlags & "\"" &
" -o:/dev/null examples/nim_timer/nim_timer.nim"
exec "nim c " & nimFlagsRefc &
" --app:lib --noMain --nimMainPrefix:libnimtimer" &
" -d:ffiGenBindings -d:targetLang=rust" &
" -d:ffiOutputDir=examples/nim_timer/rust_bindings" &
" -d:ffiNimSrcRelPath=../nim_timer.nim" &
" -d:ffiNimBuildFlags=\"" & genBakedFlags & "\"" &
" -o:/dev/null examples/nim_timer/nim_timer.nim"
task genbindings_cpp, "Generate C++ bindings for the nim_timer example":
@ -61,10 +69,12 @@ task genbindings_cpp, "Generate C++ bindings for the nim_timer example":
" -d:ffiGenBindings -d:targetLang=cpp" &
" -d:ffiOutputDir=examples/nim_timer/cpp_bindings" &
" -d:ffiNimSrcRelPath=../nim_timer.nim" &
" -d:ffiNimBuildFlags=\"" & genBakedFlags & "\"" &
" -o:/dev/null examples/nim_timer/nim_timer.nim"
exec "nim c " & nimFlagsRefc &
" --app:lib --noMain --nimMainPrefix:libnimtimer" &
" -d:ffiGenBindings -d:targetLang=cpp" &
" -d:ffiOutputDir=examples/nim_timer/cpp_bindings" &
" -d:ffiNimSrcRelPath=../nim_timer.nim" &
" -d:ffiNimBuildFlags=\"" & genBakedFlags & "\"" &
" -o:/dev/null examples/nim_timer/nim_timer.nim"

View File

@ -483,10 +483,15 @@ proc generateCppHeader*(
result = lines.join("\n")
proc generateCppCMakeLists*(libName: string, nimSrcRelPath: string): string =
proc generateCppCMakeLists*(
libName: string, nimSrcRelPath: string, nimBuildFlags: string = ""
): string =
## Generates CMakeLists.txt for the C++ bindings directory.
## CMake uses ${...} which would clash with Nim's % format operator,
## so we build the file line by line using string concatenation.
## nimBuildFlags is a whitespace-separated string of extra `nim c` flags
## injected after `nim c` and before the required --app:lib / --noMain /
## --nimMainPrefix / -o: flags.
let src = nimSrcRelPath.replace("\\", "/")
let cv = "${CMAKE_CURRENT_SOURCE_DIR}" # CMake variable shorthand
let rv = "${REPO_ROOT}"
@ -554,8 +559,8 @@ proc generateCppCMakeLists*(libName: string, nimSrcRelPath: string): string =
L.add("add_custom_command(")
L.add(" OUTPUT \"" & lf & "\"")
L.add(" COMMAND \"" & nm & "\" c")
L.add(" --mm:orc")
L.add(" -d:chronicles_log_level=WARN")
for tok in nimBuildFlags.splitWhitespace():
L.add(" " & tok)
L.add(" --app:lib")
L.add(" --noMain")
L.add(" \"--nimMainPrefix:lib" & libName & "\"")
@ -601,7 +606,11 @@ proc generateCppBindings*(
libName: string,
outputDir: string,
nimSrcRelPath: string,
nimBuildFlags: string = "",
) =
createDir(outputDir)
writeFile(outputDir / (libName & ".hpp"), generateCppHeader(procs, types, libName))
writeFile(outputDir / "CMakeLists.txt", generateCppCMakeLists(libName, nimSrcRelPath))
writeFile(
outputDir / "CMakeLists.txt",
generateCppCMakeLists(libName, nimSrcRelPath, nimBuildFlags),
)

View File

@ -44,3 +44,11 @@ const ffiOutputDir* {.strdefine.} = ""
# Nim source path (relative to outputDir) embedded in generated build files;
# set with -d:ffiNimSrcRelPath=../relative/path.nim
const ffiNimSrcRelPath* {.strdefine.} = ""
# Whitespace-separated extra Nim compiler flags to bake into the generated
# build.rs / CMakeLists.txt for the project's own `nim c` invocation. The
# generator emits only the strictly-required flags (--app:lib, --noMain,
# --nimMainPrefix, -o:) by default; everything else (gc choice, log level,
# project-specific -d defines, etc.) is up to the caller. Example:
# -d:ffiNimBuildFlags="--mm:orc -d:chronicles_log_level=WARN"
const ffiNimBuildFlags* {.strdefine.} = ""

View File

@ -81,10 +81,27 @@ tokio = { version = "1", features = ["sync"] }
""" %
[libName]
proc generateBuildRs*(libName: string, nimSrcRelPath: string): string =
proc rustEscapeStrLit(s: string): string =
## Escapes a string for safe embedding in a Rust string literal.
s.replace("\\", "\\\\").replace("\"", "\\\"")
proc generateBuildRs*(
libName: string, nimSrcRelPath: string, nimBuildFlags: string = ""
): string =
## Generates build.rs that compiles the Nim library.
## nimSrcRelPath is relative to the output (crate) directory.
## nimBuildFlags is a whitespace-separated string of extra `nim c` flags
## (e.g. "--mm:orc -d:chronicles_log_level=WARN") to inject into the
## generated `nim c` invocation, after `nim c` and before the required
## --app:lib / --noMain / --nimMainPrefix / -o: flags.
let escapedSrc = nimSrcRelPath.replace("\\", "\\\\")
# Build the leading section of the cmd.arg(...) chain: the mandatory
# `.arg("c")` plus one `.arg("...")` per caller-supplied flag, each on its
# own line. The template then reads as `cmd$3` -- $3 is the whole leading
# chain, so no placeholder ever sits inside a method-call expression.
var argChain = ".arg(\"c\")"
for tok in nimBuildFlags.splitWhitespace():
argChain.add("\n .arg(\"" & rustEscapeStrLit(tok) & "\")")
result =
"""use std::path::PathBuf;
use std::process::Command;
@ -123,9 +140,7 @@ fn main() {
compile_error!("nim-ffi build.rs: unsupported target OS (expected macos, linux, or windows)");
let mut cmd = Command::new("nim");
cmd.arg("c")
.arg("--mm:orc")
.arg("-d:chronicles_log_level=WARN")
cmd$3
.arg("--app:lib")
.arg("--noMain")
.arg(format!("--nimMainPrefix:lib$2"))
@ -140,7 +155,7 @@ fn main() {
println!("cargo:rerun-if-changed={}", nim_src.display());
}
""" %
[escapedSrc, libName]
[escapedSrc, libName, argChain]
proc generateLibRs*(): string =
result = """mod ffi;
@ -502,13 +517,17 @@ proc generateRustCrate*(
libName: string,
outputDir: string,
nimSrcRelPath: string,
nimBuildFlags: string = "",
) =
## Generates a complete Rust crate in outputDir.
createDir(outputDir)
createDir(outputDir / "src")
writeFile(outputDir / "Cargo.toml", generateCargoToml(libName))
writeFile(outputDir / "build.rs", generateBuildRs(libName, nimSrcRelPath))
writeFile(
outputDir / "build.rs",
generateBuildRs(libName, nimSrcRelPath, nimBuildFlags),
)
writeFile(outputDir / "src" / "lib.rs", generateLibRs())
writeFile(outputDir / "src" / "ffi.rs", generateFfiRs(procs))
writeFile(outputDir / "src" / "types.rs", generateTypesRs(types))

View File

@ -1604,6 +1604,7 @@ macro ffiDtor*(prc: untyped): untyped =
macro genBindings*(
outputDir: static[string] = ffiOutputDir,
nimSrcRelPath: static[string] = ffiNimSrcRelPath,
nimBuildFlags: static[string] = ffiNimBuildFlags,
): untyped =
## Emits C++ or Rust binding files from the compile-time FFI registries.
##
@ -1638,11 +1639,13 @@ macro genBindings*(
case lang
of "rust":
generateRustCrate(
ffiProcRegistry, ffiTypeRegistry, libName, outputDir, nimSrcRelPath
ffiProcRegistry, ffiTypeRegistry, libName, outputDir, nimSrcRelPath,
nimBuildFlags,
)
of "cpp", "c++":
generateCppBindings(
ffiProcRegistry, ffiTypeRegistry, libName, outputDir, nimSrcRelPath
ffiProcRegistry, ffiTypeRegistry, libName, outputDir, nimSrcRelPath,
nimBuildFlags,
)
else:
error("genBindings: unknown targetLang '" & lang & "'. Use 'rust' or 'cpp'.")