summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorruki <[email protected]>2026-01-31 08:38:32 +0800
committerGitHub <[email protected]>2026-01-31 08:38:32 +0800
commit09a670d2fef977f1494135b4146631dbf81a4446 (patch)
tree1abc2bd2d2ee867d55a6c377c8e0f755d0b719d7
parent51a68ee33918b95811420beb6a56673a83abe6cc (diff)
parent62b7806dd006540fda8c8c85d173973ae2479fbf (diff)
Merge pull request #7272 from luadebug/nimlib
enhance Nim support for shared libraries and rpath handling
-rw-r--r--tests/projects/nim/link_library/headers/test_header.h10
-rw-r--r--tests/projects/nim/link_library/inc/test.h11
-rw-r--r--tests/projects/nim/link_library/maindll.nim16
-rw-r--r--tests/projects/nim/link_library/mainlib.nim30
-rw-r--r--tests/projects/nim/link_library/shared.nim29
-rw-r--r--tests/projects/nim/link_library/static.nim26
-rw-r--r--tests/projects/nim/link_library/xmake.lua47
-rw-r--r--xmake/languages/nim/xmake.lua13
-rw-r--r--xmake/modules/core/tools/nim.lua111
9 files changed, 287 insertions, 6 deletions
diff --git a/tests/projects/nim/link_library/headers/test_header.h b/tests/projects/nim/link_library/headers/test_header.h
new file mode 100644
index 000000000..4b71d3173
--- /dev/null
+++ b/tests/projects/nim/link_library/headers/test_header.h
@@ -0,0 +1,10 @@
+#ifndef TEST_HEADER_H
+#define TEST_HEADER_H
+
+#define TEST_HEADER_VAL 123
+
+static int test_add_five(int x) {
+ return x + 5;
+}
+
+#endif
diff --git a/tests/projects/nim/link_library/inc/test.h b/tests/projects/nim/link_library/inc/test.h
new file mode 100644
index 000000000..f7d68cac8
--- /dev/null
+++ b/tests/projects/nim/link_library/inc/test.h
@@ -0,0 +1,11 @@
+
+#ifndef TEST_H
+#define TEST_H
+
+#ifdef TEST_STATIC
+ #define TEST_MSG "Hello from Static Lib!"
+#else
+ #define TEST_MSG "Hello from Shared Lib!"
+#endif
+
+#endif
diff --git a/tests/projects/nim/link_library/maindll.nim b/tests/projects/nim/link_library/maindll.nim
new file mode 100644
index 000000000..b13fe1caf
--- /dev/null
+++ b/tests/projects/nim/link_library/maindll.nim
@@ -0,0 +1,16 @@
+import shared
+
+echo "Calling shared lib mulTwo(10): ", mulTwo(10)
+echo "Calling shared lib countWords('hello, world, hello'): ", countWords("hello, world, hello")
+echo "Calling shared lib getMsg('test'): ", getMsg()
+
+{.emit: """
+#include <test_header.h>
+""".}
+
+var testHeaderVal {.importc: "TEST_HEADER_VAL", nodecl.}: cint
+echo "TEST_HEADER_VAL: ", testHeaderVal
+
+proc test_add_five(x: cint): cint {.importc: "test_add_five", nodecl.}
+echo "test_add_five(80): ", test_add_five(80)
+
diff --git a/tests/projects/nim/link_library/mainlib.nim b/tests/projects/nim/link_library/mainlib.nim
new file mode 100644
index 000000000..1a1fae06c
--- /dev/null
+++ b/tests/projects/nim/link_library/mainlib.nim
@@ -0,0 +1,30 @@
+import static
+
+{.emit: """
+#include <zlib.h>
+#define STB_IMAGE_IMPLEMENTATION
+#include <stb_image.h>
+""".}
+
+proc zlibVersion(): cstring {.importc: "zlibVersion", nodecl.}
+proc stbi_set_flip_vertically_on_load(flag_true_if_should_flip: cint) {.importc: "stbi_set_flip_vertically_on_load", nodecl.}
+
+echo "Zlib Version: ", zlibVersion()
+
+stbi_set_flip_vertically_on_load(1)
+echo "STB Image: Flip vertically on load set to 1"
+
+echo "Calling static lib addTwo(10): ", addTwo(10)
+echo "Calling static lib getAlphabet(): ", getAlphabet()
+echo "Calling static lib getMsg('test'): ", getMsg()
+
+{.emit: """
+#include <test_header.h>
+""".}
+
+var testHeaderVal {.importc: "TEST_HEADER_VAL", nodecl.}: cint
+echo "TEST_HEADER_VAL: ", testHeaderVal
+
+proc test_add_five(x: cint): cint {.importc: "test_add_five", nodecl.}
+echo "test_add_five(55): ", test_add_five(55)
+
diff --git a/tests/projects/nim/link_library/shared.nim b/tests/projects/nim/link_library/shared.nim
new file mode 100644
index 000000000..58dacc6df
--- /dev/null
+++ b/tests/projects/nim/link_library/shared.nim
@@ -0,0 +1,29 @@
+proc mulTwo*(x: int): int =
+ return x * 2
+
+import tables, strutils
+
+proc countWords*(input: string): string =
+ var wordFrequencies = initCountTable[string]()
+ for word in input.split(", "):
+ wordFrequencies.inc(word)
+ return "The most frequent word is '" & $wordFrequencies.largest & "'"
+
+{.emit: """
+#include "test.h"
+""".}
+
+proc getMsg*(): cstring {.exportc, dynlib.} =
+ var msg: cstring
+ {.emit: "`msg` = TEST_MSG;".}
+ return msg
+
+{.emit: """
+#include <test_header.h>
+""".}
+
+var testHeaderVal {.importc: "TEST_HEADER_VAL", nodecl.}: cint
+echo "TEST_HEADER_VAL: ", testHeaderVal
+
+proc test_add_five(x: cint): cint {.importc: "test_add_five", nodecl.}
+echo "test_add_five(10): ", test_add_five(10)
diff --git a/tests/projects/nim/link_library/static.nim b/tests/projects/nim/link_library/static.nim
new file mode 100644
index 000000000..6808d5895
--- /dev/null
+++ b/tests/projects/nim/link_library/static.nim
@@ -0,0 +1,26 @@
+proc addTwo*(x: int): int =
+ return x + 2
+
+proc getAlphabet*(): string =
+ for letter in 'a'..'z':
+ result.add(letter)
+
+{.emit: """
+#define TEST_STATIC
+#include "test.h"
+""".}
+
+proc getMsg*(): cstring {.exportc, dynlib.} =
+ var msg: cstring
+ {.emit: "`msg` = TEST_MSG;".}
+ return msg
+
+{.emit: """
+#include <test_header.h>
+""".}
+
+var testHeaderVal {.importc: "TEST_HEADER_VAL", nodecl.}: cint
+echo "TEST_HEADER_VAL: ", testHeaderVal
+
+proc test_add_five(x: cint): cint {.importc: "test_add_five", nodecl.}
+echo "test_add_five(60): ", test_add_five(60)
diff --git a/tests/projects/nim/link_library/xmake.lua b/tests/projects/nim/link_library/xmake.lua
new file mode 100644
index 000000000..ba56fcbf2
--- /dev/null
+++ b/tests/projects/nim/link_library/xmake.lua
@@ -0,0 +1,47 @@
+set_project("link_libs")
+add_rules("mode.debug", "mode.release")
+
+add_requires("zlib", {system = false, configs = {shared = true}})
+add_requires("stb", {system = false})
+
+target("headers")
+ set_kind("headeronly")
+ add_headerfiles("headers/*.h")
+ add_includedirs("headers", {public = true})
+
+target("executablestatic")
+ set_kind("binary")
+ add_files("mainlib.nim")
+ add_deps("staticlib")
+ add_packages("zlib", "stb", {public = true})
+ if is_plat("linux") then
+ add_syslinks("pthread", "m")
+ end
+
+target("executableshared")
+ set_kind("binary")
+ add_files("maindll.nim")
+ add_deps("sharedlib")
+ if is_plat("linux") then
+ add_syslinks("pthread", "m")
+ end
+
+target("staticlib")
+ set_kind("static")
+ add_files("static.nim")
+ add_includedirs("inc", {public = true})
+ add_headerfiles("inc/*.h")
+ if is_plat("linux") then
+ add_syslinks("pthread", "m")
+ end
+ add_deps("headers")
+
+target("sharedlib")
+ set_kind("shared")
+ add_files("shared.nim")
+ add_includedirs("inc", {public = true})
+ add_headerfiles("inc/*.h")
+ if is_plat("linux") then
+ add_syslinks("pthread", "m")
+ end
+ add_deps("headers")
diff --git a/xmake/languages/nim/xmake.lua b/xmake/languages/nim/xmake.lua
index 65ad6429b..458cd1d33 100644
--- a/xmake/languages/nim/xmake.lua
+++ b/xmake/languages/nim/xmake.lua
@@ -39,10 +39,13 @@ language("nim")
, "target.optimize:check"
, "target.vectorexts:check"
, "target.includedirs"
+ , "target.sysincludedirs"
, "toolchain.includedirs"
}
, binary = {
"config.linkdirs"
+ , "target.includedirs"
+ , "target.sysincludedirs"
, "target.linkdirs"
, "target.rpathdirs"
, "target.strip"
@@ -52,9 +55,14 @@ language("nim")
, "config.links"
, "target.links"
, "toolchain.links"
+ , "config.syslinks"
+ , "target.syslinks"
+ , "toolchain.syslinks"
}
, shared = {
"config.linkdirs"
+ , "target.includedirs"
+ , "target.sysincludedirs"
, "target.linkdirs"
, "target.strip"
, "target.symbols"
@@ -62,10 +70,15 @@ language("nim")
, "config.links"
, "target.links"
, "toolchain.links"
+ , "config.syslinks"
+ , "target.syslinks"
+ , "toolchain.syslinks"
}
, static = {
"target.strip"
, "target.symbols"
+ , "target.includedirs"
+ , "target.sysincludedirs"
}
}
diff --git a/xmake/modules/core/tools/nim.lua b/xmake/modules/core/tools/nim.lua
index 94cc0645c..415d102f2 100644
--- a/xmake/modules/core/tools/nim.lua
+++ b/xmake/modules/core/tools/nim.lua
@@ -34,6 +34,23 @@ function init(self)
-- init shflags
self:set("ncshflags", "--app:lib", "--noMain")
+
+ -- init arch flags
+ local arch = self:arch()
+ if arch then
+ if self:is_arch("x86", "i386") then
+ self:add("ncflags", "--cpu:i386", "--define:bit32")
+ if self:is_plat("linux", "macosx", "bsd", "mingw") then
+ self:add("ncflags", '--passC:"-m32"', '--passL:"-m32"')
+ end
+ elseif self:is_arch("x64", "x86_64") then
+ self:add("ncflags", "--cpu:amd64", "--define:bit64")
+ elseif self:is_arch("arm64.*") then
+ self:add("ncflags", "--cpu:arm64", "--define:bit64")
+ elseif self:is_arch("arm.*") then
+ self:add("ncflags", "--cpu:arm", "--define:bit32")
+ end
+ end
end
-- make the warning flag
@@ -92,31 +109,113 @@ end
function nf_strip(self, level)
if self:is_plat("linux", "macosx", "bsd") then
if level == "debug" or level == "all" then
- return "--passL:-s"
+ return '--passL:"-s"'
end
end
end
-- make the includedir flag
function nf_includedir(self, dir)
- return {"--passC:-I" .. path.translate(dir)}
+ return {string.format('--passC:"-I%s"', path.translate(dir))}
+end
+
+-- make the sysincludedir flag
+function nf_sysincludedir(self, dir)
+ return nf_includedir(self, dir)
end
-- make the link flag
function nf_link(self, lib)
if self:is_plat("windows") then
- return "--passL:" .. lib .. ".lib"
+ return string.format('--passL:"%s.lib"', lib)
+ else
+ return string.format('--passL:"-l%s"', lib)
+ end
+end
+
+-- make the syslink flag
+function nf_syslink(self, lib)
+ if self:is_plat("windows") then
+ return string.format('--passL:"%s.lib"', lib)
else
- return "--passL:-l" .. lib
+ if lib == "pthread" then
+ return {"--threads:on", string.format('--passL:"-l%s"', lib), '--dynlibOverride:"pthread"'}
+ else
+ return string.format('--passL:"-l%s"', lib)
+ end
end
end
-- make the linkdir flag
function nf_linkdir(self, dir)
if self:is_plat("windows") then
- return {"--passL:-libpath:" .. path.translate(dir)}
+ return {string.format('--passL:"-libpath:%s"', path.translate(dir))}
else
- return {"--passL:-L" .. path.translate(dir)}
+ return {string.format('--passL:"-L%s"', path.translate(dir))}
+ end
+end
+
+-- make the rpathdir flag
+function nf_rpathdir(self, dir, opt)
+ if self:is_plat("windows") then
+ return
+ end
+ opt = opt or {}
+ local extra = opt.extra
+ if extra and extra.installonly then
+ return
+ end
+ dir = path.translate(dir)
+
+ -- Use --passL:"-Wl,-rpath=<dir>" to pass rpath to the linker
+ -- We use standard -Wl,-rpath for gcc/clang on linux/macosx/bsd without check mainly.
+ if self:is_plat("macosx", "iphoneos") then
+ dir = dir:gsub("([@$][%w_]+)", function (name)
+ if name == "$ORIGIN" then
+ return "@loader_path"
+ end
+ return name
+ end)
+ local rpath = string.format("-Wl,-rpath,%s", dir)
+ return {string.format('--passL:"%s"', rpath)}
+ elseif self:is_plat("linux", "bsd", "android") then
+ dir = dir:gsub("([@$][%w_]+)", function (name)
+ if name == "@loader_path" or name == "@executable_path" then
+ return "\\$ORIGIN"
+ elseif name == "$ORIGIN" then
+ return "\\$ORIGIN"
+ end
+ return name
+ end)
+ local rpath = string.format("-Wl,-rpath=%s", dir)
+ local flags = {string.format('--passL:"%s"', rpath)}
+ if extra then
+ if extra.runpath == false and self:has_flags(string.format('--passL:"%s,--disable-new-dtags"', rpath), "ldflags") then
+ flags[1] = string.format('--passL:"%s,--disable-new-dtags"', rpath)
+ elseif extra.runpath == true and self:has_flags(string.format('--passL:"%s,--enable-new-dtags"', rpath), "ldflags") then
+ flags[1] = string.format('--passL:"%s,--enable-new-dtags"', rpath)
+ end
+ end
+ return flags
+ end
+
+ -- fallback
+ if self:has_flags(string.format('--passL:"-Wl,-rpath=%s"', dir), "ldflags") then
+ local flags = {string.format('--passL:"-Wl,-rpath=%s"', (dir:gsub("@[%w_]+", function (name)
+ local maps = { ["@loader_path"] = "$ORIGIN", ["@executable_path"] = "$ORIGIN" }
+ return maps[name]
+ end)))}
+ -- add_rpathdirs("...", {runpath = false})
+ if extra then
+ if extra.runpath == false and self:has_flags(string.format('--passL:"-Wl,-rpath=%s,--disable-new-dtags"', dir), "ldflags") then
+ flags[1] = string.format('--passL:"-Wl,-rpath=%s,--disable-new-dtags"', dir)
+ elseif extra.runpath == true and self:has_flags(string.format('--passL:"-Wl,-rpath=%s,--enable-new-dtags"', dir), "ldflags") then
+ flags[1] = string.format('--passL:"-Wl,-rpath=%s,--enable-new-dtags"', dir)
+ end
+ end
+ return flags
+ elseif self:has_flags('--passL:"-Xlinker" --passL:"-rpath" --passL:"-Xlinker" ' .. string.format('--passL:"%s"', dir), "ldflags") then
+ return {'--passL:"-Xlinker"', '--passL:"-rpath"', '--passL:"-Xlinker"', string.format('--passL:"%s"', (dir:gsub("%$ORIGIN", "@loader_path")))}
end
end