blob: f6b533be70e911f55a8ded721715f44cd7b38acf [file] [log] [blame]
/*
* Copyright 2014-present Facebook, Inc.
*
* 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.facebook.buck.apple;
import static com.facebook.buck.apple.ProjectGeneratorTestUtils.assertHasSingletonFrameworksPhaseWithFrameworkEntries;
import static com.facebook.buck.apple.ProjectGeneratorTestUtils.assertHasSingletonPhaseWithEntries;
import static com.facebook.buck.apple.ProjectGeneratorTestUtils.assertTargetExistsAndReturnTarget;
import static com.facebook.buck.apple.ProjectGeneratorTestUtils.createDescriptionArgWithDefaults;
import static com.facebook.buck.apple.ProjectGeneratorTestUtils.getSingletonPhaseByType;
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
import static org.hamcrest.collection.IsEmptyIterable.emptyIterable;
import static org.hamcrest.core.IsNot.not;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import com.dd.plist.NSArray;
import com.dd.plist.NSDictionary;
import com.dd.plist.NSString;
import com.facebook.buck.apple.xcode.xcodeproj.PBXBuildFile;
import com.facebook.buck.apple.xcode.xcodeproj.PBXBuildPhase;
import com.facebook.buck.apple.xcode.xcodeproj.PBXFileReference;
import com.facebook.buck.apple.xcode.xcodeproj.PBXGroup;
import com.facebook.buck.apple.xcode.xcodeproj.PBXHeadersBuildPhase;
import com.facebook.buck.apple.xcode.xcodeproj.PBXProject;
import com.facebook.buck.apple.xcode.xcodeproj.PBXReference;
import com.facebook.buck.apple.xcode.xcodeproj.PBXResourcesBuildPhase;
import com.facebook.buck.apple.xcode.xcodeproj.PBXShellScriptBuildPhase;
import com.facebook.buck.apple.xcode.xcodeproj.PBXTarget;
import com.facebook.buck.io.ProjectFilesystem;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.parser.NoSuchBuildTargetException;
import com.facebook.buck.rules.BuildRuleResolver;
import com.facebook.buck.rules.SourcePath;
import com.facebook.buck.rules.SourcePathResolver;
import com.facebook.buck.rules.TargetNode;
import com.facebook.buck.rules.TestSourcePath;
import com.facebook.buck.rules.coercer.SourceWithFlags;
import com.facebook.buck.shell.GenruleBuilder;
import com.facebook.buck.testutil.FakeProjectFilesystem;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import org.junit.Before;
import org.junit.Test;
import java.nio.file.Paths;
import java.util.List;
public class NewNativeTargetProjectMutatorTest {
private ProjectFilesystem projectFilesystem;
private BuildRuleResolver buildRuleResolver;
private PBXProject generatedProject;
private PathRelativizer pathRelativizer;
private SourcePathResolver sourcePathResolver;
@Before
public void setUp() {
projectFilesystem = new FakeProjectFilesystem();
buildRuleResolver = new BuildRuleResolver();
generatedProject = new PBXProject("TestProject");
sourcePathResolver = new SourcePathResolver(buildRuleResolver);
pathRelativizer = new PathRelativizer(
Paths.get("/test/project/root/"),
Paths.get("/test/project/root/_output"),
sourcePathResolver);
}
@Test
public void shouldCreateTargetAndTargetGroup() throws NoSuchBuildTargetException {
NewNativeTargetProjectMutator mutator = new NewNativeTargetProjectMutator(
pathRelativizer,
sourcePathResolver);
mutator
.setTargetName("TestTarget")
.setProduct(
PBXTarget.ProductType.BUNDLE,
"TestTargetProduct",
Paths.get("TestTargetProduct.bundle"))
.buildTargetAndAddToProject(generatedProject);
assertTargetExistsAndReturnTarget(generatedProject, "TestTarget");
assertHasTargetGroupWithName(generatedProject, "TestTarget");
}
@Test
public void shouldCreateTargetAndCustomTargetGroup() throws NoSuchBuildTargetException {
NewNativeTargetProjectMutator mutator = new NewNativeTargetProjectMutator(
pathRelativizer,
sourcePathResolver);
mutator
.setTargetName("TestTarget")
.setTargetGroupPath(ImmutableList.of("Grandparent", "Parent"))
.setProduct(
PBXTarget.ProductType.BUNDLE,
"TestTargetProduct",
Paths.get("TestTargetProduct.bundle"))
.buildTargetAndAddToProject(generatedProject);
assertTargetExistsAndReturnTarget(generatedProject, "TestTarget");
PBXGroup grandparentGroup =
assertHasSubgroupAndReturnIt(generatedProject.getMainGroup(), "Grandparent");
assertHasSubgroupAndReturnIt(grandparentGroup, "Parent");
}
@Test
public void testSourceGroups() throws NoSuchBuildTargetException {
NewNativeTargetProjectMutator mutator = mutatorWithCommonDefaults();
SourcePath foo = new TestSourcePath("Group1/foo.m");
SourcePath bar = new TestSourcePath("Group1/bar.m");
SourcePath baz = new TestSourcePath("Group2/baz.m");
mutator.setSourcesWithFlags(
ImmutableList.of(
SourceWithFlags.of(foo),
SourceWithFlags.of(bar, ImmutableList.of("-Wall")),
SourceWithFlags.of(baz)));
NewNativeTargetProjectMutator.Result result = mutator.buildTargetAndAddToProject(
generatedProject);
PBXGroup sourcesGroup = result.targetGroup.getOrCreateChildGroupByName("Sources");
PBXGroup group1 = (PBXGroup) Iterables.get(sourcesGroup.getChildren(), 0);
assertEquals("Group1", group1.getName());
assertThat(group1.getChildren(), hasSize(2));
PBXFileReference fileRefBar = (PBXFileReference) Iterables.get(group1.getChildren(), 0);
assertEquals("bar.m", fileRefBar.getName());
PBXFileReference fileRefFoo = (PBXFileReference) Iterables.get(group1.getChildren(), 1);
assertEquals("foo.m", fileRefFoo.getName());
PBXGroup group2 = (PBXGroup) Iterables.get(sourcesGroup.getChildren(), 1);
assertEquals("Group2", group2.getName());
assertThat(group2.getChildren(), hasSize(1));
PBXFileReference fileRefBaz = (PBXFileReference) Iterables.get(group2.getChildren(), 0);
assertEquals("baz.m", fileRefBaz.getName());
}
@Test
public void testLibraryHeaderGroups() throws NoSuchBuildTargetException {
NewNativeTargetProjectMutator mutator = mutatorWithCommonDefaults();
SourcePath foo = new TestSourcePath("HeaderGroup1/foo.h");
SourcePath bar = new TestSourcePath("HeaderGroup1/bar.h");
SourcePath baz = new TestSourcePath("HeaderGroup2/baz.h");
mutator.setPublicHeaders(ImmutableList.of(bar, baz));
mutator.setPrivateHeaders(ImmutableList.of(foo));
NewNativeTargetProjectMutator.Result result = mutator.buildTargetAndAddToProject(
generatedProject);
PBXGroup sourcesGroup = result.targetGroup.getOrCreateChildGroupByName("Sources");
assertThat(sourcesGroup.getChildren(), hasSize(2));
PBXGroup group1 = (PBXGroup) Iterables.get(sourcesGroup.getChildren(), 0);
assertEquals("HeaderGroup1", group1.getName());
assertThat(group1.getChildren(), hasSize(2));
PBXFileReference fileRefBar = (PBXFileReference) Iterables.get(group1.getChildren(), 0);
assertEquals("bar.h", fileRefBar.getName());
PBXFileReference fileRefFoo = (PBXFileReference) Iterables.get(group1.getChildren(), 1);
assertEquals("foo.h", fileRefFoo.getName());
PBXGroup group2 = (PBXGroup) Iterables.get(sourcesGroup.getChildren(), 1);
assertEquals("HeaderGroup2", group2.getName());
assertThat(group2.getChildren(), hasSize(1));
PBXFileReference fileRefBaz = (PBXFileReference) Iterables.get(group2.getChildren(), 0);
assertEquals("baz.h", fileRefBaz.getName());
PBXBuildPhase headersBuildPhase =
Iterables.find(result.target.getBuildPhases(), new Predicate<PBXBuildPhase>() {
@Override
public boolean apply(PBXBuildPhase input) {
return input instanceof PBXHeadersBuildPhase;
}
});
PBXBuildFile barHeaderBuildFile = Iterables.get(headersBuildPhase.getFiles(), 0);
assertTrue(
"bar.h should have settings dictionary",
barHeaderBuildFile.getSettings().isPresent());
NSDictionary barBuildFileSettings = barHeaderBuildFile.getSettings().get();
NSArray barAttributes = (NSArray) barBuildFileSettings.get("ATTRIBUTES");
assertArrayEquals(new NSString[]{new NSString("Public")}, barAttributes.getArray());
PBXBuildFile fooHeaderBuildFile = Iterables.get(headersBuildPhase.getFiles(), 1);
assertFalse(
"foo.h should not have settings dictionary",
fooHeaderBuildFile.getSettings().isPresent());
PBXBuildFile bazHeaderBuildFile = Iterables.get(headersBuildPhase.getFiles(), 2);
assertTrue(
"baz.h should have settings dictionary",
bazHeaderBuildFile.getSettings().isPresent());
NSDictionary blechBuildFileSettings = bazHeaderBuildFile.getSettings().get();
NSArray blechAttributes = (NSArray) blechBuildFileSettings.get("ATTRIBUTES");
assertArrayEquals(new NSString[]{new NSString("Public")}, blechAttributes.getArray());
}
@Test
public void testSuppressCopyHeaderOption() throws NoSuchBuildTargetException {
List<SourcePath> privateHeaders = ImmutableList.<SourcePath>of(new TestSourcePath("foo"));
{
NewNativeTargetProjectMutator mutator = mutatorWithCommonDefaults();
mutator
.setShouldGenerateCopyHeadersPhase(false)
.setPrivateHeaders(privateHeaders);
NewNativeTargetProjectMutator.Result result = mutator.buildTargetAndAddToProject(
generatedProject);
assertThat(
"copy headers phase should be omitted",
Iterables.filter(result.target.getBuildPhases(), PBXHeadersBuildPhase.class),
emptyIterable());
}
{
NewNativeTargetProjectMutator mutator = mutatorWithCommonDefaults();
mutator
.setShouldGenerateCopyHeadersPhase(true)
.setPrivateHeaders(privateHeaders);
NewNativeTargetProjectMutator.Result result = mutator.buildTargetAndAddToProject(
generatedProject);
assertThat(
"copy headers phase should be generated",
Iterables.filter(result.target.getBuildPhases(), PBXHeadersBuildPhase.class),
not(emptyIterable()));
}
}
@Test
public void testFrameworkBuildPhase() throws NoSuchBuildTargetException {
BuildTarget testBuildTarget = BuildTarget.builder("//foo", "binary").build();
NewNativeTargetProjectMutator mutator = mutatorWithCommonDefaults();
mutator.setFrameworks(
ImmutableSet.of(
FrameworkPath.fromString(
projectFilesystem,
testBuildTarget,
"$SDKROOT/Foo.framework")));
mutator.setArchives(
ImmutableSet.of(
new PBXFileReference(
"libdep.a",
"libdep.a",
PBXReference.SourceTree.BUILT_PRODUCTS_DIR)));
NewNativeTargetProjectMutator.Result result =
mutator.buildTargetAndAddToProject(generatedProject);
assertHasSingletonFrameworksPhaseWithFrameworkEntries(
result.target,
ImmutableList.of(
"$SDKROOT/Foo.framework",
"$BUILT_PRODUCTS_DIR/libdep.a"));
}
@Test
public void testResourcesBuildPhase() throws NoSuchBuildTargetException {
NewNativeTargetProjectMutator mutator = mutatorWithCommonDefaults();
AppleResourceDescription appleResourceDescription = new AppleResourceDescription();
AppleResourceDescription.Arg arg = createDescriptionArgWithDefaults(appleResourceDescription);
arg.files = ImmutableSet.<SourcePath>of(new TestSourcePath("foo.png"));
mutator.setResources(ImmutableSet.of(arg));
NewNativeTargetProjectMutator.Result result =
mutator.buildTargetAndAddToProject(generatedProject);
assertHasSingletonPhaseWithEntries(
result.target,
PBXResourcesBuildPhase.class,
ImmutableList.of("$SOURCE_ROOT/../foo.png"));
}
@Test
public void assetCatalogsBuildPhaseBuildsBothCommonAndBundledAssetCatalogs()
throws NoSuchBuildTargetException {
AppleAssetCatalogDescription.Arg arg1 = new AppleAssetCatalogDescription.Arg();
arg1.dirs = ImmutableSet.of(Paths.get("AssetCatalog1.xcassets"));
arg1.copyToBundles = Optional.of(false);
AppleAssetCatalogDescription.Arg arg2 = new AppleAssetCatalogDescription.Arg();
arg2.dirs = ImmutableSet.of(Paths.get("AssetCatalog2.xcassets"));
arg2.copyToBundles = Optional.of(true);
NewNativeTargetProjectMutator mutator = mutatorWithCommonDefaults();
mutator.setAssetCatalogs(
Paths.get("compile_asset_catalogs"),
ImmutableSet.of(arg1, arg2));
NewNativeTargetProjectMutator.Result result =
mutator.buildTargetAndAddToProject(generatedProject);
assertTrue(hasShellScriptPhaseToCompileCommonAndSplitAssetCatalogs(result.target));
}
@Test
public void testScriptBuildPhase() throws NoSuchBuildTargetException{
NewNativeTargetProjectMutator mutator = mutatorWithCommonDefaults();
TargetNode<?> genruleNode = GenruleBuilder
.newGenruleBuilder(BuildTarget.builder("//foo", "script").build())
.setSrcs(ImmutableList.<SourcePath>of(new TestSourcePath("script/input.png")))
.setCmd("echo \"hello world!\"")
.setOut("helloworld.txt")
.build();
mutator.setPostBuildRunScriptPhases(ImmutableList.<TargetNode<?>>of(genruleNode));
NewNativeTargetProjectMutator.Result result =
mutator.buildTargetAndAddToProject(generatedProject);
PBXShellScriptBuildPhase phase =
getSingletonPhaseByType(result.target, PBXShellScriptBuildPhase.class);
assertEquals(
"Should set input paths correctly",
"../script/input.png",
Iterables.getOnlyElement(phase.getInputPaths()));
assertEquals(
"should set script correctly",
"echo \"hello world!\"",
phase.getShellScript());
}
@Test
public void testScriptBuildPhaseWithGenruleDep() throws NoSuchBuildTargetException{
NewNativeTargetProjectMutator mutator = mutatorWithCommonDefaults();
BuildTarget depBuildTarget = BuildTarget.builder("//foo", "dep").build();
// This has the side effect of adding the dependency to the BuildRuleResolver map.
GenruleBuilder
.newGenruleBuilder(depBuildTarget)
.setCmd("echo \"hello dep!\"")
.setOut("dep.txt")
.build(buildRuleResolver);
TargetNode<?> genruleNode = GenruleBuilder
.newGenruleBuilder(BuildTarget.builder("//foo", "script").build())
.setSrcs(ImmutableList.<SourcePath>of(new TestSourcePath("script/input.png")))
.setCmd("echo \"hello world!\"")
.setOut("helloworld.txt")
.setDeps(ImmutableSortedSet.of(depBuildTarget))
.build();
mutator.setPostBuildRunScriptPhases(ImmutableList.<TargetNode<?>>of(genruleNode));
NewNativeTargetProjectMutator.Result result =
mutator.buildTargetAndAddToProject(generatedProject);
PBXShellScriptBuildPhase phase =
getSingletonPhaseByType(result.target, PBXShellScriptBuildPhase.class);
assertEquals(
"Should set input paths correctly",
"../script/input.png",
Iterables.getOnlyElement(phase.getInputPaths()));
assertEquals(
"should set script correctly",
"echo \"hello world!\"",
phase.getShellScript());
}
private NewNativeTargetProjectMutator mutatorWithCommonDefaults() {
NewNativeTargetProjectMutator mutator = new NewNativeTargetProjectMutator(
pathRelativizer,
sourcePathResolver);
mutator
.setTargetName("TestTarget")
.setProduct(
PBXTarget.ProductType.BUNDLE,
"TestTargetProduct",
Paths.get("TestTargetProduct.bundle"));
return mutator;
}
private static void assertHasTargetGroupWithName(PBXProject project, final String name) {
assertThat(
"Should contain a target group named: " + name,
Iterables.filter(
project.getMainGroup().getChildren(), new Predicate<PBXReference>() {
@Override
public boolean apply(PBXReference input) {
return input.getName().equals(name);
}
}),
not(emptyIterable()));
}
private static PBXGroup assertHasSubgroupAndReturnIt(PBXGroup group, final String subgroupName) {
ImmutableList<PBXGroup> candidates = FluentIterable
.from(group.getChildren())
.filter(
new Predicate<PBXReference>() {
@Override
public boolean apply(PBXReference input) {
return input.getName().equals(subgroupName);
}
})
.filter(PBXGroup.class)
.toList();
if (candidates.size() != 1) {
fail("Could not find a unique subgroup by its name");
}
return candidates.get(0);
}
private boolean hasShellScriptPhaseToCompileCommonAndSplitAssetCatalogs(PBXTarget target) {
PBXShellScriptBuildPhase assetCatalogBuildPhase = null;
for (PBXBuildPhase phase : target.getBuildPhases()) {
if (phase.getClass().equals(PBXShellScriptBuildPhase.class)) {
PBXShellScriptBuildPhase shellScriptBuildPhase = (PBXShellScriptBuildPhase) phase;
if (shellScriptBuildPhase.getShellScript().contains("compile_asset_catalogs")) {
assetCatalogBuildPhase = shellScriptBuildPhase;
}
}
}
assertNotNull(assetCatalogBuildPhase);
boolean foundCommonAssetCatalogCompileCommand = false;
boolean foundSplitAssetCatalogCompileCommand = false;
String[] lines = assetCatalogBuildPhase.getShellScript().split("\\n");
for (String line : lines) {
if (line.contains("compile_asset_catalogs")) {
if (line.contains(" -b ")) {
foundSplitAssetCatalogCompileCommand = true;
} else {
assertFalse("should have only one invocation", foundCommonAssetCatalogCompileCommand);
foundCommonAssetCatalogCompileCommand = true;
}
}
}
return foundCommonAssetCatalogCompileCommand && foundSplitAssetCatalogCompileCommand;
}
}