summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorruki <[email protected]>2024-02-18 21:41:52 +0800
committerGitHub <[email protected]>2024-02-18 21:41:52 +0800
commit3c1163b36e0817dbd5b62ec1ff495dc2e830d6ac (patch)
tree16f6cb664c031d80b1841e6978fc1d8753eb6761
parentcda48025005f9aa23bdd582e29bb0f1f1ca24d27 (diff)
parentbc1a30db6041c2c14ca79ccf0aa35b2d550e64e5 (diff)
Merge pull request #4673 from xmake-io/modules
Refactor modules support
-rw-r--r--tests/projects/c++/modules/aliased_headerunit/src/foo/hello.mpp12
-rw-r--r--tests/projects/c++/modules/aliased_headerunit/src/header.hpp5
-rw-r--r--tests/projects/c++/modules/aliased_headerunit/src/main.cpp7
-rw-r--r--tests/projects/c++/modules/aliased_headerunit/test.lua1
-rw-r--r--tests/projects/c++/modules/aliased_headerunit/xmake.lua8
-rw-r--r--tests/projects/c++/modules/circular_dependency/src/hello.mpp13
-rw-r--r--tests/projects/c++/modules/circular_dependency/src/hello2.mpp3
-rw-r--r--tests/projects/c++/modules/circular_dependency/src/hello3.mpp3
-rw-r--r--tests/projects/c++/modules/circular_dependency/src/main.cpp7
-rw-r--r--tests/projects/c++/modules/circular_dependency/test.lua7
-rw-r--r--tests/projects/c++/modules/circular_dependency/xmake.lua8
-rw-r--r--tests/projects/c++/modules/cpp_with_moduledeps/xmake.lua3
-rw-r--r--tests/projects/c++/modules/inline_and_template/src/main.cpp6
-rw-r--r--tests/projects/c++/modules/internal_partition/src/hello_internal.mpp4
-rw-r--r--tests/projects/c++/modules/link_order/xmake.lua6
-rw-r--r--tests/projects/c++/modules/moduleonly/src/mod.mpp (renamed from tests/projects/c++/modules/staticlib_without_cpp/src/mod.mpp)0
-rw-r--r--tests/projects/c++/modules/moduleonly/test.lua (renamed from tests/projects/c++/modules/staticlib_without_cpp/test.lua)0
-rw-r--r--tests/projects/c++/modules/moduleonly/xmake.lua (renamed from tests/projects/c++/modules/staticlib_without_cpp/xmake.lua)2
-rw-r--r--tests/projects/c++/modules/packages/my-repo/packages/b/bar/src/bar.cpp6
-rw-r--r--tests/projects/c++/modules/packages/my-repo/packages/b/bar/src/bar.mpp11
-rw-r--r--tests/projects/c++/modules/packages/my-repo/packages/b/bar/src/include/a.hpp6
-rw-r--r--tests/projects/c++/modules/packages/my-repo/packages/b/bar/src/xmake.lua4
-rw-r--r--tests/projects/c++/modules/packages/my-repo/packages/b/bar/xmake.lua2
-rw-r--r--tests/projects/c++/modules/packages/my-repo/packages/b/bar2/src/bar.mpp8
-rw-r--r--tests/projects/c++/modules/packages/my-repo/packages/b/bar2/src/xmake.lua6
-rw-r--r--tests/projects/c++/modules/packages/my-repo/packages/b/bar2/xmake.lua7
-rw-r--r--tests/projects/c++/modules/packages/my-repo/packages/f/foo/src/foo.mpp4
-rw-r--r--tests/projects/c++/modules/packages/my-repo/packages/f/foo/src/xmake.lua2
-rw-r--r--tests/projects/c++/modules/packages/src/main.cpp4
-rw-r--r--tests/projects/c++/modules/packages/xmake.lua6
-rw-r--r--tests/projects/c++/modules/private_module/src/use.cpp8
-rw-r--r--tests/projects/c++/modules/private_module/src/use.mpp7
-rw-r--r--tests/projects/c++/modules/private_module/xmake.lua1
-rw-r--r--tests/projects/c++/modules/staticlib/xmake.lua3
-rw-r--r--tests/projects/c++/modules/stdmodules/src/my_module.cpp2
-rw-r--r--tests/projects/c++/modules/stdmodules/src/my_module.mpp2
-rw-r--r--tests/projects/c++/modules/stdmodules/xmake.lua5
-rw-r--r--tests/projects/c++/modules/stdmodules_cpp_only/xmake.lua1
-rw-r--r--tests/projects/c++/modules/stdmodules_multiple_targets/xmake.lua4
-rw-r--r--tests/projects/c++/modules/test_base.lua54
-rw-r--r--tests/projects/c++/modules/test_headerunits.lua49
-rw-r--r--tests/projects/c++/modules/test_stdmodules.lua37
-rw-r--r--tests/projects/c++/modules/user_headerunit2/a/xmake.lua5
-rw-r--r--tests/projects/c++/modules/user_headerunit2/b/xmake.lua4
-rw-r--r--xmake/actions/build/build.lua2
-rw-r--r--xmake/actions/build/kinds/moduleonly.lua22
-rw-r--r--xmake/actions/package/local/main.lua100
-rw-r--r--xmake/core/project/policy.lua8
-rw-r--r--xmake/core/project/target.lua9
-rw-r--r--xmake/modules/private/check/checkers/api/target/kind.lua2
-rw-r--r--xmake/modules/target/action/uninstall/main.lua9
-rw-r--r--xmake/plugins/project/cmake/cmakelists.lua13
-rw-r--r--xmake/plugins/project/vstudio/impl/vs201x.lua27
-rw-r--r--xmake/plugins/project/vstudio/impl/vs201x_vcxproj.lua49
-rw-r--r--xmake/plugins/project/vsxmake/getinfo.lua2
-rw-r--r--xmake/rules/c++/modules/modules_support/builder.lua457
-rw-r--r--xmake/rules/c++/modules/modules_support/clang.lua986
-rw-r--r--xmake/rules/c++/modules/modules_support/clang/builder.lua401
-rw-r--r--xmake/rules/c++/modules/modules_support/clang/compiler_support.lua454
-rw-r--r--xmake/rules/c++/modules/modules_support/clang/dependency_scanner.lua84
-rw-r--r--xmake/rules/c++/modules/modules_support/common.lua737
-rw-r--r--xmake/rules/c++/modules/modules_support/compiler_support.lua298
-rw-r--r--xmake/rules/c++/modules/modules_support/dependency_scanner.lua456
-rw-r--r--xmake/rules/c++/modules/modules_support/gcc.lua622
-rw-r--r--xmake/rules/c++/modules/modules_support/gcc/builder.lua389
-rw-r--r--xmake/rules/c++/modules/modules_support/gcc/compiler_support.lua256
-rw-r--r--xmake/rules/c++/modules/modules_support/gcc/dependency_scanner.lua75
-rw-r--r--xmake/rules/c++/modules/modules_support/msvc.lua821
-rw-r--r--xmake/rules/c++/modules/modules_support/msvc/builder.lua457
-rw-r--r--xmake/rules/c++/modules/modules_support/msvc/compiler_support.lua299
-rw-r--r--xmake/rules/c++/modules/modules_support/msvc/dependency_scanner.lua69
-rw-r--r--xmake/rules/c++/modules/modules_support/stl_headers.lua2
-rw-r--r--xmake/rules/c++/modules/xmake.lua193
-rw-r--r--xmake/scripts/vsxmake/vsproj/Xmake.props5
74 files changed, 4314 insertions, 3333 deletions
diff --git a/tests/projects/c++/modules/aliased_headerunit/src/foo/hello.mpp b/tests/projects/c++/modules/aliased_headerunit/src/foo/hello.mpp
new file mode 100644
index 000000000..0a8de6291
--- /dev/null
+++ b/tests/projects/c++/modules/aliased_headerunit/src/foo/hello.mpp
@@ -0,0 +1,12 @@
+module;
+#include <cstdio>
+
+export module hello;
+
+import "../header.hpp";
+
+export namespace hello {
+ void say(const char *arg) {
+ printf("%s: %s\n", FOO, arg);
+ }
+}
diff --git a/tests/projects/c++/modules/aliased_headerunit/src/header.hpp b/tests/projects/c++/modules/aliased_headerunit/src/header.hpp
new file mode 100644
index 000000000..ab02a6792
--- /dev/null
+++ b/tests/projects/c++/modules/aliased_headerunit/src/header.hpp
@@ -0,0 +1,5 @@
+#pragma once
+
+namespace hello {
+ inline constexpr auto FOO = "Hello";
+}
diff --git a/tests/projects/c++/modules/aliased_headerunit/src/main.cpp b/tests/projects/c++/modules/aliased_headerunit/src/main.cpp
new file mode 100644
index 000000000..ad786367e
--- /dev/null
+++ b/tests/projects/c++/modules/aliased_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/aliased_headerunit/test.lua b/tests/projects/c++/modules/aliased_headerunit/test.lua
new file mode 100644
index 000000000..c18e5a1d0
--- /dev/null
+++ b/tests/projects/c++/modules/aliased_headerunit/test.lua
@@ -0,0 +1 @@
+inherit(".test_headerunits")
diff --git a/tests/projects/c++/modules/aliased_headerunit/xmake.lua b/tests/projects/c++/modules/aliased_headerunit/xmake.lua
new file mode 100644
index 000000000..5b4827d09
--- /dev/null
+++ b/tests/projects/c++/modules/aliased_headerunit/xmake.lua
@@ -0,0 +1,8 @@
+add_rules("mode.release", "mode.debug")
+set_languages("c++20")
+
+-- header.hpp should be built only one time
+target("aliased_headerunit")
+ set_kind("binary")
+ add_headerfiles("src/*.hpp")
+ add_files("src/*.cpp", "src/foo/*.mpp")
diff --git a/tests/projects/c++/modules/circular_dependency/src/hello.mpp b/tests/projects/c++/modules/circular_dependency/src/hello.mpp
new file mode 100644
index 000000000..50f0f2c8f
--- /dev/null
+++ b/tests/projects/c++/modules/circular_dependency/src/hello.mpp
@@ -0,0 +1,13 @@
+export module hello;
+
+import hello3;
+
+export namespace hello {
+ class say {
+ public:
+ say(int data);
+ void hello();
+ private:
+ int data_;
+ };
+}
diff --git a/tests/projects/c++/modules/circular_dependency/src/hello2.mpp b/tests/projects/c++/modules/circular_dependency/src/hello2.mpp
new file mode 100644
index 000000000..1b6258af1
--- /dev/null
+++ b/tests/projects/c++/modules/circular_dependency/src/hello2.mpp
@@ -0,0 +1,3 @@
+export module hello2;
+
+import hello;
diff --git a/tests/projects/c++/modules/circular_dependency/src/hello3.mpp b/tests/projects/c++/modules/circular_dependency/src/hello3.mpp
new file mode 100644
index 000000000..4966cf0c0
--- /dev/null
+++ b/tests/projects/c++/modules/circular_dependency/src/hello3.mpp
@@ -0,0 +1,3 @@
+export module hello3;
+
+import hello2;
diff --git a/tests/projects/c++/modules/circular_dependency/src/main.cpp b/tests/projects/c++/modules/circular_dependency/src/main.cpp
new file mode 100644
index 000000000..6f80b6c7d
--- /dev/null
+++ b/tests/projects/c++/modules/circular_dependency/src/main.cpp
@@ -0,0 +1,7 @@
+import hello;
+
+int main() {
+ hello::say s(sizeof(hello::say));
+ s.hello();
+ return 0;
+} \ No newline at end of file
diff --git a/tests/projects/c++/modules/circular_dependency/test.lua b/tests/projects/c++/modules/circular_dependency/test.lua
new file mode 100644
index 000000000..727e9187e
--- /dev/null
+++ b/tests/projects/c++/modules/circular_dependency/test.lua
@@ -0,0 +1,7 @@
+import(".test_base", {alias = "test_build"})
+
+function main(t)
+ if test_build.can_build() then
+ t:will_raise(test_build, "circular modules dependency detected")
+ end
+end
diff --git a/tests/projects/c++/modules/circular_dependency/xmake.lua b/tests/projects/c++/modules/circular_dependency/xmake.lua
new file mode 100644
index 000000000..73254924e
--- /dev/null
+++ b/tests/projects/c++/modules/circular_dependency/xmake.lua
@@ -0,0 +1,8 @@
+add_rules("mode.release", "mode.debug")
+set_languages("c++20")
+
+target("circular_dependency")
+ set_kind("binary")
+ add_files("src/*.cpp", "src/*.mpp")
+
+
diff --git a/tests/projects/c++/modules/cpp_with_moduledeps/xmake.lua b/tests/projects/c++/modules/cpp_with_moduledeps/xmake.lua
index 6a9b3538f..206e3746f 100644
--- a/tests/projects/c++/modules/cpp_with_moduledeps/xmake.lua
+++ b/tests/projects/c++/modules/cpp_with_moduledeps/xmake.lua
@@ -3,7 +3,8 @@ set_languages("c++20")
target("mod")
set_kind("static")
- add_files("src/mod.mpp", "src/mod.cpp")
+ add_files("src/mod.mpp", {public = true})
+ add_files("src/mod.cpp")
target("cpp_with_moduledeps")
set_kind("binary")
diff --git a/tests/projects/c++/modules/inline_and_template/src/main.cpp b/tests/projects/c++/modules/inline_and_template/src/main.cpp
index a34667fbe..bf68f307d 100644
--- a/tests/projects/c++/modules/inline_and_template/src/main.cpp
+++ b/tests/projects/c++/modules/inline_and_template/src/main.cpp
@@ -1,12 +1,12 @@
+#include <iostream>
+
import hello;
import say;
import foo;
-#include <iostream>
-
int main() {
hello::say_hello();
say{}.hello<sizeof(say)>();
std::cout << foo<int>{}.hello() << std::endl;
return 0;
-} \ No newline at end of file
+}
diff --git a/tests/projects/c++/modules/internal_partition/src/hello_internal.mpp b/tests/projects/c++/modules/internal_partition/src/hello_internal.mpp
index 8d0d4bee0..46a86c361 100644
--- a/tests/projects/c++/modules/internal_partition/src/hello_internal.mpp
+++ b/tests/projects/c++/modules/internal_partition/src/hello_internal.mpp
@@ -7,5 +7,5 @@ module hello:part_internal;
namespace hello {
void say_internal(const char *str) {
printf("%s\n", str);
- }
-} \ No newline at end of file
+ }
+}
diff --git a/tests/projects/c++/modules/link_order/xmake.lua b/tests/projects/c++/modules/link_order/xmake.lua
index 6a11418ac..4cb2af87f 100644
--- a/tests/projects/c++/modules/link_order/xmake.lua
+++ b/tests/projects/c++/modules/link_order/xmake.lua
@@ -2,13 +2,11 @@ add_rules("mode.release", "mode.debug")
set_languages("c++20")
target("foo")
- add_rules("c++")
- set_kind("static")
+ set_kind("moduleonly")
add_files("src/foo.mpp")
target("bar")
- add_rules("c++")
- set_kind("static")
+ set_kind("moduleonly")
add_files("src/bar.mpp")
target("link_order_1")
diff --git a/tests/projects/c++/modules/staticlib_without_cpp/src/mod.mpp b/tests/projects/c++/modules/moduleonly/src/mod.mpp
index b7dd3883c..b7dd3883c 100644
--- a/tests/projects/c++/modules/staticlib_without_cpp/src/mod.mpp
+++ b/tests/projects/c++/modules/moduleonly/src/mod.mpp
diff --git a/tests/projects/c++/modules/staticlib_without_cpp/test.lua b/tests/projects/c++/modules/moduleonly/test.lua
index 7717f8049..7717f8049 100644
--- a/tests/projects/c++/modules/staticlib_without_cpp/test.lua
+++ b/tests/projects/c++/modules/moduleonly/test.lua
diff --git a/tests/projects/c++/modules/staticlib_without_cpp/xmake.lua b/tests/projects/c++/modules/moduleonly/xmake.lua
index a06091414..54c681cc8 100644
--- a/tests/projects/c++/modules/staticlib_without_cpp/xmake.lua
+++ b/tests/projects/c++/modules/moduleonly/xmake.lua
@@ -2,5 +2,5 @@ add_rules("mode.release", "mode.debug")
set_languages("c++20")
target("mod")
- set_kind("static")
+ set_kind("moduleonly")
add_files("src/mod.mpp")
diff --git a/tests/projects/c++/modules/packages/my-repo/packages/b/bar/src/bar.cpp b/tests/projects/c++/modules/packages/my-repo/packages/b/bar/src/bar.cpp
index 5bbb5e5c1..279541ed0 100644
--- a/tests/projects/c++/modules/packages/my-repo/packages/b/bar/src/bar.cpp
+++ b/tests/projects/c++/modules/packages/my-repo/packages/b/bar/src/bar.cpp
@@ -1,5 +1,5 @@
-module bar;
+#include <a.hpp>
-const char *bar() {
+const char *hello() {
return "Hello world";
-} \ No newline at end of file
+}
diff --git a/tests/projects/c++/modules/packages/my-repo/packages/b/bar/src/bar.mpp b/tests/projects/c++/modules/packages/my-repo/packages/b/bar/src/bar.mpp
index a13487fea..48ed6b73b 100644
--- a/tests/projects/c++/modules/packages/my-repo/packages/b/bar/src/bar.mpp
+++ b/tests/projects/c++/modules/packages/my-repo/packages/b/bar/src/bar.mpp
@@ -1,3 +1,12 @@
+module;
+
+#include <a.hpp>
+
export module bar;
-export const char *bar(); \ No newline at end of file
+export namespace bar {
+const char *hello() {
+ return ::hello();
+}
+}
+
diff --git a/tests/projects/c++/modules/packages/my-repo/packages/b/bar/src/include/a.hpp b/tests/projects/c++/modules/packages/my-repo/packages/b/bar/src/include/a.hpp
new file mode 100644
index 000000000..7cd8caadb
--- /dev/null
+++ b/tests/projects/c++/modules/packages/my-repo/packages/b/bar/src/include/a.hpp
@@ -0,0 +1,6 @@
+#ifndef A_HPP
+#define A_HPP
+
+[[gnu::visibility("default")]] const char *hello();
+
+#endif
diff --git a/tests/projects/c++/modules/packages/my-repo/packages/b/bar/src/xmake.lua b/tests/projects/c++/modules/packages/my-repo/packages/b/bar/src/xmake.lua
index 345dd2db4..d11bc4f60 100644
--- a/tests/projects/c++/modules/packages/my-repo/packages/b/bar/src/xmake.lua
+++ b/tests/projects/c++/modules/packages/my-repo/packages/b/bar/src/xmake.lua
@@ -3,5 +3,7 @@ set_languages("c++20")
target("bar")
set_kind("static")
+ add_headerfiles("include/(**.hpp)")
+ add_includedirs("include")
add_files("*.cpp")
- add_files("*.mpp", { install = true })
+ add_files("*.mpp", { public = true })
diff --git a/tests/projects/c++/modules/packages/my-repo/packages/b/bar/xmake.lua b/tests/projects/c++/modules/packages/my-repo/packages/b/bar/xmake.lua
index 301166807..1592fc623 100644
--- a/tests/projects/c++/modules/packages/my-repo/packages/b/bar/xmake.lua
+++ b/tests/projects/c++/modules/packages/my-repo/packages/b/bar/xmake.lua
@@ -3,4 +3,4 @@ package("bar")
on_install(function(package)
import("package.tools.xmake").install(package, {})
- end) \ No newline at end of file
+ end)
diff --git a/tests/projects/c++/modules/packages/my-repo/packages/b/bar2/src/bar.mpp b/tests/projects/c++/modules/packages/my-repo/packages/b/bar2/src/bar.mpp
new file mode 100644
index 000000000..ee9fb2c78
--- /dev/null
+++ b/tests/projects/c++/modules/packages/my-repo/packages/b/bar2/src/bar.mpp
@@ -0,0 +1,8 @@
+export module bar2;
+
+export namespace bar {
+ const char *hello2() {
+ return "Hello world";
+ }
+}
+
diff --git a/tests/projects/c++/modules/packages/my-repo/packages/b/bar2/src/xmake.lua b/tests/projects/c++/modules/packages/my-repo/packages/b/bar2/src/xmake.lua
new file mode 100644
index 000000000..0324fa931
--- /dev/null
+++ b/tests/projects/c++/modules/packages/my-repo/packages/b/bar2/src/xmake.lua
@@ -0,0 +1,6 @@
+add_rules("mode.release", "mode.debug")
+set_languages("c++20")
+
+target("bar2")
+ set_kind("moduleonly")
+ add_files("*.mpp")
diff --git a/tests/projects/c++/modules/packages/my-repo/packages/b/bar2/xmake.lua b/tests/projects/c++/modules/packages/my-repo/packages/b/bar2/xmake.lua
new file mode 100644
index 000000000..ffa208234
--- /dev/null
+++ b/tests/projects/c++/modules/packages/my-repo/packages/b/bar2/xmake.lua
@@ -0,0 +1,7 @@
+package("bar2")
+ set_kind("library")
+ set_sourcedir(path.join(os.scriptdir(), "src"))
+
+ on_install(function(package)
+ import("package.tools.xmake").install(package, {})
+ end)
diff --git a/tests/projects/c++/modules/packages/my-repo/packages/f/foo/src/foo.mpp b/tests/projects/c++/modules/packages/my-repo/packages/f/foo/src/foo.mpp
index 4cab3d977..a97f3d62b 100644
--- a/tests/projects/c++/modules/packages/my-repo/packages/f/foo/src/foo.mpp
+++ b/tests/projects/c++/modules/packages/my-repo/packages/f/foo/src/foo.mpp
@@ -1,5 +1,7 @@
export module foo;
export namespace foo {
+ #ifdef FOO_EXPORT
void say(const char *);
-} \ No newline at end of file
+ #endif
+}
diff --git a/tests/projects/c++/modules/packages/my-repo/packages/f/foo/src/xmake.lua b/tests/projects/c++/modules/packages/my-repo/packages/f/foo/src/xmake.lua
index d79b2ac54..9fa37e1d7 100644
--- a/tests/projects/c++/modules/packages/my-repo/packages/f/foo/src/xmake.lua
+++ b/tests/projects/c++/modules/packages/my-repo/packages/f/foo/src/xmake.lua
@@ -4,4 +4,4 @@ set_languages("c++20")
target("foo")
set_kind("static")
add_files("*.cpp")
- add_files("*.mpp", { install = true })
+ add_files("*.mpp", {defines = "FOO_EXPORT", public = true})
diff --git a/tests/projects/c++/modules/packages/src/main.cpp b/tests/projects/c++/modules/packages/src/main.cpp
index 10c501c4a..d6f2218c0 100644
--- a/tests/projects/c++/modules/packages/src/main.cpp
+++ b/tests/projects/c++/modules/packages/src/main.cpp
@@ -1,7 +1,9 @@
import foo;
import bar;
+import bar2;
int main() {
- foo::say(bar());
+ foo::say(bar::hello());
+ foo::say(bar::hello2());
return 0;
}
diff --git a/tests/projects/c++/modules/packages/xmake.lua b/tests/projects/c++/modules/packages/xmake.lua
index fe3cc7dc5..62dba119b 100644
--- a/tests/projects/c++/modules/packages/xmake.lua
+++ b/tests/projects/c++/modules/packages/xmake.lua
@@ -1,11 +1,11 @@
add_rules("mode.release", "mode.debug")
-set_languages("c++20")
+set_languages("c++2b")
add_repositories("my-repo my-repo")
-add_requires("foo", "bar")
+add_requires("foo", "bar", "bar2")
target("packages")
set_kind("binary")
add_files("src/*.cpp")
- add_packages("foo", "bar")
+ add_packages("foo", "bar", "bar2")
set_policy("build.c++.modules", true)
diff --git a/tests/projects/c++/modules/private_module/src/use.cpp b/tests/projects/c++/modules/private_module/src/use.cpp
new file mode 100644
index 000000000..f71a3057e
--- /dev/null
+++ b/tests/projects/c++/modules/private_module/src/use.cpp
@@ -0,0 +1,8 @@
+module use;
+
+import dep1;
+import dep2;
+
+int lib() {
+ return m() + i();
+}
diff --git a/tests/projects/c++/modules/private_module/src/use.mpp b/tests/projects/c++/modules/private_module/src/use.mpp
index 8450c341c..33f90c14a 100644
--- a/tests/projects/c++/modules/private_module/src/use.mpp
+++ b/tests/projects/c++/modules/private_module/src/use.mpp
@@ -1,6 +1,3 @@
-import dep1;
-import dep2;
+export module use;
-int lib() {
- return m() + i();
-} \ No newline at end of file
+export int lib();
diff --git a/tests/projects/c++/modules/private_module/xmake.lua b/tests/projects/c++/modules/private_module/xmake.lua
index 57ede6993..16a2bea92 100644
--- a/tests/projects/c++/modules/private_module/xmake.lua
+++ b/tests/projects/c++/modules/private_module/xmake.lua
@@ -5,3 +5,4 @@ target("private_module")
add_rules("c++")
set_kind("$(kind)")
add_files("src/*.mpp")
+ add_files("src/*.cpp")
diff --git a/tests/projects/c++/modules/staticlib/xmake.lua b/tests/projects/c++/modules/staticlib/xmake.lua
index ca0e9e343..9e1cf4221 100644
--- a/tests/projects/c++/modules/staticlib/xmake.lua
+++ b/tests/projects/c++/modules/staticlib/xmake.lua
@@ -3,7 +3,8 @@ set_languages("c++20")
target("mod")
set_kind("static")
- add_files("src/mod.mpp", "src/mod.cpp")
+ add_files("src/mod.mpp", {public = true})
+ add_files("src/mod.cpp")
target("hello")
set_kind("binary")
diff --git a/tests/projects/c++/modules/stdmodules/src/my_module.cpp b/tests/projects/c++/modules/stdmodules/src/my_module.cpp
index b87808a6e..ab3119785 100644
--- a/tests/projects/c++/modules/stdmodules/src/my_module.cpp
+++ b/tests/projects/c++/modules/stdmodules/src/my_module.cpp
@@ -2,4 +2,4 @@ module my_module;
import std;
-auto my_sum(size_t a, size_t b) -> size_t { return a + b; }
+auto my_sum(std::size_t a, std::size_t b) -> std::size_t { return a + b; }
diff --git a/tests/projects/c++/modules/stdmodules/src/my_module.mpp b/tests/projects/c++/modules/stdmodules/src/my_module.mpp
index bbebb2766..a1cd98e57 100644
--- a/tests/projects/c++/modules/stdmodules/src/my_module.mpp
+++ b/tests/projects/c++/modules/stdmodules/src/my_module.mpp
@@ -2,4 +2,4 @@ export module my_module;
import std;
-export auto my_sum(size_t a, size_t b) -> size_t;
+export auto my_sum(std::size_t a, std::size_t b) -> std::size_t;
diff --git a/tests/projects/c++/modules/stdmodules/xmake.lua b/tests/projects/c++/modules/stdmodules/xmake.lua
index 0603a4c51..02447729c 100644
--- a/tests/projects/c++/modules/stdmodules/xmake.lua
+++ b/tests/projects/c++/modules/stdmodules/xmake.lua
@@ -3,11 +3,10 @@ set_languages("c++latest")
target("mod")
set_kind("static")
- add_files("src/*.cpp", "src/*.mpp")
- set_policy("build.c++.clang.stdmodules", true)
+ add_files("src/*.cpp")
+ add_files("src/*.mpp", {public = true})
target("stdmodules")
set_kind("binary")
add_files("test/*.cpp")
add_deps("mod")
- set_policy("build.c++.clang.stdmodules", true)
diff --git a/tests/projects/c++/modules/stdmodules_cpp_only/xmake.lua b/tests/projects/c++/modules/stdmodules_cpp_only/xmake.lua
index d7acc0d85..6f86e1efc 100644
--- a/tests/projects/c++/modules/stdmodules_cpp_only/xmake.lua
+++ b/tests/projects/c++/modules/stdmodules_cpp_only/xmake.lua
@@ -6,5 +6,4 @@ target("stdmodules_cpp_only")
set_kind("binary")
add_files("src/*.cpp")
set_policy("build.c++.modules", true)
- set_policy("build.c++.clang.stdmodules", true)
diff --git a/tests/projects/c++/modules/stdmodules_multiple_targets/xmake.lua b/tests/projects/c++/modules/stdmodules_multiple_targets/xmake.lua
index 32bed613b..61d451908 100644
--- a/tests/projects/c++/modules/stdmodules_multiple_targets/xmake.lua
+++ b/tests/projects/c++/modules/stdmodules_multiple_targets/xmake.lua
@@ -5,10 +5,6 @@ target("mod")
set_kind("static")
add_files("src/*.cpp", "src/*.mpp")
- set_policy("build.c++.clang.stdmodules", true)
-
target("mod2")
set_kind("static")
add_files("src/*.cpp", "src/*.mpp")
-
- set_policy("build.c++.clang.stdmodules", true)
diff --git a/tests/projects/c++/modules/test_base.lua b/tests/projects/c++/modules/test_base.lua
index efef15520..219901c63 100644
--- a/tests/projects/c++/modules/test_base.lua
+++ b/tests/projects/c++/modules/test_base.lua
@@ -1,32 +1,68 @@
import("lib.detect.find_tool")
import("core.base.semver")
+import("utils.ci.is_running", {alias = "ci_is_running"})
function _build()
- local ci = (os.getenv("CI") or os.getenv("GITHUB_ACTIONS") or ""):lower()
- if ci == "true" then
- os.exec("xmake -rvD")
+ if ci_is_running() then
+ os.run("xmake -rvD")
else
- os.exec("xmake -r")
+ os.run("xmake -r")
+ end
+ local outdata = os.iorun("xmake")
+ if outdata then
+ if outdata:find("compiling") or outdata:find("linking") or outdata:find("generating") then
+ raise("Modules incremental compilation does not work\n%s", outdata)
+ end
+ end
+end
+
+function can_build()
+ if is_subhost("windows") then
+ return true
+ elseif is_subhost("msys") then
+ return true
+ 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
+ return true
+ end
+ local clang = find_tool("clang", {version = true})
+ if clang and clang.version and semver.compare(clang.version, "14.0") >= 0 then
+ return true
+ end
end
end
function main(t)
if is_subhost("windows") then
- os.exec("xmake f -c")
+ local clang = find_tool("clang", {version = true})
+ if clang and clang.version and semver.compare(clang.version, "14.0") >= 0 then
+ os.exec("xmake f --toolchain=clang -c --yes")
+ _build()
+ os.exec("xmake clean -a")
+ os.exec("xmake f --toolchain=clang --runtimes=c++_shared -c --yes")
+ _build()
+ end
+
+ os.exec("xmake clean -a")
+ os.exec("xmake f -c --yes")
+ _build()
+ elseif is_subhost("msys") then
+ os.exec("xmake f -c -p mingw --yes")
_build()
elseif is_host("linux") then
local gcc = find_tool("gcc", {version = true})
- if is_host("linux") and gcc and gcc.version and semver.compare(gcc.version, "11.0") >= 0 then
- os.exec("xmake f -c")
+ if gcc and gcc.version and semver.compare(gcc.version, "11.0") >= 0 then
+ os.exec("xmake f -c --yes")
_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")
+ os.exec("xmake f --toolchain=clang -c --yes")
_build()
os.exec("xmake clean -a")
- os.exec("xmake f --toolchain=clang --cxxflags=\"-stdlib=libc++\" -c")
+ os.exec("xmake f --toolchain=clang --runtimes=c++_shared -c --yes")
_build()
end
end
diff --git a/tests/projects/c++/modules/test_headerunits.lua b/tests/projects/c++/modules/test_headerunits.lua
index 3b664c96c..70e8b3d51 100644
--- a/tests/projects/c++/modules/test_headerunits.lua
+++ b/tests/projects/c++/modules/test_headerunits.lua
@@ -1,27 +1,51 @@
import("lib.detect.find_tool")
import("core.base.semver")
import("detect.sdks.find_vstudio")
+import("utils.ci.is_running", {alias = "ci_is_running"})
function _build()
- local ci = (os.getenv("CI") or os.getenv("GITHUB_ACTIONS") or ""):lower()
- if ci == "true" then
- os.exec("xmake -rvD")
+ if ci_is_running() then
+ os.run("xmake -rvD")
else
- os.exec("xmake -r")
+ os.run("xmake -r")
+ end
+ local outdata = os.iorun("xmake")
+ if outdata then
+ if outdata:find("compiling") or outdata:find("linking") or outdata:find("generating") then
+ raise("Modules incremental compilation does not work\n%s", outdata)
+ end
end
end
function main(t)
if is_subhost("windows") then
+ local clang = find_tool("clang", {version = true})
+ if clang and clang.version and semver.compare(clang.version, "15.0") >= 0 then
+ -- clang headerunit are bugged
+ -- os.exec("xmake f --toolchain=clang --policies=build.c++.clang.fallbackscanner -c --yes")
+ -- _build()
+ -- if semver.compare(clang.version, "17.0") >= 0 then
+ -- os.exec("xmake clean -a")
+ -- -- clang-scan-deps dependency detection doesn't support header units atm
+ -- os.exec("xmake f --toolchain=clang --policies=build.c++.clang.fallbackscanner --runtimes=c++_shared -c --yes")
+ -- _build()
+ -- end
+ end
+
local vs = find_vstudio()
if vs and vs["2022"] then
+ os.exec("xmake clean -a")
os.exec("xmake f -c --yes")
_build()
end
+ elseif is_subhost("msys") then
+ -- on windows, mingw modulemapper doesn't handle headeunit path correctly, but it's working with mingw on macOS / Linux
+ -- os.exec("xmake f -c -p mingw --yes")
+ -- _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
- -- gcc trtbd dependency detection doesn't support header units atm
+ -- gcc dependency detection doesn't support header units atm
os.exec("xmake f --policies=build.c++.gcc.fallbackscanner -c --yes")
_build()
end
@@ -30,15 +54,16 @@ function main(t)
if semver.compare(clang.version, "15.0") >= 0 then
os.exec("xmake clean -a")
-- clang-scan-deps dependency detection doesn't support header units atm
- os.exec("xmake f --toolchain=clang --policies=build.c++.clang.fallbackscanner -c")
+ os.exec("xmake f --toolchain=clang --policies=build.c++.clang.fallbackscanner -c --yes")
_build()
- -- elseif semver.compare(clang.version, "15.0") >= 0 then
- -- there is currently a bug on llvm git that prevent to build STL header units https://github.com/llvm/llvm-project/issues/58540
- -- os.exec("xmake clean -a")
- -- clang-scan-deps dependency detection doesn't support header units atm
- -- os.exec("xmake f --toolchain=clang --policies=build.c++.modules.fallbackscanner.clang --cxxflags=\"-stdlib=libc++\" -c")
- -- _build()
end
+ -- libc++ headerunit are bugged
+ -- if semver.compare(clang.version, "17.0") >= 0 then
+ -- os.exec("xmake clean -a")
+ -- -- clang-scan-deps dependency detection doesn't support header units atm
+ -- os.exec("xmake f --toolchain=clang --policies=build.c++.clang.fallbackscanner --runtimes=c++_shared -c --yes")
+ -- _build()
+ -- end
end
end
end
diff --git a/tests/projects/c++/modules/test_stdmodules.lua b/tests/projects/c++/modules/test_stdmodules.lua
index 360a56cbc..6c3a16b2c 100644
--- a/tests/projects/c++/modules/test_stdmodules.lua
+++ b/tests/projects/c++/modules/test_stdmodules.lua
@@ -1,44 +1,63 @@
import("lib.detect.find_tool")
import("core.base.semver")
import("core.tool.toolchain")
+import("utils.ci.is_running", {alias = "ci_is_running"})
function _build()
- local ci = (os.getenv("CI") or os.getenv("GITHUB_ACTIONS") or ""):lower()
- if ci == "true" then
- os.exec("xmake -rvD")
+ if ci_is_running() then
+ os.run("xmake -rvD")
else
- os.exec("xmake -r")
+ os.run("xmake -r")
+ end
+ local outdata = os.iorun("xmake")
+ if outdata then
+ if outdata:find("compiling") or outdata:find("linking") or outdata:find("generating") then
+ raise("Modules incremental compilation does not work\n%s", outdata)
+ end
end
end
function main(t)
if is_subhost("windows") then
+ local clang = find_tool("clang", {version = true})
+ if clang and clang.version and semver.compare(clang.version, "18.0") >= 0 then
+ -- clang don't support msstl std modules atm
+ -- os.exec("xmake f --toolchain=clang -c --yes")
+ -- _build()
+ os.exec("xmake clean -a")
+ os.exec("xmake f --toolchain=clang --runtimes=c++_shared -c --yes")
+ _build()
+ end
local msvc = toolchain.load("msvc")
if msvc and msvc:check() then
local vcvars = msvc:config("vcvars")
if vcvars and vcvars.VCInstallDir and vcvars.VCToolsVersion and semver.compare(vcvars.VCToolsVersion, "14.35") then
local stdmodulesdir = path.join(vcvars.VCInstallDir, "Tools", "MSVC", vcvars.VCToolsVersion, "modules")
if os.isdir(stdmodulesdir) then
- os.exec("xmake f -c")
+ os.exec("xmake clean -a")
+ os.exec("xmake f -c --yes")
_build()
end
end
end
+ elseif is_subhost("msys") then
+ -- os.exec("xmake f -c -p mingw --yes")
+ -- _build()
elseif is_host("linux") then -- or is_host("macosx") then
-- gcc don't support std modules atm
-- local gcc = find_tool("gcc", {version = true})
-- if is_host("linux") and gcc and gcc.version and semver.compare(gcc.version, "11.0") >= 0 then
- -- os.exec("xmake f -c")
+ -- os.exec("xmake f -c --yes")
-- _build()
-- end
local clang = find_tool("clang", {version = true})
- if clang and clang.version and semver.compare(clang.version, "14.0") >= 0 then
+ if clang and clang.version and semver.compare(clang.version, "18.0") >= 0 then
-- clang don't support libstdc++ std modules atm
-- os.exec("xmake clean -a")
- -- os.exec("xmake f --toolchain=clang -c")
+ -- os.exec("xmake f --toolchain=clang -c --yes")
-- _build()
os.exec("xmake clean -a")
- os.exec("xmake f --toolchain=clang --cxxflags=\"-stdlib=libc++\" -c")
+ os.exec("xmake f --toolchain=clang --runtimes=c++_shared -c --yes")
_build()
end
end
diff --git a/tests/projects/c++/modules/user_headerunit2/a/xmake.lua b/tests/projects/c++/modules/user_headerunit2/a/xmake.lua
index cffe161b2..a3c5906fc 100644
--- a/tests/projects/c++/modules/user_headerunit2/a/xmake.lua
+++ b/tests/projects/c++/modules/user_headerunit2/a/xmake.lua
@@ -1,4 +1,5 @@
target("a")
- set_languages("cxxlatest")
- set_kind("object")
+ set_kind("moduleonly")
+ add_headerfiles("*.hpp")
add_files("a.mpp")
+ set_languages("cxxlatest")
diff --git a/tests/projects/c++/modules/user_headerunit2/b/xmake.lua b/tests/projects/c++/modules/user_headerunit2/b/xmake.lua
index 98cff4124..7a353ec26 100644
--- a/tests/projects/c++/modules/user_headerunit2/b/xmake.lua
+++ b/tests/projects/c++/modules/user_headerunit2/b/xmake.lua
@@ -1,5 +1,5 @@
target("b")
add_deps("a")
- set_languages("cxxlatest")
- set_kind("object")
+ set_kind("moduleonly")
add_files("b.mpp")
+ set_languages("cxxlatest")
diff --git a/xmake/actions/build/build.lua b/xmake/actions/build/build.lua
index bb0b61cc1..1db197cd6 100644
--- a/xmake/actions/build/build.lua
+++ b/xmake/actions/build/build.lua
@@ -65,7 +65,7 @@ function _add_batchjobs_builtin(batchjobs, rootjob, target)
end
-- 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
+ if not job and (target:is_static() or target:is_binary() or target:is_shared() or target:is_object() or target:is_moduleonly()) then
job, job_leaf = import("kinds." .. target:kind(), {anonymous = true})(batchjobs, rootjob, target)
end
job = job or rootjob
diff --git a/xmake/actions/build/kinds/moduleonly.lua b/xmake/actions/build/kinds/moduleonly.lua
new file mode 100644
index 000000000..d177b8b97
--- /dev/null
+++ b/xmake/actions/build/kinds/moduleonly.lua
@@ -0,0 +1,22 @@
+--!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 moduleonly.lua
+--
+
+-- imports
+inherit("object")
diff --git a/xmake/actions/package/local/main.lua b/xmake/actions/package/local/main.lua
index 90495dae4..f27cbd091 100644
--- a/xmake/actions/package/local/main.lua
+++ b/xmake/actions/package/local/main.lua
@@ -25,6 +25,8 @@ import("core.project.rule")
import("core.project.config")
import("core.project.project")
import("core.base.bit")
+import("rules.c++.modules.modules_support.compiler_support", {alias = "module_compiler_support", rootdir = os.programdir()})
+import("rules.c++.modules.modules_support.builder", {alias = "module_builder", rootdir = os.programdir()})
-- get library deps
function _get_librarydeps(target)
@@ -95,6 +97,7 @@ function _package_library(target)
local binarydir = path.join(packagedir, target:plat(), target:arch(), config.mode(), "bin")
local librarydir = path.join(packagedir, target:plat(), target:arch(), config.mode(), "lib")
local headerdir = path.join(packagedir, target:plat(), target:arch(), config.mode(), "include")
+ local modulesdir = path.join(packagedir, target:plat(), target:arch(), config.mode(), "modules")
-- copy the library file to the output directory
local targetfile = target:targetfile()
@@ -142,6 +145,30 @@ function _package_library(target)
end
end
+ -- copy modules
+ if target:data("cxx.has_modules") then
+
+ local modules = module_compiler_support.localcache():get2(target:name(), "c++.modules")
+ module_builder.generate_metadata(target, modules)
+
+ local sourcebatch = target:sourcebatches()["c++.build.modules.install"]
+ if sourcebatch and sourcebatch.sourcefiles then
+ for _, sourcefile in ipairs(sourcebatch.sourcefiles) do
+ local fileconfig = target:fileconfig(sourcefile)
+ local install = fileconfig and fileconfig.public or false
+ if install then
+ local modulehash = module_compiler_support.get_modulehash(target, sourcefile)
+ local prefixdir = path.join(modulesdir, modulehash)
+ os.vcp(sourcefile, path.join(prefixdir, path.filename(sourcefile)))
+ local metafile = module_compiler_support.get_metafile(target, sourcefile)
+ if os.exists(metafile) then
+ os.vcp(metafile, path.join(prefixdir, path.filename(metafile)))
+ end
+ end
+ end
+ end
+ end
+
-- generate xmake.lua
local file = io.open(path.join(packagedir, "xmake.lua"), "w")
if file then
@@ -261,6 +288,78 @@ function _package_headeronly(target)
print("package(%s): %s generated", packagename, packagedir)
end
+function _package_moduleonly(target)
+
+ -- get the output directory
+ local packagedir = target:packagedir()
+ local packagename = target:name():lower()
+ local headerdir = path.join(packagedir, target:plat(), target:arch(), config.mode(), "include")
+ local modulesdir = path.join(packagedir, target:plat(), target:arch(), config.mode(), "modules")
+
+ local modules = module_compiler_support.localcache():get2(target:name(), "c++.modules")
+ module_builder.generate_metadata(target, modules)
+
+ -- copy headers
+ local srcheaders, dstheaders = target:headerfiles(headerdir)
+ if srcheaders and dstheaders then
+ local i = 1
+ for _, srcheader in ipairs(srcheaders) do
+ local dstheader = dstheaders[i]
+ if dstheader then
+ os.vcp(srcheader, dstheader)
+ end
+ i = i + 1
+ end
+ end
+
+ -- copy modules
+ local sourcebatch = target:sourcebatches()["c++.build.modules.install"]
+ if sourcebatch and sourcebatch.sourcefiles then
+ for _, sourcefile in ipairs(sourcebatch.sourcefiles) do
+ local modulehash = module_compiler_support.get_modulehash(target, sourcefile)
+ local prefixdir = path.join(modulesdir, modulehash)
+ os.vcp(sourcefile, path.join(prefixdir, path.filename(sourcefile)))
+ local metafile = module_compiler_support.get_metafile(target, sourcefile)
+ if os.exists(metafile) then
+ os.vcp(metafile, path.join(prefixdir, path.filename(metafile)))
+ end
+ end
+ end
+
+ -- generate xmake.lua
+ local file = io.open(path.join(packagedir, "xmake.lua"), "w")
+ if file then
+ local deps = _get_librarydeps(target)
+ file:print("package(\"%s\")", packagename)
+ local homepage = option.get("homepage")
+ if homepage then
+ file:print(" set_homepage(\"%s\")", homepage)
+ end
+ local description = option.get("description") or ("The " .. packagename .. " package")
+ file:print(" set_description(\"%s\")", description)
+ if target:license() then
+ file:print(" set_license(\"%s\")", target:license())
+ end
+ if #deps > 0 then
+ file:print(" add_deps(\"%s\")", table.concat(deps, "\", \""))
+ end
+ file:print("")
+ file:print([[
+ on_load(function (package)
+ package:set("installdir", path.join(os.scriptdir(), package:plat(), package:arch(), package:mode()))
+ end)
+
+ on_fetch(function (package)
+ local result = {}
+ result.includedirs = package:installdir("include")
+ return result
+ end)]])
+ file:close()
+ end
+
+ -- show tips
+ print("package(%s): %s generated", packagename, packagedir)
+end
-- do package target
function _do_package_target(target)
if not target:is_phony() then
@@ -269,6 +368,7 @@ function _do_package_target(target)
binary = _package_binary
, static = _package_library
, shared = _package_library
+ , moduleonly = _package_moduleonly
, headeronly = _package_headeronly
}
local kind = target:kind()
diff --git a/xmake/core/project/policy.lua b/xmake/core/project/policy.lua
index e7212189e..64fc23883 100644
--- a/xmake/core/project/policy.lua
+++ b/xmake/core/project/policy.lua
@@ -64,8 +64,12 @@ function policy.policies()
["build.sanitizer.undefined"] = {description = "Enable undefined sanitizer for c/c++ building.", type = "boolean"},
-- Enable C++ modules for C++ building, even if no .mpp is involved in the compilation
["build.c++.modules"] = {description = "Enable C++ modules for C++ building.", type = "boolean"},
- -- Enable clang std modulemap
- ["build.c++.clang.stdmodules"] = {description = "Enable clang std modulemap.", default = false, type = "boolean"},
+ -- Enable std module
+ ["build.c++.modules.std"] = {description = "Enable std modules.", default = true, type = "boolean"},
+ -- Try to reuse compiled module bmi file if targets flags permit it
+ ["build.c++.modules.tryreuse"] = {description = "Try to reuse compiled module if possible.", default = true, type = "boolean"},
+ -- Enable module taking defines acbount for bmi reuse discrimination
+ ["build.c++.modules.tryreuse.discriminate_on_defines"] = {description = "Enable defines module reuse discrimination.", default = false, type = "boolean"},
-- Force C++ modules fallback dependency scanner for clang
["build.c++.clang.fallbackscanner"] = {description = "Force clang fallback module dependency scanner.", default = false, type = "boolean"},
-- Force C++ modules fallback dependency scanner for msvc
diff --git a/xmake/core/project/target.lua b/xmake/core/project/target.lua
index a1b2e414b..208054ff3 100644
--- a/xmake/core/project/target.lua
+++ b/xmake/core/project/target.lua
@@ -1228,9 +1228,14 @@ function _instance:is_headeronly()
return self:kind() == "headeronly"
end
+-- is moduleonly target?
+function _instance:is_moduleonly()
+ return self:kind() == "moduleonly"
+end
+
-- is library target?
function _instance:is_library()
- return self:is_static() or self:is_shared() or self:is_headeronly()
+ return self:is_static() or self:is_shared() or self:is_headeronly() or self:is_moduleonly()
end
-- is default target?
@@ -1587,7 +1592,7 @@ end
function _instance:filename()
-- no target file?
- if self:is_object() or self:is_phony() or self:is_headeronly() then
+ if self:is_object() or self:is_phony() or self:is_headeronly() or self:is_moduleonly() then
return
end
diff --git a/xmake/modules/private/check/checkers/api/target/kind.lua b/xmake/modules/private/check/checkers/api/target/kind.lua
index 127f78ad5..5d716b129 100644
--- a/xmake/modules/private/check/checkers/api/target/kind.lua
+++ b/xmake/modules/private/check/checkers/api/target/kind.lua
@@ -23,5 +23,5 @@ import(".api_checker")
function main(opt)
opt = opt or {}
- api_checker.check_targets("kind", table.join(opt, {values = {"object", "binary", "static", "shared", "headeronly", "phony"}}))
+ api_checker.check_targets("kind", table.join(opt, {values = {"object", "binary", "static", "shared", "headeronly", "moduleonly", "phony"}}))
end
diff --git a/xmake/modules/target/action/uninstall/main.lua b/xmake/modules/target/action/uninstall/main.lua
index cc5feb3ac..daa23917c 100644
--- a/xmake/modules/target/action/uninstall/main.lua
+++ b/xmake/modules/target/action/uninstall/main.lua
@@ -26,6 +26,12 @@ function _uninstall_files(target)
end
end
+-- uninstall modules
+function _uninstall_modules(target, opt)
+ local moduledir = path.join(target:installdir(), opt and opt.moduledir or "modules")
+ os.vrm(moduledir)
+end
+
-- the builtin uninstall main entry
function main(target, opt)
@@ -47,6 +53,9 @@ function main(target, opt)
end
end
+ -- remove modules
+ _uninstall_modules(target, opt)
+
-- uninstall the other files
_uninstall_files(target)
end
diff --git a/xmake/plugins/project/cmake/cmakelists.lua b/xmake/plugins/project/cmake/cmakelists.lua
index 6fcf38313..4402f080f 100644
--- a/xmake/plugins/project/cmake/cmakelists.lua
+++ b/xmake/plugins/project/cmake/cmakelists.lua
@@ -68,6 +68,7 @@ end
function _escape_path(filepath)
if is_host("windows") then
filepath = filepath:gsub('\\', '/')
+ filepath = filepath:gsub(' ', '\\ ')
end
return filepath
end
@@ -323,6 +324,11 @@ function _add_target_headeronly(cmakelists, target)
cmakelists:print("add_library(%s INTERFACE)", target:name())
end
+-- add target: headeronly
+function _add_target_moduleonly(cmakelists, target)
+ cmakelists:print("add_custom_target(%s)", target:name())
+end
+
-- add target dependencies
function _add_target_dependencies(cmakelists, target)
local deps = target:get("deps")
@@ -340,8 +346,8 @@ function _add_target_sources(cmakelists, target, outputdir)
local has_cuda = false
cmakelists:print("target_sources(%s PRIVATE", target:name())
local sourcebatches = target:sourcebatches()
- for _, sourcebatch in table.orderpairs(sourcebatches) do
- if _sourcebatch_is_built(sourcebatch) then
+ for name, sourcebatch in table.orderpairs(sourcebatches) do
+ if _sourcebatch_is_built(sourcebatch) and not name:startswith("c++.build.modules") then
for _, sourcefile in ipairs(sourcebatch.sourcefiles) do
cmakelists:print(" " .. _get_relative_unix_path(sourcefile, outputdir))
end
@@ -1003,6 +1009,9 @@ function _add_target(cmakelists, target, outputdir)
_add_target_headeronly(cmakelists, target)
_add_target_include_directories(cmakelists, target, outputdir)
return
+ elseif targetkind == 'moduleonly' then
+ _add_target_moduleonly(cmakelists, target)
+ return
else
raise("unknown target kind %s", target:kind())
end
diff --git a/xmake/plugins/project/vstudio/impl/vs201x.lua b/xmake/plugins/project/vstudio/impl/vs201x.lua
index a8b48dada..b3c6f84c2 100644
--- a/xmake/plugins/project/vstudio/impl/vs201x.lua
+++ b/xmake/plugins/project/vstudio/impl/vs201x.lua
@@ -185,7 +185,6 @@ function _make_custom_commands_for_objectrules(commands, target, sourcebatch, vc
scriptname = "buildcmd_file" .. (suffix and ("_" .. suffix) or "")
script = ruleinst:script(scriptname)
if script then
- local sourcekind = sourcebatch.sourcekind
for _, sourcefile in ipairs(sourcebatch.sourcefiles) do
local batchcmds_ = batchcmds.new({target = target})
script(target, batchcmds_, sourcefile, {})
@@ -219,7 +218,7 @@ function _make_custom_commands(target, vcxprojdir)
for _, sourcebatch in table.orderpairs(sourcebatches) do
local rulename = sourcebatch.rulename
local sourcekind = sourcebatch.sourcekind
- if rulename ~= "c.build" and rulename ~= "c++.build" and rulename ~= "asm.build" and rulename ~= "cuda.build" and sourcekind ~= "mrc" then
+ if rulename ~= "c.build" and rulename ~= "c++.build" and not rulename:startswith("c++.build.modules") 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")
@@ -256,6 +255,9 @@ function _make_targetinfo(mode, arch, target, vcxprojdir)
-- save symbols
targetinfo.symbols = target:get("symbols")
+ -- has modules
+ targetinfo.has_modules = target:data("cxx.has_modules")
+
-- save target kind
targetinfo.targetkind = target:kind()
if target:is_phony() or target:is_headeronly() then
@@ -268,9 +270,6 @@ function _make_targetinfo(mode, arch, target, vcxprojdir)
-- save symbol file
targetinfo.symbolfile = target:symbolfile()
- -- save sourcebatches
- targetinfo.sourcebatches = target:sourcebatches()
-
-- save sourcekinds
targetinfo.sourcekinds = target:sourcekinds()
@@ -289,9 +288,9 @@ function _make_targetinfo(mode, arch, target, vcxprojdir)
local sourcekind = sourcebatch.sourcekind
local rulename = sourcebatch.rulename
if sourcekind then
- for idx, sourcefile in ipairs(sourcebatch.sourcefiles) do
+ for _, sourcefile in ipairs(sourcebatch.sourcefiles) do
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
+ if not firstcompflags and (rulename == "c.build" or rulename == "c++.build" or rulename == "c++.build.modules" or rulename == "cuda.build") then
firstcompflags = compflags
end
targetinfo.compflags[sourcefile] = compflags
@@ -309,8 +308,11 @@ function _make_targetinfo(mode, arch, target, vcxprojdir)
end
end
+ -- save sourcebatches
+ targetinfo.sourcebatches = target:sourcebatches()
+
-- save linker flags
- local linkflags = linker.linkflags(target:kind(), target:sourcekinds(), {target = target})
+ local linkflags = linker.linkflags(target:is_moduleonly() and 'static' or target:kind(), target:sourcekinds(), {target = target})
targetinfo.linkflags = linkflags
if table.contains(target:sourcekinds(), "cu") then
@@ -323,7 +325,7 @@ function _make_targetinfo(mode, arch, target, vcxprojdir)
end
-- save execution dir (when executed from VS)
- targetinfo.rundir = target:rundir()
+ targetinfo.rundir = target:is_moduleonly() and "" or target:rundir()
-- save runenvs
local targetrunenvs = {}
@@ -544,6 +546,13 @@ function make(outputdir, vsinfo)
-- save file groups
_target.filegroups = table.unique(table.join(_target.filegroups or {}, target:get("filegroups")))
+ -- save references to deps
+ for _, dep in ipairs(target:orderdeps()) do
+ _target.deps = _target.deps or {}
+ local dep_name = dep:name()
+ _target.deps[dep_name] = path.relative(path.join(vsinfo.solution_dir, dep_name, dep_name .. ".vcxproj"), _target.project_dir)
+ end
+
for filegroup, groupconf in pairs(target:extraconf("filegroups")) do
_target.filegroups_extraconf = _target.filegroups_extraconf or {}
local mergedconf = _target.filegroups_extraconf[filegroup]
diff --git a/xmake/plugins/project/vstudio/impl/vs201x_vcxproj.lua b/xmake/plugins/project/vstudio/impl/vs201x_vcxproj.lua
index f5bb88d73..ee21e88b1 100644
--- a/xmake/plugins/project/vstudio/impl/vs201x_vcxproj.lua
+++ b/xmake/plugins/project/vstudio/impl/vs201x_vcxproj.lua
@@ -218,6 +218,17 @@ function _make_header(vcxprojfile, vsinfo)
vcxprojfile:enter("<Project DefaultTargets=\"Build\" ToolsVersion=\"%s.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">", assert(vsinfo.project_version))
end
+-- make references
+function _make_references(vcxprojfile, vsinfo, target)
+ vcxprojfile:print("<ItemGroup>")
+ for dep_name, dep_vcxprojfile in pairs(target.deps) do
+ vcxprojfile:print("<ProjectReference Include=\"%s\">", dep_vcxprojfile)
+ vcxprojfile:print("<Project>{%s}</Project>", hash.uuid4(dep_name))
+ vcxprojfile:print("</ProjectReference>")
+ end
+ vcxprojfile:print("</ItemGroup>")
+end
+
-- make tailer
function _make_tailer(vcxprojfile, vsinfo, target)
vcxprojfile:print("<Import Project=\"%$(VCTargetsPath)\\Microsoft.Cpp.targets\" />")
@@ -242,6 +253,7 @@ function _make_configurations(vcxprojfile, vsinfo, target)
binary = "Application"
, shared = "DynamicLibrary"
, static = "StaticLibrary"
+ , moduleonly = "StaticLibrary" -- emulate moduleonly with staticlib
}
-- make ProjectConfigurations
@@ -307,8 +319,10 @@ function _make_configurations(vcxprojfile, vsinfo, target)
vcxprojfile:enter("<PropertyGroup Condition=\"\'%$(Configuration)|%$(Platform)\'==\'%s|%s\'\">", targetinfo.mode, targetinfo.arch)
vcxprojfile:print("<OutDir>%s\\</OutDir>", _make_dirs(targetinfo.targetdir, target.project_dir))
vcxprojfile:print("<IntDir>%s\\</IntDir>", _make_dirs(targetinfo.objectdir, target.project_dir))
- vcxprojfile:print("<TargetName>%s</TargetName>", path.basename(targetinfo.targetfile))
- vcxprojfile:print("<TargetExt>%s</TargetExt>", path.extension(targetinfo.targetfile))
+ if targetinfo.targetfile then
+ vcxprojfile:print("<TargetName>%s</TargetName>", path.basename(targetinfo.targetfile))
+ vcxprojfile:print("<TargetExt>%s</TargetExt>", path.extension(targetinfo.targetfile))
+ end
if target.kind == "binary" then
vcxprojfile:print("<LinkIncremental>true</LinkIncremental>")
@@ -723,7 +737,7 @@ endlocal &amp; call :xmErrorLevel %errorlevel% &amp; goto :xmDone
exit /b %1
:xmDone
if %errorlevel% neq 0 goto :VCEnd]]
- vcxprojfile:print("<Command>%s</Command>", cmdstr)
+ vcxprojfile:print("<Command>%s</Command>", cmdstr:replace("<", " &lt;"):replace(">", "&gt;"):replace("/Fo ", "/Fo"))
if suffix == "after" or suffix == "after_link" then
vcxprojfile:print("</PostBuildEvent>")
elseif suffix == "before" then
@@ -742,12 +756,12 @@ end
-- make common item
function _make_common_item(vcxprojfile, vsinfo, target, targetinfo)
-
-- init the linker kinds
local linkerkinds =
{
binary = "Link"
, static = "Lib"
+ , moduleonly = "Lib" -- emulate moduleonly with staticlib
, shared = "Link"
}
if not linkerkinds[targetinfo.targetkind] then
@@ -892,6 +906,10 @@ function _make_common_item(vcxprojfile, vsinfo, target, targetinfo)
vcxprojfile:print("<LanguageStandard_C>%s</LanguageStandard_C>", cstandard)
end
+ if targetinfo.has_modules then
+ vcxprojfile:enter("<ScanSourceForModuleDependencies>true</ScanSourceForModuleDependencies>")
+ end
+
-- use c or c++ precompiled header
local pcheader = target.pcxxheader or target.pcheader
if pcheader then
@@ -959,9 +977,8 @@ function _build_common_items(vsinfo, target)
for _, sourcebatch in pairs(targetinfo.sourcebatches) do
local sourcekind = sourcebatch.sourcekind
local rulename = sourcebatch.rulename
- if (rulename == "c.build" or rulename == "c++.build" or rulename == "asm.build" or sourcekind == "mrc") then
+ if (rulename == "c.build" or rulename == "c++.build" or rulename == "c++.build.modules" or rulename == "asm.build" or sourcekind == "mrc") then
for _, sourcefile in ipairs(sourcebatch.sourcefiles) do
-
-- make compiler flags
local flags = _make_compflags(sourcefile, targetinfo, target.project_dir)
@@ -1028,7 +1045,7 @@ function _build_common_items(vsinfo, target)
for _, sourcefile in ipairs(sourcebatch.sourcefiles) do
sourceflags[sourcefile] = targetinfo.sourceflags[sourcefile]
end
- elseif rulename == "c.build" or rulename == "c++.build" then -- sourcekind maybe bind multiple rules, e.g. c++modules
+ elseif rulename == "c.build" or rulename == "c++.build" or rulename == "c++.build.modules" then -- sourcekind maybe bind multiple rules, e.g. c++modules
for _, sourcefile in ipairs(sourcebatch.sourcefiles) do
local flags = targetinfo.sourceflags[sourcefile]
local otherflags = {}
@@ -1062,7 +1079,7 @@ function _make_common_items(vcxprojfile, vsinfo, target)
-- for each mode and arch
for _, targetinfo in ipairs(target.info) do
-- make common item
- _make_common_item(vcxprojfile, vsinfo, target, targetinfo, target.project_dir)
+ _make_common_item(vcxprojfile, vsinfo, target, targetinfo)
end
end
@@ -1325,6 +1342,19 @@ function _make_source_files(vcxprojfile, vsinfo, target)
sourceinfos[sourcefile] = sourceinfos[sourcefile] or {}
table.insert(sourceinfos[sourcefile], {targetinfo = targetinfo, mode = targetinfo.mode, arch = targetinfo.arch, sourcekind = sourcekind, objectfile = objectfile, flags = flags, compargv = targetinfo.compargvs[sourcefile]})
end
+ elseif rulename == "c++.build.modules" then
+ local builder_batch = targetinfo.sourcebatches["c++.build.modules.builder"]
+ table.sort(builder_batch.objectfiles)
+ local objectfiles = builder_batch.objectfiles
+ for idx, sourcefile in ipairs(sourcebatch.sourcefiles) do
+ local is_named_module = table.contains(builder_batch.sourcefiles, sourcefile)
+ if is_named_module then
+ local objectfile = objectfiles[idx]
+ local flags = targetinfo.sourceflags[sourcefile]
+ sourceinfos[sourcefile] = sourceinfos[sourcefile] or {}
+ table.insert(sourceinfos[sourcefile], {targetinfo = targetinfo, mode = targetinfo.mode, arch = targetinfo.arch, sourcekind = "cxx", objectfile = objectfile, flags = flags, compargv = targetinfo.compargvs[sourcefile]})
+ end
+ end
end
end
end
@@ -1386,6 +1416,9 @@ function make(vsinfo, target)
-- make source files
_make_source_files(vcxprojfile, vsinfo, target)
+ -- make deps references
+ _make_references(vcxprojfile, vsinfo, target)
+
-- make tailer
_make_tailer(vcxprojfile, vsinfo, target)
diff --git a/xmake/plugins/project/vsxmake/getinfo.lua b/xmake/plugins/project/vsxmake/getinfo.lua
index e992c1bd4..af88b51f6 100644
--- a/xmake/plugins/project/vsxmake/getinfo.lua
+++ b/xmake/plugins/project/vsxmake/getinfo.lua
@@ -154,7 +154,7 @@ function _make_targetinfo(mode, arch, target)
-- fix c++17 to cxx17 for Xmake.props
targetinfo.languages = targetinfo.languages:replace("c++", "cxx", {plain = true})
end
- if target:is_phony() or target:is_headeronly() then
+ if target:is_phony() or target:is_headeronly() or target:is_moduleonly() then
return targetinfo
end
diff --git a/xmake/rules/c++/modules/modules_support/builder.lua b/xmake/rules/c++/modules/modules_support/builder.lua
new file mode 100644
index 000000000..523a421b0
--- /dev/null
+++ b/xmake/rules/c++/modules/modules_support/builder.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, Arthapz
+-- @file common.lua
+--
+
+-- imports
+import("core.base.json")
+import("core.base.option")
+import("async.runjobs")
+import("private.async.buildjobs")
+import("core.tool.compiler")
+import("core.project.config")
+import("core.project.depend")
+import("utils.progress")
+import("compiler_support")
+import("dependency_scanner")
+
+-- build target modules
+function _build_modules(target, sourcebatch, modules, opt)
+ local objectfiles = sourcebatch.objectfiles
+ for _, objectfile in ipairs(objectfiles) do
+ local module = modules[objectfile]
+ if not module then
+ goto continue
+ end
+
+ local name, _, cppfile = compiler_support.get_provided_module(module)
+ cppfile = cppfile or module.cppfile
+
+ local deps = {}
+ for _, dep in ipairs(table.keys(module.requires or {})) do
+ table.insert(deps, opt.batchjobs and target:name() .. dep or dep)
+ end
+
+ opt.build_module(deps, module, name, objectfile, cppfile)
+
+ ::continue::
+ end
+end
+
+-- build target headerunits
+function _build_headerunits(target, headerunits, opt)
+
+ local outputdir = compiler_support.headerunits_cachedir(target, {mkdir = true})
+ if opt.stl_headerunit then
+ outputdir = path.join(outputdir, "stl")
+ end
+
+ for _, headerunit in ipairs(headerunits) do
+ local outputdir = outputdir
+ if opt.stl_headerunit and headerunit.name:startswith("experimental/") then
+ outputdir = path.join(outputdir, "experimental")
+ end
+ local bmifile = path.join(outputdir, path.filename(headerunit.name) .. compiler_support.get_bmi_extension(target))
+ local key = path.normalize(headerunit.path)
+ local build = should_build(target, headerunit.path, bmifile, {key = key, headerunit = true})
+
+ if build then
+ mark_build(target, key)
+ end
+
+ opt.build_headerunit(headerunit, key, bmifile, outputdir, build)
+ end
+end
+
+-- check if flags are compatible for module reuse
+function _are_flags_compatible(target, other, cppfile)
+ local compinst1 = target:compiler("cxx")
+ local flags1 = compinst1:compflags({sourcefile = cppfile, target = target})
+
+ local compinst2 = other:compiler("cxx")
+ local flags2 = compinst2:compflags({sourcefile = cppfile, target = other})
+
+ -- strip unrelevent flags
+ flags1 = compiler_support.strip_flags(target, flags1)
+ flags2 = compiler_support.strip_flags(target, flags2)
+
+ if #flags1 ~= #flags2 then
+ return false
+ end
+
+ table.sort(flags1)
+ table.sort(flags2)
+
+ for i = 1, #flags1 do
+ if flags1[i] ~= flags2[i] then
+ return false
+ end
+ end
+
+ return true
+end
+
+-- try to reuse modules from other target
+function _try_reuse_modules(target, modules)
+ for _, module in pairs(modules) do
+ local name, provide, cppfile = compiler_support.get_provided_module(module)
+ if not provide then
+ goto continue
+ end
+
+ cppfile = cppfile or module.cppfile
+
+ local fileconfig = target:fileconfig(cppfile)
+ local public = fileconfig and (fileconfig.public or fileconfig.external)
+ if not public then
+ goto continue
+ end
+
+ for _, dep in ipairs(target:orderdeps()) do
+ if not _are_flags_compatible(target, dep, cppfile) then
+ goto nextdep
+ end
+ local mapped = get_from_target_mapper(dep, name)
+ if mapped then
+ compiler_support.memcache():set2(target:name() .. name, "reuse", true)
+ add_module_to_target_mapper(target, mapped.name, mapped.sourcefile, mapped.bmi, table.join(mapped.opt or {}, {target = dep}))
+ break
+ end
+ ::nextdep::
+ end
+
+ ::continue::
+ end
+ return modules
+end
+
+-- should we build this module or headerunit ?
+function should_build(target, sourcefile, bmifile, opt)
+ opt = opt or {}
+ local objectfile = opt.objectfile
+ local compinst = compiler.load("cxx", {target = target})
+ local compflags = compinst:compflags({sourcefile = sourcefile, target = target})
+ local dependfile = target:dependfile(bmifile or objectfile)
+ local dependinfo = target:is_rebuilt() and {} or (depend.load(dependfile) or {})
+ local depvalues = {compinst:program(), compflags}
+
+ -- force rebuild a module if any of its module dependency is rebuilt
+ local requires = opt.requires
+ if requires then
+ for required, _ in table.orderpairs(requires) do
+ local m = get_from_target_mapper(target, required)
+ if m then
+ local rebuild = (m.opt and m.opt.target) and compiler_support.memcache():get2("should_build_in_" .. m.opt.target:name(), m.key)
+ or compiler_support.memcache():get2("should_build_in_" .. target:name(), m.key)
+ if rebuild then
+ dependinfo.files = {}
+ table.insert(dependinfo.files, sourcefile)
+ dependinfo.values = depvalues
+ return true, dependinfo
+ end
+ end
+ end
+ end
+
+ -- reused
+ if opt.name then
+ local m = get_from_target_mapper(target, opt.name)
+ if m and m.opt and m.opt.target then
+ local rebuild = compiler_support.memcache():get2("should_build_in_" .. m.opt.target:name(), m.key)
+ if rebuild then
+ dependinfo.files = {}
+ table.insert(dependinfo.files, sourcefile)
+ dependinfo.values = depvalues
+ end
+ return rebuild, dependinfo
+ end
+ end
+
+ -- need build this object?
+ local dryrun = option.get("dry-run")
+ local lastmtime = os.isfile(bmifile or objectfile) and os.mtime(dependfile) or 0
+ if dryrun or depend.is_changed(dependinfo, {lastmtime = lastmtime, values = depvalues}) then
+ dependinfo.files = {}
+ table.insert(dependinfo.files, sourcefile)
+ dependinfo.values = depvalues
+ return true, dependinfo
+ end
+ return false
+end
+
+-- generate meta module informations for package / other buildsystems import
+--
+-- e.g
+-- {
+-- "defines": ["FOO=BAR"]
+-- "imports": ["std", "bar"]
+-- "name": "foo"
+-- "file": "foo.cppm"
+-- }
+function _generate_meta_module_info(target, name, sourcefile, requires)
+
+ local modulehash = compiler_support.get_modulehash(target, sourcefile)
+ local module_metadata = {name = name, file = path.join(modulehash, path.filename(sourcefile))}
+
+ -- add definitions
+ module_metadata.defines = _builder(target).get_module_required_defines(target, sourcefile)
+
+ -- add imports
+ if requires then
+ for _name, _ in table.orderpairs(requires) do
+ module_metadata.imports = module_metadata.imports or {}
+ table.append(module_metadata.imports, _name)
+ end
+ end
+ return module_metadata
+end
+
+function _target_module_map_cachekey(target)
+ local mode = config.mode()
+ return target:name() .. "module_mapper" .. (mode or "")
+end
+
+function _is_duplicated_headerunit(target, key)
+ local _, mapper_keys = get_target_module_mapper(target)
+ return mapper_keys[key]
+end
+
+function _builder(target)
+ local cachekey = tostring(target)
+ local builder = compiler_support.memcache():get2("builder", cachekey)
+ if builder == nil then
+ if target:has_tool("cxx", "clang", "clangxx") then
+ builder = import("clang.builder", {anonymous = true})
+ elseif target:has_tool("cxx", "gcc", "gxx") then
+ builder = import("gcc.builder", {anonymous = true})
+ elseif target:has_tool("cxx", "cl") then
+ builder = import("msvc.builder", {anonymous = true})
+ else
+ local _, toolname = target:tool("cxx")
+ raise("compiler(%s): does not support c++ module!", toolname)
+ end
+ compiler_support.memcache():set2("builder", cachekey, builder)
+ end
+ return builder
+end
+
+function mark_build(target, name)
+ compiler_support.memcache():set2("should_build_in_" .. target:name(), name, true)
+end
+
+-- build batchjobs for modules
+function build_batchjobs_for_modules(modules, batchjobs, rootjob)
+ return buildjobs(modules, batchjobs, rootjob)
+end
+
+-- build modules for batchjobs
+function build_modules_for_batchjobs(target, batchjobs, sourcebatch, modules, opt)
+ opt.rootjob = batchjobs:group_leave() or opt.rootjob
+ batchjobs:group_enter(target:name() .. "/build_modules", {rootjob = opt.rootjob})
+
+ batchjobs:addjob(target:name() .. "_populate_module_map", function(_, _)
+ _try_reuse_modules(target, modules)
+ _builder(target).populate_module_map(target, modules)
+ end, {rootjob = opt.rootjob})
+
+ local modulesjobs = {}
+ _build_modules(target, sourcebatch, modules, table.join(opt, {
+ build_module = function(deps, module, name, objectfile, cppfile)
+ local job_name = name and target:name() .. name or cppfile
+ modulesjobs[job_name] = _builder(target).make_module_buildjobs(target, batchjobs, job_name, deps,
+ {module = module, objectfile = objectfile, cppfile = cppfile})
+ end
+ }))
+
+ -- build batchjobs for modules
+ build_batchjobs_for_modules(modulesjobs, batchjobs, opt.rootjob)
+end
+
+-- build modules for batchcmds
+function build_modules_for_batchcmds(target, batchcmds, sourcebatch, modules, opt)
+
+ local depmtime = 0
+ opt.progress = opt.progress or 0
+
+ _try_reuse_modules(target, modules)
+ _builder(target).populate_module_map(target, modules)
+
+ -- build modules
+ _build_modules(target, sourcebatch, modules, table.join(opt, {
+ build_module = function(_, module, _, objectfile, cppfile)
+ depmtime = math.max(depmtime, _builder(target).make_module_buildcmds(target, batchcmds, {module = module, cppfile = cppfile, objectfile = objectfile, progress = opt.progress}))
+ end
+ }))
+
+ batchcmds:set_depmtime(depmtime)
+end
+
+-- generate headerunits for batchjobs
+function build_headerunits_for_batchjobs(target, batchjobs, sourcebatch, modules, opt)
+
+ local user_headerunits, stl_headerunits = dependency_scanner.get_headerunits(target, sourcebatch, modules)
+ if not user_headerunits and not stl_headerunits then
+ return
+ end
+
+ -- we need new group(headerunits)
+ -- e.g. group(build_modules) -> group(headerunits)
+ opt.rootjob = batchjobs:group_leave() or opt.rootjob
+ batchjobs:group_enter(target:name() .. "/build_headerunits", {rootjob = opt.rootjob})
+
+ local build_headerunits = function(headerunits)
+ local modulesjobs = {}
+ _build_headerunits(target, headerunits, table.join(opt, {
+ build_headerunit = function(headerunit, key, bmifile, outputdir, build)
+ local job_name = target:name() .. key
+ local job = _builder(target).make_headerunit_buildjobs(target, job_name, batchjobs, headerunit, bmifile, outputdir, table.join(opt, {build = build}))
+ if job then
+ modulesjobs[job_name] = job
+ end
+ end
+ }))
+ build_batchjobs_for_modules(modulesjobs, batchjobs, opt.rootjob)
+ end
+
+ -- build stl header units first as other headerunits may need them
+ if stl_headerunits then
+ opt.stl_headerunit = true
+ build_headerunits(stl_headerunits)
+ end
+ if user_headerunits then
+ opt.stl_headerunit = false
+ build_headerunits(user_headerunits)
+ end
+end
+
+-- generate headerunits for batchcmds
+function build_headerunits_for_batchcmds(target, batchcmds, sourcebatch, modules, opt)
+
+ local user_headerunits, stl_headerunits = dependency_scanner.get_headerunits(target, sourcebatch, modules)
+ if not user_headerunits and not stl_headerunits then
+ return
+ end
+
+ local build_headerunits = function(headerunits)
+ local depmtime = 0
+ _build_headerunits(target, headerunits, table.join(opt, {
+ build_headerunit = function(headerunit, _, bmifile, outputdir, build)
+ depmtime = math.max(depmtime, _builder(target).make_headerunit_buildcmds(target, batchcmds, headerunit, bmifile, outputdir, table.join({build = build}, opt)))
+ end
+ }))
+ batchcmds:set_depmtime(depmtime)
+ end
+
+ -- build stl header units first as other headerunits may need them
+ if stl_headerunits then
+ opt.stl_headerunit = true
+ build_headerunits(stl_headerunits)
+ end
+ if user_headerunits then
+ opt.stl_headerunit = false
+ build_headerunits(user_headerunits)
+ end
+end
+
+function generate_metadata(target, modules)
+ local public_modules
+ for _, module in table.orderpairs(modules) do
+ local _, _, cppfile = compiler_support.get_provided_module(module)
+ local fileconfig = target:fileconfig(cppfile)
+ local public = fileconfig and fileconfig.public
+ if public then
+ public_modules = public_modules or {}
+ table.insert(public_modules, module)
+ end
+ end
+
+ if not public_modules then
+ return
+ end
+
+ local jobs = option.get("jobs") or os.default_njob()
+ runjobs(target:name() .. "_install_modules", function(index)
+ local module = public_modules[index]
+ local name, _, cppfile = compiler_support.get_provided_module(module)
+ local metafilepath = compiler_support.get_metafile(target, cppfile)
+ progress.show((index * 100) / #public_modules, "${color.build.target}<%s> generating.module.metadata %s", target:name(), name)
+ local metadata = _generate_meta_module_info(target, name, cppfile, module.requires)
+ json.savefile(metafilepath, metadata)
+ end, {comax = jobs, total = #public_modules})
+end
+
+-- flush target module mapper keys
+function flush_target_module_mapper_keys(target)
+ local memcache = compiler_support.memcache()
+ memcache:set2(target:name(), "module_mapper_keys", nil)
+end
+
+-- get or create a target module mapper
+function get_target_module_mapper(target)
+ local memcache = compiler_support.memcache()
+ local mapper = memcache:get2(target:name(), "module_mapper")
+ if not mapper then
+ mapper = {}
+ memcache:set2(target:name(), "module_mapper", mapper)
+ end
+
+ -- we generate the keys map to optimise the efficiency of _is_duplicated_headerunit
+ local mapper_keys = memcache:get2(target:name(), "module_mapper_keys")
+ if not mapper_keys then
+ mapper_keys = {}
+ for _, item in pairs(mapper) do
+ if item.key then
+ mapper_keys[item.key] = item
+ end
+ end
+ memcache:set2(target:name(), "module_mapper_keys", mapper_keys)
+ end
+ return mapper, mapper_keys
+end
+
+-- get a module or headerunit from target mapper
+function get_from_target_mapper(target, name)
+ local mapper = get_target_module_mapper(target)
+ if mapper[name] then
+ return mapper[name]
+ end
+end
+
+-- add a module to target mapper
+function add_module_to_target_mapper(target, name, sourcefile, bmifile, opt)
+ local mapper = get_target_module_mapper(target)
+ if not mapper[name] then
+ mapper[name] = {name = name, key = name, bmi = bmifile, sourcefile = sourcefile, opt = opt}
+ end
+ flush_target_module_mapper_keys(target)
+end
+
+-- add a headerunit to target mapper
+function add_headerunit_to_target_mapper(target, headerunit, bmifile)
+ local mapper = get_target_module_mapper(target)
+ local key = hash.uuid(path.normalize(headerunit.path))
+ local deduplicated = _is_duplicated_headerunit(target, key)
+ if deduplicated then
+ mapper[headerunit.name] = {name = headerunit.name, key = key, aliasof = deduplicated.name, headerunit = headerunit}
+ else
+ mapper[headerunit.name] = {name = headerunit.name, key = key, headerunit = headerunit, bmi = bmifile}
+ end
+ flush_target_module_mapper_keys(target)
+ return deduplicated and true or false
+end
+
diff --git a/xmake/rules/c++/modules/modules_support/clang.lua b/xmake/rules/c++/modules/modules_support/clang.lua
deleted file mode 100644
index 3bf19238d..000000000
--- a/xmake/rules/c++/modules/modules_support/clang.lua
+++ /dev/null
@@ -1,986 +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.base.option")
-import("core.base.json")
-import("core.base.semver")
-import("core.tool.compiler")
-import("core.project.project")
-import("core.project.depend")
-import("core.project.config")
-import("lib.detect.find_tool")
-import("utils.progress")
-import("private.action.build.object", {alias = "objectbuilder"})
-import("common")
-import("stl_headers")
-
--- get bmi path
--- @see https://github.com/xmake-io/xmake/issues/4063
-function _get_bmi_path(bmifile)
- if is_host("windows") then
- bmifile = bmifile:gsub(":", "_")
- end
- return bmifile
-end
-
--- get clang path, we need get it's absolute path
--- @see https://github.com/xmake-io/xmake/issues/4575#issuecomment-1879596450
-function _get_clang_path(target)
- local clang_path = _g.clang_path
- if not clang_path then
- local program, toolname = target:tool("cxx")
- if program and (toolname == "clang" or toolname == "clangxx") then
- local clang = find_tool("clang", {program = program,
- envs = os.getenvs(), cachekey = "modules_support_clang"})
- if clang then
- clang_path = clang.program
- end
- end
- clang_path = clang_path or false
- _g.clang_path = clang_path
- end
- return clang_path or nil
-end
-
--- get clang version
-function _get_clang_version(target)
- local clang_version = _g.clang_version
- if not clang_version then
- local program, toolname = target:tool("cxx")
- if program and (toolname == "clang" or toolname == "clangxx") then
- local clang = find_tool("clang", {program = program, version = true,
- envs = os.getenvs(), cachekey = "modules_support_clang"})
- if clang then
- clang_version = clang.version
- end
- end
- clang_version = clang_version or false
- _g.clang_version = clang_version
- end
- return clang_version or nil
-end
-
--- get clang-scan-deps
-function _get_clang_scan_deps(target)
- local clang_scan_deps = _g.clang_scan_deps
- if not clang_scan_deps then
- local program, toolname = target:tool("cxx")
- if program and (toolname == "clang" or toolname == "clangxx") then
- local dir = path.directory(program)
- local basename = path.basename(program)
- local extension = path.extension(program)
- program = (basename:gsub("clang", "clang-scan-deps")) .. extension
- if dir and dir ~= "." and os.isdir(dir) then
- program = path.join(dir, program)
- end
- local result = find_tool("clang-scan-deps", {program = program, version = true})
- if result then
- clang_scan_deps = result.program
- end
- end
- clang_scan_deps = clang_scan_deps or false
- _g.clang_scan_deps = clang_scan_deps
- end
- return clang_scan_deps or nil
-end
-
--- 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
--- on LLVM >= 16
--- -fmodule-file=foo=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, opt)
- opt = opt or {}
- local modulemap = _get_modulemap_from_mapper(target, name)
- if modulemap then
- return
- end
-
- local clang_version = _get_clang_version(target)
- local namedmodule = opt.namedmodule and semver.compare(clang_version, "16.0") >= 0
- local modulefileflag = get_modulefileflag(target)
- local mapflag = namedmodule and format("%s%s=%s", modulefileflag, name, bmifile) or modulefileflag .. bmifile
- modulemap = {flag = mapflag, deps = opt.deps}
- common.localcache():set2(_mapper_cachekey(target), "modulemap" .. name, modulemap)
-end
-
-function _mapper_cachekey(target)
- local mode = config.mode()
- return target:name() .. "_modulemap_" .. (mode or "")
-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, name)
- return common.localcache():get2(_mapper_cachekey(target), "modulemap" .. name) or nil
-end
-
--- use the given stdlib? e.g. libc++ or libstdc++
-function _use_stdlib(target, name)
- local stdlib = target:data("cxx.modules.stdlib") or "libstdc++"
- return stdlib == name
-end
-
--- set stdlib flags, it will use libstdc++ if we do not set `-stdlib=`
-function _set_stdlib_flags(target)
- if _use_stdlib(target, "libc++") then
- target:add("cxxflags", "-stdlib=libc++")
- target:add("ldflags", "-stdlib=libc++")
- target:add("shflags", "-stdlib=libc++")
- end
-end
-
--- load module support for the current target
-function load(target)
- local clangmodulesflag, modulestsflag, withoutflag = get_modulesflag(target)
-
- -- add module flags
- if not withoutflag then
- target:add("cxxflags", modulestsflag)
- end
-
- -- enable clang modules to emulate std modules
- if target:policy("build.c++.clang.stdmodules") then
- target:add("cxxflags", clangmodulesflag)
- end
-
- -- fix default visibility for functions and variables [-fvisibility] differs in PCH file vs. current file
- -- module.pcm cannot be loaded due to a configuration mismatch with the current compilation.
- --
- -- it will happen in binary target depend on library target with modules, and enable release mode at same time.
- --
- -- @see https://github.com/xmake-io/xmake/issues/3358#issuecomment-1432586767
- local dep_symbols
- local has_library_deps = false
- for _, dep in ipairs(target:orderdeps()) do
- if dep:is_shared() or dep:is_static() or dep:is_object() then
- dep_symbols = dep:get("symbols")
- has_library_deps = true
- break
- end
- end
- if has_library_deps then
- target:set("symbols", dep_symbols and dep_symbols or "none")
- end
-
- -- if use libc++, we need to install libc++ and libc++abi
- --
- -- on ubuntu:
- -- sudo apt install libc++-dev libc++abi-15-dev
- --
- local flags = table.join(target:get("cxxflags") or {}, get_config("cxxflags") or {})
- if table.contains(flags, "-stdlib=libc++", "clang::-stdlib=libc++") then
- target:data_set("cxx.modules.stdlib", "libc++")
- elseif table.contains(flags, "-stdlib=libstdc++", "clang::-stdlib=libstdc++") then
- target:data_set("cxx.modules.stdlib", "libstdc++")
- end
- _set_stdlib_flags(target)
-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(target, includedirs, clang)
- local tmpfile = os.tmpfile() .. ".cc"
- io.writefile(tmpfile, "#include <vector>")
- local argv = {"-E", "-x", "c++", tmpfile}
- if _use_stdlib(target, "libc++") then
- table.insert(argv, 1, "-stdlib=libc++")
- end
- local result = try {function () return os.iorunv(clang, argv) 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
-
--- do compile for batchcmds
--- @note we need to use batchcmds:compilev to translate paths in compflags for generator, e.g. -Ixx
-function _batchcmds_compile(batchcmds, target, flags, sourcefile)
- local compinst = target:compiler("cxx")
- local compflags = compinst:compflags({sourcefile = sourcefile, target = target})
- batchcmds:compilev(table.join(compflags or {}, flags), {compiler = compinst, sourcekind = "cxx"})
-end
-
--- build module file
-function _build_modulefile(target, sourcefile, opt)
- local objectfile = opt.objectfile
- local dependfile = opt.dependfile
- local compinst = compiler.load("cxx", {target = target})
- local compflags = compinst:compflags({sourcefile = sourcefile, target = target})
- local dependinfo = target:is_rebuilt() and {} or (depend.load(dependfile) or {})
-
- -- need build this object?
- local dryrun = option.get("dry-run")
- local depvalues = {compinst:program(), compflags}
- local lastmtime = os.isfile(objectfile) and os.mtime(dependfile) or 0
- if not dryrun and not depend.is_changed(dependinfo, {lastmtime = lastmtime, values = depvalues}) then
- return
- end
-
- -- init flags
- local common_args = opt.common_args
- local requiresflags = opt.requiresflags
- local moduleoutputflag = get_moduleoutputflag(target)
-
- -- trace
- progress.show(opt.progress, "${color.build.object}compiling.module.$(mode) %s", opt.provide and opt.provide.name or sourcefile)
-
- local bmifile
- local compileflags = {}
- local bmiflags
- if opt.provide then
- bmifile = _get_bmi_path(opt.provide.bmifile)
- if bmifile then
- common.memcache():set2(bmifile, "compiling", true)
- end
- if moduleoutputflag then
- compileflags = table.join("-x", "c++-module", moduleoutputflag .. bmifile, requiresflags)
- else
- bmiflags = table.join("-x", "c++-module", "--precompile", compflags, common_args, requiresflags)
- end
- else
- compileflags = {"-x", "c++"}
- end
-
- if bmiflags then
- vprint(compinst:compcmd(sourcefile, bmifile, {compflags = bmiflags, rawargs = true}))
- end
-
- compileflags = table.join2(compileflags, compflags, common_args, requiresflags or {})
- vprint(compinst:compcmd(bmiflags and bmifile or sourcefile, objectfile, {compflags = compileflags, rawargs = true}))
-
- if not dryrun then
-
- -- do compile
- dependinfo.files = {}
- if bmiflags then
- assert(compinst:compile(sourcefile, bmifile, {dependinfo = dependinfo, compflags = bmiflags}))
- end
- assert(compinst:compile(bmiflags and bmifile or sourcefile, objectfile, {dependinfo = dependinfo, compflags = compileflags}))
-
- -- update files and values to the dependent file
- dependinfo.values = depvalues
- table.join2(dependinfo.files, sourcefile)
- table.join2(dependinfo.files, opt.requires or {})
- depend.save(dependinfo, dependfile)
- end
-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("cxx")
- assert(toolname:startswith("clang"))
- _get_toolchain_includedirs_for_stlheaders(target, includedirs, clang)
- local _, result = try {function () return os.iorunv(clang, {"-E", "-stdlib=libc++", "-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 compinst = target:compiler("cxx")
- 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.module.deps %s", sourcefile)
- end
-
- local outputdir = common.get_outputdir(target, sourcefile)
- local jsonfile = path.translate(path.join(outputdir, path.filename(sourcefile) .. ".json"))
- if has_clangscandepssupport(target) and not target:policy("build.c++.clang.fallbackscanner") then
- -- We need absolute path of clang to use clang-scan-deps
- -- See https://clang.llvm.org/docs/StandardCPlusPlusModules.html#possible-issues-failed-to-find-system-headers
- local clang_path = compinst:program()
- if not path.is_absolute(clang_path) then
- clang_path = _get_clang_path(target) or compinst:program()
- end
- local clangscandeps = _get_clang_scan_deps(target)
- local compinst = target:compiler("cxx")
- local compflags = compinst:compflags({sourcefile = sourcefile, target = target})
- local flags = table.join("--format=p1689", "--",
- clang_path, "-x", "c++", "-c", sourcefile, "-o", target:objectfile(sourcefile),
- compflags)
- vprint(table.concat(table.join(clangscandeps, flags), " "))
- local outdata, errdata = os.iorunv(clangscandeps, flags)
- assert(errdata, errdata)
-
- io.writefile(jsonfile, outdata)
- else
- common.fallback_generate_dependencies(target, jsonfile, sourcefile, function(file)
- local compflags = compinst:compflags({sourcefile = file, target = target})
- -- exclude -fmodule* and -std=c++/gnu++* flags because,
- -- when they are set clang try to find bmi of imported modules but they don't exists a this point of compilation
- table.remove_if(compflags, function(_, flag)
- return flag:startswith("-fmodule") or flag:startswith("-std=c++") or flag:startswith("-std=gnu++")
- end)
- local ifile = path.translate(path.join(outputdir, path.filename(file) .. ".i"))
- os.vrunv(compinst:program(), table.join(compflags, {"-E", "-x", "c++", file, "-o", ifile}))
- local content = io.readfile(ifile)
- os.rm(ifile)
- return content
- end)
- end
- changed = true
-
- local rawdependinfo = io.readfile(jsonfile)
- if rawdependinfo then
- local dependinfo = json.decode(rawdependinfo)
- if target:data("cxx.modules.stdlib") == nil then
- local has_std_modules = false
- for _, r in ipairs(dependinfo.rules) do
- for _, required in ipairs(r.requires) do
- -- it may be `std:utility`, ..
- -- @see https://github.com/xmake-io/xmake/issues/3373
- local logical_name = required["logical-name"]
- if logical_name and (logical_name == "std" or logical_name:startswith("std.") or logical_name:startswith("std:")) then
- has_std_modules = true
- break
- end
- end
-
- if has_std_modules then
- break
- end
- end
- if has_std_modules then
-
- -- we need clang >= 17.0 or use clang stdmodules if the current target contains std module
- local clang_version = _get_clang_version(target)
- assert((clang_version and semver.compare(clang_version, "17.0") >= 0) or target:policy("build.c++.clang.stdmodules"),
- [[On llvm <= 16 standard C++ modules are not supported ;
- they can be emulated through clang modules and supported only on libc++ ;
- please add -stdlib=libc++ cxx flag or disable strict mode]])
-
- -- we use libc++ by default if we do not explicitly specify -stdlib:libstdc++
- target:data_set("cxx.modules.stdlib", "libc++")
- _set_stdlib_flags(target)
- end
- end
- end
-
- return {moduleinfo = rawdependinfo}
- end, {dependfile = dependfile, files = {sourcefile}, changed = target:is_rebuilt()})
- 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, {mkdir = true})
- 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 _, 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}compiling.headerunit.$(mode) %s", headerunit.name)
- local args = {modulecachepathflag .. stlcachedir, "-c", "-Wno-everything", "-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}, changed = target:is_rebuilt()})
-
- -- libc++ have a builtin module mapper
- if not _use_stdlib(target, "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 stlcachedir = common.stlmodules_cachedir(target, {mkdir = true})
- local modulecachepathflag = get_modulecachepathflag(target)
- assert(has_headerunitsupport(target), "compiler(clang): does not support c++ header units!")
-
- -- build headerunits
- local depmtime = 0
- for _, 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 flags = {
- path(stlcachedir, function (p) return modulecachepathflag .. p end),
- "-c", "-Wno-everything", "-o", path(bmifile), "-x", "c++-system-header", headerunit.name}
- batchcmds:show_progress(opt.progress, "${color.build.object}compiling.headerunit.$(mode) %s", headerunit.name)
- _batchcmds_compile(batchcmds, target, flags)
- end
- -- libc++ have a builtin module mapper
- if not _use_stdlib(target, "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, {mkdir = true})
- 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
- for _, headerunit in ipairs(headerunits) do
- local file = path.relative(headerunit.path, target:scriptdir())
- local objectfile = target:objectfile(file)
-
- local outputdir = common.get_outputdir(target, headerunit.path)
- 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()
- progress.show((index * 100) / total, "${color.build.object}compiling.headerunit.$(mode) %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}, changed = target:is_rebuilt()})
- _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)
- assert(has_headerunitsupport(target), "compiler(clang): does not support c++ header units!")
-
- -- get cachedirs
- local cachedir = common.modules_cachedir(target, {mkdir = true})
- local modulecachepathflag = get_modulecachepathflag(target)
-
- -- build headerunits
- local depmtime = 0
- for _, headerunit in ipairs(headerunits) do
- local file = path.relative(headerunit.path, target:scriptdir())
- local objectfile = target:objectfile(file)
-
- local outputdir = common.get_outputdir(target, headerunit.path)
- 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 flags = {path(cachedir, function (p) return modulecachepathflag .. p end), "-c", "-o", path(bmifile)}
- if headerunit.type == ":quote" then
- table.join2(flags, {"-I", path(headerunit.path):directory(), "-x", "c++-user-header", path(headerunit.path)})
- elseif headerunit.type == ":angle" then
- table.join2(flags, {"-x", "c++-system-header", headerunit.name})
- end
-
- batchcmds:show_progress(opt.progress, "${color.build.object}compiling.headerunit.$(mode) %s", headerunit.name)
- _batchcmds_compile(batchcmds, target, flags)
- 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)
-
- -- get flags
- local cachedir = common.modules_cachedir(target, {mkdir = true})
- local modulecachepathflag = get_modulecachepathflag(target)
-
- -- flush job
- local flushjob = batchjobs:addjob(target:name() .. "_modules", 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
- local cppfile = module.cppfile
- local name, provide
- if module.provides then
- -- assume there that provides is only one, until we encounter the case
- local length = 0
- for k, v in pairs(module.provides) do
- length = length + 1
- name = k
- provide = v
- cppfile = provide.sourcefile
- if length > 1 then
- raise("multiple provides are not supported now!")
- end
- break
- end
- end
- local moduleinfo = table.copy(provide) or {}
-
- if provide then
- local fileconfig = target:fileconfig(cppfile)
- if fileconfig and fileconfig.install then
- batchjobs:addjob(name .. "_metafile", function(index, total)
- local metafilepath = common.get_metafile(target, cppfile)
- depend.on_changed(function()
- progress.show(opt.progress, "${color.build.object}generating.module.metadata %s", name)
- local metadata = common.generate_meta_module_info(target, name, cppfile, module.requires)
- json.savefile(metafilepath, metadata)
- end, {dependfile = target:dependfile(metafilepath), files = {cppfile}, changed = target:is_rebuilt()})
- end, {rootjob = flushjob})
- end
- end
-
- table.join2(moduleinfo, {
- name = name or cppfile,
- deps = table.keys(module.requires or {}),
- sourcefile = cppfile,
- job = batchjobs:newjob(name or cppfile, function(index, total)
- -- @note we add it at the end to ensure that the full modulemap are already stored in the mapper
- local requiresflags
- local requires
- if module.requires then
- requiresflags = get_requiresflags(target, module.requires)
- requires = get_requires(target, module.requires)
- end
-
- if provide or common.has_module_extension(cppfile) then
- local bmifile = _get_bmi_path(provide and provide.bmi)
- if not common.memcache():get2(name or cppfile, "compiling") then
- if name and module.external then
- common.memcache():set2(name or cppfile, "compiling", true)
- end
- _build_modulefile(target, provide and provide.sourcefile or cppfile, {
- objectfile = objectfile,
- dependfile = target:dependfile(bmifile or objectfile),
- provide = provide and {bmifile = bmifile, name = name},
- common_args = common_args,
- requiresflags = requiresflags,
- requires = requires,
- progress = (index * 100) / total})
- end
- target:add("objectfiles", objectfile)
-
- if provide then
- _add_module_to_mapper(target, name, bmifile, {deps = requiresflags, namedmodule = true})
- end
- elseif requiresflags then
- local cxxflags = {}
- for _, flag in ipairs(requiresflags) do
- -- we need to wrap flag to support flag with space
- if type(flag) == "string" and flag:find(" ", 1, true) then
- table.insert(cxxflags, {flag})
- else
- table.insert(cxxflags, flag)
- end
- end
- target:fileconfig_add(cppfile, {force = {cxxflags = cxxflags}})
-
- -- force rebuild .cpp file if any of its module dependency is rebuilt
- local rebuild = false
- for _, requiredfile in ipairs(requires) do
- if common.memcache():get2(requiredfile, "compiling") == true then
- rebuild = true
- break
- end
- end
- if rebuild then
- os.tryrm(target:objectfile(cppfile))
- end
- end
- end)})
- modulesjobs[name or cppfile] = moduleinfo
- 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 cachedir = common.modules_cachedir(target, {mkdir = true})
- local modulecachepathflag = get_modulecachepathflag(target)
-
- -- build modules
- local depmtime = 0
- for _, objectfile in ipairs(objectfiles) do
- local module = modules[objectfile]
- if module then
- local cppfile = module.cppfile
- local name, provide
- if module.provides then
- local length = 0
- for k, v in pairs(module.provides) do
- length = length + 1
- name = k
- provide = v
- cppfile = provide.sourcefile
- if length > 1 then
- raise("multiple provides are not supported now!")
- end
- break
- end
- end
- local requiresflags
- if module.requires then
- requiresflags = get_requiresflags(target, module.requires)
- end
-
- local flags = table.join({path(cachedir, function (p) return modulecachepathflag .. p end)}, requiresflags or {})
- if provide or common.has_module_extension(cppfile) then
- local file = provide and path(provide.bmi) or path(cppfile)
-
- batchcmds:show_progress(opt.progress, "${color.build.object}compiling.module.$(mode) %s", name or cppfile)
- batchcmds:mkdir(path.directory(objectfile))
- if provide then
- _batchcmds_compile(batchcmds, target, table.join(flags,
- {"-x", "c++-module", "--precompile", "-c", path(cppfile), "-o", path(provide.bmi)}), cppfile)
- _add_module_to_mapper(target, name, provide.bmi, {namedmodule = true})
- -- add requiresflags to module. it will be used for project generation
- target:fileconfig_add(cppfile, {force = {cxxflags = requiresflags}})
- end
- _batchcmds_compile(batchcmds, target, table.join(flags,
- not provide and {"-x", "c++"} or {}, {"-c", file, "-o", path(objectfile)}), file)
- target:add("objectfiles", objectfile)
- elseif requiresflags then
- local cxxflags = {}
- for _, flag in ipairs(requiresflags) do
- -- we need to wrap flag to support flag with space
- if type(flag) == "string" and flag:find(" ", 1, true) then
- table.insert(cxxflags, {flag})
- else
- table.insert(cxxflags, flag)
- end
- end
- target:fileconfig_add(cppfile, {force = {cxxflags = cxxflags}})
- end
-
- batchcmds:add_depfiles(cppfile)
- depmtime = math.max(depmtime, os.mtime(objectfile))
- end
- end
- batchcmds:set_depmtime(depmtime)
- _flush_mapper(target)
-end
-
--- not supported atm
-function get_stdmodules(target)
- local modules = {}
- return modules
-end
-
-function get_bmi_extension()
- return ".pcm"
-end
-
-function get_modulesflag(target)
- local clangmodulesflag = _g.clangmodulesflag
- local modulestsflag = _g.modulestsflag
- local withoutflag = _g.withoutflag
- if clangmodulesflag == nil and modulestsflag == nil then
- local compinst = target:compiler("cxx")
- if compinst:has_flags("-fmodules", "cxxflags", {flagskey = "clang_modules"}) then
- clangmodulesflag = "-fmodules"
- end
- if compinst:has_flags("-fmodules-ts", "cxxflags", {flagskey = "clang_modules_ts"}) then
- modulestsflag = "-fmodules-ts"
- end
- local clang_version = _get_clang_version(target)
- withoutflag = semver.compare(clang_version, "16.0") >= 0
- assert(withoutflag or modulestsflag, "compiler(clang): does not support c++ module!")
- _g.clangmodulesflag = clangmodulesflag or false
- _g.modulestsflag = modulestsflag or false
- _g.withoutflag = withoutflag or false
- end
- return clangmodulesflag or nil, modulestsflag or nil, withoutflag or nil
-end
-
-function get_builtinmodulemapflag(target)
- local builtinmodulemapflag = _g.builtinmodulemapflag
- if builtinmodulemapflag == nil then
- -- this flag seems clang on mingw doesn't distribute it
- -- @see https://github.com/xmake-io/xmake/pull/2833
- if not target:is_plat("mingw") 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!")
- end
- _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_implicitmodulemapsflag(target)
- local implicitmodulemapsflag = _g.implicitmodulemapsflag
- if implicitmodulemapsflag == nil then
- local compinst = target:compiler("cxx")
- if compinst:has_flags("-fimplicit-module-maps", "cxxflags", {flagskey = "clang_implicit_module_map"}) then
- implicitmodulemapsflag = "-fimplicit-module-maps"
- end
- assert(implicitmodulemapsflag, "compiler(clang): does not support c++ module!")
- _g.implicitmodulemapsflag = implicitmodulemapsflag or false
- end
- return implicitmodulemapsflag 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")
- local _, modulestsflag, withoutflag = get_modulesflag(target)
- modulestsflag = withoutflag and "" or modulestsflag
- if compinst:has_flags(modulestsflag .. " -std=c++20 -x c++-user-header", "cxxflags", {
- snippet = "inline int foo() { return 0; }",
- flagskey = "clang_user_header_unit_support",
- tryrun = true}) and
- compinst:has_flags(modulestsflag .. " -std=c++20 -x c++-system-header", "cxxflags", {
- snippet = "inline int foo() { return 0; }",
- 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 has_clangscandepssupport(target)
- local support_clangscandeps = _g.support_clangscandeps
- if support_clangscandeps == nil then
- local clangscandeps = _get_clang_scan_deps(target)
- local clang_version = _get_clang_version(target)
- if clangscandeps and clang_version and semver.compare(clang_version, "16.0") >= 0 then
- support_clangscandeps = true
- end
- _g.support_clangscandeps = support_clangscandeps or false
- end
- return support_clangscandeps or nil
-end
-
-function get_moduleoutputflag(target)
- local moduleoutputflag = _g.moduleoutputflag
- if moduleoutputflag == nil then
- local compinst = target:compiler("cxx")
- local clang_version = _get_clang_version(target)
- if compinst:has_flags("-fmodule-output=", "cxxflags", {flagskey = "clang_module_output", tryrun = true}) and
- semver.compare(clang_version, "16.0") >= 0 then
- moduleoutputflag = "-fmodule-output="
- end
- _g.moduleoutputflag = moduleoutputflag or false
- end
- return moduleoutputflag or nil
-end
-
-function get_requires(target, requires)
- local requires
- local flags = get_requiresflags(target, requires)
- for _, flag in ipairs(flags) do
- requires = requires or {}
- table.insert(requires, flag:split("=")[3])
- end
- return requires
-end
-
-function get_requiresflags(target, requires)
- local flags = {}
- -- add deps required module flags
- local already_mapped_modules = {}
- for name, _ in table.orderpairs(requires) do
- -- if already in flags, continue
- if already_mapped_modules[name] then
- goto continue
- end
-
- for _, dep in ipairs(target:orderdeps()) do
- local modulemap_ = _get_modulemap_from_mapper(dep, name)
- if modulemap_ then
- already_mapped_modules[name] = true
- table.insert(flags, modulemap_.flag)
- if modulemap_.deps then
- table.join2(flags, modulemap_.deps)
- end
- goto continue
- end
- end
-
- -- append target required module mapper flags
- local modulemap = _get_modulemap_from_mapper(target, name)
- if modulemap then
- already_mapped_modules[name] = true
- table.insert(flags, modulemap.flag)
- if modulemap.deps then
- table.join2(flags, modulemap.deps)
- end
- goto continue
- end
-
- ::continue::
- end
- if #flags > 0 then
- return table.unique(flags)
- end
-end
diff --git a/xmake/rules/c++/modules/modules_support/clang/builder.lua b/xmake/rules/c++/modules/modules_support/clang/builder.lua
new file mode 100644
index 000000000..6a912bed4
--- /dev/null
+++ b/xmake/rules/c++/modules/modules_support/clang/builder.lua
@@ -0,0 +1,401 @@
+--!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 clang/builder.lua
+--
+
+-- imports
+import("core.base.json")
+import("core.base.option")
+import("core.base.semver")
+import("utils.progress")
+import("private.action.build.object", {alias = "objectbuilder"})
+import("core.tool.compiler")
+import("core.project.config")
+import("core.project.depend")
+import("compiler_support")
+import(".builder", {inherit = true})
+
+function _compile_one_step(target, bmifile, sourcefile, objectfile, opt)
+ -- get flags
+ local module_outputflag = compiler_support.get_moduleoutputflag(target)
+ if module_outputflag then
+ local flags = table.join({"-x", "c++-module", module_outputflag .. bmifile}, opt.std and {"-Wno-include-angled-in-module-purview", "-Wno-reserved-module-identifier"} or {})
+ if opt and opt.batchcmds then
+ _batchcmds_compile(opt.batchcmds, target, flags, sourcefile, objectfile)
+ else
+ _compile(target, flags, sourcefile, objectfile)
+ end
+ else
+ _compile_bmi_step(target, bmifile, sourcefile, opt)
+ _compile_objectfile_step(target, bmifile, sourcefile, objectfile, opt)
+ end
+end
+
+function _compile_bmi_step(target, bmifile, sourcefile, opt)
+ local flags = table.join({"-x", "c++-module", "--precompile"}, opt.std and {"-Wno-include-angled-in-module-purview", "-Wno-reserved-module-identifier"} or {})
+ if opt and opt.batchcmds then
+ _batchcmds_compile(opt.batchcmds, target, flags, sourcefile, bmifile)
+ else
+ _compile(target, flags, sourcefile, bmifile)
+ end
+end
+
+function _compile_objectfile_step(target, bmifile, sourcefile, objectfile, opt)
+ _compile(target, {}, sourcefile, objectfile, {bmifile = bmifile})
+ if opt and opt.batchcmds then
+ _batchcmds_compile(opt.batchcmds, target, {}, sourcefile, objectfile, {bmifile = bmifile})
+ else
+ _compile(target, {}, sourcefile, objectfile, {bmifile = bmifile})
+ end
+end
+
+-- get flags for building a headerunit
+function _make_headerunitflags(target, headerunit, bmifile)
+
+ local module_headerflag = compiler_support.get_moduleheaderflag(target)
+ assert(module_headerflag, "compiler(clang): does not support c++ header units!")
+
+ local local_directory = (headerunit.type == ":quote") and {"-I" .. path.directory(headerunit.path)} or {}
+ local headertype = (headerunit.type == ":angle") and "system" or "user"
+ local flags = table.join(local_directory, {"-xc++-header", "-Wno-everything", module_headerflag .. headertype})
+ return flags
+end
+
+-- do compile
+function _compile(target, flags, sourcefile, outputfile, opt)
+
+ opt = opt or {}
+ local dryrun = option.get("dry-run")
+ local compinst = target:compiler("cxx")
+ local compflags = compinst:compflags({sourcefile = sourcefile, target = target})
+ local flags = table.join(compflags or {}, flags)
+
+ -- trace
+ if option.get("verbose") then
+ print(compinst:compcmd(opt.bmifile or sourcefile, outputfile, {target = target, compflags = flags, rawargs = true}))
+ end
+
+ -- do compile
+ if not dryrun then
+ assert(compinst:compile(opt.bmifile or sourcefile, outputfile, {target = target, compflags = flags}))
+ end
+end
+
+-- do compile for batchcmds
+-- @note we need to use batchcmds:compilev to translate paths in compflags for generator, e.g. -Ixx
+function _batchcmds_compile(batchcmds, target, flags, sourcefile, outputfile, opt)
+ opt = opt or {}
+ local compinst = target:compiler("cxx")
+ local compflags = compinst:compflags({sourcefile = sourcefile, target = target})
+ flags = table.join("-c", compflags or {}, flags, {"-o", outputfile, opt.bmifile or sourcefile})
+ batchcmds:compilev(flags, {compiler = compinst, sourcekind = "cxx"})
+end
+
+-- get module requires flags
+-- 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
+-- on LLVM >= 16
+-- -fmodule-file=foo=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 _get_requiresflags(target, module, opt)
+
+ local modulefileflag = compiler_support.get_modulefileflag(target)
+ local name = module.name
+ local cachekey = target:name() .. name
+
+ local requiresflags = compiler_support.memcache():get2(cachekey, "requiresflags")
+ or compiler_support.localcache():get2(cachekey, "requiresflags")
+
+ if not requiresflags or (opt and opt.regenerate) then
+ requiresflags = {}
+ for required, _ in table.orderpairs(module.requires) do
+ local dep_module = get_from_target_mapper(target, required)
+ assert(dep_module, "module dependency %s required for %s not found", required, name)
+
+ -- aliased headerunit
+ local bmifile = dep_module.bmi
+ if dep_module.aliasof then
+ local aliased = get_from_target_mapper(target, dep_module.aliasof)
+ bmifile = aliased.bmi
+ end
+ local mapflag = (dep_module.opt and dep_module.opt.namedmodule) and format("%s%s=%s", modulefileflag, required, bmifile) or modulefileflag .. bmifile
+ table.insert(requiresflags, mapflag)
+
+ -- append deps
+ if dep_module.opt and dep_module.opt.deps then
+ local deps = _get_requiresflags(target, {name = dep_module.name or dep_module.sourcefile, bmi = bmifile, requires = dep_module.opt.deps})
+ table.join2(requiresflags, deps)
+ end
+ end
+ compiler_support.memcache():set2(cachekey, "requiresflags", table.unique(requiresflags))
+ compiler_support.localcache():set2(cachekey, "requiresflags", table.unique(requiresflags))
+ end
+ return requiresflags
+end
+
+function _append_requires_flags(target, module, name, cppfile, bmifile, opt)
+ local cxxflags = {}
+ local requiresflags = _get_requiresflags(target, {name = (name or cppfile), bmi = bmifile, requires = module.requires}, {regenerate = opt.build})
+ for _, flag in ipairs(requiresflags) do
+ -- we need to wrap flag to support flag with space
+ if type(flag) == "string" and flag:find(" ", 1, true) then
+ table.insert(cxxflags, {flag})
+ else
+ table.insert(cxxflags, flag)
+ end
+ end
+ target:fileconfig_add(cppfile, {force = {cxxflags = cxxflags}})
+end
+
+-- populate module map
+function populate_module_map(target, modules)
+ local clang_version = compiler_support.get_clang_version(target)
+ local support_namedmodule = semver.compare(clang_version, "16.0") >= 0
+ for _, module in pairs(modules) do
+ local name, provide, cppfile = compiler_support.get_provided_module(module)
+ if provide then
+ local bmifile = compiler_support.get_bmi_path(provide.bmi)
+ add_module_to_target_mapper(target, name, cppfile, bmifile, {deps = module.requires, namedmodule = support_namedmodule})
+ end
+ end
+end
+
+-- get defines for a module
+function get_module_required_defines(target, sourcefile)
+ local compinst = compiler.load("cxx", {target = target})
+ local compflags = compinst:compflags({sourcefile = sourcefile, target = target})
+ local defines
+ for _, flag in ipairs(compflags) do
+ if flag:startswith("-D") then
+ defines = defines or {}
+ table.insert(defines, flag:sub(3))
+ end
+ end
+ return defines
+end
+
+-- build module file for batchjobs
+function make_module_buildjobs(target, batchjobs, job_name, deps, opt)
+
+ local name, provide, _ = compiler_support.get_provided_module(opt.module)
+ local bmifile = provide and compiler_support.get_bmi_path(provide.bmi)
+ local dryrun = option.get("dry-run")
+
+ return {
+ name = job_name,
+ deps = table.join(target:name() .. "_populate_module_map", deps),
+ sourcefile = opt.cppfile,
+ job = batchjobs:newjob(name or opt.cppfile, function(index, total)
+ local mapped_bmi
+ if provide and compiler_support.memcache():get2(target:name() .. name, "reuse") then
+ if not target:is_binary() then
+ return
+ else
+ mapped_bmi = get_from_target_mapper(target, name).bmi
+ end
+ end
+
+ local build, dependinfo
+ local dependfile = target:dependfile(bmifile or opt.objectfile)
+ if provide or compiler_support.has_module_extension(opt.cppfile) then
+ build, dependinfo = should_build(target, opt.cppfile, bmifile, {name = name, objectfile = opt.objectfile, requires = opt.module.requires})
+
+ -- needed to detect rebuild of dependencies
+ if provide and build then
+ mark_build(target, name)
+ end
+ end
+
+ -- append requires flags
+ if opt.module.requires then
+ _append_requires_flags(target, opt.module, name, opt.cppfile, bmifile, opt)
+ end
+
+ -- for cpp file we need to check after appendings the flags
+ if build == nil then
+ build, dependinfo = should_build(target, opt.cppfile, bmifile, {name = name, objectfile = opt.objectfile, requires = opt.module.requires})
+ end
+
+ if build then
+ -- compile if it's a named module
+ if provide or compiler_support.has_module_extension(opt.cppfile) then
+ if not dryrun then
+ local objectdir = path.directory(opt.objectfile)
+ if not os.isdir(objectdir) then
+ os.mkdir(objectdir)
+ end
+ end
+
+ local fileconfig = target:fileconfig(opt.cppfile)
+ local public = fileconfig and fileconfig.public
+ local external = fileconfig and fileconfig.external
+ local bmifile = mapped_bmi or bmifile
+ if target:is_binary() then
+ if mapped_bmi then
+ progress.show((index * 100) / total, "${color.build.target}<%s> ${clear}${color.build.object}compiling.objectfile.$(mode) %s", target:name(), name or opt.cppfile)
+ _compile_objectfile_step(target, bmifile, opt.cppfile, opt.objectfile)
+ else
+ progress.show((index * 100) / total, "${color.build.target}<%s> ${clear}${color.build.object}compiling.module.$(mode) %s", target:name(), name or opt.cppfile)
+ _compile_one_step(target, bmifile, opt.cppfile, opt.objectfile, {std = (name == "std" or name == "std.compat")})
+ end
+ else
+ if not public and not external then
+ progress.show((index * 100) / total, "${color.build.target}<%s> ${clear}${color.build.object}compiling.module.$(mode) %s", target:name(), name or opt.cppfile)
+ _compile_one_step(target, bmifile, opt.cppfile, opt.objectfile, {std = (name == "std" or name == "std.compat")})
+ else
+ progress.show((index * 100) / total, "${color.build.target}<%s> ${clear}${color.build.object}compiling.bmi.$(mode) %s", target:name(), name or opt.cppfile)
+ _compile_bmi_step(target, bmifile, opt.cppfile, {std = (name == "std" or name == "std.compat")})
+ end
+ end
+ else
+ os.tryrm(opt.objectfile) -- force rebuild for .cpp files
+ end
+ depend.save(dependinfo, dependfile)
+ end
+ end)}
+end
+
+-- build module file for batchcmds
+function make_module_buildcmds(target, batchcmds, opt)
+
+ local name, provide, _ = compiler_support.get_provided_module(opt.module)
+ local bmifile = provide and compiler_support.get_bmi_path(provide.bmi)
+
+ local mapped_bmi
+ if provide and compiler_support.memcache():get2(target:name() .. name, "reuse") then
+ if not target:is_binary() then
+ return
+ else
+ mapped_bmi = get_from_target_mapper(target, name).bmi
+ end
+ end
+
+ local build
+ if provide or compiler_support.has_module_extension(opt.cppfile) then
+ build = should_build(target, opt.cppfile, bmifile, {name = name, objectfile = opt.objectfile, requires = opt.module.requires})
+
+ -- needed to detect rebuild of dependencies
+ if provide and build then
+ mark_build(target, name)
+ end
+ end
+
+ -- append requires flags
+ if opt.module.requires then
+ _append_requires_flags(target, opt.module, name, opt.cppfile, bmifile, opt)
+ end
+
+ -- for cpp file we need to check after appendings the flags
+ if build == nil then
+ build = should_build(target, opt.cppfile, bmifile, {name = name, objectfile = opt.objectfile, requires = opt.module.requires})
+ end
+
+ if build then
+ -- compile if it's a named module
+ if provide or compiler_support.has_module_extension(opt.cppfile) then
+ batchcmds:mkdir(path.directory(opt.objectfile))
+
+ local fileconfig = target:fileconfig(opt.cppfile)
+ local public = fileconfig and fileconfig.public
+ local external = fileconfig and fileconfig.external
+ local bmifile = mapped_bmi or bmifile
+ if target:is_binary() then
+ if mapped_bmi then
+ batchcmds:show_progress(opt.progress, "${color.build.target}<%s> ${clear}${color.build.object}compiling.objectfile.$(mode) %s", target:name(), name or opt.cppfile)
+ _compile_objectfile_step(target, bmifile, opt.cppfile, opt.objectfile, {batchcmds = batchcmds})
+ else
+ batchcmds:show_progress(opt.progress, "${color.build.target}<%s> ${clear}${color.build.object}compiling.module.$(mode) %s", target:name(), name or opt.cppfile)
+ _compile_one_step(target, bmifile, opt.cppfile, opt.objectfile, {std = (name == "std" or name == "std.compat"), batchcmds = batchcmds})
+ end
+ else
+ if not public and not external then
+ batchcmds:show_progress(opt.progress, "${color.build.target}<%s> ${clear}${color.build.object}compiling.module.$(mode) %s", target:name(), name or opt.cppfile)
+ _compile_one_step(target, bmifile, opt.cppfile, opt.objectfile, {std = (name == "std" or name == "std.compat"), batchcmds = batchcmds})
+ else
+ batchcmds:show_progress(opt.progress, "${color.build.target}<%s> ${clear}${color.build.object}compiling.bmi.$(mode) %s", target:name(), name or opt.cppfile)
+ _compile_bmi_step(target, bmifile, opt.cppfile, {std = (name == "std" or name == "std.compat"), batchcmds = batchcmds})
+ end
+ end
+ else
+ batchcmds:rm(opt.objectfile) -- force rebuild for .cpp files
+ end
+ end
+ batchcmds:add_depfiles(opt.cppfile)
+ return os.mtime(opt.objectfile)
+end
+
+-- build headerunit file for batchjobs
+function make_headerunit_buildjobs(target, job_name, batchjobs, headerunit, bmifile, outputdir, opt)
+ local already_exists = add_headerunit_to_target_mapper(target, headerunit, bmifile)
+ if not already_exists then
+ return {
+ name = job_name,
+ sourcefile = headerunit.path,
+ job = batchjobs:newjob(job_name, function(index, total)
+ if not os.isdir(outputdir) then
+ os.mkdir(outputdir)
+ end
+
+ local compinst = compiler.load("cxx", {target = target})
+ local compflags = compinst:compflags({sourcefile = headerunit.path, target = target})
+
+ local dependfile = target:dependfile(bmifile)
+ local dependinfo = depend.load(dependfile) or {}
+ dependinfo.files = {}
+ local depvalues = {compinst:program(), compflags}
+
+ if opt.build then
+ progress.show((index * 100) / total, "${color.build.target}<%s> ${clear}${color.build.object}compiling.headerunit.$(mode) %s", target:name(), headerunit.name)
+ _compile(target, _make_headerunitflags(target, headerunit, bmifile), headerunit.path, bmifile)
+ end
+
+ table.insert(dependinfo.files, headerunit.path)
+ dependinfo.values = depvalues
+ depend.save(dependinfo, dependfile)
+ end)}
+ end
+end
+
+-- build headerunit file for batchcmds
+function make_headerunit_buildcmds(target, batchcmds, headerunit, bmifile, outputdir, opt)
+ batchcmds:mkdir(outputdir)
+ add_headerunit_to_target_mapper(target, headerunit, bmifile)
+
+ if opt.build then
+ local name = headerunit.unique and headerunit.name or headerunit.path
+ batchcmds:show_progress(opt.progress, "${color.build.target}<%s> ${clear}${color.build.object}compiling.headerunit.$(mode) %s", target:name(), name)
+ _batchcmds_compile(batchcmds, target, _make_headerunitflags(target, headerunit, bmifile), bmifile)
+ end
+ batchcmds:add_depfiles(headerunit.path)
+ return os.mtime(bmifile)
+end
+
+function get_requires(target, module)
+ local _requires
+ local flags = _get_requiresflags(target, module)
+ for _, flag in ipairs(flags) do
+ _requires = _requires or {}
+ table.insert(_requires, flag:split("=")[3])
+ end
+ return _requires
+end
+
diff --git a/xmake/rules/c++/modules/modules_support/clang/compiler_support.lua b/xmake/rules/c++/modules/modules_support/clang/compiler_support.lua
new file mode 100644
index 000000000..49e01db90
--- /dev/null
+++ b/xmake/rules/c++/modules/modules_support/clang/compiler_support.lua
@@ -0,0 +1,454 @@
+--!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 clang/compiler_support.lua
+--
+
+-- imports
+import("core.base.semver")
+import("core.base.option")
+import("core.base.json")
+import("lib.detect.find_tool")
+import("lib.detect.find_file")
+import(".compiler_support", {inherit = true})
+
+-- 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(target, includedirs, clang)
+ local tmpfile = os.tmpfile() .. ".cc"
+ io.writefile(tmpfile, "#include <vector>")
+ local argv = {"-E", "-x", "c++", tmpfile}
+ local cpplib = _get_cpplibrary_name(target)
+ if cpplib then
+ if cpplib == "c++" then
+ table.insert(argv, 1, "-stdlib=libc++")
+ elseif cpplib == "stdc++" then
+ table.insert(argv, 1, "-stdlib=libstdc++")
+ end
+ end
+ local result = try {function () return os.iorunv(clang, argv) 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
+
+function _get_cpplibrary_name(target)
+ -- libc++ come first because on windows, if we use libc++ clang will still use msvc crt so MD / MT / MDd / MTd can be set
+ if target:has_runtime("c++_shared", "c++_static") then
+ return "c++"
+ elseif target:has_runtime("stdc++_shared", "stdc++_static") then
+ return "stdc++"
+ elseif target:is_plat("windows") and target:has_runtime("MD", "MT", "MDd", "MTd") then
+ return "msstl"
+ end
+end
+
+-- load module support for the current target
+function load(target)
+ local clangmodulesflag, modulestsflag, withoutflag = get_modulesflag(target)
+
+ -- add module flags
+ if not withoutflag then
+ target:add("cxxflags", modulestsflag)
+ end
+
+ -- fix default visibility for functions and variables [-fvisibility] differs in PCH file vs. current file
+ -- module.pcm cannot be loaded due to a configuration mismatch with the current compilation.
+ --
+ -- it will happen in binary target depend on library target with modules, and enable release mode at same time.
+ --
+ -- @see https://github.com/xmake-io/xmake/issues/3358#issuecomment-1432586767
+ local dep_symbols
+ local has_library_deps = false
+ for _, dep in ipairs(target:orderdeps()) do
+ if dep:is_shared() or dep:is_static() or dep:is_object() then
+ dep_symbols = dep:get("symbols")
+ has_library_deps = true
+ break
+ end
+ end
+ if has_library_deps then
+ target:set("symbols", dep_symbols and dep_symbols or "none")
+ end
+
+ -- on Windows before llvm18 we need to disable delayed-template-parsing because it's incompatible with modules, from llvm >= 18, it's disabled by default
+ local clang_version = get_clang_version(target)
+ if semver.compare(clang_version, "18") < 0 then
+ target:add("cxxflags", "-fno-delayed-template-parsing")
+ end
+end
+
+-- strip flags that doesn't affect bmi generation
+function strip_flags(target, flags)
+ -- speculative list as there is no resource that list flags that prevent reusability, this list will likely be improve over time
+ -- @see https://clang.llvm.org/docs/StandardCPlusPlusModules.html#consistency-requirement
+ local strippable_flags = {
+ "-I",
+ "-isystem",
+ "-g",
+ "-O",
+ "-W",
+ "-w",
+ "-cxx-isystem",
+ "-Q",
+ }
+ if not target:policy("build.c++.modules.tryreuse.discriminate_on_defines") then
+ table.join2(strippable_flags, {"-D", "-U"})
+ end
+ local output = {}
+ local last_flag_I = false
+ for _, flag in ipairs(flags) do
+ local strip = false
+ for _, _flag in ipairs(strippable_flags) do
+ if flag:startswith(_flag) or last_flag_I then
+ last_flag_I = _flag == "-I"
+ strip = true
+ break
+ end
+ end
+ if not strip then
+ table.insert(output, flag)
+ end
+ end
+ return output
+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("cxx")
+ assert(toolname:startswith("clang"))
+ _get_toolchain_includedirs_for_stlheaders(target, includedirs, clang)
+ local cpplib = _get_cpplibrary_name(target)
+ local runtime_flag
+ if cpplib then
+ if cpplib == "c++" then
+ runtime_flag = "-stdlib=libc++"
+ elseif cpplib == "stdc++" then
+ runtime_flag = "-stdlib=libstdc++"
+ end
+ end
+ local _, result = try {function () return os.iorunv(clang, table.join({"-E", "-Wp,-v", "-xc", os.nuldev()}, runtime_flag or {})) 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
+
+-- get clang path
+function get_clang_path(target)
+ local clang_path = _g.clang_path
+ if not clang_path then
+ local program, toolname = target:tool("cxx")
+ if program and toolname:startswith("clang") then
+ local clang = find_tool(toolname, {program = program})
+ if clang then
+ clang_path = clang.program
+ end
+ end
+ clang_path = clang_path or false
+ _g.clang_path = clang_path
+ end
+ return clang_path or nil
+end
+
+-- get clang version
+function get_clang_version(target)
+ local clang_version = _g.clang_version
+ if not clang_version then
+ local program, toolname = target:tool("cxx")
+ if program and toolname:startswith("clang") then
+ local clang = find_tool(toolname, {program = program, version = true})
+ if clang then
+ clang_version = clang.version
+ end
+ end
+ clang_version = clang_version or false
+ _g.clang_version = clang_version
+ end
+ return clang_version or nil
+end
+
+-- get clang-scan-deps
+function get_clang_scan_deps(target)
+ local clang_scan_deps = _g.clang_scan_deps
+ if not clang_scan_deps then
+ local program, toolname = target:tool("cxx")
+ if program and toolname:startswith("clang") then
+ local dir = path.directory(program)
+ local basename = path.basename(program)
+ local extension = path.extension(program)
+ program = (basename:gsub("clang", "clang-scan-deps")) .. extension
+ if dir and dir ~= "." and os.isdir(dir) then
+ program = path.join(dir, program)
+ end
+ local result = find_tool("clang-scan-deps", {program = program, version = true})
+ if result then
+ clang_scan_deps = result.program
+ end
+ end
+ clang_scan_deps = clang_scan_deps or false
+ _g.clang_scan_deps = clang_scan_deps
+ end
+ return clang_scan_deps or nil
+end
+
+function get_stdmodules(target)
+ if target:policy("build.c++.modules.std") then
+ local cpplib = _get_cpplibrary_name(target)
+ if cpplib then
+ if cpplib == "c++" then
+ -- libc++ module is found by parsing libc++.modules.json
+ -- which can be found in <llvm_path>/lib subdirectory (i.e on debian it should be <llvm_path>/lib/x86_64-unknown-linux-gnu/)
+ -- in the futur llvm may provide a way to directory get the path of libc++.modules.json
+ -- @see https://github.com/llvm/llvm-project/pull/76451 (has been revert, so we need to wait)
+ local clang_path = path.directory(get_clang_path(target))
+ local clang_lib_path = path.join(clang_path, "..", "lib")
+ local modules_json_path = path.join(clang_lib_path, "libc++.modules.json")
+ if not os.isfile(modules_json_path) then
+ modules_json_path = find_file("*/libc++.modules.json", clang_lib_path)
+ end
+ if modules_json_path then
+ local modules_json = json.decode(io.readfile(modules_json_path))
+ local std_module_directory = path.directory(modules_json.modules[1]["source-path"])
+ if not path.is_absolute(std_module_directory) then
+ std_module_directory = path.join(path.directory(modules_json_path), std_module_directory)
+ end
+ return {path.join(std_module_directory, "std.cppm"), path.join(std_module_directory, "std.compat.cppm")}
+ end
+ elseif cpplib == "stdc++" then
+ -- libstdc++ doesn't have a std module file atm
+ elseif cpplib == "msstl" then
+ -- msstl std module file is not compatible with llvm <= 19
+ -- local toolchain = target:toolchain("clang")
+ -- local msvc = import("core.tool.toolchain", {anonymous = true}).load("msvc", {plat = toolchain:plat(), arch = toolchain:arch()})
+ -- if msvc then
+ -- local vcvars = msvc:config("vcvars")
+ -- if vcvars.VCInstallDir and vcvars.VCToolsVersion then
+ -- modules = {}
+ --
+ -- local stdmodulesdir = path.join(vcvars.VCInstallDir, "Tools", "MSVC", vcvars.VCToolsVersion, "modules")
+ -- assert(stdmodulesdir, "Can't enable C++23 std modules, directory missing !")
+ --
+ -- return {path.join(stdmodulesdir, "std.ixx"), path.join(stdmodulesdir, "std.compat.ixx")}
+ -- end
+ -- end
+ end
+ end
+ end
+end
+
+function get_bmi_extension()
+ return ".pcm"
+end
+
+function get_modulesflag(target)
+ local clangmodulesflag = _g.clangmodulesflag
+ local modulestsflag = _g.modulestsflag
+ local withoutflag = _g.withoutflag
+ if clangmodulesflag == nil and modulestsflag == nil then
+ local compinst = target:compiler("cxx")
+ if compinst:has_flags("-fmodules", "cxxflags", {flagskey = "clang_modules"}) then
+ clangmodulesflag = "-fmodules"
+ end
+ if compinst:has_flags("-fmodules-ts", "cxxflags", {flagskey = "clang_modules_ts"}) then
+ modulestsflag = "-fmodules-ts"
+ end
+ local clang_version = get_clang_version(target)
+ withoutflag = semver.compare(clang_version, "16.0") >= 0
+ assert(withoutflag or modulestsflag, "compiler(clang): does not support c++ module!")
+ _g.clangmodulesflag = clangmodulesflag or false
+ _g.modulestsflag = modulestsflag or false
+ _g.withoutflag = withoutflag or false
+ end
+ return clangmodulesflag or nil, modulestsflag or nil, withoutflag or nil
+end
+
+function get_builtinmodulemapflag(target)
+ local builtinmodulemapflag = _g.builtinmodulemapflag
+ if builtinmodulemapflag == nil then
+ -- this flag seems clang on mingw doesn't distribute it
+ -- @see https://github.com/xmake-io/xmake/pull/2833
+ if not target:is_plat("mingw") 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!")
+ end
+ _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_implicitmodulemapsflag(target)
+ local implicitmodulemapsflag = _g.implicitmodulemapsflag
+ if implicitmodulemapsflag == nil then
+ local compinst = target:compiler("cxx")
+ if compinst:has_flags("-fimplicit-module-maps", "cxxflags", {flagskey = "clang_implicit_module_map"}) then
+ implicitmodulemapsflag = "-fimplicit-module-maps"
+ end
+ assert(implicitmodulemapsflag, "compiler(clang): does not support c++ module!")
+ _g.implicitmodulemapsflag = implicitmodulemapsflag or false
+ end
+ return implicitmodulemapsflag 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 get_moduleheaderflag(target)
+ local moduleheaderflag = _g.moduleheaderflag
+ if moduleheaderflag == nil then
+ local compinst = target:compiler("cxx")
+ if compinst:has_flags("-fmodule-header=system", "cxxflags", {flagskey = "clang_module_header"}) then
+ moduleheaderflag = "-fmodule-header="
+ end
+ _g.moduleheaderflag = moduleheaderflag or false
+ end
+ return moduleheaderflag or nil
+end
+
+function has_clangscandepssupport(target)
+ local support_clangscandeps = _g.support_clangscandeps
+ if support_clangscandeps == nil then
+ local clangscandeps = get_clang_scan_deps(target)
+ local clang_version = get_clang_version(target)
+ if clangscandeps and clang_version and semver.compare(clang_version, "16.0") >= 0 then
+ support_clangscandeps = true
+ end
+ _g.support_clangscandeps = support_clangscandeps or false
+ end
+ return support_clangscandeps or nil
+end
+
+function get_keepsystemincludesflag(target)
+ local keepsystemincludesflag = _g.keepsystemincludesflag
+ if keepsystemincludesflag == nil then
+ local compinst = target:compiler("cxx")
+ local clang_version = get_clang_version(target)
+ if compinst:has_flags("-E -fkeep-system-includes", "cxxflags", {flagskey = "clang_keep_system_includes", tryrun = true}) and
+ semver.compare(clang_version, "18.0") >= 0 then
+ keepsystemincludesflag = "-fkeep-system-includes"
+ end
+ _g.keepsystemincludesflag = keepsystemincludesflag or false
+ end
+ return keepsystemincludesflag or nil
+end
+
+function get_moduleoutputflag(target)
+ local moduleoutputflag = _g.moduleoutputflag
+ if moduleoutputflag == nil then
+ local compinst = target:compiler("cxx")
+ local clang_version = get_clang_version(target)
+ if compinst:has_flags("-fmodule-output=", "cxxflags", {flagskey = "clang_module_output", tryrun = true}) and
+ semver.compare(clang_version, "16.0") >= 0 then
+ moduleoutputflag = "-fmodule-output="
+ end
+ _g.moduleoutputflag = moduleoutputflag or false
+ end
+ return moduleoutputflag or nil
+end
+
diff --git a/xmake/rules/c++/modules/modules_support/clang/dependency_scanner.lua b/xmake/rules/c++/modules/modules_support/clang/dependency_scanner.lua
new file mode 100644
index 000000000..9e3982bb0
--- /dev/null
+++ b/xmake/rules/c++/modules/modules_support/clang/dependency_scanner.lua
@@ -0,0 +1,84 @@
+--!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 clang/dependency_scanner.lua
+--
+
+-- imports
+import("core.base.json")
+import("core.base.semver")
+import("core.base.option")
+import("core.project.depend")
+import("utils.progress")
+import("compiler_support")
+import(".dependency_scanner", {inherit = true})
+
+function generate_dependency_for(target, sourcefile, opt)
+ local compinst = target:compiler("cxx")
+ local changed = false
+ local dependfile = target:dependfile(sourcefile)
+ depend.on_changed(function()
+ if opt.progress then
+ progress.show(opt.progress, "${color.build.target}<%s> generating.module.deps %s", target:name(), sourcefile)
+ end
+
+ local outputdir = compiler_support.get_outputdir(target, sourcefile)
+ local jsonfile = path.translate(path.join(outputdir, path.filename(sourcefile) .. ".json"))
+ if compiler_support.has_clangscandepssupport(target) and not target:policy("build.c++.clang.fallbackscanner") then
+ -- We need absolute path of clang to use clang-scan-deps
+ -- See https://clang.llvm.org/docs/StandardCPlusPlusModules.html#possible-issues-failed-to-find-system-headers
+ local clang_path = compinst:program()
+ if not path.is_absolute(clang_path) then
+ clang_path = compiler_support.get_clang_path(target) or compinst:program()
+ end
+ local clangscandeps = compiler_support.get_clang_scan_deps(target)
+ local compflags = compinst:compflags({sourcefile = sourcefile, target = target})
+ local flags = table.join({"--format=p1689", "--",
+ clang_path, "-x", "c++", "-c", sourcefile, "-o", target:objectfile(sourcefile)}, compflags or {})
+ if option.get("verbose") then
+ print(os.args(table.join(clangscandeps, flags)))
+ end
+ local outdata, errdata = os.iorunv(clangscandeps, flags)
+ assert(errdata, errdata)
+
+ io.writefile(jsonfile, outdata)
+ else
+ fallback_generate_dependencies(target, jsonfile, sourcefile, function(file)
+ local keepsystemincludesflag = compiler_support.get_keepsystemincludesflag(target)
+ local compflags = compinst:compflags({sourcefile = file, target = target})
+ -- exclude -fmodule* and -std=c++/gnu++* flags because,
+ -- when they are set clang try to find bmi of imported modules but they don't exists a this point of compilation
+ table.remove_if(compflags, function(_, flag)
+ return flag:startswith("-fmodule") or flag:startswith("-std=c++") or flag:startswith("-std=gnu++")
+ end)
+ local ifile = path.translate(path.join(outputdir, path.filename(file) .. ".i"))
+ local flags = table.join(compflags or {}, keepsystemincludesflag or {}, {"-E", "-x", "c++", file, "-o", ifile})
+ os.vrunv(compinst:program(), flags)
+ local content = io.readfile(ifile)
+ os.rm(ifile)
+ return content
+ end)
+ end
+ changed = true
+
+ local rawdependinfo = io.readfile(jsonfile)
+ return {moduleinfo = rawdependinfo}
+ end, {dependfile = dependfile, files = {sourcefile}, changed = target:is_rebuilt()})
+
+ return changed
+end
+
diff --git a/xmake/rules/c++/modules/modules_support/common.lua b/xmake/rules/c++/modules/modules_support/common.lua
deleted file mode 100644
index d42d0f958..000000000
--- a/xmake/rules/c++/modules/modules_support/common.lua
+++ /dev/null
@@ -1,737 +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 Arthapz, ruki
--- @file common.lua
---
-
--- imports
-import("core.base.json")
-import("core.base.bytes")
-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("private.async.buildjobs")
-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, opt)
- opt = opt or {}
- local stlcachedir = path.join(config.buildir(), "stlmodules", "cache", target:arch(), config.mode() or "release")
- if opt.mkdir and 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, opt)
- opt = opt or {}
- local cachedir = path.join(target:autogendir(), "rules", "modules", "cache")
- if opt.mkdir and 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, unique = r.unique})
- 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, unique = r.unique})
- 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
-
-function parse_meta_info(target, metafile)
- local metadata = json.loadfile(metafile)
- if metadata._VENDOR_extension.xmake then
- return metadata._VENDOR_extension.xmake.file, metadata._VENDOR_extension.xmake.name, metadata
- end
-
- local filename = path.basename(metafile)
- local metadir = path.directory(metafile)
- for _, ext in ipairs({".mpp", ".mxx", ".cppm", ".ixx"}) do
- if os.isfile(path.join(metadir, filename .. ext)) then
- filename = filename .. ext
- break
- end
- end
-
- local sourcecode = io.readfile(path.join(path.directory(metafile), filename))
- sourcecode = sourcecode:gsub("//.-\n", "\n")
- sourcecode = sourcecode:gsub("/%*.-%*/", "")
-
- local name
- for _, line in ipairs(sourcecode:split("\n", {plain = true})) do
- name = line:match("export%s+module%s+(.+)%s*;") or line:match("export%s+__preprocessed_module%s+(.+)%s*;")
- if name then
- break
- end
- end
-
- return filename, name, metadata
-end
-
--- extract packages modules dependencies
-function get_all_package_modules(target, modules, opt)
- local package_modules
-
- -- parse all meta-info and append their informations to the package store
- for _, package in pairs(target:pkgs()) do
- package_modules = package_modules or {}
- local modulesdir = path.join(package:installdir(), "modules")
- local metafiles = os.files(path.join(modulesdir, "*", "*.meta-info"))
- for _, metafile in ipairs(metafiles) do
- local modulefile, name, metadata = parse_meta_info(target, metafile)
- package_modules[name] = {
- file = path.join(modulesdir, modulefile),
- metadata = metadata
- }
- end
- end
- return package_modules
-end
-
--- cull unused modules
-function cull_unused_modules(target, modules, package_modules_data)
- local needed_modules = {}
- -- append all target dependencies
- for _, module in pairs(modules) do
- if module.requires then
- for required, _ in pairs(module.requires) do
- table.insert(needed_modules, required)
- end
- end
- end
-
- -- append all package dependencies
- local culled
- local module_names = table.keys(package_modules_data)
- for _, name in ipairs(module_names) do
- culled = culled or {}
- if table.find(needed_modules, name) and package_modules_data[name] and not culled[name] then
- culled[name] = package_modules_data[name]
-
- if culled[name].metadata.imports then
- table.join2(needed_modules, culled[name].metadata.imports)
- table.join2(module_names, culled[name].metadata.imports)
- end
- end
- end
- return culled
-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
- target_with_modules = target:policy("build.c++.modules")
- end
- 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_dependencies_data(target, moduleinfos)
- local modules
- 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"])
- local bmifile = provide["compiled-module-path"]
- -- try to find the compiled module path in outputs filed (MSVC doesn't generate compiled-module-path)
- if not bmifile then
- for _, output in ipairs(rule.outputs) do
- if output:endswith(".ifc") or output:endswith(".pcm") or output:endswith(".bmi") then
- bmifile = output
- break
- end
- end
-
- -- we didn't found the compiled module path, so we assume it
- if not bmifile then
- local name = provide["logical-name"] .. bmi_extension(target)
- -- partition ":" character is invalid path character on windows
- -- @see https://github.com/xmake-io/xmake/issues/2954
- name = name:replace(":", "-")
- bmifile = path.join(get_outputdir(target, name), name)
- end
- end
- m.provides[provide["logical-name"]] = {
- bmi = bmifile,
- sourcefile = moduleinfo.sourcefile,
- interface = provide["is-interface"]
- }
- 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
-
--- check circular dependencies for the given module
-function _check_circular_dependencies_of_module(name, moduledeps, modulesources, depspath)
- for _, dep in ipairs(moduledeps[name]) do
- local depinfo = moduledeps[dep]
- if depinfo then
- local depspath_sub
- if depspath then
- for idx, name in ipairs(depspath) do
- if name == dep then
- local circular_deps = table.slice(depspath, idx)
- table.insert(circular_deps, dep)
- local sourceinfo = ""
- for _, circular_depname in ipairs(circular_deps) do
- local sourcefile = modulesources[circular_depname]
- if sourcefile then
- sourceinfo = sourceinfo .. ("\n -> module(%s) in %s"):format(circular_depname, sourcefile)
- end
- end
- os.raise("circular modules dependency(%s) detected!%s", table.concat(circular_deps, ", "), sourceinfo)
- end
- end
- depspath_sub = table.join(depspath, dep)
- end
- _check_circular_dependencies_of_module(dep, moduledeps, modulesources, depspath_sub)
- end
- end
-end
-
--- check circular dependencies
--- @see https://github.com/xmake-io/xmake/issues/3031
-function _check_circular_dependencies(modules)
- local moduledeps = {}
- local modulesources = {}
- for _, mod in pairs(modules) do
- if mod then
- if mod.provides and mod.requires then
- for name, provide in pairs(mod.provides) do
- modulesources[name] = provide.sourcefile
- local deps = moduledeps[name]
- if deps then
- table.join2(deps, mod.requires)
- else
- moduledeps[name] = table.keys(mod.requires)
- end
- end
- end
- end
- end
- for name, _ in pairs(moduledeps) do
- _check_circular_dependencies_of_module(name, moduledeps, modulesources, {name})
- end
-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 = table.join(dep:get("sysincludedirs") or {}, dep:get("includedirs") or {})
- table.join2(headerpaths, includedirs)
- end
- for _, pkg in pairs(target:pkgs()) do
- local includedirs = table.join(pkg:get("sysincludedirs") or {}, pkg:get("includedirs") or {})
- table.join2(headerpaths, includedirs)
- 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, preprocess_file)
- local output = {version = 1, revision = 0, rules = {}}
- local rule = {outputs = {jsonfile}}
- rule["primary-output"] = target:objectfile(sourcefile)
-
- local module_name_export
- local module_name_private
- local module_deps = {}
- local module_deps_set = hashset.new()
- local sourcecode = preprocess_file(sourcefile) or io.readfile(sourcefile)
- local internal = false
- sourcecode = sourcecode:gsub("//.-\n", "\n")
- sourcecode = sourcecode:gsub("/%*.-%*/", "")
- for _, line in ipairs(sourcecode:split("\n", {plain = true})) do
- if line:match("#") then
- goto continue
- end
- if not module_name_export then
- module_name_export = line:match("export%s+module%s+(.+)%s*;") or line:match("export%s+__preprocessed_module%s+(.+)%s*;")
- end
- if not module_name_private then
- module_name_private = line:match("module%s+(.+)%s*;") or line:match("__preprocessed_module%s+(.+)%s*;")
- if module_name_private then
- internal = module_name_private:find(":")
- end
- end
- local module_depname = line:match("import%s+(.+)%s*;")
- -- we need to 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 = module_name_private
- end
- if module_depname and not module_deps_set:has(module_depname) then
- local module_dep = {}
- -- partition? import :xxx;
- if module_depname:startswith(":") then
- local module_name = (module_name_export or module_name_private or "")
- module_name = module_name:split(":")[1]
- module_dep["unique-on-source-path"] = true
- 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)
- module_deps_set:insert(module_depname)
- end
- ::continue::
- end
-
- if module_name_export or internal then
- local outputdir = get_outputdir(target, sourcefile)
-
- local provide = {}
- provide["logical-name"] = module_name_export or module_name_private
- provide["source-path"] = sourcefile
- provide["is-interface"] = not internal
- provide["compiled-module-path"] = path.join(outputdir, (module_name_export or module_name_private) .. bmi_extension(target))
-
- 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 or opt.regenerate 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_dependencies_data(target, moduleinfos)
- modules = table.join(modules or {}, modules_support(target).get_stdmodules(target))
- if modules then
- _check_circular_dependencies(modules)
- end
- localcache():set2("modules", cachekey, modules)
- localcache():save()
- end
- memcache():set2("modules", cachekey, modules)
- end
- return modules
-end
-
--- generate headerunits for batchcmds
-function generate_headerunits_for_batchcmds(target, batchcmds, sourcebatch, modules, opt)
- local user_headerunits, stl_headerunits = get_headerunits(target, sourcebatch, modules)
- -- build stl header units as other headerunits may need them
- 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
-
--- build batchjobs for modules
-function build_batchjobs_for_modules(modules, batchjobs, rootjob)
- return buildjobs(modules, batchjobs, rootjob)
-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_dependency_objectfiles(target)
- local cachekey = target:name() .. "dependency_objectfiles"
- local cache = localcache():get(cachekey)
- if cache then
- if target:is_binary() then
- target:add("ldflags", cache, {force = true, expand = false})
- elseif target:is_static() then
- target:add("arflags", cache, {force = true, expand = false})
- elseif target:is_shared() then
- target:add("shflags", cache, {force = true, expand = false})
- end
- end
-end
-
--- generate meta module informations for package / other buildsystems import
--- based on https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2473r1.pdf
---
--- e.g
--- {
--- "include_paths": ["foo/", "bar/"]
--- "definitions": ["FOO=BAR"]
--- "imports": ["std", "bar"]
--- "_VENDOR_extension": {}
--- }
-function generate_meta_module_info(target, name, sourcefile, requires)
- local module_metadata = {}
-
- -- add include paths
- module_metadata.include_paths = table.wrap(target:get("includedirs")) or {}
- for _, deps in ipairs(target:orderdeps()) do
- table.join2(module_metadata.include_paths, deps:get("includedirs") or {})
- end
-
- -- add definitions
- module_metadata.definitions = table.wrap(target:get("defines")) or {}
- for _, deps in ipairs(target:orderdeps()) do
- table.join2(module_metadata.definitions, deps:get("defines") or {})
- end
-
- -- add imports
- if requires then
- for name, _ in pairs(requires) do
- module_metadata.imports = module_metadata.imports or {}
- table.append(module_metadata.imports, name)
- end
- end
-
- local modulehash = get_modulehash(target, sourcefile)
- module_metadata._VENDOR_extension = { xmake = { name = name, file = path.join(modulehash, path.filename(sourcefile)) }}
- return module_metadata
-end
-
-function install_module_target(target)
- local sourcebatch = target:sourcebatches()["c++.build.modules.install"]
- if sourcebatch and sourcebatch.sourcefiles then
- for _, sourcefile in ipairs(sourcebatch.sourcefiles) do
- local install = (fileconfig and not fileconfig.install) and false or true
- if install then
- local modulehash = get_modulehash(target, sourcefile)
- prefixdir = path.join("modules", modulehash)
- target:add("installfiles", sourcefile, {prefixdir = prefixdir})
- local metafile = get_metafile(target, sourcefile)
- if os.exists(metafile) then
- target:add("installfiles", metafile, {prefixdir = prefixdir})
- end
- end
- end
- end
-end
-
-function get_modulehash(target, modulepath)
- local key = path.directory(modulepath) .. target:name()
- return hash.uuid(key):split("-", {plain = true})[1]:lower()
-end
-
-function get_metafile(target, modulefile)
- local outputdir = get_outputdir(target, modulefile)
- return path.join(outputdir, path.filename(modulefile) .. ".meta-info")
-end
-
-function get_outputdir(target, module)
- local cachedir = modules_cachedir(target)
- local modulehash = get_modulehash(target, module.path or module)
- local outputdir = path.join(cachedir, modulehash)
- if not os.exists(outputdir) then
- os.mkdir(outputdir)
- end
- return outputdir
-end
diff --git a/xmake/rules/c++/modules/modules_support/compiler_support.lua b/xmake/rules/c++/modules/modules_support/compiler_support.lua
new file mode 100644
index 000000000..9d3d4fadf
--- /dev/null
+++ b/xmake/rules/c++/modules/modules_support/compiler_support.lua
@@ -0,0 +1,298 @@
+-- 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 compiler_support.lua
+--
+
+-- imports
+import("core.base.json")
+import("core.base.hashset")
+import("core.cache.memcache", {alias = "_memcache"})
+import("core.cache.localcache", {alias = "_localcache"})
+import("lib.detect.find_file")
+import("core.project.project")
+import("core.project.config")
+
+function _compiler_support(target)
+ local cachekey = tostring(target)
+ local compiler_support = memcache():get2("compiler_support", cachekey)
+ if compiler_support == nil then
+ if target:has_tool("cxx", "clang", "clangxx") then
+ compiler_support = import("clang.compiler_support", {anonymous = true})
+ elseif target:has_tool("cxx", "gcc", "gxx") then
+ compiler_support = import("gcc.compiler_support", {anonymous = true})
+ elseif target:has_tool("cxx", "cl") then
+ compiler_support = import("msvc.compiler_support", {anonymous = true})
+ else
+ local _, toolname = target:tool("cxx")
+ raise("compiler(%s): does not support c++ module!", toolname)
+ end
+ memcache():set2("compiler_support", cachekey, compiler_support)
+ end
+ return compiler_support
+end
+
+-- load module support for the current target
+function load(target)
+ _compiler_support(target).load(target)
+end
+
+-- strip flags not relevent for module reuse
+function strip_flags(target, flags)
+ return _compiler_support(target).strip_flags(target, flags)
+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)
+ table.insert(sourcebatch.objectfiles, objectfile)
+
+ local dependfile = target:dependfile(sourcefile or objectfile)
+ table.insert(sourcebatch.dependfiles, dependfile)
+ end
+end
+
+-- cull sourcebatch objectfiles
+function cull_objectfiles(target, modules, sourcebatch)
+
+ -- don't cull for executables
+ if target:is_binary() then
+ return
+ end
+
+ sourcebatch.objectfiles = {}
+ for _, sourcefile in ipairs(sourcebatch.sourcefiles) do
+ local objectfile = target:objectfile(sourcefile)
+ local module = modules[objectfile]
+ local _, provide, _ = get_provided_module(module)
+ if provide then
+ local fileconfig = target:fileconfig(sourcefile)
+ local public = fileconfig and fileconfig.public
+ local external = fileconfig and fileconfig.external
+ if not public and not external then
+ table.insert(sourcebatch.objectfiles, objectfile)
+ end
+ else
+ table.insert(sourcebatch.objectfiles, objectfile)
+ end
+ end
+end
+
+-- get bmi extension
+function get_bmi_extension(target)
+ return _compiler_support(target).get_bmi_extension()
+end
+
+-- get bmi path
+-- @see https://github.com/xmake-io/xmake/issues/4063
+function get_bmi_path(bmifile)
+ bmifile = bmifile:gsub(":", "_PARTITION_")
+ return path.normalize(bmifile)
+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.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
+ target_with_modules = target:policy("build.c++.modules")
+ end
+ 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
+
+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 = _compiler_support(target).toolchain_includedirs(target)
+ for _, dep in ipairs(target:orderdeps()) do
+ local includedirs = table.join(dep:get("sysincludedirs") or {}, dep:get("includedirs") or {})
+ table.join2(headerpaths, includedirs)
+ end
+ for _, pkg in ipairs(target:orderpkgs()) do
+ local includedirs = table.join(pkg:get("sysincludedirs") or {}, pkg:get("includedirs") or {})
+ table.join2(headerpaths, includedirs)
+ end
+ table.join2(headerpaths, target:get("includedirs"))
+ local p = find_file(file, headerpaths)
+ assert(p, "find <%s> not found!", file)
+ return p
+end
+
+-- get stdmodules
+function get_stdmodules(target)
+ return _compiler_support(target).get_stdmodules(target)
+end
+
+-- get memcache
+function memcache()
+ return _memcache.cache("cxxmodules")
+end
+
+-- get localcache
+function localcache()
+ return _localcache.cache("cxxmodules")
+end
+
+
+-- get stl headerunits cache directory
+function stlheaderunits_cachedir(target, opt)
+ opt = opt or {}
+ local stlcachedir = path.join(target:autogendir(), "rules", "bmi", "cache", "stl-headerunits")
+ if opt.mkdir and not os.isdir(stlcachedir) then
+ os.mkdir(stlcachedir)
+ os.mkdir(path.join(stlcachedir, "experimental"))
+ end
+ return stlcachedir
+end
+-- get stl modules cache directory
+function stlmodules_cachedir(target, opt)
+ opt = opt or {}
+ local stlcachedir = path.join(target:autogendir(), "rules", "bmi", "cache", "stl-modules")
+ if opt.mkdir and not os.isdir(stlcachedir) then
+ os.mkdir(stlcachedir)
+ end
+ return stlcachedir
+end
+
+-- get headerunits cache directory
+function headerunits_cachedir(target, opt)
+ opt = opt or {}
+ local cachedir = path.join(target:autogendir(), "rules", "bmi", "cache", "headerunits")
+ if opt.mkdir and not os.isdir(cachedir) then
+ os.mkdir(cachedir)
+ end
+ return cachedir
+end
+
+-- get modules cache directory
+function modules_cachedir(target, opt)
+ opt = opt or {}
+ local cachedir = path.join(target:autogendir(), "rules", "bmi", "cache", "modules")
+ if opt.mkdir and not os.isdir(cachedir) then
+ os.mkdir(cachedir)
+ end
+ return cachedir
+end
+
+function get_modulehash(target, modulepath)
+ local key = path.directory(modulepath) .. target:name()
+ return hash.uuid(key):split("-", {plain = true})[1]:lower()
+end
+
+function get_metafile(target, modulefile)
+ local outputdir = get_outputdir(target, modulefile)
+ return path.join(outputdir, path.filename(modulefile) .. ".meta-info")
+end
+
+function get_outputdir(target, module)
+ local cachedir = module and modules_cachedir(target) or headerunits_cachedir(target)
+ local modulehash = get_modulehash(target, module.path or module)
+ local outputdir = path.join(cachedir, modulehash)
+ if not os.exists(outputdir) then
+ os.mkdir(outputdir)
+ end
+ return outputdir
+end
+
+-- get name provide info and cpp sourcefile of a module
+function get_provided_module(module)
+
+ local name, provide, cppfile
+ if module.provides then
+ -- assume there that provides is only one, until we encounter the cases
+ -- "Some compiler may choose to implement the :private module partition as a separate module for lookup purposes, and if so, it should be indicated as a separate provides entry."
+ local length = 0
+ for k, v in pairs(module.provides) do
+ length = length + 1
+ name = k
+ provide = v
+ cppfile = provide.sourcefile
+ if length > 1 then
+ raise("multiple provides are not supported now!")
+ end
+ break
+ end
+ end
+
+ return name, provide, cppfile
+end
+
+function install_module_target(target)
+ local sourcebatch = target:sourcebatches()["c++.build.modules.install"]
+ if sourcebatch and sourcebatch.sourcefiles then
+ for _, sourcefile in ipairs(sourcebatch.sourcefiles) do
+ local fileconfig = target:fileconfig(sourcefile)
+ local install = fileconfig and fileconfig.public or false
+ if install then
+ local modulehash = get_modulehash(target, sourcefile)
+ local prefixdir = path.join("modules", modulehash)
+ target:add("installfiles", sourcefile, {prefixdir = prefixdir})
+ local metafile = get_metafile(target, sourcefile)
+ if os.exists(metafile) then
+ target:add("installfiles", metafile, {prefixdir = prefixdir})
+ end
+ end
+ end
+ end
+end
+
diff --git a/xmake/rules/c++/modules/modules_support/dependency_scanner.lua b/xmake/rules/c++/modules/modules_support/dependency_scanner.lua
new file mode 100644
index 000000000..ef0e66cc6
--- /dev/null
+++ b/xmake/rules/c++/modules/modules_support/dependency_scanner.lua
@@ -0,0 +1,456 @@
+--!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 dependency_scanner.lua
+--
+
+-- imports
+import("core.base.json")
+import("core.base.hashset")
+import("core.base.graph")
+import("core.base.option")
+import("async.runjobs")
+import("compiler_support")
+import("stl_headers")
+
+function _dependency_scanner(target)
+ local cachekey = tostring(target)
+ local dependency_scanner = compiler_support.memcache():get2("dependency_scanner", cachekey)
+ if dependency_scanner == nil then
+ if target:has_tool("cxx", "clang", "clangxx") then
+ dependency_scanner = import("clang.dependency_scanner", {anonymous = true})
+ elseif target:has_tool("cxx", "gcc", "gxx") then
+ dependency_scanner = import("gcc.dependency_scanner", {anonymous = true})
+ elseif target:has_tool("cxx", "cl") then
+ dependency_scanner = import("msvc.dependency_scanner", {anonymous = true})
+ else
+ local _, toolname = target:tool("cxx")
+ raise("compiler(%s): does not support c++ module!", toolname)
+ end
+ compiler_support.memcache():set2("dependency_scanner", cachekey, dependency_scanner)
+ end
+ return dependency_scanner
+end
+
+function _parse_meta_info(target, metafile)
+ local metadata = json.loadfile(metafile)
+ if metadata.file and metadata.name then
+ return metadata.file, metadata.name, metadata
+ end
+
+ local filename = path.basename(metafile)
+ local metadir = path.directory(metafile)
+ for _, ext in ipairs({".mpp", ".mxx", ".cppm", ".ixx"}) do
+ if os.isfile(path.join(metadir, filename .. ext)) then
+ filename = filename .. ext
+ break
+ end
+ end
+
+ local sourcecode = io.readfile(path.join(path.directory(metafile), filename))
+ sourcecode = sourcecode:gsub("//.-\n", "\n")
+ sourcecode = sourcecode:gsub("/%*.-%*/", "")
+
+ local name
+ for _, line in ipairs(sourcecode:split("\n", {plain = true})) do
+ name = line:match("export%s+module%s+(.+)%s*;") or line:match("export%s+__preprocessed_module%s+(.+)%s*;")
+ if name then
+ break
+ end
+ end
+ return filename, name, metadata
+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_dependencies_data(target, moduleinfos)
+ local modules
+ 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"])
+ local bmifile = provide["compiled-module-path"]
+ -- try to find the compiled module path in outputs filed (MSVC doesn't generate compiled-module-path)
+ if not bmifile then
+ for _, output in ipairs(rule.outputs) do
+ if output:endswith(compiler_support.get_bmi_extension(target)) then
+ bmifile = output
+ break
+ end
+ end
+
+ -- we didn't found the compiled module path, so we assume it
+ if not bmifile then
+ local name = provide["logical-name"] .. compiler_support.get_bmi_extension(target)
+ -- partition ":" character is invalid path character on windows
+ -- @see https://github.com/xmake-io/xmake/issues/2954
+ name = name:replace(":", "-")
+ bmifile = path.join(compiler_support.get_outputdir(target, name), name)
+ end
+ end
+ m.provides[provide["logical-name"]] = {
+ bmi = bmifile,
+ sourcefile = moduleinfo.sourcefile,
+ interface = provide["is-interface"]
+ }
+ 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
+
+-- generate edges for DAG
+function _get_edges(nodes, modules)
+ local edges = {}
+ for _, node in ipairs(nodes) do
+ local module = modules[node]
+ if module.requires then
+ for required_name, _ in table.orderpairs(module.requires) do
+ for _, required_node in ipairs(nodes) do
+ local name, _, _ = compiler_support.get_provided_module(modules[required_node])
+ if name and name == required_name then
+ table.insert(edges, {required_node, node})
+ break
+ end
+ end
+ end
+ end
+ end
+ return edges
+end
+
+function _get_package_modules(target, package, opt)
+ local package_modules
+
+ local modulesdir = path.join(package:installdir(), "modules")
+ local metafiles = os.files(path.join(modulesdir, "*", "*.meta-info"))
+ for _, metafile in ipairs(metafiles) do
+ package_modules = package_modules or {}
+ local modulefile, name, metadata = _parse_meta_info(target, metafile)
+ package_modules[name] = {file = path.join(modulesdir, modulefile), metadata = metadata}
+ end
+
+ return package_modules
+end
+
+-- generate dependency files
+function _generate_dependencies(target, sourcebatch, opt)
+ local changed = false
+ if opt.batchjobs then
+ local jobs = option.get("jobs") or os.default_njob()
+ runjobs(target:name() .. "_module_dependency_scanner", function(index)
+ local sourcefile = sourcebatch.sourcefiles[index]
+ changed = _dependency_scanner(target).generate_dependency_for(target, sourcefile, opt) or changed
+ end, {comax = jobs, total = #sourcebatch.sourcefiles})
+ else
+ for _, sourcefile in ipairs(sourcebatch.sourcefiles) do
+ changed = _dependency_scanner(target).generate_dependency_for(target, sourcefile, opt) or changed
+ end
+ end
+ return changed
+end
+-- get module dependencies
+function get_module_dependencies(target, sourcebatch, opt)
+ local cachekey = target:name() .. "/" .. sourcebatch.rulename
+ local modules = compiler_support.memcache():get2("modules", cachekey)
+ if modules == nil or opt.regenerate then
+ modules = compiler_support.localcache():get2("modules", cachekey)
+ opt.progress = opt.progress or 0
+ local changed = _generate_dependencies(target, sourcebatch, opt)
+ if changed or modules == nil then
+ local moduleinfos = compiler_support.load_moduleinfos(target, sourcebatch)
+ modules = _parse_dependencies_data(target, moduleinfos)
+ compiler_support.localcache():set2("modules", cachekey, modules)
+ compiler_support.localcache():save()
+ end
+ compiler_support.memcache():set2("modules", cachekey, modules)
+ end
+ return modules
+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, unique = r.unique})
+ 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, unique = r.unique})
+ end
+ end
+ end
+ end
+ end
+ end
+ return headerunits, stl_headerunits
+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, preprocess_file)
+ local output = {version = 1, revision = 0, rules = {}}
+ local rule = {outputs = {jsonfile}}
+ rule["primary-output"] = target:objectfile(sourcefile)
+
+ local module_name_export
+ local module_name_private
+ local module_deps = {}
+ local module_deps_set = hashset.new()
+ local sourcecode = preprocess_file(sourcefile) or io.readfile(sourcefile)
+ local internal = false
+ sourcecode = sourcecode:gsub("//.-\n", "\n")
+ sourcecode = sourcecode:gsub("/%*.-%*/", "")
+ for _, line in ipairs(sourcecode:split("\n", {plain = true})) do
+ if line:match("#") then
+ goto continue
+ end
+ if not module_name_export then
+ module_name_export = line:match("export%s+module%s+(.+)%s*;") or line:match("export%s+__preprocessed_module%s+(.+)%s*;")
+ end
+ if not module_name_private then
+ module_name_private = line:match("module%s+(.+)%s*;") or line:match("__preprocessed_module%s+(.+)%s*;")
+ if module_name_private then
+ internal = module_name_private:find(":")
+ end
+ end
+ local module_depname = line:match("import%s+(.+)%s*;")
+ -- we need to 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 compiler_support.has_module_extension(sourcefile) then
+ module_depname = module_name_private
+ end
+ if module_depname and not module_deps_set:has(module_depname) then
+ local module_dep = {}
+ -- partition? import :xxx;
+ if module_depname:startswith(":") then
+ local module_name = (module_name_export or module_name_private or "")
+ module_name = module_name:split(":")[1]
+ module_dep["unique-on-source-path"] = true
+ 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"] = compiler_support.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"] = compiler_support.find_angle_header_file(target, module_depname)
+ end
+ module_dep["logical-name"] = module_depname
+ table.insert(module_deps, module_dep)
+ module_deps_set:insert(module_depname)
+ end
+ ::continue::
+ end
+
+ if module_name_export or internal then
+ local outputdir = compiler_support.get_outputdir(target, sourcefile)
+
+ local provide = {}
+ provide["logical-name"] = module_name_export or module_name_private
+ provide["source-path"] = sourcefile
+ provide["is-interface"] = not internal
+ provide["compiled-module-path"] = path.join(outputdir, (module_name_export or module_name_private) .. compiler_support.get_bmi_extension(target))
+
+ 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
+
+-- extract packages modules dependencies
+function get_all_packages_modules(target, opt)
+
+ -- parse all meta-info and append their informations to the package store
+ local packages = target:pkgs() or {}
+ for _, deps in ipairs(target:orderdeps()) do
+ table.join2(packages, deps:pkgs())
+ end
+
+ local packages_modules
+ for _, package in table.orderpairs(packages) do
+ local package_modules = _get_package_modules(target, package, opt)
+ if package_modules then
+ packages_modules = packages_modules or {}
+ table.join2(packages_modules, package_modules)
+ end
+ end
+ return packages_modules
+end
+
+-- topological sort
+function sort_modules_by_dependencies(target, objectfiles, modules)
+ local result = {}
+ local edges = _get_edges(objectfiles, modules)
+ local dag = graph.new(true)
+ for _, e in ipairs(edges) do
+ dag:add_edge(e[1], e[2])
+ end
+ local cycle = dag:find_cycle()
+ if cycle then
+ local names = {}
+ for _, objectfile in ipairs(cycle) do
+ local name, _, cppfile = compiler_support.get_provided_module(modules[objectfile])
+ table.insert(names, name or cppfile)
+ end
+ local name, _, cppfile = compiler_support.get_provided_module(modules[cycle[1]])
+ table.insert(names, name or cppfile)
+ raise("circular modules dependency detected!\n%s", table.concat(names, "\n -> import "))
+ end
+ local objectfiles_sorted = dag:topological_sort()
+ for _, objectfile in ipairs(objectfiles_sorted) do
+ table.insert(result, objectfile)
+ end
+
+ local objectfiles_sorted_set = hashset.from(objectfiles_sorted)
+ for _, objectfile in ipairs(objectfiles) do
+ if not objectfiles_sorted_set:has(objectfile) then
+ -- cull unreferenced non-public named module but add non-module files and implementation modules
+ local _, provide, cppfile = compiler_support.get_provided_module(modules[objectfile])
+ local fileconfig = target:fileconfig(cppfile)
+ local public = fileconfig and fileconfig.public
+ if not provide or public then
+ table.insert(result, objectfile)
+ end
+ end
+ end
+ return result
+end
+
+-- get source modulefile for external target deps
+function get_targetdeps_modules(target)
+ local sourcefiles
+ for _, dep in ipairs(target:orderdeps()) do
+ local sourcebatch = dep:sourcebatches()["c++.build.modules.builder"]
+ if sourcebatch and sourcebatch.sourcefiles then
+ for _, sourcefile in ipairs(sourcebatch.sourcefiles) do
+ local fileconfig = dep:fileconfig(sourcefile)
+ local public = (fileconfig and fileconfig.public and not fileconfig.external) or false
+ if public then
+ sourcefiles = sourcefiles or {}
+ table.insert(sourcefiles, sourcefile)
+ target:fileconfig_add(sourcefile, {external = true})
+ end
+ end
+ end
+ end
+ return sourcefiles
+end
+
diff --git a/xmake/rules/c++/modules/modules_support/gcc.lua b/xmake/rules/c++/modules/modules_support/gcc.lua
deleted file mode 100644
index 6cc5c29d6..000000000
--- a/xmake/rules/c++/modules/modules_support/gcc.lua
+++ /dev/null
@@ -1,622 +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.base.option")
-import("core.base.json")
-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(target)
- local mapper_file = path.join(config.buildir(), target:name(), "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, modulepath, bmi)
- for line in io.lines(file) do
- if line:startswith(modulepath .. " ") then
- return false
- end
- end
- local f = io.open(file, "a")
- f:print("%s %s", modulepath, bmi)
- f:close()
- return true
-end
-
--- get a module from mapper
-function _get_module_from_mapper(file, module)
- for line in io.lines(file) do
- if line:startswith(module .. " ") then
- return line:split(" ", {plain = true})
- end
- end
-end
-
--- load module support for the current target
-function load(target)
- local modulesflag = get_modulesflag(target)
- local modulemapperflag = get_modulemapperflag(target)
- if os.isfile(_get_module_mapper(target)) then
- os.rm(_get_module_mapper(target))
- end
- target:add("cxxflags", {modulesflag, modulemapperflag .. path.translate(_get_module_mapper(target))}, {force = true, expand = false})
- -- fix cxxabi issue
- -- @see https://github.com/xmake-io/xmake/issues/2716#issuecomment-1225057760
- -- https://github.com/xmake-io/xmake/issues/3855
- if target:policy("build.c++.gcc.modules.cxx11abi") then
- target:add("cxxflags", "-D_GLIBCXX_USE_CXX11_ABI=1")
- else
- target:add("cxxflags", "-D_GLIBCXX_USE_CXX11_ABI=0")
- end
-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
-
--- do compile for batchcmds
--- @note we need to use batchcmds:compilev to translate paths in compflags for generator, e.g. -Ixx
-function _batchcmds_compile(batchcmds, target, flags, sourcefile)
- local compinst = target:compiler("cxx")
- local compflags = compinst:compflags({sourcefile = sourcefile, target = target})
- batchcmds:compilev(table.join(compflags or {}, flags), {compiler = compinst, sourcekind = "cxx"})
-end
-
--- build module file
-function _build_modulefile(target, sourcefile, opt)
- local objectfile = opt.objectfile
- local dependfile = opt.dependfile
- local compinst = compiler.load("cxx", {target = target})
- local compflags = table.join("-x", "c++", compinst:compflags({sourcefile = sourcefile, target = target}))
- local dependinfo = target:is_rebuilt() and {} or (depend.load(dependfile, {target = target}) or {})
-
- -- need build this object?
- local dryrun = option.get("dry-run")
- local depvalues = {compinst:program(), compflags}
- local lastmtime = os.isfile(objectfile) and os.mtime(dependfile) or 0
- if not dryrun and not depend.is_changed(dependinfo, {lastmtime = lastmtime, values = depvalues}) then
- return
- end
-
- -- trace
- progress.show(opt.progress, "${color.build.object}compiling.module.$(mode) %s", opt.name)
- vprint(compinst:compcmd(sourcefile, objectfile, {compflags = compflags, rawargs = true}))
-
- if not dryrun then
-
- -- do compile
- dependinfo.files = {}
- assert(compinst:compile(sourcefile, objectfile, {dependinfo = dependinfo, compflags = compflags}))
-
- -- update files and values to the dependent file
- dependinfo.values = depvalues
- table.join2(dependinfo.files, sourcefile)
- depend.save(dependinfo, dependfile)
- end
-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("cxx")
- 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 compinst = target:compiler("cxx")
- local common_args = {"-E", "-x", "c++"}
- local depformatflag = get_depflag(target, "p1689r5") or get_depflag(target, "trtbd")
- 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.module.deps %s", sourcefile)
- end
-
- local outputdir = common.get_outputdir(target, sourcefile)
- local jsonfile = path.translate(path.join(outputdir, path.filename(sourcefile) .. ".json"))
- if depformatflag and depfileflag and depoutputflag and not target:policy("build.c++.gcc.fallbackscanner") 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, "-MT", jsonfile, "-MD", "-MF", dfile, depformatflag, depfileflag .. jsonfile, depoutputflag .. target:objectfile(sourcefile), "-o", ifile}
- local compflags = compinst:compflags({sourcefile = sourcefile, target = target})
- -- module mapper flag force gcc to check the imports but this is not wanted at this stage
- local modulemapperflag = get_modulemapperflag(target) .. path.translate(_get_module_mapper(target))
- table.remove(compflags, table.unpack(table.find(compflags, modulemapperflag)))
- os.vrunv(compinst:program(), table.join(compflags, common_args, args))
- os.rm(ifile)
- os.rm(dfile)
- else
- common.fallback_generate_dependencies(target, jsonfile, sourcefile, function(file)
- local compinst = target:compiler("cxx")
- local compflags = compinst:compflags({sourcefile = file, target = target})
- -- exclude -fmodule* flags because, when they are set gcc try to find bmi of imported modules but they don't exists a this point of compilation
- table.remove_if(compflags, function(_, flag) return flag:startswith("-fmodule") end)
- local ifile = path.translate(path.join(outputdir, path.filename(file) .. ".i"))
- os.vrunv(compinst:program(), table.join(common_args, compflags, {file, "-o", ifile}))
- local content = io.readfile(ifile)
- os.rm(ifile)
- return content
- end)
- end
- changed = true
-
- local dependinfo = io.readfile(jsonfile)
- return { moduleinfo = dependinfo }
- end, {dependfile = dependfile, files = {sourcefile}, changed = target:is_rebuilt()})
- 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(target)
- local stlcachedir = common.stlmodules_cachedir(target, {mkdir = true})
- local modulemapperflag = get_modulemapperflag(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}compiling.headerunit.$(mode) %s", headerunit.name)
- local args = {"-c", "-x", "c++-system-header", headerunit.name}
- local flags = table.join(compinst:compflags({target = target}), args)
- -- we need to support reading and writing mapperfile in parallel, otherwise it will be broken
- -- @see tests/c++/modules/stl_headerunit_cpp_only
- local mapper_file_tmp = os.tmpfile()
- os.cp(mapper_file, mapper_file_tmp)
- _add_module_to_mapper(mapper_file_tmp, headerunit.path, path.absolute(bmifile, projectdir))
- for idx, flag in ipairs(flags) do
- if flag:startswith(modulemapperflag) then
- flags[idx] = modulemapperflag .. mapper_file_tmp
- break
- end
- end
- os.vrunv(compinst:program(), flags)
- _add_module_to_mapper(mapper_file, headerunit.path, path.absolute(bmifile, projectdir))
- os.tryrm(mapper_file_tmp)
- end, {dependfile = target:dependfile(bmifile), files = {headerunit.path}, changed = target:is_rebuilt()})
- end, {rootjob = opt.rootjob})
- else
- _add_module_to_mapper(mapper_file, headerunit.path, path.absolute(bmifile, projectdir))
- end
- end
-end
-
--- generate target stl header units for batchcmds
-function generate_stl_headerunits_for_batchcmds(target, batchcmds, headerunits, opt)
- local mapper_file = _get_module_mapper(target)
- local stlcachedir = common.stlmodules_cachedir(target, {mkdir = true})
-
- -- 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 flags = {"-c", "-x", "c++-system-header", headerunit.name}
- batchcmds:show_progress(opt.progress, "${color.build.object}compiling.headerunit.$(mode) %s", headerunit.name)
- _batchcmds_compile(batchcmds, target, flags)
- 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(target)
-
- -- build headerunits
- local projectdir = os.projectdir()
- for _, headerunit in ipairs(headerunits) do
- local headerunit_path
- 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
- local objectfile = target:objectfile(headerunit_path)
- local outputdir = common.get_outputdir(target, headerunit.path)
- 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()
- progress.show((index * 100) / total, "${color.build.object}compiling.headerunit.$(mode) %s", headerunit.name)
- local objectdir = path.directory(objectfile)
- if not os.isdir(objectdir) then
- os.mkdir(objectdir)
- end
-
- -- generate headerunit
- local args = { "-c" }
- if headerunit.type == ":quote" then
- local includedir
- local p = headerunit.path
- if p:endswith(headerunit.name) then
- includedir = p:sub(1, #p - #headerunit.name - 1)
- else
- includedir = path.directory(p)
- end
- if path.is_absolute(includedir) then
- includedir = path.relative(includedir, projectdir)
- end
- table.join2(args, { "-I", includedir, "-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}, changed = target:is_rebuilt()})
- 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 mapper_file = _get_module_mapper(target)
-
- -- build headerunits
- local projectdir = os.projectdir()
- local depmtime = 0
- for _, headerunit in ipairs(headerunits) do
- local flags = {"-c"}
- local headerunit_path
- if headerunit.type == ":quote" then
- local includedir
- local p = headerunit.path
- if p:endswith(headerunit.name) then
- includedir = p:sub(1, #p - #headerunit.name - 1)
- else
- includedir = path.directory(p)
- end
- if path.is_absolute(includedir) then
- includedir = path.relative(includedir, projectdir)
- end
- table.join2(flags, {"-I", path(includedir), "-x", "c++-user-header", headerunit.name})
- headerunit_path = path.join(".", path.relative(headerunit.path, projectdir))
- elseif headerunit.type == ":angle" then
- table.join2(flags, {"-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
- local objectfile = target:objectfile(headerunit_path)
- local outputdir = common.get_outputdir(target, headerunit.path)
- 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))
-
- batchcmds:show_progress(opt.progress, "${color.build.object}compiling.headerunit.$(mode) %s", headerunit.name)
- _batchcmds_compile(batchcmds, target, flags)
- 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 mapper_file = _get_module_mapper(target)
-
- -- build modules
- local projectdir = os.projectdir()
- local modulesjobs = {}
- for _, objectfile in ipairs(objectfiles) do
- local module = modules[objectfile]
- if module then
- local cppfile = module.cppfile
- local name, provide
- if module.provides then
- -- assume there that provides is only one, until we encounter the case
- local length = 0
- for k, v in pairs(module.provides) do
- length = length + 1
- name = k
- provide = v
- cppfile = provide.sourcefile
- if length > 1 then
- raise("multiple provides are not supported now!")
- end
- break
- end
- end
- local moduleinfo = table.copy(provide) or {}
- local dependfile = (provide and provide.bmi) and target:dependfile(provide.bmi) or target:dependfile(objectfile)
-
- if provide then
- local fileconfig = target:fileconfig(cppfile)
- if fileconfig and fileconfig.install then
- batchjobs:addjob(name .. "_metafile", function(index, total)
- local metafilepath = common.get_metafile(target, cppfile)
- depend.on_changed(function()
- progress.show((index * 100) / total, "${color.build.object}generating.module.metadata %s", name)
- local metadata = common.generate_meta_module_info(target, name, cppfile, module.requires)
- json.savefile(metafilepath, metadata)
- end, {dependfile = target:dependfile(metafilepath), files = {cppfile}, changed = target:is_rebuilt()})
- end, {rootjob = opt.rootjob})
- end
- end
-
- table.join2(moduleinfo, {
- name = name or cppfile,
- deps = table.keys(module.requires or {}),
- sourcefile = cppfile,
- job = batchjobs:newjob(name or cppfile, function(index, total)
- -- append dependencies module now to ensures deps modulemap is filled
- for required, _ in pairs(module.requires) do
- local m
- for _, dep in ipairs(target:orderdeps()) do
- m = _get_module_from_mapper(_get_module_mapper(dep), required)
- if m then
- break
- end
- end
- if m then
- _add_module_to_mapper(mapper_file, m[1], m[2])
- break
- end
- end
-
- if provide or common.has_module_extension(cppfile) then
- if not common.memcache():get2(name or cppfile, "compiling") then
- if name and module.external then
- common.memcache():set2(name or cppfile, "compiling", true)
- end
- _build_modulefile(target, cppfile, {
- objectfile = objectfile,
- dependfile = dependfile,
- name = name or cppfile,
- progress = (index * 100) / total})
- end
- target:add("objectfiles", objectfile)
- end
- end)})
- if provide then
- _add_module_to_mapper(mapper_file, name, path.absolute(provide.bmi, projectdir))
- end
- modulesjobs[name or cppfile] = moduleinfo
- end
- end
-
- -- build batchjobs for modules
- common.build_batchjobs_for_modules(modulesjobs, batchjobs, opt.rootjob)
-end
-
--- build module files for batchcmds
-function build_modules_for_batchcmds(target, batchcmds, objectfiles, modules, opt)
- local modulemapperflag = get_modulemapperflag(target)
- local mapper_file = _get_module_mapper(target)
-
- -- build modules
- local projectdir = os.projectdir()
- local depmtime = 0
- for _, objectfile in ipairs(objectfiles) do
- local module = modules[objectfile]
- if module then
- local cppfile = module.cppfile
- local name, provide
- if module.provides then
- local length = 0
- for k, v in pairs(module.provides) do
- length = length + 1
- name = k
- provide = v
- cppfile = provide.sourcefile
- if length > 1 then
- raise("multiple provides are not supported now!")
- end
- break
- end
- end
- -- append dependencies module now to ensures deps modulemap is filled
- for required, _ in pairs(module.requires) do
- local m
- for _, dep in ipairs(target:orderdeps()) do
- m = _get_module_from_mapper(_get_module_mapper(dep), required)
- if m then
- break
- end
- end
- if m then
- _add_module_to_mapper(mapper_file, m[1], m[2])
- break
- end
- end
- if provide or common.has_module_extension(cppfile) then
- local flags = {"-x", "c++", "-c", path(cppfile), "-o", path(objectfile)}
- batchcmds:show_progress(opt.progress, "${color.build.object}compiling.module.$(mode) %s", name or cppfile)
- batchcmds:mkdir(path.directory(objectfile))
- _batchcmds_compile(batchcmds, target, flags, cppfile)
- batchcmds:add_depfiles(cppfile)
- target:add("objectfiles", objectfile)
- _add_module_to_mapper(mapper_file, name, path.absolute(provide.bmi, projectdir))
- end
- depmtime = math.max(depmtime, os.mtime(provide and provide.bmi or objectfile))
- end
- end
- batchcmds:set_depmtime(depmtime)
-end
-
--- not supported atm
-function get_stdmodules(target)
- local modules = {}
- return modules
-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_depflag(target, format)
- local depflag = _g.depflag
- if depflag == nil then
- local compinst = target:compiler("cxx")
- if compinst:has_flags("-fdep-format=" .. format, "cxxflags", {flagskey = "gcc_dep_format"}) then
- depflag = "-fdep-format=" .. format
- end
- _g.depflag = depflag or false
- end
- return depflag 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",
- on_check = function (ok, errors)
- if errors:find("cc1plus: error: to generate dependencies") then
- ok = true
- end
- return ok, errors
- end}) 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",
- on_check = function (ok, errors)
- if errors:find("cc1plus: error: to generate dependencies") then
- ok = true
- end
- return ok, errors
- end}) then
- depoutputflag = "-fdep-output="
- end
- _g.depoutputflag = depoutputflag or false
- end
- return depoutputflag or nil
-end
-
-function get_cppversionflag(target)
- local cppversionflag = _g.cppversionflag
- if cppversionflag == nil then
- local compinst = target:compiler("cxx")
- local flags = compinst:compflags({target = target})
- cppversionflag = table.find_if(flags, function(v) string.startswith(v, "-std=c++") end) or "-std=c++20"
- _g.cppversionflag = cppversionflag
- end
- return cppversionflag or nil
-end
diff --git a/xmake/rules/c++/modules/modules_support/gcc/builder.lua b/xmake/rules/c++/modules/modules_support/gcc/builder.lua
new file mode 100644
index 000000000..9b8ed1212
--- /dev/null
+++ b/xmake/rules/c++/modules/modules_support/gcc/builder.lua
@@ -0,0 +1,389 @@
+--!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 gcc/builder.lua
+--
+
+-- imports
+import("core.base.json")
+import("core.base.option")
+import("core.base.semver")
+import("utils.progress")
+import("private.action.build.object", {alias = "objectbuilder"})
+import("core.tool.compiler")
+import("core.project.config")
+import("core.project.depend")
+import("compiler_support")
+import(".builder", {inherit = true})
+
+-- get flags for building a headerunit
+function _make_headerunitflags(target, headerunit, headerunit_mapper)
+ local module_headerflag = compiler_support.get_moduleheaderflag(target)
+ local module_onlyflag = compiler_support.get_moduleonlyflag(target)
+ local module_mapperflag = compiler_support.get_modulemapperflag(target)
+ assert(module_headerflag and module_onlyflag, "compiler(gcc): does not support c++ header units!")
+
+ local local_directory = (headerunit.type == ":quote") and {"-I" .. path.directory(path.normalize(headerunit.path))} or {}
+ local headertype = (headerunit.type == ":angle") and "system" or "user"
+ local flags = table.join(local_directory, {module_mapperflag .. headerunit_mapper,
+ module_headerflag .. headertype,
+ module_onlyflag,
+ "-xc++-header"})
+ return flags
+end
+
+-- do compile
+function _compile(target, flags, sourcefile, outputfile)
+
+ local dryrun = option.get("dry-run")
+ local compinst = target:compiler("cxx")
+ local compflags = compinst:compflags({sourcefile = sourcefile, target = target})
+ local flags = table.join(compflags or {}, flags)
+
+ -- trace
+ if option.get("verbose") then
+ print(compinst:compcmd(sourcefile, outputfile, {target = target, compflags = flags, rawargs = true}))
+ end
+
+ -- do compile
+ if not dryrun then
+ assert(compinst:compile(sourcefile, outputfile, {target = target, compflags = flags}))
+ end
+end
+
+-- do compile for batchcmds
+-- @note we need to use batchcmds:compilev to translate paths in compflags for generator, e.g. -Ixx
+function _batchcmds_compile(batchcmds, target, flags, sourcefile, outputfile)
+ local compinst = target:compiler("cxx")
+ local compflags = compinst:compflags({sourcefile = sourcefile, target = target})
+ local flags = table.join("-c", compflags or {}, flags, {"-o", outputfile, sourcefile})
+ batchcmds:compilev(flags, {compiler = compinst, sourcekind = "cxx"})
+end
+
+function _module_map_cachekey(target)
+ local mode = config.mode()
+ return target:name() .. "module_mapper" .. (mode or "")
+end
+
+-- generate a module mapper file for build a headerunit
+function _generate_headerunit_modulemapper_file(module)
+ local mapper_path = os.tmpfile()
+ local mapper_file = io.open(mapper_path, "wb")
+ mapper_file:write("root " .. os.projectdir():replace("\\", "/"))
+ mapper_file:write("\n")
+ mapper_file:write(mapper_file, module.name:replace("\\", "/") .. " " .. module.bmifile:replace("\\", "/"))
+ mapper_file:write("\n")
+ mapper_file:close()
+ return mapper_path
+end
+
+function _get_maplines(target, module)
+ local maplines = {}
+ local m_name, m, _ = compiler_support.get_provided_module(module)
+ if m then
+ table.insert(maplines, m_name .. " " .. compiler_support.get_bmi_path(m.bmi))
+ end
+ for required, _ in table.orderpairs(module.requires) do
+ local dep_module = get_from_target_mapper(target, required)
+ assert(dep_module, "module dependency %s required for %s not found", required, m_name or module.cppfile)
+
+ local bmifile = dep_module.bmi
+ local mapline
+ -- aliased headerunit
+ if dep_module.aliasof then
+ local aliased = get_from_target_mapper(target, dep_module.aliasof)
+ bmifile = aliased.bmi
+ mapline = dep_module.headerunit.path:replace("\\", "/") .. " " .. bmifile:replace("\\", "/")
+ -- headerunit
+ elseif dep_module.headerunit then
+ mapline = dep_module.headerunit.path:replace("\\", "/") .. " " .. bmifile:replace("\\", "/")
+ -- named module
+ else
+ mapline = required .. " " .. bmifile:replace("\\", "/")
+ end
+ table.insert(maplines, mapline)
+
+ -- append deps
+ if dep_module.opt and dep_module.opt.deps then
+ local deps = _get_maplines(target, {name = dep_module.name, bmi = bmifile, requires = dep_module.opt.deps})
+ table.join2(maplines, deps)
+ end
+ end
+
+ -- remove duplicates
+ return table.unique(maplines)
+end
+
+-- generate a module mapper file for build a module
+-- 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 _generate_modulemapper_file(target, module, cppfile)
+ local maplines = _get_maplines(target, module)
+ local mapper_path = path.join(os.tmpdir(), target:name():replace(" ", "_"), name or cppfile:replace(" ", "_"))
+ local mapper_file = io.open(mapper_path, "wb")
+ mapper_file:write("root " .. os.projectdir():replace("\\", "/"))
+ mapper_file:write("\n")
+ for _, mapline in ipairs(maplines) do
+ mapper_file:write(mapline)
+ mapper_file:write("\n")
+ end
+ mapper_file:close()
+ return mapper_path
+end
+
+-- populate module map
+function populate_module_map(target, modules)
+ for _, module in pairs(modules) do
+ local name, provide = compiler_support.get_provided_module(module)
+ if provide then
+ local bmifile = compiler_support.get_bmi_path(provide.bmi)
+ add_module_to_target_mapper(target, name, provide.sourcefile, bmifile, {deps = module.requires})
+ end
+ end
+end
+
+-- get defines for a module
+function get_module_required_defines(target, sourcefile)
+ local compinst = compiler.load("cxx", {target = target})
+ local compflags = compinst:compflags({sourcefile = sourcefile, target = target})
+ local defines
+ for _, flag in ipairs(compflags) do
+ if flag:startswith("-D") then
+ defines = defines or {}
+ table.insert(defines, flag:sub(3))
+ end
+ end
+ return defines
+end
+
+-- build module file for batchjobs
+function make_module_buildjobs(target, batchjobs, job_name, deps, opt)
+
+ local name, provide, _ = compiler_support.get_provided_module(opt.module)
+ local bmifile = provide and compiler_support.get_bmi_path(provide.bmi)
+ local module_mapperflag = compiler_support.get_modulemapperflag(target)
+
+ return {
+ name = job_name,
+ deps = table.join(target:name() .. "_populate_module_map", deps),
+ sourcefile = opt.cppfile,
+ job = batchjobs:newjob(name or opt.cppfile, function(index, total)
+ local mapped_bmi
+ if provide and compiler_support.memcache():get2(target:name() .. name, "reuse") then
+ if not target:is_binary() then
+ return
+ else
+ mapped_bmi = get_from_target_mapper(target, name).bmi
+ end
+ end
+
+ -- generate and append module mapper file
+ local module_mapper
+ if provide or opt.module.requires then
+ module_mapper = _generate_modulemapper_file(target, opt.module, opt.cppfile)
+ target:fileconfig_add(opt.cppfile, {force = {cxxflags = {module_mapperflag .. module_mapper}}})
+ end
+
+ local dependfile = target:dependfile(bmifile or opt.objectfile)
+ local build, dependinfo = should_build(target, opt.cppfile, bmifile, {name = name, objectfile = opt.objectfile, requires = opt.module.requires})
+
+ -- needed to detect rebuild of dependencies
+ if provide and build then
+ mark_build(target, name)
+ end
+
+ if build then
+ -- compile if it's a named module
+ if provide or compiler_support.has_module_extension(opt.cppfile) then
+ local fileconfig = target:fileconfig(opt.cppfile)
+ local public = fileconfig and fileconfig.public
+ local external = fileconfig and fileconfig.external
+ local bmifile = mapped_bmi or bmifile
+ local flags = {"-x", "c++"}
+ local sourcefile
+ if target:is_binary() then
+ if mapped_bmi then
+ progress.show((index * 100) / total, "${color.build.target}<%s> ${clear}${color.build.object}compiling.objectfile.$(mode) %s", target:name(), name or opt.cppfile)
+ sourcefile = bmifile
+ else
+ progress.show((index * 100) / total, "${color.build.target}<%s> ${clear}${color.build.object}compiling.module.$(mode) %s", target:name(), name or opt.cppfile)
+ sourcefile = opt.cppfile
+ end
+ else
+ if not public and not external then
+ progress.show((index * 100) / total, "${color.build.target}<%s> ${clear}${color.build.object}compiling.module.$(mode) %s", target:name(), name or opt.cppfile)
+ sourcefile = opt.cppfile
+ else
+ progress.show((index * 100) / total, "${color.build.target}<%s> ${clear}${color.build.object}compiling.bmi.$(mode) %s", target:name(), name or opt.cppfile)
+ local module_onlyflag = compiler_support.get_moduleonlyflag(target)
+ table.insert(flags, module_onlyflag)
+ sourcefile = opt.cppfile
+ end
+ end
+ if option.get("diagnosis") then
+ print("mapper file --------\n%s--------", io.readfile(module_mapper))
+ end
+ _compile(target, flags, sourcefile, opt.objectfile)
+ os.tryrm(module_mapper)
+ else
+ os.tryrm(opt.objectfile) -- force rebuild for .cpp files
+ end
+ depend.save(dependinfo, dependfile)
+ end
+ end)}
+end
+
+-- build module file for batchcmds
+function make_module_buildcmds(target, batchcmds, opt)
+
+ local name, provide, _ = compiler_support.get_provided_module(opt.module)
+ local bmifile = provide and compiler_support.get_bmi_path(provide.bmi)
+ local module_mapperflag = compiler_support.get_modulemapperflag(target)
+
+ local mapped_bmi
+ if provide and compiler_support.memcache():get2(target:name() .. name, "reuse") then
+ if not target:is_binary() then
+ return
+ else
+ mapped_bmi = get_from_target_mapper(target, name).bmi
+ end
+ end
+
+ -- generate and append module mapper file
+ local module_mapper
+ if provide or opt.module.requires then
+ module_mapper = _generate_modulemapper_file(target, opt.module, opt.cppfile)
+ target:fileconfig_add(opt.cppfile, {force = {cxxflags = {module_mapperflag .. module_mapper}}})
+ end
+
+ local build = should_build(target, opt.cppfile, bmifile, {name = name, objectfile = opt.objectfile, requires = opt.module.requires})
+
+ -- needed to detect rebuild of dependencies
+ if provide and build then
+ mark_build(target, name)
+ end
+
+ if build then
+ -- compile if it's a named module
+ if provide or compiler_support.has_module_extension(opt.cppfile) then
+ batchcmds:mkdir(path.directory(opt.objectfile))
+ local fileconfig = target:fileconfig(opt.cppfile)
+ local public = fileconfig and fileconfig.public
+ local external = fileconfig and fileconfig.external
+ local bmifile = mapped_bmi or bmifile
+ local flags = {"-x", "c++"}
+ local sourcefile
+ if target:is_binary() then
+ if mapped_bmi then
+ batchcmds:show_progress(opt.progress, "${color.build.target}<%s> ${clear}${color.build.object}compiling.objectfile.$(mode) %s", target:name(), name or opt.cppfile)
+ sourcefile = bmifile
+ else
+ batchcmds:show_progress(opt.progress, "${color.build.target}<%s> ${clear}${color.build.object}compiling.module.$(mode) %s", target:name(), name or opt.cppfile)
+ sourcefile = opt.cppfile
+ end
+ else
+ if not public and not external then
+ batchcmds:show_progress(opt.progress, "${color.build.target}<%s> ${clear}${color.build.object}compiling.module.$(mode) %s", target:name(), name or opt.cppfile)
+ sourcefile = opt.cppfile
+ else
+ batchcmds:show_progress(opt.progress, "${color.build.target}<%s> ${clear}${color.build.object}compiling.bmi.$(mode) %s", target:name(), name or opt.cppfile)
+ local module_onlyflag = compiler_support.get_moduleonlyflag(target)
+ table.insert(flags, module_onlyflag)
+ sourcefile = opt.cppfile
+ end
+ end
+ if option.get("diagnosis") then
+ batchcmds:print("mapper file: %s", io.readfile(module_mapper))
+ end
+ _batchcmds_compile(batchcmds, target, flags, sourcefile, opt.objectfile)
+ batchcmds:rm(module_mapper)
+ else
+ batchcmds:rm(opt.objectfile) -- force rebuild for .cpp files
+ end
+ end
+ batchcmds:add_depfiles(opt.cppfile)
+ return os.mtime(opt.objectfile)
+end
+
+-- build headerunit file for batchjobs
+function make_headerunit_buildjobs(target, job_name, batchjobs, headerunit, bmifile, outputdir, opt)
+
+ local _headerunit = headerunit
+ _headerunit.path = headerunit.type == ":quote" and "./" .. path.relative(headerunit.path) or headerunit.path
+ local already_exists = add_headerunit_to_target_mapper(target, _headerunit, bmifile)
+ if not already_exists then
+ return {
+ name = job_name,
+ sourcefile = headerunit.path,
+ job = batchjobs:newjob(job_name, function(index, total)
+ if not os.isdir(outputdir) then
+ os.mkdir(outputdir)
+ end
+
+ local compinst = compiler.load("cxx", {target = target})
+ local compflags = compinst:compflags({sourcefile = headerunit.path, target = target})
+
+ local dependfile = target:dependfile(bmifile)
+ local dependinfo = depend.load(dependfile) or {}
+ dependinfo.files = {}
+ local depvalues = {compinst:program(), compflags}
+
+ if opt.build then
+ local headerunit_mapper = _generate_headerunit_modulemapper_file({name = path.normalize(headerunit.path), bmifile = bmifile})
+ progress.show((index * 100) / total, "${color.build.target}<%s> ${clear}${color.build.object}compiling.headerunit.$(mode) %s", target:name(), headerunit.name)
+ if option.get("diagnosis") then
+ print("mapper file:\n%s", io.readfile(headerunit_mapper))
+ end
+ _compile(target,
+ _make_headerunitflags(target, headerunit, headerunit_mapper, opt),
+ path.translate(path.filename(headerunit.name)), bmifile)
+ os.tryrm(headerunit_mapper)
+ end
+
+ table.insert(dependinfo.files, headerunit.path)
+ dependinfo.values = depvalues
+ depend.save(dependinfo, dependfile)
+ end)}
+ end
+end
+
+-- build headerunit file for batchcmds
+function make_headerunit_buildcmds(target, batchcmds, headerunit, bmifile, outputdir, opt)
+
+ local headerunit_mapper = _generate_headerunit_modulemapper_file({name = path.normalize(headerunit.path), bmifile = bmifile})
+ batchcmds:mkdir(outputdir)
+
+ local _headerunit = headerunit
+ _headerunit.path = headerunit.type == ":quote" and "./" .. path.relative(headerunit.path) or headerunit.path
+ add_headerunit_to_target_mapper(target, _headerunit, bmifile)
+
+ if opt.build then
+ local name = headerunit.unique and headerunit.name or headerunit.path
+ batchcmds:show_progress(opt.progress, "${color.build.target}<%s> ${clear}${color.build.object}compiling.headerunit.$(mode) %s", target:name(), name)
+ if option.get("diagnosis") then
+ batchcmds:print("mapper file:\n%s", io.readfile(headerunit_mapper))
+ end
+ _batchcmds_compile(batchcmds, target, _make_headerunitflags(target, headerunit, headerunit_mapper), bmifile)
+ end
+
+ batchcmds:rm(headerunit_mapper)
+ batchcmds:add_depfiles(headerunit.path)
+ return os.mtime(bmifile)
+end
+
diff --git a/xmake/rules/c++/modules/modules_support/gcc/compiler_support.lua b/xmake/rules/c++/modules/modules_support/gcc/compiler_support.lua
new file mode 100644
index 000000000..2253c9866
--- /dev/null
+++ b/xmake/rules/c++/modules/modules_support/gcc/compiler_support.lua
@@ -0,0 +1,256 @@
+--!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 gcc/compiler_support.lua
+--
+
+-- imports
+import("core.base.semver")
+import("core.project.config")
+import("lib.detect.find_tool")
+import(".compiler_support", {inherit = true})
+
+-- 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
+
+-- load module support for the current target
+function load(target)
+ local modulesflag = get_modulesflag(target)
+ target:add("cxxflags", modulesflag, {force = true, expand = false})
+
+ -- fix cxxabi issue
+ -- @see https://github.com/xmake-io/xmake/issues/2716#issuecomment-1225057760
+ -- https://github.com/xmake-io/xmake/issues/3855
+ if target:policy("build.c++.gcc.modules.cxx11abi") then
+ target:add("cxxflags", "-D_GLIBCXX_USE_CXX11_ABI=1")
+ else
+ target:add("cxxflags", "-D_GLIBCXX_USE_CXX11_ABI=0")
+ end
+end
+
+-- strip flags that doesn't affect bmi generation
+function strip_flags(target, flags)
+ -- speculative list as there is no resource that list flags that prevent reusability, this list will likely be improve over time
+ local strippable_flags = {
+ "-I",
+ "-isystem",
+ "-g",
+ "-O",
+ "-W",
+ "-w",
+ "-cxx-isystem",
+ "-Q",
+ }
+ if not target:policy("build.c++.modules.tryreuse.discriminate_on_defines") then
+ table.join2(strippable_flags, {"-D", "-U"})
+ end
+ local output = {}
+ local last_flag_I = false
+ for _, flag in ipairs(flags) do
+ local strip = false
+ for _, _flag in ipairs(strippable_flags) do
+ if flag:startswith(_flag) or last_flag_I then
+ last_flag_I = _flag == "-I"
+ strip = true
+ break
+ end
+ end
+ if not strip then
+ table.insert(output, flag)
+ end
+ end
+ return output
+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("cxx")
+ assert(toolname == "gcc" or toolname == "gxx")
+ _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
+
+function get_target_module_mapperpath(target)
+ local path = path.join(modules_cachedir(target, {mkdir = true}), "..", "mapper.txt")
+ if not os.isfile(path) then
+ io.writefile(path, "")
+ end
+ return path
+end
+
+-- not supported atm
+function get_stdmodules(target)
+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_moduleheaderflag(target)
+ local moduleheaderflag = _g.moduleheaderflag
+ if moduleheaderflag == nil then
+ local compinst = target:compiler("cxx")
+ if compinst:has_flags("-fmodule-header", "cxxflags", {flagskey = "gcc_module_header"}) then
+ moduleheaderflag = "-fmodule-header="
+ end
+ _g.moduleheaderflag = moduleheaderflag or false
+ end
+ return moduleheaderflag or nil
+end
+
+function get_moduleonlyflag(target)
+ local moduleonlyflag = _g.moduleonlyflag
+ if moduleonlyflag == nil then
+ local compinst = target:compiler("cxx")
+ if compinst:has_flags("-fmodule-only", "cxxflags", {flagskey = "gcc_module_only"}) then
+ moduleonlyflag = "-fmodule-only"
+ end
+ _g.moduleonlyflag = moduleonlyflag or false
+ end
+ return moduleonlyflag 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_depsflag(target)
+ local depflag = _g.depflag
+ if depflag == nil then
+ local compinst = target:compiler("cxx")
+ if compinst:has_flags("-fdeps-format=p1689r5", "cxxflags", {flagskey = "gcc_deps_format",
+ on_check = function (ok, errors)
+ if errors:find("-M") then
+ ok = true
+ end
+ return ok, errors
+ end}) then
+ depflag = "-fdeps-format=p1689r5"
+ end
+ _g.depflag = depflag or false
+ end
+ return depflag or nil
+end
+
+function get_depsfileflag(target)
+ local depfileflag = _g.depfileflag
+ if depfileflag == nil then
+ local compinst = target:compiler("cxx")
+ if compinst:has_flags("-fdeps-file=" .. os.tmpfile(), "cxxflags", {flagskey = "gcc_deps_file",
+ on_check = function (ok, errors)
+ if errors:find("-M") then
+ ok = true
+ end
+ return ok, errors
+ end}) then
+ depfileflag = "-fdeps-file="
+ end
+ _g.depfileflag = depfileflag or false
+ end
+ return depfileflag or nil
+end
+
+function get_depstargetflag(target)
+ local depoutputflag = _g.depoutputflag
+ if depoutputflag == nil then
+ local compinst = target:compiler("cxx")
+ if compinst:has_flags("-fdeps-target=" .. os.tmpfile() .. ".o", "cxxflags", {flagskey = "gcc_deps_output",
+ on_check = function (ok, errors)
+ if errors:find("-M") then
+ ok = true
+ end
+ return ok, errors
+ end}) then
+ depoutputflag = "-fdeps-target="
+ end
+ _g.depoutputflag = depoutputflag or false
+ end
+ return depoutputflag or nil
+end
+
+function get_cppversionflag(target)
+ local cppversionflag = _g.cppversionflag
+ if cppversionflag == nil then
+ local compinst = target:compiler("cxx")
+ local flags = compinst:compflags({target = target})
+ cppversionflag = table.find_if(flags, function(v) string.startswith(v, "-std=c++") end) or "-std=c++20"
+ _g.cppversionflag = cppversionflag
+ end
+ return cppversionflag or nil
+end
+
diff --git a/xmake/rules/c++/modules/modules_support/gcc/dependency_scanner.lua b/xmake/rules/c++/modules/modules_support/gcc/dependency_scanner.lua
new file mode 100644
index 000000000..8cdb0a0a3
--- /dev/null
+++ b/xmake/rules/c++/modules/modules_support/gcc/dependency_scanner.lua
@@ -0,0 +1,75 @@
+--!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 gcc/dependency_scanner.lua
+--
+
+-- imports
+import("core.base.json")
+import("core.base.semver")
+import("core.project.depend")
+import("utils.progress")
+import("compiler_support")
+import("builder")
+import(".dependency_scanner", {inherit = true})
+
+-- generate dependency files
+function generate_dependency_for(target, sourcefile, opt)
+ local compinst = target:compiler("cxx")
+ local baselineflags = {"-E", "-x", "c++"}
+ local depsformatflag = compiler_support.get_depsflag(target, "p1689r5")
+ local depsfileflag = compiler_support.get_depsfileflag(target)
+ local depstargetflag = compiler_support.get_depstargetflag(target)
+ local dependfile = target:dependfile(sourcefile)
+ local changed = false
+
+ depend.on_changed(function()
+ if opt.progress then
+ progress.show(opt.progress, "${color.build.target}<%s> generating.module.deps %s", target:name(), sourcefile)
+ end
+
+ local outputdir = compiler_support.get_outputdir(target, sourcefile)
+ local jsonfile = path.translate(path.join(outputdir, path.filename(sourcefile) .. ".json"))
+ if depsformatflag and depsfileflag and depstargetflag and not target:policy("build.c++.gcc.fallbackscanner") then
+ local ifile = path.translate(path.join(outputdir, path.filename(sourcefile) .. ".i"))
+ local dfile = path.translate(path.join(outputdir, path.filename(sourcefile) .. ".d"))
+ local compflags = compinst:compflags({sourcefile = sourcefile, target = target})
+ local flags = table.join(compflags or {}, baselineflags, {sourcefile, "-MT", jsonfile, "-MD", "-MF", dfile, depsformatflag, depsfileflag .. jsonfile, depstargetflag .. target:objectfile(sourcefile), "-o", ifile})
+ os.vrunv(compinst:program(), flags)
+ os.rm(ifile)
+ os.rm(dfile)
+ else
+ fallback_generate_dependencies(target, jsonfile, sourcefile, function(file)
+ local compflags = compinst:compflags({sourcefile = file, target = target})
+ -- exclude -fmodule* flags because, when they are set gcc try to find bmi of imported modules but they don't exists a this point of compilation
+ table.remove_if(compflags, function(_, flag) return flag:startswith("-fmodule") end)
+ local ifile = path.translate(path.join(outputdir, path.filename(file) .. ".i"))
+ local flags = table.join(baselineflags, compflags or {}, {file, "-o", ifile})
+ os.vrunv(compinst:program(), flags)
+ local content = io.readfile(ifile)
+ os.rm(ifile)
+ return content
+ end)
+ end
+ changed = true
+
+ local dependinfo = io.readfile(jsonfile)
+ return { moduleinfo = dependinfo }
+ end, {dependfile = dependfile, files = {sourcefile}, changed = target:is_rebuilt()})
+ return changed
+end
+
diff --git a/xmake/rules/c++/modules/modules_support/msvc.lua b/xmake/rules/c++/modules/modules_support/msvc.lua
deleted file mode 100644
index e90ae3df3..000000000
--- a/xmake/rules/c++/modules/modules_support/msvc.lua
+++ /dev/null
@@ -1,821 +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, Arthapz
--- @file msvc.lua
---
-
--- imports
-import("core.base.option")
-import("core.base.json")
-import("core.tool.compiler")
-import("core.project.project")
-import("core.project.depend")
-import("core.project.config")
-import("core.base.semver")
-import("utils.progress")
-import("private.action.build.object", {alias = "objectbuilder"})
-import("common")
-
--- add a module or header unit into the mapper
---
--- e.g
--- /reference 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, namekey, path, objectfile, bmifile, deps)
- local modulemap = _get_modulemap_from_mapper(target)
- if modulemap[namekey] then
- return
- end
- local mapflag = {argument, path .. "=" .. bmifile}
- modulemap[namekey] = {flag = mapflag, objectfile = objectfile, deps = deps}
- common.localcache():set2(_mapper_cachekey(target), "modulemap", modulemap)
-end
-
-function _mapper_cachekey(target)
- local mode = config.mode()
- return target:name() .. "_modulemap_" .. (mode or "")
-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
-
--- do compile
-function _compile(target, flags, sourcefile)
- local compinst = target:compiler("cxx")
- local msvc = target:toolchain("msvc")
- local compflags = compinst:compflags({sourcefile = sourcefile, target = target})
- os.vrunv(compinst:program(), winos.cmdargv(table.join(compflags or {}, flags)), {envs = msvc:runenvs()})
-end
-
--- do compile for batchcmds
--- @note we need to use batchcmds:compilev to translate paths in compflags for generator, e.g. -Ixx
-function _batchcmds_compile(batchcmds, target, flags, sourcefile)
- local compinst = target:compiler("cxx")
- local compflags = compinst:compflags({sourcefile = sourcefile, target = target})
- batchcmds:compilev(table.join(compflags or {}, flags), {compiler = compinst, sourcekind = "cxx"})
-end
-
--- add an objectfile to the linker flags
---
--- e.g
--- foo.obj
---
-function _add_objectfile_to_link_arguments(target, objectfile)
- local cachekey = target:name() .. "dependency_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
-
--- build module file
-function _build_modulefile(target, sourcefile, opt)
- local objectfile = opt.objectfile
- local dependfile = opt.dependfile
- local compinst = compiler.load("cxx", {target = target})
- local compflags = compinst:compflags({sourcefile = sourcefile, target = target})
- local dependinfo = target:is_rebuilt() and {} or (depend.load(dependfile) or {})
-
- -- need build this object?
- local dryrun = option.get("dry-run")
- local depvalues = {compinst:program(), compflags}
- local lastmtime = os.isfile(objectfile) and os.mtime(dependfile) or 0
- if not dryrun and not depend.is_changed(dependinfo, {lastmtime = lastmtime, values = depvalues}) then
- return
- end
-
- -- init flags
- local flags = table.join("-TP", compflags, opt.flags or {})
-
- -- trace
- progress.show(opt.progress, "${color.build.object}compiling.module.$(mode) %s", opt.name)
- vprint(compinst:compcmd(sourcefile, objectfile, {compflags = flags, rawargs = true}))
-
- if not dryrun then
-
- -- do compile
- dependinfo.files = {}
- assert(compinst:compile(sourcefile, objectfile, {dependinfo = dependinfo, compflags = flags}))
-
- -- update files and values to the dependent file
- dependinfo.values = depvalues
- table.join2(dependinfo.files, sourcefile)
- depend.save(dependinfo, dependfile)
- end
-end
-
--- load module support for the current target
-function load(target)
-
- -- add modules flags if visual studio version is < 16.11 (vc 14.29)
- local modulesflag = get_modulesflag(target)
- local msvc = target:toolchain("msvc")
- local vcvars = msvc:config("vcvars")
- if vcvars.VCInstallDir and vcvars.VCToolsVersion and semver.compare(vcvars.VCToolsVersion, "14.29") < 0 then
- target:add("cxxflags", modulesflag)
- end
-
- -- enable std modules if c++23 by defaults
- if target:data("c++.msvc.enable_std_import") == nil then
- local languages = target:get("languages")
- local isatleastcpp23 = false
- for _, language in ipairs(languages) do
- if language:startswith("c++") or language:startswith("cxx") then
- isatleastcpp23 = true
- local version = tonumber(language:match("%d+"))
- if (not version or version <= 20) and not language:match("latest") then
- isatleastcpp23 = false
- break
- end
- end
- end
- local stdmodulesdir
- local msvc = target:toolchain("msvc")
- if msvc then
- local vcvars = msvc:config("vcvars")
- if vcvars.VCInstallDir and vcvars.VCToolsVersion and semver.compare(vcvars.VCToolsVersion, "14.35") > 0 then
- stdmodulesdir = path.join(vcvars.VCInstallDir, "Tools", "MSVC", vcvars.VCToolsVersion, "modules")
- end
- end
- target:data_set("c++.msvc.enable_std_import", isatleastcpp23 and os.isdir(stdmodulesdir))
- 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 msvc = target:toolchain("msvc")
- local scandependenciesflag = get_scandependenciesflag(target)
- local ifcoutputflag = get_ifcoutputflag(target)
- local common_flags = {"-TP", scandependenciesflag}
- 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.module.deps %s", sourcefile)
- end
- local outputdir = common.get_outputdir(target, sourcefile)
-
- local jsonfile = path.join(outputdir, path.filename(sourcefile) .. ".module.json")
- if scandependenciesflag and not target:policy("build.c++.msvc.fallbackscanner") then
- local flags = {jsonfile, sourcefile, ifcoutputflag, outputdir, "-Fo" .. target:objectfile(sourcefile)}
- _compile(target, table.join(common_flags, flags), sourcefile)
- else
- common.fallback_generate_dependencies(target, jsonfile, sourcefile, function(file)
- local compinst = target:compiler("cxx")
- local compflags = compinst:compflags({sourcefile = file, target = target})
- local ifile = path.translate(path.join(outputdir, path.filename(file) .. ".i"))
- os.vrunv(compinst:program(), table.join(compflags,
- {"/P", "-TP", file, "/Fi" .. ifile}), {envs = msvc:runenvs()})
- local content = io.readfile(ifile)
- os.rm(ifile)
- return content
- end)
- end
- changed = true
-
- local dependinfo = io.readfile(jsonfile)
- return { moduleinfo = dependinfo }
- end, {dependfile = dependfile, files = {sourcefile}, changed = target:is_rebuilt()})
- end
- return changed
-end
-
--- generate header unit module bmi for batchjobs
-function generate_headerunit_for_batchjob(target, name, flags, objectfile, index, total)
- -- don't generate same header unit bmi at the same time across targets
- if not common.memcache():get2(name, "generating") then
- local common_flags = {"-TP", "-c"}
- common.memcache():set2(name, "generating", true)
- progress.show((index * 100) / total, "${color.build.object}compiling.headerunit.$(mode) %s", name)
- _compile(target, table.join(common_flags, flags))
- _add_objectfile_to_link_arguments(target, objectfile)
- common.memcache():set2(name, "generating", false)
- end
-end
-
--- generate header unit module bmi for batchcmds
-function generate_headerunit_for_batchcmds(target, name, flags, objectfile, batchcmds, opt)
- local common_flags = {"-TP", "-c"}
- batchcmds:show_progress(opt.progress, "${color.build.object}compiling.headerunit.$(mode) %s", name)
- _batchcmds_compile(batchcmds, target, table.join(common_flags, flags))
- _add_objectfile_to_link_arguments(target, objectfile)
-end
-
--- generate target stl header unit modules for batchjobs
-function generate_stl_headerunits_for_batchjobs(target, batchjobs, headerunits, opt)
- local stlcachedir = common.stlmodules_cachedir(target, {mkdir = true})
-
- -- 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
- for _, headerunit in ipairs(headerunits) do
- local bmifile = path.join(stlcachedir, headerunit.name .. get_bmi_extension())
- local objectfile = bmifile .. ".obj"
- batchjobs:addjob(headerunit.name, function(index, total)
- depend.on_changed(function()
- local flags = {
- exportheaderflag,
- headernameflag .. ":angle",
- headerunit.name,
- ifcoutputflag,
- headerunit.name:startswith("experimental/") and path.join(stlcachedir, "experimental") or stlcachedir,
- "-Fo" .. objectfile
- }
- generate_headerunit_for_batchjob(target, headerunit.name, flags, objectfile, index, total)
-
- end, {dependfile = target:dependfile(bmifile), files = {headerunit.path}, changed = target:is_rebuilt()})
- _add_module_to_mapper(target, headerunitflag .. ":angle", headerunit.name, headerunit.name, objectfile, bmifile)
- end, {rootjob = flushjob})
- end
-end
-
--- generate target stl header units for batchcmds
-function generate_stl_headerunits_for_batchcmds(target, batchcmds, headerunits, opt)
- local stlcachedir = common.stlmodules_cachedir(target, {mkdir = true})
- 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 depmtime = 0
- for _, headerunit in ipairs(headerunits) do
- local bmifile = path.join(stlcachedir, headerunit.name .. get_bmi_extension())
- local objectfile = bmifile .. ".obj"
- local flags = {
- exportheaderflag,
- 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)}
- generate_headerunit_for_batchcmds(target, headerunit.name, flags, objectfile, batchcmds, opt)
- batchcmds:add_depfiles(headerunit.path)
- _add_module_to_mapper(target, headerunitflag .. ":angle", headerunit.name, headerunit.name, objectfile, bmifile)
- 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)
- -- 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
- for _, headerunit in ipairs(headerunits) do
- local file = path.relative(headerunit.path, target:scriptdir())
- local objectfile = target:objectfile(file)
- local outputdir = common.get_outputdir(target, headerunit)
- 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()
- 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 flags = {
- exportheaderflag,
- headernameflag .. headerunit.type,
- headerunit.path,
- ifcoutputflag,
- outputdir,
- "/Fo" .. objectfile
- }
- generate_headerunit_for_batchjob(target, headerunit.name, flags, objectfile, index, total)
- end, {dependfile = target:dependfile(bmifile), files = {headerunit.path}, changed = target:is_rebuilt()})
- _add_module_to_mapper(target, headerunitflag, headerunit.name, headerunit.path, objectfile, bmifile)
- end, {rootjob = flushjob})
- end
-end
-
--- generate target user header units for batchcmds
-function generate_user_headerunits_for_batchcmds(target, batchcmds, headerunits, opt)
- 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 depmtime = 0
- for _, headerunit in ipairs(headerunits) do
- local file = path.relative(headerunit.path, target:scriptdir())
- local objectfile = target:objectfile(file)
- local outputdir = common.get_outputdir(target, headerunit)
- batchcmds:mkdir(outputdir)
-
- local bmifilename = path.basename(objectfile) .. get_bmi_extension()
- local bmifile = path.join(outputdir, bmifilename)
- batchcmds:mkdir(path.directory(objectfile))
-
- local flags = {
- exportheaderflag,
- headernameflag .. headerunit.type,
- headerunit.path,
- ifcoutputflag,
- outputdir,
- "/Fo" .. objectfile
- }
- generate_headerunit_for_batchcmds(target, headerunit.name, flags, objectfile, batchcmds, opt)
- batchcmds:add_depfiles(headerunit.path)
-
- _add_module_to_mapper(target, headerunitflag, headerunit.name, headerunit.path, objectfile, 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)
-
- -- get flags
- local ifcoutputflag = get_ifcoutputflag(target)
- local interfaceflag = get_interfaceflag(target)
- local referenceflag = get_referenceflag(target)
- local internalpartitionflag = get_internalpartitionflag(target)
-
- -- flush job
- local flushjob = batchjobs:addjob(target:name() .. "_modules", function(index, total)
- _flush_mapper(target)
- end, {rootjob = opt.rootjob})
-
- if target:data("c++.msvc.enable_std_import") then
- for objectfile, module in pairs(get_stdmodules(target)) do
- table.insert(objectfiles, objectfile)
- modules[objectfile] = module
- modules[objectfile].external = true
- end
- end
-
- local modulesjobs = {}
- for _, objectfile in ipairs(objectfiles) do
- local module = modules[objectfile]
- if module then
- local cppfile = module.cppfile
- local name, provide
- if module.provides then
- -- assume there that provides is only one, until we encounter the case
- local length = 0
- for k, v in pairs(module.provides) do
- length = length + 1
- name = k
- provide = v
- cppfile = provide.sourcefile
- if length > 1 then
- raise("multiple provides are not supported now!")
- end
- break
- end
- end
- local moduleinfo = table.copy(provide) or {}
- local flags = {"-TP"}
- local dependfile = target:dependfile(objectfile)
-
- if provide then
- table.join2(flags, {ifcoutputflag, path(provide.bmi), provide.interface and interfaceflag or internalpartitionflag})
- dependfile = target:dependfile(provide.bmi)
-
- local fileconfig = target:fileconfig(cppfile)
- if fileconfig and fileconfig.install then
- batchjobs:addjob(name .. "_metafile", function(index, total)
- local metafilepath = common.get_metafile(target, cppfile)
- depend.on_changed(function()
- progress.show((index * 100) / total, "${color.build.object}generating.module.metadata %s", name)
- local metadata = common.generate_meta_module_info(target, name, cppfile, module.requires)
- json.savefile(metafilepath, metadata)
- end, {dependfile = target:dependfile(metafilepath), files = {cppfile}, changed = target:is_rebuilt()})
- end, {rootjob = flushjob})
- end
- end
-
- table.join2(moduleinfo, {
- name = name or cppfile,
- deps = table.keys(module.requires or {}),
- sourcefile = cppfile,
- job = batchjobs:newjob(name or cppfile, 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
- local _flags = table.join(flags, requiresflags or {})
-
- if provide or common.has_module_extension(cppfile) then
- if not common.memcache():get2(name or cppfile, "compiling") then
- if name and module.external then
- common.memcache():set2(name or cppfile, "compiling", true)
- end
- _build_modulefile(target, cppfile, {
- objectfile = objectfile,
- dependfile = dependfile,
- name = name or module.cppfile,
- flags = _flags,
- progress = (index * 100) / total})
- end
- _add_objectfile_to_link_arguments(target, objectfile)
- elseif requiresflags then
- requiresflags = get_requiresflags(target, module.requires)
- target:fileconfig_add(cppfile, {force = {cxxflags = table.join(flags, requiresflags)}})
- end
-
- if provide then
- _add_module_to_mapper(target, referenceflag, name, name, objectfile, provide.bmi, requiresflags)
- end
- end)})
- modulesjobs[name or cppfile] = moduleinfo
- 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)
-
- -- get flags
- local ifcoutputflag = get_ifcoutputflag(target)
- local interfaceflag = get_interfaceflag(target)
- local referenceflag = get_referenceflag(target)
- local internalpartitionflag = get_internalpartitionflag(target)
-
- if target:data("c++.msvc.enable_std_import") then
- for objectfile, module in pairs(get_stdmodules(target)) do
- table.insert(objectfiles, objectfile)
- modules[objectfile] = module
- end
- end
-
- -- build modules
- local depmtime = 0
- for _, objectfile in ipairs(objectfiles) do
- local module = modules[objectfile]
- if module then
- local cppfile = module.cppfile
- local name, provide
- if module.provides then
- local length = 0
- for k, v in pairs(module.provides) do
- length = length + 1
- name = k
- provide = v
- cppfile = provide.sourcefile
- if length > 1 then
- raise("multiple provides are not supported now!")
- end
- break
- end
- end
- -- append required modulemap flags to module
- local requiresflags
- if module.requires then
- requiresflags = get_requiresflags(target, module.requires, {expand = true})
- end
-
- local flags = table.join({"-TP", "-c", path(cppfile), path(objectfile, function (p) return "-Fo" .. p end)})
- if provide or common.has_module_extension(cppfile) then
- if provide then
- table.join2(flags, {ifcoutputflag, path(provide.bmi), provide.interface and interfaceflag or internalpartitionflag})
- end
-
- batchcmds:show_progress(opt.progress, "${color.build.object}compiling.module.$(mode) %s", name or cppfile)
- batchcmds:mkdir(path.directory(objectfile))
- _batchcmds_compile(batchcmds, target, table.join(flags, requiresflags or {}), cppfile)
- batchcmds:add_depfiles(cppfile)
- _add_objectfile_to_link_arguments(target, path.translate(objectfile))
- if provide then
- _add_module_to_mapper(target, referenceflag, name, name, objectfile, provide.bmi, requiresflags)
- -- add requiresflags to module. it will be used for project generation
- target:fileconfig_add(cppfile, {force = {cxxflags = requiresflags}})
- end
- depmtime = math.max(depmtime, os.mtime(provide and provide.bmi or objectfile))
- elseif requiresflags then
- requiresflags = get_requiresflags(target, module.requires)
- target:fileconfig_add(cppfile, {force = {cxxflags = table.join(flags, requiresflags)}})
- end
- end
- end
-
- batchcmds:set_depmtime(depmtime)
- _flush_mapper(target)
-end
-
-function get_stdmodules(target)
- local modules = {}
-
- -- build c++23 standard modules if needed
- if target:data("c++.msvc.enable_std_import") then
- local msvc = target:toolchain("msvc")
- if msvc then
- local vcvars = msvc:config("vcvars")
- if vcvars.VCInstallDir and vcvars.VCToolsVersion then
- local stdmodulesdir = path.join(vcvars.VCInstallDir, "Tools", "MSVC", vcvars.VCToolsVersion, "modules")
- assert(stdmodulesdir, "Can't enable C++23 std modules, directory missing !")
-
- local stlcachedir = common.stlmodules_cachedir(target)
- local modulefile = path.join(stdmodulesdir, "std.ixx")
- local bmifile = path.join(stlcachedir, "std.ixx" .. get_bmi_extension())
- local objfile = bmifile .. ".obj"
- modules[objfile] = {provides = {std = {interface = true, sourcefile = modulefile, bmi = bmifile}}}
- stlcachedir = common.stlmodules_cachedir(target)
- modulefile = path.join(stdmodulesdir, "std.compat.ixx")
- bmifile = path.join(stlcachedir, "std.compat.ixx" .. get_bmi_extension())
- objfile = bmifile .. ".obj"
- modules[objfile] = {provides = {["std.compat"] = {interface = true, sourcefile = modulefile, bmi = bmifile}}, requires = {std = {unique = false, method = "by-name"}}}
- end
- end
- end
- return modules
-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
- local msvc = target:toolchain("msvc")
- local vcvars = msvc:config("vcvars")
- if vcvars.VCInstallDir and vcvars.VCToolsVersion and semver.compare(vcvars.VCToolsVersion, "14.29") < 0 then
- assert(modulesflag, "compiler(msvc): does not support c++ module!")
- end
- _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", os.tmpfile()}, "cxxflags", {flagskey = "cl_ifc_output"}) then
- ifcoutputflag = "-ifcOutput"
- end
- assert(ifcoutputflag, "compiler(msvc): does not support c++ module flag(/ifcOutput)!")
- _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", os.tmpdir()}, "cxxflags", {flagskey = "cl_ifc_search_dir"}) then
- ifcsearchdirflag = "-ifcSearchDir"
- end
- assert(ifcsearchdirflag, "compiler(msvc): does not support c++ module flag(/ifcSearchDir)!")
- _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 flag(/interface)!")
- _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", "Foo=" .. os.tmpfile()}, "cxxflags", {flagskey = "cl_reference"}) then
- referenceflag = "-reference"
- end
- assert(referenceflag, "compiler(msvc): does not support c++ module flag(/reference)!")
- _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({"-std:c++latest", "-exportHeader", "-headerName:quote"}, "cxxflags", {flagskey = "cl_header_name_quote"}) and
- compinst:has_flags({"-std:c++latest", "-exportHeader", "-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")
- local ifcfile = os.tmpfile()
- if compinst:has_flags({"-std:c++latest", "-headerUnit:quote", "foo.h=" .. ifcfile}, "cxxflags", {flagskey = "cl_header_unit_quote"}) and
- compinst:has_flags({"-std:c++latest", "-headerUnit:angle", "foo.h=" .. ifcfile}, "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 exportheaderflag = _g.exportheaderflag
- if exportheaderflag == nil then
- if get_headernameflag(target) then
- exportheaderflag = "-exportHeader"
- end
- _g.exportheaderflag = exportheaderflag or false
- end
- return exportheaderflag 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
- local already_mapped_modules = {}
- for name, _ in table.orderpairs(requires) do
- -- if already in flags, continue
- if already_mapped_modules[name] then
- goto continue
- end
-
- for _, dep in ipairs(target:orderdeps()) do
- local modulemap_ = _get_modulemap_from_mapper(dep)
- if modulemap_[name] then
- table.join2(flags, modulemap_[name].flag)
- -- we need to ignore headerunits from deps
- -- @see https://github.com/xmake-io/xmake/issues/3925
- local skip = 0
- for _, flag in ipairs(modulemap_[name].deps) do
- if flag:find("headerUnit:quote", 1, true) then
- skip = 2
- end
- if skip == 0 then
- table.insert(flags, flag)
- end
- if skip > 0 then
- skip = skip - 1
- end
- end
- already_mapped_modules[name] = true
- 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
-
-function get_cppversionflag(target)
- local cppversionflag = _g.cppversionflag
- if cppversionflag == nil then
- local compinst = target:compiler("cxx")
- local flags = compinst:compflags({target = target})
- cppversionflag = table.find_if(flags, function(v) string.startswith(v, "/std:c++") end) or "/std:c++latest"
- end
- return cppversionflag or nil
-end
-
-function get_internalpartitionflag(target)
- local internalpartitionflag = _g.internalpartitionflag
- if internalpartitionflag == nil then
- local compinst = target:compiler("cxx")
- if compinst:has_flags("-internalPartition", "cxxflags", {flagskey = "cl_internal_partition"}) then
- internalpartitionflag = "-internalPartition"
- end
- _g.internalpartitionflag = internalpartitionflag or false
- end
- return internalpartitionflag or nil
-end
diff --git a/xmake/rules/c++/modules/modules_support/msvc/builder.lua b/xmake/rules/c++/modules/modules_support/msvc/builder.lua
new file mode 100644
index 000000000..5bb9fc4d8
--- /dev/null
+++ b/xmake/rules/c++/modules/modules_support/msvc/builder.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, Arthapz
+-- @file msvc/builder.lua
+--
+
+-- imports
+import("core.base.json")
+import("core.base.option")
+import("core.base.semver")
+import("utils.progress")
+import("private.action.build.object", {alias = "objectbuilder"})
+import("core.tool.compiler")
+import("core.project.config")
+import("core.project.depend")
+import("private.tools.vstool")
+import("compiler_support")
+import(".builder", {inherit = true})
+
+-- get flags for building a module
+function _make_modulebuildflags(target, provide, bmifile, opt)
+ local ifcoutputflag = compiler_support.get_ifcoutputflag(target)
+ local ifconlyflag = compiler_support.get_ifconlyflag(target)
+ local interfaceflag = compiler_support.get_interfaceflag(target)
+ local internalpartitionflag = compiler_support.get_internalpartitionflag(target)
+ local ifconly = (not opt.build_objectfile and ifconlyflag)
+
+ local flags
+ if provide then -- named module
+ flags = table.join({"-TP", ifcoutputflag, path(bmifile), provide.interface and interfaceflag or internalpartitionflag}, ifconly or {})
+ else
+ flags = {"-TP"}
+ end
+ return flags
+end
+function _compile_one_step(target, bmifile, sourcefile, objectfile, provide, opt)
+ local ifcoutputflag = compiler_support.get_ifcoutputflag(target)
+ local interfaceflag = compiler_support.get_interfaceflag(target)
+ local internalpartitionflag = compiler_support.get_internalpartitionflag(target)
+ -- get flags
+ local flags = {"-TP"}
+ if provide then
+ table.join2(flags, ifcoutputflag, path(bmifile), provide.interface and interfaceflag or internalpartitionflag)
+ end
+ if opt and opt.batchcmds then
+ _batchcmds_compile(opt.batchcmds, target, flags, sourcefile, objectfile)
+ else
+ _compile(target, flags, sourcefile, objectfile)
+ end
+end
+
+function _compile_bmi_step(target, bmifile, sourcefile, objectfile, provide, opt)
+ local ifcoutputflag = compiler_support.get_ifcoutputflag(target)
+ local interfaceflag = compiler_support.get_interfaceflag(target)
+ local ifconlyflag = compiler_support.get_ifconlyflag(target)
+
+ if not ifconlyflag then
+ _compile_one_step(target, bmifile, sourcefile, objectfile, provide, opt)
+ else
+ local flags = {"-TP", ifcoutputflag, path(bmifile), interfaceflag, ifconlyflag}
+ if opt and opt.batchcmds then
+ _batchcmds_compile(opt.batchcmds, target, flags, sourcefile, bmifile)
+ else
+ _compile(target, flags, sourcefile, bmifile)
+ end
+ end
+end
+
+function _compile_objectfile_step(target, bmifile, sourcefile, objectfile, provide, opt)
+ local ifconlyflag = compiler_support.get_ifconlyflag(target)
+ local interfaceflag = compiler_support.get_interfaceflag(target)
+ local internalpartitionflag = compiler_support.get_internalpartitionflag(target)
+
+ local flags = {"-TP", (provide and provide.interface) and interfaceflag or internalpartitionflag}
+ if not ifconlyflag then
+ _compile_one_step(target, bmifile, sourcefile, objectfile, provide, opt)
+ else
+ if opt and opt.batchcmds then
+ _batchcmds_compile(opt.batchcmds, target, flags, sourcefile, objectfile)
+ else
+ _compile(target, flags, sourcefile, objectfile)
+ end
+ end
+end
+
+
+-- get flags for building a headerunit
+function _make_headerunitflags(target, headerunit, bmifile)
+
+ -- get flags
+ local exportheaderflag = compiler_support.get_exportheaderflag(target)
+ local headernameflag = compiler_support.get_headernameflag(target)
+ local ifcoutputflag = compiler_support.get_ifcoutputflag(target)
+ local ifconlyflag = compiler_support.get_ifconlyflag(target)
+ assert(headernameflag and exportheaderflag, "compiler(msvc): does not support c++ header units!")
+
+ local local_directory = (headerunit.type == ":quote") and {"-I" .. path.directory(headerunit.path)} or {}
+ local flags = table.join(local_directory, {"-TP",
+ exportheaderflag,
+ headernameflag .. headerunit.type,
+ headerunit.type == ":angle" and headerunit.name or headerunit.path,
+ ifcoutputflag,
+ bmifile}, ifconlyflag or {})
+ return flags
+end
+
+-- do compile
+function _compile(target, flags, sourcefile, outputfile, headerunit)
+
+ local dryrun = option.get("dry-run")
+ local compinst = target:compiler("cxx")
+ local compflags = compinst:compflags({sourcefile = sourcefile, target = target})
+ local flags = table.join(compflags or {}, flags)
+
+ -- trace
+ if option.get("verbose") then
+ if headerunit then
+ print(os.args(compinst:program(), flags))
+ else
+ print(compinst:compcmd(sourcefile, outputfile, {target = target, compflags = flags, rawargs = true}))
+ end
+ end
+
+ -- do compile
+ if not dryrun then
+ if headerunit then
+ local msvc = target:toolchain("msvc")
+ os.vrunv(compinst:program(), flags, {envs = msvc:runenvs()})
+ else
+ assert(compinst:compile(sourcefile, outputfile, {target = target, compflags = flags}))
+ end
+ end
+end
+
+-- do compile for batchcmds
+-- @note we need to use batchcmds:compilev to translate paths in compflags for generator, e.g. -Ixx
+function _batchcmds_compile(batchcmds, target, flags, sourcefile, outputfile)
+ opt = opt or {}
+ local compinst = target:compiler("cxx")
+ local compflags = compinst:compflags({sourcefile = sourcefile, target = target})
+ flags = table.join(compflags or {}, flags)
+ batchcmds:compile(sourcefile, outputfile, {sourcekind = "cxx", compflags = flags})
+end
+
+-- get module requires flags
+-- e.g
+-- /reference 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 _get_requiresflags(target, module, opt)
+
+ local referenceflag = compiler_support.get_referenceflag(target)
+ local headerunitflag = compiler_support.get_headerunitflag(target)
+
+ local name = module.name
+ local cachekey = target:name() .. name
+
+ local requiresflags = compiler_support.memcache():get2(cachekey, "requiresflags")
+ or compiler_support.localcache():get2(cachekey, "requiresflags")
+ if not requiresflags or (opt and opt.regenerate) then
+ local deps_flags = {}
+ for required, _ in table.orderpairs(module.requires) do
+ local dep_module = get_from_target_mapper(target, required)
+
+ assert(dep_module, "module dependency %s required for %s not found <%s>", required, name, target:name())
+
+ local mapflag
+ local bmifile = dep_module.bmi
+ -- aliased headerunit
+ if dep_module.aliasof then
+ local aliased = get_from_target_mapper(target, dep_module.aliasof)
+ bmifile = aliased.bmi
+ mapflag = {headerunitflag .. aliased.headerunit.type, required .. "=" .. bmifile}
+ -- headerunit
+ elseif dep_module.headerunit then
+ mapflag = {headerunitflag .. dep_module.headerunit.type, required .. "=" .. bmifile}
+ -- named module
+ else
+ mapflag = {referenceflag, required .. "=" .. bmifile}
+ end
+ table.insert(deps_flags, mapflag)
+
+ -- append deps
+ if dep_module.opt and dep_module.opt.deps then
+ local deps = _get_requiresflags(target, { name = dep_module.name or dep_module.sourcefile, bmi = bmifile, requires = dep_module.opt.deps })
+ table.join2(deps_flags, deps)
+ end
+ end
+
+ -- remove duplicates
+ requiresflags = {}
+ local contains = {}
+ for _, map in ipairs(deps_flags) do
+ local name, _ = map[2]:split("=")[1], map[2]:split("=")[2]
+ if not contains[name] then
+ table.insert(requiresflags, map)
+ contains[name] = true
+ end
+ end
+ compiler_support.memcache():set2(cachekey, "requiresflags", requiresflags)
+ compiler_support.localcache():set2(cachekey, "requiresflags", requiresflags)
+ end
+ return requiresflags
+end
+
+function _append_requires_flags(target, module, name, cppfile, bmifile, opt)
+ local cxxflags = {}
+ local requiresflags = _get_requiresflags(target, {name = (name or cppfile), bmi = bmifile, requires = module.requires}, {regenerate = opt.build})
+ for _, flag in ipairs(requiresflags) do
+ -- we need to wrap flag to support flag with space
+ if type(flag) == "string" and flag:find(" ", 1, true) then
+ table.insert(cxxflags, {flag})
+ else
+ table.insert(cxxflags, flag)
+ end
+ end
+ target:fileconfig_add(cppfile, {force = {cxxflags = cxxflags}})
+end
+
+-- populate module map
+function populate_module_map(target, modules)
+ for _, module in pairs(modules) do
+ local name, provide, cppfile = compiler_support.get_provided_module(module)
+ if provide then
+ local bmifile = compiler_support.get_bmi_path(provide.bmi)
+ add_module_to_target_mapper(target, name, cppfile, bmifile, {deps = module.requires})
+ end
+ end
+end
+
+-- get defines for a module
+function get_module_required_defines(target, sourcefile)
+ local compinst = compiler.load("cxx", {target = target})
+ local compflags = compinst:compflags({sourcefile = sourcefile, target = target})
+ local defines
+ for _, flag in ipairs(compflags) do
+ if flag:startswith("-D") or flag:startswith("/D") then
+ defines = defines or {}
+ table.insert(defines, flag:sub(3))
+ end
+ end
+ return defines
+end
+
+-- build module file for batchjobs
+function make_module_buildjobs(target, batchjobs, job_name, deps, opt)
+
+ local name, provide, _ = compiler_support.get_provided_module(opt.module)
+ local bmifile = provide and compiler_support.get_bmi_path(provide.bmi)
+ local dryrun = option.get("dry-run")
+
+ return {
+ name = job_name,
+ deps = table.join(target:name() .. "_populate_module_map", deps),
+ sourcefile = opt.cppfile,
+ job = batchjobs:newjob(name or opt.cppfile, function(index, total)
+
+ local mapped_bmi
+ if provide and compiler_support.memcache():get2(target:name() .. name, "reuse") then
+ if not target:is_binary() then
+ return
+ else
+ mapped_bmi = get_from_target_mapper(target, name).bmi
+ end
+ end
+
+ local build, dependinfo
+ local dependfile = target:dependfile(bmifile or opt.objectfile)
+ if provide or compiler_support.has_module_extension(opt.cppfile) then
+ build, dependinfo = should_build(target, opt.cppfile, bmifile, {name = name, objectfile = opt.objectfile, requires = opt.module.requires})
+
+ -- needed to detect rebuild of dependencies
+ if provide and build then
+ mark_build(target, name)
+ end
+ end
+
+ -- append requires flags
+ if opt.module.requires then
+ _append_requires_flags(target, opt.module, name, opt.cppfile, bmifile, opt)
+ end
+
+ -- for cpp file we need to check after appendings the flags
+ if build == nil then
+ build, dependinfo = should_build(target, opt.cppfile, bmifile, {name = name, objectfile = opt.objectfile, requires = opt.module.requires})
+ end
+
+ if build then
+ -- compile if it's a named module
+ if provide or compiler_support.has_module_extension(opt.cppfile) then
+ if not dryrun then
+ local objectdir = path.directory(opt.objectfile)
+ if not os.isdir(objectdir) then
+ os.mkdir(objectdir)
+ end
+ end
+
+ local fileconfig = target:fileconfig(opt.cppfile)
+ local public = fileconfig and fileconfig.public
+ local external = fileconfig and fileconfig.external
+ local bmifile = mapped_bmi or bmifile
+ if target:is_binary() then
+ if mapped_bmi then
+ progress.show((index * 100) / total, "${color.build.target}<%s> ${clear}${color.build.object}compiling.objectfile.$(mode) %s", target:name(), name or opt.cppfile)
+ _compile_objectfile_step(target, bmifile, opt.cppfile, opt.objectfile, provide)
+ else
+ progress.show((index * 100) / total, "${color.build.target}<%s> ${clear}${color.build.object}compiling.module.$(mode) %s", target:name(), name or opt.cppfile)
+ _compile_one_step(target, bmifile, opt.cppfile, opt.objectfile, provide)
+ end
+ else
+ if not public and not external then
+ progress.show((index * 100) / total, "${color.build.target}<%s> ${clear}${color.build.object}compiling.module.$(mode) %s", target:name(), name or opt.cppfile)
+ _compile_one_step(target, bmifile, opt.cppfile, opt.objectfile, provide)
+ else
+ progress.show((index * 100) / total, "${color.build.target}<%s> ${clear}${color.build.object}compiling.bmi.$(mode) %s", target:name(), name or opt.cppfile)
+ _compile_bmi_step(target, bmifile, opt.cppfile, opt.objectfile, provide)
+ end
+ end
+ else
+ os.tryrm(opt.objectfile) -- force rebuild for .cpp files
+ end
+ depend.save(dependinfo, dependfile)
+ end
+ end)}
+end
+
+-- build module file for batchcmds
+function make_module_buildcmds(target, batchcmds, opt)
+
+ local name, provide, _ = compiler_support.get_provided_module(opt.module)
+ local bmifile = provide and compiler_support.get_bmi_path(provide.bmi)
+
+ local mapped_bmi
+ if provide and compiler_support.memcache():get2(target:name() .. name, "reuse") then
+ if not target:is_binary() then
+ return
+ else
+ mapped_bmi = get_from_target_mapper(target, name).bmi
+ end
+ end
+
+ local build
+ if provide or compiler_support.has_module_extension(opt.cppfile) then
+ build = should_build(target, opt.cppfile, bmifile, {name = name, objectfile = opt.objectfile, requires = opt.module.requires})
+
+ -- needed to detect rebuild of dependencies
+ if provide and build then
+ mark_build(target, name)
+ end
+ end
+
+ -- append requires flags
+ if opt.module.requires then
+ _append_requires_flags(target, opt.module, name, opt.cppfile, bmifile, opt)
+ end
+
+ -- for cpp file we need to check after appendings the flags
+ if build == nil then
+ build = should_build(target, opt.cppfile, bmifile, {name = name, objectfile = opt.objectfile, requires = opt.module.requires})
+ end
+
+ if build then
+ -- compile if it's a named module
+ if provide or compiler_support.has_module_extension(opt.cppfile) then
+ batchcmds:mkdir(path.directory(opt.objectfile))
+
+ local fileconfig = target:fileconfig(opt.cppfile)
+ local public = fileconfig and fileconfig.public
+ local external = fileconfig and fileconfig.external
+ local bmifile = mapped_bmi or bmifile
+ if target:is_binary() then
+ if mapped_bmi then
+ batchcmds:show_progress(opt.progress, "${color.build.target}<%s> ${clear}${color.build.object}compiling.objectfile.$(mode) %s", target:name(), name or opt.cppfile)
+ _compile_objectfile_step(target, bmifile, opt.cppfile, opt.objectfile, provide, {batchcmds = batchcmds})
+ else
+ batchcmds:show_progress(opt.progress, "${color.build.target}<%s> ${clear}${color.build.object}compiling.module.$(mode) %s", target:name(), name or opt.cppfile)
+ _compile_one_step(target, bmifile, opt.cppfile, opt.objectfile, provide, {batchcmds = batchcmds})
+ end
+ else
+ if not public and not external then
+ batchcmds:show_progress(opt.progress, "${color.build.target}<%s> ${clear}${color.build.object}compiling.module.$(mode) %s", target:name(), name or opt.cppfile)
+ _compile_one_step(target, bmifile, opt.cppfile, opt.objectfile, provide {batchcmds = batchcmds})
+ else
+ batchcmds:show_progress(opt.progress, "${color.build.target}<%s> ${clear}${color.build.object}compiling.bmi.$(mode) %s", target:name(), name or opt.cppfile)
+ _compile_bmi_step(target, bmifile, opt.cppfile, provide, {batchcmds = batchcmds})
+ end
+ end
+ else
+ batchcmds:rm(opt.objectfile) -- force rebuild for .cpp files
+ end
+ end
+ batchcmds:add_depfiles(opt.cppfile)
+ return os.mtime(opt.objectfile)
+end
+
+-- build headerunit file for batchjobs
+function make_headerunit_buildjobs(target, job_name, batchjobs, headerunit, bmifile, outputdir, opt)
+ local already_exists = add_headerunit_to_target_mapper(target, headerunit, bmifile)
+ if not already_exists then
+ return {
+ name = job_name,
+ sourcefile = headerunit.path,
+ job = batchjobs:newjob(job_name, function(index, total)
+ if not os.isdir(outputdir) then
+ os.mkdir(outputdir)
+ end
+
+ local compinst = compiler.load("cxx", {target = target})
+ local compflags = compinst:compflags({sourcefile = headerunit.path, target = target})
+
+ local dependfile = target:dependfile(bmifile)
+ local dependinfo = depend.load(dependfile) or {}
+ dependinfo.files = {}
+ local depvalues = {compinst:program(), compflags}
+
+ local name = headerunit.unique and headerunit.name or headerunit.path
+
+ if opt.build then
+ progress.show((index * 100) / total, "${color.build.target}<%s> ${clear}${color.build.object}compiling.headerunit.$(mode) %s", target:name(), headerunit.name)
+ _compile(target, _make_headerunitflags(target, headerunit, bmifile), name, target:objectfile(headerunit.path), true)
+ end
+
+ table.insert(dependinfo.files, headerunit.path)
+ dependinfo.values = depvalues
+ depend.save(dependinfo, dependfile)
+ end)}
+ end
+end
+
+-- build headerunit file for batchcmds
+function make_headerunit_buildcmds(target, batchcmds, headerunit, bmifile, outputdir, opt)
+ batchcmds:mkdir(outputdir)
+ add_headerunit_to_target_mapper(target, headerunit, bmifile)
+
+ if opt.build then
+ local name = headerunit.unique and headerunit.name or headerunit.path
+ batchcmds:show_progress(opt.progress, "${color.build.target}<%s> ${clear}${color.build.object}compiling.headerunit.$(mode) %s", target:name(), name)
+ _batchcmds_compile(batchcmds, target, _make_headerunitflags(target, headerunit, bmifile), target:objectfile(headerunit.path))
+ end
+ batchcmds:add_depfiles(headerunit.path)
+ return os.mtime(bmifile)
+end
+
diff --git a/xmake/rules/c++/modules/modules_support/msvc/compiler_support.lua b/xmake/rules/c++/modules/modules_support/msvc/compiler_support.lua
new file mode 100644
index 000000000..69f677706
--- /dev/null
+++ b/xmake/rules/c++/modules/modules_support/msvc/compiler_support.lua
@@ -0,0 +1,299 @@
+--!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/compiler_support.lua
+--
+
+-- imports
+import("core.base.semver")
+import("core.project.config")
+import("lib.detect.find_tool")
+import(".compiler_support", {inherit = true})
+
+-- load module support for the current target
+function load(target)
+
+ local msvc = target:toolchain("msvc")
+ local vcvars = msvc:config("vcvars")
+
+ -- enable std modules if c++23 by defaults
+ if target:data("c++.msvc.enable_std_import") == nil and target:policy("build.c++.modules.std") then
+ local languages = target:get("languages")
+ local isatleastcpp23 = false
+ for _, language in ipairs(languages) do
+ if language:startswith("c++") or language:startswith("cxx") then
+ isatleastcpp23 = true
+ local version = tonumber(language:match("%d+"))
+ if (not version or version <= 20) and not language:match("latest") then
+ isatleastcpp23 = false
+ break
+ end
+ end
+ end
+ local stdmodulesdir
+ local msvc = target:toolchain("msvc")
+ if msvc then
+ local vcvars = msvc:config("vcvars")
+ if vcvars.VCInstallDir and vcvars.VCToolsVersion and semver.compare(vcvars.VCToolsVersion, "14.35") > 0 then
+ stdmodulesdir = path.join(vcvars.VCInstallDir, "Tools", "MSVC", vcvars.VCToolsVersion, "modules")
+ end
+ end
+ target:data_set("c++.msvc.enable_std_import", isatleastcpp23 and os.isdir(stdmodulesdir))
+ end
+end
+
+-- strip flags that doesn't affect bmi generation
+function strip_flags(target, flags)
+ -- speculative list as there is no resource that list flags that prevent reusability, this list will likely be improve over time
+ -- @see https://learn.microsoft.com/en-us/cpp/build/reference/compiler-options-listed-alphabetically?view=msvc-170
+ local strippable_flags = {
+ "I",
+ "TP",
+ "errorReport",
+ "W",
+ "w",
+ "sourceDependencies",
+ "scanDependencies",
+ "reference",
+ "PD",
+ "nologo",
+ "MP",
+ "internalPartition",
+ "interface",
+ "ifcOutput",
+ "help",
+ "headerUnit",
+ "headerName",
+ "Fp",
+ "Fo",
+ "Fm",
+ "Fe",
+ "Fd",
+ "FC",
+ "exportHeader",
+ "EP",
+ "E",
+ "doc",
+ "diagnostics",
+ "cgthreads",
+ "C",
+ "analyze",
+ "?",
+ }
+ if not target:policy("build.c++.modules.tryreuse.discriminate_on_defines") then
+ table.join2(strippable_flags, {"D", "U"})
+ end
+ local output = {}
+ for _, flag in ipairs(flags) do
+ local strip = false
+ for _, _flag in ipairs(strippable_flags) do
+ if flag:startswith("cl::-" .. _flag) or flag:startswith("cl::/" .. _flag) or
+ flag:startswith("-" .. _flag) or flag:startswith("/" .. _flag) then
+ strip = true
+ break
+ end
+ end
+ if not strip then
+ table.insert(output, flag)
+ end
+ end
+ return output
+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
+
+-- build c++23 standard modules if needed
+function get_stdmodules(target)
+ if target:policy("build.c++.modules.std") then
+ if target:data("c++.msvc.enable_std_import") then
+ local msvc = target:toolchain("msvc")
+ if msvc then
+ local vcvars = msvc:config("vcvars")
+ if vcvars.VCInstallDir and vcvars.VCToolsVersion then
+ modules = {}
+
+ local stdmodulesdir = path.join(vcvars.VCInstallDir, "Tools", "MSVC", vcvars.VCToolsVersion, "modules")
+ assert(stdmodulesdir, "Can't enable C++23 std modules, directory missing !")
+
+ return {path.join(stdmodulesdir, "std.ixx"), path.join(stdmodulesdir, "std.compat.ixx")}
+ end
+ end
+ end
+ end
+end
+
+function get_bmi_extension()
+ return ".ifc"
+end
+
+function get_ifcoutputflag(target)
+ local ifcoutputflag = _g.ifcoutputflag
+ if ifcoutputflag == nil then
+ local compinst = target:compiler("cxx")
+ if compinst:has_flags({"-ifcOutput", os.tmpfile()}, "cxxflags", {flagskey = "cl_ifc_output"}) then
+ ifcoutputflag = "-ifcOutput"
+ end
+ assert(ifcoutputflag, "compiler(msvc): does not support c++ module flag(/ifcOutput)!")
+ _g.ifcoutputflag = ifcoutputflag or false
+ end
+ return ifcoutputflag or nil
+end
+
+function get_ifconlyflag(target)
+ local ifconlyflag = _g.ifconlyflag
+ if ifconlyflag == nil then
+ local compinst = target:compiler("cxx")
+ if compinst:has_flags({"-ifcOnly"}, "cxxflags", {flagskey = "cl_ifc_only"}) then
+ ifconlyflag = "-ifcOnly"
+ end
+ _g.ifconlyflag = ifconlyflag or false
+ end
+ return ifconlyflag 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", os.tmpdir()}, "cxxflags", {flagskey = "cl_ifc_search_dir"}) then
+ ifcsearchdirflag = "-ifcSearchDir"
+ end
+ assert(ifcsearchdirflag, "compiler(msvc): does not support c++ module flag(/ifcSearchDir)!")
+ _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 flag(/interface)!")
+ _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", "Foo=" .. os.tmpfile()}, "cxxflags", {flagskey = "cl_reference"}) then
+ referenceflag = "-reference"
+ end
+ assert(referenceflag, "compiler(msvc): does not support c++ module flag(/reference)!")
+ _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({"-std:c++latest", "-exportHeader", "-headerName:quote"}, "cxxflags", {flagskey = "cl_header_name_quote"}) and
+ compinst:has_flags({"-std:c++latest", "-exportHeader", "-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")
+ local ifcfile = os.tmpfile()
+ if compinst:has_flags({"-std:c++latest", "-headerUnit:quote", "foo.h=" .. ifcfile}, "cxxflags", {flagskey = "cl_header_unit_quote"}) and
+ compinst:has_flags({"-std:c++latest", "-headerUnit:angle", "foo.h=" .. ifcfile}, "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 exportheaderflag = _g.exportheaderflag
+ if exportheaderflag == nil then
+ if get_headernameflag(target) then
+ exportheaderflag = "-exportHeader"
+ end
+ _g.exportheaderflag = exportheaderflag or false
+ end
+ return exportheaderflag 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
+
+function get_cppversionflag(target)
+ local cppversionflag = _g.cppversionflag
+ if cppversionflag == nil then
+ local compinst = target:compiler("cxx")
+ local flags = compinst:compflags({target = target})
+ cppversionflag = table.find_if(flags, function(v) string.startswith(v, "/std:c++") end) or "/std:c++latest"
+ end
+ return cppversionflag or nil
+end
+
+function get_internalpartitionflag(target)
+ local internalpartitionflag = _g.internalpartitionflag
+ if internalpartitionflag == nil then
+ local compinst = target:compiler("cxx")
+ if compinst:has_flags("-internalPartition", "cxxflags", {flagskey = "cl_internal_partition"}) then
+ internalpartitionflag = "-internalPartition"
+ end
+ _g.internalpartitionflag = internalpartitionflag or false
+ end
+ return internalpartitionflag or nil
+end
diff --git a/xmake/rules/c++/modules/modules_support/msvc/dependency_scanner.lua b/xmake/rules/c++/modules/modules_support/msvc/dependency_scanner.lua
new file mode 100644
index 000000000..90906fb0e
--- /dev/null
+++ b/xmake/rules/c++/modules/modules_support/msvc/dependency_scanner.lua
@@ -0,0 +1,69 @@
+--!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/dependency_scanner.lua
+--
+
+-- imports
+import("core.base.json")
+import("core.base.semver")
+import("core.project.depend")
+import("private.tools.vstool")
+import("utils.progress")
+import("compiler_support")
+import("builder")
+import(".dependency_scanner", {inherit = true})
+
+-- generate dependency files
+function generate_dependency_for(target, sourcefile, opt)
+ local msvc = target:toolchain("msvc")
+ local scandependenciesflag = compiler_support.get_scandependenciesflag(target)
+ local ifcoutputflag = compiler_support.get_ifcoutputflag(target)
+ local common_flags = {"-TP", scandependenciesflag}
+ local dependfile = target:dependfile(sourcefile)
+ local changed = false
+
+ depend.on_changed(function ()
+ progress.show(opt.progress, "${color.build.target}<%s> generating.module.deps %s", target:name(), sourcefile)
+ local outputdir = compiler_support.get_outputdir(target, sourcefile)
+
+ local jsonfile = path.join(outputdir, path.filename(sourcefile) .. ".module.json")
+ if scandependenciesflag and not target:policy("build.c++.msvc.fallbackscanner") then
+ local flags = {jsonfile, sourcefile, ifcoutputflag, outputdir, "-Fo" .. target:objectfile(sourcefile)}
+ local compinst = target:compiler("cxx")
+ local compflags = table.join(compinst:compflags({sourcefile = sourcefile, target = target}) or {}, common_flags, flags)
+ os.vrunv(compinst:program(), winos.cmdargv(compflags), {envs = msvc:runenvs()})
+ else
+ fallback_generate_dependencies(target, jsonfile, sourcefile, function(file)
+ local compinst = target:compiler("cxx")
+ local compflags = compinst:compflags({sourcefile = file, target = target})
+ local ifile = path.translate(path.join(outputdir, path.filename(file) .. ".i"))
+ os.vrunv(compinst:program(), table.join(compflags,
+ {"/P", "-TP", file, "/Fi" .. ifile}), {envs = msvc:runenvs()})
+ local content = io.readfile(ifile)
+ os.rm(ifile)
+ return content
+ end)
+ end
+ changed = true
+
+ local dependinfo = io.readfile(jsonfile)
+ return { moduleinfo = dependinfo }
+ end, {dependfile = dependfile, files = {sourcefile}, changed = target:is_rebuilt()})
+ return changed
+end
+
diff --git a/xmake/rules/c++/modules/modules_support/stl_headers.lua b/xmake/rules/c++/modules/modules_support/stl_headers.lua
index 2bc108765..4b2d6ed23 100644
--- a/xmake/rules/c++/modules/modules_support/stl_headers.lua
+++ b/xmake/rules/c++/modules/modules_support/stl_headers.lua
@@ -14,7 +14,7 @@
--
-- Copyright (C) 2015-present, TBOOX Open Source Group.
--
--- @author Arthapz, ruki
+-- @author ruki, Arthapz
-- @file stl_headers.lua
--
diff --git a/xmake/rules/c++/modules/xmake.lua b/xmake/rules/c++/modules/xmake.lua
index ab90f67a7..922841cd1 100644
--- a/xmake/rules/c++/modules/xmake.lua
+++ b/xmake/rules/c++/modules/xmake.lua
@@ -14,7 +14,7 @@
--
-- Copyright (C) 2015-present, TBOOX Open Source Group.
--
--- @author ruki
+-- @author ruki, Arthapz
-- @file xmake.lua
--
@@ -28,11 +28,11 @@ rule("c++.build.modules")
add_deps("c++.build.modules.install")
on_config(function (target)
- import("modules_support.common")
+ import("modules_support.compiler_support")
-- 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
- if common.contains_modules(target) then
+ if compiler_support.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.
--
@@ -47,14 +47,19 @@ rule("c++.build.modules")
-- @see https://github.com/xmake-io/xmake/issues/3000
target:set("policy", "build.ccache", false)
- -- get modules support
- local modules_support = common.modules_support(target)
-
- -- load module support
- modules_support.load(target)
+ -- load compiler support
+ compiler_support.load(target)
-- mark this target with modules
target:data_set("cxx.has_modules", true)
+
+ -- moduleonly modules are implicitly public
+ if target:is_moduleonly() then
+ local sourcebatch = target:sourcebatches()["c++.build.modules.builder"]
+ for _, sourcefile in ipairs(sourcebatch.sourcefiles) do
+ target:fileconfig_add(sourcefile, {public = true})
+ end
+ end
end
end)
@@ -66,48 +71,60 @@ rule("c++.build.modules.builder")
-- 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)
+ import("modules_support.compiler_support")
+ import("modules_support.dependency_scanner")
+ import("modules_support.builder")
+
+ -- add target deps modules
+ if target:orderdeps() then
+ local deps_sourcefiles = dependency_scanner.get_targetdeps_modules(target)
+ if deps_sourcefiles then
+ table.join2(sourcebatch.sourcefiles, deps_sourcefiles)
+ end
+ end
+
+ -- append std module
+ local std_modules = compiler_support.get_stdmodules(target)
+ if std_modules then
+ table.join2(sourcebatch.sourcefiles, std_modules)
+ target:fileconfig_set(std_modules[1], {external = true})
+ target:fileconfig_set(std_modules[2], {external = true})
+ end
-- extract packages modules dependencies
- local package_modules_data = common.get_all_package_modules(target, modules, opt)
+ local package_modules_data = dependency_scanner.get_all_packages_modules(target, opt)
if package_modules_data then
- -- cull unused modules
- package_modules_data = common.cull_unused_modules(target, modules, package_modules_data)
- if package_modules_data then
- -- append to sourcebatch
- for name, package_module_data in pairs(package_modules_data) do
- table.insert(sourcebatch.sourcefiles, package_module_data.file)
- end
-
- -- we need to repatch and regenerate dependencies at this point
- common.patch_sourcebatch(target, sourcebatch, opt)
- opt.regenerate = true
- modules = common.get_module_dependencies(target, sourcebatch, opt)
+ -- append to sourcebatch
+ for _, package_module_data in table.orderpairs(package_modules_data) do
+ table.insert(sourcebatch.sourcefiles, package_module_data.file)
+ target:fileconfig_set(package_module_data.file, {external = true, defines = package_module_data.metadata.defines})
end
end
- -- build modules
- common.build_modules_for_batchjobs(target, batchjobs, sourcebatch, modules, opt)
+ opt = opt or {}
+ opt.batchjobs = true
- -- generate headerunits and we need to do it before building modules
- local user_headerunits, stl_headerunits = common.get_headerunits(target, sourcebatch, modules)
- if user_headerunits or stl_headerunits then
- -- we need new group(headerunits)
- -- e.g. group(build_modules) -> group(headerunits)
- opt.rootjob = batchjobs:group_leave() or opt.rootjob
- batchjobs:group_enter(target:name() .. "/generate_headerunits", {rootjob = opt.rootjob})
- local modules_support = common.modules_support(target)
- if stl_headerunits then
- -- build stl header units as other headerunits may need them
- -- TODO maybe we need new group(build_modules) -> group(user_headerunits) -> group(stl_headerunits)
- modules_support.generate_stl_headerunits_for_batchjobs(target, batchjobs, stl_headerunits, opt)
- end
- if user_headerunits then
- modules_support.generate_user_headerunits_for_batchjobs(target, batchjobs, user_headerunits, opt)
- end
+ compiler_support.patch_sourcebatch(target, sourcebatch, opt)
+ local modules = dependency_scanner.get_module_dependencies(target, sourcebatch, opt)
+
+ if not target:is_moduleonly() then
+ -- avoid building non referenced modules
+ sourcebatch.objectfiles = dependency_scanner.sort_modules_by_dependencies(target, sourcebatch.objectfiles, modules)
+
+ -- build modules
+ builder.build_modules_for_batchjobs(target, batchjobs, sourcebatch, modules, opt)
+
+ -- build headerunits and we need to do it before building modules
+ builder.build_headerunits_for_batchjobs(target, batchjobs, sourcebatch, modules, opt)
+
+ -- cull external modules objectfile
+ compiler_support.cull_objectfiles(target, modules, sourcebatch)
+ else
+ sourcebatch.objectfiles = {}
end
+
+ compiler_support.localcache():set2(target:name(), "c++.modules", modules)
+ compiler_support.localcache():save()
else
-- avoid duplicate linking of object files of non-module programs
sourcebatch.objectfiles = {}
@@ -117,43 +134,81 @@ rule("c++.build.modules.builder")
-- 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")
+ import("modules_support.compiler_support")
+ import("modules_support.dependency_scanner")
+ import("modules_support.builder")
+
+ -- add target deps modules
+ if target:orderdeps() then
+ local deps_sourcefiles = dependency_scanner.get_targetdeps_modules(target)
+ if deps_sourcefiles then
+ table.join2(sourcebatch.sourcefiles, deps_sourcefiles)
+ end
+ end
+
+ -- append std module
+ local std_modules = compiler_support.get_stdmodules(target)
+ if std_modules then
+ table.join2(sourcebatch.sourcefiles, std_modules)
+ target:fileconfig_set(std_modules[1], {external = true})
+ target:fileconfig_set(std_modules[2], {external = true})
+ end
+
+ -- extract packages modules dependencies
+ local package_modules_data = dependency_scanner.get_all_packages_modules(target, opt)
+ if package_modules_data then
+ -- append to sourcebatch
+ for _, package_module_data in table.orderpairs(package_modules_data) do
+ table.insert(sourcebatch.sourcefiles, package_module_data.file)
+ target:fileconfig_set(package_module_data.file, {external = true, defines = package_module_data.metadata.defines})
+ end
+ end
+
+ opt = opt or {}
+ opt.batchjobs = false
+
+ compiler_support.patch_sourcebatch(target, sourcebatch, opt)
+ local modules = dependency_scanner.get_module_dependencies(target, sourcebatch, opt)
- -- patch sourcebatch
- common.patch_sourcebatch(target, sourcebatch, opt)
+ if not target:is_moduleonly() then
+ -- avoid building non referenced modules
+ sourcebatch.objectfiles = dependency_scanner.sort_modules_by_dependencies(target, sourcebatch.objectfiles, modules)
- -- generate headerunits
- local modules = common.get_module_dependencies(target, sourcebatch, opt)
- common.generate_headerunits_for_batchcmds(target, batchcmds, sourcebatch, modules, opt)
+ -- build headerunits
+ builder.build_headerunits_for_batchcmds(target, batchcmds, sourcebatch, modules, opt)
- -- build modules
- common.build_modules_for_batchcmds(target, batchcmds, sourcebatch, modules, opt)
+ -- build modules
+ builder.build_modules_for_batchcmds(target, batchcmds, sourcebatch, modules, opt)
+
+ -- cull external modules objectfile
+ compiler_support.cull_objectfiles(target, modules, sourcebatch)
+ else
+ -- avoid duplicate linking of object files of non-module programs
+ sourcebatch.objectfiles = {}
+ end
+
+ compiler_support.localcache():set2(target:name(), "c++.modules", modules)
+ compiler_support.localcache():save()
else
- -- avoid duplicate linking of object files of non-module programs
+ sourcebatch.sourcefiles = {}
sourcebatch.objectfiles = {}
- end
- end)
-
- before_link(function (target)
- import("modules_support.common")
- if target:data("cxx.has_modules") then
- common.append_dependency_objectfiles(target)
+ sourcebatch.dependfiles = {}
end
end)
after_clean(function (target)
import("core.base.option")
- import("modules_support.common")
+ import("modules_support.compiler_support")
import("private.action.clean.remove_files")
-- we cannot use target:data("cxx.has_modules"),
-- because on_config will be not called when cleaning targets
- if common.contains_modules(target) then
- remove_files(common.modules_cachedir(target))
+ if compiler_support.contains_modules(target) then
+ remove_files(compiler_support.modules_cachedir(target))
if option.get("all") then
- remove_files(common.stlmodules_cachedir(target))
- common.localcache():clear()
- common.localcache():save()
+ remove_files(compiler_support.stlmodules_cachedir(target))
+ compiler_support.localcache():clear()
+ compiler_support.localcache():save()
end
end
end)
@@ -163,11 +218,15 @@ rule("c++.build.modules.install")
set_extensions(".mpp", ".mxx", ".cppm", ".ixx")
before_install(function (target)
- import("modules_support.common")
+ import("modules_support.compiler_support")
+ import("modules_support.builder")
-- we cannot use target:data("cxx.has_modules"),
-- because on_config will be not called when installing targets
- if common.contains_modules(target) then
- common.install_module_target(target)
+ if compiler_support.contains_modules(target) then
+ local modules = compiler_support.localcache():get2(target:name(), "c++.modules")
+ builder.generate_metadata(target, modules)
+
+ compiler_support.install_module_target(target)
end
end)
diff --git a/xmake/scripts/vsxmake/vsproj/Xmake.props b/xmake/scripts/vsxmake/vsproj/Xmake.props
index c5c0d6029..dd50e39e4 100644
--- a/xmake/scripts/vsxmake/vsproj/Xmake.props
+++ b/xmake/scripts/vsxmake/vsproj/Xmake.props
@@ -93,6 +93,11 @@
<ConfigurationType>Unknown</ConfigurationType>
</PropertyGroup>
</When>
+ <When Condition="'$(XmakeKind)' == 'moduleonly'">
+ <PropertyGroup>
+ <ConfigurationType>Unknown</ConfigurationType>
+ </PropertyGroup>
+ </When>
</Choose>
<ItemDefinitionGroup>