cmCxxModuleMapper: Fix transitive requirements computation

Previously CMake may generate incomplete transitive requirements in
CMakeFiles/<target>.dir/CXXModules.json and therefore in module mapper
for compiler, when source files were listed in CMakeList.txt in a
certain order.

This commit fixes the problem by correctly tracking unfinished
transitive requirements computation of module units.

There have been a simple circular test case whose circular dependency
was reported by build system. Now with this correct implementation it's
reported by CMake generating module mappers.

Add two test cases for transitive requirements computation, one with
adding source files in hardcoded order, and the other in randomized
order.

Fixes: #25465
This commit is contained in:
namniav
2023-12-04 12:28:33 +08:00
committed by Brad King
parent 3e8f021137
commit e9b0dcbbfa
36 changed files with 392 additions and 6 deletions

View File

@@ -346,13 +346,16 @@ std::set<std::string> CxxModuleUsageSeed(
// Add the direct usage.
this_usages.insert(r.LogicalName);
// Add the transitive usage.
if (transitive_usages != usages.Usage.end()) {
if (transitive_usages == usages.Usage.end() ||
internal_usages.find(r.LogicalName) != internal_usages.end()) {
// Mark that we need to update transitive usages later.
if (bmi_loc.IsKnown()) {
internal_usages[p.LogicalName].insert(r.LogicalName);
}
} else {
// Add the transitive usage.
this_usages.insert(transitive_usages->second.begin(),
transitive_usages->second.end());
} else if (bmi_loc.IsKnown()) {
// Mark that we need to update transitive usages later.
internal_usages[p.LogicalName].insert(r.LogicalName);
}
}

View File

@@ -174,6 +174,8 @@ if ("named" IN_LIST CMake_TEST_MODULE_COMPILATION)
run_cxx_module_test(object-library)
run_cxx_module_test(generated)
run_cxx_module_test(deep-chain)
run_cxx_module_test(non-trivial-collation-order)
run_cxx_module_test(non-trivial-collation-order-randomized)
run_cxx_module_test(duplicate)
set(RunCMake_CXXModules_NO_TEST 1)
run_cxx_module_test(circular)

View File

@@ -1 +1 @@
((Ninja generators)?(build stopped: dependency cycle:)|(Visual Studio generators)?(error : Cannot build the following source files because there is a cyclic dependency between them))
((Ninja generators)?(CMake Error: Circular dependency detected in the C\+\+ module import graph. See modules named: "a", "b")|(Visual Studio generators)?(error : Cannot build the following source files because there is a cyclic dependency between them))

View File

@@ -0,0 +1,9 @@
cmake_minimum_required(VERSION 3.24...3.28)
project(cxx_modules_non_trivial_collation_order_randomized CXX)
include("${CMAKE_SOURCE_DIR}/../cxx-modules-rules.cmake")
set(non_trivial_collation_order_randomized TRUE)
add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../non-trivial-collation-order/"
"${CMAKE_CURRENT_BINARY_DIR}/non-trivial-collation-order")

View File

