/* * Copyright 2014-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.cxx; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import com.facebook.buck.cli.FakeBuckConfig; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.model.BuildTargetFactory; import com.facebook.buck.parser.NoSuchBuildTargetException; import com.facebook.buck.rules.BuildRule; import com.facebook.buck.rules.BuildRuleParams; import com.facebook.buck.rules.BuildRuleResolver; import com.facebook.buck.rules.DefaultTargetNodeToBuildRuleTransformer; import com.facebook.buck.rules.FakeBuildRule; import com.facebook.buck.rules.FakeBuildRuleParamsBuilder; import com.facebook.buck.rules.FakeSourcePath; import com.facebook.buck.rules.SourcePath; import com.facebook.buck.rules.SourcePathResolver; import com.facebook.buck.rules.SourcePathRuleFinder; import com.facebook.buck.rules.TargetGraph; import com.facebook.buck.shell.Genrule; import com.facebook.buck.shell.GenruleBuilder; import com.facebook.buck.testutil.FakeProjectFilesystem; import com.google.common.base.Preconditions; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSortedSet; import java.nio.file.Path; import java.nio.file.Paths; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; public class CxxPreprocessablesTest { private static class FakeCxxPreprocessorDep extends FakeBuildRule implements CxxPreprocessorDep { private final CxxPreprocessorInput input; public FakeCxxPreprocessorDep( BuildRuleParams params, SourcePathResolver resolver, CxxPreprocessorInput input) { super(params, resolver); this.input = Preconditions.checkNotNull(input); } @Override public Iterable<? extends CxxPreprocessorDep> getCxxPreprocessorDeps(CxxPlatform cxxPlatform) { return FluentIterable.from(getBuildDeps()).filter(CxxPreprocessorDep.class); } @Override public CxxPreprocessorInput getCxxPreprocessorInput( CxxPlatform cxxPlatform, HeaderVisibility headerVisibility) { return input; } @Override public ImmutableMap<BuildTarget, CxxPreprocessorInput> getTransitiveCxxPreprocessorInput( CxxPlatform cxxPlatform, HeaderVisibility headerVisibility) throws NoSuchBuildTargetException { ImmutableMap.Builder<BuildTarget, CxxPreprocessorInput> builder = ImmutableMap.builder(); builder.put(getBuildTarget(), getCxxPreprocessorInput(cxxPlatform, headerVisibility)); for (BuildRule dep : getBuildDeps()) { if (dep instanceof CxxPreprocessorDep) { builder.putAll( ((CxxPreprocessorDep) dep) .getTransitiveCxxPreprocessorInput(cxxPlatform, headerVisibility)); } } return builder.build(); } } private static FakeCxxPreprocessorDep createFakeCxxPreprocessorDep( BuildTarget target, SourcePathResolver resolver, CxxPreprocessorInput input, BuildRule... deps) { return new FakeCxxPreprocessorDep( new FakeBuildRuleParamsBuilder(target) .setDeclaredDeps(ImmutableSortedSet.copyOf(deps)) .build(), resolver, input); } private static FakeCxxPreprocessorDep createFakeCxxPreprocessorDep( String target, SourcePathResolver resolver, CxxPreprocessorInput input, BuildRule... deps) { return createFakeCxxPreprocessorDep( BuildTargetFactory.newInstance(target), resolver, input, deps); } private static FakeBuildRule createFakeBuildRule( BuildTarget target, SourcePathResolver resolver, BuildRule... deps) { return new FakeBuildRule( new FakeBuildRuleParamsBuilder(target) .setDeclaredDeps(ImmutableSortedSet.copyOf(deps)) .build(), resolver); } @Rule public ExpectedException exception = ExpectedException.none(); @Test public void resolveHeaderMap() { BuildTarget target = BuildTargetFactory.newInstance("//hello/world:test"); ImmutableMap<String, SourcePath> headerMap = ImmutableMap.of( "foo/bar.h", new FakeSourcePath("header1.h"), "foo/hello.h", new FakeSourcePath("header2.h")); // Verify that the resolveHeaderMap returns sane results. ImmutableMap<Path, SourcePath> expected = ImmutableMap.of( target.getBasePath().resolve("foo/bar.h"), new FakeSourcePath("header1.h"), target.getBasePath().resolve("foo/hello.h"), new FakeSourcePath("header2.h")); ImmutableMap<Path, SourcePath> actual = CxxPreprocessables.resolveHeaderMap(target.getBasePath(), headerMap); assertEquals(expected, actual); } @Test public void getTransitiveCxxPreprocessorInput() throws Exception { FakeProjectFilesystem filesystem = new FakeProjectFilesystem(); SourcePathResolver pathResolver = new SourcePathResolver( new SourcePathRuleFinder( new BuildRuleResolver( TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer()))); CxxPlatform cxxPlatform = CxxPlatformUtils.build( new CxxBuckConfig(FakeBuckConfig.builder().setFilesystem(filesystem).build())); // Setup a simple CxxPreprocessorDep which contributes components to preprocessing. BuildTarget cppDepTarget1 = BuildTargetFactory.newInstance(filesystem.getRootPath(), "//:cpp1"); CxxPreprocessorInput input1 = CxxPreprocessorInput.builder() .addRules(cppDepTarget1) .putPreprocessorFlags(CxxSource.Type.C, "-Dtest=yes") .putPreprocessorFlags(CxxSource.Type.CXX, "-Dtest=yes") .build(); BuildTarget depTarget1 = BuildTargetFactory.newInstance(filesystem.getRootPath(), "//:dep1"); FakeCxxPreprocessorDep dep1 = createFakeCxxPreprocessorDep(depTarget1, pathResolver, input1); // Setup another simple CxxPreprocessorDep which contributes components to preprocessing. BuildTarget cppDepTarget2 = BuildTargetFactory.newInstance(filesystem.getRootPath(), "//:cpp2"); CxxPreprocessorInput input2 = CxxPreprocessorInput.builder() .addRules(cppDepTarget2) .putPreprocessorFlags(CxxSource.Type.C, "-DBLAH") .putPreprocessorFlags(CxxSource.Type.CXX, "-DBLAH") .build(); BuildTarget depTarget2 = BuildTargetFactory.newInstance("//:dep2"); FakeCxxPreprocessorDep dep2 = createFakeCxxPreprocessorDep(depTarget2, pathResolver, input2); // Create a normal dep which depends on the two CxxPreprocessorDep rules above. BuildTarget depTarget3 = BuildTargetFactory.newInstance(filesystem.getRootPath(), "//:dep3"); CxxPreprocessorInput nothing = CxxPreprocessorInput.EMPTY; FakeCxxPreprocessorDep dep3 = createFakeCxxPreprocessorDep(depTarget3, pathResolver, nothing, dep1, dep2); // Verify that getTransitiveCxxPreprocessorInput gets all CxxPreprocessorInput objects // from the relevant rules above. ImmutableList<CxxPreprocessorInput> expected = ImmutableList.of(nothing, input1, input2); ImmutableList<CxxPreprocessorInput> actual = ImmutableList.copyOf( CxxPreprocessables.getTransitiveCxxPreprocessorInput( cxxPlatform, ImmutableList.<BuildRule>of(dep3))); assertEquals(expected, actual); } @Test public void createHeaderSymlinkTreeBuildRuleHasNoDeps() throws Exception { BuildRuleResolver resolver = new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer()); SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(resolver); SourcePathResolver pathResolver = new SourcePathResolver(ruleFinder); FakeProjectFilesystem filesystem = new FakeProjectFilesystem(); // Setup up the main build target and build params, which some random dep. We'll make // sure the dep doesn't get propagated to the symlink rule below. FakeBuildRule dep = createFakeBuildRule( BuildTargetFactory.newInstance(filesystem.getRootPath(), "//random:dep"), pathResolver); BuildTarget target = BuildTargetFactory.newInstance(filesystem.getRootPath(), "//foo:bar"); BuildRuleParams params = new FakeBuildRuleParamsBuilder(target) .setDeclaredDeps(ImmutableSortedSet.of(dep)) .setProjectFilesystem(filesystem) .build(); Path root = Paths.get("root"); // Setup a simple genrule we can wrap in a ExplicitBuildTargetSourcePath to model a input source // that is built by another rule. Genrule genrule = GenruleBuilder.newGenruleBuilder( BuildTargetFactory.newInstance(filesystem.getRootPath(), "//:genrule")) .setOut("foo/bar.o") .build(resolver); // Setup the link map with both a regular path-based source path and one provided by // another build rule. ImmutableMap<Path, SourcePath> links = ImmutableMap.of( Paths.get("link1"), new FakeSourcePath("hello"), Paths.get("link2"), genrule.getSourcePathToOutput()); // Build our symlink tree rule using the helper method. HeaderSymlinkTree symlinkTree = CxxPreprocessables.createHeaderSymlinkTreeBuildRule( target, params.getProjectFilesystem(), root, links, CxxPreprocessables.HeaderMode.SYMLINK_TREE_ONLY, ruleFinder); // Verify that the symlink tree has no deps. This is by design, since setting symlinks can // be done completely independently from building the source that the links point to and // independently from the original deps attached to the input build rule params. assertTrue(symlinkTree.getBuildDeps().isEmpty()); } @Test public void getTransitiveNativeLinkableInputDoesNotTraversePastNonNativeLinkables() throws Exception { SourcePathResolver pathResolver = new SourcePathResolver( new SourcePathRuleFinder( new BuildRuleResolver( TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer()))); CxxPlatform cxxPlatform = CxxPlatformUtils.build(new CxxBuckConfig(FakeBuckConfig.builder().build())); // Create a native linkable that sits at the bottom of the dep chain. String sentinal = "bottom"; CxxPreprocessorInput bottomInput = CxxPreprocessorInput.builder().putPreprocessorFlags(CxxSource.Type.C, sentinal).build(); BuildRule bottom = createFakeCxxPreprocessorDep("//:bottom", pathResolver, bottomInput); // Create a non-native linkable that sits in the middle of the dep chain, preventing // traversals to the bottom native linkable. BuildRule middle = new FakeBuildRule("//:middle", pathResolver, bottom); // Create a native linkable that sits at the top of the dep chain. CxxPreprocessorInput topInput = CxxPreprocessorInput.EMPTY; BuildRule top = createFakeCxxPreprocessorDep("//:top", pathResolver, topInput, middle); // Now grab all input via traversing deps and verify that the middle rule prevents pulling // in the bottom input. CxxPreprocessorInput totalInput = CxxPreprocessorInput.concat( CxxPreprocessables.getTransitiveCxxPreprocessorInput( cxxPlatform, ImmutableList.of(top))); assertTrue(bottomInput.getPreprocessorFlags().get(CxxSource.Type.C).contains(sentinal)); assertFalse(totalInput.getPreprocessorFlags().get(CxxSource.Type.C).contains(sentinal)); } }