diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 966ff508df..8788f10e89 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -293,6 +293,72 @@ jobs: ${SCCACHE_PATH} --show-stats ${SCCACHE_PATH} --show-stats | grep -e "Cache hits\s*[1-9]" + cmake-modules: + runs-on: ubuntu-24.04 + needs: build + + env: + SCCACHE_GHA_ENABLED: "on" + LLVM_VERSION: "19" + + steps: + - uses: actions/checkout@v5 + + - name: Configure Cache Env + uses: actions/github-script@v8 + with: + script: | + core.exportVariable('ACTIONS_RESULTS_URL', process.env.ACTIONS_RESULTS_URL || ''); + core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '') + + - uses: actions/download-artifact@v5 + with: + name: integration-tests + path: /home/runner/.cargo/bin/ + - name: Chmod for binary + run: chmod +x ${SCCACHE_PATH} + + - name: Install dependencies + shell: bash + run: | + wget https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + sudo ./llvm.sh "${LLVM_VERSION}" + sudo apt-get update + sudo apt-get install -y ninja-build + pip install cmake + + - name: Test + run: | + cd `pwd`/tests/cmake-modules/ + cmake -B build -G Ninja \ + -DCMAKE_C_COMPILER=clang-${LLVM_VERSION} \ + -DCMAKE_CXX_COMPILER=clang++-${LLVM_VERSION} \ + -DCMAKE_C_COMPILER_LAUNCHER=${SCCACHE_PATH} \ + -DCMAKE_CXX_COMPILER_LAUNCHER=${SCCACHE_PATH} + cmake --build build + + - name: Output + run: | + ${SCCACHE_PATH} --show-stats + + - name: Test Twice for Cache Read + run: | + cd `pwd`/tests/cmake-modules/ + rm -rf build + cmake -B build -G Ninja \ + -DCMAKE_C_COMPILER=clang-${LLVM_VERSION} \ + -DCMAKE_CXX_COMPILER=clang++-${LLVM_VERSION} \ + -DCMAKE_C_COMPILER_LAUNCHER=${SCCACHE_PATH} \ + -DCMAKE_CXX_COMPILER_LAUNCHER=${SCCACHE_PATH} + cmake --build build + + - name: Output + run: | + ${SCCACHE_PATH} --show-stats + + ${SCCACHE_PATH} --show-stats | grep -e "Cache hits\s*[1-9]" + xcode: runs-on: macos-latest env: diff --git a/.gitignore b/.gitignore index 00fdcab171..12cacf5a92 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,4 @@ tests/**/Cargo.lock .direnv/ result +.vscode/settings.json diff --git a/README.md b/README.md index 7103540dc3..f5342acac8 100644 --- a/README.md +++ b/README.md @@ -372,6 +372,22 @@ Known Caveats [More details on Rust caveats](/docs/Rust.md) +### C++20 Modules + +sccache has partial support for C++20 named modules when using **Clang**. The following flags are supported: + +* `-fmodule-file=` and `-fmodule-file==` - importing precompiled module interfaces +* `-fmodule-output=` - generating module interface output alongside object files +* `--precompile` - compiling module interface units +* `-fmodules-reduced-bmi` - generating reduced BMI files + +The following module-related flags are **not supported** and will bypass the cache: + +* `-fmodules` and `-fcxx-modules` - Clang header modules (not C++20 named modules) +* `-fprebuilt-implicit-modules` and `-fprebuilt-module-path` - implicit module discovery + +**GCC** and **MSVC** C++20 modules are not yet supported. Compilations using `-fmodules-ts` (GCC) or `/interface`, `/ifcOutput`, etc. (MSVC) will bypass the cache. + ### User Agent * Requests sent to your storage option of choice will have a user agent header indicating the current sccache version, e.g. `sccache/0.8.2`. diff --git a/src/compiler/clang.rs b/src/compiler/clang.rs index 1d986c496d..e6294d5241 100644 --- a/src/compiler/clang.rs +++ b/src/compiler/clang.rs @@ -187,6 +187,7 @@ counted_array!(pub static ARGS: [ArgInfo; _] = [ take_arg!("--dependent-lib", OsString, Concatenated(b'='), PassThrough), take_arg!("--hip-device-lib-path", PathBuf, Concatenated(b'='), PassThroughPath), take_arg!("--hip-path", PathBuf, Concatenated(b'='), PassThroughPath), + flag!("--precompile", ModuleOnlyFlag), take_arg!("--rocm-path", PathBuf, Concatenated(b'='), PassThroughPath), take_arg!("--serialize-diagnostics", OsString, Separated, PassThrough), take_arg!("--target", OsString, Separated, PassThrough), @@ -207,12 +208,16 @@ counted_array!(pub static ARGS: [ArgInfo; _] = [ take_arg!("-fdebug-compilation-dir", OsString, Separated, PassThrough), take_arg!("-fembed-offload-object", PathBuf, Concatenated(b'='), ExtraHashFile), take_arg!("-fexperimental-assignment-tracking", OsString, Concatenated(b'='), PassThrough), - flag!("-fmodules", TooHardFlag), + take_arg!("-fmodule-file", OsString, Concatenated(b'='), ExtraHashFileClangModuleFile), + take_arg!("-fmodule-output", OsString, Concatenated, ClangModuleOutput), + flag!("-fmodules-reduced-bmi", PassThroughFlag), flag!("-fno-color-diagnostics", NoDiagnosticsColorFlag), flag!("-fno-pch-timestamp", PassThroughFlag), flag!("-fno-profile-instr-generate", TooHardFlag), flag!("-fno-profile-instr-use", TooHardFlag), take_arg!("-fplugin", PathBuf, CanBeConcatenated(b'='), ExtraHashFile), + flag!("-fprebuilt-implicit-modules", TooHardFlag), + take_arg!("-fprebuilt-module-path", OsString, Concatenated, TooHard), flag!("-fprofile-instr-generate", ProfileGenerate), // Note: the PathBuf argument is optional take_arg!("-fprofile-instr-use", PathBuf, Concatenated(b'='), ClangProfileUse), @@ -1130,6 +1135,197 @@ mod test { ); } + #[test] + fn test_parse_arguments_cxx20_modules_unsupported() { + // -fprebuilt-implicit-modules is not supported (implicit module discovery) + assert_eq!( + CompilerArguments::CannotCache("-fprebuilt-implicit-modules", None), + parse_arguments_(stringvec![ + "-c", + "foo.c", + "-fprebuilt-implicit-modules", + "-o", + "foo.o" + ]) + ); + + // -fprebuilt-module-path is not supported (implicit module path discovery) + assert_eq!( + CompilerArguments::CannotCache("-fprebuilt-module-path", None), + parse_arguments_(stringvec![ + "-c", + "foo.c", + "-fprebuilt-module-path=/path/to/modules", + "-o", + "foo.o" + ]) + ); + } + + #[test] + fn test_parse_arguments_cxx20_module_precompile() { + // Test --precompile flag for creating module interface units + let a = parses!( + "-c", + "module.cppm", + "-o", + "module.pcm", + "--precompile", + "-x", + "c++-module" + ); + assert_eq!(Some("module.cppm"), a.input.to_str()); + assert_eq!(Language::CxxModule, a.language); + assert_map_contains!( + a.outputs, + ( + "obj", + ArtifactDescriptor { + path: PathBuf::from("module.pcm"), + optional: false + } + ) + ); + } + + #[test] + fn test_parse_arguments_cxx20_module_fmodule_file() { + // Test -fmodule-file= for importing precompiled modules + let a = parses!("-c", "foo.cpp", "-o", "foo.o", "-fmodule-file=mymodule.pcm"); + assert_eq!(Some("foo.cpp"), a.input.to_str()); + assert_eq!(ovec!["-fmodule-file=mymodule.pcm"], a.common_args); + assert_eq!( + ovec![std::env::current_dir().unwrap().join("mymodule.pcm")], + a.extra_hash_files + ); + } + + #[test] + fn test_parse_arguments_cxx20_module_fmodule_file_with_name() { + // Test -fmodule-file=name=path syntax + let a = parses!( + "-c", + "foo.cpp", + "-o", + "foo.o", + "-fmodule-file=mymodule=path/to/mymodule.pcm" + ); + assert_eq!(Some("foo.cpp"), a.input.to_str()); + assert_eq!( + ovec!["-fmodule-file=mymodule=path/to/mymodule.pcm"], + a.common_args + ); + assert_eq!( + ovec![ + std::env::current_dir() + .unwrap() + .join("path/to/mymodule.pcm") + ], + a.extra_hash_files + ); + } + + #[test] + fn test_parse_arguments_cxx20_module_fmodule_output() { + // Test -fmodule-output= for generating module output alongside object file + let a = parses!( + "-c", + "module.cppm", + "-o", + "module.o", + "-fmodule-output=module.pcm" + ); + assert_eq!(Some("module.cppm"), a.input.to_str()); + assert_map_contains!( + a.outputs, + ( + "obj", + ArtifactDescriptor { + path: PathBuf::from("module.o"), + optional: false + } + ), + ( + "module", + ArtifactDescriptor { + path: PathBuf::from("module.pcm"), + optional: false + } + ) + ); + assert_eq!(ovec!["-fmodule-output=module.pcm"], a.common_args); + } + + #[test] + fn test_parse_arguments_cxx20_module_fmodule_output_implicit_path() { + // Test -fmodule-output without explicit path (uses input name + .pcm) + let a = parses!("-c", "mymodule.cppm", "-o", "mymodule.o", "-fmodule-output"); + assert_eq!(Some("mymodule.cppm"), a.input.to_str()); + assert_map_contains!( + a.outputs, + ( + "obj", + ArtifactDescriptor { + path: PathBuf::from("mymodule.o"), + optional: false + } + ), + ( + "module", + ArtifactDescriptor { + path: PathBuf::from("mymodule.cppm.pcm"), + optional: false + } + ) + ); + } + + #[test] + fn test_parse_arguments_cxx20_module_fmodules_reduced_bmi() { + // Test -fmodules-reduced-bmi flag (Clang 18+) + let a = parses!( + "-c", + "module.cppm", + "-o", + "module.o", + "-fmodule-output=module.pcm", + "-fmodules-reduced-bmi" + ); + assert_eq!(Some("module.cppm"), a.input.to_str()); + assert_eq!( + ovec!["-fmodule-output=module.pcm", "-fmodules-reduced-bmi"], + a.common_args + ); + } + + #[test] + fn test_parse_arguments_cxx20_module_combined_flags() { + // Test combination of module flags as typically used in practice + let a = parses!( + "-c", + "consumer.cpp", + "-o", + "consumer.o", + "-fmodule-file=mymodule=mymodule.pcm", + "-fmodule-file=othermodule=other.pcm" + ); + assert_eq!(Some("consumer.cpp"), a.input.to_str()); + assert_eq!( + ovec![ + "-fmodule-file=mymodule=mymodule.pcm", + "-fmodule-file=othermodule=other.pcm" + ], + a.common_args + ); + assert_eq!( + vec![ + std::env::current_dir().unwrap().join("mymodule.pcm"), + std::env::current_dir().unwrap().join("other.pcm"), + ], + a.extra_hash_files + ); + } + #[test] fn test_compile_clang_cuda_does_not_dist_compile() { let creator = new_creator(); diff --git a/src/compiler/compiler.rs b/src/compiler/compiler.rs index 8620ca9b4e..ccb612ca79 100644 --- a/src/compiler/compiler.rs +++ b/src/compiler/compiler.rs @@ -242,6 +242,7 @@ pub enum Language { Cubin, Rust, Hip, + CxxModule, } impl Language { @@ -259,6 +260,7 @@ impl Language { Some("ii") => Some(Language::CxxPreprocessed), Some("H") | Some("hh") | Some("hp") | Some("hpp") | Some("HPP") | Some("hxx") | Some("h++") | Some("tcc") => Some(Language::CxxHeader), + Some("cppm") | Some("ixx") => Some(Language::CxxModule), Some("m") => Some(Language::ObjectiveC), Some("mi") => Some(Language::ObjectiveCPreprocessed), Some("M") | Some("mm") => Some(Language::ObjectiveCxx), @@ -297,6 +299,7 @@ impl Language { Language::Cubin => "cubin", Language::Rust => "rust", Language::Hip => "hip", + Language::CxxModule => "c++-module", } } @@ -358,6 +361,7 @@ impl Language { Language::Rust => None, // Let the compiler decide Language::Hip => Some("hip"), Language::GenericHeader => None, // Let the compiler decide + Language::CxxModule => Some("c++-module"), } } @@ -383,6 +387,7 @@ impl CompilerKind { | Language::CPreprocessed | Language::Cxx | Language::CxxHeader + | Language::CxxModule | Language::CxxPreprocessed | Language::GenericHeader | Language::ObjectiveC diff --git a/src/compiler/gcc.rs b/src/compiler/gcc.rs index 16fccd5049..5613ac8920 100644 --- a/src/compiler/gcc.rs +++ b/src/compiler/gcc.rs @@ -159,8 +159,13 @@ ArgData! { pub ClangProfileUse(PathBuf), TestCoverage, Coverage, + ModuleOnlyFlag, ExtraHashFile(PathBuf), // Only valid for clang, but this needs to be here since clang shares gcc's arg parsing. + // For -fmodule-file which can be either "path" or "name=path" + ExtraHashFileClangModuleFile(OsString), + // For -fmodule-output + ClangModuleOutput(OsString), XClang(OsString), Arch(OsString), PedanticFlag, @@ -212,6 +217,12 @@ counted_array!(pub static ARGS: [ArgInfo; _] = [ take_arg!("-b", OsString, Separated, PassThrough), flag!("-c", DoCompilation), take_arg!("-fdiagnostics-color", OsString, Concatenated(b'='), DiagnosticsColor), + // Old: gcc/clang header module flag. + flag!("-fmodules", TooHardFlag), + // New: C++20 gcc modules flag. + // TODO: Add support for GCC modules + // See https://github.com/mozilla/sccache/issues/2630 + flag!("-fmodules-ts", TooHardFlag), flag!("-fno-diagnostics-color", NoDiagnosticsColorFlag), flag!("-fno-profile-generate", TooHardFlag), flag!("-fno-profile-use", TooHardFlag), @@ -278,6 +289,8 @@ where S: SearchableArgInfo, { let mut output_arg = None; + let mut module_output_path = None; + let mut module_only_flag = false; let mut input_arg = None; let mut double_dash_input = false; let mut dep_target = None; @@ -348,6 +361,7 @@ where Some(TooHardFlag) | Some(TooHard(_)) => { cannot_cache!(arg.flag_str().expect("Can't be Argument::Raw/UnknownFlag",)) } + Some(ModuleOnlyFlag) => module_only_flag = true, Some(PedanticFlag) => pedantic_flag = true, // standard values vary, but extension values all start with "gnu" Some(Standard(version)) => language_extensions = version.starts_with("gnu"), @@ -376,6 +390,18 @@ where }; } Some(Output(p)) => output_arg = Some(p.clone()), + Some(ClangModuleOutput(p)) => { + if let Some(p) = p.split_prefix("=") { + module_output_path = Some(Some(PathBuf::from(p))); + } else if p.is_empty() { + module_output_path = Some(None); + } else { + cannot_cache!( + "unknown module output format", + p.to_string_lossy().into_owned() + ); + } + } Some(NeedDepTarget) => { need_explicit_dep_target = true; if let DepArgumentRequirePath::NotNeeded = need_explicit_dep_argument_path { @@ -394,6 +420,7 @@ where serialize_diagnostics = Some(path.clone()); } Some(ExtraHashFile(_)) + | Some(ExtraHashFileClangModuleFile(_)) | Some(PassThroughFlag) | Some(PreprocessorArgumentFlag) | Some(PreprocessorArgument(_)) @@ -412,6 +439,7 @@ where "c-header" => Some(Language::CHeader), "c++" => Some(Language::Cxx), "c++-header" => Some(Language::CxxHeader), + "c++-module" => Some(Language::CxxModule), "objective-c" => Some(Language::ObjectiveC), "objective-c-header" => Some(Language::ObjectiveCHeader), "objective-c++" => Some(Language::ObjectiveCxx), @@ -454,6 +482,7 @@ where } let args = match arg.get_data() { Some(SplitDwarf) + | Some(ModuleOnlyFlag) | Some(PedanticFlag) | Some(Standard(_)) | Some(ProfileGenerate) @@ -465,6 +494,7 @@ where | Some(NoDiagnosticsColorFlag) | Some(PassThroughFlag) | Some(PassThrough(_)) + | Some(ClangModuleOutput(_)) | Some(PassThroughPath(_)) => &mut common_args, Some(UnhashedFlag) | Some(Unhashed(_)) => &mut unhashed_args, Some(Arch(_)) => &mut arch_args, @@ -472,6 +502,17 @@ where extra_hash_files.push(cwd.join(path)); &mut common_args } + Some(ExtraHashFileClangModuleFile(val)) => { + // -fmodule-file can be either "path" or "name=path" + let val_str = val.to_string_lossy(); + let path = if let Some(idx) = val_str.find('=') { + PathBuf::from(&val_str[idx + 1..]) + } else { + PathBuf::from(val) + }; + extra_hash_files.push(cwd.join(path)); + &mut common_args + } Some(PreprocessorArgument(_)) => { too_hard_for_preprocessor_cache_mode = match arg.flag_str() { Some(s) if s == "-Xpreprocessor" || s == "-Wp" => Some(arg.to_os_string()), @@ -513,6 +554,7 @@ where let arg = try_or_cannot_cache!(arg, "argument parse"); let args = match arg.get_data() { Some(SplitDwarf) + | Some(ModuleOnlyFlag) | Some(PedanticFlag) | Some(Standard(_)) | Some(ProfileGenerate) @@ -522,6 +564,7 @@ where | Some(DoCompilation) | Some(Language(_)) | Some(Output(_)) + | Some(ClangModuleOutput(_)) | Some(TooHardFlag) | Some(XClang(_)) | Some(TooHard(_)) => cannot_cache!( @@ -555,6 +598,17 @@ where extra_hash_files.push(cwd.join(path)); &mut common_args } + Some(ExtraHashFileClangModuleFile(val)) => { + // -fmodule-file can be either "path" or "name=path" + let val_str = val.to_string_lossy(); + let path = if let Some(idx) = val_str.find('=') { + PathBuf::from(&val_str[idx + 1..]) + } else { + PathBuf::from(val) + }; + extra_hash_files.push(cwd.join(path)); + &mut common_args + } Some(PreprocessorArgumentFlag) | Some(PreprocessorArgument(_)) | Some(PreprocessorArgumentPath(_)) => &mut preprocessor_args, @@ -674,6 +728,17 @@ where ); } + if !module_only_flag { + outputs.extend(module_output_path.into_iter().map(|p| { + ( + "module", + module_artifact_descriptor(Path::new(&input), &output, p.as_deref()), + ) + })); + } + + // Sometimes if this is precompiled this will be a module file, + // however we call it an object because there MUST be an object file output. outputs.insert( "obj", ArtifactDescriptor { @@ -704,6 +769,33 @@ where }) } +fn module_artifact_descriptor( + input: &Path, + output: &Path, + module_output_path: Option<&Path>, +) -> ArtifactDescriptor { + let empty_os_string = OsString::new(); + + let path = module_output_path + .map(|p| p.to_path_buf()) + .unwrap_or_else(|| { + let input_file_name = input.file_name().unwrap_or(&empty_os_string); + let mut path = output.with_file_name(input_file_name); + let mut ext = path + .extension() + .map(|e| e.to_os_string()) + .unwrap_or_default(); + ext.push(".pcm"); + path.set_extension(ext); + path + }); + + ArtifactDescriptor { + path, + optional: false, + } +} + pub fn language_to_gcc_arg(lang: Language) -> Option<&'static str> { lang.to_gcc_arg() } @@ -2091,6 +2183,29 @@ mod test { ); } + #[test] + fn test_parse_arguments_cxx20_modules_unsupported() { + // C++20 modules are not yet supported in GCC mode + + // -fmodules-ts - enables C++20 modules support in GCC + assert_eq!( + CompilerArguments::CannotCache("-fmodules-ts", None), + parse_arguments_( + stringvec!["-c", "foo.cpp", "-fmodules-ts", "-o", "foo.o"], + false + ) + ); + + // -fmodules - older/clang-style modules flag + assert_eq!( + CompilerArguments::CannotCache("-fmodules", None), + parse_arguments_( + stringvec!["-c", "foo.cpp", "-fmodules", "-o", "foo.o"], + false + ) + ); + } + #[test] fn test_parse_arguments_response_file() { assert_eq!( diff --git a/src/compiler/msvc.rs b/src/compiler/msvc.rs index 590fdd27cd..e862000381 100644 --- a/src/compiler/msvc.rs +++ b/src/compiler/msvc.rs @@ -457,7 +457,16 @@ msvc_args!(static ARGS: [ArgInfo; _] = [ msvc_take_arg!("guard:cf", OsString, Concatenated, PassThroughWithSuffix), msvc_flag!("homeparams", PassThrough), msvc_flag!("hotpatch", PassThrough), + // New: C++20 msvc modules flags. + // TODO: Add support for msvc modules + // See https://github.com/mozilla/sccache/issues/2629 + msvc_take_arg!("ifcMap", PathBuf, Separated, TooHardPath), + msvc_flag!("ifcOnly", TooHardFlag), + msvc_take_arg!("ifcOutput", PathBuf, Separated, TooHardPath), + msvc_take_arg!("ifcSearchDir", PathBuf, Separated, TooHardPath), msvc_take_arg!("imsvc", PathBuf, CanBeSeparated, PreprocessorArgumentPath), + msvc_flag!("interface", TooHardFlag), + msvc_flag!("internalPartition", TooHardFlag), msvc_flag!("kernel", PassThrough), msvc_flag!("kernel-", PassThrough), msvc_flag!("nologo", PassThrough), @@ -467,12 +476,14 @@ msvc_args!(static ARGS: [ArgInfo; _] = [ msvc_flag!("openmp:experimental", PassThrough), msvc_flag!("permissive", PassThrough), msvc_flag!("permissive-", PassThrough), + msvc_take_arg!("reference", OsString, Separated, TooHard), msvc_flag!("sdl", PassThrough), msvc_flag!("sdl-", PassThrough), msvc_flag!("showIncludes", ShowIncludes), msvc_take_arg!("source-charset:", OsString, Concatenated, PassThroughWithSuffix), msvc_take_arg!("sourceDependencies", PathBuf, CanBeSeparated, DepFile), msvc_take_arg!("std:", OsString, Concatenated, PassThroughWithSuffix), + msvc_take_arg!("stdIfcDir", PathBuf, Separated, TooHardPath), msvc_flag!("u", PassThrough), msvc_flag!("utf-8", PassThrough), msvc_flag!("validate-charset", PassThrough), @@ -662,8 +673,17 @@ pub fn parse_arguments( // Eagerly bail if it looks like we need to do more complicated work use crate::compiler::gcc::ArgData::*; let args = match arg.get_data() { - Some(SplitDwarf) | Some(TestCoverage) | Some(Coverage) | Some(DoCompilation) - | Some(Language(_)) | Some(Output(_)) | Some(TooHardFlag) | Some(XClang(_)) + Some(SplitDwarf) + | Some(TestCoverage) + | Some(Coverage) + | Some(DoCompilation) + | Some(Language(_)) + | Some(Output(_)) + | Some(TooHardFlag) + | Some(XClang(_)) + | Some(ClangModuleOutput(_)) + | Some(ExtraHashFileClangModuleFile(_)) + | Some(ModuleOnlyFlag) | Some(TooHard(_)) => cannot_cache!( arg.flag_str() .unwrap_or("Can't handle complex arguments through clang",) @@ -2167,6 +2187,89 @@ mod test { ); } + #[test] + fn test_parse_arguments_cxx20_modules_unsupported() { + // C++20 modules are not yet supported in MSVC mode + + // /interface - indicates the input is a module interface + assert_eq!( + CompilerArguments::CannotCache("-interface", None), + parse_arguments(ovec!["-c", "foo.ixx", "-Fofoo.obj", "-interface"]) + ); + + // /internalPartition - indicates the input is an internal partition + assert_eq!( + CompilerArguments::CannotCache("-internalPartition", None), + parse_arguments(ovec!["-c", "foo.ixx", "-Fofoo.obj", "-internalPartition"]) + ); + + // /ifcOutput - specifies output path for IFC (module interface) + assert_eq!( + CompilerArguments::CannotCache("-ifcOutput", None), + parse_arguments(ovec![ + "-c", + "foo.ixx", + "-Fofoo.obj", + "-ifcOutput", + "foo.ifc" + ]) + ); + + // /ifcOnly - only produce IFC, no object file + assert_eq!( + CompilerArguments::CannotCache("-ifcOnly", None), + parse_arguments(ovec!["-c", "foo.ixx", "-ifcOnly"]) + ); + + // /ifcSearchDir - directory to search for IFC files + assert_eq!( + CompilerArguments::CannotCache("-ifcSearchDir", None), + parse_arguments(ovec![ + "-c", + "foo.cpp", + "-Fofoo.obj", + "-ifcSearchDir", + "/path/to/ifcs" + ]) + ); + + // /reference - reference a named module IFC + assert_eq!( + CompilerArguments::CannotCache("-reference", None), + parse_arguments(ovec![ + "-c", + "foo.cpp", + "-Fofoo.obj", + "-reference", + "mymodule=mymodule.ifc" + ]) + ); + + // /stdIfcDir - directory for standard library IFCs + assert_eq!( + CompilerArguments::CannotCache("-stdIfcDir", None), + parse_arguments(ovec![ + "-c", + "foo.cpp", + "-Fofoo.obj", + "-stdIfcDir", + "/path/to/std/ifcs" + ]) + ); + + // /ifcMap - specifies a module map file + assert_eq!( + CompilerArguments::CannotCache("-ifcMap", None), + parse_arguments(ovec![ + "-c", + "foo.cpp", + "-Fofoo.obj", + "-ifcMap", + "module.map" + ]) + ); + } + #[test] fn test_responsefile_missing() { assert_eq!( diff --git a/tests/cmake-modules/CMakeLists.txt b/tests/cmake-modules/CMakeLists.txt new file mode 100644 index 0000000000..039af1d8a1 --- /dev/null +++ b/tests/cmake-modules/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 3.28) + +project(myproject LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_CXX_SCAN_FOR_MODULES ON) + +add_library(mymodule OBJECT) +target_sources(mymodule PUBLIC FILE_SET CXX_MODULES FILES mymodule.cppm) + +add_library(myproject OBJECT main.cpp) +target_link_libraries(myproject PRIVATE mymodule) diff --git a/tests/cmake-modules/main.cpp b/tests/cmake-modules/main.cpp new file mode 100644 index 0000000000..55e6c33d51 --- /dev/null +++ b/tests/cmake-modules/main.cpp @@ -0,0 +1,9 @@ +#include + +import mymodule; + +int main() { + print_hello_world(); + printf("add(1, 2) = %d\n", add(1, 2)); + return 0; +} diff --git a/tests/cmake-modules/mymodule.cppm b/tests/cmake-modules/mymodule.cppm new file mode 100644 index 0000000000..096d758a3d --- /dev/null +++ b/tests/cmake-modules/mymodule.cppm @@ -0,0 +1,13 @@ +module; + +#include + +export module mymodule; + +export void print_hello_world() { + std::cout << "Hello, World!" << std::endl; +} + +export int add(int a, int b) { + return a + b; +}