@@ -0,0 +1,132 @@
cmake_minimum_required(VERSION 3.24...3.28)
project(cxx_modules_non_trivial_collation_order CXX)
include("${CMAKE_SOURCE_DIR}/../cxx-modules-rules.cmake")
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
include(CheckCompilerFlag)
check_compiler_flag(CXX "-Wread-modules-implicitly" have_implicit_module_warning)
if (have_implicit_module_warning)
add_compile_options(-Werror=read-modules-implicitly)
endif ()
endif ()
# Returns an random integer from range [0,length)
function(get_random_index result length)
# math(EXPR) parses and evaluates as 64-bit signed integer.
string(RANDOM LENGTH 18 ALPHABET "0123456789" value)
# Modulo is not uniformly distributed, but we doesn't need that here.
math(EXPR ${result} "${value} % ${length}")
return(PROPAGATE ${result})
endfunction()
# Shuffle list_var randomly
function(shuffle_list list_var)
set(result)
set(length 0)
foreach(item IN LISTS "${list_var}")
math(EXPR length "${length} + 1")
get_random_index(index ${length})
list(INSERT result ${index} "${item}")
endforeach()
set("${list_var}" "${result}" PARENT_SCOPE)
endfunction()
# The import chain is:
# impl-non-partition --(implicitly)--> partition_level(primary interface unit)
# --> :intf7 --> :intf6 --> :intf5 --> :intf4 --> :intf3 --> :intf2 --> :intf1
# --> :impl7 --> :impl6 --> :impl5 --> :impl4 --> :impl3 --> :impl2 --> :impl1
set(partition_level_srcs
partition_level/partition_level.cxx
partition_level/intf1.cxx
partition_level/intf3.cxx
partition_level/intf4.cxx
partition_level/intf2.cxx # intentional order
partition_level/intf5.cxx
partition_level/intf6.cxx
partition_level/intf7.cxx
partition_level/impl1.cxx
partition_level/impl3.cxx
partition_level/impl4.cxx
partition_level/impl2.cxx # intentional order
partition_level/impl5.cxx
partition_level/impl6.cxx
partition_level/impl7.cxx
)
# The import chain is:
# mod7 --> mod6 --> mod5 --> mod4 --> mod3 --> mod2 --> mod1 --> partition_level
set(module_level_srcs
${partition_level_srcs}
module_level/mod1.cxx
module_level/mod3.cxx
module_level/mod4.cxx
module_level/mod2.cxx # intentional order
module_level/mod5.cxx
module_level/mod6.cxx
module_level/mod7.cxx
)
# The import chain is:
# target7 --> target6 --> target5 --> target4 -->
# target3 --> target2 --> target1 --> target_module_level
set(targets
target_module_level
target1
target3
target4
target2 # intentional order
target5
target6
target7
)
if(non_trivial_collation_order_randomized)
shuffle_list(module_level_srcs)
shuffle_list(targets)
endif()
message(STATUS "module_level_srcs: ${module_level_srcs}")
message(STATUS "targets: ${targets}")
foreach(tgt IN LISTS targets)
if (tgt STREQUAL "target_module_level")
add_library(target_module_level STATIC)
target_sources(target_module_level
PUBLIC
FILE_SET CXX_MODULES
BASE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}"
FILES ${module_level_srcs}
PRIVATE
partition_level/impl-non-partition.cxx
)
target_compile_features(target_module_level PUBLIC cxx_std_20)
else()
add_library(${tgt} STATIC)
target_sources(${tgt}
PUBLIC
FILE_SET CXX_MODULES
BASE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}"
FILES target_level/${tgt}.cxx
)
target_compile_features(${tgt} PUBLIC cxx_std_20)
if (tgt MATCHES "^target([2-9])$")
math(EXPR dep_number "${CMAKE_MATCH_1} - 1")
target_link_libraries(${tgt} PRIVATE "target${dep_number}")
else()
target_link_libraries(${tgt} PRIVATE target_module_level)
endif()
endif()
endforeach()
add_executable(exe)
target_link_libraries(exe PRIVATE target7)
target_sources(exe
PRIVATE
main.cxx)
add_test(NAME exe COMMAND exe)

View File

@@ -0,0 +1,6 @@
import target7;
int main()
{
return target7();
}

View File

@@ -0,0 +1,7 @@
export module mod1;
import partition_level;
export int mod1()
{
return partition_level::get();
}

View File

@@ -0,0 +1,7 @@
export module mod2;
import mod1;
export int mod2()
{
return mod1();
}

View File

@@ -0,0 +1,7 @@
export module mod3;
import mod2;
export int mod3()
{
return mod2();
}

View File

@@ -0,0 +1,7 @@
export module mod4;
import mod3;
export int mod4()
{
return mod3();
}

View File

