// Copyright 2015 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.bazel.rules.android.ndkcrosstools;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.lib.bazel.rules.android.ndkcrosstools.r10e.NdkMajorRevisionR10;
import com.google.devtools.build.lib.bazel.rules.android.ndkcrosstools.r12.NdkMajorRevisionR12;
import com.google.devtools.build.lib.events.NullEventHandler;
import com.google.devtools.build.lib.util.ResourceFileLoader;
import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig.CToolchain;
import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig.CrosstoolRelease;
import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig.DefaultCpuToolchain;
import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig.ToolPath;
import java.io.IOException;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map.Entry;
import java.util.Set;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
/** Tests for {@link AndroidNdkCrosstools}. */
@RunWith(Parameterized.class)
public class AndroidNdkCrosstoolsTest {
private static final String HOST_PLATFORM = "linux-x86_64";
private static final String REPOSITORY_NAME = "testrepository";
private static class AndroidNdkCrosstoolsTestParams {
private final ApiLevel apiLevel;
private final NdkRelease ndkRelease;
// ndkfiles.txt contains a list of every file in the ndk, created using this command at the
// root of the Android NDK for version r10e (64-bit):
// find . -xtype f | sed 's|^\./||' | sort
// and similarly for ndkdirectories, except "-xtype d" is used.
//
// It's unfortunate to have files like these, since they're large and brittle, but since the
// whole NDK can't be checked in to test against, it's about the most that can be done right
// now.
private final String ndkFilesFilename;
private final String ndkDirectoriesFilename;
AndroidNdkCrosstoolsTestParams(
ApiLevel apiLevel,
NdkRelease ndkRelease,
String ndkFilesFilename,
String ndkDirectoriesFilename) {
this.apiLevel = apiLevel;
this.ndkRelease = ndkRelease;
this.ndkFilesFilename = ndkFilesFilename;
this.ndkDirectoriesFilename = ndkDirectoriesFilename;
}
NdkMajorRevision getNdkMajorRevision() {
return AndroidNdkCrosstools.KNOWN_NDK_MAJOR_REVISIONS.get(ndkRelease.majorRevision);
}
ImmutableSet<String> getNdkFiles() throws IOException {
String ndkFilesFileContent =
ResourceFileLoader.loadResource(AndroidNdkCrosstoolsTest.class, ndkFilesFilename);
ImmutableSet.Builder<String> ndkFiles = ImmutableSet.builder();
for (String line : ndkFilesFileContent.split("\n")) {
// The contents of the NDK are placed at "external/%repositoryName%/ndk".
// The "external/%repositoryName%" part is removed using NdkPaths.stripRepositoryPrefix,
// but to make it easier the "ndk/" part is added here.
ndkFiles.add("ndk/" + line);
}
return ndkFiles.build();
}
ImmutableSet<String> getNdkDirectories() throws IOException {
String ndkFilesFileContent =
ResourceFileLoader.loadResource(AndroidNdkCrosstoolsTest.class, ndkDirectoriesFilename);
ImmutableSet.Builder<String> ndkDirectories = ImmutableSet.builder();
for (String line : ndkFilesFileContent.split("\n")) {
ndkDirectories.add("ndk/" + line);
}
return ndkDirectories.build();
}
}
@Parameters
public static Collection<AndroidNdkCrosstoolsTestParams[]> data() {
return ImmutableList.of(
new AndroidNdkCrosstoolsTestParams[] {
new AndroidNdkCrosstoolsTestParams(
new NdkMajorRevisionR10()
.apiLevel(NullEventHandler.INSTANCE, REPOSITORY_NAME, "21"),
NdkRelease.create("r10e (64-bit)"),
"ndkfiles.txt",
"ndkdirectories.txt"
)
},
new AndroidNdkCrosstoolsTestParams[] {
new AndroidNdkCrosstoolsTestParams(
new NdkMajorRevisionR12()
.apiLevel(NullEventHandler.INSTANCE, REPOSITORY_NAME, "21"),
NdkRelease.create("Pkg.Desc = Android NDK\nPkg.Revision = 12.1.297705\n"),
"ndk12bfiles.txt",
"ndk12bdirectories.txt"
)
});
}
private final ImmutableSet<String> ndkFiles;
private final ImmutableSet<String> ndkDirectories;
private final ImmutableList<CrosstoolRelease> crosstoolReleases;
private final ImmutableMap<String, String> stlFilegroups;
public AndroidNdkCrosstoolsTest(AndroidNdkCrosstoolsTestParams params) throws IOException {
// NDK test data is based on the x86 64-bit Linux Android NDK.
NdkPaths ndkPaths = new NdkPaths(REPOSITORY_NAME, HOST_PLATFORM, params.apiLevel);
ImmutableList.Builder<CrosstoolRelease> crosstools = ImmutableList.builder();
ImmutableMap.Builder<String, String> stlFilegroupsBuilder = ImmutableMap.builder();
for (StlImpl ndkStlImpl : StlImpls.get(ndkPaths)) {
// Protos are immutable, so this can be shared between tests.
CrosstoolRelease crosstool =
params.getNdkMajorRevision().crosstoolRelease(ndkPaths, ndkStlImpl, HOST_PLATFORM);
crosstools.add(crosstool);
stlFilegroupsBuilder.putAll(ndkStlImpl.getFilegroupNamesAndFilegroupFileGlobPatterns());
}
crosstoolReleases = crosstools.build();
stlFilegroups = stlFilegroupsBuilder.build();
ndkFiles = params.getNdkFiles();
ndkDirectories = params.getNdkDirectories();
}
@Test
public void testPathsExist() throws Exception {
for (CrosstoolRelease crosstool : crosstoolReleases) {
for (CToolchain toolchain : crosstool.getToolchainList()) {
// Test that all tool paths exist.
for (ToolPath toolpath : toolchain.getToolPathList()) {
// TODO(tmsriram): Not all crosstools contain llvm-profdata tool yet, remove
// the check once llvm-profdata becomes always available.
if (toolpath.getPath().contains("llvm-profdata")) {
continue;
}
assertThat(ndkFiles).contains(toolpath.getPath());
}
// Test that all cxx_builtin_include_directory paths exist.
for (String includeDirectory : toolchain.getCxxBuiltinIncludeDirectoryList()) {
// Special case for builtin_sysroot.
if (!includeDirectory.equals("%sysroot%/usr/include")) {
String path = NdkPaths.stripRepositoryPrefix(includeDirectory);
assertThat(ndkDirectories).contains(path);
}
}
// Test that the builtin_sysroot path exists.
{
String builtinSysroot = NdkPaths.stripRepositoryPrefix(toolchain.getBuiltinSysroot());
assertThat(ndkDirectories).contains(builtinSysroot);
}
// Test that all include directories added through unfiltered_cxx_flag exist.
for (String flag : toolchain.getUnfilteredCxxFlagList()) {
if (!flag.equals("-isystem")) {
flag = NdkPaths.stripRepositoryPrefix(flag);
assertThat(ndkDirectories).contains(flag);
}
}
}
}
}
// Regression test for b/36091573
@Test
public void testBuiltinIncludesDirectories() {
for (CrosstoolRelease crosstool : crosstoolReleases) {
for (CToolchain toolchain : crosstool.getToolchainList()) {
// Each toolchain has at least one built-in include directory
assertThat(toolchain.getCxxBuiltinIncludeDirectoryList()).isNotEmpty();
for (String flag : toolchain.getUnfilteredCxxFlagList()) {
// This list only contains "-isystem" and the values after "-isystem".
if (!flag.equals("-isystem")) {
// We should NOT be setting -isystem for the builtin includes directories. They are
// already on the search list and adding the -isystem flag just changes their priority.
assertThat(toolchain.getCxxBuiltinIncludeDirectoryList()).doesNotContain(flag);
}
}
}
}
}
@Test
public void testStlFilegroupPathsExist() throws Exception {
for (String fileglob : stlFilegroups.values()) {
String fileglobNoWildcard = fileglob.substring(0, fileglob.lastIndexOf('/'));
assertThat(ndkDirectories).contains(fileglobNoWildcard);
assertThat(findFileByPattern(fileglob)).isTrue();
}
}
private boolean findFileByPattern(String globPattern) {
String start = globPattern.substring(0, globPattern.indexOf('*'));
String end = globPattern.substring(globPattern.lastIndexOf('.'));
for (String f : ndkFiles) {
if (f.startsWith(start) && f.endsWith(end)) {
return true;
}
}
return false;
}
@Test
public void testAllToolchainsHaveRuntimesFilegroup() {
for (CrosstoolRelease crosstool : crosstoolReleases) {
for (CToolchain toolchain : crosstool.getToolchainList()) {
assertThat(toolchain.getDynamicRuntimesFilegroup()).isNotEmpty();
assertThat(toolchain.getStaticRuntimesFilegroup()).isNotEmpty();
}
}
}
@Test
public void testDefaultToolchainsExist() {
for (CrosstoolRelease crosstool : crosstoolReleases) {
Set<String> toolchainNames = new HashSet<>();
for (CToolchain toolchain : crosstool.getToolchainList()) {
toolchainNames.add(toolchain.getToolchainIdentifier());
}
for (DefaultCpuToolchain defaultCpuToolchain : crosstool.getDefaultToolchainList()) {
assertThat(toolchainNames).contains(defaultCpuToolchain.getToolchainIdentifier());
}
}
}
/**
* Tests that each (cpu, compiler, glibc) triple in each crosstool is unique in that crosstool.
*/
@Test
public void testCrosstoolTriples() {
StringBuilder errorBuilder = new StringBuilder();
for (CrosstoolRelease crosstool : crosstoolReleases) {
// Create a map of (cpu, compiler, glibc) triples -> toolchain.
ImmutableMultimap.Builder<String, CToolchain> triples = ImmutableMultimap.builder();
for (CToolchain toolchain : crosstool.getToolchainList()) {
String triple = "(" + Joiner.on(", ").join(
toolchain.getTargetCpu(),
toolchain.getCompiler(),
toolchain.getTargetLibc()) + ")";
triples.put(triple, toolchain);
}
// Collect all the duplicate triples.
for (Entry<String, Collection<CToolchain>> entry : triples.build().asMap().entrySet()) {
if (entry.getValue().size() > 1) {
errorBuilder.append(entry.getKey() + ": " + Joiner.on(", ").join(
Collections2.transform(entry.getValue(), new Function<CToolchain, String>() {
@Override public String apply(CToolchain toolchain) {
return toolchain.getToolchainIdentifier();
}
})));
errorBuilder.append("\n");
}
}
errorBuilder.append("\n");
}
// This is a rather awkward condition to test on, but collecting all the duplicates first is
// the only way to make a useful error message rather than finding the errors one by one.
String error = errorBuilder.toString().trim();
if (!error.isEmpty()) {
fail("Toolchains contain duplicate (cpu, compiler, glibc) triples:\n" + error);
}
}
}