blob: 8436ac925242c094dc15d3032013ac1c7fc43b78 [file] [log] [blame]
// Copyright 2017 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.objc;
import static com.google.common.truth.Truth.assertThat;
import static com.google.devtools.build.lib.testutil.MoreAsserts.assertThrows;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.Artifact.DerivedArtifact;
import com.google.devtools.build.lib.actions.Artifact.SpecialArtifact;
import com.google.devtools.build.lib.actions.Artifact.SpecialArtifactType;
import com.google.devtools.build.lib.actions.ArtifactPathResolver;
import com.google.devtools.build.lib.actions.ArtifactRoot;
import com.google.devtools.build.lib.actions.ExecException;
import com.google.devtools.build.lib.actions.UserExecException;
import com.google.devtools.build.lib.actions.util.ActionsTestUtil;
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.analysis.actions.SpawnAction;
import com.google.devtools.build.lib.analysis.util.ScratchAttributeWriter;
import com.google.devtools.build.lib.packages.util.MockObjcSupport;
import com.google.devtools.build.lib.rules.cpp.CppCompileAction;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.IORuntimeException;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Tests for header thinning. */
@RunWith(JUnit4.class)
public class HeaderThinningTest extends ObjcRuleTestCase {
private static final String CPP_COMPILE_ACTION_RULE_TYPE = "objc_library";
@Before
public void beforeEach() throws Exception {
MockObjcSupport.setupCcToolchainConfig(mockToolsConfig);
MockObjcSupport.setupAppleSdks(mockToolsConfig);
useConfiguration(
"--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL,
"--experimental_objc_header_thinning",
"--objc_use_dotd_pruning",
"--xcode_version=" + MockObjcSupport.DEFAULT_XCODE_VERSION,
"--ios_sdk_version=" + MockObjcSupport.DEFAULT_IOS_SDK_VERSION);
}
private Iterable<Artifact> determineAdditionalInputs(
HeaderThinning headerThinning, CppCompileAction action)
throws ExecException, InterruptedException, IOException {
try {
return headerThinning.determineAdditionalInputs(null, action, null, null).get();
} catch (ExecutionException e) {
Throwables.throwIfInstanceOf(e.getCause(), ExecException.class);
Throwables.throwIfInstanceOf(e.getCause(), InterruptedException.class);
if (e.getCause() instanceof IORuntimeException) {
throw ((IORuntimeException) e.getCause()).getCauseIOException();
}
Throwables.throwIfUnchecked(e.getCause());
throw new IllegalStateException(e.getCause());
}
}
@Test
public void testCppCompileActionHeaderThinningCanDetermineAdditionalInputs() throws Exception {
Artifact sourceFile = getSourceArtifact("objc/a.m");
CppCompileAction action = createCppCompileAction(sourceFile);
List<Artifact> expectedHeaders =
ImmutableList.of(
getSourceArtifact("objc/a.pch"),
getSourceArtifact("objc/b.h"),
getSourceArtifact("objc/c"),
getSourceArtifact("objc/d.hpp"));
HeaderThinning headerThinning = new HeaderThinning(getPotentialHeaders(expectedHeaders));
writeToHeadersListFile(action, "objc/a.pch", "objc/b.h", "objc/c", "objc/d.hpp");
Iterable<Artifact> headersFound = determineAdditionalInputs(headerThinning, action);
assertThat(headersFound).containsExactlyElementsIn(expectedHeaders);
}
@Test
public void testCppCompileActionHeaderThinningThrowsWhenUnknownHeaderFound() throws Exception {
Artifact sourceFile = getSourceArtifact("objc/a.m");
CppCompileAction action = createCppCompileAction(sourceFile);
List<Artifact> expectedHeaders =
ImmutableList.of(getSourceArtifact("objc/a.h"), getSourceArtifact("objc/b.h"));
HeaderThinning headerThinning = new HeaderThinning(getPotentialHeaders(expectedHeaders));
writeToHeadersListFile(action, "objc/a.h", "objc/b.h", "objc/c.h");
ExecException e =
assertThrows(ExecException.class, () -> determineAdditionalInputs(headerThinning, action));
assertThat(e).hasMessageThat().containsMatch("(objc/c.h)");
assertThat(e).isInstanceOf(UserExecException.class);
}
@Test
public void testCppCompileActionHeaderThinningFindsHeadersInTreeArtifacts() throws Exception {
Artifact sourceFile = getSourceArtifact("objc/a.m");
CppCompileAction action = createCppCompileAction(sourceFile);
List<Artifact> expectedHeaders =
ImmutableList.of(getSourceArtifact("objc/a.h"), getTreeArtifact("tree/dir"));
HeaderThinning headerThinning = new HeaderThinning(getPotentialHeaders(expectedHeaders));
writeToHeadersListFile(action, "objc/a.h", "out/tree/dir/c.h");
Iterable<Artifact> headersFound = determineAdditionalInputs(headerThinning, action);
assertThat(headersFound).containsExactlyElementsIn(expectedHeaders);
}
@Test
public void testObjcCompileActionHeaderThinningCanFindRequiredHeaderInputs() throws Exception {
Artifact sourceFile = getSourceArtifact("objc/a.m");
Artifact headersListFile = getHeadersListArtifact(sourceFile);
scratch.file(
headersListFile.getExecPathString(), "objc/a.pch", "objc/b.h", "objc/c", "objc/d.hpp");
List<Artifact> expectedHeaders =
ImmutableList.of(
getSourceArtifact("objc/a.pch"),
getSourceArtifact("objc/b.h"),
getSourceArtifact("objc/c"),
getSourceArtifact("objc/d.hpp"));
Iterable<Artifact> headersFound =
HeaderThinning.findRequiredHeaderInputs(
sourceFile,
headersListFile,
createHeaderFilesMap(getPotentialHeaders(expectedHeaders)),
ArtifactPathResolver.IDENTITY);
assertThat(headersFound).containsExactlyElementsIn(expectedHeaders);
}
private void writeToHeadersListFile(CppCompileAction action, String... lines) throws Exception {
Artifact headersListFile = null;
for (Artifact input : action.getMandatoryInputs()) {
if (input.getExtension().equals("headers_list")) {
headersListFile = input;
break;
}
}
assertThat(headersListFile).isNotNull();
scratch.file(headersListFile.getPath().getPathString(), lines);
}
/**
* Simple utility to populate some unused header Artifacts along with the ones we expect to find.
*/
private Iterable<Artifact> getPotentialHeaders(List<Artifact> expectedHeaders) {
return Iterables.concat(
ImmutableList.of(
getSourceArtifact("objc/unused.h"),
getSourceArtifact("some/other.h"),
getSourceArtifact("objc/foo.pch")),
expectedHeaders);
}
private CppCompileAction createCppCompileAction(Artifact sourceFile) throws Exception {
String ownerLabel = "//objc:lib";
ScratchAttributeWriter.fromLabelString(this, CPP_COMPILE_ACTION_RULE_TYPE, ownerLabel)
.setList("srcs", sourceFile.getFilename())
.write();
ConfiguredTarget target = getConfiguredTarget(ownerLabel);
List<CppCompileAction> allActions =
actionsTestUtil()
.findTransitivePrerequisitesOf(
ActionsTestUtil.getFirstArtifactEndingWith(getFilesToBuild(target), ".a"),
CppCompileAction.class);
for (CppCompileAction action : allActions) {
if (action.getSourceFile().getExecPath().equals(sourceFile.getExecPath())) {
return action;
}
}
return null;
}
private Artifact getHeadersListArtifact(Artifact sourceFile) {
return getSourceArtifact(
FileSystemUtils.replaceExtension(sourceFile.getExecPath(), ".headers_list"),
sourceFile.getRoot().getRoot());
}
private static Map<PathFragment, Artifact> createHeaderFilesMap(Iterable<Artifact> artifacts) {
ImmutableMap.Builder<PathFragment, Artifact> headerFilesMapBuilder = ImmutableMap.builder();
for (Artifact artifact : artifacts) {
headerFilesMapBuilder.put(artifact.getExecPath(), artifact);
}
return headerFilesMapBuilder.build();
}
private Artifact getTreeArtifact(String name) {
DerivedArtifact treeArtifactBase =
getDerivedArtifact(
PathFragment.create(name),
ArtifactRoot.asDerivedRoot(
directories.getExecRoot("workspace"),
directories.getExecRoot("workspace").getChild("out")),
ActionsTestUtil.NULL_ARTIFACT_OWNER);
return new SpecialArtifact(
treeArtifactBase.getRoot(),
treeArtifactBase.getExecPath(),
treeArtifactBase.getArtifactOwner(),
SpecialArtifactType.TREE);
}
@Test
public void generatesHeaderScanningAction() throws Exception {
Set<SpawnAction> scanningActions =
createTargetAndGetHeaderScanningActions(ImmutableList.of("one.m", "two.m"));
assertThat(scanningActions).hasSize(1);
}
@Test
public void generates2HeaderScanningActionsWhenObjcAndCppSources() throws Exception {
Set<SpawnAction> scanningActions =
createTargetAndGetHeaderScanningActions(ImmutableList.of("one.m", "two.cc"));
assertThat(scanningActions).hasSize(2);
}
@Test
public void generatesMultipleHeaderScanningActionsForLargeTargets2() throws Exception {
validateGeneratesMultipleHeaderScanningActionsForLargeTargets(
2, targetConfig.getFragment(ObjcConfiguration.class).objcHeaderThinningPartitionSize());
}
@Test
public void generatesMultipleHeaderScanningActionsForLargeTargets4() throws Exception {
validateGeneratesMultipleHeaderScanningActionsForLargeTargets(
4, targetConfig.getFragment(ObjcConfiguration.class).objcHeaderThinningPartitionSize());
}
@Test
public void generatesMultipleHeaderScanningActionsForLargeTargets8() throws Exception {
validateGeneratesMultipleHeaderScanningActionsForLargeTargets(
8, targetConfig.getFragment(ObjcConfiguration.class).objcHeaderThinningPartitionSize());
}
@Test
public void generatesMultipleHeaderScanningActionsForLargeTargetsCustomPartition()
throws Exception {
int partitionSize = 5;
useConfiguration(
"--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL,
"--experimental_objc_header_thinning",
"--objc_header_thinning_partition_size=" + 5,
"--objc_use_dotd_pruning",
"--xcode_version=" + MockObjcSupport.DEFAULT_XCODE_VERSION,
"--ios_sdk_version=" + MockObjcSupport.DEFAULT_IOS_SDK_VERSION);
validateGeneratesMultipleHeaderScanningActionsForLargeTargets(12, partitionSize);
}
private void validateGeneratesMultipleHeaderScanningActionsForLargeTargets(
int actionCount, int partitionSize) throws Exception {
ImmutableList.Builder<String> sourcesBuilder = ImmutableList.builder();
for (int i = 0; i < partitionSize * actionCount; ++i) {
sourcesBuilder.add(String.format("source_%d.m", i));
}
Set<SpawnAction> scanningActions =
createTargetAndGetHeaderScanningActions(sourcesBuilder.build());
assertThat(scanningActions).hasSize(actionCount);
}
private Set<SpawnAction> createTargetAndGetHeaderScanningActions(Iterable<String> sources)
throws Exception {
String ownerLabel = "//objc:lib";
ScratchAttributeWriter.fromLabelString(this, CPP_COMPILE_ACTION_RULE_TYPE, ownerLabel)
.setList("srcs", sources)
.write();
ConfiguredTarget target = getConfiguredTarget(ownerLabel);
List<SpawnAction> spawnActions =
actionsTestUtil()
.findTransitivePrerequisitesOf(
ActionsTestUtil.getFirstArtifactEndingWith(getFilesToBuild(target), ".a"),
SpawnAction.class);
return Sets.newHashSet(
Iterables.filter(spawnActions, a -> a.getMnemonic().equals("ObjcHeaderScanning")));
}
}