@@ -0,0 +1,7 @@
export module mod5;
import mod4;
export int mod5()
{
return mod4();
}

View File

@@ -0,0 +1,7 @@
export module mod6;
import mod5;
export int mod6()
{
return mod5();
}

View File

@@ -0,0 +1,7 @@
export module mod7;
import mod6;
export int mod7()
{
return mod6();
}

View File

@@ -0,0 +1,10 @@
module partition_level;
namespace partition_level {
int non_partition()
{
return primary() + intf1() + intf2() + intf3() + intf4() + intf5() +
intf6() + intf7() + impl1() + impl2() + impl3() + impl4() + impl5() +
impl6() + impl7();
}
}

View File

@@ -0,0 +1,7 @@
module partition_level:impl1;
namespace partition_level {
int impl1()
{
return 0;
}
}

View File

@@ -0,0 +1,8 @@
module partition_level:impl2;
import :impl1;
namespace partition_level {
int impl2()
{
return impl1();
}
}

View File

@@ -0,0 +1,8 @@
module partition_level:impl3;
import :impl2;
namespace partition_level {
int impl3()
{
return impl2();
}
}

View File

@@ -0,0 +1,8 @@
module partition_level:impl4;
import :impl3;
namespace partition_level {
int impl4()
{
return impl3();
}
}

View File

@@ -0,0 +1,8 @@
module partition_level:impl5;
import :impl4;
namespace partition_level {
int impl5()
{
return impl4();
}
}

View File

@@ -0,0 +1,8 @@
module partition_level:impl6;
import :impl5;
namespace partition_level {
int impl6()
{
return impl5();
}
}

View File

@@ -0,0 +1,8 @@
module partition_level:impl7;
import :impl6;
namespace partition_level {
int impl7()
{
return impl6();
}
}

View File

@@ -0,0 +1,8 @@
export module partition_level:intf1;
import :impl7;
namespace partition_level {
int intf1()
{
return impl7();
}
}

View File

@@ -0,0 +1,8 @@
export module partition_level:intf2;
export import :intf1;
namespace partition_level {
int intf2()
{
return intf1();
}
}

View File

@@ -0,0 +1,8 @@
export module partition_level:intf3;
export import :intf2;
namespace partition_level {
int intf3()
{
return intf2();
}
}

View File

@@ -0,0 +1,8 @@
export module partition_level:intf4;
export import :intf3;
namespace partition_level {
int intf4()
{
return intf3();
}
}

View File

@@ -0,0 +1,8 @@
export module partition_level:intf5;
export import :intf4;
namespace partition_level {
int intf5()
{
return intf4();
}
}

View File

@@ -0,0 +1,8 @@
export module partition_level:intf6;
export import :intf5;
namespace partition_level {
int intf6()
{
return intf5();
}
}

View File

@@ -0,0 +1,8 @@
export module partition_level:intf7;
export import :intf6;
namespace partition_level {
int intf7()
{
return intf6();
}
}

View File

@@ -0,0 +1,15 @@
export module partition_level;
export import :intf7;
namespace partition_level {
int primary()
{
return intf7();
}
int non_partition();
export int get()
{
return non_partition();
}
}

View File

@@ -0,0 +1,7 @@
export module target1;
import mod7;
export int target1()
{
return mod7();
}

View File

@@ -0,0 +1,7 @@
export module target2;
import target1;
export int target2()
{
return target1();
}

View File

@@ -0,0 +1,7 @@
export module target3;
import target2;
export int target3()
{
return target2();
}

View File

@@ -0,0 +1,7 @@
export module target4;
import target3;
export int target4()
{
return target3();
}

View File

@@ -0,0 +1,7 @@
export module target5;
import target4;
export int target5()
{
return target4();
}

View File

@@ -0,0 +1,7 @@
export module target6;
import target5;
export int target6()
{
return target5();
}

View File

@@ -0,0 +1,7 @@
export module target7;
import target6;
export int target7()
{
return target6();
}