/*
* Copyright 2015-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.android;
import static org.hamcrest.Matchers.hasItems;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import com.facebook.buck.cli.BuckConfig;
import com.facebook.buck.cli.FakeBuckConfig;
import com.facebook.buck.cxx.CxxBuckConfig;
import com.facebook.buck.cxx.CxxLinkableEnhancer;
import com.facebook.buck.cxx.CxxPlatformUtils;
import com.facebook.buck.cxx.CxxPreprocessAndCompile;
import com.facebook.buck.cxx.CxxSource;
import com.facebook.buck.cxx.CxxSourceRuleFactory;
import com.facebook.buck.cxx.Linker;
import com.facebook.buck.cxx.NativeLinkableInput;
import com.facebook.buck.io.AlwaysFoundExecutableFinder;
import com.facebook.buck.io.MoreFiles;
import com.facebook.buck.io.ProjectFilesystem;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.model.BuildTargetFactory;
import com.facebook.buck.model.InternalFlavor;
import com.facebook.buck.model.Pair;
import com.facebook.buck.parser.NoSuchBuildTargetException;
import com.facebook.buck.rules.BuildRule;
import com.facebook.buck.rules.BuildRuleResolver;
import com.facebook.buck.rules.DefaultTargetNodeToBuildRuleTransformer;
import com.facebook.buck.rules.FakeBuildRuleParamsBuilder;
import com.facebook.buck.rules.FakeSourcePath;
import com.facebook.buck.rules.RuleKey;
import com.facebook.buck.rules.SourcePathResolver;
import com.facebook.buck.rules.SourcePathRuleFinder;
import com.facebook.buck.rules.TargetGraph;
import com.facebook.buck.rules.args.SourcePathArg;
import com.facebook.buck.rules.keys.DefaultRuleKeyFactory;
import com.facebook.buck.testutil.FakeFileHashCache;
import com.facebook.buck.testutil.integration.TemporaryPaths;
import com.facebook.buck.util.environment.Platform;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import org.hamcrest.Matchers;
import org.junit.Rule;
import org.junit.Test;
public class NdkCxxPlatformTest {
@Rule public TemporaryPaths tmp = new TemporaryPaths();
enum Operation {
COMPILE,
PREPROCESS_AND_COMPILE,
}
// Create and return some rule keys from a dummy source for the given platforms.
private ImmutableMap<NdkCxxPlatforms.TargetCpuType, RuleKey> constructCompileRuleKeys(
Operation operation,
ImmutableMap<NdkCxxPlatforms.TargetCpuType, NdkCxxPlatform> cxxPlatforms) {
BuildRuleResolver resolver =
new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer());
SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(resolver);
SourcePathResolver pathResolver = new SourcePathResolver(ruleFinder);
String source = "source.cpp";
DefaultRuleKeyFactory ruleKeyFactory =
new DefaultRuleKeyFactory(
0,
FakeFileHashCache.createFromStrings(
ImmutableMap.<String, String>builder()
.put("source.cpp", Strings.repeat("a", 40))
.build()),
pathResolver,
ruleFinder);
BuildTarget target = BuildTargetFactory.newInstance("//:target");
ImmutableMap.Builder<NdkCxxPlatforms.TargetCpuType, RuleKey> ruleKeys = ImmutableMap.builder();
for (Map.Entry<NdkCxxPlatforms.TargetCpuType, NdkCxxPlatform> entry : cxxPlatforms.entrySet()) {
CxxSourceRuleFactory cxxSourceRuleFactory =
CxxSourceRuleFactory.builder()
.setParams(new FakeBuildRuleParamsBuilder(target).build())
.setResolver(resolver)
.setPathResolver(pathResolver)
.setRuleFinder(ruleFinder)
.setCxxBuckConfig(CxxPlatformUtils.DEFAULT_CONFIG)
.setCxxPlatform(entry.getValue().getCxxPlatform())
.setPicType(CxxSourceRuleFactory.PicType.PIC)
.build();
CxxPreprocessAndCompile rule;
switch (operation) {
case PREPROCESS_AND_COMPILE:
rule =
cxxSourceRuleFactory.createPreprocessAndCompileBuildRule(
source,
CxxSource.of(CxxSource.Type.CXX, new FakeSourcePath(source), ImmutableList.of()));
break;
case COMPILE:
rule =
cxxSourceRuleFactory.createCompileBuildRule(
source,
CxxSource.of(
CxxSource.Type.CXX_CPP_OUTPUT,
new FakeSourcePath(source),
ImmutableList.of()));
break;
default:
throw new IllegalStateException();
}
ruleKeys.put(entry.getKey(), ruleKeyFactory.build(rule));
}
return ruleKeys.build();
}
// Create and return some rule keys from a dummy source for the given platforms.
private ImmutableMap<NdkCxxPlatforms.TargetCpuType, RuleKey> constructLinkRuleKeys(
ImmutableMap<NdkCxxPlatforms.TargetCpuType, NdkCxxPlatform> cxxPlatforms)
throws NoSuchBuildTargetException {
BuildRuleResolver resolver =
new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer());
SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(resolver);
SourcePathResolver pathResolver = new SourcePathResolver(ruleFinder);
DefaultRuleKeyFactory ruleKeyFactory =
new DefaultRuleKeyFactory(
0,
FakeFileHashCache.createFromStrings(
ImmutableMap.<String, String>builder()
.put("input.o", Strings.repeat("a", 40))
.build()),
pathResolver,
ruleFinder);
BuildTarget target = BuildTargetFactory.newInstance("//:target");
ImmutableMap.Builder<NdkCxxPlatforms.TargetCpuType, RuleKey> ruleKeys = ImmutableMap.builder();
for (Map.Entry<NdkCxxPlatforms.TargetCpuType, NdkCxxPlatform> entry : cxxPlatforms.entrySet()) {
BuildRule rule =
CxxLinkableEnhancer.createCxxLinkableBuildRule(
CxxPlatformUtils.DEFAULT_CONFIG,
entry.getValue().getCxxPlatform(),
new FakeBuildRuleParamsBuilder(target).build(),
resolver,
pathResolver,
ruleFinder,
target,
Linker.LinkType.EXECUTABLE,
Optional.empty(),
Paths.get("output"),
Linker.LinkableDepType.SHARED,
/* thinLto */ false,
ImmutableList.of(),
Optional.empty(),
Optional.empty(),
ImmutableSet.of(),
NativeLinkableInput.builder()
.setArgs(SourcePathArg.from(new FakeSourcePath("input.o")))
.build());
ruleKeys.put(entry.getKey(), ruleKeyFactory.build(rule));
}
return ruleKeys.build();
}
@Test
public void testNdkMajorVersion() {
assertEquals(9, NdkCxxPlatforms.getNdkMajorVersion("r9"));
assertEquals(9, NdkCxxPlatforms.getNdkMajorVersion("r9b"));
assertEquals(10, NdkCxxPlatforms.getNdkMajorVersion("r10c"));
assertEquals(10, NdkCxxPlatforms.getNdkMajorVersion("r10e"));
assertEquals(11, NdkCxxPlatforms.getNdkMajorVersion("11.0.1234"));
assertEquals(11, NdkCxxPlatforms.getNdkMajorVersion("11.2.2725575"));
assertEquals(12, NdkCxxPlatforms.getNdkMajorVersion("12.0.1234"));
assertEquals(12, NdkCxxPlatforms.getNdkMajorVersion("12.1.2977051"));
}
@Test
public void testNdkFlags() throws IOException, InterruptedException {
ProjectFilesystem filesystem = new ProjectFilesystem(tmp.getRoot());
Path ndkRoot = tmp.newFolder("android-ndk-r10b");
NdkCxxPlatformTargetConfiguration targetConfiguration =
NdkCxxPlatforms.getTargetConfiguration(
NdkCxxPlatforms.TargetCpuType.X86,
NdkCxxPlatformCompiler.builder()
.setType(NdkCxxPlatformCompiler.Type.GCC)
.setVersion("gcc-version")
.setGccVersion("clang-version")
.build(),
"target-app-platform");
MoreFiles.writeLinesToFile(ImmutableList.of("r9c"), ndkRoot.resolve("RELEASE.TXT"));
NdkCxxPlatform platform =
NdkCxxPlatforms.build(
CxxPlatformUtils.DEFAULT_CONFIG,
new AndroidBuckConfig(FakeBuckConfig.builder().build(), Platform.detect()),
filesystem,
InternalFlavor.of("android-x86"),
Platform.detect(),
ndkRoot,
targetConfiguration,
NdkCxxRuntime.GNUSTL,
new AlwaysFoundExecutableFinder(),
false /* strictToolchainPaths */);
assertThat(platform.getCxxPlatform().getCflags(), hasItems("-std=gnu11", "-O2"));
assertThat(
platform.getCxxPlatform().getCxxflags(),
hasItems("-std=gnu++11", "-O2", "-fno-exceptions", "-fno-rtti"));
assertThat(platform.getCxxPlatform().getCppflags(), hasItems("-std=gnu11", "-O2"));
}
public void testExtraNdkFlags() throws IOException, InterruptedException {
ProjectFilesystem filesystem = new ProjectFilesystem(tmp.getRoot());
Path ndkRoot = tmp.newFolder("android-ndk-r10b");
NdkCxxPlatformTargetConfiguration targetConfiguration =
NdkCxxPlatforms.getTargetConfiguration(
NdkCxxPlatforms.TargetCpuType.X86,
NdkCxxPlatformCompiler.builder()
.setType(NdkCxxPlatformCompiler.Type.GCC)
.setVersion("gcc-version")
.setGccVersion("clang-version")
.build(),
"target-app-platform");
MoreFiles.writeLinesToFile(ImmutableList.of("r9c"), ndkRoot.resolve("RELEASE.TXT"));
BuckConfig buckConfig =
FakeBuckConfig.builder()
.setSections(
ImmutableMap.of(
"ndk",
ImmutableMap.of(
"extra_cflags", "-fa-flag",
"extra_cppflags", "-DSOME_FLAG -DSOME_OTHER_FLAG",
"extra_cxxflags", "-fc++flag"),
"cxx",
ImmutableMap.of(
"cxxflags", "-Wignored-cxx-flag",
"cflags", "-Wignored-c-flag",
"cppflags", "-Wignored-cpp-flag")))
.build();
NdkCxxPlatform platform =
NdkCxxPlatforms.build(
new CxxBuckConfig(buckConfig),
new AndroidBuckConfig(buckConfig, Platform.detect()),
filesystem,
InternalFlavor.of("android-x86"),
Platform.detect(),
ndkRoot,
targetConfiguration,
NdkCxxRuntime.GNUSTL,
new AlwaysFoundExecutableFinder(),
false /* strictToolchainPaths */);
assertThat(platform.getCxxPlatform().getCflags(), hasItems("-std=gnu11", "-O2", "-fa-flag"));
assertThat(
platform.getCxxPlatform().getCxxflags(),
hasItems("-std=gnu++11", "-O2", "-fno-exceptions", "-fno-rtti", "-fc++flag"));
assertThat(
platform.getCxxPlatform().getCppflags(),
hasItems("-std=gnu11", "-O2", "-DSOME_FLAG", "-DSOME_OTHER_FLAG"));
}
// The important aspects we check for in rule keys is that the host platform and the path
// to the NDK don't cause changes.
@Test
public void checkRootAndPlatformDoNotAffectRuleKeys() throws Exception {
ProjectFilesystem filesystem = new ProjectFilesystem(tmp.getRoot());
// Test all major compiler and runtime combinations.
ImmutableList<Pair<NdkCxxPlatformCompiler.Type, NdkCxxRuntime>> configs =
ImmutableList.of(
new Pair<>(NdkCxxPlatformCompiler.Type.GCC, NdkCxxRuntime.GNUSTL),
new Pair<>(NdkCxxPlatformCompiler.Type.CLANG, NdkCxxRuntime.GNUSTL),
new Pair<>(NdkCxxPlatformCompiler.Type.CLANG, NdkCxxRuntime.LIBCXX));
for (Pair<NdkCxxPlatformCompiler.Type, NdkCxxRuntime> config : configs) {
Map<String, ImmutableMap<NdkCxxPlatforms.TargetCpuType, RuleKey>>
preprocessAndCompileRukeKeys = new HashMap<>();
Map<String, ImmutableMap<NdkCxxPlatforms.TargetCpuType, RuleKey>> compileRukeKeys =
new HashMap<>();
Map<String, ImmutableMap<NdkCxxPlatforms.TargetCpuType, RuleKey>> linkRukeKeys =
new HashMap<>();
// Iterate building up rule keys for combinations of different platforms and NDK root
// directories.
for (String dir : ImmutableList.of("android-ndk-r9c", "android-ndk-r10b")) {
for (Platform platform :
ImmutableList.of(Platform.LINUX, Platform.MACOS, Platform.WINDOWS)) {
Path root = tmp.newFolder(dir);
MoreFiles.writeLinesToFile(ImmutableList.of("r9c"), root.resolve("RELEASE.TXT"));
ImmutableMap<NdkCxxPlatforms.TargetCpuType, NdkCxxPlatform> platforms =
NdkCxxPlatforms.getPlatforms(
CxxPlatformUtils.DEFAULT_CONFIG,
new AndroidBuckConfig(FakeBuckConfig.builder().build(), platform),
filesystem,
root,
NdkCxxPlatformCompiler.builder()
.setType(config.getFirst())
.setVersion("gcc-version")
.setGccVersion("clang-version")
.build(),
NdkCxxRuntime.GNUSTL,
"target-app-platform",
ImmutableSet.of("x86"),
platform,
new AlwaysFoundExecutableFinder(),
/* strictToolchainPaths */ false);
preprocessAndCompileRukeKeys.put(
String.format("NdkCxxPlatform(%s, %s)", dir, platform),
constructCompileRuleKeys(Operation.PREPROCESS_AND_COMPILE, platforms));
compileRukeKeys.put(
String.format("NdkCxxPlatform(%s, %s)", dir, platform),
constructCompileRuleKeys(Operation.COMPILE, platforms));
linkRukeKeys.put(
String.format("NdkCxxPlatform(%s, %s)", dir, platform),
constructLinkRuleKeys(platforms));
MoreFiles.deleteRecursively(root);
}
}
// If everything worked, we should be able to collapse all the generated rule keys down
// to a singleton set.
assertThat(
Arrays.toString(preprocessAndCompileRukeKeys.entrySet().toArray()),
Sets.newHashSet(preprocessAndCompileRukeKeys.values()),
Matchers.hasSize(1));
assertThat(
Arrays.toString(compileRukeKeys.entrySet().toArray()),
Sets.newHashSet(compileRukeKeys.values()),
Matchers.hasSize(1));
assertThat(
Arrays.toString(linkRukeKeys.entrySet().toArray()),
Sets.newHashSet(linkRukeKeys.values()),
Matchers.hasSize(1));
}
}
@Test
public void headerVerificationWhitelistsNdkRoot() throws InterruptedException, IOException {
ProjectFilesystem filesystem = new ProjectFilesystem(tmp.getRoot());
String dir = "android-ndk-r9c";
Path root = tmp.newFolder(dir);
MoreFiles.writeLinesToFile(ImmutableList.of("r9c"), root.resolve("RELEASE.TXT"));
ImmutableMap<NdkCxxPlatforms.TargetCpuType, NdkCxxPlatform> platforms =
NdkCxxPlatforms.getPlatforms(
CxxPlatformUtils.DEFAULT_CONFIG,
new AndroidBuckConfig(FakeBuckConfig.builder().build(), Platform.detect()),
filesystem,
root,
NdkCxxPlatformCompiler.builder()
.setType(NdkCxxPlatformCompiler.Type.GCC)
.setVersion("gcc-version")
.setGccVersion("clang-version")
.build(),
NdkCxxRuntime.GNUSTL,
"target-app-platform",
ImmutableSet.of("x86"),
Platform.LINUX,
new AlwaysFoundExecutableFinder(),
/* strictToolchainPaths */ false);
for (NdkCxxPlatform ndkCxxPlatform : platforms.values()) {
assertTrue(
ndkCxxPlatform
.getCxxPlatform()
.getHeaderVerification()
.isWhitelisted(root.resolve("test.h").toString()));
}
}
}