diff options
| author | ruki <[email protected]> | 2022-08-16 12:33:47 +0800 |
|---|---|---|
| committer | GitHub <[email protected]> | 2022-08-16 12:33:47 +0800 |
| commit | 81aca6fbe9149b377cdd88e8ee4b8f045d5a46da (patch) | |
| tree | 0f138f960ab9fd95265ecbffca7c6ae27d65f4a3 | |
| parent | d46b16f8c40493bfc78764931bf7c3209442696b (diff) | |
| parent | d9f82e1bba29adc8b11dc1f7b3887717f03137d6 (diff) | |
Merge pull request #2641 from xmake-io/cxxmodules
Improve C++20 modules support
50 files changed, 3038 insertions, 719 deletions
diff --git a/tests/apis/rules/xmake.lua b/tests/apis/rules/xmake.lua index 5a6e5d546..7d67f0dca 100644 --- a/tests/apis/rules/xmake.lua +++ b/tests/apis/rules/xmake.lua @@ -58,6 +58,12 @@ rule("stub2") after_build(function (target) print("rule(stub2): after_build") end) + before_build_files(function (target) + print("rule(stub2): before_build_files") + end) + after_build_files(function (target) + print("rule(stub2): after_build_files") + end) -- define rule: stub1 rule("stub1") @@ -148,3 +154,10 @@ target("test") add_files("src/index.md") add_files("src/test.c.in", {rule = "c code"}) + before_build(function (target) + print("target: before_build") + end) + + after_build(function (target) + print("target: after_build") + end) diff --git a/tests/projects/c++/modules/class/test.lua b/tests/projects/c++/modules/class/test.lua new file mode 100644 index 000000000..7717f8049 --- /dev/null +++ b/tests/projects/c++/modules/class/test.lua @@ -0,0 +1 @@ +inherit(".test_base") diff --git a/tests/projects/c++/modules/cpp_with_moduledeps/src/main.cpp b/tests/projects/c++/modules/cpp_with_moduledeps/src/main.cpp new file mode 100644 index 000000000..b943e786a --- /dev/null +++ b/tests/projects/c++/modules/cpp_with_moduledeps/src/main.cpp @@ -0,0 +1,8 @@ +// main.cpp +import mod; + +int main() +{ + f(); + return 0; +} diff --git a/tests/projects/c++/modules/cpp_with_moduledeps/src/mod.cpp b/tests/projects/c++/modules/cpp_with_moduledeps/src/mod.cpp new file mode 100644 index 000000000..bedf48a6c --- /dev/null +++ b/tests/projects/c++/modules/cpp_with_moduledeps/src/mod.cpp @@ -0,0 +1,4 @@ +module mod; +void f() +{ +} diff --git a/tests/projects/c++/modules/cpp_with_moduledeps/src/mod.mpp b/tests/projects/c++/modules/cpp_with_moduledeps/src/mod.mpp new file mode 100644 index 000000000..0f77aa62b --- /dev/null +++ b/tests/projects/c++/modules/cpp_with_moduledeps/src/mod.mpp @@ -0,0 +1,2 @@ +export module mod; +export void f(); diff --git a/tests/projects/c++/modules/cpp_with_moduledeps/test.lua b/tests/projects/c++/modules/cpp_with_moduledeps/test.lua new file mode 100644 index 000000000..7717f8049 --- /dev/null +++ b/tests/projects/c++/modules/cpp_with_moduledeps/test.lua @@ -0,0 +1 @@ +inherit(".test_base") diff --git a/tests/projects/c++/modules/cpp_with_moduledeps/xmake.lua b/tests/projects/c++/modules/cpp_with_moduledeps/xmake.lua new file mode 100644 index 000000000..5c7a8a6f7 --- /dev/null +++ b/tests/projects/c++/modules/cpp_with_moduledeps/xmake.lua @@ -0,0 +1,10 @@ +set_languages("c++20") + +target("mod") + set_kind("static") + add_files("src/mod.mpp", "src/mod.cpp") + +target("main") + set_kind("binary") + add_deps("mod") + add_files("src/main.cpp") diff --git a/tests/projects/c++/modules/dependence/test.lua b/tests/projects/c++/modules/dependence/test.lua new file mode 100644 index 000000000..7717f8049 --- /dev/null +++ b/tests/projects/c++/modules/dependence/test.lua @@ -0,0 +1 @@ +inherit(".test_base") diff --git a/tests/projects/c++/modules/dependence2/test.lua b/tests/projects/c++/modules/dependence2/test.lua new file mode 100644 index 000000000..7717f8049 --- /dev/null +++ b/tests/projects/c++/modules/dependence2/test.lua @@ -0,0 +1 @@ +inherit(".test_base") diff --git a/tests/projects/c++/modules/hello with spaces/test.lua b/tests/projects/c++/modules/hello with spaces/test.lua new file mode 100644 index 000000000..7717f8049 --- /dev/null +++ b/tests/projects/c++/modules/hello with spaces/test.lua @@ -0,0 +1 @@ +inherit(".test_base") diff --git a/tests/projects/c++/modules/hello/test.lua b/tests/projects/c++/modules/hello/test.lua new file mode 100644 index 000000000..7717f8049 --- /dev/null +++ b/tests/projects/c++/modules/hello/test.lua @@ -0,0 +1 @@ +inherit(".test_base") diff --git a/tests/projects/c++/modules/impl_unit/test.lua b/tests/projects/c++/modules/impl_unit/test.lua new file mode 100644 index 000000000..7717f8049 --- /dev/null +++ b/tests/projects/c++/modules/impl_unit/test.lua @@ -0,0 +1 @@ +inherit(".test_base") diff --git a/tests/projects/c++/modules/inline_and_template/test.lua b/tests/projects/c++/modules/inline_and_template/test.lua new file mode 100644 index 000000000..7717f8049 --- /dev/null +++ b/tests/projects/c++/modules/inline_and_template/test.lua @@ -0,0 +1 @@ +inherit(".test_base") diff --git a/tests/projects/c++/modules/staticlib/test.lua b/tests/projects/c++/modules/staticlib/test.lua new file mode 100644 index 000000000..7717f8049 --- /dev/null +++ b/tests/projects/c++/modules/staticlib/test.lua @@ -0,0 +1 @@ +inherit(".test_base") diff --git a/tests/projects/c++/modules/stl_headerunit/src/hello.mpp b/tests/projects/c++/modules/stl_headerunit/src/hello.mpp new file mode 100644 index 000000000..d45c5759e --- /dev/null +++ b/tests/projects/c++/modules/stl_headerunit/src/hello.mpp @@ -0,0 +1,11 @@ +module; + +export module hello; + +import <iostream>; + +export namespace hello { + void say(const char* str) { + std::cout << str << std::endl; + } +} diff --git a/tests/projects/c++/modules/stl_headerunit/src/main.cpp b/tests/projects/c++/modules/stl_headerunit/src/main.cpp new file mode 100644 index 000000000..1e5cc698f --- /dev/null +++ b/tests/projects/c++/modules/stl_headerunit/src/main.cpp @@ -0,0 +1,6 @@ +import hello; + +int main() { + hello::say("hello module!"); + return 0; +} diff --git a/tests/projects/c++/modules/stl_headerunit/test.lua b/tests/projects/c++/modules/stl_headerunit/test.lua new file mode 100644 index 000000000..c18e5a1d0 --- /dev/null +++ b/tests/projects/c++/modules/stl_headerunit/test.lua @@ -0,0 +1 @@ +inherit(".test_headerunits") diff --git a/tests/projects/c++/modules/stl_headerunit/xmake.lua b/tests/projects/c++/modules/stl_headerunit/xmake.lua new file mode 100644 index 000000000..e85378c46 --- /dev/null +++ b/tests/projects/c++/modules/stl_headerunit/xmake.lua @@ -0,0 +1,4 @@ +set_languages("c++20") +target("stl_headerunit") + set_kind("binary") + add_files("src/*.cpp", "src/*.mpp") diff --git a/tests/projects/c++/modules/submodules/test.lua b/tests/projects/c++/modules/submodules/test.lua new file mode 100644 index 000000000..7717f8049 --- /dev/null +++ b/tests/projects/c++/modules/submodules/test.lua @@ -0,0 +1 @@ +inherit(".test_base") diff --git a/tests/projects/c++/modules/submodules2/test.lua b/tests/projects/c++/modules/submodules2/test.lua new file mode 100644 index 000000000..7717f8049 --- /dev/null +++ b/tests/projects/c++/modules/submodules2/test.lua @@ -0,0 +1 @@ +inherit(".test_base") diff --git a/tests/projects/c++/modules/test_base.lua b/tests/projects/c++/modules/test_base.lua new file mode 100644 index 000000000..4aee9f8e7 --- /dev/null +++ b/tests/projects/c++/modules/test_base.lua @@ -0,0 +1,30 @@ +import("lib.detect.find_tool") +import("core.base.semver") + +function _build() + local ci = (os.getenv("CI") or os.getenv("GITHUB_ACTIONS") or ""):lower() + if ci == "true" then + os.exec("xmake -rvD") + else + os.exec("xmake -r") + end +end + +function main(t) + if is_subhost("windows") then + os.exec("xmake f -c") + _build() + elseif is_host("linux") then + local gcc = find_tool("gcc", {version = true}) + if gcc and gcc.version and semver.compare(gcc.version, "11.0") >= 0 then + os.exec("xmake f -c") + _build() + end + local clang = find_tool("clang", {version = true}) + if clang and clang.version and semver.compare(clang.version, "14.0") >= 0 then + os.exec("xmake clean -a") + os.exec("xmake f --toolchain=clang -c") + _build() + end + end +end diff --git a/tests/projects/c++/modules/test_headerunits.lua b/tests/projects/c++/modules/test_headerunits.lua new file mode 100644 index 000000000..905812c14 --- /dev/null +++ b/tests/projects/c++/modules/test_headerunits.lua @@ -0,0 +1,34 @@ +import("lib.detect.find_tool") +import("core.base.semver") +import("detect.sdks.find_vstudio") + +function _build() + local ci = (os.getenv("CI") or os.getenv("GITHUB_ACTIONS") or ""):lower() + if ci == "true" then + os.exec("xmake -rvD") + else + os.exec("xmake -r") + end +end + +function main(t) + if is_subhost("windows") then + local vs = find_vstudio() + if vs and vs["2022"] then + os.exec("xmake f -c") + _build() + end + elseif is_host("linux") then + local gcc = find_tool("gcc", {version = true}) + if gcc and gcc.version and semver.compare(gcc.version, "11.0") >= 0 then + os.exec("xmake f -c") + _build() + end + local clang = find_tool("clang", {version = true}) + if clang and clang.version and semver.compare(clang.version, "16.0") >= 0 then + os.exec("xmake clean -a") + os.exec("xmake f --toolchain=clang -c") + _build() + end + end +end diff --git a/tests/projects/c++/modules/user_headerunit/src/header.hpp b/tests/projects/c++/modules/user_headerunit/src/header.hpp new file mode 100644 index 000000000..840c3da80 --- /dev/null +++ b/tests/projects/c++/modules/user_headerunit/src/header.hpp @@ -0,0 +1,5 @@ +#pragma once + +namespace hello { + constexpr auto FOO = "Hello"; +} diff --git a/tests/projects/c++/modules/user_headerunit/src/hello.mpp b/tests/projects/c++/modules/user_headerunit/src/hello.mpp new file mode 100644 index 000000000..5b2f434ad --- /dev/null +++ b/tests/projects/c++/modules/user_headerunit/src/hello.mpp @@ -0,0 +1,10 @@ +module; +#include <cstdio> + +export module hello; + +export namespace hello { + void say(const char *arg) { + printf("%s\n", arg); + } +} diff --git a/tests/projects/c++/modules/user_headerunit/src/main.cpp b/tests/projects/c++/modules/user_headerunit/src/main.cpp new file mode 100644 index 000000000..ad786367e --- /dev/null +++ b/tests/projects/c++/modules/user_headerunit/src/main.cpp @@ -0,0 +1,7 @@ +import hello; +import "header.hpp"; + +int main() { + hello::say(hello::FOO); + return 0; +} diff --git a/tests/projects/c++/modules/user_headerunit/test.lua b/tests/projects/c++/modules/user_headerunit/test.lua new file mode 100644 index 000000000..c18e5a1d0 --- /dev/null +++ b/tests/projects/c++/modules/user_headerunit/test.lua @@ -0,0 +1 @@ +inherit(".test_headerunits") diff --git a/tests/projects/c++/modules/user_headerunit/xmake.lua b/tests/projects/c++/modules/user_headerunit/xmake.lua new file mode 100644 index 000000000..dae91d11f --- /dev/null +++ b/tests/projects/c++/modules/user_headerunit/xmake.lua @@ -0,0 +1,5 @@ +set_languages("c++20") +target("user_headerunit") + set_kind("binary") + add_headerfiles("src/*.hpp") + add_files("src/*.cpp", "src/*.mpp") diff --git a/tests/projects/linux/bpf/minimal/test.lua b/tests/projects/linux/bpf/minimal/test.lua index 2856a3831..474060ab0 100644 --- a/tests/projects/linux/bpf/minimal/test.lua +++ b/tests/projects/linux/bpf/minimal/test.lua @@ -1,5 +1,9 @@ function main(t) if is_host("linux") and os.arch() == "x86_64" then + -- TODO, we need wait fix of linux kernel + if linuxos.name() == "archlinux" then + return + end os.vrun("xmake f -y -p android -vD") os.vrun("xmake -y -vD") end diff --git a/xmake/actions/build/build.lua b/xmake/actions/build/build.lua index a32b8d705..3eddf1610 100644 --- a/xmake/actions/build/build.lua +++ b/xmake/actions/build/build.lua @@ -37,25 +37,27 @@ function _clean_target(target) end end --- add builtin batch jobs -function _add_batchjobs_builtin(batchjobs, rootjob, target) +-- add batch jobs for rules +function _add_batchjobs_for_rules(batchjobs, rootjob, target, suffix) -- uses the rules script? local job, job_leaf for _, r in irpairs(target:orderules()) do -- reverse rules order for batchjobs:addjob() - local script = r:script("build") + local scriptname = "build" .. (suffix and ("_" .. suffix) or "") + local script = r:script(scriptname) if script then - if r:extraconf("build", "batch") then - job, job_leaf = assert(script(target, batchjobs, {rootjob = job or rootjob}), "rule(%s):on_build(): no returned job!", r:name()) + if r:extraconf(scriptname, "batch") then + job, job_leaf = assert(script(target, batchjobs, {rootjob = job or rootjob}), "rule(%s):%s(): no returned job!", r:name(), scriptname) else - job = batchjobs:addjob("rule/" .. r:name() .. "/build", function (index, total) + job = batchjobs:addjob("rule/" .. r:name() .. "/" .. scriptname, function (index, total) script(target, {progress = (index * 100) / total}) end, {rootjob = job or rootjob}) end else - local buildcmd = r:script("buildcmd") + scriptname = "buildcmd" .. (suffix and ("_" .. suffix) or "") + local buildcmd = r:script(scriptname) if buildcmd then - job = batchjobs:addjob("rule/" .. r:name() .. "/build", function (index, total) + job = batchjobs:addjob("rule/" .. r:name() .. "/" .. scriptname, function (index, total) local batchcmds_ = batchcmds.new({target = target}) buildcmd(target, batchcmds_, {progress = (index * 100) / total}) batchcmds_:runcmds({dryrun = option.get("dry-run")}) @@ -63,6 +65,14 @@ function _add_batchjobs_builtin(batchjobs, rootjob, target) end end end + return job, job_leaf or job +end + +-- add builtin batch jobs +function _add_batchjobs_builtin(batchjobs, rootjob, target) + + -- add batchjobs for rules + local job, job_leaf = _add_batchjobs_for_rules(batchjobs, rootjob, target) -- uses the builtin target script if not job and (target:is_static() or target:is_binary() or target:is_shared() or target:is_object()) then @@ -125,19 +135,6 @@ function _add_batchjobs_for_target(batchjobs, rootjob, target) if after_build then after_build(target, {progress = progress}) end - for _, r in ipairs(target:orderules()) do - local after_build = r:script("build_after") - if after_build then - after_build(target, {progress = progress}) - else - local after_buildcmd = r:script("buildcmd_after") - if after_buildcmd then - local batchcmds_ = batchcmds.new({target = target}) - after_buildcmd(target, batchcmds_, {progress = progress}) - batchcmds_:runcmds({dryrun = option.get("dry-run")}) - end - end - end -- restore environments if oldenvs then @@ -146,8 +143,14 @@ function _add_batchjobs_for_target(batchjobs, rootjob, target) end, {rootjob = rootjob}) + -- add batchjobs for rules/build_after + local rules_job_build_after, rules_job_build_after_leaf = _add_batchjobs_for_rules(batchjobs, job_build_after, target, "after") + -- add batch jobs for target, @note only on_build script support batch jobs - local job_build, job_build_leaf = _add_batchjobs(batchjobs, job_build_after, target) + local job_build, job_build_leaf = _add_batchjobs(batchjobs, rules_job_build_after_leaf or job_build_after, target) + + -- add batchjobs for rules/build_before + local rules_job_build_before, rules_job_build_before_leaf = _add_batchjobs_for_rules(batchjobs, job_build_leaf, target, "before") -- add before_build job for target local job_build_before = batchjobs:addjob(target:name() .. "/before_build", function (index, total) @@ -166,20 +169,7 @@ function _add_batchjobs_for_target(batchjobs, rootjob, target) if before_build then before_build(target, {progress = progress}) end - for _, r in ipairs(target:orderules()) do - local before_build = r:script("build_before") - if before_build then - before_build(target, {progress = progress}) - else - local before_buildcmd = r:script("buildcmd_before") - if before_buildcmd then - local batchcmds_ = batchcmds.new({target = target}) - before_buildcmd(target, batchcmds_, {progress = progress}) - batchcmds_:runcmds({dryrun = option.get("dry-run")}) - end - end - end - end, {rootjob = job_build_leaf}) + end, {rootjob = rules_job_build_before_leaf or job_build_leaf}) return job_build_before, job_build, job_build_after end diff --git a/xmake/core/project/target.lua b/xmake/core/project/target.lua index 4397fa701..61f928819 100644 --- a/xmake/core/project/target.lua +++ b/xmake/core/project/target.lua @@ -1280,6 +1280,38 @@ function _instance:fileconfig_set(sourcefile, info) self._FILESCONFIG = filesconfig end +-- add the config info to the given source file +function _instance:fileconfig_add(sourcefile, info) + local filesconfig = self._FILESCONFIG or {} + local fileconfig = filesconfig[sourcefile] + if fileconfig then + for k, v in pairs(info) do + if k == "force" then + -- fileconfig_add("xxx.c", {force = {cxxflags = ""}}) + local force = fileconfig[k] or {} + for k2, v2 in pairs(v) do + if force[k2] then + force[k2] = table.join(force[k2], v2) + else + force[k2] = v2 + end + end + fileconfig[k] = force + else + -- fileconfig_add("xxx.c", {cxxflags = ""}) + if fileconfig[k] then + fileconfig[k] = table.join(fileconfig[k], v) + else + fileconfig[k] = v + end + end + end + else + filesconfig[sourcefile] = info + end + self._FILESCONFIG = filesconfig +end + -- get the source files function _instance:sourcefiles() diff --git a/xmake/modules/detect/tools/cl/has_flags.lua b/xmake/modules/detect/tools/cl/has_flags.lua index ceb3561d4..fe57e38b0 100644 --- a/xmake/modules/detect/tools/cl/has_flags.lua +++ b/xmake/modules/detect/tools/cl/has_flags.lua @@ -83,7 +83,8 @@ end function main(flags, opt) -- attempt to check it from the argument list - if _check_from_arglist(flags, opt) then + opt = opt or {} + if not opt.tryrun and _check_from_arglist(flags, opt) then return true end diff --git a/xmake/modules/detect/tools/gcc/has_flags.lua b/xmake/modules/detect/tools/gcc/has_flags.lua index f7cf1d29e..b8e63423a 100644 --- a/xmake/modules/detect/tools/gcc/has_flags.lua +++ b/xmake/modules/detect/tools/gcc/has_flags.lua @@ -101,10 +101,11 @@ end function main(flags, opt) -- is linker? + opt = opt or {} local islinker = _islinker(flags, opt) -- attempt to check it from the argument list - if _check_from_arglist(flags, opt, islinker) then + if not opt.tryrun and _check_from_arglist(flags, opt, islinker) then return true end diff --git a/xmake/plugins/project/clang/compile_commands.lua b/xmake/plugins/project/clang/compile_commands.lua index ce2dfcd30..3145b71b6 100644 --- a/xmake/plugins/project/clang/compile_commands.lua +++ b/xmake/plugins/project/clang/compile_commands.lua @@ -113,6 +113,15 @@ function _make_arguments(jsonfile, arguments, sourcefile) table.insert(arguments_escape, _escape_path(arg)) end + -- remove repeat + -- this is because some rules will repeatedly bind the same sourcekind, e.g. `rule("c++.build.modules.builder")` + local key = hash.uuid(os.args(arguments_escape) .. sourcefile) + local map = _g.map or {} + _g.map = map + if map[key] then + return + end + -- make body jsonfile:printf( [[%s{ @@ -123,6 +132,26 @@ function _make_arguments(jsonfile, arguments, sourcefile) -- clear first line marks _g.firstline = false + map[key] = true +end + +-- make commands for target +function _make_commands_for_targetrules(jsonfile, target, suffix) + for _, ruleinst in ipairs(target:orderules()) do + local scriptname = "buildcmd" .. (suffix and ("_" .. suffix) or "") + local script = ruleinst:script(scriptname) + if script then + local batchcmds_ = batchcmds.new({target = target}) + script(target, batchcmds_, {}) + if not batchcmds_:empty() then + for _, cmd in ipairs(batchcmds_:cmds()) do + if cmd.program then + _make_arguments(jsonfile, table.join(cmd.program, cmd.argv)) + end + end + end + end + end end -- make commands for object rules @@ -183,11 +212,13 @@ end -- make objects function _make_objects(jsonfile, target, sourcebatch) + _make_commands_for_targetrules(jsonfile, target, "before") _make_commands_for_objectrules(jsonfile, target, sourcebatch, "before") if not _make_commands_for_objects(jsonfile, target, sourcebatch) then _make_commands_for_objectrules(jsonfile, target, sourcebatch) end _make_commands_for_objectrules(jsonfile, target, sourcebatch, "after") + _make_commands_for_targetrules(jsonfile, target, "after") end -- make target diff --git a/xmake/plugins/project/cmake/cmakelists.lua b/xmake/plugins/project/cmake/cmakelists.lua index 09feb78e3..5154956e6 100644 --- a/xmake/plugins/project/cmake/cmakelists.lua +++ b/xmake/plugins/project/cmake/cmakelists.lua @@ -19,7 +19,9 @@ -- -- imports +import("core.base.colors") import("core.project.project") +import("core.project.config") import("core.tool.compiler") import("core.base.semver") import("core.base.hashset") @@ -109,6 +111,67 @@ function _get_configs_from_target(target, name) return table.unique(values) end +-- this sourcebatch is built? +function _sourcebatch_is_built(sourcebatch) + -- we can only use rulename to filter them because sourcekind may be bound to multiple rules + local rulename = sourcebatch.rulename + if rulename == "c.build" or rulename == "c++.build" or rulename == "asm.build" or rulename == "cuda.build" then + return true + end +end + +-- translate flag +function _translate_flag(flag, outputdir) + if flag then + if path.instance_of(flag) then + flag = flag:clone():set(_get_unix_path_relative_to_cmake(flag:rawstr(), outputdir)):str() + elseif path.is_absolute(flag) then + flag = _get_unix_path_relative_to_cmake(flag, outputdir) + elseif flag:startswith("-fmodule-file=") then + flag = "-fmodule-file=" .. _get_unix_path_relative_to_cmake(flag:sub(15), outputdir) + elseif flag:startswith("-fmodule-mapper=") then + flag = "-fmodule-mapper=" .. _get_unix_path_relative_to_cmake(flag:sub(17), outputdir) + elseif flag:match("(.+)=(.+)") then + local k, v = flag:match("(.+)=(.+)") + if v and v:endswith(".ifc") then -- e.g. hello=xxx/hello.ifc + flag = k .. "=" .. _get_unix_path_relative_to_cmake(v, outputdir) + end + end + end + return flag +end + +-- translate flags +function _translate_flags(flags, outputdir) + if not flags then + return + end + local result = {} + for _, flag in ipairs(flags) do + if type(flag) == "table" and not path.instance_of(flag) then + for _, v in ipairs(flag) do + table.insert(result, _translate_flag(v, outputdir)) + end + else + table.insert(result, _translate_flag(flag, outputdir)) + end + end + return result +end + +-- get flags from fileconfig +function _get_flags_from_fileconfig(fileconfig, outputdir, name) + local flags = {} + table.join2(flags, fileconfig[name]) + if fileconfig.force then + table.join2(flags, fileconfig.force[name]) + end + flags = _translate_flags(flags, outputdir) + if #flags > 0 then + return flags + end +end + -- add project info function _add_project(cmakelists, languages, outputdir) @@ -163,8 +226,34 @@ function _add_target_phony(cmakelists, target) cmakelists:print("") end +-- set compiler +function _set_target_compiler(cmakelists, target) + -- use custom toolchain? + if config.get("toolchain") or target:get("toolchains") then + local cc = target:tool("cc") + if cc then + cc = cc:gsub("\\", "/") + cmakelists:print("set(CMAKE_C_COMPILER \"%s\")", cc) + end + local cxx, cxx_name = target:tool("cxx") + if cxx then + if cxx_name == "clang" or cxx_name == "gcc" then + local dir = path.directory(cxx) + local name = path.filename(cxx) + name = name:gsub("clang$", "clang++") + name = name:gsub("clang%-", "clang++-") + name = name:gsub("gcc$", "g++") + name = name:gsub("gcc%-", "g++-") + cxx = path.join(dir, name):gsub("\\", "/") + end + cmakelists:print("set(CMAKE_CXX_COMPILER \"%s\")", cxx) + end + end +end + -- add target: binary function _add_target_binary(cmakelists, target, outputdir) + _set_target_compiler(cmakelists, target) cmakelists:print("add_executable(%s \"\")", target:name()) cmakelists:print("set_target_properties(%s PROPERTIES OUTPUT_NAME \"%s\")", target:name(), target:basename()) cmakelists:print("set_target_properties(%s PROPERTIES RUNTIME_OUTPUT_DIRECTORY \"%s\")", target:name(), _get_unix_path_relative_to_cmake(target:targetdir(), outputdir)) @@ -172,6 +261,7 @@ end -- add target: static function _add_target_static(cmakelists, target, outputdir) + _set_target_compiler(cmakelists, target) cmakelists:print("add_library(%s STATIC \"\")", target:name()) cmakelists:print("set_target_properties(%s PROPERTIES OUTPUT_NAME \"%s\")", target:name(), target:basename()) cmakelists:print("set_target_properties(%s PROPERTIES ARCHIVE_OUTPUT_DIRECTORY \"%s\")", target:name(), _get_unix_path_relative_to_cmake(target:targetdir(), outputdir)) @@ -179,6 +269,7 @@ end -- add target: shared function _add_target_shared(cmakelists, target, outputdir) + _set_target_compiler(cmakelists, target) cmakelists:print("add_library(%s SHARED \"\")", target:name()) cmakelists:print("set_target_properties(%s PROPERTIES OUTPUT_NAME \"%s\")", target:name(), target:basename()) if target:is_plat("windows") then @@ -212,13 +303,12 @@ function _add_target_sources(cmakelists, target, outputdir) local has_cuda = false cmakelists:print("target_sources(%s PRIVATE", target:name()) for _, sourcebatch in table.orderpairs(target:sourcebatches()) do - local sourcekind = sourcebatch.sourcekind - if sourcekind == "cc" or sourcekind == "cxx" or sourcekind == "as" or sourcekind == "cu" then + if _sourcebatch_is_built(sourcebatch) then for _, sourcefile in ipairs(sourcebatch.sourcefiles) do cmakelists:print(" " .. _get_unix_path(sourcefile, outputdir)) end end - if sourcekind == "cu" then + if sourcebatch.sourcekind == "cu" then has_cuda = true end end @@ -390,76 +480,66 @@ function _add_target_compile_definitions(cmakelists, target) end end +-- add target source files flags +function _add_target_sourcefiles_flags(cmakelists, target, sourcefile, name, outputdir) + local fileconfig = target:fileconfig(sourcefile) + if fileconfig then + local flags = _get_flags_from_fileconfig(fileconfig, outputdir, name) + if flags and #flags > 0 then + cmakelists:print("set_source_files_properties(" + .. _get_unix_path_relative_to_cmake(sourcefile, outputdir) + .. " PROPERTIES COMPILE_OPTIONS") + local flagstrs = {} + for _, flag in ipairs(flags) do + if name == "cxxflags" then + table.insert(flagstrs, "$<$<COMPILE_LANGUAGE:CXX>:" .. flag .. ">") + elseif name == "cflags" then + table.insert(flagstrs, "$<$<COMPILE_LANGUAGE:C>:" .. flag .. ">") + elseif name == "cxflags" then + table.insert(flagstrs, "$<$<COMPILE_LANGUAGE:C>:" .. flag .. ">") + table.insert(flagstrs, "$<$<COMPILE_LANGUAGE:CXX>:" .. flag .. ">") + elseif name == "cuflags" then + table.insert(flagstrs, "$<$<COMPILE_LANGUAGE:CUDA>:" .. flag .. ">") + end + end + cmakelists:print(" \"%s\"", table.concat(flagstrs, ";")) + cmakelists:print(")") + end + end +end + -- add target compile options -function _add_target_compile_options(cmakelists, target) +function _add_target_compile_options(cmakelists, target, outputdir) local cflags = _get_configs_from_target(target, "cflags") local cxflags = _get_configs_from_target(target, "cxflags") local cxxflags = _get_configs_from_target(target, "cxxflags") local cuflags = _get_configs_from_target(target, "cuflags") if #cflags > 0 or #cxflags > 0 or #cxxflags > 0 or #cuflags > 0 then cmakelists:print("target_compile_options(%s PRIVATE", target:name()) - for _, flag in ipairs(cflags) do + for _, flag in ipairs(_translate_flags(cflags, outputdir)) do cmakelists:print(" $<$<COMPILE_LANGUAGE:C>:" .. flag .. ">") end - for _, flag in ipairs(cxflags) do + for _, flag in ipairs(_translate_flags(cxflags, outputdir)) do cmakelists:print(" $<$<COMPILE_LANGUAGE:C>:" .. flag .. ">") cmakelists:print(" $<$<COMPILE_LANGUAGE:CXX>:" .. flag .. ">") end - for _, flag in ipairs(cxxflags) do + for _, flag in ipairs(_translate_flags(cxxflags, outputdir)) do cmakelists:print(" $<$<COMPILE_LANGUAGE:CXX>:" .. flag .. ">") end - for _, flag in ipairs(cuflags) do + for _, flag in ipairs(_translate_flags(cuflags, outputdir)) do cmakelists:print(" $<$<COMPILE_LANGUAGE:CUDA>:" .. flag .. ">") end cmakelists:print(")") end -end --- add target language standards -function _add_target_language_standards(cmakelists, target) - local cstds = - { - c89 = "90" - , gnu89 = "90" -- TODO add cflags -std=gnu90 if supported - , c99 = "99" - , gnu99 = "99" -- TODO - , c11 = "11" - , gnu11 = "11" -- TODO - } - local cxxstds = - { - cxx98 = "98" - , gnuxx98 = "98" -- TODO - , cxx11 = "11" - , gnuxx11 = "11" - , cxx14 = "14" - , gnuxx14 = "14" - , cxx17 = "17" - , gnuxx17 = "17" - , cxx1z = "17" - , gnuxx1z = "17" - , cxx2a = "20" - , gnuxx2a = "20" - , cxxlatest = "latest" - } - for _, lang in ipairs(target:get("languages")) do - local cstd = cstds[lang] - if cstd then - cmakelists:print("set_property(TARGET %s PROPERTY C_STANDARD %s)", target:name(), cstd) - if cstd == "99" or cstd == "11" then - cmakelists:print("if(MSVC)") - cmakelists:print(" target_compile_options(%s PRIVATE $<$<COMPILE_LANGUAGE:C>:-TP>)", target:name()) - cmakelists:print("endif()") - end - end - local cxxstd = cxxstds[lang] - if cxxstd then - if cxxstd == "latest" then - cmakelists:print("if (MSVC)") - cmakelists:print(" target_compile_options(%s PRIVATE $<$<COMPILE_LANGUAGE:CXX>:/std:c++latest>)", target:name()) - cmakelists:print("endif()") - else - cmakelists:print("set_property(TARGET %s PROPERTY CXX_STANDARD %s)", target:name(), cxxstd) + -- add cflags/cxxflags for the specific source files + for _, sourcebatch in table.orderpairs(target:sourcebatches()) do + if _sourcebatch_is_built(sourcebatch) then + for _, sourcefile in ipairs(sourcebatch.sourcefiles) do + _add_target_sourcefiles_flags(cmakelists, target, sourcefile, "cxxflags", outputdir) + _add_target_sourcefiles_flags(cmakelists, target, sourcefile, "cflags", outputdir) + _add_target_sourcefiles_flags(cmakelists, target, sourcefile, "cxflags", outputdir) + _add_target_sourcefiles_flags(cmakelists, target, sourcefile, "cuflags", outputdir) end end end @@ -516,8 +596,14 @@ function _add_target_languages(cmakelists, target) local languages = target:get("languages") if languages then for _, lang in ipairs(languages) do + local has_ext = false + if lang:startswith("gnu") then + lang = lang:sub(4) + has_ext = true + end local feature = features[lang] or (features[lang:replace("++", "xx")]) if feature then + cmakelists:print("set_target_properties(%s PROPERTIES CXX_EXTENSIONS %s)", target:name(), has_ext and "ON" or "OFF") cmakelists:print("target_compile_features(%s PRIVATE %s)", target:name(), feature) end end @@ -615,7 +701,7 @@ function _add_target_vs_runtime(cmakelists, target) end -- add target link libraries -function _add_target_link_libraries(cmakelists, target) +function _add_target_link_libraries(cmakelists, target, outputdir) -- add links local links = _get_configs_from_target(target, "links") @@ -634,6 +720,25 @@ function _add_target_link_libraries(cmakelists, target) end cmakelists:print(")") end + + -- add other object files, maybe from custom rules + local objectfiles_set = hashset.new() + for _, sourcebatch in table.orderpairs(target:sourcebatches()) do + if _sourcebatch_is_built(sourcebatch) then + for _, objectfile in ipairs(sourcebatch.objectfiles) do + objectfiles_set:insert(objectfile) + end + end + end + if #target:objectfiles() > objectfiles_set:size() then + cmakelists:print("target_link_libraries(%s PRIVATE", target:name()) + for _, objectfile in ipairs(target:objectfiles()) do + if not objectfiles_set:has(objectfile) then + cmakelists:print(" " .. _get_unix_path_relative_to_cmake(objectfile, outputdir)) + end + end + cmakelists:print(")") + end end -- add target link directories @@ -699,12 +804,7 @@ function _get_command_string(cmd, outputdir) -- @see https://github.com/xmake-io/xmake/discussions/2156 local argv = {} for _, v in ipairs(cmd.argv) do - if path.instance_of(v) then - v = v:clone():set(_get_unix_path_relative_to_cmake(v:rawstr(), outputdir)):str() - elseif path.is_absolute(v) then - v = _get_unix_path_relative_to_cmake(v, outputdir) - end - table.insert(argv, v) + table.insert(argv, _translate_flag(v, outputdir)) end local command = _get_unix_path_relative_to_cmake(cmd.program) .. " " .. os.args(argv) if opt and opt.curdir then @@ -729,12 +829,12 @@ function _get_command_string(cmd, outputdir) elseif kind == "mkdir" then return string.format("${CMAKE_COMMAND} -E make_directory %s", _get_unix_path_relative_to_cmake(cmd.dir, outputdir)) elseif kind == "show" then - return string.format("echo %s", cmd.showtext) + return string.format("echo %s", colors.ignore(cmd.showtext)) end end --- add custom command -function _add_target_custom_command(cmakelists, target, command, suffix) +-- add target custom commands for batchcmds +function _add_target_custom_commands_for_batchcmds(cmakelists, target, outputdir, suffix, batchcmds) if suffix == "before" then -- ADD_CUSTOM_COMMAND and PRE_BUILD did not work as I expected, -- so we need use add_dependencies and fake target to support it. @@ -743,7 +843,12 @@ function _add_target_custom_command(cmakelists, target, command, suffix) -- local key = target:name() .. "_" .. hash.uuid():split("-", {plain = true})[1] cmakelists:print("add_custom_command(OUTPUT output_%s", key) - cmakelists:print(" COMMAND %s", command) + for _, cmd in ipairs(batchcmds:cmds()) do + local command = _get_command_string(cmd, outputdir) + if command then + cmakelists:print(" COMMAND %s", command) + end + end cmakelists:print(" VERBATIM") cmakelists:print(")") cmakelists:print("add_custom_target(target_%s", key) @@ -755,7 +860,12 @@ function _add_target_custom_command(cmakelists, target, command, suffix) if suffix == "after" then cmakelists:print(" POST_BUILD") end - cmakelists:print(" COMMAND %s", command) + for _, cmd in ipairs(batchcmds:cmds()) do + local command = _get_command_string(cmd, outputdir) + if command then + cmakelists:print(" COMMAND %s", command) + end + end cmakelists:print(" VERBATIM") cmakelists:print(")") end @@ -770,12 +880,7 @@ function _add_target_custom_commands_for_target(cmakelists, target, outputdir, s local batchcmds_ = batchcmds.new({target = target}) script(target, batchcmds_, {}) if not batchcmds_:empty() then - for _, cmd in ipairs(batchcmds_:cmds()) do - local command = _get_command_string(cmd, outputdir) - if command then - _add_target_custom_command(cmakelists, target, command, suffix) - end - end + _add_target_custom_commands_for_batchcmds(cmakelists, target, outputdir, suffix, batchcmds_) end end end @@ -795,12 +900,7 @@ function _add_target_custom_commands_for_objectrules(cmakelists, target, sourceb local batchcmds_ = batchcmds.new({target = target}) script(target, batchcmds_, sourcebatch, {}) if not batchcmds_:empty() then - for _, cmd in ipairs(batchcmds_:cmds()) do - local command = _get_command_string(cmd, outputdir) - if command then - _add_target_custom_command(cmakelists, target, command, suffix) - end - end + _add_target_custom_commands_for_batchcmds(cmakelists, target, outputdir, suffix, batchcmds_) end end @@ -814,12 +914,7 @@ function _add_target_custom_commands_for_objectrules(cmakelists, target, sourceb local batchcmds_ = batchcmds.new({target = target}) script(target, batchcmds_, sourcefile, {}) if not batchcmds_:empty() then - for _, cmd in ipairs(batchcmds_:cmds()) do - local command = _get_command_string(cmd, outputdir) - if command then - _add_target_custom_command(cmakelists, target, command, suffix) - end - end + _add_target_custom_commands_for_batchcmds(cmakelists, target, outputdir, suffix, batchcmds_) end end end @@ -830,8 +925,7 @@ end function _add_target_custom_commands(cmakelists, target, outputdir) _add_target_custom_commands_for_target(cmakelists, target, outputdir, "before") for _, sourcebatch in table.orderpairs(target:sourcebatches()) do - local sourcekind = sourcebatch.sourcekind - if sourcekind ~= "cc" and sourcekind ~= "cxx" and sourcekind ~= "as" then + if not _sourcebatch_is_built(sourcebatch) then _add_target_custom_commands_for_objectrules(cmakelists, target, sourcebatch, outputdir, "before") _add_target_custom_commands_for_objectrules(cmakelists, target, sourcebatch, outputdir) _add_target_custom_commands_for_objectrules(cmakelists, target, sourcebatch, outputdir, "after") @@ -885,6 +979,10 @@ function _add_target(cmakelists, target, outputdir) -- add target dependencies _add_target_dependencies(cmakelists, target) + -- add target custom commands + -- we need call it first for running all rules, these rules will change some flags, e.g. c++modules + _add_target_custom_commands(cmakelists, target, outputdir) + -- add target precompilied header _add_target_precompiled_header(cmakelists, target, outputdir) @@ -900,11 +998,8 @@ function _add_target(cmakelists, target, outputdir) -- add target compile definitions _add_target_compile_definitions(cmakelists, target) - -- add target language standards - _add_target_language_standards(cmakelists, target) - -- add target compile options - _add_target_compile_options(cmakelists, target) + _add_target_compile_options(cmakelists, target, outputdir) -- add target warnings _add_target_warnings(cmakelists, target) @@ -922,7 +1017,7 @@ function _add_target(cmakelists, target, outputdir) _add_target_vs_runtime(cmakelists, target) -- add target link libraries - _add_target_link_libraries(cmakelists, target) + _add_target_link_libraries(cmakelists, target, outputdir) -- add target link directories _add_target_link_directories(cmakelists, target, outputdir) @@ -930,9 +1025,6 @@ function _add_target(cmakelists, target, outputdir) -- add target link options _add_target_link_options(cmakelists, target) - -- add target custom commands - _add_target_custom_commands(cmakelists, target, outputdir) - -- add target sources _add_target_sources(cmakelists, target, outputdir) diff --git a/xmake/plugins/project/make/makefile.lua b/xmake/plugins/project/make/makefile.lua index 7b0ec8f60..87c3b6f55 100644 --- a/xmake/plugins/project/make/makefile.lua +++ b/xmake/plugins/project/make/makefile.lua @@ -192,10 +192,18 @@ end -- make objects function _make_objects(makefile, target, sourcekind, sourcebatch, sourceflags) - - -- make them + local handled_objects = target:data("makefile.handled_objects") + if not handled_objects then + handled_objects = {} + target:data_set("makefile.handled_objects", handled_objects) + end for index, objectfile in ipairs(sourcebatch.objectfiles) do - _make_object(makefile, target, sourcebatch.sourcefiles[index], objectfile, sourceflags) + -- remove repeat + -- this is because some rules will repeatedly bind the same sourcekind, e.g. `rule("c++.build.modules.builder")` + if not handled_objects[objectfile] then + _make_object(makefile, target, sourcebatch.sourcefiles[index], objectfile, sourceflags) + handled_objects[objectfile] = true + end end end diff --git a/xmake/plugins/project/vstudio/impl/vs201x.lua b/xmake/plugins/project/vstudio/impl/vs201x.lua index ce07549c0..5d0e730d9 100644 --- a/xmake/plugins/project/vstudio/impl/vs201x.lua +++ b/xmake/plugins/project/vstudio/impl/vs201x.lua @@ -20,6 +20,7 @@ -- imports import("core.base.option") +import("core.base.colors") import("core.project.rule") import("core.project.config") import("core.project.project") @@ -109,7 +110,7 @@ function _get_command_string(cmd, vcxprojdir) local dir = _translate_path(cmd.dir, vcxprojdir) return string.format("if not exist \"%s\" mkdir \"%s\"", dir, dir) elseif kind == "show" then - return string.format("echo %s", cmd.showtext) + return string.format("echo %s", colors.ignore(cmd.showtext)) end end @@ -213,8 +214,9 @@ function _make_custom_commands(target, vcxprojdir) _make_custom_commands_for_target(commands, target, vcxprojdir, "before") _make_custom_commands_for_target(commands, target, vcxprojdir) for _, sourcebatch in pairs(target:sourcebatches()) do + local rulename = sourcebatch.rulename local sourcekind = sourcebatch.sourcekind - if sourcekind ~= "cc" and sourcekind ~= "cxx" and sourcekind ~= "as" then + if rulename ~= "c.build" and rulename ~= "c++.build" and rulename ~= "asm.build" and rulename ~= "cuda.build" and sourcekind ~= "mrc" then _make_custom_commands_for_objectrules(commands, target, sourcebatch, vcxprojdir, "before") _make_custom_commands_for_objectrules(commands, target, sourcebatch, vcxprojdir, nil) _make_custom_commands_for_objectrules(commands, target, sourcebatch, vcxprojdir, "after") @@ -281,10 +283,11 @@ function _make_targetinfo(mode, arch, target, vcxprojdir) targetinfo.compargvs = {} for _, sourcebatch in pairs(target:sourcebatches()) do local sourcekind = sourcebatch.sourcekind + local rulename = sourcebatch.rulename if sourcekind then for idx, sourcefile in ipairs(sourcebatch.sourcefiles) do - local compflags = compiler.compflags(sourcefile, {target = target}) - if not firstcompflags and (sourcekind == "cc" or sourcekind == "cxx" or sourcekind == "cu") then + local compflags = compiler.compflags(sourcefile, {target = target, sourcekind = sourcekind}) + if not firstcompflags and (rulename == "c.build" or rulename == "c++.build" or rulename == "cuda.build") then firstcompflags = compflags end targetinfo.compflags[sourcefile] = compflags @@ -541,6 +544,7 @@ function make(outputdir, vsinfo) localcache.clear("option") localcache.clear("package") localcache.clear("toolchain") + localcache.clear("cxxmodules") -- check platform platform.load(config.plat(), arch):check() diff --git a/xmake/plugins/project/vstudio/impl/vs201x_vcxproj.lua b/xmake/plugins/project/vstudio/impl/vs201x_vcxproj.lua index 452292abc..f9842bff1 100644 --- a/xmake/plugins/project/vstudio/impl/vs201x_vcxproj.lua +++ b/xmake/plugins/project/vstudio/impl/vs201x_vcxproj.lua @@ -713,18 +713,18 @@ function _make_custom_commands_item(vcxprojfile, commands, suffix) vcxprojfile:print("<PreLinkEvent>") end vcxprojfile:print("<Message></Message>") - vcxprojfile:print("<Command>setlocal") + local cmdstr = "setlocal" for _, command in ipairs(commands) do - vcxprojfile:print("%s", command) + cmdstr = cmdstr .. "\n" .. command + cmdstr = cmdstr .. "\nif %errorlevel% neq 0 goto :xmEnd" end - vcxprojfile:write([[if %errorlevel% neq 0 goto :xmEnd -:xmEnd + cmdstr = cmdstr .. "\n" .. [[:xmEnd endlocal & call :xmErrorLevel %errorlevel% & goto :xmDone :xmErrorLevel exit /b %1 :xmDone -if %errorlevel% neq 0 goto :VCEnd</Command> -]]) +if %errorlevel% neq 0 goto :VCEnd]] + vcxprojfile:print("<Command>%s</Command>", cmdstr) if suffix == "after" or suffix == "after_link" then vcxprojfile:print("</PostBuildEvent>") elseif suffix == "before" then @@ -957,7 +957,8 @@ function _build_common_items(vsinfo, target) targetinfo.sourceflags = {} for _, sourcebatch in pairs(targetinfo.sourcebatches) do local sourcekind = sourcebatch.sourcekind - if (sourcekind == "cc" or sourcekind == "cxx" or sourcekind == "as" or sourcekind == "mrc") then + local rulename = sourcebatch.rulename + if (rulename == "c.build" or rulename == "c++.build" or rulename == "asm.build" or sourcekind == "mrc") then for _, sourcefile in ipairs(sourcebatch.sourcefiles) do -- make compiler flags @@ -1020,12 +1021,13 @@ function _build_common_items(vsinfo, target) local sourceflags = {} for _, sourcebatch in pairs(targetinfo.sourcebatches) do local sourcekind = sourcebatch.sourcekind + local rulename = sourcebatch.rulename if (sourcekind == "as" or sourcekind == "mrc") then -- no common flags for as/mrc files for _, sourcefile in ipairs(sourcebatch.sourcefiles) do sourceflags[sourcefile] = targetinfo.sourceflags[sourcefile] end - elseif (sourcekind == "cc" or sourcekind == "cxx") then + elseif rulename == "c.build" or rulename == "c++.build" then -- sourcekind maybe bind multiple rules, e.g. c++modules for _, sourcefile in ipairs(sourcebatch.sourcefiles) do local flags = targetinfo.sourceflags[sourcefile] local otherflags = {} @@ -1313,7 +1315,8 @@ function _make_source_files(vcxprojfile, vsinfo, target) for _, targetinfo in ipairs(target.info) do for _, sourcebatch in pairs(targetinfo.sourcebatches) do local sourcekind = sourcebatch.sourcekind - if (sourcekind == "cc" or sourcekind == "cxx" or sourcekind == "as" or sourcekind == "mrc" or sourcekind == "cu") then + local rulename = sourcebatch.rulename + if (rulename == "c.build" or rulename == "c++.build" or sourcekind == "as" or sourcekind == "mrc" or sourcekind == "cu") then local objectfiles = sourcebatch.objectfiles for idx, sourcefile in ipairs(sourcebatch.sourcefiles) do local objectfile = objectfiles[idx] diff --git a/xmake/plugins/project/vsxmake/getinfo.lua b/xmake/plugins/project/vsxmake/getinfo.lua index e45947482..49bbfe745 100644 --- a/xmake/plugins/project/vsxmake/getinfo.lua +++ b/xmake/plugins/project/vsxmake/getinfo.lua @@ -468,6 +468,7 @@ function main(outputdir, vsinfo) localcache.clear("option") localcache.clear("package") localcache.clear("toolchain") + localcache.clear("cxxmodules") -- check platform platform.load(config.plat(), arch):check() diff --git a/xmake/rules/c++/modules/build_modules/clang.lua b/xmake/rules/c++/modules/build_modules/clang.lua deleted file mode 100644 index 120e1d5d0..000000000 --- a/xmake/rules/c++/modules/build_modules/clang.lua +++ /dev/null @@ -1,128 +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, TBOOX Open Source Group. --- --- @author ruki --- @file clang.lua --- - --- imports -import("core.tool.compiler") -import("core.project.config") -import("private.action.build.object", {alias = "objectbuilder"}) -import("module_parser") - --- load parent target with modules files -function load_parent(target, opt) - -- get modules flag - local modulesflag - local compinst = compiler.load("cxx", {target = target}) - if compinst:has_flags("-fmodules") then - modulesflag = "-fmodules" - elseif compinst:has_flags("-fmodules-ts") then - modulesflag = "-fmodules-ts" - end - assert(modulesflag, "compiler(clang): does not support c++ module!") - - -- add module flags - target:add("cxxflags", modulesflag) - - -- add the module cache directory - local cachedir = path.join(config.buildir(), ".gens", "rules", "modules", "cache") - target:add("cxxflags", "-fmodules-cache-path=" .. cachedir, {force = true}) - target:add("cxxflags", "-fimplicit-modules", "-fimplicit-module-maps", "-fprebuilt-module-path=" .. cachedir, {force = true}) -end - --- build module files -function build_with_batchjobs(target, batchjobs, sourcebatch, opt) - - -- get modules flag - local modulesflag - local compinst = compiler.load("cxx", {target = target}) - if compinst:has_flags("-fmodules") then - modulesflag = "-fmodules" - elseif compinst:has_flags("-fmodules-ts") then - modulesflag = "-fmodules-ts" - end - assert(modulesflag, "compiler(clang): does not support c++ module!") - - -- get the module cache directory, @note we must use same cache directory for each targets - -- @see https://github.com/xmake-io/xmake/issues/2194 - -- - local cachedir = path.join(config.buildir(), ".gens", "rules", "modules", "cache") - - -- we need patch objectfiles to sourcebatch for linking module objects - sourcebatch.sourcekind = "cxx" - sourcebatch.objectfiles = sourcebatch.objectfiles or {} - sourcebatch.dependfiles = sourcebatch.dependfiles or {} - for _, sourcefile in ipairs(sourcebatch.sourcefiles) do - local objectfile = target:objectfile(sourcefile) - table.insert(sourcebatch.objectfiles, objectfile) - table.insert(sourcebatch.dependfiles, target:dependfile(objectfile)) - end - - -- load moduledeps - local moduledeps, moduledeps_files = module_parser.load(target, sourcebatch, opt) - - -- compile module files to object files - local count = 0 - local modulefiles = {} - local sourcefiles_total = #sourcebatch.sourcefiles - for i = 1, sourcefiles_total do - local sourcefile = sourcebatch.sourcefiles[i] - local moduleinfo = moduledeps_files[sourcefile] or {} - - -- make module file path, @note we need process submodule name, e.g. module.submodule.mpp -> module.submodule.pcm - -- @see https://github.com/xmake-io/xmake/pull/1982 - local modulefile = path.join(cachedir, (moduleinfo.name or path.basename(sourcefile)) .. ".pcm") - table.insert(modulefiles, modulefile) - - -- make build job - moduleinfo.job = batchjobs:newjob(sourcefile, function (index, total) - - -- compile module files to *.pcm - local opt2 = table.join(opt, {configs = {force = {cxxflags = {modulesflag, - "-fimplicit-modules", "-fimplicit-module-maps", "-fprebuilt-module-path=" .. cachedir, - "--precompile", "-x c++-module", "-fmodules-cache-path=" .. cachedir}}}}) - opt2.progress = (index * 100) / total - opt2.objectfile = modulefiles[i] - opt2.dependfile = target:dependfile(opt2.objectfile) - opt2.sourcekind = assert(sourcebatch.sourcekind, "%s: sourcekind not found!", sourcefile) - objectbuilder.build_object(target, sourcefile, opt2) - - -- compile *.pcm to object files - opt2.configs = {force = {cxxflags = {modulesflag, "-fmodules-cache-path=" .. cachedir, - "-fimplicit-modules", "-fimplicit-module-maps", "-fprebuilt-module-path=" .. cachedir}}} - opt2.quiet = true - opt2.objectfile = sourcebatch.objectfiles[i] - opt2.dependfile = sourcebatch.dependfiles[i] - objectbuilder.build_object(target, modulefiles[i], opt2) - - -- add module flags to other c++ files after building all modules - count = count + 1 - if count == sourcefiles_total then - target:add("cxxflags", modulesflag, "-fmodules-cache-path=" .. cachedir, {force = true}) - -- FIXME It is invalid for the module implementation unit - --target:add("cxxflags", "-fimplicit-modules", "-fimplicit-module-maps", "-fprebuilt-module-path=" .. cachedir, {force = true}) - for _, modulefile in ipairs(modulefiles) do - target:add("cxxflags", "-fmodule-file=" .. modulefile, {force = true}) - end - end - end) - end - - -- build batchjobs - module_parser.build_batchjobs(moduledeps, batchjobs, opt.rootjob) -end diff --git a/xmake/rules/c++/modules/build_modules/gcc.lua b/xmake/rules/c++/modules/build_modules/gcc.lua deleted file mode 100644 index 3bf8b8fcc..000000000 --- a/xmake/rules/c++/modules/build_modules/gcc.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, TBOOX Open Source Group. --- --- @author ruki --- @file gcc.lua --- - --- imports -import("core.tool.compiler") -import("private.action.build.object", {alias = "objectbuilder"}) -import("module_parser") - --- load parent target with modules files -function load_parent(target, opt) - -- get modules flag - local modulesflag - local compinst = compiler.load("cxx", {target = target}) - if compinst:has_flags("-fmodules-ts") then - modulesflag = "-fmodules-ts" - end - assert(modulesflag, "compiler(gcc): does not support c++ module!") - - -- add module flags - target:add("cxxflags", modulesflag) -end - --- build module files -function build_with_batchjobs(target, batchjobs, sourcebatch, opt) - - -- get modules flag - local modulesflag - local compinst = compiler.load("cxx", {target = target}) - if compinst:has_flags("-fmodules-ts") then - modulesflag = "-fmodules-ts" - end - assert(modulesflag, "compiler(gcc): does not support c++ module!") - - -- we need patch objectfiles to sourcebatch for linking module objects - sourcebatch.sourcekind = "cxx" - sourcebatch.objectfiles = sourcebatch.objectfiles or {} - sourcebatch.dependfiles = sourcebatch.dependfiles or {} - for _, sourcefile in ipairs(sourcebatch.sourcefiles) do - local objectfile = target:objectfile(sourcefile) - table.insert(sourcebatch.objectfiles, objectfile) - table.insert(sourcebatch.dependfiles, target:dependfile(objectfile)) - end - - -- load moduledeps - local moduledeps, moduledeps_files = module_parser.load(target, sourcebatch, opt) - - -- compile module files to object files - for i = 1, #sourcebatch.sourcefiles do - local sourcefile = sourcebatch.sourcefiles[i] - local moduleinfo = assert(moduledeps_files[sourcefile], "moduleinfo(%s) not found!", sourcefile) - moduleinfo.job = batchjobs:newjob(sourcefile, function (index, total) - local opt2 = table.join(opt, {configs = {force = {cxxflags = {"-x c++"}}}}) - opt2.progress = (index * 100) / total - opt2.objectfile = sourcebatch.objectfiles[i] - opt2.dependfile = sourcebatch.dependfiles[i] - opt2.sourcekind = assert(sourcebatch.sourcekind, "%s: sourcekind not found!", sourcefile) - objectbuilder.build_object(target, sourcefile, opt2) - end) - end - - -- add module flags - target:add("cxxflags", modulesflag) - - -- build batchjobs - module_parser.build_batchjobs(moduledeps, batchjobs, opt.rootjob) -end - diff --git a/xmake/rules/c++/modules/build_modules/module_parser.lua b/xmake/rules/c++/modules/build_modules/module_parser.lua deleted file mode 100644 index 91a09aadb..000000000 --- a/xmake/rules/c++/modules/build_modules/module_parser.lua +++ /dev/null @@ -1,149 +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, TBOOX Open Source Group. --- --- @author ruki --- @file module_parser.lua --- - --- imports -import("core.project.depend") -import("core.base.hashset") - --- get depend file of module source file -function _get_dependfile_of_modulesource(target, sourcefile) - return target:dependfile(sourcefile) -end - --- get depend file of module object file, compiler will rewrite it -function _get_dependfile_of_moduleobject(target, sourcefile) - local objectfile = target:objectfile(sourcefile) - return target:dependfile(objectfile) -end - --- generate module deps for the given file -function _generate_moduledeps(target, sourcefile, opt) - local dependfile = _get_dependfile_of_modulesource(target, sourcefile) - depend.on_changed(function () - - -- trace - vprint("generating.moduledeps %s", sourcefile) - - -- generating deps - local module_name - local module_deps - local sourcecode = io.readfile(sourcefile) - sourcecode = sourcecode:gsub("//.-\n", "\n") - sourcecode = sourcecode:gsub("/%*.-%*/", "") - for _, line in ipairs(sourcecode:split("\n", {plain = true})) do - if not module_name then - module_name = line:match("export%s+module%s+(.+)%s*;") - end - local module_depname = line:match("import%s+(.+)%s*;") - if module_depname then - -- partition? import :xxx; - if module_depname:startswith(":") then - module_depname = module_name .. module_depname - end - module_deps = module_deps or {} - table.insert(module_deps, module_depname) - end - end - - -- save depend data - if module_name then - local dependinfo = {moduleinfo = {name = module_name, deps = module_deps, file = sourcefile}} - return dependinfo - end - - end, {dependfile = dependfile, files = {sourcefile}}) -end - --- build batch jobs with deps -function _build_batchjobs_with_deps(moduledeps, batchjobs, rootjob, jobrefs, moduleinfo) - local targetjob_ref = jobrefs[moduleinfo.name] - if targetjob_ref then - batchjobs:add(targetjob_ref, rootjob) - else - local modulejob = batchjobs:add(moduleinfo.job, rootjob) - if modulejob then - jobrefs[moduleinfo.name] = modulejob - for _, depname in ipairs(moduleinfo.deps) do - local dep = moduledeps[depname] - if dep then -- maybe nil, e.g. `import <string>;` - _build_batchjobs_with_deps(moduledeps, batchjobs, modulejob, jobrefs, dep) - end - end - end - end -end - --- generate module deps -function generate(target, sourcebatch, opt) - for _, sourcefile in ipairs(sourcebatch.sourcefiles) do - _generate_moduledeps(target, sourcefile, opt) - end -end - --- load module deps -function load(target, sourcebatch, opt) - - -- do generate first - generate(target, sourcebatch, opt) - - -- load deps - local moduledeps - for _, sourcefile in ipairs(sourcebatch.sourcefiles) do - local dependfile = _get_dependfile_of_modulesource(target, sourcefile) - if os.isfile(dependfile) then - local data = io.load(dependfile) - if data then - local moduleinfo = data.moduleinfo - if moduleinfo then - moduledeps = moduledeps or {} - moduledeps[moduleinfo.name] = moduleinfo - end - end - end - end - - -- get moduledeps with file map - local moduledeps_files = {} - for _, moduleinfo in pairs(moduledeps) do - moduledeps_files[moduleinfo.file] = moduleinfo - end - return moduledeps, moduledeps_files -end - --- build batch jobs -function build_batchjobs(moduledeps, batchjobs, rootjob) - local depset = hashset.new() - for _, moduleinfo in pairs(moduledeps) do - assert(moduleinfo.job) - for _, depname in ipairs(moduleinfo.deps) do - depset:insert(depname) - end - end - local moduledeps_root = {} - for _, moduleinfo in pairs(moduledeps) do - if not depset:has(moduleinfo.name) then - table.insert(moduledeps_root, moduleinfo) - end - end - local jobrefs = {} - for _, moduleinfo in pairs(moduledeps_root) do - _build_batchjobs_with_deps(moduledeps, batchjobs, rootjob, jobrefs, moduleinfo) - end -end diff --git a/xmake/rules/c++/modules/build_modules/msvc.lua b/xmake/rules/c++/modules/build_modules/msvc.lua deleted file mode 100644 index 0a8976468..000000000 --- a/xmake/rules/c++/modules/build_modules/msvc.lua +++ /dev/null @@ -1,178 +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, TBOOX Open Source Group. --- --- @author ruki --- @file msvc.lua --- - --- imports -import("core.tool.compiler") -import("private.action.build.object", {alias = "objectbuilder"}) -import("module_parser") - --- load parent target with modules files -function load_parent(target, opt) - - -- get modules flag - local modulesflag - local compinst = compiler.load("cxx", {target = target}) - if compinst:has_flags("/experimental:module", "cxxflags") then - modulesflag = "/experimental:module" - end - assert(modulesflag, "compiler(msvc): does not support c++ module!") - - -- add module flags - target:add("cxxflags", modulesflag) - - -- get output flag - if compinst:has_flags("/ifcOutput", "cxxflags") then - for _, dep in ipairs(target:orderdeps()) do - local sourcebatches = dep:sourcebatches() - if sourcebatches and sourcebatches["c++.build.modules"] then - local cachedir = path.join(dep:autogendir(), "rules", "modules", "cache") - target:add("cxxflags", {"/ifcSearchDir", cachedir}, {force = true, expand = false}) - end - end - end -end - --- build module files -function build_with_batchjobs(target, batchjobs, sourcebatch, opt) - - -- get modules flag - local modulesflag - local compinst = compiler.load("cxx", {target = target}) - if compinst:has_flags("/experimental:module", "cxxflags") then - modulesflag = "/experimental:module" - end - assert(modulesflag, "compiler(msvc): does not support c++ module!") - - -- get output flag - local cachedir - local outputflag - if compinst:has_flags("/ifcOutput", "cxxflags") then - outputflag = "/ifcOutput" - cachedir = path.join(target:autogendir(), "rules", "modules", "cache") - if not os.isdir(cachedir) then - os.mkdir(cachedir) - end - elseif compinst:has_flags("/module:output", "cxxflags") then - outputflag = "/module:output" - end - assert(outputflag, "compiler(msvc): does not support c++ module!") - - -- get interface flag - local interfaceflag - if compinst:has_flags("/interface", "cxxflags") then - interfaceflag = "/interface" - elseif compinst:has_flags("/module:interface", "cxxflags") then - interfaceflag = "/module:interface" - end - assert(interfaceflag, "compiler(msvc): does not support c++ module!") - - -- get reference flag - local referenceflag - if compinst:has_flags("/reference", "cxxflags") then - referenceflag = "/reference" - elseif compinst:has_flags("/module:interface", "cxxflags") then - referenceflag = "/module:reference" - end - assert(referenceflag, "compiler(msvc): does not support c++ module!") - - -- get stdifcdir flag - local stdifcdirflag - if compinst:has_flags("/stdIfcDir", "cxxflags") then - stdifcdirflag = "/stdIfcDir" - elseif compinst:has_flags("/module:stdIfcDir", "cxxflags") then - stdifcdirflag = "/module:stdIfcDir" - end - assert(stdifcdirflag, "compiler(msvc): does not support c++ module!") - - -- we need patch objectfiles to sourcebatch for linking module objects - sourcebatch.sourcekind = "cxx" - sourcebatch.objectfiles = sourcebatch.objectfiles or {} - sourcebatch.dependfiles = sourcebatch.dependfiles or {} - for _, sourcefile in ipairs(sourcebatch.sourcefiles) do - local objectfile = target:objectfile(sourcefile) - local dependfile = target:dependfile(objectfile) - table.insert(sourcebatch.objectfiles, objectfile) - table.insert(sourcebatch.dependfiles, dependfile) - end - - -- load moduledeps - local moduledeps, moduledeps_files = module_parser.load(target, sourcebatch, opt) - - -- get modulefiles - local modulefiles = {} - for _, sourcefile in ipairs(sourcebatch.sourcefiles) do - local moduleinfo = moduledeps_files[sourcefile] or {} - -- make module file path, @note we need process submodule name, e.g. module.submodule.mpp -> module.submodule.ifc - -- @see https://github.com/xmake-io/xmake/issues/2614 - local modulefile = (cachedir and path.join(cachedir, moduleinfo.name or path.basename(sourcefile)) or objectfile) .. ".ifc" - table.insert(modulefiles, modulefile) - end - - -- compile module files to object files - local count = 0 - local sourcefiles_total = #sourcebatch.sourcefiles - for i = 1, sourcefiles_total do - local sourcefile = sourcebatch.sourcefiles[i] - local moduleinfo = assert(moduledeps_files[sourcefile], "moduleinfo(%s) not found!", sourcefile) - moduleinfo.job = batchjobs:newjob(sourcefile, function (index, total) - local opt2 = table.join(opt, {configs = {force = {cxxflags = { - interfaceflag, - {outputflag, modulefiles[i]}, - "/TP"}}}}) - opt2.progress = (index * 100) / total - opt2.objectfile = sourcebatch.objectfiles[i] - opt2.dependfile = sourcebatch.dependfiles[i] - opt2.sourcekind = assert(sourcebatch.sourcekind, "%s: sourcekind not found!", sourcefile) - objectbuilder.build_object(target, sourcefile, opt2) - - -- add module flags to other c++ files after building all modules - count = count + 1 - if count == sourcefiles_total and not cachedir then - for _, modulefile in ipairs(modulefiles) do - target:add("cxxflags", {referenceflag, modulefile}, {force = true, expand = false}) - end - end - end) - end - - -- add module flags - target:add("cxxflags", modulesflag) - if cachedir then - target:add("cxxflags", {"/ifcSearchDir", cachedir}, {force = true, expand = false}) - end - if stdifcdirflag then - for _, toolchain_inst in ipairs(target:toolchains()) do - if toolchain_inst:name() == "msvc" then - local vcvars = toolchain_inst:config("vcvars") - if vcvars.VCInstallDir and vcvars.VCToolsVersion then - local stdifcdir = path.join(vcvars.VCInstallDir, "Tools", "MSVC", vcvars.VCToolsVersion, "ifc", target:is_arch("x64") and "x64" or "x86") - if os.isdir(stdifcdir) then - target:add("cxxflags", {stdifcdirflag, winos.short_path(stdifcdir)}, {force = true, expand = false}) - end - end - break - end - end - end - - -- build batchjobs - module_parser.build_batchjobs(moduledeps, batchjobs, opt.rootjob) -end - diff --git a/xmake/rules/c++/modules/modules_support/clang.lua b/xmake/rules/c++/modules/modules_support/clang.lua new file mode 100644 index 000000000..fb3a2d1fa --- /dev/null +++ b/xmake/rules/c++/modules/modules_support/clang.lua @@ -0,0 +1,603 @@ +--!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, TBOOX Open Source Group. +-- +-- @author ruki +-- @file clang.lua +-- + +-- imports +import("core.tool.compiler") +import("core.project.project") +import("core.project.depend") +import("core.project.config") +import("utils.progress") +import("private.action.build.object", {alias = "objectbuilder"}) +import("common") +import("stl_headers") + +-- add a module or an header unit into the mapper +-- +-- e.g +-- -fmodule-file=build/.gens/Foo/rules/modules/cache/foo.pcm +-- -fmodule-file=build/.gens/Foo/rules/modules/cache/iostream.pcm +-- -fmodule-file=build/.gens/Foo/rules/modules/cache/bar.hpp.pcm +-- +function _add_module_to_mapper(target, name, bmifile, deps) + local modulemap = _get_modulemap_from_mapper(target) + if modulemap[name] then + return + end + + local modulefileflag = get_modulefileflag(target) + local mapflag = format("%s%s", modulefileflag, bmifile) + modulemap[name] = {flag = mapflag, deps = deps} + common.localcache():set2(_mapper_cachekey(target), "modulemap", modulemap) +end + +function _mapper_cachekey(target) + return target:name() .. "_modulemap" +end + +-- flush modulemap to mapper file cache +function _flush_mapper(target) + -- not using set2/get2 to flush only current target mapper + common.localcache():save(_mapper_cachekey(target)) +end + +-- get modulemap from mapper +function _get_modulemap_from_mapper(target) + return common.localcache():get2(_mapper_cachekey(target), "modulemap") or {} +end + +-- load module support for the current target +function load(target) + -- get module and module cache flags + local modulesflag = get_modulesflag(target) + local builtinmodulemapflag = get_builtinmodulemapflag(target) + local implicitmodulesflag = get_implicitmodulesflag(target) + local noimplicitmodulemapsflag = get_noimplicitmodulemapsflag(target) + + -- add module flags + target:add("cxxflags", modulesflag) + + -- add the module cache directory + target:add("cxxflags", builtinmodulemapflag, {force = true}) + target:add("cxxflags", implicitmodulesflag, {force = true}) + target:add("cxxflags", noimplicitmodulemapsflag, {force = true}) + + target:data_set("cxx.modules.use_libc++", table.contains(target:get("cxxflags"), "-stdlib=libc++")) +end + +-- get includedirs for stl headers +-- +-- $ echo '#include <vector>' | clang -x c++ -E - | grep '/vector"' +-- # 1 "/usr/include/c++/11/vector" 1 3 +-- # 58 "/usr/include/c++/11/vector" 3 +-- # 59 "/usr/include/c++/11/vector" 3 +-- +function _get_toolchain_includedirs_for_stlheaders(includedirs, clang) + local tmpfile = os.tmpfile() .. ".cc" + io.writefile(tmpfile, "#include <vector>") + local result = try {function () return os.iorunv(clang, {"-E", "-x", "c++", tmpfile}) end} + if result then + for _, line in ipairs(result:split("\n", {plain = true})) do + line = line:trim() + if line:startswith("#") and line:find("/vector\"", 1, true) then + local includedir = line:match("\"(.+)/vector\"") + if includedir and os.isdir(includedir) then + table.insert(includedirs, path.normalize(includedir)) + break + end + end + end + end + os.tryrm(tmpfile) +end + +-- provide toolchain include directories for stl headerunit when p1689 is not supported +function toolchain_includedirs(target) + local includedirs = _g.includedirs + if includedirs == nil then + includedirs = {} + local clang, toolname = target:tool("cc") + assert(toolname == "clang") + _get_toolchain_includedirs_for_stlheaders(includedirs, clang) + local _, result = try {function () return os.iorunv(clang, {"-E", "-Wp,-v", "-xc", os.nuldev()}) end} + if result then + for _, line in ipairs(result:split("\n", {plain = true})) do + line = line:trim() + if os.isdir(line) then + table.insert(includedirs, path.normalize(line)) + elseif line:startswith("End") then + break + end + end + end + _g.includedirs = includedirs + end + return includedirs +end + +-- generate dependency files +function generate_dependencies(target, sourcebatch, opt) + local changed = false + local cachedir = common.modules_cachedir(target) + for _, sourcefile in ipairs(sourcebatch.sourcefiles) do + local dependfile = target:dependfile(sourcefile) + depend.on_changed(function() + if opt.progress then + progress.show(opt.progress, "${color.build.object}generating.cxx.module.deps %s", sourcefile) + end + + local outputdir = path.translate(path.join(cachedir, path.directory(path.relative(sourcefile, projectdir)))) + if not os.isdir(outputdir) then + os.mkdir(outputdir) + end + + -- no support of p1689 atm + local jsonfile = path.translate(path.join(outputdir, path.filename(sourcefile) .. ".json")) + common.fallback_generate_dependencies(target, jsonfile, sourcefile) + changed = true + + local dependinfo = io.readfile(jsonfile) + return { moduleinfo = dependinfo } + end, {dependfile = dependfile, files = {sourcefile}}) + end + return changed +end + +-- generate target stl header units for batchjobs +function generate_stl_headerunits_for_batchjobs(target, batchjobs, headerunits, opt) + local compinst = target:compiler("cxx") + local stlcachedir = common.stlmodules_cachedir(target) + local modulecachepathflag = get_modulecachepathflag(target) + assert(has_headerunitsupport(target), "compiler(clang): does not support c++ header units!") + + -- flush job + local flushjob = batchjobs:addjob(target:name() .. "_stl_headerunits_flush_mapper", function(index, total) + _flush_mapper(target) + end, {rootjob = opt.rootjob}) + + -- build headerunits + for i, headerunit in ipairs(headerunits) do + local bmifile = path.join(stlcachedir, headerunit.name .. get_bmi_extension()) + if not os.isfile(bmifile) then + batchjobs:addjob(headerunit.name, function (index, total) + depend.on_changed(function() + -- don't build same header unit at the same time + if not common.memcache():get2(headerunit.name, "building") then + common.memcache():set2(headerunit.name, "building", true) + progress.show((index * 100) / total, "${color.build.object}generating.cxx.headerunit.bmi %s", headerunit.name) + local args = {modulecachepathflag .. stlcachedir, "-c", "-o", bmifile, "-x", "c++-system-header", headerunit.name} + os.vrunv(compinst:program(), table.join(compinst:compflags({target = target}), args)) + end + + end, {dependfile = target:dependfile(bmifile), files = {headerunit.path}}) + -- libc++ have a builtin module mapper + if not target:data_set("cxx.modules.use_libc++") then + _add_module_to_mapper(target, headerunit.name, bmifile) + end + end, {rootjob = flushjob}) + end + end +end + +-- generate target stl header units for batchcmds +function generate_stl_headerunits_for_batchcmds(target, batchcmds, headerunits, opt) + local compinst = target:compiler("cxx") + local stlcachedir = common.stlmodules_cachedir(target) + local modulecachepathflag = get_modulecachepathflag(target) + assert(has_headerunitsupport(target), "compiler(clang): does not support c++ header units!") + + -- build headerunits + local projectdir = os.projectdir() + local depmtime = 0 + for i, headerunit in ipairs(headerunits) do + local bmifile = path.join(stlcachedir, headerunit.name .. get_bmi_extension()) + -- don't build same header unit at the same time + if not common.memcache():get2(headerunit.name, "building") then + common.memcache():set2(headerunit.name, "building", true) + local args = { + path(stlcachedir, function (p) return modulecachepathflag .. p end), + "-c", "-o", path(bmifile), "-x", "c++-system-header", headerunit.name} + batchcmds:show_progress(opt.progress, "${color.build.object}generating.cxx.headerunit.bmi %s", headerunit.name) + batchcmds:vrunv(compinst:program(), table.join(compinst:compflags({target = target}), args)) + end + -- libc++ have a builtin module mapper + if not target:data_set("cxx.modules.use_libc++") then + _add_module_to_mapper(target, headerunit.name, bmifile) + end + depmtime = math.max(depmtime, os.mtime(bmifile)) + end + batchcmds:set_depmtime(depmtime) + _flush_mapper(target) +end + +-- generate target user header units for batchjobs +function generate_user_headerunits_for_batchjobs(target, batchjobs, headerunits, opt) + local compinst = target:compiler("cxx") + assert(has_headerunitsupport(target), "compiler(clang): does not support c++ header units!") + + -- get cachedirs + local cachedir = common.modules_cachedir(target) + local modulecachepathflag = get_modulecachepathflag(target) + + -- flush job + local flushjob = batchjobs:addjob(target:name() .. "_user_headerunits_flush_mapper", function(index, total) + _flush_mapper(target) + end, {rootjob = opt.rootjob}) + + -- build headerunits + local projectdir = os.projectdir() + for _, headerunit in ipairs(headerunits) do + local file = path.relative(headerunit.path, target:scriptdir()) + local objectfile = target:objectfile(file) + + local outputdir + if headerunit.type == ":quote" then + outputdir = path.join(cachedir, path.directory(path.relative(headerunit.path, projectdir))) + else + outputdir = path.join(cachedir, path.directory(headerunit.path)) + end + local bmifilename = path.basename(objectfile) .. get_bmi_extension() + local bmifile = (outputdir and path.join(outputdir, bmifilename) or bmifilename) + batchjobs:addjob(headerunit.name, function (index, total) + depend.on_changed(function() + progress.show((index * 100) / total, "${color.build.object}generating.cxx.headerunit.bmi %s", headerunit.name) + local objectdir = path.directory(objectfile) + if not os.isdir(objectdir) then + os.mkdir(objectdir) + end + if not os.isdir(outputdir) then + os.mkdir(outputdir) + end + + -- generate headerunit + local args = { modulecachepathflag .. cachedir, "-c", "-o", bmifile} + if headerunit.type == ":quote" then + table.join2(args, {"-I", path.directory(headerunit.path), "-x", "c++-user-header", headerunit.path}) + elseif headerunit.type == ":angle" then + table.join2(args, {"-x", "c++-system-header", headerunit.name}) + end + os.vrunv(compinst:program(), table.join(compinst:compflags({target = target}), args)) + + end, {dependfile = target:dependfile(bmifile), files = {headerunit.path}}) + _add_module_to_mapper(target, headerunit.name, bmifile) + end, {rootjob = flushjob}) + end +end + +-- generate target user header units for batchcmds +function generate_user_headerunits_for_batchcmds(target, batchcmds, headerunits, opt) + local compinst = target:compiler("cxx") + assert(has_headerunitsupport(target), "compiler(clang): does not support c++ header units!") + + -- get cachedirs + local cachedir = common.modules_cachedir(target) + local modulecachepathflag = get_modulecachepathflag(target) + + -- build headerunits + local projectdir = os.projectdir() + local depmtime = 0 + for _, headerunit in ipairs(headerunits) do + local file = path.relative(headerunit.path, target:scriptdir()) + local objectfile = target:objectfile(file) + + local outputdir + if headerunit.type == ":quote" then + outputdir = path.join(cachedir, path.directory(path.relative(headerunit.path, projectdir))) + else + outputdir = path.join(cachedir, path.directory(headerunit.path)) + end + batchcmds:mkdir(outputdir) + + local bmifilename = path.basename(objectfile) .. get_bmi_extension() + local bmifile = (outputdir and path.join(outputdir, bmifilename) or bmifilename) + batchcmds:mkdir(path.directory(objectfile)) + + local args = {path(cachedir, function (p) return modulecachepathflag .. p end), "-c", "-o", path(bmifile)} + if headerunit.type == ":quote" then + table.join2(args, {"-I", path(headerunit.path):directory(), "-x", "c++-user-header", path(headerunit.path)}) + elseif headerunit.type == ":angle" then + table.join2(args, {"-x", "c++-system-header", headerunit.name}) + end + + batchcmds:show_progress(opt.progress, "${color.build.object}generating.cxx.headerunit.bmi %s", headerunit.name) + batchcmds:vrunv(compinst:program(), table.join(compinst:compflags({target = target}), args)) + batchcmds:add_depfiles(headerunit.path) + + _add_module_to_mapper(target, headerunit.name, bmifile) + + depmtime = math.max(depmtime, os.mtime(bmifile)) + end + batchcmds:set_depmtime(depmtime) + _flush_mapper(target) +end + +-- build module files for batchjobs +function build_modules_for_batchjobs(target, batchjobs, objectfiles, modules, opt) + local compinst = target:compiler("cxx") + local cachedir = common.modules_cachedir(target) + local modulecachepathflag = get_modulecachepathflag(target) + local modulefileflag = get_modulefileflag(target) + + -- flush job + local flushjob = batchjobs:addjob(target:name() .. "_stl_flush_mapper", function(index, total) + _flush_mapper(target) + end, {rootjob = opt.rootjob}) + + -- build modules + local common_args = {modulecachepathflag .. cachedir} + local modulesjobs = {} + for _, objectfile in ipairs(objectfiles) do + local module = modules[objectfile] + if module then + if module.provides then + -- assume there that provides is only one, until we encounter the case + local length = 0 + local name, provide + for k, v in pairs(module.provides) do + length = length + 1 + name = k + provide = v + if length > 1 then + raise("multiple provides are not supported now!") + end + end + + local bmifile = provide.bmi + local moduleinfo = table.copy(provide) + moduleinfo.job = batchjobs:newjob(provide.sourcefile, function (index, total) + -- append module mapper flags first + -- @note we add it at the end to ensure that the full modulemap are already stored in the mapper + local requiresflags + if module.requires then + requiresflags = get_requiresflags(target, module.requires) + end + + depend.on_changed(function() + progress.show((index * 100) / total, "${color.build.object}generating.cxx.module.bmi %s", name) + local bmidir = path.directory(bmifile) + if not os.isdir(bmidir) then + os.mkdir(bmidir) + end + local objectdir = path.directory(objectfile) + if not os.isdir(objectdir) then + os.mkdir(objectdir) + end + local args = { "-c", "-x", "c++-module", "--precompile", provide.sourcefile, "-o", bmifile} + os.vrunv(compinst:program(), table.join(compinst:compflags({target = target}), common_args, requiresflags or {}, args)) + os.vrunv(compinst:program(), table.join(compinst:compflags({target = target}), common_args, requiresflags or {}, {bmifile}, {"-c", "-o", objectfile})) + end, {dependfile = target:dependfile(bmifile), files = {provide.sourcefile}}) + _add_module_to_mapper(target, name, bmifile, requiresflags) + end) + if module.requires then + moduleinfo.deps = table.keys(module.requires) + end + moduleinfo.name = name + modulesjobs[name] = moduleinfo + target:add("objectfiles", objectfile) + else + if module.requires then + modulesjobs[module.cppfile] = { + name = module.cppfile, + deps = table.keys(module.requires), + sourcefile = module.cppfile, + job = batchjobs:newjob(module.cppfile, function(index, total) + -- append module mapper flags + -- @note we add it at the end to ensure that the full modulemap are already stored in the mapper + local requiresflags = get_requiresflags(target, module.requires) + if requiresflags then + target:fileconfig_add(module.cppfile, {force = {cxxflags = requiresflags}}) + end + end) + } + end + end + end + end + + -- build batchjobs for modules + common.build_batchjobs_for_modules(modulesjobs, batchjobs, flushjob) +end + +-- build module files for batchcmds +function build_modules_for_batchcmds(target, batchcmds, objectfiles, modules, opt) + local compinst = target:compiler("cxx") + local cachedir = common.modules_cachedir(target) + local modulecachepathflag = get_modulecachepathflag(target) + local modulefileflag = get_modulefileflag(target) + + -- build modules + local depmtime = 0 + local common_args = {path(cachedir, function (p) return modulecachepathflag .. p end)} + for _, objectfile in ipairs(objectfiles) do + local module = modules[objectfile] + if module then + if module.provides then + local name, provide + for k, v in pairs(module.provides) do + name = k + provide = v + break + end + local bmifile = provide.bmi + local args = {"-c", "-x", "c++-module", "--precompile", path(provide.sourcefile), "-o", path(bmifile)} + local requiresflags + if module.requires then + requiresflags = get_requiresflags(target, module.requires) + end + batchcmds:show_progress(opt.progress, "${color.build.object}generating.cxx.module.bmi %s", name) + batchcmds:mkdir(path.directory(objectfile)) + batchcmds:vrunv(compinst:program(), table.join(compinst:compflags({target = target}), common_args, requiresflags or {}, args)) + batchcmds:vrunv(compinst:program(), table.join(compinst:compflags({target = target}), common_args, requiresflags or {}, path(bmifile), {"-c", "-o", path(objectfile)})) + batchcmds:add_depfiles(provide.sourcefile) + _add_module_to_mapper(target, name, bmifile) + depmtime = math.max(depmtime, os.mtime(bmifile)) + else + if module.requires then + local requiresflags = get_requiresflags(target, module.requires) + if requiresflags then + target:fileconfig_add(module.cppfile, {force = {cxxflags = requiresflags}}) + end + end + end + end + end + batchcmds:set_depmtime(depmtime) + _flush_mapper(target) +end + +function get_bmi_extension() + return ".pcm" +end + +function get_modulesflag(target) + local modulesflag = _g.modulesflag + if modulesflag == nil then + local compinst = target:compiler("cxx") + if compinst:has_flags("-fmodules", "cxxflags", {flagskey = "clang_modules"}) then + modulesflag = "-fmodules" + end + if not modulesflag then + if compinst:has_flags("-fmodules-ts", "cxxflags", {flagskey = "clang_modules_ts"}) then + modulesflag = "-fmodules-ts" + end + end + assert(modulesflag, "compiler(clang): does not support c++ module!") + _g.modulesflag = modulesflag or false + end + return modulesflag or nil +end + +function get_builtinmodulemapflag(target) + local builtinmodulemapflag = _g.builtinmodulemapflag + if builtinmodulemapflag == nil then + local compinst = target:compiler("cxx") + if compinst:has_flags("-fbuiltin-module-map", "cxxflags", {flagskey = "clang_builtin_module_map"}) then + builtinmodulemapflag = "-fbuiltin-module-map" + end + assert(builtinmodulemapflag, "compiler(clang): does not support c++ module!") + _g.builtinmodulemapflag = builtinmodulemapflag or false + end + return builtinmodulemapflag or nil +end + +function get_implicitmodulesflag(target) + local implicitmodulesflag = _g.implicitmodulesflag + if implicitmodulesflag == nil then + local compinst = target:compiler("cxx") + if compinst:has_flags("-fimplicit-modules", "cxxflags", {flagskey = "clang_implicit_modules"}) then + implicitmodulesflag = "-fimplicit-modules" + end + assert(implicitmodulesflag, "compiler(clang): does not support c++ module!") + _g.implicitmodulesflag = implicitmodulesflag or false + end + return implicitmodulesflag or nil +end + +function get_noimplicitmodulemapsflag(target) + local noimplicitmodulemapsflag = _g.noimplicitmodulemapsflag + if noimplicitmodulemapsflag == nil then + local compinst = target:compiler("cxx") + if compinst:has_flags("-fno-implicit-module-maps", "cxxflags", {flagskey = "clang_no_implicit_module_maps"}) then + noimplicitmodulemapsflag = "-fno-implicit-module-maps" + end + assert(noimplicitmodulemapsflag, "compiler(clang): does not support c++ module!") + _g.noimplicitmodulemapsflag = noimplicitmodulemapsflag or false + end + return noimplicitmodulemapsflag or nil +end + +function get_prebuiltmodulepathflag(target) + local prebuiltmodulepathflag = _g.prebuiltmodulepathflag + if prebuiltmodulepathflag == nil then + local compinst = target:compiler("cxx") + if compinst:has_flags("-fprebuilt-module-path=" .. os.tmpdir(), "cxxflags", {flagskey = "clang_prebuild_module_path"}) then + prebuiltmodulepathflag = "-fprebuilt-module-path=" + end + assert(prebuiltmodulepathflag, "compiler(clang): does not support c++ module!") + _g.prebuiltmodulepathflag = prebuiltmodulepathflag or false + end + return prebuiltmodulepathflag or nil +end + +function get_modulecachepathflag(target) + local modulecachepathflag = _g.modulecachepathflag + if modulecachepathflag == nil then + local compinst = target:compiler("cxx") + if compinst:has_flags("-fmodules-cache-path=" .. os.tmpdir(), "cxxflags", {flagskey = "clang_modules_cache_path"}) then + modulecachepathflag = "-fmodules-cache-path=" + end + assert(modulecachepathflag, "compiler(clang): does not support c++ module!") + _g.modulecachepathflag = modulecachepathflag or false + end + return modulecachepathflag or nil +end + +function get_modulefileflag(target) + local modulefileflag = _g.modulefileflag + if modulefileflag == nil then + local compinst = target:compiler("cxx") + if compinst:has_flags("-fmodule-file=" .. os.tmpfile() .. get_bmi_extension(), "cxxflags", {flagskey = "clang_module_file"}) then + modulefileflag = "-fmodule-file=" + end + assert(modulefileflag, "compiler(clang): does not support c++ module!") + _g.modulefileflag = modulefileflag or false + end + return modulefileflag or nil +end + +function has_headerunitsupport(target) + local support_headerunits = _g.support_headerunits + if support_headerunits == nil then + local compinst = target:compiler("cxx") + if compinst:has_flags(get_modulesflag(target) .. " -std=c++20 -x c++-user-header", "cxxflags", {flagskey = "clang_user_header_unit_support", tryrun = true}) and + compinst:has_flags(get_modulesflag(target) .. " -std=c++20 -x c++-system-header", "cxxflags", {flagskey = "clang_system_header_unit_support", tryrun = true}) then + support_headerunits = true + end + _g.support_headerunits = support_headerunits or false + end + return support_headerunits or nil +end + +function get_requiresflags(target, requires) + local flags = {} + local modulemap = _get_modulemap_from_mapper(target) + -- add deps required module flags + for name, _ in pairs(requires) do + for _, dep in ipairs(target:orderdeps()) do + local modulemap_ = _get_modulemap_from_mapper(dep) + if modulemap_[name] then + table.join2(flags, modulemap_[name].flag) + table.join2(flags, modulemap_[name].deps or {}) + goto continue + end + end + + -- append target required module mapper flags + if modulemap[name] then + table.join2(flags, modulemap[name].flag) + table.join2(flags, modulemap[name].deps or {}) + goto continue + end + + ::continue:: + end + if #flags > 0 then + return table.unique(flags) + end +end diff --git a/xmake/rules/c++/modules/modules_support/common.lua b/xmake/rules/c++/modules/modules_support/common.lua new file mode 100644 index 000000000..aa7c0fb59 --- /dev/null +++ b/xmake/rules/c++/modules/modules_support/common.lua @@ -0,0 +1,555 @@ +--!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, TBOOX Open Source Group. +-- +-- @author Arthapz, ruki +-- @file common.lua +-- + +-- imports +import("core.base.json") +import("core.base.hashset") +import("core.project.config") +import("core.tool.compiler") +import("core.cache.memcache", {alias = "_memcache"}) +import("core.cache.localcache", {alias = "_localcache"}) +import("core.project.project") +import("lib.detect.find_file") +import("stl_headers") + +-- get memcache +function memcache() + return _memcache.cache("cxxmodules") +end + +-- get localcache +function localcache() + return _localcache.cache("cxxmodules") +end + +-- get stl modules cache directory +function stlmodules_cachedir(target) + local stlcachedir = path.join(config.buildir(), "stlmodules", "cache") + if not os.isdir(stlcachedir) then + os.mkdir(stlcachedir) + os.mkdir(path.join(stlcachedir, "experimental")) + end + return stlcachedir +end + +-- get modules cache directory +function modules_cachedir(target) + local cachedir = path.join(target:autogendir(), "rules", "modules", "cache") + if not os.isdir(cachedir) then + os.mkdir(cachedir) + end + return cachedir +end + +-- get headerunits info +function get_headerunits(target, sourcebatch, modules) + local headerunits + local stl_headerunits + for _, objectfile in ipairs(sourcebatch.objectfiles) do + local m = modules[objectfile] + if m then + for name, r in pairs(m.requires) do + if r.method ~= "by-name" then + local unittype = r.method == "include-angle" and ":angle" or ":quote" + if stl_headers.is_stl_header(name) then + stl_headerunits = stl_headerunits or {} + if not table.find_if(stl_headerunits, function(i, v) return v.name == name end) then + table.insert(stl_headerunits, {name = name, path = r.path, type = unittype}) + end + else + headerunits = headerunits or {} + if not table.find_if(headerunits, function(i, v) return v.name == name end) then + table.insert(headerunits, {name = name, path = r.path, type = unittype}) + end + end + end + end + end + end + return headerunits, stl_headerunits +end + +-- patch sourcebatch +function patch_sourcebatch(target, sourcebatch) + sourcebatch.sourcekind = "cxx" + sourcebatch.objectfiles = {} + sourcebatch.dependfiles = {} + for _, sourcefile in ipairs(sourcebatch.sourcefiles) do + local objectfile = target:objectfile(sourcefile) + local dependfile = target:dependfile(objectfile) + table.insert(sourcebatch.objectfiles, objectfile) + table.insert(sourcebatch.dependfiles, dependfile) + end +end + +-- get modules support +function modules_support(target) + local cachekey = tostring(target) + local module_builder = memcache():get2("modules_support", cachekey) + if module_builder == nil then + if target:has_tool("cxx", "clang", "clangxx") then + module_builder = import("clang", {anonymous = true}) + elseif target:has_tool("cxx", "gcc", "gxx") then + module_builder = import("gcc", {anonymous = true}) + elseif target:has_tool("cxx", "cl") then + module_builder = import("msvc", {anonymous = true}) + else + local _, toolname = target:tool("cxx") + raise("compiler(%s): does not support c++ module!", toolname) + end + memcache():set2("modules_support", cachekey, module_builder) + end + return module_builder +end + +-- get bmi extension +function bmi_extension(target) + return modules_support(target).get_bmi_extension() +end + +-- has module extension? e.g. *.mpp, ... +function has_module_extension(sourcefile) + local modulexts = _g.modulexts + if modulexts == nil then + modulexts = hashset.of(".mpp", ".mxx", ".cppm", ".ixx") + _g.modulexts = modulexts + end + local extension = path.extension(sourcefile) + return modulexts:has(extension:lower()) +end + +-- this target contains module files? +function contains_modules(target) + -- we can not use `"c++.build.modules.builder"`, because it contains sourcekind/cxx. + local target_with_modules = target:sourcebatches()["c++.build.modules"] and true or false + if not target_with_modules then + for _, dep in ipairs(target:orderdeps()) do + local sourcebatches = dep:sourcebatches() + if sourcebatches["c++.build.modules"] then + target_with_modules = true + break + end + end + end + return target_with_modules +end + +-- load module infos +function load_moduleinfos(target, sourcebatch) + local moduleinfos + for _, sourcefile in ipairs(sourcebatch.sourcefiles) do + local dependfile = target:dependfile(sourcefile) + if os.isfile(dependfile) then + local data = io.load(dependfile) + if data then + moduleinfos = moduleinfos or {} + local moduleinfo = json.decode(data.moduleinfo) + moduleinfo.sourcefile = sourcefile + if moduleinfo then + table.insert(moduleinfos, moduleinfo) + end + end + end + end + return moduleinfos +end + +-- parse module dependency data +--[[ +{ + "build/.objs/stl_headerunit/linux/x86_64/release/src/hello.mpp.o" = { + requires = { + iostream = { + method = "include-angle", + unique = true, + path = "/usr/include/c++/11/iostream" + } + }, + provides = { + hello = { + bmi = "build/.gens/stl_headerunit/linux/x86_64/release/rules/modules/cache/hello.gcm", + sourcefile = "src/hello.mpp" + } + } + }, + "build/.objs/stl_headerunit/linux/x86_64/release/src/main.cpp.o" = { + requires = { + hello = { + method = "by-name", + unique = false, + path = "build/.gens/stl_headerunit/linux/x86_64/release/rules/modules/cache/hello.gcm" + } + } + } +}]] +function parse_dependency_data(target, moduleinfos) + local modules + local cachedir = modules_cachedir(target) + for _, moduleinfo in ipairs(moduleinfos) do + assert(moduleinfo.version <= 1) + for _, rule in ipairs(moduleinfo.rules) do + modules = modules or {} + local m = {} + if rule.provides then + for _, provide in ipairs(rule.provides) do + m.provides = m.provides or {} + assert(provide["logical-name"]) + if provide["compiled-module-path"] then + if not path.is_absolute(provide["compiled-module-path"]) then + m.provides[provide["logical-name"]] = path.absolute(path.translate(provide["compiled-module-path"])) + else + m.provides[provide["logical-name"]] = path.translate(provide["compiled-module-path"]) + end + else + -- assume path with name + local name = provide["logical-name"] .. bmi_extension(target) + name:replace(":", "-") + m.provides[provide["logical-name"]] = { + bmi = path.join(cachedir, name), + sourcefile = moduleinfo.sourcefile + } + end + end + else + m.cppfile = moduleinfo.sourcefile + end + assert(rule["primary-output"]) + modules[path.translate(rule["primary-output"])] = m + end + end + + for _, moduleinfo in ipairs(moduleinfos) do + for _, rule in ipairs(moduleinfo.rules) do + local m = modules[path.translate(rule["primary-output"])] + for _, r in ipairs(rule.requires) do + m.requires = m.requires or {} + local p = r["source-path"] + if not p then + for _, dependency in pairs(modules) do + if dependency.provides and dependency.provides[r["logical-name"]] then + p = dependency.provides[r["logical-name"]].bmi + break + end + end + end + m.requires[r["logical-name"]] = { + method = r["lookup-method"] or "by-name", + path = p and path.translate(p) or nil, + unique = r["unique-on-source-path"] or false + } + end + end + end + return modules +end + +function _topological_sort_visit(node, nodes, modules, output) + if node.marked then + return + end + assert(not node.tempmarked) + node.tempmarked = true + local m1 = modules[node.objectfile] + for _, n in ipairs(nodes) do + if not n.tempmarked then + local m2 = modules[n.objectfile] + if m2 then + for name, provide in pairs(m1.provides) do + if m2.requires and m2.requires[name] then + _topological_sort_visit(n, nodes, modules, output) + end + end + end + end + end + node.tempmarked = false + node.marked = true + table.insert(output, 1, node.objectfile) +end + +function _topological_sort_has_node_without_mark(nodes) + for _, node in ipairs(nodes) do + if not node.marked then + return true + end + end + return false +end + +function _topological_sort_get_first_unmarked_node(nodes) + for _, node in ipairs(nodes) do + if not node.marked and not node.tempmarked then + return node + end + end +end + +-- topological sort +function sort_modules_by_dependencies(objectfiles, modules) + local output = {} + local nodes = {} + for _, objectfile in ipairs(objectfiles) do + local m = modules[objectfile] + if m then + table.insert(nodes, {marked = false, tempmarked = false, objectfile = objectfile}) + end + end + while _topological_sort_has_node_without_mark(nodes) do + local node = _topological_sort_get_first_unmarked_node(nodes) + _topological_sort_visit(node, nodes, modules, output) + end + return output +end + +function find_quote_header_file(target, sourcefile, file) + local p = path.join(path.directory(path.absolute(sourcefile, project.directory())), file) + assert(os.isfile(p)) + return p +end + +function find_angle_header_file(target, file) + local headerpaths = modules_support(target).toolchain_includedirs(target) + for _, dep in ipairs(target:orderdeps()) do + local includedirs = dep:get("sysincludedirs") or dep:get("includedirs") + if includedirs then + table.join2(headerpaths, includedirs) + end + end + for _, pkg in pairs(target:pkgs()) do + local includedirs = pkg:get("sysincludedirs") or pkg:get("includedirs") + if includedirs then + table.join2(headerpaths, includedirs) + end + end + table.join2(headerpaths, target:get("includedirs")) + local p = find_file(file, headerpaths) + assert(p, "find <%s> not found!", file) + return p +end + +-- https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p1689r5.html +--[[ +{ + "version": 1, + "revision": 0, + "rules": [ + { + "primary-output": "use-header.mpp.o", + "requires": [ + { + "logical-name": "<header.hpp>", + "source-path": "/path/to/found/header.hpp", + "unique-on-source-path": true, + "lookup-method": "include-angle" + } + ] + }, + { + "primary-output": "header.hpp.bmi", + "provides": [ + { + "logical-name": "header.hpp", + "source-path": "/path/to/found/header.hpp", + "unique-on-source-path": true, + } + ] + } + ] +}]] +function fallback_generate_dependencies(target, jsonfile, sourcefile) + local output = {version = 0, revision = 0, rules = {}} + local rule = {outputs = {jsonfile}} + rule["primary-output"] = target:objectfile(sourcefile) + + local module_name + local module_deps = {} + local sourcecode = io.readfile(sourcefile) + sourcecode = sourcecode:gsub("//.-\n", "\n") + sourcecode = sourcecode:gsub("/%*.-%*/", "") + for _, line in ipairs(sourcecode:split("\n", {plain = true})) do + if not module_name then + module_name = line:match("export%s+module%s+(.+)%s*;") + end + local module_depname = line:match("import%s+(.+)%s*;") + -- we need parse module interface dep in cxx/impl_unit.cpp, e.g. hello.mpp and hello_impl.cpp + -- @see https://github.com/xmake-io/xmake/pull/2664#issuecomment-1213167314 + if not module_depname and not has_module_extension(sourcefile) then + module_depname = line:match("module%s+(.+)%s*;") + end + if module_depname then + local module_dep = {} + -- partition? import :xxx; + if module_depname:startswith(":") then + module_depname = module_name .. module_depname + elseif module_depname:startswith("\"") then + module_depname = module_depname:sub(2, -2) + module_dep["lookup-method"] = "include-quote" + module_dep["unique-on-source-path"] = true + module_dep["source-path"] = find_quote_header_file(target, sourcefile, module_depname) + elseif module_depname:startswith("<") then + module_depname = module_depname:sub(2, -2) + module_dep["lookup-method"] = "include-angle" + module_dep["unique-on-source-path"] = true + module_dep["source-path"] = find_angle_header_file(target, module_depname) + end + module_dep["logical-name"] = module_depname + table.insert(module_deps, module_dep) + end + end + + if module_name then + table.insert(rule.outputs, module_name .. bmi_extension(target)) + + local provide = {} + provide["logical-name"] = module_name + provide["source-path"] = path.absolute(sourcefile, project.directory()) + + rule.provides = {} + table.insert(rule.provides, provide) + end + + rule.requires = module_deps + table.insert(output.rules, rule) + local jsondata = json.encode(output) + io.writefile(jsonfile, jsondata) +end + +-- get module dependencies +function get_module_dependencies(target, sourcebatch, opt) + local cachekey = target:name() .. "/" .. sourcebatch.rulename + local modules = memcache():get2("modules", cachekey) + if modules == nil then + modules = localcache():get2("modules", cachekey) + opt.progress = opt.progress or 0 + local changed = modules_support(target).generate_dependencies(target, sourcebatch, opt) + if changed or modules == nil then + local moduleinfos = load_moduleinfos(target, sourcebatch) + modules = parse_dependency_data(target, moduleinfos) + localcache():set2("modules", cachekey, modules) + localcache():save() + end + memcache():set2("modules", cachekey, modules) + end + return modules +end + +-- generate headerunits for batchjobs +function generate_headerunits_for_batchjobs(target, batchjobs, sourcebatch, modules, opt) + + -- get headerunits info + local headerunits, stl_headerunits = get_headerunits(target, sourcebatch, modules) + + -- generate headerunits + -- build stl header units as other headerunits may need them + local headerunits_flags + if stl_headerunits then + headerunits_flags = headerunits_flags or {} + table.join2(headerunits_flags, modules_support(target).generate_stl_headerunits_for_batchjobs(target, batchjobs, stl_headerunits, opt)) + end + if headerunits then + headerunits_flags = headerunits_flags or {} + table.join2(headerunits_flags, modules_support(target).generate_user_headerunits_for_batchjobs(target, batchjobs, headerunits, opt)) + end + return headerunits_flags +end + +-- generate headerunits for batchcmds +function generate_headerunits_for_batchcmds(target, batchcmds, sourcebatch, modules, opt) + + -- get headerunits info + local user_headerunits, stl_headerunits = get_headerunits(target, sourcebatch, modules) + + -- generate headerunits + -- build stl header units as other headerunits may need them + if stl_headerunits or user_headerunits then + local headerunits_flags = localcache():get("headerunits_flags") + if stl_headerunits then + modules_support(target).generate_stl_headerunits_for_batchcmds(target, batchcmds, stl_headerunits, opt) + end + if user_headerunits then + modules_support(target).generate_user_headerunits_for_batchcmds(target, batchcmds, user_headerunits, opt) + end + end +end + +-- build batch jobs for module dependencies +function _build_batchjobs_for_moduledeps(modules, batchjobs, rootjob, jobrefs, moduleinfo) + local targetjob_ref = jobrefs[moduleinfo.name] + if targetjob_ref then + batchjobs:add(targetjob_ref, rootjob) + else + local modulejob = batchjobs:add(moduleinfo.job, rootjob) + if modulejob then + jobrefs[moduleinfo.name] = modulejob + for _, depname in ipairs(moduleinfo.deps) do + local dep = modules[depname] + if dep then -- maybe nil, e.g. `import <string>;` + _build_batchjobs_for_moduledeps(modules, batchjobs, modulejob, jobrefs, dep) + end + end + end + end +end + +-- build batchjobs for modules +function build_batchjobs_for_modules(modules, batchjobs, rootjob) + local depset = hashset.new() + for _, moduleinfo in pairs(modules) do + assert(moduleinfo.job) + for _, depname in ipairs(moduleinfo.deps) do + depset:insert(depname) + end + end + local modules_root = {} + for _, moduleinfo in pairs(modules) do + if not depset:has(moduleinfo.name) then + table.insert(modules_root, moduleinfo) + end + end + local jobrefs = {} + for _, moduleinfo in pairs(modules_root) do + _build_batchjobs_for_moduledeps(modules, batchjobs, rootjob, jobrefs, moduleinfo) + end +end + +-- build modules for batchjobs +function build_modules_for_batchjobs(target, batchjobs, sourcebatch, modules, opt) + local objectfiles = sort_modules_by_dependencies(sourcebatch.objectfiles, modules) + modules_support(target).build_modules_for_batchjobs(target, batchjobs, objectfiles, modules, opt) +end + +-- build modules for batchcmds +function build_modules_for_batchcmds(target, batchcmds, sourcebatch, modules, opt) + local objectfiles = sort_modules_by_dependencies(sourcebatch.objectfiles, modules) + modules_support(target).build_modules_for_batchcmds(target, batchcmds, objectfiles, modules, opt) +end + +-- append headerunits objectfiles to link +function append_headerunits_objectfiles(target) + local cachekey = target:name() .. "headerunit_objectfiles" + local cache = localcache():get(cachekey) or {} + if target:is_binary() then + target:add("ldflags", cache, {force = true}) + elseif target:is_static() then + target:add("arflags", cache, {force = true}) + elseif target:is_shared() then + target:add("shflags", cache, {force = true}) + end +end diff --git a/xmake/rules/c++/modules/modules_support/gcc.lua b/xmake/rules/c++/modules/modules_support/gcc.lua new file mode 100644 index 000000000..1405d1cfa --- /dev/null +++ b/xmake/rules/c++/modules/modules_support/gcc.lua @@ -0,0 +1,457 @@ +--!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, TBOOX Open Source Group. +-- +-- @author ruki +-- @file gcc.lua +-- + +-- imports +import("core.tool.compiler") +import("core.project.project") +import("core.project.depend") +import("core.project.config") +import("utils.progress") +import("private.action.build.object", {alias = "objectbuilder"}) +import("common") + +-- get and create the path of module mapper +function _get_module_mapper() + local mapper_file = path.join(config.buildir(), "mapper.txt") + if not os.isfile(mapper_file) then + io.writefile(mapper_file, "") + end + return mapper_file +end + +-- add a module or header unit into the mapper +-- +-- e.g +-- /usr/include/c++/11/iostream build/.gens/stl_headerunit/linux/x86_64/release/stlmodules/cache/iostream.gcm +-- hello build/.gens/stl_headerunit/linux/x86_64/release/rules/modules/cache/hello.gcm +-- +function _add_module_to_mapper(file, module, bmi) + for line in io.lines(file) do + if line:startswith(module .. " ") then + return false + end + end + local f = io.open(file, "a") + f:print("%s %s", module, bmi) + f:close() + return true +end + +-- load module support for the current target +function load(target) + local modulesflag = get_modulesflag(target) + local modulemapperflag = get_modulemapperflag(target) + target:add("cxxflags", modulesflag) + if os.isfile(_get_module_mapper()) then + os.rm(_get_module_mapper()) + end + target:add("cxxflags", modulemapperflag .. _get_module_mapper(), {force = true, expand = false}) +end + +-- get includedirs for stl headers +-- +-- $ echo '#include <vector>' | gcc -x c++ -E - | grep '/vector"' +-- # 1 "/usr/include/c++/11/vector" 1 3 +-- # 58 "/usr/include/c++/11/vector" 3 +-- # 59 "/usr/include/c++/11/vector" 3 +-- +function _get_toolchain_includedirs_for_stlheaders(includedirs, gcc) + local tmpfile = os.tmpfile() .. ".cc" + io.writefile(tmpfile, "#include <vector>") + local result = try {function () return os.iorunv(gcc, {"-E", "-x", "c++", tmpfile}) end} + if result then + for _, line in ipairs(result:split("\n", {plain = true})) do + line = line:trim() + if line:startswith("#") and line:find("/vector\"", 1, true) then + local includedir = line:match("\"(.+)/vector\"") + if includedir and os.isdir(includedir) then + table.insert(includedirs, path.normalize(includedir)) + break + end + end + end + end + os.tryrm(tmpfile) +end + +-- provide toolchain include directories for stl headerunit when p1689 is not supported +function toolchain_includedirs(target) + local includedirs = _g.includedirs + if includedirs == nil then + includedirs = {} + local gcc, toolname = target:tool("cc") + assert(toolname == "gcc") + _get_toolchain_includedirs_for_stlheaders(includedirs, gcc) + local _, result = try {function () return os.iorunv(gcc, {"-E", "-Wp,-v", "-xc", os.nuldev()}) end} + if result then + for _, line in ipairs(result:split("\n", {plain = true})) do + line = line:trim() + if os.isdir(line) then + table.insert(includedirs, path.normalize(line)) + elseif line:startswith("End") then + break + end + end + end + _g.includedirs = includedirs + end + return includedirs +end + +-- generate dependency files +function generate_dependencies(target, sourcebatch, opt) + local cachedir = common.modules_cachedir(target) + local compinst = target:compiler("cxx") + local common_args = {"-E", "-x", "c++"} + local trtbdflag = get_trtbdflag(target) + local depfileflag = get_depfileflag(target) + local depoutputflag = get_depoutputflag(target) + local changed = false + for _, sourcefile in ipairs(sourcebatch.sourcefiles) do + local dependfile = target:dependfile(sourcefile) + depend.on_changed(function() + if opt.progress then + progress.show(opt.progress, "${color.build.object}generating.cxx.module.deps %s", sourcefile) + end + + local outputdir = path.translate(path.join(cachedir, path.directory(path.relative(sourcefile, projectdir)))) + if not os.isdir(outputdir) then + os.mkdir(outputdir) + end + + local jsonfile = path.translate(path.join(outputdir, path.filename(sourcefile) .. ".json")) + if trtbdflag and depfileflag and depoutputflag then + local ifile = path.translate(path.join(outputdir, path.filename(sourcefile) .. ".i")) + local dfile = path.translate(path.join(outputdir, path.filename(sourcefile) .. ".d")) + local args = {sourcefile, "-MD", "-MT", jsonfile, "-MF", dfile, depfileflag .. jsonfile, trtbdflag, depoutputfile .. target:objectfile(sourcefile), "-o", ifile} + os.vrunv(compinst:program(), table.join(compinst:compflags({target = target}), common_args, args), {envs = vcvars}) + else + common.fallback_generate_dependencies(target, jsonfile, sourcefile) + end + changed = true + + local dependinfo = io.readfile(jsonfile) + return { moduleinfo = dependinfo } + end, {dependfile = dependfile, files = {sourcefile}}) + end + return changed +end + +-- generate target stl header units for batchjobs +function generate_stl_headerunits_for_batchjobs(target, batchjobs, headerunits, opt) + local compinst = target:compiler("cxx") + local mapper_file = _get_module_mapper() + local stlcachedir = common.stlmodules_cachedir(target) + + -- build headerunits + local projectdir = os.projectdir() + for _, headerunit in ipairs(headerunits) do + local bmifile = path.join(stlcachedir, headerunit.name .. get_bmi_extension()) + if not os.isfile(bmifile) then + batchjobs:addjob(headerunit.name, function (index, total) + depend.on_changed(function() + progress.show((index * 100) / total, "${color.build.object}generating.cxx.headerunit.bmi %s", headerunit.name) + local args = {"-c", "-x", "c++-system-header", headerunit.name} + os.vrunv(compinst:program(), table.join(compinst:compflags({target = target}), args)) + end, {dependfile = target:dependfile(bmifile), files = {headerunit.path}}) + end, {rootjob = opt.rootjob}) + end + _add_module_to_mapper(mapper_file, headerunit.path, path.absolute(bmifile, projectdir)) + end +end + +-- generate target stl header units for batchcmds +function generate_stl_headerunits_for_batchcmds(target, batchcmds, headerunits, opt) + local compinst = target:compiler("cxx") + local mapper_file = _get_module_mapper() + local stlcachedir = common.stlmodules_cachedir(target) + + -- build headerunits + local projectdir = os.projectdir() + local depmtime = 0 + for _, headerunit in ipairs(headerunits) do + local bmifile = path.join(stlcachedir, headerunit.name .. get_bmi_extension()) + if not os.isfile(bmifile) then + local args = {"-c", "-x", "c++-system-header", headerunit.name} + batchcmds:show_progress(opt.progress, "${color.build.object}generating.cxx.headerunit.bmi %s", headerunit.name) + batchcmds:vrunv(compinst:program(), table.join(compinst:compflags({target = target}), args)) + end + batchcmds:add_depfiles(headerunit.path) + _add_module_to_mapper(mapper_file, headerunit.path, path.absolute(bmifile, projectdir)) + depmtime = math.max(depmtime, os.mtime(bmifile)) + end + batchcmds:set_depmtime(depmtime) +end + +-- generate target user header units for batchjobs +function generate_user_headerunits_for_batchjobs(target, batchjobs, headerunits, opt) + local compinst = target:compiler("cxx") + local mapper_file = _get_module_mapper() + local cachedir = common.modules_cachedir(target) + + -- build headerunits + local projectdir = os.projectdir() + for _, headerunit in ipairs(headerunits) do + local file = path.relative(headerunit.path, projectdir) + local objectfile = target:objectfile(file) + local outputdir + local headerunit_path + if headerunit.type == ":quote" then + outputdir = path.join(cachedir, path.directory(path.relative(headerunit.path, projectdir))) + else + outputdir = path.join(cachedir, path.directory(headerunit.path)) + end + local bmifilename = path.basename(objectfile) .. get_bmi_extension() + local bmifile = (outputdir and path.join(outputdir, bmifilename) or bmifilename) + if headerunit.type == ":quote" then + headerunit_path = path.join(".", path.relative(headerunit.path, projectdir)) + elseif headerunit.type == ":angle" then + -- if path is relative then its a subtarget path + headerunit_path = path.is_absolute(headerunit.path) and headerunit.path or path.join(".", headerunit.path) + end + batchjobs:addjob(headerunit.name, function (index, total) + depend.on_changed(function() + progress.show((index * 100) / total, "${color.build.object}generating.cxx.headerunit.bmi %s", headerunit.name) + local objectdir = path.directory(objectfile) + if not os.isdir(objectdir) then + os.mkdir(objectdir) + end + if not os.isdir(outputdir) then + os.mkdir(outputdir) + end + + -- generate headerunit + local args = { "-c" } + if headerunit.type == ":quote" then + table.join2(args, { "-I", path.directory(path.relative(headerunit.path, projectdir)), "-x", "c++-user-header", headerunit.name }) + elseif headerunit.type == ":angle" then + table.join2(args, { "-x", "c++-system-header", headerunit.name }) + end + os.vrunv(compinst:program(), table.join(compinst:compflags({target = target}), args)) + + end, {dependfile = target:dependfile(bmifile), files = {headerunit.path}}) + end, {rootjob = opt.rootjob}) + _add_module_to_mapper(mapper_file, headerunit_path, path.absolute(bmifile, projectdir)) + end +end + +-- generate target user header units for batchcmds +function generate_user_headerunits_for_batchcmds(target, batchcmds, headerunits, opt) + local compinst = target:compiler("cxx") + local mapper_file = _get_module_mapper() + local cachedir = common.modules_cachedir(target) + + -- build headerunits + local projectdir = os.projectdir() + local depmtime = 0 + for _, headerunit in ipairs(headerunits) do + local file = path.relative(headerunit.path, projectdir) + local objectfile = target:objectfile(file) + local outputdir + if headerunit.type == ":quote" then + outputdir = path.join(cachedir, path.directory(path.relative(headerunit.path, projectdir))) + else + outputdir = path.join(cachedir, path.directory(headerunit.path)) + end + batchcmds:mkdir(outputdir) + + local bmifilename = path.basename(objectfile) .. get_bmi_extension() + local bmifile = (outputdir and path.join(outputdir, bmifilename) or bmifilename) + batchcmds:mkdir(path.directory(objectfile)) + + local args = {"-c"} + local headerunit_path + if headerunit.type == ":quote" then + table.join2(args, {"-I", path(path.relative(headerunit.path, projectdir)):directory(), "-x", "c++-user-header", headerunit.name}) + headerunit_path = path.join(".", path.relative(headerunit.path, projectdir)) + elseif headerunit.type == ":angle" then + table.join2(args, {"-x", "c++-system-header", headerunit.name}) + -- if path is relative then its a subtarget path + headerunit_path = path.is_absolute(headerunit.path) and headerunit.path or path.join(".", headerunit.path) + end + + batchcmds:show_progress(opt.progress, "${color.build.object}generating.cxx.headerunit.bmi %s", headerunit.name) + batchcmds:vrunv(compinst:program(), table.join(compinst:compflags({target = target}), args)) + batchcmds:add_depfiles(headerunit.path) + + _add_module_to_mapper(mapper_file, headerunit_path, path.absolute(bmifile, projectdir)) + depmtime = math.max(depmtime, os.mtime(bmifile)) + end + batchcmds:set_depmtime(depmtime) +end + +-- build module files for batchjobs +function build_modules_for_batchjobs(target, batchjobs, objectfiles, modules, opt) + local compinst = target:compiler("cxx") + local mapper_file = _get_module_mapper() + local common_args = {"-x", "c++"} + local cachedir = common.modules_cachedir(target) + + -- build modules + local projectdir = os.projectdir() + local provided_modules = {} + for _, objectfile in ipairs(objectfiles) do + local m = modules[objectfile] + if m and m.provides then + -- assume there that provides is only one, until we encounter the case + local length = 0 + local name, provide + for k, v in pairs(m.provides) do + length = length + 1 + name = k + provide = v + if length > 1 then + raise("multiple provides are not supported now!") + end + end + + local bmifile = provide.bmi + local moduleinfo = table.copy(provide) + moduleinfo.job = batchjobs:newjob(provide.sourcefile, function (index, total) + depend.on_changed(function() + progress.show((index * 100) / total, "${color.build.object}generating.cxx.module.bmi %s", name) + local objectdir = path.directory(objectfile) + if not os.isdir(objectdir) then + os.mkdir(objectdir) + end + local args = {"-o", objectfile, "-c", provide.sourcefile} + os.vrunv(compinst:program(), table.join(compinst:compflags({target = target}), common_args, args)) + end, {dependfile = target:dependfile(bmifile), files = {provide.sourcefile}}) + end) + if m.requires then + moduleinfo.deps = table.keys(m.requires) + end + moduleinfo.name = name + provided_modules[name] = moduleinfo + _add_module_to_mapper(mapper_file, name, path.absolute(bmifile, projectdir)) + target:add("objectfiles", objectfile) + end + end + + -- build batchjobs for modules + common.build_batchjobs_for_modules(provided_modules, batchjobs, opt.rootjob) +end + +-- build module files for batchcmds +function build_modules_for_batchcmds(target, batchcmds, objectfiles, modules, opt) + local compinst = target:compiler("cxx") + local mapper_file = _get_module_mapper() + local common_args = {"-x", "c++"} + local cachedir = common.modules_cachedir(target) + + -- build modules + local projectdir = os.projectdir() + local depmtime = 0 + for _, objectfile in ipairs(objectfiles) do + local m = modules[objectfile] + if m and m.provides then + -- assume there that provides is only one, until we encounter the case + local length = 0 + local name, provide + for k, v in pairs(m.provides) do + length = length + 1 + name = k + provide = v + if length > 1 then + raise("multiple provides are not supported now!") + end + end + + local bmifile = provide.bmi + local args = {"-o", path(objectfile), "-c", path(provide.sourcefile)} + batchcmds:show_progress(opt.progress, "${color.build.object}generating.cxx.module.bmi %s", name) + batchcmds:mkdir(path.directory(objectfile)) + batchcmds:vrunv(compinst:program(), table.join(compinst:compflags({target = target}), common_args, args)) + batchcmds:add_depfiles(provide.sourcefile) + + _add_module_to_mapper(mapper_file, name, path.absolute(bmifile, projectdir)) + + target:add("objectfiles", objectfile) + depmtime = math.max(depmtime, os.mtime(bmifile)) + end + end + batchcmds:set_depmtime(depmtime) +end + +function get_bmi_extension() + return ".gcm" +end + +function get_modulesflag(target) + local modulesflag = _g.modulesflag + if modulesflag == nil then + local compinst = target:compiler("cxx") + if compinst:has_flags("-fmodules-ts", "cxxflags", {flagskey = "gcc_modules_ts"}) then + modulesflag = "-fmodules-ts" + end + assert(modulesflag, "compiler(gcc): does not support c++ module!") + _g.modulesflag = modulesflag or false + end + return modulesflag or nil +end + +function get_modulemapperflag(target) + local modulemapperflag = _g.modulemapperflag + if modulemapperflag == nil then + local compinst = target:compiler("cxx") + if compinst:has_flags("-fmodule-mapper=" .. os.tmpfile(), "cxxflags", {flagskey = "gcc_module_mapper"}) then + modulemapperflag = "-fmodule-mapper=" + end + assert(modulemapperflag, "compiler(gcc): does not support c++ module!") + _g.modulemapperflag = modulemapperflag or false + end + return modulemapperflag or nil +end + +function get_trtbdflag(target) + local trtbdflag = _g.trtbdflag + if trtbdflag == nil then + local compinst = target:compiler("cxx") + if compinst:has_flags("-fdep-format=trtbd", "cxxflags", {flagskey = "gcc_dep_format"}) then + trtbdflag = "-fdep-format=trtbd" + end + _g.trtbdflag = trtbdflag or false + end + return trtbdflag or nil +end + +function get_depfileflag(target) + local depfileflag = _g.depfileflag + if depfileflag == nil then + local compinst = target:compiler("cxx") + if compinst:has_flags("-fdep-file=" .. os.tmpfile(), "cxxflags", {flagskey = "gcc_dep_file"}) then + depfileflag = "-fdep-file=" + end + _g.depfileflag = depfileflag or false + end + return depfileflag or nil +end + +function get_depoutputflag(target) + local depoutputflag = _g.depoutputflag + if depoutputflag == nil then + local compinst = target:compiler("cxx") + if compinst:has_flags("-fdep-output=" .. os.tmpfile() .. ".o", "cxxflags", {flagskey = "gcc_dep_output"}) then + depoutputflag = "-fdep-output=" + end + _g.depoutputflag = depoutputflag or false + end + return depoutputflag or nil +end diff --git a/xmake/rules/c++/modules/modules_support/msvc.lua b/xmake/rules/c++/modules/modules_support/msvc.lua new file mode 100644 index 000000000..5214f12df --- /dev/null +++ b/xmake/rules/c++/modules/modules_support/msvc.lua @@ -0,0 +1,688 @@ +--!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, TBOOX Open Source Group. +-- +-- @author ruki, Arthapz +-- @file msvc.lua +-- + +-- imports +import("core.tool.compiler") +import("core.project.project") +import("core.project.depend") +import("core.project.config") +import("core.base.hashset") +import("utils.progress") +import("private.action.build.object", {alias = "objectbuilder"}) +import("common") + +-- add a module or header unit into the mapper +-- +-- e.g +-- /headerUnit:angle Foo=build/.gens/Foo/rules/modules/cache/Foo.ifc +-- /headerUnit:angle glm/mat4x4.hpp=Users\arthu\AppData\Local\.xmake\packages\g\glm\0.9.9+8\91454f3ee0be416cb9c7452970a2300f\include\glm\mat4x4.hpp.ifc +-- +function _add_module_to_mapper(target, argument, name, bmifile, deps) + local modulemap = _get_modulemap_from_mapper(target) + if modulemap[name] then + return + end + local mapflag = {argument, name .. "=" .. bmifile} + modulemap[name] = {flag = mapflag, deps = deps} + common.localcache():set2(_mapper_cachekey(target), "modulemap", modulemap) +end + +function _mapper_cachekey(target) + return target:name() .. "_modulemap" +end + +-- flush mapper file cache +function _flush_mapper(target) + -- not using set2/get2 to flush only current target mapper + common.localcache():save(_mapper_cachekey(target)) +end + +-- get modulemap from mapper +function _get_modulemap_from_mapper(target) + return common.localcache():get2(_mapper_cachekey(target), "modulemap") or {} +end + +-- add an objectfile to the linker args +-- +-- e.g +-- foo.obj +-- +function _add_objectfile_to_link_arguments(target, objectfile) + local cachekey = target:name() .. "headerunit_objectfiles" + local cache = common.localcache():get(cachekey) or {} + if table.contains(cache, objectfile) then + return + end + table.insert(cache, objectfile) + common.localcache():set(cachekey, cache) + common.localcache():save(cachekey) +end + +-- load module support for the current target +function load(target) + -- get flags + local modulesflag = get_modulesflag(target) + + -- add modules flags + target:add("cxxflags", modulesflag) + + -- add stdifcdir in case of if the user ask for it + if target:values("msvc.modules.stdifcdir") then + local stdifcdirflag = get_stdifcdirflag(target) + for _, toolchain_inst in ipairs(target:toolchains()) do + if toolchain_inst:name() == "msvc" then + local vcvars = toolchain_inst:config("vcvars") + if vcvars.VCInstallDir and vcvars.VCToolsVersion then + local stdifcdir = path.join(vcvars.VCInstallDir, "Tools", "MSVC", vcvars.VCToolsVersion, "ifc", target:is_arch("x64") and "x64" or "x86") + if os.isdir(stdifcdir) then + target:add("cxxflags", {stdifcdirflag, winos.short_path(stdifcdir)}, {force = true, expand = false}) + end + end + break + end + end + end +end + +-- provide toolchain include dir for stl headerunit when p1689 is not supported +function toolchain_includedirs(target) + for _, toolchain_inst in ipairs(target:toolchains()) do + if toolchain_inst:name() == "msvc" then + local vcvars = toolchain_inst:config("vcvars") + if vcvars.VCInstallDir and vcvars.VCToolsVersion then + return { path.join(vcvars.VCInstallDir, "Tools", "MSVC", vcvars.VCToolsVersion, "include") } + end + break + end + end + raise("msvc toolchain includedirs not found!") +end + +-- generate dependency files +function generate_dependencies(target, sourcebatch, opt) + local compinst = target:compiler("cxx") + local toolchain = target:toolchain("msvc") + local vcvars = toolchain:config("vcvars") + local scandependenciesflag = get_scandependenciesflag(target) + local common_args = {"-TP", scandependenciesflag} + local cachedir = common.modules_cachedir(target) + local changed = false + for _, sourcefile in ipairs(sourcebatch.sourcefiles) do + local dependfile = target:dependfile(sourcefile) + depend.on_changed(function () + if opt.progress then + progress.show(opt.progress, "${color.build.object}generating.cxx.module.deps %s", sourcefile) + end + local outputdir = path.join(cachedir, path.directory(path.relative(sourcefile, projectdir))) + if not os.isdir(outputdir) then + os.mkdir(outputdir) + end + + local jsonfile = path.join(outputdir, path.filename(sourcefile) .. ".json") + if scandependenciesflag then + local args = {jsonfile, sourcefile, "-Fo" .. target:objectfile(sourcefile)} + os.vrunv(compinst:program(), table.join(compinst:compflags({target = target}), common_args, args), {envs = vcvars}) + else + common.fallback_generate_dependencies(target, jsonfile, sourcefile) + end + changed = true + + local dependinfo = io.readfile(jsonfile) + return { moduleinfo = dependinfo } + end, {dependfile = dependfile, files = {sourcefile}}) + end + return changed +end + +-- generate target stl header units for batchjobs +function generate_stl_headerunits_for_batchjobs(target, batchjobs, headerunits, opt) + local compinst = target:compiler("cxx") + local toolchain = target:toolchain("msvc") + local vcvars = toolchain:config("vcvars") + local stlcachedir = common.stlmodules_cachedir(target) + + -- get flags + local exportheaderflag = get_exportheaderflag(target) + local headerunitflag = get_headerunitflag(target) + local headernameflag = get_headernameflag(target) + local ifcoutputflag = get_ifcoutputflag(target) + assert(headerunitflag and headernameflag and exportheaderflag, "compiler(msvc): does not support c++ header units!") + + -- flush job + local flushjob = batchjobs:addjob(target:name() .. "_stl_headerunits_flush_mapper", function(index, total) + _flush_mapper(target) + end, {rootjob = opt.rootjob}) + + -- build headerunits + local common_args = {"-TP", exportheaderflag, "-c"} + for _, headerunit in ipairs(headerunits) do + local bmifile = path.join(stlcachedir, headerunit.name .. get_bmi_extension()) + local objectfile = bmifile .. ".obj" + if not os.isfile(bmifile) or not os.isfile(objectfile) then + batchjobs:addjob(headerunit.name, function(index, total) + depend.on_changed(function() + -- don't build same header unit at the same time + if not common.memcache():get2(headerunit.name, "building") then + common.memcache():set2(headerunit.name, "building", true) + progress.show((index * 100) / total, "${color.build.object}generating.cxx.headerunit.bmi %s", headerunit.name) + local args = {headernameflag .. ":angle", headerunit.name, ifcoutputflag, headerunit.name:startswith("experimental/") and path.join(stlcachedir, "experimental") or stlcachedir, "-Fo" .. objectfile} + os.vrunv(compinst:program(), table.join(compinst:compflags({target = target}), common_args, args), {envs = vcvars}) + end + + end, {dependfile = target:dependfile(bmifile), files = {headerunit.path}}) + _add_module_to_mapper(target, headerunitflag .. ":angle", headerunit.name, bmifile) + if os.isfile(objectfile) then + _add_objectfile_to_link_arguments(target, objectfile) + end + end, {rootjob = flushjob}) + end + end +end + +-- generate target stl header units for batchcmds +function generate_stl_headerunits_for_batchcmds(target, batchcmds, headerunits, opt) + local compinst = target:compiler("cxx") + local toolchain = target:toolchain("msvc") + local vcvars = toolchain:config("vcvars") + local stlcachedir = common.stlmodules_cachedir(target) + + -- get flags + local exportheaderflag = get_exportheaderflag(target) + local headerunitflag = get_headerunitflag(target) + local headernameflag = get_headernameflag(target) + local ifcoutputflag = get_ifcoutputflag(target) + assert(headerunitflag and headernameflag and exportheaderflag, "compiler(msvc): does not support c++ header units!") + + -- build headerunits + local common_args = {"-TP", exportheaderflag, "-c"} + local depmtime = 0 + for _, headerunit in ipairs(headerunits) do + local bmifile = path.join(stlcachedir, headerunit.name .. get_bmi_extension()) + local objectfile = bmifile .. ".obj" + -- don't build same header unit at the same time + if not common.memcache():get2(headerunit.name, "building") then + common.memcache():set2(headerunit.name, "building", true) + local args = { + headernameflag .. ":angle", + headerunit.name, + ifcoutputflag, + path(headerunit.name:startswith("experimental/") and path.join(stlcachedir, "experimental") or stlcachedir), + path(objectfile, function (p) return "-Fo" .. p end)} + batchcmds:show_progress(opt.progress, "${color.build.object}generating.cxx.headerunit.bmi %s", headerunit.name) + batchcmds:vrunv(compinst:program(), table.join(compinst:compflags({target = target}), common_args, args), {envs = vcvars}) + batchcmds:add_depfiles(headerunit.path) + end + _add_module_to_mapper(target, headerunitflag .. ":angle", headerunit.name, bmifile) + if os.isfile(objectfile) then + _add_objectfile_to_link_arguments(target, objectfile) + end + depmtime = math.max(depmtime, os.mtime(bmifile)) + end + batchcmds:set_depmtime(depmtime) + _flush_mapper(target) +end + +-- generate target user header units for batchcmds +function generate_user_headerunits_for_batchjobs(target, batchjobs, headerunits, opt) + local compinst = target:compiler("cxx") + local toolchain = target:toolchain("msvc") + local vcvars = toolchain:config("vcvars") + local cachedir = common.modules_cachedir(target) + + -- get flags + local exportheaderflag = get_exportheaderflag(target) + local headerunitflag = get_headerunitflag(target) + local headernameflag = get_headernameflag(target) + local ifcoutputflag = get_ifcoutputflag(target) + assert(headerunitflag and headernameflag and exportheaderflag, "compiler(msvc): does not support c++ header units!") + + -- flush job + local flushjob = batchjobs:addjob(target:name() .. "_user_headerunits_flush_mapper", function(index, total) + _flush_mapper(target) + end, {rootjob = opt.rootjob}) + + -- build headerunits + local common_args = {"-TP", exportheaderflag, "-c"} + local projectdir = os.projectdir() + for _, headerunit in ipairs(headerunits) do + local file = path.relative(headerunit.path, target:scriptdir()) + local objectfile = target:objectfile(file) + local outputdir + if headerunit.type == ":quote" then + outputdir = path.join(cachedir, path.directory(path.relative(headerunit.path, projectdir))) + else + -- if path is relative then its a subtarget path + outputdir = path.join(cachedir, path.is_absolute(headerunit.path) and path.directory(headerunit.path):sub(3) or headerunit.path) + end + local bmifilename = path.basename(objectfile) .. get_bmi_extension() + local bmifile = path.join(outputdir, bmifilename) + batchjobs:addjob(headerunit.name, function (index, total) + depend.on_changed(function() + if not common.memcache():get2(headerunit.name, "building") then + common.memcache():set2(headerunit.name, "building", true) + progress.show((index * 100) / total, "${color.build.object}generating.cxx.headerunit.bmi %s", headerunit.name) + local objectdir = path.directory(objectfile) + if not os.isdir(objectdir) then + os.mkdir(objectdir) + end + if not os.isdir(outputdir) then + os.mkdir(outputdir) + end + + -- generate headerunit + local args = {headernameflag .. headerunit.type, headerunit.path, ifcoutputflag, outputdir, "/Fo" .. objectfile} + os.vrunv(compinst:program(), table.join(compinst:compflags({target = target}), common_args, args), {envs = vcvars}) + end + _add_module_to_mapper(target, headerunitflag .. headerunit.type, headerunit.name, bmifile) + if os.isfile(objectfile) then + _add_objectfile_to_link_arguments(target, objectfile) + end + end, {dependfile = target:dependfile(bmifile), files = {headerunit.path}}) + end, {rootjob = flushjob}) + end +end + +-- generate target user header units for batchcmds +function generate_user_headerunits_for_batchcmds(target, batchcmds, headerunits, opt) + local compinst = target:compiler("cxx") + local toolchain = target:toolchain("msvc") + local vcvars = toolchain:config("vcvars") + local cachedir = common.modules_cachedir(target) + + -- get flags + local exportheaderflag = get_exportheaderflag(target) + local headerunitflag = get_headerunitflag(target) + local headernameflag = get_headernameflag(target) + local ifcoutputflag = get_ifcoutputflag(target) + assert(headerunitflag and headernameflag and exportheaderflag, "compiler(msvc): does not support c++ header units!") + + -- build headerunits + local common_args = {"-TP", exportheaderflag, "-c"} + local projectdir = os.projectdir() + local depmtime = 0 + for _, headerunit in ipairs(headerunits) do + local file = path.relative(headerunit.path, target:scriptdir()) + local objectfile = target:objectfile(file) + local outputdir + if headerunit.type == ":quote" then + outputdir = path.join(cachedir, path.directory(path.relative(headerunit.path, projectdir))) + else + -- if path is relative then its a subtarget path + outputdir = path.join(cachedir, path.is_absolute(headerunit.path) and path.directory(headerunit.path):sub(3) or headerunit.path) + end + batchcmds:mkdir(outputdir) + + local bmifilename = path.basename(objectfile) .. get_bmi_extension() + local bmifile = path.join(outputdir, bmifilename) + batchcmds:mkdir(path.directory(objectfile)) + + local args = {headernameflag .. headerunit.type, headerunit.path, ifcoutputflag, outputdir, "/Fo" .. objectfile} + + batchcmds:show_progress(opt.progress, "${color.build.object}generating.cxx.headerunit.bmi %s", headerunit.name) + batchcmds:vrunv(compinst:program(), table.join(compinst:compflags({target = target}), common_args, args), {envs = vcvars}) + batchcmds:add_depfiles(headerunit.path) + + _add_module_to_mapper(target, headerunitflag .. headerunit.type, headerunit.name, bmifile) + _add_objectfile_to_link_arguments(target, objectfile) + + depmtime = math.max(depmtime, os.mtime(bmifile)) + end + batchcmds:set_depmtime(depmtime) + _flush_mapper(target) +end + +-- build module files for batchjobs +function build_modules_for_batchjobs(target, batchjobs, objectfiles, modules, opt) + local compinst = target:compiler("cxx") + local toolchain = target:toolchain("msvc") + local vcvars = toolchain:config("vcvars") + + -- get flags + local ifcoutputflag = get_ifcoutputflag(target) + local interfaceflag = get_interfaceflag(target) + local referenceflag = get_referenceflag(target) + + -- flush job + local flushjob = batchjobs:addjob(target:name() .. "_modules", function(index, total) + _flush_mapper(target) + end, {rootjob = opt.rootjob}) + + local common_args = {"-TP"} + local modulesjobs = {} + for _, objectfile in ipairs(objectfiles) do + local module = modules[objectfile] + if module then + if module.provides then + -- assume there that provides is only one, until we encounter the case + local length = 0 + local name, provide + for k, v in pairs(module.provides) do + length = length + 1 + name = k + provide = v + if length > 1 then + raise("multiple provides are not supported now!") + end + end + + local bmifile = provide.bmi + local moduleinfo = table.copy(provide) + moduleinfo.job = batchjobs:newjob(provide.sourcefile, function (index, total) + -- append module mapper flags first + -- @note we add it at the end to ensure that the full modulemap are already stored in the mapper + local requiresflags + if module.requires then + requiresflags = get_requiresflags(target, module.requires, {expand = true}) + end + depend.on_changed(function() + progress.show((index * 100) / total, "${color.build.object}generating.cxx.module.bmi %s", name) + local objectdir = path.directory(objectfile) + if not os.isdir(objectdir) then + os.mkdir(objectdir) + end + local args = {"-c", "-Fo" .. objectfile, interfaceflag, ifcoutputflag, bmifile, provide.sourcefile} + os.vrunv(compinst:program(), table.join(compinst:compflags({target = target}), common_args, requiresflags or {}, args), {envs = vcvars}) + end, {dependfile = target:dependfile(bmifile), files = {provide.sourcefile}}) + _add_module_to_mapper(target, referenceflag, name, bmifile, requiresflags) + end) + if module.requires then + moduleinfo.deps = table.keys(module.requires) + end + moduleinfo.name = name + modulesjobs[name] = moduleinfo + target:add("objectfiles", objectfile) + else + if module.requires then + modulesjobs[module.cppfile] = { + name = module.cppfile, + deps = table.keys(module.requires), + sourcefile = module.cppfile, + job = batchjobs:newjob(module.cppfile, function(index, total) + function contains(t, v) + for _, flag in ipairs(t) do + if table.contains(flag, v) then + return true + end + end + return false + end + -- append module mapper flags + -- @note we add it at the end to ensure that the full modulemap are already stored in the mapper + local requiresflags = get_requiresflags(target, module.requires) + if requiresflags then + target:fileconfig_add(module.cppfile, {force = {cxxflags = requiresflags}}) + end + end) + } + end + end + end + end + + -- build batchjobs for modules + common.build_batchjobs_for_modules(modulesjobs, batchjobs, flushjob) +end + +-- build module files for batchcmds +function build_modules_for_batchcmds(target, batchcmds, objectfiles, modules, opt) + local compinst = target:compiler("cxx") + local toolchain = target:toolchain("msvc") + local vcvars = toolchain:config("vcvars") + local cachedir = common.modules_cachedir(target) + + -- get flags + local ifcoutputflag = get_ifcoutputflag(target) + local interfaceflag = get_interfaceflag(target) + local referenceflag = get_referenceflag(target) + + -- build modules + local common_args = {"-TP"} + local depmtime = 0 + for _, objectfile in ipairs(objectfiles) do + local module = modules[objectfile] + if module then + if module.provides then + local name, provide + for k, v in pairs(module.provides) do + name = k + provide = v + break + end + + -- append required modulemap flags to module + local requiresflags + if module.requires then + requiresflags = get_requiresflags(target, module.requires, {expand = true}) + end + + local bmifile = provide.bmi + local args = {"-c", + path(objectfile, function (p) return "-Fo" .. p end), + interfaceflag, + ifcoutputflag, + path(bmifile), + path(provide.sourcefile)} + batchcmds:show_progress(opt.progress, "${color.build.object}generating.cxx.module.bmi %s", name) + batchcmds:mkdir(path.directory(objectfile)) + batchcmds:vrunv(compinst:program(), table.join(compinst:compflags({target = target}), common_args, requiresflags or {}, args), {envs = vcvars}) + batchcmds:add_depfiles(provide.sourcefile) + _add_module_to_mapper(target, referenceflag, name, bmifile, requiresflags) + depmtime = math.max(depmtime, os.mtime(bmifile)) + else + if module.requires then + local requiresflags = get_requiresflags(target, module.requires) + if requiresflags then + target:fileconfig_add(module.cppfile, {force = {cxxflags = requiresflags}}) + end + end + end + end + end + + batchcmds:set_depmtime(depmtime) + _flush_mapper(target) +end + +function get_bmi_extension() + return ".ifc" +end + +function get_modulesflag(target) + local modulesflag = _g.modulesflag + if modulesflag == nil then + local compinst = target:compiler("cxx") + if compinst:has_flags("-experimental:module", "cxxflags", {flagskey = "cl_experimental_module"}) then + modulesflag = "-experimental:module" + end + assert(modulesflag, "compiler(msvc): does not support c++ module!") + _g.modulesflag = modulesflag or false + end + return modulesflag or nil +end + +function get_ifcoutputflag(target) + local ifcoutputflag = _g.ifcoutputflag + if ifcoutputflag == nil then + local compinst = target:compiler("cxx") + if compinst:has_flags("-ifcOutput", "cxxflags", {flagskey = "cl_ifc_output"}) then + ifcoutputflag = "-ifcOutput" + end + assert(ifcoutputflag, "compiler(msvc): does not support c++ module!") + _g.ifcoutputflag = ifcoutputflag or false + end + return ifcoutputflag or nil +end + +function get_ifcsearchdirflag(target) + local ifcsearchdirflag = _g.ifcsearchdirflag + if ifcsearchdirflag == nil then + local compinst = target:compiler("cxx") + if compinst:has_flags("-ifcSearchDir", "cxxflags", {flagskey = "cl_ifc_search_dir"}) then + ifcsearchdirflag = "-ifcSearchDir" + end + assert(ifcsearchdirflag, "compiler(msvc): does not support c++ module!") + _g.ifcsearchdirflag = ifcsearchdirflag or false + end + return ifcsearchdirflag or nil +end + +function get_interfaceflag(target) + local interfaceflag = _g.interfaceflag + if interfaceflag == nil then + local compinst = target:compiler("cxx") + if compinst:has_flags("-interface", "cxxflags", {flagskey = "cl_interface"}) then + interfaceflag = "-interface" + end + assert(interfaceflag, "compiler(msvc): does not support c++ module!") + _g.interfaceflag = interfaceflag or false + end + return interfaceflag +end + +function get_referenceflag(target) + local referenceflag = _g.referenceflag + if referenceflag == nil then + local compinst = target:compiler("cxx") + if compinst:has_flags("-reference", "cxxflags", {flagskey = "cl_reference"}) then + referenceflag = "-reference" + end + assert(referenceflag, "compiler(msvc): does not support c++ module!") + _g.referenceflag = referenceflag or false + end + return referenceflag or nil +end + +function get_headernameflag(target) + local headernameflag = _g.headernameflag + if headernameflag == nil then + local compinst = target:compiler("cxx") + if compinst:has_flags("-headerName:quote", "cxxflags", {flagskey = "cl_header_name_quote"}) and + compinst:has_flags("-headerName:angle", "cxxflags", {flagskey = "cl_header_name_angle"}) then + headernameflag = "-headerName" + end + _g.headernameflag = headernameflag or false + end + return headernameflag or nil +end + +function get_headerunitflag(target) + local headerunitflag = _g.headerunitflag + if headerunitflag == nil then + local compinst = target:compiler("cxx") + if compinst:has_flags("-headerUnit:quote", "cxxflags", {flagskey = "cl_header_unit_quote"}) and + compinst:has_flags("-headerUnit:angle", "cxxflags", {flagskey = "cl_header_unit_angle"}) then + headerunitflag = "-headerUnit" + end + _g.headerunitflag = headerunitflag or false + end + return headerunitflag or nil +end + +function get_exportheaderflag(target) + local modulesflag = get_modulesflag(target) + local exportheaderflag = _g.exportheaderflag + if exportheaderflag == nil then + local compinst = target:compiler("cxx") + if compinst:has_flags(modulesflag .. " -exportHeader", "cxxflags", {flagskey = "cl_export_header"}) then + exportheaderflag = "-exportHeader" + end + _g.exportheaderflag = exportheaderflag or false + end + return exportheaderflag or nil +end + +function get_stdifcdirflag(target) + local stdifcdirflag = _g.stdifcdirflag + if stdifcdirflag == nil then + local compinst = target:compiler("cxx") + if compinst:has_flags("-stdIfcDir", "cxxflags", {flagskey = "cl_std_ifc_dir"}) then + stdifcdirflag = "-stdIfcDir" + end + _g.stdifcdirflag = stdifcdirflag or false + end + return stdifcdirflag or nil +end + +function get_scandependenciesflag(target) + local scandependenciesflag = _g.scandependenciesflag + if scandependenciesflag == nil then + local compinst = target:compiler("cxx") + local scan_dependencies_jsonfile = os.tmpfile() .. ".json" + if compinst:has_flags("-scanDependencies " .. scan_dependencies_jsonfile, "cxflags", {flagskey = "cl_scan_dependencies", + on_check = function (ok, errors) + if os.isfile(scan_dependencies_jsonfile) then + ok = true + end + if ok and not os.isfile(scan_dependencies_jsonfile) then + ok = false + end + return ok, errors + end}) then + scandependenciesflag = "-scanDependencies" + end + _g.scandependenciesflag = scandependenciesflag or false + end + return scandependenciesflag or nil +end + +-- get requireflags from module mapper +function get_requiresflags(target, requires, opt) + opt = opt or {} + local flags = {} + local modulemap = _get_modulemap_from_mapper(target) + -- add deps required module flags + for name, _ in pairs(requires) do + for _, dep in ipairs(target:orderdeps()) do + local modulemap_ = _get_modulemap_from_mapper(dep) + if modulemap_[name] then + table.join2(flags, modulemap_[name].flag) + table.join2(flags, modulemap_[name].deps or {}) + goto continue + end + end + + -- append target required module mapper flags + if modulemap[name] then + table.join2(flags, modulemap[name].flag) + table.join2(flags, modulemap[name].deps or {}) + goto continue + end + + ::continue:: + end + local requireflags = {} + local contains = {} + for i = 1, #flags, 2 do + local value = flags[i + 1] + if not contains[value] then + local key = flags[i] + if opt.expand then + table.insert(requireflags, key) + table.insert(requireflags, value) + else + table.insert(requireflags, {key, value}) + end + contains[value] = true + end + end + if #requireflags > 0 then + return requireflags + end +end diff --git a/xmake/rules/c++/modules/modules_support/stl_headers.lua b/xmake/rules/c++/modules/modules_support/stl_headers.lua new file mode 100644 index 000000000..599e2830c --- /dev/null +++ b/xmake/rules/c++/modules/modules_support/stl_headers.lua @@ -0,0 +1,153 @@ +--!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, TBOOX Open Source Group. +-- +-- @author Arthapz, ruki +-- @file stl_headers.lua +-- + +-- imports +import("core.base.hashset") + +-- the stl headers list +function _stl_headers() + return { + "algorithm", + "forward_list", + "numbers", + "stop_token", + "any", + "fstream", + "numeric", + "streambuf", + "array", + "functional", + "optional", + "string", + "atomic", + "future", + "ostream", + "string_view", + "barrier", + "initializer_list", + "queue", + "bit", + "iomanip", + "random", + "syncstream", + "bitset", + "ios", + "ranges", + "system_error", + "charconv", + "iosfwd", + "ratio", + "thread", + "chrono", + "iostream", + "regex", + "tuple", + "codecvt", + "istream", + "scoped_allocator", + "typeindex", + "compare", + "iterator", + "semaphore", + "typeinfo", + "complex", + "latch", + "set", + "type_traits", + "concepts", + "limits", + "shared_mutex", + "unordered_map", + "condition_variable", + "list", + "source_location", + "unordered_set", + "coroutine", + "locale", + "span", + "utility", + "deque", + "map", + "spanstream", + "valarray", + "exception", + "memory", + "sstream", + "variant", + "execution", + "memory_resource", + "stack", + "vector", + "filesystem", + "mutex", + "version", + "format", + "new", + "type_traits", + "string_view", + "stdexcept", + "condition_variable", + "print", + "flat_map", + "flat_set", + "mdspan", + "stdfloat", + "generator", + "csetjmp", + "csignal", + "cstdarg", + "cstddef", + "cstdlib", + "cfloat", + "cinttypes", + "climits", + "cstdint", + "cassert", + "cerrno", + "cctype", + "cstring", + "cuchar", + "cwchar", + "cwctype", + "cfenv", + "cmath", + "ctime", + "clocale", + "cstdio"} +end + +-- get all stl headers +function get_stl_headers() + local stl_headers = _g.stl_headers + if stl_headers == nil then + stl_headers = hashset.from(_stl_headers()) + _g.stl_headers = stl_headers or false + end + return stl_headers or nil +end + +-- is stl header? +function is_stl_header(header) + if header:startswith("experimental/") then + header = header:sub(14, -1) + end + return get_stl_headers():has(header) +end + diff --git a/xmake/rules/c++/modules/xmake.lua b/xmake/rules/c++/modules/xmake.lua index 12132da54..3ef960d7a 100644 --- a/xmake/rules/c++/modules/xmake.lua +++ b/xmake/rules/c++/modules/xmake.lua @@ -21,45 +21,118 @@ -- define rule: c++.build.modules rule("c++.build.modules") set_extensions(".mpp", ".mxx", ".cppm", ".ixx") + add_deps("c++.build.modules.builder") + add_deps("c++.build.modules.install") + on_config(function (target) + import("modules_support.common") + -- we disable to build across targets in parallel, because the source files may depend on other target modules -- @see https://github.com/xmake-io/xmake/issues/1858 - local target_with_modules - for _, dep in ipairs(target:orderdeps()) do - local sourcebatches = dep:sourcebatches() - if sourcebatches and sourcebatches["c++.build.modules"] then - target_with_modules = true - break - end - end - if target_with_modules then + if common.contains_modules(target) then -- @note this will cause cross-parallel builds to be disabled for all sub-dependent targets, -- even if some sub-targets do not contain C++ modules. -- -- maybe we will have a more fine-grained configuration strategy to disable it in the future. target:set("policy", "build.across_targets_in_parallel", false) - if target:has_tool("cxx", "clang", "clangxx") then - import("build_modules.clang").load_parent(target, opt) - elseif target:has_tool("cxx", "gcc", "gxx") then - import("build_modules.gcc").load_parent(target, opt) - elseif target:has_tool("cxx", "cl") then - import("build_modules.msvc").load_parent(target, opt) - else - local _, toolname = target:tool("cxx") - raise("compiler(%s): does not support c++ module!", toolname) - end + + -- get modules support + local modules_support = common.modules_support(target) + + -- load module support + modules_support.load(target) + + -- mark this target with modules + target:data_set("cxx.has_modules", true) end end) - before_build_files(function (target, batchjobs, sourcebatch, opt) - if target:has_tool("cxx", "clang", "clangxx") then - import("build_modules.clang").build_with_batchjobs(target, batchjobs, sourcebatch, opt) - elseif target:has_tool("cxx", "gcc", "gxx") then - import("build_modules.gcc").build_with_batchjobs(target, batchjobs, sourcebatch, opt) - elseif target:has_tool("cxx", "cl") then - import("build_modules.msvc").build_with_batchjobs(target, batchjobs, sourcebatch, opt) + +-- build modules +rule("c++.build.modules.builder") + set_sourcekinds("cxx") + set_extensions(".mpp", ".mxx", ".cppm", ".ixx") + + -- generate headerunits + -- parallel build support to accelerate `xmake build` to build headerunits + before_build(function(target, batchjobs, opt) + local job + if target:data("cxx.has_modules") then + import("modules_support.common") + local sourcebatch = target:sourcebatches()["c++.build.modules.builder"] + common.patch_sourcebatch(target, sourcebatch, opt) + + -- generate headerunits + local modules = common.get_module_dependencies(target, sourcebatch, opt) + batchjobs:group_enter(target:name() .. "/generate_headerunits") + common.generate_headerunits_for_batchjobs(target, batchjobs, sourcebatch, modules, opt) + job = batchjobs:group_leave() + end + return job or opt.rootjob + end, {batch = true}) + + -- build modules + -- parallel build support to accelerate `xmake build` to build modules + before_build_files(function(target, batchjobs, sourcebatch, opt) + if target:data("cxx.has_modules") then + import("modules_support.common") + common.patch_sourcebatch(target, sourcebatch, opt) + local modules = common.get_module_dependencies(target, sourcebatch, opt) + common.build_modules_for_batchjobs(target, batchjobs, sourcebatch, modules, opt) else - local _, toolname = target:tool("cxx") - raise("compiler(%s): does not support c++ module!", toolname) + -- avoid duplicate linking of object files of non-module programs + sourcebatch.objectfiles = {} end end, {batch = true}) + -- serial compilation only, usually used to support project generator + before_buildcmd_files(function(target, batchcmds, sourcebatch, opt) + if target:data("cxx.has_modules") then + import("modules_support.common") + + -- patch sourcebatch + common.patch_sourcebatch(target, sourcebatch, opt) + + -- generate headerunits + local modules = common.get_module_dependencies(target, sourcebatch, opt) + common.generate_headerunits_for_batchcmds(target, batchcmds, sourcebatch, modules, opt) + + -- build modules + common.build_modules_for_batchcmds(target, batchcmds, sourcebatch, modules, opt) + else + -- avoid duplicate linking of object files of non-module programs + sourcebatch.objectfiles = {} + end + end) + + before_link(function (target) + import("modules_support.common") + common.append_headerunits_objectfiles(target) + end) + + after_clean(function (target) + import("core.base.option") + import("modules_support.common") + os.tryrm(common.modules_cachedir(target)) + if option.get("all") then + os.tryrm(common.stlmodules_cachedir(target)) + common.localcache():clear() + common.localcache():save() + end + end) + +-- install modules +rule("c++.build.modules.install") + set_extensions(".mpp", ".mxx", ".cppm", ".ixx") + + before_install(function (target) + import("modules_support.common") + + -- we cannot use target:data("cxx.has_modules"), + -- because on_config will be not called when installing targets + if common.contains_modules(target) then + local sourcebatch = target:sourcebatches()["c++.build.modules.install"] + if sourcebatch then + target:add("installfiles", sourcebatch.sourcefiles, {prefixdir = "include"}) + end + end + end) diff --git a/xmake/rules/qt/env/xmake.lua b/xmake/rules/qt/env/xmake.lua index 592ef07d9..815ae49cb 100644 --- a/xmake/rules/qt/env/xmake.lua +++ b/xmake/rules/qt/env/xmake.lua @@ -34,9 +34,9 @@ rule("qt.env") local qmlimportpath = target:values("qt.env.qmlimportpath") or {} if target:is_plat("windows") or (target:is_plat("mingw") and is_host("windows")) then target:add("runenvs", "PATH", qt.bindir) - table.append(qmlimportpath, qt.qmldir) + table.insert(qmlimportpath, qt.qmldir) -- add targetdir in QML2_IMPORT_PATH in case of the user have qml plugins - table.append(qmlimportpath, target:targetdir()) + table.insert(qmlimportpath, target:targetdir()) target:set("runenv", "QML_IMPORT_TRACE", "1") elseif target:is_plat("msys", "cygwin") then raise("please run `xmake f -p mingw --mingw=/mingw64` to support Qt/Mingw64 on Msys!") diff --git a/xmake/rules/qt/qmltyperegistrar/xmake.lua b/xmake/rules/qt/qmltyperegistrar/xmake.lua index ae913c9aa..46c6b21da 100644 --- a/xmake/rules/qt/qmltyperegistrar/xmake.lua +++ b/xmake/rules/qt/qmltyperegistrar/xmake.lua @@ -101,7 +101,7 @@ rule("qt.qmltyperegistrar") } -- gen sourcefile - batchcmds:show_progress(opt.progress, "${color.build.object}generate.qt.qmltyperegistrar %s", path.filename(sourcefile)) + batchcmds:show_progress(opt.progress, "${color.build.object}generating.qt.qmltyperegistrar %s", path.filename(sourcefile)) qmltype_source = os.vrunv(qmltyperegistrar, table.join(args, metatypefiles)) -- add objectfile @@ -109,7 +109,7 @@ rule("qt.qmltyperegistrar") table.insert(target:objectfiles(), objectfile) -- compile sourcefile - batchcmds:show_progress(opt.progress, "${color.build.object}compile.qt.qmltyperegistrar %s", path.filename(sourcefile)) + batchcmds:show_progress(opt.progress, "${color.build.object}compiling.qt.qmltyperegistrar %s", path.filename(sourcefile)) batchcmds:compile(sourcefile, objectfile) batchcmds:add_depfiles(sourcefile) |
