summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorruki <[email protected]>2025-12-05 22:36:39 +0800
committerruki <[email protected]>2025-12-05 22:36:39 +0800
commite688a7bd853c49ec10aa8656e704c2115ea5e14c (patch)
treeea514a602e65bec18b18e4037df605357bd95443
parent6d803e27d205bc7282597ac4db2a105f2a79d5ad (diff)
rewrite golang support
-rw-r--r--tests/projects/go/static_library/src/test.go2
-rw-r--r--xmake/modules/core/tools/go.lua93
-rw-r--r--xmake/modules/core/tools/go/has_flags.lua95
-rw-r--r--xmake/modules/private/tools/go/goenv.lua84
-rw-r--r--xmake/rules/go/build/object.lua84
-rw-r--r--xmake/rules/go/env/xmake.lua83
-rw-r--r--xmake/rules/go/xmake.lua3
-rw-r--r--xmake/toolchains/go/xmake.lua51
8 files changed, 216 insertions, 279 deletions
diff --git a/tests/projects/go/static_library/src/test.go b/tests/projects/go/static_library/src/test.go
index cf10523f9..c4e13af53 100644
--- a/tests/projects/go/static_library/src/test.go
+++ b/tests/projects/go/static_library/src/test.go
@@ -2,7 +2,7 @@ package main
import (
"fmt"
- "module"
+ "test"
)
func Run() {
diff --git a/xmake/modules/core/tools/go.lua b/xmake/modules/core/tools/go.lua
index 70e667ad9..90431320c 100644
--- a/xmake/modules/core/tools/go.lua
+++ b/xmake/modules/core/tools/go.lua
@@ -18,6 +18,7 @@
-- @file go.lua
--
+
-- imports
import("core.base.option")
import("core.project.config")
@@ -26,45 +27,39 @@ import("core.language.language")
-- init it
function init(self)
- self:set("gcarflags", "grc")
+ self:set("gcarflags", "")
end
-- make the optimize flag
function nf_optimize(self, level)
- -- only for source kind
- local kind = self:kind()
- if language.sourcekinds()[kind] then
- local maps = {
- none = "-N"
- }
- return maps[level]
- end
+ -- Go build flags for optimization
+ local maps = {
+ none = "-gcflags=-N"
+ }
+ return maps[level]
end
-- make the symbol flag
function nf_symbol(self, level, opt)
- local targetkind = opt.targetkind
- if targetkind ~= "object" then
- return
- end
- local maps = {
- debug = "-E"
- }
- return maps[level]
+ -- Go doesn't use separate symbol flags like C/C++
+ return
end
-- make the strip flag
function nf_strip(self, level)
+ -- Go uses -ldflags="-s -w" for stripping
+ -- -ldflags needs to be passed as separate arguments: -ldflags and the value
local maps = {
- debug = "-s"
- , all = "-s"
+ debug = {"-ldflags", "-s"}
+ , all = {"-ldflags", "-s -w"}
}
return maps[level]
end
-- make the includedir flag
function nf_includedir(self, dir)
- return {"-I", dir}
+ -- Go doesn't use -I flags, but we can use build tags or build constraints
+ return
end
-- make the sysincludedir flag
@@ -74,34 +69,50 @@ end
-- make the linkdir flag
function nf_linkdir(self, dir)
- return {"-L", dir}
+ -- Go uses -L flag for linker search paths via -ldflags
+ -- -ldflags needs to be passed as separate arguments: -ldflags and the value
+ -- Note: -L must be inside the -ldflags value, not as a separate argument
+ return {"-ldflags", "-L " .. dir}
end
--- make the link arguments list
-function linkargv(self, objectfiles, targetkind, targetfile, flags)
+-- make the build arguments list
+-- Modern Go uses "go build" command for both compilation and linking
+function buildargv(self, sourcefiles, targetkind, targetfile, flags)
+ local argv = {"build"}
+
+ -- add build flags
+ if flags then
+ table.join2(argv, flags)
+ end
+
+ -- set output file
+ table.insert(argv, "-o")
+ table.insert(argv, targetfile)
+
+ -- for static library, use buildmode=archive
if targetkind == "static" then
- return self:program(), table.join("tool", "pack", flags, targetfile, objectfiles)
+ table.insert(argv, "-buildmode=archive")
+ end
+
+ -- add source files (Go supports building from source file list)
+ -- The caller should change to the source directory before calling build
+ if sourcefiles and #sourcefiles > 0 then
+ -- convert source files to relative paths from current directory
+ -- or use absolute paths if needed
+ for _, sourcefile in ipairs(sourcefiles) do
+ table.insert(argv, sourcefile)
+ end
else
- return self:program(), table.join("tool", "link", flags, "-o", targetfile, objectfiles)
+ -- fallback to current package if no source files
+ table.insert(argv, ".")
end
+
+ return self:program(), argv
end
--- link the target file
-function link(self, objectfiles, targetkind, targetfile, flags)
+-- build the target file (compile and link in one step)
+function build(self, sourcefiles, targetkind, targetfile, flags)
os.mkdir(path.directory(targetfile))
- local program, argv = linkargv(self, objectfiles, targetkind, targetfile, flags)
- os.runv(program, argv, {envs = self:runenvs()})
-end
-
--- make the compile arguments list
-function compargv(self, sourcefiles, objectfile, flags)
- return self:program(), table.join("tool", "compile", flags, "-o", objectfile, sourcefiles)
-end
-
--- compile the source file
-function compile(self, sourcefiles, objectfile, dependinfo, flags)
- os.mkdir(path.directory(objectfile))
- local program, argv = compargv(self, sourcefiles, objectfile, flags)
+ local program, argv = buildargv(self, sourcefiles, targetkind, targetfile, flags)
os.runv(program, argv, {envs = self:runenvs()})
end
-
diff --git a/xmake/modules/core/tools/go/has_flags.lua b/xmake/modules/core/tools/go/has_flags.lua
index 1e84ac279..d3df6bf89 100644
--- a/xmake/modules/core/tools/go/has_flags.lua
+++ b/xmake/modules/core/tools/go/has_flags.lua
@@ -24,24 +24,31 @@ import("core.language.language")
-- is linker?
function _islinker(flags, opt)
-
-- the tool kind is gcld or gcsh?
local toolkind = opt.toolkind or ""
return toolkind:endswith("ld") or toolkind:endswith("sh")
end
-- try running
-function _try_running(...)
-
- local argv = {...}
+function _try_running(program, argv, opt)
local errors = nil
- return try { function () os.runv(table.unpack(argv)); return true end, catch { function (errs) errors = (errs or ""):trim() end }}, errors
+ local ok = try {
+ function ()
+ os.runv(program, argv, opt)
+ return true
+ end,
+ catch {
+ function (errs)
+ errors = (errs or ""):trim()
+ end
+ }
+ }
+ return ok, errors
end
-- attempt to check it from the argument list
function _check_from_arglist(flags, opt, islinker)
-
- -- only for compiler
+ -- only for compiler and single flag
if islinker or #flags > 1 then
return
end
@@ -55,66 +62,67 @@ function _check_from_arglist(flags, opt, islinker)
-- get all flags from argument list
local allflags = detectcache:get2(key, flagskey)
if not allflags then
-
- -- attempt to get argument list from the error info (help menu)
allflags = {}
- try
- {
- function () os.runv(opt.program, {"tool", "compile", "--help"}) end,
- catch
- {
- function (errors)
- local arglist = errors
- if arglist then
- for arg in arglist:gmatch("%s+(%-[%-%a%d]+)%s+") do
- allflags[arg] = true
- end
- end
- end
- }
+
+ -- try to get help from "go help build"
+ local help_output = try {
+ function ()
+ return os.iorunv(opt.program, {"help", "build"}, {envs = opt.envs})
+ end
+ }
+
+ if help_output then
+ -- parse flags from help output
+ for flag in help_output:gmatch("%s+(%-[%-%a%d=]+)%s+") do
+ allflags[flag] = true
+ end
+ end
+
+ -- also try "go build -h" for more detailed flags
+ local build_help = try {
+ function ()
+ return os.iorunv(opt.program, {"build", "-h"}, {envs = opt.envs})
+ end
}
+
+ if build_help then
+ for flag in build_help:gmatch("%s+(%-[%-%a%d=]+)%s+") do
+ allflags[flag] = true
+ end
+ end
-- save cache
detectcache:set2(key, flagskey, allflags)
end
+
return allflags[flags[1]]
end
-- try running to check flags
function _check_try_running(flags, opt, islinker)
-
-- make an stub source file
local sourcefile = path.join(os.tmpdir(), "detect", "go_has_flags.go")
- local objectfile = path.join(os.tmpdir(), "detect", "go_has_flags.o")
- local targetfile = path.join(os.tmpdir(), "detect", "go_has_flags.bin")
if not os.isfile(sourcefile) then
- io.writefile(sourcefile, "package main\nfunc main() {\n}")
- end
-
- -- check flags for linker
- if islinker then
-
- -- compile a object file first
- if not os.isfile(objectfile) and not _try_running(opt.program, table.join("tool", "compile", "-o", objectfile, sourcefile)) then
- return false
- end
-
- -- check it
- return _try_running(opt.program, table.join("tool", "link", flags, "-o", targetfile, objectfile))
+ io.writefile(sourcefile, "package main\n\nfunc main() {\n}\n")
end
- -- check flags for compiler
- return _try_running(opt.program, table.join("tool", "compile", flags, "-o", objectfile, sourcefile))
+ -- check flags for linker or compiler
+ -- Modern Go uses "go build" for both compilation and linking
+ local argv = {"build", "-o", os.tmpfile()}
+ table.join2(argv, flags)
+ table.insert(argv, sourcefile)
+
+ local ok, errors = _try_running(opt.program, argv, {envs = opt.envs})
+ return ok == true
end
-- has_flags(flags)?
--
--- @param opt the argument options, e.g. {toolname = "", program = "", programver = "", toolkind = "[cc|cxx|ld|ar|sh|gc|mm|mxx]"}
+-- @param opt the argument options, e.g. {toolname = "", program = "", programver = "", toolkind = "[gc|gcld|gcar]"}
--
-- @return true or false
--
function main(flags, opt)
-
-- is linker?
local islinker = _islinker(flags, opt)
@@ -126,4 +134,3 @@ function main(flags, opt)
-- try running to check it
return _check_try_running(flags, opt, islinker)
end
-
diff --git a/xmake/modules/private/tools/go/goenv.lua b/xmake/modules/private/tools/go/goenv.lua
index 278146a6c..1a448e6f3 100644
--- a/xmake/modules/private/tools/go/goenv.lua
+++ b/xmake/modules/private/tools/go/goenv.lua
@@ -21,26 +21,88 @@
-- imports
import("lib.detect.find_tool")
+-- Map xmake platform to Go OS
function GOOS(plat)
- local goos
- if plat == "windows" or plat == "mingw" or plat == "msys" or plat == "cygwin" then
- goos = "windows"
- elseif plat == "linux" then
- goos = "linux"
- elseif plat == "macosx" then
- goos = "darwin"
- end
- return goos
+ local goos_map = {
+ windows = "windows",
+ mingw = "windows",
+ msys = "windows",
+ cygwin = "windows",
+ linux = "linux",
+ macosx = "darwin",
+ android = "android",
+ ios = "ios",
+ freebsd = "freebsd",
+ netbsd = "netbsd",
+ openbsd = "openbsd",
+ dragonfly = "dragonfly",
+ solaris = "solaris",
+ aix = "aix",
+ plan9 = "plan9"
+ }
+ return goos_map[plat]
end
+-- Map xmake architecture to Go architecture
function GOARCH(arch)
- return (arch == "x86" or arch == "i386") and "386" or "amd64"
+ local goarch_map = {
+ x86 = "386",
+ i386 = "386",
+ x64 = "amd64",
+ x86_64 = "amd64",
+ amd64 = "amd64",
+ arm = "arm",
+ armv7 = "arm",
+ armv7s = "arm",
+ arm64 = "arm64",
+ aarch64 = "arm64",
+ mips = "mips",
+ mips64 = "mips64",
+ mips64le = "mips64le",
+ mipsle = "mipsle",
+ ppc = "ppc",
+ ppc64 = "ppc64",
+ ppc64le = "ppc64le",
+ riscv64 = "riscv64",
+ s390x = "s390x",
+ wasm = "wasm"
+ }
+
+ -- try direct match first
+ if goarch_map[arch] then
+ return goarch_map[arch]
+ end
+
+ -- try pattern matching for arm variants
+ if arch:match("^arm") then
+ if arch:match("64") or arch:match("aarch64") then
+ return "arm64"
+ else
+ return "arm"
+ end
+ end
+
+ -- try pattern matching for x86 variants
+ if arch:match("^x86") or arch:match("^i386") or arch:match("^i686") then
+ return "386"
+ end
+
+ if arch:match("^x64") or arch:match("^amd64") or arch:match("^x86_64") then
+ return "amd64"
+ end
+
+ return nil
end
+-- Get GOROOT from Go installation
function GOROOT(toolchain)
local go = find_tool("go")
if go then
- local gorootdir = try { function() return os.iorunv(go.program, {"env", "GOROOT"}) end }
+ local gorootdir = try {
+ function()
+ return os.iorunv(go.program, {"env", "GOROOT"}, {envs = toolchain and toolchain:get("runenvs") or nil})
+ end
+ }
if gorootdir then
return gorootdir:trim()
end
diff --git a/xmake/rules/go/build/object.lua b/xmake/rules/go/build/object.lua
deleted file mode 100644
index f9f1ccee4..000000000
--- a/xmake/rules/go/build/object.lua
+++ /dev/null
@@ -1,84 +0,0 @@
---!A cross-platform build utility based on Lua
---
--- Licensed under the Apache License, Version 2.0 (the "License");
--- you may not use this file except in compliance with the License.
--- You may obtain a copy of the License at
---
--- http://www.apache.org/licenses/LICENSE-2.0
---
--- Unless required by applicable law or agreed to in writing, software
--- distributed under the License is distributed on an "AS IS" BASIS,
--- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
--- See the License for the specific language governing permissions and
--- limitations under the License.
---
--- Copyright (C) 2015-present, Xmake Open Source Community.
---
--- @author ruki
--- @file object.lua
---
-
--- imports
-import("core.base.option")
-import("core.base.hashset")
-import("core.tool.compiler")
-import("core.project.depend")
-import("utils.progress")
-
--- build the source files
-function main(target, sourcebatch, opt)
-
- -- get source files and kind
- local sourcefiles = sourcebatch.sourcefiles
- local sourcekind = sourcebatch.sourcekind
-
- -- get object file
- local objectfile = target:objectfile(path.join(path.directory(sourcefiles[1]), "__go__"))
-
- -- get depend file
- local dependfile = target:dependfile(objectfile)
-
- -- remove the object files in sourcebatch
- local objectfiles_set = hashset.from(sourcebatch.objectfiles)
- for idx, file in irpairs(target:objectfiles()) do
- if objectfiles_set:has(file) then
- table.remove(target:objectfiles(), idx)
- end
- end
-
- -- add object file
- table.insert(target:objectfiles(), objectfile)
-
- -- load compiler
- local compinst = compiler.load(sourcekind, {target = target})
-
- -- get compile flags
- local compflags = compinst:compflags({target = target})
-
- -- load dependent info
- local dependinfo = option.get("rebuild") and {} or (depend.load(dependfile) or {})
-
- -- need build this object?
- local depvalues = {compinst:program(), compflags}
- if not depend.is_changed(dependinfo, {lastmtime = os.mtime(objectfile), values = depvalues}) then
- return
- end
-
- -- trace progress info
- for index, sourcefile in ipairs(sourcefiles) do
- progress.show(opt.progress, "${color.build.object}compiling.$(mode) %s", sourcefile)
- end
-
- -- trace verbose info
- vprint(compinst:compcmd(sourcefiles, objectfile, {compflags = compflags}))
-
- -- compile it
- dependinfo.files = {}
- assert(compinst:compile(sourcefiles, objectfile, {dependinfo = dependinfo, compflags = compflags}))
-
- -- update files and values to the dependent file
- dependinfo.values = depvalues
- table.join2(dependinfo.files, sourcefiles)
- depend.save(dependinfo, dependfile)
-end
-
diff --git a/xmake/rules/go/env/xmake.lua b/xmake/rules/go/env/xmake.lua
deleted file mode 100644
index 5ac24297a..000000000
--- a/xmake/rules/go/env/xmake.lua
+++ /dev/null
@@ -1,83 +0,0 @@
---!A cross-platform build utility based on Lua
---
--- Licensed under the Apache License, Version 2.0 (the "License");
--- you may not use this file except in compliance with the License.
--- You may obtain a copy of the License at
---
--- http://www.apache.org/licenses/LICENSE-2.0
---
--- Unless required by applicable law or agreed to in writing, software
--- distributed under the License is distributed on an "AS IS" BASIS,
--- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
--- See the License for the specific language governing permissions and
--- limitations under the License.
---
--- Copyright (C) 2015-present, Xmake Open Source Community.
---
--- @author ruki
--- @file xmake.lua
---
-
-rule("go.env")
- on_load(function (target)
-
- -- imports
- import("private.tools.go.goenv")
- import("async.runjobs")
- import("core.base.tty")
- import("core.base.option")
- import("core.project.config")
-
- -- check and install go packages for other platform
- local goroot = goenv.GOROOT()
- local goos = goenv.GOOS(target:plat())
- local goarch = goenv.GOARCH(target:arch())
- if goroot and goos and goarch then
- local goroot_local = path.join(config.builddir(), ".goenv", path.filename(goroot))
- local gopkgdir = path.join(os.isdir(goroot_local) and goroot_local or goroot, "pkg", goos .. "_" .. goarch)
- if not os.isdir(gopkgdir) or os.emptydir(gopkgdir) then
- local gosrcdir = path.join(goroot, "src")
- local confirm = utils.confirm({default = true, description = ("we need to build go for %s_%s only once first!"):format(goos, goarch)})
- if confirm then
- local build_task = function ()
- tty.erase_line_to_start().cr()
- printf("building go for %s_%s .. ", goos, goarch)
- io.flush()
- if is_host("windows") then
- os.vrunv(path.join(gosrcdir, "make.bat"), {"--no-clean"}, {envs = {GOOS = goos, GOARCH = goarch, GOROOT_BOOTSTRAP = goroot}, curdir = gosrcdir})
- else
-
- -- we need to copy goroot to the local directory to solving permission problem
- if is_host("linux") then
- os.vcp(goroot, goroot_local)
- goroot = path.absolute(goroot_local)
- gosrcdir = path.absolute(path.join(goroot_local, "src"))
- end
-
- -- we patch '/' to GOROOT_BOOTSTRAP to solve the following issue
- --
- -- in make.bash
- -- ERROR: $GOROOT_BOOTSTRAP must not be set to $GOROOT
- -- Set $GOROOT_BOOTSTRAP to a working Go tree >= Go 1.4.
- --
- os.vrunv(path.join(gosrcdir, "make.bash"), {"--no-clean"}, {envs = {GOOS = goos, GOARCH = goarch, GOROOT_BOOTSTRAP = goroot .. "/"}, curdir = gosrcdir})
- end
- tty.erase_line_to_start().cr()
- cprint("building go for %s_%s .. ${color.success}${text.success}", goos, goarch)
- end
- if option.get("verbose") then
- build_task()
- else
- runjobs("build/goenv", build_task, {waiting_indicator = true})
- end
- end
- end
-
- -- switch to the local go root directory
- if os.isdir(goroot_local) and os.isdir(gopkgdir) and not os.emptydir(gopkgdir) then
- config.set("gc", path.join(goroot_local, "bin", "go"), {readonly = true, force = true})
- config.set("gcld", path.join(goroot_local, "bin", "go"), {readonly = true, force = true})
- config.set("gcar", path.join(goroot_local, "bin", "go"), {readonly = true, force = true})
- end
- end
- end)
diff --git a/xmake/rules/go/xmake.lua b/xmake/rules/go/xmake.lua
index 18cee3e96..25ab5af56 100644
--- a/xmake/rules/go/xmake.lua
+++ b/xmake/rules/go/xmake.lua
@@ -20,7 +20,6 @@
rule("go.build")
set_sourcekinds("gc")
- add_deps("go.env")
on_load(function (target)
-- we disable to build across targets in parallel, because the source files may depend on other target modules
target:set("policy", "build.fence", true)
@@ -29,7 +28,7 @@ rule("go.build")
target:set("prefixname", "")
end
end)
- on_build_files("build.object")
+ on_build("build.target")
rule("go")
diff --git a/xmake/toolchains/go/xmake.lua b/xmake/toolchains/go/xmake.lua
index 69c06e3e0..d12fe0dc2 100644
--- a/xmake/toolchains/go/xmake.lua
+++ b/xmake/toolchains/go/xmake.lua
@@ -26,23 +26,48 @@ toolchain("go")
set_description("Go Programming Language Compiler")
-- set toolset
- set_toolset("gc", "$(env GC)", "go", "gccgo")
- set_toolset("gcld", "$(env GC)", "go", "gccgo")
- set_toolset("gcar", "$(env GC)", "go", "gccgo")
+ -- Modern Go uses "go" command for all operations
+ set_toolset("gc", "$(env GC)", "go")
+ set_toolset("gcld", "$(env GC)", "go")
+ set_toolset("gcar", "$(env GC)", "go")
-- on load
on_load(function (toolchain)
- if not toolchain:is_plat(os.host()) or not toolchain:is_arch(os.arch()) then
- import("private.tools.go.goenv")
- local goos = goenv.GOOS(toolchain:plat())
- if goos then
- toolchain:add("runenvs", "GOOS", goos)
- end
- local goarch = goenv.GOARCH(toolchain:arch())
- if goarch then
- toolchain:add("runenvs", "GOARCH", goarch)
+ import("private.tools.go.goenv")
+
+ -- set GOOS and GOARCH for cross-compilation
+ local goos = goenv.GOOS(toolchain:plat())
+ if goos then
+ toolchain:add("runenvs", "GOOS", goos)
+ end
+
+ local goarch = goenv.GOARCH(toolchain:arch())
+ if goarch then
+ toolchain:add("runenvs", "GOARCH", goarch)
+ end
+
+ -- Set CGO_ENABLED=0 by default for better cross-compilation support
+ -- Users can override this if they need CGO
+ if not os.getenv("CGO_ENABLED") then
+ toolchain:add("runenvs", "CGO_ENABLED", "0")
+ end
+
+ -- Disable Go modules mode to use GOPATH mode
+ -- This allows Go to find packages using GOPATH
+ if not os.getenv("GO111MODULE") then
+ toolchain:add("runenvs", "GO111MODULE", "off")
+ end
+
+ -- Set GOPATH to project directory if not already set
+ -- This allows Go to find packages in the project
+ if not os.getenv("GOPATH") then
+ local projectdir = os.projectdir()
+ if projectdir then
+ toolchain:add("runenvs", "GOPATH", projectdir)
end
end
+
+ -- set default flags
toolchain:set("gcldflags", "")
+ toolchain:set("gcarflags", "")
end)
-