| // Copyright 2014 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 com.google.common.annotations.VisibleForTesting; |
| import com.google.common.base.Optional; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.Iterables; |
| import com.google.devtools.build.lib.actions.ActionExecutionContext; |
| import com.google.devtools.build.lib.actions.ActionKeyContext; |
| import com.google.devtools.build.lib.actions.ActionOwner; |
| import com.google.devtools.build.lib.actions.Artifact; |
| import com.google.devtools.build.lib.actions.Artifact.ArtifactExpander; |
| import com.google.devtools.build.lib.analysis.actions.AbstractFileWriteAction; |
| import com.google.devtools.build.lib.analysis.actions.DeterministicWriter; |
| import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; |
| import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; |
| import com.google.devtools.build.lib.util.Fingerprint; |
| import com.google.devtools.build.lib.vfs.PathFragment; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.io.OutputStreamWriter; |
| import java.nio.charset.StandardCharsets; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashSet; |
| import java.util.List; |
| import javax.annotation.Nullable; |
| |
| /** |
| * Creates C++ module map artifact genfiles. These are then passed to Clang to do dependency |
| * checking. |
| */ |
| @Immutable |
| public final class CppModuleMapAction extends AbstractFileWriteAction { |
| |
| private static final String GUID = "4f407081-1951-40c1-befc-d6b4daff5de3"; |
| |
| // C++ module map of the current target |
| private final CppModuleMap cppModuleMap; |
| |
| /** |
| * If set, the paths in the module map are relative to the current working directory instead of |
| * relative to the module map file's location. |
| */ |
| private final boolean moduleMapHomeIsCwd; |
| |
| // Data required to build the actual module map. |
| // NOTE: If you add a field here, you'll likely need to add it to the cache key in computeKey(). |
| private final ImmutableList<Artifact> privateHeaders; |
| private final ImmutableList<Artifact> publicHeaders; |
| private final ImmutableList<CppModuleMap> dependencies; |
| private final ImmutableList<PathFragment> additionalExportedHeaders; |
| private final ImmutableList<Artifact> separateModuleHeaders; |
| private final boolean compiledModule; |
| private final boolean generateSubmodules; |
| private final boolean externDependencies; |
| |
| public CppModuleMapAction( |
| ActionOwner owner, |
| CppModuleMap cppModuleMap, |
| Iterable<Artifact> privateHeaders, |
| Iterable<Artifact> publicHeaders, |
| Iterable<CppModuleMap> dependencies, |
| Iterable<PathFragment> additionalExportedHeaders, |
| Iterable<Artifact> separateModuleHeaders, |
| boolean compiledModule, |
| boolean moduleMapHomeIsCwd, |
| boolean generateSubmodules, |
| boolean externDependencies) { |
| super( |
| owner, |
| NestedSetBuilder.<Artifact>stableOrder() |
| .addAll(Iterables.filter(privateHeaders, Artifact::isTreeArtifact)) |
| .addAll(Iterables.filter(publicHeaders, Artifact::isTreeArtifact)) |
| .build(), |
| cppModuleMap.getArtifact(), |
| // In theory, module maps should not be executable but, in practice, we don't care. As |
| // 'executable' is the default (see ActionMetadataHandler.setPathReadOnlyAndExecutable()), |
| // we want to avoid the extra file operation of making this file non-executable. |
| /*makeExecutable=*/ true); |
| this.cppModuleMap = cppModuleMap; |
| this.moduleMapHomeIsCwd = moduleMapHomeIsCwd; |
| this.privateHeaders = ImmutableList.copyOf(privateHeaders); |
| this.publicHeaders = ImmutableList.copyOf(publicHeaders); |
| this.dependencies = ImmutableList.copyOf(dependencies); |
| this.additionalExportedHeaders = ImmutableList.copyOf(additionalExportedHeaders); |
| this.separateModuleHeaders = ImmutableList.copyOf(separateModuleHeaders); |
| this.compiledModule = compiledModule; |
| this.generateSubmodules = generateSubmodules; |
| this.externDependencies = externDependencies; |
| } |
| |
| @Override |
| public DeterministicWriter newDeterministicWriter(ActionExecutionContext ctx) { |
| final ArtifactExpander artifactExpander = ctx.getArtifactExpander(); |
| return new DeterministicWriter() { |
| @Override |
| public void writeOutputFile(OutputStream out) throws IOException { |
| OutputStreamWriter content = new OutputStreamWriter(out, StandardCharsets.ISO_8859_1); |
| PathFragment fragment = cppModuleMap.getArtifact().getExecPath(); |
| int segmentsToExecPath = fragment.segmentCount() - 1; |
| Optional<Artifact> umbrellaHeader = cppModuleMap.getUmbrellaHeader(); |
| String leadingPeriods = moduleMapHomeIsCwd ? "" : "../".repeat(segmentsToExecPath); |
| |
| Iterable<Artifact> separateModuleHdrs = |
| expandedHeaders(artifactExpander, separateModuleHeaders); |
| |
| // For details about the different header types, see: |
| // http://clang.llvm.org/docs/Modules.html#header-declaration |
| content.append("module \"").append(cppModuleMap.getName()).append("\" {\n"); |
| content.append(" export *\n"); |
| |
| HashSet<PathFragment> deduper = new HashSet<>(); |
| if (umbrellaHeader.isPresent()) { |
| appendHeader( |
| content, |
| "", |
| umbrellaHeader.get().getExecPath(), |
| leadingPeriods, |
| /*canCompile=*/ false, |
| deduper, |
| /*isUmbrellaHeader*/ true); |
| } else { |
| for (Artifact artifact : expandedHeaders(artifactExpander, publicHeaders)) { |
| appendHeader( |
| content, |
| "", |
| artifact.getExecPath(), |
| leadingPeriods, |
| /*canCompile=*/ true, |
| deduper, |
| /*isUmbrellaHeader*/ false); |
| } |
| for (Artifact artifact : expandedHeaders(artifactExpander, privateHeaders)) { |
| appendHeader( |
| content, |
| "private", |
| artifact.getExecPath(), |
| leadingPeriods, |
| /*canCompile=*/ true, |
| deduper, |
| /*isUmbrellaHeader*/ false); |
| } |
| for (Artifact artifact : separateModuleHdrs) { |
| appendHeader( |
| content, |
| "", |
| artifact.getExecPath(), |
| leadingPeriods, |
| /*canCompile=*/ false, |
| deduper, |
| /*isUmbrellaHeader*/ false); |
| } |
| for (PathFragment additionalExportedHeader : additionalExportedHeaders) { |
| appendHeader( |
| content, |
| "", |
| additionalExportedHeader, |
| leadingPeriods, |
| /*canCompile*/ false, |
| deduper, |
| /*isUmbrellaHeader*/ false); |
| } |
| } |
| for (CppModuleMap dep : dependencies) { |
| content.append(" use \"").append(dep.getName()).append("\"\n"); |
| } |
| |
| if (!Iterables.isEmpty(separateModuleHdrs)) { |
| String separateName = cppModuleMap.getName() + CppModuleMap.SEPARATE_MODULE_SUFFIX; |
| content.append(" use \"").append(separateName).append("\"\n"); |
| content.append("}\n"); |
| content.append("module \"").append(separateName).append("\" {\n"); |
| content.append(" export *\n"); |
| deduper = new HashSet<>(); |
| for (Artifact artifact : separateModuleHdrs) { |
| appendHeader( |
| content, |
| "", |
| artifact.getExecPath(), |
| leadingPeriods, |
| /*canCompile=*/ true, |
| deduper, |
| /*isUmbrellaHeader*/ false); |
| } |
| for (CppModuleMap dep : dependencies) { |
| content.append(" use \"").append(dep.getName()).append("\"\n"); |
| } |
| } |
| content.append("}"); |
| |
| if (externDependencies) { |
| for (CppModuleMap dep : dependencies) { |
| content |
| .append("\nextern module \"") |
| .append(dep.getName()) |
| .append("\" \"") |
| .append(leadingPeriods) |
| .append(dep.getArtifact().getExecPathString()) |
| .append("\""); |
| } |
| } |
| content.flush(); |
| } |
| }; |
| } |
| |
| private static Iterable<Artifact> expandedHeaders(ArtifactExpander artifactExpander, |
| Iterable<Artifact> unexpandedHeaders) { |
| List<Artifact> expandedHeaders = new ArrayList<>(); |
| for (Artifact unexpandedHeader : unexpandedHeaders) { |
| if (unexpandedHeader.isTreeArtifact()) { |
| artifactExpander.expand(unexpandedHeader, expandedHeaders); |
| } else { |
| expandedHeaders.add(unexpandedHeader); |
| } |
| } |
| |
| return ImmutableList.copyOf(expandedHeaders); |
| } |
| |
| private void appendHeader(Appendable content, String visibilitySpecifier, |
| PathFragment path, String leadingPeriods, boolean canCompile, HashSet<PathFragment> deduper, |
| boolean isUmbrellaHeader) throws IOException { |
| if (deduper.contains(path)) { |
| return; |
| } |
| deduper.add(path); |
| if (isUmbrellaHeader) { |
| content.append(" umbrella header \"umbrella.h\"\n"); |
| return; |
| } |
| if (generateSubmodules) { |
| content.append(" module \"").append(path.toString()).append("\" {\n"); |
| content.append(" export *\n "); |
| } |
| content.append(" "); |
| if (!visibilitySpecifier.isEmpty()) { |
| content.append(visibilitySpecifier).append(" "); |
| } |
| if (!canCompile || !shouldCompileHeader(path)) { |
| content.append("textual "); |
| } |
| content.append("header \"").append(leadingPeriods).append(path.toString()).append("\""); |
| if (generateSubmodules) { |
| content.append("\n }"); |
| } |
| content.append("\n"); |
| } |
| |
| private boolean shouldCompileHeader(PathFragment path) { |
| return compiledModule && !CppFileTypes.CPP_TEXTUAL_INCLUDE.matches(path); |
| } |
| |
| @Override |
| public String getMnemonic() { |
| return "CppModuleMap"; |
| } |
| |
| @Override |
| protected void computeKey( |
| ActionKeyContext actionKeyContext, |
| @Nullable Artifact.ArtifactExpander artifactExpander, |
| Fingerprint fp) { |
| fp.addString(GUID); |
| fp.addInt(privateHeaders.size()); |
| for (Artifact artifact : privateHeaders) { |
| fp.addPath(artifact.getExecPath()); |
| } |
| fp.addInt(publicHeaders.size()); |
| for (Artifact artifact : publicHeaders) { |
| fp.addPath(artifact.getExecPath()); |
| } |
| fp.addInt(separateModuleHeaders.size()); |
| for (Artifact artifact : separateModuleHeaders) { |
| fp.addPath(artifact.getExecPath()); |
| } |
| fp.addInt(dependencies.size()); |
| for (CppModuleMap dep : dependencies) { |
| fp.addString(dep.getName()); |
| fp.addPath(dep.getArtifact().getExecPath()); |
| } |
| fp.addInt(additionalExportedHeaders.size()); |
| for (PathFragment path : additionalExportedHeaders) { |
| fp.addPath(path); |
| } |
| fp.addPath(cppModuleMap.getArtifact().getExecPath()); |
| Optional<Artifact> umbrellaHeader = cppModuleMap.getUmbrellaHeader(); |
| if (umbrellaHeader.isPresent()) { |
| fp.addPath(umbrellaHeader.get().getExecPath()); |
| } |
| fp.addString(cppModuleMap.getName()); |
| fp.addBoolean(moduleMapHomeIsCwd); |
| fp.addBoolean(compiledModule); |
| fp.addBoolean(generateSubmodules); |
| fp.addBoolean(externDependencies); |
| } |
| |
| @VisibleForTesting |
| public CppModuleMap getCppModuleMap() { |
| return cppModuleMap; |
| } |
| |
| @VisibleForTesting |
| public ImmutableList<Artifact> getPublicHeaders() { |
| return publicHeaders; |
| } |
| |
| @VisibleForTesting |
| public ImmutableList<Artifact> getPrivateHeaders() { |
| return privateHeaders; |
| } |
| |
| @VisibleForTesting |
| public ImmutableList<PathFragment> getAdditionalExportedHeaders() { |
| return additionalExportedHeaders; |
| } |
| |
| @VisibleForTesting |
| public ImmutableList<Artifact> getSeparateModuleHeaders() { |
| return separateModuleHeaders; |
| } |
| |
| @VisibleForTesting |
| public Collection<Artifact> getDependencyArtifacts() { |
| List<Artifact> artifacts = new ArrayList<>(); |
| for (CppModuleMap map : dependencies) { |
| artifacts.add(map.getArtifact()); |
| } |
| return artifacts; |
| } |
| } |