/*
* Copyright 2013-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 org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import com.facebook.buck.cxx.CxxCompilationDatabase;
import com.facebook.buck.cxx.CxxInferEnhancer;
import com.facebook.buck.io.ProjectFilesystem;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.model.BuildTargetFactory;
import com.facebook.buck.model.Flavor;
import com.facebook.buck.model.Flavored;
import com.facebook.buck.model.InternalFlavor;
import com.facebook.buck.rules.AbstractNodeBuilder;
import com.facebook.buck.rules.BuildRule;
import com.facebook.buck.rules.BuildRuleResolver;
import com.facebook.buck.rules.DefaultTargetNodeToBuildRuleTransformer;
import com.facebook.buck.rules.Description;
import com.facebook.buck.rules.FakeBuildContext;
import com.facebook.buck.rules.FakeBuildableContext;
import com.facebook.buck.rules.FakeSourcePath;
import com.facebook.buck.rules.SourcePathResolver;
import com.facebook.buck.rules.SourcePathRuleFinder;
import com.facebook.buck.rules.SourceWithFlags;
import com.facebook.buck.rules.TargetGraph;
import com.facebook.buck.shell.ShellStep;
import com.facebook.buck.step.ExecutionContext;
import com.facebook.buck.step.Step;
import com.facebook.buck.step.TestExecutionContext;
import com.facebook.buck.testutil.FakeProjectFilesystem;
import com.facebook.buck.testutil.TargetGraphFactory;
import com.facebook.buck.util.HumanReadableException;
import com.facebook.buck.util.environment.Platform;
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 java.util.Arrays;
import java.util.Collection;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
public class MultiarchFileTest {
@Parameterized.Parameters(name = "{0}")
public static Collection<Object[]> data() {
return Arrays.asList(
new Object[][] {
{
"AppleBinaryDescription",
FakeAppleRuleDescriptions.BINARY_DESCRIPTION,
(NodeBuilderFactory) AppleBinaryBuilder::createBuilder
},
{
"AppleLibraryDescription (static)",
FakeAppleRuleDescriptions.LIBRARY_DESCRIPTION,
(NodeBuilderFactory)
target ->
AppleLibraryBuilder.createBuilder(
target.withAppendedFlavors(InternalFlavor.of("static")))
.setSrcs(
ImmutableSortedSet.of(SourceWithFlags.of(new FakeSourcePath("foo.c"))))
},
{
"AppleLibraryDescription (shared)",
FakeAppleRuleDescriptions.LIBRARY_DESCRIPTION,
(NodeBuilderFactory)
target ->
AppleLibraryBuilder.createBuilder(
target.withAppendedFlavors(InternalFlavor.of("shared")))
.setSrcs(
ImmutableSortedSet.of(SourceWithFlags.of(new FakeSourcePath("foo.c"))))
},
});
}
@Parameterized.Parameter(0)
public String name;
@Parameterized.Parameter(1)
public Description<?> description;
@Parameterized.Parameter(2)
public NodeBuilderFactory nodeBuilderFactory;
@Before
public void setUp() {
assumeTrue(Platform.detect() == Platform.MACOS || Platform.detect() == Platform.LINUX);
}
@Test
public void shouldAllowMultiplePlatformFlavors() {
assertTrue(
((Flavored) description)
.hasFlavors(
ImmutableSet.of(
InternalFlavor.of("iphoneos-i386"), InternalFlavor.of("iphoneos-x86_64"))));
}
@SuppressWarnings({"unchecked"})
@Test
public void descriptionWithMultiplePlatformArgsShouldGenerateMultiarchFile() throws Exception {
BuildTarget target =
BuildTargetFactory.newInstance("//foo:thing#iphoneos-i386,iphoneos-x86_64");
BuildTarget sandboxTarget =
BuildTargetFactory.newInstance("//foo:thing#iphoneos-i386,iphoneos-x86_64,sandbox");
BuildRuleResolver resolver =
new BuildRuleResolver(
TargetGraphFactory.newInstance(new AppleLibraryBuilder(sandboxTarget).build()),
new DefaultTargetNodeToBuildRuleTransformer());
SourcePathResolver pathResolver = new SourcePathResolver(new SourcePathRuleFinder(resolver));
ProjectFilesystem filesystem = new FakeProjectFilesystem();
BuildRule multiarchRule = nodeBuilderFactory.getNodeBuilder(target).build(resolver, filesystem);
assertThat(multiarchRule, instanceOf(MultiarchFile.class));
ImmutableList<Step> steps =
multiarchRule.getBuildSteps(
FakeBuildContext.withSourcePathResolver(pathResolver), new FakeBuildableContext());
ShellStep step = Iterables.getLast(Iterables.filter(steps, ShellStep.class));
ExecutionContext executionContext = TestExecutionContext.newInstance();
ImmutableList<String> command = step.getShellCommand(executionContext);
assertThat(
command,
Matchers.contains(
endsWith("lipo"),
equalTo("-create"),
equalTo("-output"),
containsString("foo/thing#"),
containsString("/thing#"),
containsString("/thing#")));
}
@Test
public void descriptionWithMultipleDifferentSdksShouldFail() throws Exception {
BuildRuleResolver resolver =
new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer());
HumanReadableException exception = null;
try {
nodeBuilderFactory
.getNodeBuilder(
BuildTargetFactory.newInstance("//foo:xctest#iphoneos-i386,macosx-x86_64"))
.build(resolver);
} catch (HumanReadableException e) {
exception = e;
}
assertThat(exception, notNullValue());
assertThat(
"Should throw exception about different architectures",
exception.getHumanReadableErrorMessage(),
endsWith("Fat binaries can only be generated from binaries compiled for the same SDK."));
}
@Test
public void ruleWithSpecialBuildActionShouldFail() throws Exception {
BuildRuleResolver resolver =
new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer());
HumanReadableException exception = null;
Iterable<Flavor> forbiddenFlavors =
ImmutableList.<Flavor>builder()
.addAll(CxxInferEnhancer.InferFlavors.getAll())
.add(CxxCompilationDatabase.COMPILATION_DATABASE)
.build();
for (Flavor flavor : forbiddenFlavors) {
try {
nodeBuilderFactory
.getNodeBuilder(
BuildTargetFactory.newInstance(
"//foo:xctest#" + "iphoneos-i386,iphoneos-x86_64," + flavor.toString()))
.build(resolver);
} catch (HumanReadableException e) {
exception = e;
}
assertThat(exception, notNullValue());
assertThat(
"Should throw exception about special build actions.",
exception.getHumanReadableErrorMessage(),
endsWith("Fat binaries is only supported when building an actual binary."));
}
}
/** Rule builders pass BuildTarget as a constructor arg, so this is unfortunately necessary. */
private interface NodeBuilderFactory {
AbstractNodeBuilder<?, ?, ?> getNodeBuilder(BuildTarget target);
}
}