diff options
| author | ruki <[email protected]> | 2025-12-05 22:36:39 +0800 |
|---|---|---|
| committer | ruki <[email protected]> | 2025-12-05 22:36:39 +0800 |
| commit | e688a7bd853c49ec10aa8656e704c2115ea5e14c (patch) | |
| tree | ea514a602e65bec18b18e4037df605357bd95443 | |
| parent | 6d803e27d205bc7282597ac4db2a105f2a79d5ad (diff) | |
rewrite golang support
| -rw-r--r-- | tests/projects/go/static_library/src/test.go | 2 | ||||
| -rw-r--r-- | xmake/modules/core/tools/go.lua | 93 | ||||
| -rw-r--r-- | xmake/modules/core/tools/go/has_flags.lua | 95 | ||||
| -rw-r--r-- | xmake/modules/private/tools/go/goenv.lua | 84 | ||||
| -rw-r--r-- | xmake/rules/go/build/object.lua | 84 | ||||
| -rw-r--r-- | xmake/rules/go/env/xmake.lua | 83 | ||||
| -rw-r--r-- | xmake/rules/go/xmake.lua | 3 | ||||
| -rw-r--r-- | xmake/toolchains/go/xmake.lua | 51 |
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) - |
