diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/cpp/BazelCppSemantics.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/cpp/BazelCppSemantics.java index 26539aa992ccce..32b1d9926841a0 100644 --- a/src/main/java/com/google/devtools/build/lib/bazel/rules/cpp/BazelCppSemantics.java +++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/cpp/BazelCppSemantics.java @@ -106,6 +106,7 @@ public void finalizeCompileActionBuilder( .setShouldScanIncludes( cppConfig.experimentalIncludeScanning() && featureConfiguration.getRequestedFeatures().contains("cc_include_scanning") + && !actionBuilder.getActionName().equals(CppActionNames.CPP_MODULE_DEPS_SCANNING) && !sourceFile.isFileType(CppFileTypes.ASSEMBLER) && !sourceFile.isFileType(CppFileTypes.CPP_MODULE)); } else { diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/ArtifactCategory.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/ArtifactCategory.java index 246f7be207c43c..f70de6650c249c 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/cpp/ArtifactCategory.java +++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/ArtifactCategory.java @@ -31,9 +31,7 @@ public enum ArtifactCategory { SERIALIZED_DIAGNOSTICS_FILE("", ".dia"), OBJECT_FILE("", ".o", ".obj"), PIC_OBJECT_FILE("", ".pic.o"), - CPP_MODULE("", ".pcm"), - CPP_MODULE_GCM("", ".gcm"), - CPP_MODULE_IFC("", ".ifc"), + CPP_MODULE("", ".pcm", ".gcm", ".ifc"), CPP_MODULES_INFO("", ".CXXModules.json"), CPP_MODULES_DDI("", ".ddi"), CPP_MODULES_MODMAP("", ".modmap"), diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCompilationContext.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCompilationContext.java index c51d3af1b0eb6f..f84ea8efb557ed 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCompilationContext.java +++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCompilationContext.java @@ -74,6 +74,10 @@ public final class CcCompilationContext implements CcCompilationContextApi transitiveModules; private final NestedSet transitivePicModules; + private final NestedSet moduleFiles; + private final NestedSet picModuleFiles; + private final NestedSet modulesInfoFiles; + private final NestedSet picModulesInfoFiles; + private final CppModuleMap cppModuleMap; // Derived from depsContexts. @@ -126,6 +135,10 @@ private CcCompilationContext( HeaderInfo headerInfo, NestedSet transitiveModules, NestedSet transitivePicModules, + NestedSet moduleFiles, + NestedSet picModuleFiles, + NestedSet modulesInfoFiles, + NestedSet picModulesInfoFiles, ImmutableList directModuleMaps, ImmutableList exportingModuleMaps, CppModuleMap cppModuleMap, @@ -139,6 +152,10 @@ private CcCompilationContext( this.headerInfo = headerInfo; this.transitiveModules = transitiveModules; this.transitivePicModules = transitivePicModules; + this.moduleFiles = moduleFiles; + this.picModuleFiles = picModuleFiles; + this.modulesInfoFiles = modulesInfoFiles; + this.picModulesInfoFiles = picModulesInfoFiles; this.cppModuleMap = cppModuleMap; this.nonCodeInputs = nonCodeInputs; this.virtualToOriginalHeaders = virtualToOriginalHeaders; @@ -153,6 +170,10 @@ public static CcCompilationContext create( HeaderInfo headerInfo, NestedSet transitiveModules, NestedSet transitivePicModules, + NestedSet moduleFiles, + NestedSet picModuleFiles, + NestedSet modulesInfoFiles, + NestedSet picModulesInfoFiles, ImmutableList directModuleMaps, ImmutableList exportingModuleMaps, CppModuleMap cppModuleMap, @@ -165,6 +186,10 @@ public static CcCompilationContext create( headerInfo, transitiveModules, transitivePicModules, + moduleFiles, + picModuleFiles, + modulesInfoFiles, + picModulesInfoFiles, directModuleMaps, exportingModuleMaps, cppModuleMap, @@ -196,6 +221,10 @@ public static CcCompilationContext createAndMerge( NestedSetBuilder nonCodeInputs = NestedSetBuilder.stableOrder(); NestedSetBuilder transitiveModules = NestedSetBuilder.stableOrder(); NestedSetBuilder transitivePicModules = NestedSetBuilder.stableOrder(); + NestedSetBuilder moduleFiles = NestedSetBuilder.stableOrder(); + NestedSetBuilder picModuleFiles = NestedSetBuilder.stableOrder(); + NestedSetBuilder modulesInfoFiles = NestedSetBuilder.stableOrder(); + NestedSetBuilder picModulesInfoFiles = NestedSetBuilder.stableOrder(); Set directModuleMaps = new LinkedHashSet<>(); Set exportingModuleMaps = new LinkedHashSet<>(); NestedSetBuilder virtualToOriginalHeaders = NestedSetBuilder.stableOrder(); @@ -231,6 +260,10 @@ public static CcCompilationContext createAndMerge( addIfNotNull(transitiveModules, otherCcCompilationContext.getHeaderInfo().getModule(false)); addIfNotNull( transitiveModules, otherCcCompilationContext.getHeaderInfo().getSeparateModule(false)); + moduleFiles.addTransitive(otherCcCompilationContext.getModuleFiles(false)); + picModuleFiles.addTransitive(otherCcCompilationContext.getModuleFiles(true)); + modulesInfoFiles.addTransitive(otherCcCompilationContext.getModulesInfoFiles(false)); + picModulesInfoFiles.addTransitive(otherCcCompilationContext.getModulesInfoFiles(true)); transitivePicModules.addTransitive(otherCcCompilationContext.getTransitiveModules(true)); addIfNotNull(transitivePicModules, otherCcCompilationContext.getHeaderInfo().getModule(true)); @@ -299,6 +332,10 @@ public static CcCompilationContext createAndMerge( headerInfo, transitiveModules.build(), transitivePicModules.build(), + moduleFiles.build(), + picModuleFiles.build(), + modulesInfoFiles.build(), + picModulesInfoFiles.build(), ImmutableList.copyOf(directModuleMaps), ImmutableList.copyOf(exportingModuleMaps), single.getCppModuleMap(), @@ -727,6 +764,34 @@ public NestedSet getTransitiveModules(boolean usePic) { return usePic ? transitivePicModules : transitiveModules; } + public NestedSet getModuleFiles(boolean usePic) { + return usePic ? picModuleFiles : moduleFiles; + } + + public NestedSet getModulesInfoFiles(boolean usePic) { + return usePic ? picModulesInfoFiles : modulesInfoFiles; + } + + @Override + public Depset getStarlarkModulesInfoFiles() { + return Depset.of(Artifact.class, getModulesInfoFiles(false)); + } + + @Override + public Depset getStarlarkPicModulesInfoFiles() { + return Depset.of(Artifact.class, getModulesInfoFiles(true)); + } + + @Override + public Depset getStarlarkModuleFiles() { + return Depset.of(Artifact.class, getModuleFiles(false)); + } + + @Override + public Depset getStarlarkPicModuleFiles() { + return Depset.of(Artifact.class, getModuleFiles(true)); + } + @Override public Depset getStarlarkTransitiveModules() { return Depset.of(Artifact.class, getTransitiveModules(false)); diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcModule.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcModule.java index e5af02597c65c5..cc094aa27a7b28 100755 --- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcModule.java +++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcModule.java @@ -275,6 +275,10 @@ public CcCompilationContext createCcCompilationContext( headerInfo, /* transitiveModules= */ NestedSetBuilder.emptySet(Order.STABLE_ORDER), /* transitivePicModules= */ NestedSetBuilder.emptySet(Order.STABLE_ORDER), + /* moduleFiles= */ NestedSetBuilder.emptySet(Order.STABLE_ORDER), + /* picModuleFiles= */ NestedSetBuilder.emptySet(Order.STABLE_ORDER), + /* modulesInfoFiles= */ NestedSetBuilder.emptySet(Order.STABLE_ORDER), + /* picModulesInfoFiles= */ NestedSetBuilder.emptySet(Order.STABLE_ORDER), /* directModuleMaps= */ ImmutableList.of(), /* exportingModuleMaps= */ ImmutableList.of(), moduleMap instanceof CppModuleMap cppModuleMap ? cppModuleMap : null, diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcStarlarkInternal.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcStarlarkInternal.java index 8befa918a4c010..7dcea7ef490285 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcStarlarkInternal.java +++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcStarlarkInternal.java @@ -58,6 +58,7 @@ import com.google.devtools.build.lib.starlarkbuildapi.NativeComputedDefaultApi; import com.google.devtools.build.lib.vfs.PathFragment; import java.util.Objects; +import java.util.Map; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import javax.annotation.Nullable; @@ -482,7 +483,84 @@ public ImmutableList collectPerFileLtoBackendOpts( .flatMap(options -> options.stream()) .collect(toImmutableList()); } - +/** + * Returns a {@code CcCompilationContext} that is based on a given {@code CcCompilationContext}, + * with C++20 Module files and Modules info files in {@code ccOutputs} added. + */ + @StarlarkMethod( + name = "create_cc_compilation_context_with_cpp20_modules", + doc = + "Creates a CompilationContext based on an existing one, with C++20 Modules outputs", + parameters = { + @Param( + name = "cc_compilation_context", + doc = "The base CompilationContext.", + positional = false, + named = true), + @Param( + name = "cpp_module_files", + doc = "C++20 Module files", + positional = false, + named = true), + @Param( + name = "pic_cpp_module_files", + doc = "PIC C++20 Module files", + positional = false, + named = true), + @Param( + name = "cpp_modules_info_file", + doc = "C++20 Modules info file", + positional = false, + named = true, + allowedTypes = {@ParamType(type = Artifact.class), @ParamType(type = NoneType.class)}), + @Param( + name = "pic_cpp_modules_info_file", + doc = "PIC C++20 Modules info file", + positional = false, + named = true, + allowedTypes = {@ParamType(type = Artifact.class), @ParamType(type = NoneType.class)}), + }) + public CcCompilationContext createCcCompilationContextWithCpp20Modules( + CcCompilationContext ccCompilationContext, + Sequence cppModuleFiles, + Sequence picCppModuleFiles, + Object cppModulesInfoFile, + Object picCppModulesInfoFile + ) + throws EvalException { + var moduleFilesBuilder = + NestedSetBuilder.fromNestedSet(ccCompilationContext.getModuleFiles(/*pic= */false)) + .addAll(Sequence.cast(cppModuleFiles, Artifact.class, "cpp_module_files").getImmutableList()); + var picModuleFilesBuilder = + NestedSetBuilder.fromNestedSet(ccCompilationContext.getModuleFiles(/*pic= */true)) + .addAll(Sequence.cast(picCppModuleFiles, Artifact.class, "pic_cpp_module_files").getImmutableList()); + var modulesInfoFilesBuilder = + NestedSetBuilder.fromNestedSet(ccCompilationContext.getModulesInfoFiles(/*pic= */false)); + if (cppModulesInfoFile instanceof Artifact artifact) { + modulesInfoFilesBuilder.add(artifact); + } + var picModulesInfoFilesBuilder = + NestedSetBuilder.fromNestedSet(ccCompilationContext.getModulesInfoFiles(/*pic= */true)); + if (picCppModulesInfoFile instanceof Artifact artifact) { + picModulesInfoFilesBuilder.add(artifact); + } + return CcCompilationContext.create( + ccCompilationContext.getCommandLineCcCompilationContext(), + ccCompilationContext.getDeclaredIncludeSrcs(), + ccCompilationContext.getNonCodeInputs(), + ccCompilationContext.getHeaderInfo(), + ccCompilationContext.getTransitiveModules(false), + ccCompilationContext.getTransitiveModules(true), + moduleFilesBuilder.build(), + picModuleFilesBuilder.build(), + modulesInfoFilesBuilder.build(), + picModulesInfoFilesBuilder.build(), + ccCompilationContext.getDirectModuleMaps(), + ccCompilationContext.getExportingModuleMaps(), + ccCompilationContext.getCppModuleMap(), + ccCompilationContext.getVirtualToOriginalHeaders(), + ccCompilationContext.getHeaderTokens()); + } /** * Returns a {@code CcCompilationContext} that is based on a given {@code CcCompilationContext}, * with {@code extraHeaderTokens} added to the header tokens. @@ -519,6 +597,10 @@ public CcCompilationContext createCcCompilationContextWithExtraHeaderTokens( ccCompilationContext.getHeaderInfo(), ccCompilationContext.getTransitiveModules(false), ccCompilationContext.getTransitiveModules(true), + ccCompilationContext.getModuleFiles(false), + ccCompilationContext.getModuleFiles(true), + ccCompilationContext.getModulesInfoFiles(false), + ccCompilationContext.getModulesInfoFiles(true), ccCompilationContext.getDirectModuleMaps(), ccCompilationContext.getExportingModuleMaps(), ccCompilationContext.getCppModuleMap(), @@ -629,6 +711,20 @@ public String computeOutputNamePrefixDir(BuildConfigurationValue configuration, @Param(name = "lto_indexing_file", positional = false, named = true, defaultValue = "None"), @Param(name = "use_pic", positional = false, named = true, defaultValue = "False"), @Param(name = "compile_build_variables", positional = false, named = true), + @Param( + name = "action_name", + positional = false, + named = true, + allowedTypes = {@ParamType(type = String.class), @ParamType(type = NoneType.class)}, + defaultValue = "None"), + @Param(name = "module_files", positional = false, named = true, defaultValue = "None"), + @Param(name = "modmap_file", positional = false, named = true, defaultValue = "None"), + @Param(name = "modmap_input_file", positional = false, named = true, defaultValue = "None"), + @Param( + name = "additional_outputs", + positional = false, + named = true, + defaultValue = "[]"), }) public void createCppCompileAction( StarlarkRuleContext starlarkRuleContext, @@ -648,7 +744,13 @@ public void createCppCompileAction( Object dwoFile, Object ltoIndexingFile, boolean usePic, - CcToolchainVariables compileBuildVariables) + CcToolchainVariables compileBuildVariables, + Object actionName, + Object moduleFiles, + Object modmapFile, + Object modmapInputFile, + Sequence additionalOutputs + ) throws EvalException { CppCompileActionBuilder builder = createCppCompileActionBuilder( @@ -670,6 +772,17 @@ public void createCppCompileAction( ltoIndexingFile, usePic); builder.setVariables(compileBuildVariables); + if (actionName instanceof String actionNameString) { + builder.setActionName(actionNameString); + } + builder.setModuleFiles(Depset.noneableCast(moduleFiles, Artifact.class, "module_files")); + builder.setModmapFile(nullIfNone(modmapFile, Artifact.class)); + builder.setModmapInputFile(nullIfNone(modmapInputFile, Artifact.class)); + builder.setAdditionalOutputs(Sequence.cast( + additionalOutputs, + Artifact.class, + "additional_outputs" + ).getImmutableList()); semantics.finalizeCompileActionBuilder( configuration, featureConfigurationForStarlark.getFeatureConfiguration(), builder); try { diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileAction.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileAction.java index 169d9000b81c7b..74ed2cfa13b8df 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileAction.java +++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileAction.java @@ -107,6 +107,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.UUID; import java.util.function.Predicate; @@ -183,6 +184,8 @@ public class CppCompileAction extends AbstractAction implements IncludeScannable */ private Set usedModules; + private Set usedCpp20Modules; + private boolean inputsDiscovered = false; /** @@ -208,6 +211,9 @@ public class CppCompileAction extends AbstractAction implements IncludeScannable private ParamFileActionInput paramFileActionInput; @Nullable private final PathFragment paramFilePath; + private final NestedSet moduleFiles; + private final Artifact modmapInputFile; + /** * Creates a new action to compile C/C++ source files. * @@ -263,7 +269,9 @@ public class CppCompileAction extends AbstractAction implements IncludeScannable CppSemantics cppSemantics, ImmutableList builtInIncludeDirectories, @Nullable Artifact grepIncludes, - ImmutableList additionalOutputs) { + ImmutableList additionalOutputs, + NestedSet moduleFiles, + Artifact modmapInputFile) { super( owner, mandatoryInputs, @@ -274,7 +282,9 @@ public class CppCompileAction extends AbstractAction implements IncludeScannable gcnoFile, dwoFile, ltoIndexingFile, - additionalOutputs)); + additionalOutputs, + featureConfiguration, + actionName)); this.gcnoFile = gcnoFile; this.sourceFile = sourceFile; this.shareable = shareable; @@ -322,7 +332,12 @@ public class CppCompileAction extends AbstractAction implements IncludeScannable if (separateModule != null && !separateModule.equals(getPrimaryOutput())) { allowedDerivedInputsBuilder.add(separateModule); } + if (moduleFiles != null) { + allowedDerivedInputsBuilder.addTransitive(moduleFiles); + } this.allowedDerivedInputs = allowedDerivedInputsBuilder.build(); + this.moduleFiles = moduleFiles; + this.modmapInputFile = modmapInputFile; } /** Constructor for serialization. */ @@ -353,7 +368,9 @@ public class CppCompileAction extends AbstractAction implements IncludeScannable String actionName, FeatureConfiguration featureConfiguration, ImmutableList builtInIncludeDirectories, - @Nullable PathFragment paramFilePath) { + @Nullable PathFragment paramFilePath, + NestedSet moduleFiles, + Artifact modmapInputFile) { super(owner, mandatoryInputs, rawOutputs); this.gcnoFile = gcnoFile; this.sourceFile = sourceFile; @@ -378,6 +395,8 @@ public class CppCompileAction extends AbstractAction implements IncludeScannable this.featureConfiguration = featureConfiguration; this.builtInIncludeDirectories = builtInIncludeDirectories; this.paramFilePath = paramFilePath; + this.moduleFiles = moduleFiles; + this.modmapInputFile = modmapInputFile; } private static ImmutableSet collectOutputs( @@ -387,7 +406,9 @@ private static ImmutableSet collectOutputs( @Nullable Artifact gcnoFile, @Nullable Artifact dwoFile, @Nullable Artifact ltoIndexingFile, - ImmutableList additionalOutputs) { + ImmutableList additionalOutputs, + FeatureConfiguration featureConfiguration, + String actionName) { ImmutableSet.Builder outputs = ImmutableSet.builder(); outputs.add(outputFile); if (gcnoFile != null) { @@ -495,7 +516,10 @@ private void clearAdditionalInputs() { @Override public boolean discoversInputs() { - return shouldScanIncludes || getDotdFile() != null || shouldParseShowIncludes(); + return isCpp20ModuleCompilationAction(actionName) + || shouldScanIncludes + || getDotdFile() != null + || shouldParseShowIncludes(); } @Override @@ -617,7 +641,9 @@ public void prepareInputDiscovery() { @Override public NestedSet discoverInputs(ActionExecutionContext actionExecutionContext) throws ActionExecutionException, InterruptedException { - Preconditions.checkArgument(!sourceFile.isFileType(CppFileTypes.CPP_MODULE)); + Preconditions.checkArgument( + !sourceFile.isFileType(CppFileTypes.CPP_MODULE) + || isCpp20ModuleCompilationAction(actionName)); if (additionalInputs == null) { List options; @@ -639,6 +665,7 @@ public NestedSet discoverInputs(ActionExecutionContext actionExecution .getOptions(BuildLanguageOptions.class) .experimentalSiblingRepositoryLayout; if (!shouldScanIncludes) { + usedCpp20Modules = computeUsedCpp20Modules(actionExecutionContext); // When not actually doing include scanning, add all prunable headers to additionalInputs. // This is necessary because the inputs that can be pruned by .d file parsing must be // returned from discoverInputs() and they cannot be in mandatoryInputs. Thus, even with @@ -646,6 +673,7 @@ public NestedSet discoverInputs(ActionExecutionContext actionExecution additionalInputs = NestedSetBuilder.fromNestedSet(ccCompilationContext.getDeclaredIncludeSrcs()) .addTransitive(additionalPrunableHeaders) + .addAll(usedCpp20Modules) .build(); if (needsIncludeValidation) { verifyActionIncludePaths(systemIncludeDirs, siblingLayout); @@ -698,7 +726,8 @@ public NestedSet discoverInputs(ActionExecutionContext actionExecution NestedSetBuilder.fromNestedSet(additionalInputs) .addTransitive(requiredModules.transitivelyUsed()) .build(); - if (getPrimaryOutput().isFileType(CppFileTypes.CPP_MODULE)) { + if (getPrimaryOutput().isFileType(CppFileTypes.CPP_MODULE) + && !isCpp20ModuleCompilationAction(actionName)) { this.discoveredModules = requiredModules.transitivelyUsed(); } usedModules = null; @@ -1264,7 +1293,8 @@ public NestedSet getAllowedDerivedInputs() { @Override public synchronized void updateInputs(NestedSet inputs) { super.updateInputs(inputs); - if (getPrimaryOutput().isFileType(CppFileTypes.CPP_MODULE)) { + if (getPrimaryOutput().isFileType(CppFileTypes.CPP_MODULE) + && !isCpp20ModuleCompilationAction(actionName)) { discoveredModules = NestedSetBuilder.wrap( Order.STABLE_ORDER, @@ -1275,9 +1305,11 @@ public synchronized void updateInputs(NestedSet inputs) { @Override protected String getRawProgressMessage() { - return (actionName.equals(CppActionNames.CPP_HEADER_ANALYSIS) - ? "Header analysis for " - : "Compiling ") + return switch (actionName) { + case CppActionNames.CPP_HEADER_ANALYSIS -> "Header analysis for "; + case CppActionNames.CPP_MODULE_DEPS_SCANNING -> "Deps scanning for "; + default -> "Compiling "; + } + getSourceFile().prettyPrint(); } @@ -1546,6 +1578,13 @@ public ActionResult execute(ActionExecutionContext actionExecutionContext) pathMapper); dotDContents = null; // Garbage collect in-memory .d contents. + if (usedCpp20Modules != null) { + discoveredInputs = + NestedSetBuilder.stableOrder() + .addAll(usedCpp20Modules) + .addTransitive(discoveredInputs) + .build(); + } updateActionInputs(discoveredInputs); // hdrs_check: This cannot be switched off for C++ build actions, @@ -1604,6 +1643,41 @@ private boolean shouldParseShowIncludes() { return featureConfiguration.isEnabled(CppRuleClasses.PARSE_SHOWINCLUDES); } + /** Dynamically compute the dependencies of a compilation using C++20 modules. */ + private ImmutableSet computeUsedCpp20Modules( + ActionExecutionContext actionExecutionContext) throws ActionExecutionException { + if (!featureConfiguration.isEnabled(CppRuleClasses.CPP_MODULES)) { + return ImmutableSet.of(); + } + // Module dependency scanning only needs source and header files. + if (actionName.equals(CppActionNames.CPP_MODULE_DEPS_SCANNING)) { + return ImmutableSet.of(); + } + if (!isCpp20ModuleCompilationAction(actionName) + && !CppFileTypes.CPP_SOURCE.matches(sourceFile.getExecPath())) { + return ImmutableSet.of(); + } + ImmutableSet usedModulePaths; + try { + // Read the file paths as raw bytes, which matches Bazel's internal encoding of path strings + // (see StringEncoding). + usedModulePaths = + ImmutableSet.copyOf( + FileSystemUtils.readLinesAsLatin1( + actionExecutionContext.getInputPath(modmapInputFile))); + } catch (IOException e) { + String message = + String.format("failed to read modmap input: %s", modmapInputFile.getExecPathString()); + DetailedExitCode code = createDetailedExitCode(message, Code.MODMAP_INPUT_FILE_READ_FAILURE); + throw new ActionExecutionException(message, this, /* catastrophe= */ false, code); + } + // All module files referenced in the modmap input file are expected to be known modules. We + // delegate error reporting to the compiler by silently skipping over unknown files. + return moduleFiles.toList().stream() + .filter(moduleFile -> usedModulePaths.contains(moduleFile.getExecPathString())) + .collect(toImmutableSet()); + } + Spawn createSpawn(Path execRoot, Map clientEnv, PathMapper pathMapper) throws ActionExecutionException { // Intentionally not adding {@link CppCompileAction#inputsForInvalidation}, those are not needed @@ -1906,6 +1980,8 @@ static String actionNameToMnemonic( : CPP_COMPILE_MNEMONIC + suffix; case CppActionNames.CPP_HEADER_ANALYSIS: return "CppHeaderAnalysis"; + case CppActionNames.CPP_MODULE_DEPS_SCANNING: + return "CppDepsScanning"; default: return CPP_COMPILE_MNEMONIC; } @@ -2005,4 +2081,9 @@ private boolean isGenerateDotdFile(Artifact sourceArtifact) { return CppFileTypes.headerDiscoveryRequired(sourceArtifact) && !featureConfiguration.isEnabled(CppRuleClasses.PARSE_SHOWINCLUDES); } + + public static boolean isCpp20ModuleCompilationAction(String actionName) { + return actionName.equals(CppActionNames.CPP20_MODULE_COMPILE) + || actionName.equals(CppActionNames.CPP20_MODULE_CODEGEN); + } } diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileActionBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileActionBuilder.java index 122c4d4516018d..2c81ff259f2738 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileActionBuilder.java +++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileActionBuilder.java @@ -51,6 +51,9 @@ public final class CppCompileActionBuilder implements StarlarkValue { private Artifact sourceFile; private final NestedSetBuilder mandatoryInputsBuilder; private Artifact outputFile; + private Artifact modmapFile; + private Artifact modmapInputFile; + private NestedSet moduleFiles; private Artifact dwoFile; private Artifact ltoIndexingFile; private Artifact dotdFile; @@ -129,6 +132,9 @@ public CppCompileActionBuilder(CppCompileActionBuilder other) { this.ccToolchain = other.ccToolchain; this.actionName = other.actionName; this.additionalOutputs = other.additionalOutputs; + this.moduleFiles = other.moduleFiles; + this.modmapFile = other.modmapFile; + this.modmapInputFile = other.modmapInputFile; } @CanIgnoreReturnValue @@ -294,7 +300,9 @@ public CppCompileAction buildAndVerify() throws UnconfiguredActionConfigExceptio cppSemantics, getBuiltinIncludeDirectories(), ccToolchain.getGrepIncludes(), - additionalOutputs); + additionalOutputs, + moduleFiles, + modmapInputFile); } private ImmutableList getBuiltinIncludeFiles() throws EvalException { @@ -333,6 +341,9 @@ NestedSet buildMandatoryInputs() throws EvalException { realMandatoryInputsBuilder.addTransitive(ccCompilationContext.getDeclaredIncludeSrcs()); realMandatoryInputsBuilder.addTransitive(additionalPrunableHeaders); } + if (modmapFile != null) { + realMandatoryInputsBuilder.add(modmapFile).add(Preconditions.checkNotNull(modmapInputFile)); + } return realMandatoryInputsBuilder.build(); } @@ -535,6 +546,24 @@ public CppCompileActionBuilder setAdditionalPrunableHeaders( return this; } + @CanIgnoreReturnValue + public CppCompileActionBuilder setModmapFile(Artifact modmapFile) { + this.modmapFile = modmapFile; + return this; + } + + @CanIgnoreReturnValue + public CppCompileActionBuilder setModmapInputFile(Artifact modmapInputFile) { + this.modmapInputFile = modmapInputFile; + return this; + } + + @CanIgnoreReturnValue + public CppCompileActionBuilder setModuleFiles(NestedSet moduleFiles) { + this.moduleFiles = moduleFiles; + return this; + } + ImmutableList getBuiltinIncludeDirectories() throws EvalException { return ccToolchain.getBuiltInIncludeDirectories(); } diff --git a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/cpp/CcCompilationContextApi.java b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/cpp/CcCompilationContextApi.java index 8d4d86f2a7adc0..a7875048a990e5 100644 --- a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/cpp/CcCompilationContextApi.java +++ b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/cpp/CcCompilationContextApi.java @@ -137,6 +137,18 @@ public interface CcCompilationContextApi< structField = true) Depset getStarlarkValidationArtifacts(); + @StarlarkMethod(name = "_modules_info_files", structField = true, documented = false) + Depset getStarlarkModulesInfoFiles(); + + @StarlarkMethod(name = "_pic_modules_info_files", structField = true, documented = false) + Depset getStarlarkPicModulesInfoFiles(); + + @StarlarkMethod(name = "_module_files", structField = true, documented = false) + Depset getStarlarkModuleFiles(); + + @StarlarkMethod(name = "_pic_module_files", structField = true, documented = false) + Depset getStarlarkPicModuleFiles(); + @StarlarkMethod(name = "_transitive_modules", structField = true, documented = false) Depset getStarlarkTransitiveModules(); diff --git a/src/main/protobuf/failure_details.proto b/src/main/protobuf/failure_details.proto index aa4c206840956a..14dd68a9f0a1b4 100644 --- a/src/main/protobuf/failure_details.proto +++ b/src/main/protobuf/failure_details.proto @@ -1139,6 +1139,7 @@ message CppCompile { D_FILE_PARSE_FAILURE = 9 [(metadata) = { exit_code: 1 }]; COVERAGE_NOTES_CREATION_FAILURE = 10 [(metadata) = { exit_code: 1 }]; MODULE_EXPANSION_MISSING_DATA = 11 [(metadata) = { exit_code: 1 }]; + MODMAP_INPUT_FILE_READ_FAILURE = 12 [(metadata) = { exit_code: 36 }]; } Code code = 1; diff --git a/src/main/starlark/builtins_bzl/common/cc/compile/cc_compilation_outputs.bzl b/src/main/starlark/builtins_bzl/common/cc/compile/cc_compilation_outputs.bzl index 17801f76c53bfe..0c30b4cfd6a875 100644 --- a/src/main/starlark/builtins_bzl/common/cc/compile/cc_compilation_outputs.bzl +++ b/src/main/starlark/builtins_bzl/common/cc/compile/cc_compilation_outputs.bzl @@ -38,6 +38,10 @@ CcCompilationOutputsInfo = provider( "temps": '(() -> depset[File]) All artifacts that are created if "--save_temps" is true.', "_header_tokens": "(list[File]) All token .h.processed files created when preprocessing or parsing headers.", "_module_files": "(list[File]) All .pcm files built by the target.", + "cpp_module_files": "(list[File]) All .pcm files built by the target's C++20 Modules interface.", + "pic_cpp_module_files": "(list[File]) All .pic.pcm files built by the target's C++20 Modules interface.", + "cpp_modules_info_file": "(File) .CXXModules.json file created by the target.", + "pic_cpp_modules_info_file": "(File) .CXXModules.json file created by the target.", }, ) @@ -52,7 +56,11 @@ def create_compilation_outputs_internal( pic_gcno_files = [], temps = [], header_tokens = [], - module_files = []): + module_files = [], + cpp_module_files = [], + pic_cpp_module_files = [], + cpp_modules_info_file = None, + pic_cpp_modules_info_file = None): """Creates a CcCompilationOutputsInfo provider. Args: @@ -66,6 +74,10 @@ def create_compilation_outputs_internal( temps: A depset of temporary files. header_tokens: A list of header tokens. module_files: A list of module files. + cpp_module_files: A list of C++ module files. + pic_cpp_module_files: A list of PIC C++ module files. + cpp_modules_info_file: A C++ modules info file. + pic_cpp_modules_info_file: A PIC C++ modules info file. Returns: A CcCompilationOutputsInfo provider. @@ -83,6 +95,10 @@ def create_compilation_outputs_internal( _pic_gcno_files = cc_internal.freeze(pic_gcno_files), _dwo_files = cc_internal.freeze(dwo_files), _pic_dwo_files = cc_internal.freeze(pic_dwo_files), + cpp_module_files = cc_internal.freeze(cpp_module_files), + pic_cpp_module_files = cc_internal.freeze(pic_cpp_module_files), + cpp_modules_info_file = cpp_modules_info_file, + pic_cpp_modules_info_file = pic_cpp_modules_info_file, ) EMPTY_COMPILATION_OUTPUTS = create_compilation_outputs_internal() diff --git a/src/main/starlark/builtins_bzl/common/cc/compile/compile.bzl b/src/main/starlark/builtins_bzl/common/cc/compile/compile.bzl index b472b607cde7bc..cfc62554f58b5b 100644 --- a/src/main/starlark/builtins_bzl/common/cc/compile/compile.bzl +++ b/src/main/starlark/builtins_bzl/common/cc/compile/compile.bzl @@ -17,6 +17,7 @@ The cc_common.compile function. Used for C++ compiling. """ +load(":common/cc/action_names.bzl", "ACTION_NAMES") load( ":common/cc/cc_helper_internal.bzl", "CPP_SOURCE_TYPE_CLIF_INPUT_PROTO", @@ -234,6 +235,12 @@ def compile( if not generate_pic_action and not generate_no_pic_action: fail("Either PIC or no PIC actions have to be created.") + if module_interfaces and not feature_configuration.is_enabled("cpp_modules"): + fail("to use C++20 Modules, the feature cpp_modules must be enabled") + if module_interfaces and len(module_interfaces) > 1: + fail("module_interfaces must be a list of files with exactly one file " + + "due to implementation limitation. see https://github.com/bazelbuild/bazel/pull/22553") + language_normalized = "c++" if language == None else language language_normalized = language_normalized.replace("+", "p").upper() source_category = SOURCE_CATEGORY_CC if language_normalized == "CPP" else SOURCE_CATEGORY_CC_AND_OBJC @@ -243,6 +250,7 @@ def compile( textual_hdrs_list = textual_hdrs.to_list() if type(textual_hdrs) == "depset" else textual_hdrs compilation_unit_sources = {} + module_interfaces_sources = {} if (feature_configuration.is_enabled("parse_headers") and not feature_configuration.is_enabled("header_modules")): public_hdrs_with_labels = _to_file_label_tuple_list(public_hdrs, label) @@ -261,6 +269,11 @@ def compile( srcs_with_labels, source_category, ) + module_interfaces_with_labels = _to_file_label_tuple_list(module_interfaces, label) + _add_suitable_srcs_to_module_interfaces_sources( + module_interfaces_sources, + module_interfaces_with_labels, + ) public_hdrs_artifacts = _to_file_list(public_hdrs) private_hdrs_artifacts = _to_file_list(private_hdrs) @@ -332,14 +345,20 @@ def compile( "pic_gcno_files": [], "dwo_files": [], "pic_dwo_files": [], + "cpp_module_files": [], + "pic_cpp_module_files": [], + "cpp_modules_info_file": None, + "pic_cpp_modules_info_file": None, } _create_cc_compile_actions( + actions = actions, action_construction_context = ctx, additional_compilation_inputs = additional_inputs, additional_include_scanning_roots = additional_include_scanning_roots, cc_compilation_context = cc_compilation_context, cc_toolchain = cc_toolchain, compilation_unit_sources = compilation_unit_sources, + module_interfaces_sources = module_interfaces_sources, configuration = ctx.configuration, conlyopts = conly_flags, copts = user_compile_flags, @@ -367,7 +386,14 @@ def compile( objects = compilation_outputs_dict["lto_compilation_context"], ) compilation_outputs = create_compilation_outputs_internal(**compilation_outputs_dict) - + if feature_configuration.is_enabled("cpp_modules"): + public_compilation_context = cc_internal.create_cc_compilation_context_with_cpp20_modules( + cc_compilation_context = public_compilation_context, + cpp_module_files = compilation_outputs.cpp_module_files, + pic_cpp_module_files = compilation_outputs.pic_cpp_module_files, + cpp_modules_info_file = compilation_outputs.cpp_modules_info_file, + pic_cpp_modules_info_file = compilation_outputs.pic_cpp_modules_info_file, + ) if cpp_configuration.process_headers_in_dependencies(): compilation_context = cc_internal.create_cc_compilation_context_with_extra_header_tokens( cc_compilation_context = public_compilation_context, @@ -416,6 +442,17 @@ def _add_suitable_srcs_to_compilation_unit_sources( type = CPP_SOURCE_TYPE_CLIF_INPUT_PROTO if "." + source.extension in extensions.CLIF_INPUT_PROTO else CPP_SOURCE_TYPE_SOURCE, ) +def _add_suitable_srcs_to_module_interfaces_sources( + module_interfaces_sources, + module_interfaces_with_labels): + """Adds CPP_SOURCE_TYPE_SOURCE to module_interfaces_sources""" + for source, label in module_interfaces_with_labels: + module_interfaces_sources[source] = _CppSourceInfo( + label = label, + source = source, + type = CPP_SOURCE_TYPE_SOURCE, + ) + def _to_file_list(list_of_files_or_tuples): """Converts a list that may contain File objects or tuples into a list of File objects. @@ -462,14 +499,583 @@ def _should_provide_header_modules( (private_headers or public_headers) ) +def _create_scan_deps_action( + *, + action_construction_context, + additional_compilation_inputs, + additional_include_scanning_roots, + cc_compilation_context, + cc_toolchain, + configuration, + conlyopts, + copts, + copts_filter, + cpp_configuration, # Note: this is from the cc_toolchain, and is NOT the same as ctx.fragments.cpp + cxxopts, + feature_configuration, + label, + common_toolchain_variables, + language, + native_cc_semantics, + source_artifact, + source_label, + use_pic, + ddi_file, + ddi_output_name): + dotd_file = None + if ( + dotd_files_enabled(native_cc_semantics, configuration, feature_configuration) and + _use_dotd_file(feature_configuration, source_artifact) + ): + dotd_file = _get_compile_output_file( + action_construction_context, + label, + configuration = configuration, + output_name = cc_internal.get_artifact_name_for_category( + cc_toolchain = cc_toolchain, + category = artifact_category.INCLUDED_FILE_LIST, + output_name = ddi_output_name, + ), + ) + specific_compile_build_variables = get_specific_compile_build_variables( + feature_configuration, + use_pic = use_pic, + source_file = source_artifact, + output_file = ddi_file, + dotd_file = dotd_file, + cpp_module_map = cc_compilation_context._module_map, + direct_module_maps = cc_compilation_context._direct_module_maps, + user_compile_flags = get_copts( + language = language, + cpp_configuration = cpp_configuration, + source_file = source_artifact, + conlyopts = conlyopts, + copts = copts, + cxxopts = cxxopts, + label = source_label, + ), + ) + compile_variables = cc_internal.combine_cc_toolchain_variables( + common_toolchain_variables, + specific_compile_build_variables, + ) + cc_internal.create_cpp_compile_action( + action_construction_context = action_construction_context, + cc_compilation_context = cc_compilation_context, + cc_toolchain = cc_toolchain, + configuration = configuration, + copts_filter = copts_filter, + feature_configuration = feature_configuration, + cpp_semantics = native_cc_semantics, + source_artifact = source_artifact, + additional_compilation_inputs = additional_compilation_inputs, + output_file = ddi_file, + dotd_file = dotd_file, + compile_build_variables = compile_variables, + action_name = ACTION_NAMES.cpp_module_deps_scanning, + ) + +def _create_aggregate_ddi_action( + *, + actions, + cc_toolchain, + ddi_files, + direct_module_files, + transitive_modules_info_files, + modules_info_file): + aggregate_ddi_tool = cc_toolchain._aggregate_ddi + if aggregate_ddi_tool == None: + fail("the 'aggregate_ddi' tool is not defined in the C++ toolchain") + + args = actions.args() + args.add_all(ddi_files, before_each = "-d") + args.add_all(direct_module_files, before_each = "-f") + args.add_all(transitive_modules_info_files, before_each = "-m") + args.add("-o", modules_info_file) + actions.run( + outputs = [modules_info_file], + # direct_module_files are not read by the tool and would cause a dependency cycle if added to inputs. + inputs = depset(ddi_files, transitive = [transitive_modules_info_files]), + executable = aggregate_ddi_tool, + arguments = [args], + mnemonic = "CppAggregateDdi", + progress_message = "Generating C++20 modules info %{output}", + toolchain = None, + ) + +def _create_gen_modmap_action( + *, + actions, + cc_toolchain, + ddi_file, + modules_info_file, + modmap_file, + modmap_input_file): + gen_modmap_tool = cc_toolchain.generate_modmap + if gen_modmap_tool == None: + fail("the 'generate_modmap' tool is not defined in the C++ toolchain") + args = actions.args() + args.add(cc_toolchain.compiler) + args.add(ddi_file) + args.add(modules_info_file) + args.add(modmap_file) + actions.run( + outputs = [modmap_file, modmap_input_file], + inputs = [ddi_file, modules_info_file], + executable = gen_modmap_tool, + arguments = [args], + mnemonic = "CppGenModmap", + progress_message = "Generating C++20 modules modmap %{output}", + execution_requirements = {"internal-inline-outputs": modmap_input_file.path}, + toolchain = None, + ) + +def _create_cc_compile_actions_with_cpp20_module_helper( + *, + actions, + action_construction_context, + additional_compilation_inputs, + additional_include_scanning_roots, + cc_compilation_context, + cc_toolchain, + compilation_unit_sources, + module_interfaces_sources, + configuration, + conlyopts, + copts, + copts_filter, + cpp_configuration, + cxxopts, + fdo_context, + feature_configuration, + is_code_coverage_enabled, + label, + private_headers, + public_headers, + purpose, + language, + outputs, + common_compile_build_variables, + auxiliary_fdo_inputs, + fdo_build_variables, + use_pic, + output_name_map): + direct_module_files = [] + source_to_module_file_map = {} + source_to_ddi_file_map = {} + modules_info_file = _get_compile_output_file( + action_construction_context, + label, + configuration = configuration, + output_name = cc_internal.get_artifact_name_for_category( + cc_toolchain = cc_toolchain, + category = artifact_category.CPP_MODULES_INFO, + output_name = label.name, + ), + ) + if use_pic: + outputs["pic_cpp_modules_info_file"] = modules_info_file + else: + outputs["cpp_modules_info_file"] = modules_info_file + + native_cc_semantics = cc_common_internal.get_cpp_semantics(language = language) + for cpp_source in module_interfaces_sources.values(): + source_artifact = cpp_source.file + output_name = output_name_map[source_artifact] + source_label = cpp_source.label + + if use_pic: + output_name_base = cc_internal.get_artifact_name_for_category( + cc_toolchain = cc_toolchain, + category = artifact_category.PIC_FILE, + output_name = output_name, + ) + else: + output_name_base = output_name + + ouptut_category = artifact_category.CPP_MODULE + module_file = _get_compile_output_file( + action_construction_context, + label, + configuration = configuration, + output_name = cc_internal.get_artifact_name_for_category( + cc_toolchain = cc_toolchain, + category = ouptut_category, + output_name = output_name_base, + ), + ) + direct_module_files.append(module_file) + source_to_module_file_map[source_artifact] = module_file + + # dependencies information are put in .ddi file + # the format is https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p1689r5.html + ddi_output_name = cc_internal.get_artifact_name_for_category( + cc_toolchain = cc_toolchain, + category = artifact_category.CPP_MODULES_DDI, + output_name = output_name_base, + ) + ddi_file = _get_compile_output_file( + action_construction_context, + label, + configuration = configuration, + output_name = ddi_output_name, + ) + _create_scan_deps_action( + action_construction_context = action_construction_context, + additional_compilation_inputs = additional_compilation_inputs, + additional_include_scanning_roots = additional_include_scanning_roots, + cc_compilation_context = cc_compilation_context, + cc_toolchain = cc_toolchain, + configuration = configuration, + conlyopts = conlyopts, + copts = copts, + copts_filter = copts_filter, + cpp_configuration = cpp_configuration, + cxxopts = cxxopts, + feature_configuration = feature_configuration, + label = label, + common_toolchain_variables = common_compile_build_variables, + language = language, + native_cc_semantics = native_cc_semantics, + source_artifact = source_artifact, + source_label = source_label, + use_pic = use_pic, + ddi_file = ddi_file, + ddi_output_name = ddi_output_name, + ) + source_to_ddi_file_map[source_artifact] = ddi_file + + _create_aggregate_ddi_action( + actions = actions, + cc_toolchain = cc_toolchain, + ddi_files = source_to_ddi_file_map.values(), + direct_module_files = direct_module_files, + transitive_modules_info_files = cc_compilation_context._pic_modules_info_files if use_pic else cc_compilation_context._modules_info_files, + modules_info_file = modules_info_file, + ) + compiled_basenames = set() + transitive_module_files = cc_compilation_context._pic_module_files if use_pic else cc_compilation_context._module_files + for cpp_source in module_interfaces_sources.values(): + source_artifact = cpp_source.file + output_name = output_name_map[source_artifact] + source_label = cpp_source.label + bitcode_output = feature_configuration.is_enabled("thin_lto") and (("." + source_artifact.extension) in LTO_SOURCE_EXTENSIONS) + module_file = source_to_module_file_map[source_artifact] + if use_pic: + output_name_base = cc_internal.get_artifact_name_for_category( + cc_toolchain = cc_toolchain, + category = artifact_category.PIC_FILE, + output_name = output_name, + ) + else: + output_name_base = output_name + if use_pic: + outputs["pic_cpp_module_files"].append(module_file) + else: + outputs["cpp_module_files"].append(module_file) + + modmap_file = _get_compile_output_file( + action_construction_context, + label, + configuration = configuration, + output_name = cc_internal.get_artifact_name_for_category( + cc_toolchain = cc_toolchain, + category = artifact_category.CPP_MODULES_MODMAP, + output_name = output_name_base, + ), + ) + modmap_input_file = _get_compile_output_file( + action_construction_context, + label, + configuration = configuration, + output_name = cc_internal.get_artifact_name_for_category( + cc_toolchain = cc_toolchain, + category = artifact_category.CPP_MODULES_MODMAP_INPUT, + output_name = output_name_base, + ), + ) + _create_gen_modmap_action( + actions = actions, + cc_toolchain = cc_toolchain, + ddi_file = source_to_ddi_file_map[source_artifact], + modules_info_file = modules_info_file, + modmap_file = modmap_file, + modmap_input_file = modmap_input_file, + ) + all_other_module_files = depset( + [m for m in direct_module_files if m != module_file], + transitive = [transitive_module_files], + ) + compiled_basenames.add(_basename_without_extension(source_artifact)) + + _create_compile_source_action( + action_construction_context = action_construction_context, + cc_compilation_context = cc_compilation_context, + label = label, + source_label = source_label, + source_artifact = source_artifact, + output_name = output_name, + outputs = outputs, + cc_toolchain = cc_toolchain, + feature_configuration = feature_configuration, + configuration = configuration, + cpp_configuration = cpp_configuration, + cpp_semantics = native_cc_semantics, + language = language, + conlyopts = conlyopts, + copts = copts, + cxxopts = cxxopts, + copts_filter = copts_filter, + common_compile_variables = common_compile_build_variables, + fdo_build_variables = fdo_build_variables, + output_category = artifact_category.CLIF_OUTPUT_PROTO if cpp_source.type == CPP_SOURCE_TYPE_CLIF_INPUT_PROTO else artifact_category.OBJECT_FILE, + cpp_module_map = cc_compilation_context._module_map, + add_object = True, + enable_coverage = is_code_coverage_enabled, + generate_dwo = should_create_per_object_debug_info(feature_configuration, cpp_configuration), + bitcode_output = bitcode_output, + fdo_context = fdo_context, + auxiliary_fdo_inputs = auxiliary_fdo_inputs, + additional_compilation_inputs = additional_compilation_inputs, + additional_include_scanning_roots = additional_include_scanning_roots, + use_pic = use_pic, + additional_build_variables = { + "cpp_module_output_file": module_file, + "cpp_module_modmap_file": modmap_file, + }, + action_name = ACTION_NAMES.cpp20_module_compile, + additional_outputs = [module_file], + module_files = all_other_module_files, + modmap_file = modmap_file, + modmap_input_file = modmap_input_file, + ) + + all_module_files = depset(direct_module_files, transitive = [transitive_module_files]) + for cpp_source in compilation_unit_sources.values(): + source_artifact = cpp_source.file + output_name = output_name_map[source_artifact] + source_label = cpp_source.label + bitcode_output = feature_configuration.is_enabled("thin_lto") and (("." + source_artifact.extension) in LTO_SOURCE_EXTENSIONS) + + if use_pic: + output_name_base = cc_internal.get_artifact_name_for_category( + cc_toolchain = cc_toolchain, + category = artifact_category.PIC_FILE, + output_name = output_name, + ) + else: + output_name_base = output_name + + additional_build_variables = {} + modmap_file = None + modmap_input_file = None + + # Only C++ compilation unit will be compiled with C++20 Modules. + if "." + source_artifact.extension in extensions.CC_SOURCE: + modmap_file = _get_compile_output_file( + action_construction_context, + label, + configuration = configuration, + output_name = cc_internal.get_artifact_name_for_category( + cc_toolchain = cc_toolchain, + category = artifact_category.CPP_MODULES_MODMAP, + output_name = output_name_base, + ), + ) + modmap_input_file = _get_compile_output_file( + action_construction_context, + label, + configuration = configuration, + output_name = cc_internal.get_artifact_name_for_category( + cc_toolchain = cc_toolchain, + category = artifact_category.CPP_MODULES_MODMAP_INPUT, + output_name = output_name_base, + ), + ) + + ddi_output_name = cc_internal.get_artifact_name_for_category( + cc_toolchain = cc_toolchain, + category = artifact_category.CPP_MODULES_DDI, + output_name = output_name_base, + ) + ddi_file = _get_compile_output_file( + action_construction_context, + label, + configuration = configuration, + output_name = ddi_output_name, + ) + _create_scan_deps_action( + action_construction_context = action_construction_context, + additional_compilation_inputs = additional_compilation_inputs, + additional_include_scanning_roots = additional_include_scanning_roots, + cc_compilation_context = cc_compilation_context, + cc_toolchain = cc_toolchain, + configuration = configuration, + conlyopts = conlyopts, + copts = copts, + copts_filter = copts_filter, + cpp_configuration = cpp_configuration, + cxxopts = cxxopts, + feature_configuration = feature_configuration, + label = label, + common_toolchain_variables = common_compile_build_variables, + language = language, + native_cc_semantics = native_cc_semantics, + source_artifact = source_artifact, + source_label = source_label, + use_pic = use_pic, + ddi_file = ddi_file, + ddi_output_name = ddi_output_name, + ) + modmap_file = _get_compile_output_file( + action_construction_context, + label, + configuration = configuration, + output_name = cc_internal.get_artifact_name_for_category( + cc_toolchain = cc_toolchain, + category = artifact_category.CPP_MODULES_MODMAP, + output_name = output_name_base, + ), + ) + modmap_input_file = _get_compile_output_file( + action_construction_context, + label, + configuration = configuration, + output_name = cc_internal.get_artifact_name_for_category( + cc_toolchain = cc_toolchain, + category = artifact_category.CPP_MODULES_MODMAP_INPUT, + output_name = output_name_base, + ), + ) + additional_build_variables["cpp_module_modmap_file"] = modmap_file + _create_gen_modmap_action( + actions = actions, + cc_toolchain = cc_toolchain, + ddi_file = ddi_file, + modules_info_file = modules_info_file, + modmap_file = modmap_file, + modmap_input_file = modmap_input_file, + ) + compiled_basenames.add(_basename_without_extension(source_artifact)) + _create_compile_source_action( + action_construction_context = action_construction_context, + cc_compilation_context = cc_compilation_context, + label = label, + source_label = source_label, + source_artifact = source_artifact, + output_name = output_name, + outputs = outputs, + cc_toolchain = cc_toolchain, + feature_configuration = feature_configuration, + configuration = configuration, + cpp_configuration = cpp_configuration, + cpp_semantics = native_cc_semantics, + language = language, + conlyopts = conlyopts, + copts = copts, + cxxopts = cxxopts, + copts_filter = copts_filter, + common_compile_variables = common_compile_build_variables, + fdo_build_variables = fdo_build_variables, + output_category = artifact_category.CLIF_OUTPUT_PROTO if cpp_source.type == CPP_SOURCE_TYPE_CLIF_INPUT_PROTO else artifact_category.OBJECT_FILE, + cpp_module_map = cc_compilation_context._module_map, + add_object = True, + enable_coverage = is_code_coverage_enabled, + generate_dwo = should_create_per_object_debug_info(feature_configuration, cpp_configuration), + bitcode_output = bitcode_output, + fdo_context = fdo_context, + auxiliary_fdo_inputs = auxiliary_fdo_inputs, + additional_compilation_inputs = additional_compilation_inputs, + additional_include_scanning_roots = additional_include_scanning_roots, + use_pic = use_pic, + additional_build_variables = additional_build_variables, + module_files = all_module_files, + modmap_file = modmap_file, + modmap_input_file = modmap_input_file, + ) + + +def _create_cc_compile_actions_with_cpp20_module( + *, + actions, + action_construction_context, + additional_compilation_inputs, + additional_include_scanning_roots, + cc_compilation_context, + cc_toolchain, + compilation_unit_sources, + module_interfaces_sources, + configuration, + conlyopts, + copts, + copts_filter, + cpp_configuration, + cxxopts, + fdo_context, + feature_configuration, + generate_no_pic_action, + generate_pic_action, + is_code_coverage_enabled, + label, + private_headers, + public_headers, + purpose, + language, + outputs, + common_compile_build_variables, + auxiliary_fdo_inputs, + fdo_build_variables): + """Constructs the C++ compiler actions with C++20 modules support. + """ + output_name_prefix_dir = cc_internal.compute_output_name_prefix_dir(configuration = configuration, purpose = purpose) + output_name_map = _calculate_output_name_map_by_type(compilation_unit_sources | module_interfaces_sources, output_name_prefix_dir) + use_pic_values = [] + if generate_no_pic_action: + use_pic_values.append(False) + if generate_pic_action: + use_pic_values.append(True) + for use_pic in use_pic_values: + _create_cc_compile_actions_with_cpp20_module_helper( + actions = actions, + action_construction_context = action_construction_context, + additional_compilation_inputs = additional_compilation_inputs, + additional_include_scanning_roots = additional_include_scanning_roots, + cc_compilation_context = cc_compilation_context, + cc_toolchain = cc_toolchain, + compilation_unit_sources = compilation_unit_sources, + module_interfaces_sources = module_interfaces_sources, + configuration = configuration, + conlyopts = conlyopts, + copts = copts, + copts_filter = copts_filter, + cpp_configuration = cpp_configuration, + cxxopts = cxxopts, + fdo_context = fdo_context, + feature_configuration = feature_configuration, + is_code_coverage_enabled = is_code_coverage_enabled, + label = label, + private_headers = private_headers, + public_headers = public_headers, + purpose = purpose, + language = language, + outputs = outputs, + common_compile_build_variables = common_compile_build_variables, + auxiliary_fdo_inputs = auxiliary_fdo_inputs, + fdo_build_variables = fdo_build_variables, + use_pic = use_pic, + output_name_map = output_name_map, + ) + def _create_cc_compile_actions( *, + actions, action_construction_context, additional_compilation_inputs, additional_include_scanning_roots, cc_compilation_context, cc_toolchain, compilation_unit_sources, + module_interfaces_sources, configuration, conlyopts, copts, @@ -503,6 +1109,40 @@ def _create_cc_compile_actions( native_cc_semantics = cc_common_internal.get_cpp_semantics(language = language) + # If C++20 modules are enabled, delegate to the module-aware implementation and return early. + if feature_configuration.is_enabled("cpp_modules"): + _create_cc_compile_actions_with_cpp20_module( + actions = actions, + action_construction_context = action_construction_context, + additional_compilation_inputs = additional_compilation_inputs, + additional_include_scanning_roots = additional_include_scanning_roots, + cc_compilation_context = cc_compilation_context, + cc_toolchain = cc_toolchain, + compilation_unit_sources = compilation_unit_sources, + module_interfaces_sources = module_interfaces_sources, + configuration = configuration, + conlyopts = conlyopts, + copts = copts, + copts_filter = copts_filter, + cpp_configuration = cpp_configuration, + cxxopts = cxxopts, + fdo_context = fdo_context, + feature_configuration = feature_configuration, + generate_no_pic_action = generate_no_pic_action, + generate_pic_action = generate_pic_action, + is_code_coverage_enabled = is_code_coverage_enabled, + label = label, + private_headers = private_headers, + public_headers = public_headers, + purpose = purpose, + language = language, + outputs = outputs, + common_compile_build_variables = common_compile_build_variables, + auxiliary_fdo_inputs = auxiliary_fdo_inputs, + fdo_build_variables = fdo_build_variables, + ) + return + if _should_provide_header_modules(feature_configuration, private_headers, public_headers): cpp_module_map = cc_compilation_context._module_map module_map_label = Label(cpp_module_map.name()) @@ -889,7 +1529,13 @@ def _create_compile_source_action( auxiliary_fdo_inputs, additional_compilation_inputs, additional_include_scanning_roots, - use_pic): + use_pic, + additional_build_variables = {}, + action_name = None, + additional_outputs = [], + module_files = depset(), + modmap_file = None, + modmap_input_file = None): output_pic_nopic_name = output_name if use_pic: output_pic_nopic_name = cc_internal.get_artifact_name_for_category( @@ -985,7 +1631,7 @@ def _create_compile_source_action( feature_configuration = feature_configuration, direct_module_maps = cc_compilation_context._direct_module_maps, fdo_build_variables = fdo_build_variables, - additional_build_variables = {}, + additional_build_variables = additional_build_variables, ) temp_action_outputs = _create_temps_action( @@ -1038,6 +1684,11 @@ def _create_compile_source_action( dwo_file = dwo_file, use_pic = use_pic, lto_indexing_file = lto_indexing_file, + action_name = action_name, + additional_outputs = additional_outputs, + module_files = module_files, + modmap_file = modmap_file, + modmap_input_file = modmap_input_file, compile_build_variables = cc_internal.combine_cc_toolchain_variables( common_compile_variables, compile_variables, diff --git a/src/main/starlark/builtins_bzl/common/cc/semantics.bzl b/src/main/starlark/builtins_bzl/common/cc/semantics.bzl index 1833a4002c7d61..7b67e583a5a956 100644 --- a/src/main/starlark/builtins_bzl/common/cc/semantics.bzl +++ b/src/main/starlark/builtins_bzl/common/cc/semantics.bzl @@ -140,6 +140,7 @@ def _cpp_modules_tools(): "generate_modmap": attr.label( executable = True, cfg = "exec", + default = "@" + _get_repo() + "//tools/cpp:generate-modmap", ), } diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/BUILD b/src/test/java/com/google/devtools/build/lib/rules/cpp/BUILD index 6ffd70ff09d784..b4ff92944f1c8c 100644 --- a/src/test/java/com/google/devtools/build/lib/rules/cpp/BUILD +++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/BUILD @@ -721,16 +721,3 @@ java_test( "//third_party:truth", ], ) - -java_test( - name = "CppModulesConfiguredTargetTest", - srcs = ["CppModulesConfiguredTargetTest.java"], - deps = [ - "//src/main/java/com/google/devtools/build/lib/rules/cpp", - "//src/test/java/com/google/devtools/build/lib/analysis/util", - "//src/test/java/com/google/devtools/build/lib/packages:testutil", - "//third_party:guava", - "//third_party:junit4", - "//third_party:truth", - ], -) diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/CppModulesConfiguredTargetTest.java b/src/test/java/com/google/devtools/build/lib/rules/cpp/CppModulesConfiguredTargetTest.java deleted file mode 100644 index cc21d32ef5c1e1..00000000000000 --- a/src/test/java/com/google/devtools/build/lib/rules/cpp/CppModulesConfiguredTargetTest.java +++ /dev/null @@ -1,283 +0,0 @@ -// Copyright 2024 The Bazel Authors. All rights reserved. -// -// 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. -package com.google.devtools.build.lib.rules.cpp; - -import static com.google.common.truth.Truth.assertThat; - -import com.google.common.collect.ImmutableSet; -import com.google.devtools.build.lib.analysis.util.AnalysisMock; -import com.google.devtools.build.lib.analysis.util.BuildViewTestCase; -import com.google.devtools.build.lib.packages.util.Crosstool; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@RunWith(JUnit4.class) -public class CppModulesConfiguredTargetTest extends BuildViewTestCase { - void useFeatures(String... args) throws Exception { - AnalysisMock.get() - .ccSupport() - .setupCcToolchainConfig( - mockToolsConfig, Crosstool.CcToolchainConfig.builder().withFeatures(args)); - } - - @Test - public void testCppModulesCcBinaryConfigurationNoFlags() throws Exception { - scratch.file( - "foo/BUILD", - """ - load("@rules_cc//cc:cc_library.bzl", "cc_library") - cc_library( - name = 'lib', - module_interfaces = ["foo.cppm"], - ) - """); - reporter.removeHandler(failFastHandler); - getConfiguredTarget("//foo:lib"); - assertContainsEvent("requires --experimental_cpp_modules"); - } - - @Test - public void testCppModulesCcLibraryConfigurationNoFlags() throws Exception { - scratch.file( - "foo/BUILD", - """ - load("@rules_cc//cc:cc_binary.bzl", "cc_binary") - cc_binary( - name = 'bin', - module_interfaces = ["foo.cppm"], - ) - """); - reporter.removeHandler(failFastHandler); - getConfiguredTarget("//foo:bin"); - assertContainsEvent("requires --experimental_cpp_modules"); - } - - @Test - public void testCppModulesCcTestConfigurationNoFlags() throws Exception { - scratch.file( - "foo/BUILD", - """ - load("@rules_cc//cc:cc_test.bzl", "cc_test") - cc_test( - name = 'test', - module_interfaces = ["foo.cppm"], - ) - """); - reporter.removeHandler(failFastHandler); - getConfiguredTarget("//foo:test"); - assertContainsEvent("requires --experimental_cpp_modules"); - } - - @Test - public void testCppModulesCcLibraryConfigurationNoFeatures() throws Exception { - scratch.file( - "foo/BUILD", - """ - load("@rules_cc//cc:cc_library.bzl", "cc_library") - cc_library( - name = 'lib', - module_interfaces = ["foo.cppm"], - ) - """); - useConfiguration("--experimental_cpp_modules"); - - reporter.removeHandler(failFastHandler); - getConfiguredTarget("//foo:lib"); - assertDoesNotContainEvent("requires --experimental_cpp_modules"); - assertContainsEvent("the feature cpp_modules must be enabled"); - } - - @Test - public void testCppModulesCcBinaryConfigurationNoFeatures() throws Exception { - scratch.file( - "foo/BUILD", - """ - load("@rules_cc//cc:cc_binary.bzl", "cc_binary") - cc_binary( - name = 'bin', - module_interfaces = ["foo.cppm"], - ) - """); - useConfiguration("--experimental_cpp_modules"); - - reporter.removeHandler(failFastHandler); - getConfiguredTarget("//foo:bin"); - assertDoesNotContainEvent("requires --experimental_cpp_modules"); - assertContainsEvent("the feature cpp_modules must be enabled"); - } - - @Test - public void testCppModulesCcTestConfigurationNoFeatures() throws Exception { - scratch.file( - "foo/BUILD", - """ - load("@rules_cc//cc:cc_test.bzl", "cc_test") - cc_test( - name = 'test', - module_interfaces = ["foo.cppm"], - ) - """); - useConfiguration("--experimental_cpp_modules"); - - reporter.removeHandler(failFastHandler); - getConfiguredTarget("//foo:test"); - assertDoesNotContainEvent("requires --experimental_cpp_modules"); - assertContainsEvent("the feature cpp_modules must be enabled"); - } - - @Test - public void testCppModulesCcLibraryConfigurationWithFeatures() throws Exception { - scratch.file( - "foo/BUILD", - """ - load("@rules_cc//cc:cc_library.bzl", "cc_library") - cc_library( - name = 'lib', - module_interfaces = ["foo.cppm"], - ) - """); - useFeatures(CppRuleClasses.CPP_MODULES); - useConfiguration("--experimental_cpp_modules", "--features=cpp_modules"); - - ImmutableSet features = getRuleContext(getConfiguredTarget("//foo:lib")).getFeatures(); - assertThat(features).contains("cpp_modules"); - assertDoesNotContainEvent("requires --experimental_cpp_modules"); - assertDoesNotContainEvent("the feature cpp_modules must be enabled"); - } - - @Test - public void testCppModulesCcBinaryConfigurationWithFeatures() throws Exception { - scratch.file( - "foo/BUILD", - """ - load("@rules_cc//cc:cc_binary.bzl", "cc_binary") - cc_binary( - name = 'bin', - module_interfaces = ["foo.cppm"], - ) - """); - useFeatures(CppRuleClasses.CPP_MODULES); - useConfiguration("--experimental_cpp_modules", "--features=cpp_modules"); - - ImmutableSet features = getRuleContext(getConfiguredTarget("//foo:bin")).getFeatures(); - assertThat(features).contains("cpp_modules"); - assertDoesNotContainEvent("requires --experimental_cpp_modules"); - assertDoesNotContainEvent("the feature cpp_modules must be enabled"); - } - - @Test - public void testCppModulesCcTestConfigurationWithFeatures() throws Exception { - scratch.file( - "foo/BUILD", - """ - load("@rules_cc//cc:cc_test.bzl", "cc_test") - cc_test( - name = 'test', - module_interfaces = ["foo.cppm"], - ) - """); - useFeatures(CppRuleClasses.CPP_MODULES); - useConfiguration("--experimental_cpp_modules", "--features=cpp_modules"); - - ImmutableSet features = getRuleContext(getConfiguredTarget("//foo:test")).getFeatures(); - assertThat(features).contains("cpp_modules"); - assertDoesNotContainEvent("requires --experimental_cpp_modules"); - assertDoesNotContainEvent("the feature cpp_modules must be enabled"); - } - - @Test - public void testSameModuleInterfacesFileInCcLibraryTwice() throws Exception { - scratch.file( - "a/BUILD", - """ - load("@rules_cc//cc:cc_library.bzl", "cc_library") - filegroup( - name = "a1", - srcs = ["a.cppm"], - ) - filegroup( - name = "a2", - srcs = ["a.cppm"], - ) - cc_library( - name = "lib", - module_interfaces = ["a1", "a2"], - ) - """); - - useFeatures(CppRuleClasses.CPP_MODULES); - useConfiguration("--experimental_cpp_modules", "--features=cpp_modules"); - - reporter.removeHandler(failFastHandler); - getConfiguredTarget("//a:lib"); - assertContainsEvent("Artifact '' is duplicated"); - } - - @Test - public void testSameModuleInterfacesFileInCcBinaryTwice() throws Exception { - scratch.file( - "a/BUILD", - """ - load("@rules_cc//cc:cc_binary.bzl", "cc_binary") - filegroup( - name = "a1", - srcs = ["a.cppm"], - ) - filegroup( - name = "a2", - srcs = ["a.cppm"], - ) - cc_binary( - name = "bin", - module_interfaces = ["a1", "a2"], - ) - """); - - useFeatures(CppRuleClasses.CPP_MODULES); - useConfiguration("--experimental_cpp_modules", "--features=cpp_modules"); - - reporter.removeHandler(failFastHandler); - getConfiguredTarget("//a:bin"); - assertContainsEvent("Artifact '' is duplicated"); - } - - @Test - public void testSameModuleInterfacesFileInCcTestTwice() throws Exception { - scratch.file( - "a/BUILD", - """ - load("@rules_cc//cc:cc_test.bzl", "cc_test") - filegroup( - name = "a1", - srcs = ["a.cppm"], - ) - filegroup( - name = "a2", - srcs = ["a.cppm"], - ) - cc_test( - name = "test", - module_interfaces = ["a1", "a2"], - ) - """); - - useFeatures(CppRuleClasses.CPP_MODULES); - useConfiguration("--experimental_cpp_modules", "--features=cpp_modules"); - - reporter.removeHandler(failFastHandler); - getConfiguredTarget("//a:test"); - assertContainsEvent("Artifact '' is duplicated"); - } -} diff --git a/src/test/shell/bazel/cc_integration_test.sh b/src/test/shell/bazel/cc_integration_test.sh index 7ba5429a5fc24e..54c716463561f4 100755 --- a/src/test/shell/bazel/cc_integration_test.sh +++ b/src/test/shell/bazel/cc_integration_test.sh @@ -2206,4 +2206,90 @@ EOF expect_log "Hello from main.cpp" } +function test_cpp20_modules_with_clang() { + type -P clang || return 0 + # Check if clang version is less than 17 + clang_version=$(clang --version | head -n1 | grep -oE '[0-9]+\.[0-9]+' | head -n1) + if [[ -n "$clang_version" ]]; then + major_version=$(echo "$clang_version" | cut -d. -f1) + if [[ "$major_version" -lt 17 ]]; then + return 0 + fi + fi + if [[ "$(uname -s)" == "Darwin" ]]; then + return 0 + fi + + add_rules_cc "MODULE.bazel" + + cat > BUILD.bazel <<'EOF' +load("@rules_cc//cc:defs.bzl", "cc_library", "cc_binary") + +package(features = ["cpp_modules"]) + +cc_library( + name = "base", + module_interfaces = ["base.cppm"], +) +cc_library( + name = "foo", + module_interfaces = ["foo.cppm"], + deps = [":base"] +) +cc_library( + name = "bar", + module_interfaces = ["bar.cppm"], + deps = [":base"] +) +cc_binary( + name = "main", + srcs = ["main.cc"], + deps = [":foo", ":bar"] +) +EOF + cat > main.cc <<'EOF' +import foo; +import bar; + +void f() { + f_foo(); + f_bar(); +} + +int main() { + f(); + return 0; +} +EOF + cat > foo.cppm <<'EOF' +export module foo; +import base; + +export void f_foo() { + f_base(); +} +EOF + cat > bar.cppm <<'EOF' +export module bar; +import base; + +export void f_bar() { + f_base(); +} +EOF + cat > base.cppm <<'EOF' +export module base; + +export void f_base() { +} +EOF + + bazel build //:main --experimental_cpp_modules --repo_env=CC=clang --copt=-std=c++20 --disk_cache=disk &> $TEST_log || fail "Expected build C++20 Modules success with compiler 'clang'" + + # Verify that the build can hit the cache without action cycles. + bazel clean || fail "Expected clean success" + bazel build //:main --experimental_cpp_modules --repo_env=CC=clang --copt=-std=c++20 --disk_cache=disk &> $TEST_log || fail "Expected build C++20 Modules success with compiler 'clang'" + expect_log "17 disk cache hit" +} + run_suite "cc_integration_test" diff --git a/tools/cpp/modules_tools/aggregate-ddi/main.cc b/tools/cpp/modules_tools/aggregate-ddi/main.cc index 951aab0c25f864..57ce39332e4b0f 100644 --- a/tools/cpp/modules_tools/aggregate-ddi/main.cc +++ b/tools/cpp/modules_tools/aggregate-ddi/main.cc @@ -27,8 +27,9 @@ int main(int argc, char *argv[]) { std::string arg = argv[i]; if (arg == "-m" && i + 1 < argc) { cpp20modules_info.emplace_back(argv[++i]); - } else if (arg == "-d" && i + 2 < argc) { + } else if (arg == "-d" && i + 1 < argc) { ddi.emplace_back(argv[++i]); + } else if (arg == "-f" && i + 1 < argc) { module_file.emplace_back(argv[++i]); } else if (arg == "-o" && i + 1 < argc) { output = argv[++i]; @@ -38,6 +39,11 @@ int main(int argc, char *argv[]) { std::exit(1); } } + if (ddi.size() != module_file.size()) { + std::cerr << "ERROR: The number of -d and -f arguments must match." + << std::endl; + std::exit(1); + } if (output.empty()) { std::cerr << "ERROR: output not specified" << std::endl; std::exit(1);