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