/* * 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.equalTo; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; import com.facebook.buck.cli.FakeBuckConfig; import com.facebook.buck.io.ProjectFilesystem; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.model.BuildTargetFactory; import com.facebook.buck.model.Flavor; import com.facebook.buck.model.FlavorDomain; import com.facebook.buck.model.InternalFlavor; import com.facebook.buck.rules.BuildRule; import com.facebook.buck.rules.BuildRuleResolver; import com.facebook.buck.rules.BuildRules; import com.facebook.buck.rules.DefaultBuildTargetSourcePath; import com.facebook.buck.rules.DefaultTargetNodeToBuildRuleTransformer; import com.facebook.buck.rules.DependencyAggregationTestUtil; import com.facebook.buck.rules.FakeSourcePath; import com.facebook.buck.rules.PathSourcePath; import com.facebook.buck.rules.SourcePathResolver; import com.facebook.buck.rules.SourcePathRuleFinder; import com.facebook.buck.rules.SourceWithFlags; import com.facebook.buck.rules.TargetGraph; import com.facebook.buck.rules.TargetGraphAndBuildTargets; import com.facebook.buck.rules.TargetNode; import com.facebook.buck.rules.args.Arg; import com.facebook.buck.rules.coercer.DefaultTypeCoercerFactory; import com.facebook.buck.rules.coercer.FrameworkPath; import com.facebook.buck.rules.coercer.PatternMatchedCollection; import com.facebook.buck.rules.coercer.SourceList; import com.facebook.buck.rules.macros.LocationMacro; import com.facebook.buck.rules.macros.StringWithMacros; import com.facebook.buck.rules.macros.StringWithMacrosUtils; import com.facebook.buck.rules.query.Query; import com.facebook.buck.shell.Genrule; import com.facebook.buck.shell.GenruleBuilder; import com.facebook.buck.testutil.FakeProjectFilesystem; import com.facebook.buck.testutil.TargetGraphFactory; import com.facebook.buck.util.MoreCollectors; import com.facebook.buck.versions.Version; import com.facebook.buck.versions.VersionUniverse; import com.facebook.buck.versions.VersionUniverseVersionSelector; import com.facebook.buck.versions.VersionedAliasBuilder; import com.facebook.buck.versions.VersionedTargetGraphBuilder; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Sets; import java.nio.file.Paths; import java.util.Collection; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.ForkJoinPool; import java.util.regex.Pattern; import org.hamcrest.Matchers; import org.junit.Assume; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @RunWith(Parameterized.class) public class CxxBinaryDescriptionTest { @Parameterized.Parameters(name = "{0}") public static Collection<Object[]> data() { return ImmutableList.of( new Object[] {"sandbox_sources=false"}, new Object[] {"sandbox_sources=true"}); } private final CxxBuckConfig cxxBuckConfig; public CxxBinaryDescriptionTest(String sandboxConfig) { this.cxxBuckConfig = new CxxBuckConfig(FakeBuckConfig.builder().setSections("[cxx]", sandboxConfig).build()); } private TargetGraph prepopulateWithSandbox(BuildTarget libTarget) { if (cxxBuckConfig.sandboxSources()) { return TargetGraphFactory.newInstance(mkSandboxNode(libTarget)); } else { return TargetGraph.EMPTY; } } private TargetNode<CxxBinaryDescriptionArg, ?> mkSandboxNode(BuildTarget libTarget) { Optional<Map.Entry<Flavor, CxxLibraryDescription.Type>> type = CxxLibraryDescription.getLibType(libTarget); Set<Flavor> flavors = Sets.newHashSet(libTarget.getFlavors()); if (type.isPresent()) { flavors.remove(type.get().getKey()); } BuildTarget target = BuildTarget.builder(libTarget.getUnflavoredBuildTarget()) .addAllFlavors(flavors) .addFlavors(CxxLibraryDescription.Type.SANDBOX_TREE.getFlavor()) .build(); return new CxxBinaryBuilder(target, cxxBuckConfig).build(); } @Test public void createBuildRule() throws Exception { Assume.assumeFalse("this test is not for sandboxing", cxxBuckConfig.sandboxSources()); ProjectFilesystem projectFilesystem = new FakeProjectFilesystem(); CxxPlatform cxxPlatform = CxxPlatformUtils.DEFAULT_PLATFORM; // Setup a genrule the generates a header we'll list. String genHeaderName = "test/foo.h"; BuildTarget genHeaderTarget = BuildTargetFactory.newInstance("//:genHeader"); GenruleBuilder genHeaderBuilder = GenruleBuilder.newGenruleBuilder(genHeaderTarget).setOut(genHeaderName); // Setup a genrule the generates a source we'll list. String genSourceName = "test/foo.cpp"; BuildTarget genSourceTarget = BuildTargetFactory.newInstance("//:genSource"); GenruleBuilder genSourceBuilder = GenruleBuilder.newGenruleBuilder(genSourceTarget).setOut(genSourceName); // Setup a C/C++ library that we'll depend on form the C/C++ binary description. BuildTarget depTarget = BuildTargetFactory.newInstance("//:dep"); CxxLibraryBuilder depBuilder = new CxxLibraryBuilder(depTarget) .setExportedHeaders( SourceList.ofUnnamedSources(ImmutableSortedSet.of(new FakeSourcePath("blah.h")))) .setSrcs(ImmutableSortedSet.of(SourceWithFlags.of(new FakeSourcePath("test.cpp")))); BuildTarget archiveTarget = BuildTarget.builder(depTarget) .addFlavors(CxxDescriptionEnhancer.STATIC_FLAVOR) .addFlavors(cxxPlatform.getFlavor()) .build(); BuildTarget headerSymlinkTreeTarget = BuildTarget.builder(depTarget) .addFlavors(CxxDescriptionEnhancer.EXPORTED_HEADER_SYMLINK_TREE_FLAVOR) .addFlavors(CxxPreprocessables.HeaderMode.SYMLINK_TREE_ONLY.getFlavor()) .build(); // Setup the build params we'll pass to description when generating the build rules. BuildTarget target = BuildTargetFactory.newInstance("//:rule"); CxxBinaryBuilder cxxBinaryBuilder = new CxxBinaryBuilder(target, cxxBuckConfig) .setSrcs( ImmutableSortedSet.of( SourceWithFlags.of(new FakeSourcePath("test/bar.cpp")), SourceWithFlags.of(new DefaultBuildTargetSourcePath(genSourceTarget)))) .setHeaders( ImmutableSortedSet.of( new FakeSourcePath("test/bar.h"), new DefaultBuildTargetSourcePath(genHeaderTarget))) .setDeps(ImmutableSortedSet.of(depTarget)); // Create the target graph. TargetGraph targetGraph = TargetGraphFactory.newInstance( genHeaderBuilder.build(), genSourceBuilder.build(), depBuilder.build(), cxxBinaryBuilder.build()); // Create the build rules. BuildRuleResolver resolver = new BuildRuleResolver(targetGraph, new DefaultTargetNodeToBuildRuleTransformer()); SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(resolver); SourcePathResolver pathResolver = new SourcePathResolver(ruleFinder); genHeaderBuilder.build(resolver, projectFilesystem, targetGraph); genSourceBuilder.build(resolver, projectFilesystem, targetGraph); depBuilder.build(resolver, projectFilesystem, targetGraph); CxxBinary binRule = cxxBinaryBuilder.build(resolver, projectFilesystem, targetGraph); assertThat(binRule.getLinkRule(), Matchers.instanceOf(CxxLink.class)); CxxLink rule = (CxxLink) binRule.getLinkRule(); CxxSourceRuleFactory cxxSourceRuleFactory = CxxSourceRuleFactory.builder() .setParams(cxxBinaryBuilder.createBuildRuleParams(resolver, projectFilesystem)) .setResolver(resolver) .setPathResolver(pathResolver) .setRuleFinder(ruleFinder) .setCxxBuckConfig(CxxPlatformUtils.DEFAULT_CONFIG) .setCxxPlatform(cxxPlatform) .setPicType(CxxSourceRuleFactory.PicType.PDC) .build(); // Check that link rule has the expected deps: the object files for our sources and the // archive from the dependency. assertEquals( ImmutableSet.of( cxxSourceRuleFactory.createCompileBuildTarget("test/bar.cpp"), cxxSourceRuleFactory.createCompileBuildTarget(genSourceName), archiveTarget), rule.getBuildDeps() .stream() .map(BuildRule::getBuildTarget) .collect(MoreCollectors.toImmutableSet())); // Verify that the compile rule for our user-provided source has correct deps setup // for the various header rules. BuildRule compileRule1 = resolver.getRule(cxxSourceRuleFactory.createCompileBuildTarget("test/bar.cpp")); assertNotNull(compileRule1); assertThat( DependencyAggregationTestUtil.getDisaggregatedDeps(compileRule1) .map(BuildRule::getBuildTarget) .collect(MoreCollectors.toImmutableSet()), Matchers.containsInAnyOrder( genHeaderTarget, headerSymlinkTreeTarget, CxxDescriptionEnhancer.createHeaderSymlinkTreeTarget( target, HeaderVisibility.PRIVATE, cxxPlatform.getFlavor()))); // Verify that the compile rule for our genrule-generated source has correct deps setup // for the various header rules and the generating genrule. BuildRule compileRule2 = resolver.getRule(cxxSourceRuleFactory.createCompileBuildTarget(genSourceName)); assertNotNull(compileRule2); assertThat( DependencyAggregationTestUtil.getDisaggregatedDeps(compileRule2) .map(BuildRule::getBuildTarget) .collect(MoreCollectors.toImmutableList()), Matchers.containsInAnyOrder( genHeaderTarget, genSourceTarget, headerSymlinkTreeTarget, CxxDescriptionEnhancer.createHeaderSymlinkTreeTarget( target, HeaderVisibility.PRIVATE, cxxPlatform.getFlavor()))); } @Test public void staticPicLinkStyle() throws Exception { BuildTarget target = BuildTargetFactory.newInstance("//foo:bar"); BuildRuleResolver resolver = new BuildRuleResolver( prepopulateWithSandbox(target), new DefaultTargetNodeToBuildRuleTransformer()); ProjectFilesystem filesystem = new FakeProjectFilesystem(); new CxxBinaryBuilder(target, cxxBuckConfig) .setSrcs( ImmutableSortedSet.of( SourceWithFlags.of(new PathSourcePath(filesystem, Paths.get("test.cpp"))))) .build(resolver, filesystem); } @Test public void runtimeDepOnDeps() throws Exception { ProjectFilesystem filesystem = new FakeProjectFilesystem(); BuildTarget leafBinaryTarget = BuildTargetFactory.newInstance("//:dep"); CxxBinaryBuilder leafCxxBinaryBuilder = new CxxBinaryBuilder(leafBinaryTarget, cxxBuckConfig); BuildTarget libraryTarget = BuildTargetFactory.newInstance("//:lib"); CxxLibraryBuilder cxxLibraryBuilder = new CxxLibraryBuilder(libraryTarget).setDeps(ImmutableSortedSet.of(leafBinaryTarget)); BuildTarget topLevelBinaryTarget = BuildTargetFactory.newInstance("//:bin"); CxxBinaryBuilder topLevelCxxBinaryBuilder = new CxxBinaryBuilder(topLevelBinaryTarget, cxxBuckConfig) .setDeps(ImmutableSortedSet.of(libraryTarget)); BuildRuleResolver resolver = new BuildRuleResolver( TargetGraphFactory.newInstance( leafCxxBinaryBuilder.build(), cxxLibraryBuilder.build(), topLevelCxxBinaryBuilder.build()), new DefaultTargetNodeToBuildRuleTransformer()); BuildRule leafCxxBinary = leafCxxBinaryBuilder.build(resolver, filesystem); cxxLibraryBuilder.build(resolver, filesystem); CxxBinary topLevelCxxBinary = topLevelCxxBinaryBuilder.build(resolver, filesystem); assertThat( BuildRules.getTransitiveRuntimeDeps(topLevelCxxBinary, resolver), Matchers.hasItem(leafCxxBinary.getBuildTarget())); } @Test public void linkerFlagsLocationMacro() throws Exception { BuildTarget target = BuildTargetFactory.newInstance("//:rule"); BuildRuleResolver resolver = new BuildRuleResolver( prepopulateWithSandbox(target), new DefaultTargetNodeToBuildRuleTransformer()); SourcePathResolver pathResolver = new SourcePathResolver(new SourcePathRuleFinder(resolver)); Genrule dep = GenruleBuilder.newGenruleBuilder(BuildTargetFactory.newInstance("//:dep")) .setOut("out") .build(resolver); CxxBinaryBuilder builder = new CxxBinaryBuilder(target, cxxBuckConfig) .setLinkerFlags( ImmutableList.of( StringWithMacrosUtils.format( "--linker-script=%s", LocationMacro.of(dep.getBuildTarget())))); assertThat(builder.build().getExtraDeps(), Matchers.hasItem(dep.getBuildTarget())); BuildRule binary = builder.build(resolver).getLinkRule(); assertThat(binary, Matchers.instanceOf(CxxLink.class)); assertThat( Arg.stringify(((CxxLink) binary).getArgs(), pathResolver), Matchers.hasItem( String.format("--linker-script=%s", dep.getAbsoluteOutputFilePath(pathResolver)))); assertThat(binary.getBuildDeps(), Matchers.hasItem(dep)); } @Test public void platformLinkerFlagsLocationMacroWithMatch() throws Exception { BuildTarget target = BuildTargetFactory.newInstance("//:rule"); BuildRuleResolver resolver = new BuildRuleResolver( prepopulateWithSandbox(target), new DefaultTargetNodeToBuildRuleTransformer()); SourcePathResolver pathResolver = new SourcePathResolver(new SourcePathRuleFinder(resolver)); Genrule dep = GenruleBuilder.newGenruleBuilder(BuildTargetFactory.newInstance("//:dep")) .setOut("out") .build(resolver); CxxBinaryBuilder builder = new CxxBinaryBuilder(target, cxxBuckConfig) .setPlatformLinkerFlags( new PatternMatchedCollection.Builder<ImmutableList<StringWithMacros>>() .add( Pattern.compile( Pattern.quote( CxxPlatformUtils.DEFAULT_PLATFORM.getFlavor().toString())), ImmutableList.of( StringWithMacrosUtils.format( "--linker-script=%s", LocationMacro.of(dep.getBuildTarget())))) .build()); assertThat(builder.build().getExtraDeps(), Matchers.hasItem(dep.getBuildTarget())); BuildRule binary = builder.build(resolver).getLinkRule(); assertThat(binary, Matchers.instanceOf(CxxLink.class)); assertThat( Arg.stringify(((CxxLink) binary).getArgs(), pathResolver), Matchers.hasItem( String.format("--linker-script=%s", dep.getAbsoluteOutputFilePath(pathResolver)))); assertThat(binary.getBuildDeps(), Matchers.hasItem(dep)); } @Test public void platformLinkerFlagsLocationMacroWithoutMatch() throws Exception { BuildTarget target = BuildTargetFactory.newInstance("//:rule"); BuildRuleResolver resolver = new BuildRuleResolver( prepopulateWithSandbox(target), new DefaultTargetNodeToBuildRuleTransformer()); SourcePathResolver pathResolver = new SourcePathResolver(new SourcePathRuleFinder(resolver)); Genrule dep = GenruleBuilder.newGenruleBuilder(BuildTargetFactory.newInstance("//:dep")) .setOut("out") .build(resolver); CxxBinaryBuilder builder = new CxxBinaryBuilder(target, cxxBuckConfig) .setPlatformLinkerFlags( new PatternMatchedCollection.Builder<ImmutableList<StringWithMacros>>() .add( Pattern.compile("nothing matches this string"), ImmutableList.of( StringWithMacrosUtils.format( "--linker-script=%s", LocationMacro.of(dep.getBuildTarget())))) .build()); assertThat(builder.build().getExtraDeps(), Matchers.hasItem(dep.getBuildTarget())); BuildRule binary = builder.build(resolver).getLinkRule(); assertThat(binary, Matchers.instanceOf(CxxLink.class)); assertThat( Arg.stringify(((CxxLink) binary).getArgs(), pathResolver), Matchers.not( Matchers.hasItem( String.format("--linker-script=%s", dep.getAbsoluteOutputFilePath(pathResolver))))); assertThat(binary.getBuildDeps(), Matchers.not(Matchers.hasItem(dep))); } @Test public void binaryShouldLinkOwnRequiredLibraries() throws Exception { ProjectFilesystem filesystem = new FakeProjectFilesystem(); CxxPlatform platform = CxxPlatformUtils.DEFAULT_PLATFORM; CxxBinaryBuilder binaryBuilder = new CxxBinaryBuilder( BuildTargetFactory.newInstance("//:foo") .withFlavors(platform.getFlavor(), InternalFlavor.of("shared")), cxxBuckConfig); binaryBuilder .setLibraries( ImmutableSortedSet.of( FrameworkPath.ofSourcePath(new FakeSourcePath("/some/path/libs.dylib")), FrameworkPath.ofSourcePath(new FakeSourcePath("/another/path/liba.dylib")))) .setSrcs(ImmutableSortedSet.of(SourceWithFlags.of(new FakeSourcePath("foo.c")))); TargetGraph targetGraph = TargetGraphFactory.newInstance(binaryBuilder.build()); BuildRuleResolver resolver = new BuildRuleResolver(targetGraph, new DefaultTargetNodeToBuildRuleTransformer()); SourcePathResolver pathResolver = new SourcePathResolver(new SourcePathRuleFinder(resolver)); CxxBinary binary = binaryBuilder.build(resolver, filesystem, targetGraph); assertThat(binary.getLinkRule(), Matchers.instanceOf(CxxLink.class)); assertThat( Arg.stringify(((CxxLink) binary.getLinkRule()).getArgs(), pathResolver), Matchers.hasItems("-L", "/another/path", "/some/path", "-la", "-ls")); } @Test public void testBinaryWithStripFlavorHasStripLinkRuleWithCorrectStripStyle() throws Exception { ProjectFilesystem filesystem = new FakeProjectFilesystem(); CxxPlatform platform = CxxPlatformUtils.DEFAULT_PLATFORM; CxxBinaryBuilder binaryBuilder = new CxxBinaryBuilder( BuildTargetFactory.newInstance("//:foo") .withFlavors( platform.getFlavor(), InternalFlavor.of("shared"), StripStyle.ALL_SYMBOLS.getFlavor()), cxxBuckConfig); binaryBuilder.setSrcs(ImmutableSortedSet.of(SourceWithFlags.of(new FakeSourcePath("foo.c")))); TargetGraph targetGraph = TargetGraphFactory.newInstance(binaryBuilder.build()); BuildRuleResolver resolver = new BuildRuleResolver(targetGraph, new DefaultTargetNodeToBuildRuleTransformer()); BuildRule resultRule = binaryBuilder.build(resolver, filesystem, targetGraph); assertThat(resultRule, Matchers.instanceOf(CxxBinary.class)); assertThat(((CxxBinary) resultRule).getLinkRule(), Matchers.instanceOf(CxxStrip.class)); CxxStrip strip = (CxxStrip) ((CxxBinary) resultRule).getLinkRule(); assertThat(strip.getStripStyle(), equalTo(StripStyle.ALL_SYMBOLS)); } @Test public void depQuery() throws Exception { CxxLibraryBuilder transitiveDepBuilder = new CxxLibraryBuilder(BuildTargetFactory.newInstance("//:transitive_dep")); CxxLibraryBuilder depBuilder = new CxxLibraryBuilder(BuildTargetFactory.newInstance("//:dep")) .setDeps(ImmutableSortedSet.of(transitiveDepBuilder.getTarget())); CxxBinaryBuilder builder = new CxxBinaryBuilder(BuildTargetFactory.newInstance("//:rule")) .setDepQuery(Query.of("filter(transitive, deps(//:dep))")); TargetGraph targetGraph = TargetGraphFactory.newInstance( transitiveDepBuilder.build(), depBuilder.build(), builder.build()); BuildRuleResolver resolver = new BuildRuleResolver(targetGraph, new DefaultTargetNodeToBuildRuleTransformer()); CxxLibrary transitiveDep = (CxxLibrary) transitiveDepBuilder.build(resolver, targetGraph); depBuilder.build(resolver, targetGraph); CxxBinary binary = builder.build(resolver, targetGraph); // TODO(agallagher): should also test that `:dep` does *not* get included. assertThat(binary.getBuildDeps(), Matchers.hasItem(transitiveDep)); } @Test public void versionUniverse() throws Exception { GenruleBuilder transitiveDepBuilder = GenruleBuilder.newGenruleBuilder(BuildTargetFactory.newInstance("//:tdep")).setOut("out"); VersionedAliasBuilder depBuilder = new VersionedAliasBuilder(BuildTargetFactory.newInstance("//:dep")) .setVersions( ImmutableMap.of( Version.of("1.0"), transitiveDepBuilder.getTarget(), Version.of("2.0"), transitiveDepBuilder.getTarget())); CxxBinaryBuilder builder = new CxxBinaryBuilder(BuildTargetFactory.newInstance("//:rule")) .setVersionUniverse("1") .setDeps(ImmutableSortedSet.of(depBuilder.getTarget())); TargetGraph unversionedTargetGraph = TargetGraphFactory.newInstance( transitiveDepBuilder.build(), depBuilder.build(), builder.build()); VersionUniverse universe1 = VersionUniverse.of(ImmutableMap.of(depBuilder.getTarget(), Version.of("1.0"))); VersionUniverse universe2 = VersionUniverse.of(ImmutableMap.of(depBuilder.getTarget(), Version.of("2.0"))); TargetGraph versionedTargetGraph = VersionedTargetGraphBuilder.transform( new VersionUniverseVersionSelector( unversionedTargetGraph, ImmutableMap.of("1", universe1, "2", universe2)), TargetGraphAndBuildTargets.of( unversionedTargetGraph, ImmutableSet.of(builder.getTarget())), new ForkJoinPool(), new DefaultTypeCoercerFactory()) .getTargetGraph(); assertThat( versionedTargetGraph.get(builder.getTarget()).getSelectedVersions(), equalTo(Optional.of(universe1.getVersions()))); } @Test public void testDefaultPlatformArg() throws Exception { CxxPlatform alternatePlatform = CxxPlatformUtils.DEFAULT_PLATFORM.withFlavor(InternalFlavor.of("alternate")); FlavorDomain<CxxPlatform> cxxPlatforms = new FlavorDomain<>( "C/C++ Platform", ImmutableMap.of( CxxPlatformUtils.DEFAULT_PLATFORM.getFlavor(), CxxPlatformUtils.DEFAULT_PLATFORM, alternatePlatform.getFlavor(), alternatePlatform)); CxxBinaryBuilder binaryBuilder = new CxxBinaryBuilder( BuildTargetFactory.newInstance("//:foo"), CxxPlatformUtils.DEFAULT_PLATFORM, cxxPlatforms); binaryBuilder.setDefaultPlatform(alternatePlatform.getFlavor()); TargetGraph targetGraph = TargetGraphFactory.newInstance(binaryBuilder.build()); BuildRuleResolver resolver = new BuildRuleResolver(targetGraph, new DefaultTargetNodeToBuildRuleTransformer()); CxxBinary binary = binaryBuilder.build(resolver, targetGraph); assertThat(binary.getCxxPlatform(), equalTo(alternatePlatform)); } }