/* * 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.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertThat; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.model.BuildTargetFactory; import com.facebook.buck.rules.BuildRuleParams; import com.facebook.buck.rules.BuildRuleResolver; import com.facebook.buck.rules.DefaultBuildTargetSourcePath; import com.facebook.buck.rules.DefaultTargetNodeToBuildRuleTransformer; import com.facebook.buck.rules.FakeBuildRule; import com.facebook.buck.rules.FakeBuildRuleParamsBuilder; 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.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Iterables; import java.nio.file.Paths; import java.util.HashSet; import java.util.Optional; import java.util.Set; import org.hamcrest.Matchers; import org.junit.Test; public class CxxDescriptionEnhancerTest { @Test public void libraryTestIncludesPrivateHeadersOfLibraryUnderTest() throws Exception { SourcePathResolver pathResolver = new SourcePathResolver( new SourcePathRuleFinder( new BuildRuleResolver( TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer()))); BuildTarget libTarget = BuildTargetFactory.newInstance("//:lib"); BuildTarget testTarget = BuildTargetFactory.newInstance("//:test"); BuildRuleParams libParams = new FakeBuildRuleParamsBuilder(libTarget).build(); FakeCxxLibrary libRule = new FakeCxxLibrary( libParams, BuildTargetFactory.newInstance("//:header"), BuildTargetFactory.newInstance("//:symlink"), BuildTargetFactory.newInstance("//:privateheader"), BuildTargetFactory.newInstance("//:privatesymlink"), new FakeBuildRule("//:archive", pathResolver), new FakeBuildRule("//:shared", pathResolver), Paths.get("output/path/lib.so"), "lib.so", // Ensure the test is listed as a dep of the lib. ImmutableSortedSet.of(testTarget)); BuildRuleParams testParams = new FakeBuildRuleParamsBuilder(testTarget) .setDeclaredDeps(ImmutableSortedSet.of(libRule)) .build(); ImmutableList<CxxPreprocessorInput> combinedInput = CxxDescriptionEnhancer.collectCxxPreprocessorInput( testParams, CxxPlatformUtils.DEFAULT_PLATFORM, testParams.getBuildDeps(), ImmutableMultimap.of(), ImmutableList.of(), ImmutableSet.of(), CxxPreprocessables.getTransitiveCxxPreprocessorInput( CxxPlatformUtils.DEFAULT_PLATFORM, FluentIterable.from(testParams.getBuildDeps()) .filter(CxxPreprocessorDep.class::isInstance)), ImmutableList.of(), Optional.empty()); Set<SourcePath> roots = new HashSet<>(); for (CxxHeaders headers : CxxPreprocessorInput.concat(combinedInput).getIncludes()) { roots.add(headers.getRoot()); } assertThat( "Test of library should include both public and private headers", roots, Matchers.hasItems( new DefaultBuildTargetSourcePath(BuildTargetFactory.newInstance("//:symlink")), new DefaultBuildTargetSourcePath(BuildTargetFactory.newInstance("//:privatesymlink")))); } @Test public void libraryTestIncludesPublicHeadersOfDependenciesOfLibraryUnderTest() throws Exception { SourcePathResolver pathResolver = new SourcePathResolver( new SourcePathRuleFinder( new BuildRuleResolver( TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer()))); BuildTarget libTarget = BuildTargetFactory.newInstance("//:lib"); BuildTarget otherlibTarget = BuildTargetFactory.newInstance("//:otherlib"); BuildTarget testTarget = BuildTargetFactory.newInstance("//:test"); BuildRuleParams otherlibParams = new FakeBuildRuleParamsBuilder(otherlibTarget).build(); FakeCxxLibrary otherlibRule = new FakeCxxLibrary( otherlibParams, BuildTargetFactory.newInstance("//:otherheader"), BuildTargetFactory.newInstance("//:othersymlink"), BuildTargetFactory.newInstance("//:otherprivateheader"), BuildTargetFactory.newInstance("//:otherprivatesymlink"), new FakeBuildRule("//:archive", pathResolver), new FakeBuildRule("//:shared", pathResolver), Paths.get("output/path/lib.so"), "lib.so", // This library has no tests. ImmutableSortedSet.of()); BuildRuleParams libParams = new FakeBuildRuleParamsBuilder(libTarget) .setDeclaredDeps(ImmutableSortedSet.of(otherlibRule)) .build(); FakeCxxLibrary libRule = new FakeCxxLibrary( libParams, BuildTargetFactory.newInstance("//:header"), BuildTargetFactory.newInstance("//:symlink"), BuildTargetFactory.newInstance("//:privateheader"), BuildTargetFactory.newInstance("//:privatesymlink"), new FakeBuildRule("//:archive", pathResolver), new FakeBuildRule("//:shared", pathResolver), Paths.get("output/path/lib.so"), "lib.so", // Ensure the test is listed as a dep of the lib. ImmutableSortedSet.of(testTarget)); BuildRuleParams testParams = new FakeBuildRuleParamsBuilder(testTarget) .setDeclaredDeps(ImmutableSortedSet.of(libRule)) .build(); ImmutableList<CxxPreprocessorInput> combinedInput = CxxDescriptionEnhancer.collectCxxPreprocessorInput( testParams, CxxPlatformUtils.DEFAULT_PLATFORM, testParams.getBuildDeps(), ImmutableMultimap.of(), ImmutableList.of(), ImmutableSet.of(), CxxPreprocessables.getTransitiveCxxPreprocessorInput( CxxPlatformUtils.DEFAULT_PLATFORM, FluentIterable.from(testParams.getBuildDeps()) .filter(CxxPreprocessorDep.class::isInstance)), ImmutableList.of(), Optional.empty()); Set<SourcePath> roots = new HashSet<>(); for (CxxHeaders headers : CxxPreprocessorInput.concat(combinedInput).getIncludes()) { roots.add(headers.getRoot()); } assertThat( "Test of library should include public dependency headers", Iterables.transform( CxxPreprocessorInput.concat(combinedInput).getIncludes(), CxxHeaders::getRoot), allOf( hasItem( new DefaultBuildTargetSourcePath( BuildTargetFactory.newInstance("//:othersymlink"))), not( hasItem( new DefaultBuildTargetSourcePath( BuildTargetFactory.newInstance("//:otherprivatesymlink")))))); } @Test public void nonTestLibraryDepDoesNotIncludePrivateHeadersOfLibrary() throws Exception { SourcePathResolver pathResolver = new SourcePathResolver( new SourcePathRuleFinder( new BuildRuleResolver( TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer()))); BuildTarget libTarget = BuildTargetFactory.newInstance("//:lib"); BuildRuleParams libParams = new FakeBuildRuleParamsBuilder(libTarget).build(); FakeCxxLibrary libRule = new FakeCxxLibrary( libParams, BuildTargetFactory.newInstance("//:header"), BuildTargetFactory.newInstance("//:symlink"), BuildTargetFactory.newInstance("//:privateheader"), BuildTargetFactory.newInstance("//:privatesymlink"), new FakeBuildRule("//:archive", pathResolver), new FakeBuildRule("//:shared", pathResolver), Paths.get("output/path/lib.so"), "lib.so", // This library has no tests. ImmutableSortedSet.of()); BuildTarget otherLibDepTarget = BuildTargetFactory.newInstance("//:other"); BuildRuleParams otherLibDepParams = new FakeBuildRuleParamsBuilder(otherLibDepTarget) .setDeclaredDeps(ImmutableSortedSet.of(libRule)) .build(); ImmutableList<CxxPreprocessorInput> otherInput = CxxDescriptionEnhancer.collectCxxPreprocessorInput( otherLibDepParams, CxxPlatformUtils.DEFAULT_PLATFORM, otherLibDepParams.getBuildDeps(), ImmutableMultimap.of(), ImmutableList.of(), ImmutableSet.of(), CxxPreprocessables.getTransitiveCxxPreprocessorInput( CxxPlatformUtils.DEFAULT_PLATFORM, FluentIterable.from(otherLibDepParams.getBuildDeps()) .filter(CxxPreprocessorDep.class::isInstance)), ImmutableList.of(), Optional.empty()); Set<SourcePath> roots = new HashSet<>(); for (CxxHeaders headers : CxxPreprocessorInput.concat(otherInput).getIncludes()) { roots.add(headers.getRoot()); } assertThat( "Non-test rule with library dep should include public and not private headers", roots, allOf( hasItem(new DefaultBuildTargetSourcePath(BuildTargetFactory.newInstance("//:symlink"))), not( hasItem( new DefaultBuildTargetSourcePath( BuildTargetFactory.newInstance("//:privatesymlink")))))); } @Test public void testSonameExpansion() { assertThat(soname("libfoo.so", "dylib", "%s.dylib"), equalTo("libfoo.so")); assertThat(soname("libfoo.$(ext)", "good", "%s.bad"), equalTo("libfoo.good")); assertThat(soname("libfoo.$(ext 2.3)", "bad", "%s.good"), equalTo("libfoo.2.3.good")); assertThat(soname("libfoo.$(ext 2.3)", "bad", "good.%s"), equalTo("libfoo.good.2.3")); assertThat(soname("libfoo.$(ext 2.3)", "bad", "windows"), equalTo("libfoo.windows")); } /** Just a helper to make this shorter to write. */ private static String soname(String declared, String extension, String versionedFormat) { return CxxDescriptionEnhancer.getNonDefaultSharedLibrarySoname( declared, extension, versionedFormat); } }