// 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); } } }