/* * 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.python; import static org.junit.Assert.assertThat; import com.facebook.buck.cli.FakeBuckConfig; import com.facebook.buck.cxx.CxxBinaryBuilder; import com.facebook.buck.cxx.CxxBuckConfig; import com.facebook.buck.cxx.CxxLibraryBuilder; import com.facebook.buck.cxx.CxxLink; import com.facebook.buck.cxx.CxxPlatform; import com.facebook.buck.cxx.CxxPlatformUtils; import com.facebook.buck.cxx.NativeLinkStrategy; import com.facebook.buck.cxx.PrebuiltCxxLibraryBuilder; import com.facebook.buck.io.AlwaysFoundExecutableFinder; import com.facebook.buck.io.MorePaths; import com.facebook.buck.io.ProjectFilesystem; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.model.BuildTargetFactory; 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.BuildTargetSourcePath; import com.facebook.buck.rules.CommandTool; import com.facebook.buck.rules.DefaultBuildTargetSourcePath; import com.facebook.buck.rules.DefaultTargetNodeToBuildRuleTransformer; import com.facebook.buck.rules.FakeBuildContext; import com.facebook.buck.rules.FakeBuildableContext; import com.facebook.buck.rules.FakeSourcePath; import com.facebook.buck.rules.HashedFileTool; import com.facebook.buck.rules.RuleKey; import com.facebook.buck.rules.SourcePath; 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.Tool; import com.facebook.buck.rules.args.Arg; import com.facebook.buck.rules.args.SourcePathArg; import com.facebook.buck.rules.coercer.PatternMatchedCollection; import com.facebook.buck.rules.coercer.SourceList; import com.facebook.buck.rules.keys.DefaultRuleKeyFactory; import com.facebook.buck.rules.keys.RuleKeyFieldLoader; import com.facebook.buck.shell.Genrule; import com.facebook.buck.shell.GenruleBuilder; import com.facebook.buck.shell.ShBinary; import com.facebook.buck.shell.ShBinaryBuilder; import com.facebook.buck.step.Step; import com.facebook.buck.testutil.AllExistingProjectFilesystem; import com.facebook.buck.testutil.FakeProjectFilesystem; import com.facebook.buck.testutil.TargetGraphFactory; import com.facebook.buck.util.MoreCollectors; import com.facebook.buck.util.cache.DefaultFileHashCache; import com.facebook.buck.util.cache.StackedFileHashCache; 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.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Iterables; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Optional; import java.util.regex.Pattern; import org.hamcrest.Matchers; import org.junit.Test; public class PythonBinaryDescriptionTest { private static final BuildTarget PYTHON2_DEP_TARGET = BuildTargetFactory.newInstance("//:python2_dep"); private static final PythonPlatform PY2 = PythonPlatform.of( InternalFlavor.of("py2"), new PythonEnvironment(Paths.get("python2"), PythonVersion.of("CPython", "2.6")), Optional.of(PYTHON2_DEP_TARGET)); @Test public void thatComponentSourcePathDepsPropagateProperly() throws Exception { GenruleBuilder genruleBuilder = GenruleBuilder.newGenruleBuilder(BuildTargetFactory.newInstance("//:gen")) .setOut("blah.py"); PythonLibraryBuilder libBuilder = new PythonLibraryBuilder(BuildTargetFactory.newInstance("//:lib")) .setSrcs( SourceList.ofUnnamedSources( ImmutableSortedSet.of( new DefaultBuildTargetSourcePath(genruleBuilder.getTarget())))); PythonBinaryBuilder binaryBuilder = PythonBinaryBuilder.create(BuildTargetFactory.newInstance("//:bin")) .setMainModule("main") .setDeps(ImmutableSortedSet.of(libBuilder.getTarget())); TargetGraph targetGraph = TargetGraphFactory.newInstance( genruleBuilder.build(), libBuilder.build(), binaryBuilder.build()); ProjectFilesystem filesystem = new FakeProjectFilesystem(); BuildRuleResolver resolver = new BuildRuleResolver(targetGraph, new DefaultTargetNodeToBuildRuleTransformer()); Genrule genrule = genruleBuilder.build(resolver, filesystem, targetGraph); libBuilder.build(resolver, filesystem, targetGraph); PythonBinary binary = binaryBuilder.build(resolver, filesystem, targetGraph); assertThat(binary.getBuildDeps(), Matchers.hasItem(genrule)); } @Test public void thatMainSourcePathPropagatesToDeps() throws Exception { BuildRuleResolver resolver = new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer()); Genrule genrule = GenruleBuilder.newGenruleBuilder(BuildTargetFactory.newInstance("//:gen")) .setOut("blah.py") .build(resolver); PythonBinary binary = PythonBinaryBuilder.create(BuildTargetFactory.newInstance("//:bin")) .setMain(genrule.getSourcePathToOutput()) .build(resolver); assertThat(binary.getBuildDeps(), Matchers.hasItem(genrule)); } @Test public void baseModule() throws Exception { BuildTarget target = BuildTargetFactory.newInstance("//foo:bin"); String sourceName = "main.py"; SourcePath source = new FakeSourcePath("foo/" + sourceName); // Run without a base module set and verify it defaults to using the build target // base name. PythonBinary normal = PythonBinaryBuilder.create(target) .setMain(source) .build( new BuildRuleResolver( TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer())); assertThat( normal.getComponents().getModules().keySet(), Matchers.hasItem(target.getBasePath().resolve(sourceName))); // Run *with* a base module set and verify it gets used to build the main module path. String baseModule = "blah"; PythonBinary withBaseModule = PythonBinaryBuilder.create(target) .setMain(source) .setBaseModule(baseModule) .build( new BuildRuleResolver( TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer())); assertThat( withBaseModule.getComponents().getModules().keySet(), Matchers.hasItem(Paths.get(baseModule).resolve(sourceName))); } @Test public void mainModule() throws Exception { BuildTarget target = BuildTargetFactory.newInstance("//foo:bin"); BuildRuleResolver resolver = new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer()); String mainModule = "foo.main"; PythonBinary binary = PythonBinaryBuilder.create(target).setMainModule(mainModule).build(resolver); assertThat(mainModule, Matchers.equalTo(binary.getMainModule())); } @Test public void extensionConfig() throws Exception { BuildTarget target = BuildTargetFactory.newInstance("//foo:bin"); BuildRuleResolver resolver = new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer()); SourcePathResolver pathResolver = new SourcePathResolver(new SourcePathRuleFinder(resolver)); PythonBuckConfig config = new PythonBuckConfig( FakeBuckConfig.builder() .setSections( ImmutableMap.of( "python", ImmutableMap.of("pex_extension", ".different_extension"))) .build(), new AlwaysFoundExecutableFinder()); PythonBinaryBuilder builder = new PythonBinaryBuilder( target, config, PythonTestUtils.PYTHON_PLATFORMS, CxxPlatformUtils.DEFAULT_PLATFORM, CxxPlatformUtils.DEFAULT_PLATFORMS); PythonBinary binary = builder.setMainModule("main").build(resolver); assertThat( pathResolver .getRelativePath(Preconditions.checkNotNull(binary.getSourcePathToOutput())) .toString(), Matchers.endsWith(".different_extension")); } @Test public void extensionParameter() throws Exception { BuildTarget target = BuildTargetFactory.newInstance("//foo:bin"); BuildRuleResolver resolver = new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer()); SourcePathResolver pathResolver = new SourcePathResolver(new SourcePathRuleFinder(resolver)); PythonBinaryBuilder builder = PythonBinaryBuilder.create(target); PythonBinary binary = builder.setMainModule("main").setExtension(".different_extension").build(resolver); assertThat( pathResolver .getRelativePath(Preconditions.checkNotNull(binary.getSourcePathToOutput())) .toString(), Matchers.endsWith(".different_extension")); } @Test public void buildArgs() throws Exception { BuildTarget target = BuildTargetFactory.newInstance("//foo:bin"); BuildRuleResolver resolver = new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer()); SourcePathResolver pathResolver = new SourcePathResolver(new SourcePathRuleFinder(resolver)); ImmutableList<String> buildArgs = ImmutableList.of("--some", "--args"); PythonBinary binary = PythonBinaryBuilder.create(target) .setMainModule("main") .setBuildArgs(buildArgs) .build(resolver); ImmutableList<Step> buildSteps = binary.getBuildSteps( FakeBuildContext.withSourcePathResolver(pathResolver), new FakeBuildableContext()); PexStep pexStep = FluentIterable.from(buildSteps).filter(PexStep.class).get(0); assertThat( pexStep.getCommandPrefix(), Matchers.hasItems(buildArgs.toArray(new String[buildArgs.size()]))); } @Test public void explicitPythonHome() throws Exception { PythonPlatform platform1 = PythonPlatform.of( InternalFlavor.of("pyPlat1"), new PythonEnvironment(Paths.get("python2.6"), PythonVersion.of("CPython", "2.6.9")), Optional.empty()); PythonPlatform platform2 = PythonPlatform.of( InternalFlavor.of("pyPlat2"), new PythonEnvironment(Paths.get("python2.7"), PythonVersion.of("CPython", "2.7.11")), Optional.empty()); PythonBinaryBuilder builder = PythonBinaryBuilder.create( BuildTargetFactory.newInstance("//:bin"), FlavorDomain.of("Python Platform", platform1, platform2)); builder.setMainModule("main"); PythonBinary binary1 = builder .setPlatform(platform1.getFlavor().toString()) .build( new BuildRuleResolver( TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer())); assertThat(binary1.getPythonPlatform(), Matchers.equalTo(platform1)); PythonBinary binary2 = builder .setPlatform(platform2.getFlavor().toString()) .build( new BuildRuleResolver( TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer())); assertThat(binary2.getPythonPlatform(), Matchers.equalTo(platform2)); } @Test public void runtimeDepOnDeps() throws Exception { ProjectFilesystem filesystem = new FakeProjectFilesystem(); for (PythonBuckConfig.PackageStyle packageStyle : PythonBuckConfig.PackageStyle.values()) { CxxBinaryBuilder cxxBinaryBuilder = new CxxBinaryBuilder(BuildTargetFactory.newInstance("//:dep")); PythonLibraryBuilder pythonLibraryBuilder = new PythonLibraryBuilder(BuildTargetFactory.newInstance("//:lib")) .setSrcs( SourceList.ofUnnamedSources( ImmutableSortedSet.of(new FakeSourcePath("something.py")))) .setDeps(ImmutableSortedSet.of(cxxBinaryBuilder.getTarget())); PythonBinaryBuilder pythonBinaryBuilder = PythonBinaryBuilder.create(BuildTargetFactory.newInstance("//:bin")) .setMainModule("main") .setDeps(ImmutableSortedSet.of(pythonLibraryBuilder.getTarget())) .setPackageStyle(packageStyle); TargetGraph targetGraph = TargetGraphFactory.newInstance( cxxBinaryBuilder.build(), pythonLibraryBuilder.build(), pythonBinaryBuilder.build()); BuildRuleResolver resolver = new BuildRuleResolver(targetGraph, new DefaultTargetNodeToBuildRuleTransformer()); BuildRule cxxBinary = cxxBinaryBuilder.build(resolver, filesystem, targetGraph); pythonLibraryBuilder.build(resolver, filesystem, targetGraph); PythonBinary pythonBinary = pythonBinaryBuilder.build(resolver, filesystem, targetGraph); assertThat( String.format( "Transitive runtime deps of %s [%s]", pythonBinary, packageStyle.toString()), BuildRules.getTransitiveRuntimeDeps(pythonBinary, resolver), Matchers.hasItem(cxxBinary.getBuildTarget())); } } @Test public void executableCommandWithPathToPexExecutor() throws Exception { BuildTarget target = BuildTargetFactory.newInstance("//foo:bin"); BuildRuleResolver resolver = new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer()); SourcePathResolver pathResolver = new SourcePathResolver(new SourcePathRuleFinder(resolver)); final Path executor = Paths.get("executor"); PythonBuckConfig config = new PythonBuckConfig(FakeBuckConfig.builder().build(), new AlwaysFoundExecutableFinder()) { @Override public Optional<Tool> getPexExecutor(BuildRuleResolver resolver) { return Optional.of(new HashedFileTool(executor)); } }; PythonBinaryBuilder builder = new PythonBinaryBuilder( target, config, PythonTestUtils.PYTHON_PLATFORMS, CxxPlatformUtils.DEFAULT_PLATFORM, CxxPlatformUtils.DEFAULT_PLATFORMS); PythonPackagedBinary binary = (PythonPackagedBinary) builder.setMainModule("main").build(resolver); assertThat( binary.getExecutableCommand().getCommandPrefix(pathResolver), Matchers.contains( executor.toString(), pathResolver.getAbsolutePath(binary.getSourcePathToOutput()).toString())); } @Test public void executableCommandWithNoPathToPexExecutor() throws Exception { BuildTarget target = BuildTargetFactory.newInstance("//foo:bin"); BuildRuleResolver resolver = new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer()); SourcePathResolver pathResolver = new SourcePathResolver(new SourcePathRuleFinder(resolver)); PythonPackagedBinary binary = (PythonPackagedBinary) PythonBinaryBuilder.create(target).setMainModule("main").build(resolver); assertThat( binary.getExecutableCommand().getCommandPrefix(pathResolver), Matchers.contains( PythonTestUtils.PYTHON_PLATFORM.getEnvironment().getPythonPath().toString(), pathResolver.getAbsolutePath(binary.getSourcePathToOutput()).toString())); } @Test public void packagedBinaryAttachedPexToolDeps() throws Exception { BuildTarget target = BuildTargetFactory.newInstance("//foo:bin"); BuildRuleResolver resolver = new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer()); final Genrule pexTool = GenruleBuilder.newGenruleBuilder(BuildTargetFactory.newInstance("//:pex_tool")) .setOut("pex-tool") .build(resolver); PythonBuckConfig config = new PythonBuckConfig(FakeBuckConfig.builder().build(), new AlwaysFoundExecutableFinder()) { @Override public PackageStyle getPackageStyle() { return PackageStyle.STANDALONE; } @Override public Tool getPexTool(BuildRuleResolver resolver) { return new CommandTool.Builder() .addArg(SourcePathArg.of(pexTool.getSourcePathToOutput())) .build(); } }; PythonBinaryBuilder builder = new PythonBinaryBuilder( target, config, PythonTestUtils.PYTHON_PLATFORMS, CxxPlatformUtils.DEFAULT_PLATFORM, CxxPlatformUtils.DEFAULT_PLATFORMS); PythonPackagedBinary binary = (PythonPackagedBinary) builder.setMainModule("main").build(resolver); assertThat(binary.getBuildDeps(), Matchers.hasItem(pexTool)); } @Test public void transitiveNativeDepsUsingMergedNativeLinkStrategy() throws Exception { CxxLibraryBuilder transitiveCxxDepBuilder = new CxxLibraryBuilder(BuildTargetFactory.newInstance("//:transitive_dep")) .setSrcs( ImmutableSortedSet.of(SourceWithFlags.of(new FakeSourcePath("transitive_dep.c")))); CxxLibraryBuilder cxxDepBuilder = new CxxLibraryBuilder(BuildTargetFactory.newInstance("//:dep")) .setSrcs(ImmutableSortedSet.of(SourceWithFlags.of(new FakeSourcePath("dep.c")))) .setDeps(ImmutableSortedSet.of(transitiveCxxDepBuilder.getTarget())); CxxLibraryBuilder cxxBuilder = new CxxLibraryBuilder(BuildTargetFactory.newInstance("//:cxx")) .setSrcs(ImmutableSortedSet.of(SourceWithFlags.of(new FakeSourcePath("cxx.c")))) .setDeps(ImmutableSortedSet.of(cxxDepBuilder.getTarget())); PythonBuckConfig config = new PythonBuckConfig(FakeBuckConfig.builder().build(), new AlwaysFoundExecutableFinder()) { @Override public NativeLinkStrategy getNativeLinkStrategy() { return NativeLinkStrategy.MERGED; } }; PythonBinaryBuilder binaryBuilder = new PythonBinaryBuilder( BuildTargetFactory.newInstance("//:bin"), config, PythonTestUtils.PYTHON_PLATFORMS, CxxPlatformUtils.DEFAULT_PLATFORM, CxxPlatformUtils.DEFAULT_PLATFORMS); binaryBuilder.setMainModule("main"); binaryBuilder.setDeps(ImmutableSortedSet.of(cxxBuilder.getTarget())); BuildRuleResolver resolver = new BuildRuleResolver( TargetGraphFactory.newInstance( transitiveCxxDepBuilder.build(), cxxDepBuilder.build(), cxxBuilder.build(), binaryBuilder.build()), new DefaultTargetNodeToBuildRuleTransformer()); transitiveCxxDepBuilder.build(resolver); cxxDepBuilder.build(resolver); cxxBuilder.build(resolver); PythonBinary binary = binaryBuilder.build(resolver); assertThat( Iterables.transform(binary.getComponents().getNativeLibraries().keySet(), Object::toString), Matchers.containsInAnyOrder("libomnibus.so", "libcxx.so")); } @Test public void transitiveNativeDepsUsingSeparateNativeLinkStrategy() throws Exception { CxxLibraryBuilder transitiveCxxDepBuilder = new CxxLibraryBuilder(BuildTargetFactory.newInstance("//:transitive_dep")) .setSrcs( ImmutableSortedSet.of(SourceWithFlags.of(new FakeSourcePath("transitive_dep.c")))); CxxLibraryBuilder cxxDepBuilder = new CxxLibraryBuilder(BuildTargetFactory.newInstance("//:dep")) .setSrcs(ImmutableSortedSet.of(SourceWithFlags.of(new FakeSourcePath("dep.c")))) .setDeps(ImmutableSortedSet.of(transitiveCxxDepBuilder.getTarget())); CxxLibraryBuilder cxxBuilder = new CxxLibraryBuilder(BuildTargetFactory.newInstance("//:cxx")) .setSrcs(ImmutableSortedSet.of(SourceWithFlags.of(new FakeSourcePath("cxx.c")))) .setDeps(ImmutableSortedSet.of(cxxDepBuilder.getTarget())); PythonBuckConfig config = new PythonBuckConfig(FakeBuckConfig.builder().build(), new AlwaysFoundExecutableFinder()) { @Override public NativeLinkStrategy getNativeLinkStrategy() { return NativeLinkStrategy.SEPARATE; } }; PythonBinaryBuilder binaryBuilder = new PythonBinaryBuilder( BuildTargetFactory.newInstance("//:bin"), config, PythonTestUtils.PYTHON_PLATFORMS, CxxPlatformUtils.DEFAULT_PLATFORM, CxxPlatformUtils.DEFAULT_PLATFORMS); binaryBuilder.setMainModule("main"); binaryBuilder.setDeps(ImmutableSortedSet.of(cxxBuilder.getTarget())); BuildRuleResolver resolver = new BuildRuleResolver( TargetGraphFactory.newInstance( transitiveCxxDepBuilder.build(), cxxDepBuilder.build(), cxxBuilder.build(), binaryBuilder.build()), new DefaultTargetNodeToBuildRuleTransformer()); transitiveCxxDepBuilder.build(resolver); cxxDepBuilder.build(resolver); cxxBuilder.build(resolver); PythonBinary binary = binaryBuilder.build(resolver); assertThat( Iterables.transform(binary.getComponents().getNativeLibraries().keySet(), Object::toString), Matchers.containsInAnyOrder("libtransitive_dep.so", "libdep.so", "libcxx.so")); } @Test public void extensionDepUsingMergedNativeLinkStrategy() throws Exception { FlavorDomain<PythonPlatform> pythonPlatforms = FlavorDomain.of("Python Platform", PY2); PrebuiltCxxLibraryBuilder python2Builder = new PrebuiltCxxLibraryBuilder(PYTHON2_DEP_TARGET) .setProvided(true) .setExportedLinkerFlags(ImmutableList.of("-lpython2")); CxxPythonExtensionBuilder extensionBuilder = new CxxPythonExtensionBuilder( BuildTargetFactory.newInstance("//:extension"), pythonPlatforms, new CxxBuckConfig(FakeBuckConfig.builder().build()), CxxPlatformUtils.DEFAULT_PLATFORMS); extensionBuilder.setBaseModule("hello"); PythonBuckConfig config = new PythonBuckConfig(FakeBuckConfig.builder().build(), new AlwaysFoundExecutableFinder()) { @Override public NativeLinkStrategy getNativeLinkStrategy() { return NativeLinkStrategy.MERGED; } }; PythonBinaryBuilder binaryBuilder = new PythonBinaryBuilder( BuildTargetFactory.newInstance("//:bin"), config, pythonPlatforms, CxxPlatformUtils.DEFAULT_PLATFORM, CxxPlatformUtils.DEFAULT_PLATFORMS); binaryBuilder.setMainModule("main"); binaryBuilder.setDeps(ImmutableSortedSet.of(extensionBuilder.getTarget())); TargetGraph targetGraph = TargetGraphFactory.newInstance( python2Builder.build(), extensionBuilder.build(), binaryBuilder.build()); ProjectFilesystem filesystem = new FakeProjectFilesystem(); BuildRuleResolver resolver = new BuildRuleResolver(targetGraph, new DefaultTargetNodeToBuildRuleTransformer()); python2Builder.build(resolver, filesystem, targetGraph); extensionBuilder.build(resolver, filesystem, targetGraph); PythonBinary binary = binaryBuilder.build(resolver, filesystem, targetGraph); assertThat(binary.getComponents().getNativeLibraries().entrySet(), Matchers.empty()); assertThat( Iterables.transform(binary.getComponents().getModules().keySet(), Object::toString), Matchers.containsInAnyOrder(MorePaths.pathWithPlatformSeparators("hello/extension.so"))); } @Test public void transitiveDepsOfLibsWithPrebuiltNativeLibsAreNotIncludedInMergedLink() throws Exception { CxxLibraryBuilder transitiveCxxLibraryBuilder = new CxxLibraryBuilder(BuildTargetFactory.newInstance("//:transitive_cxx")) .setSrcs( ImmutableSortedSet.of(SourceWithFlags.of(new FakeSourcePath("transitive_cxx.c")))); CxxLibraryBuilder cxxLibraryBuilder = new CxxLibraryBuilder(BuildTargetFactory.newInstance("//:cxx")) .setSrcs(ImmutableSortedSet.of(SourceWithFlags.of(new FakeSourcePath("cxx.c")))) .setDeps(ImmutableSortedSet.of(transitiveCxxLibraryBuilder.getTarget())); PythonLibraryBuilder pythonLibraryBuilder = new PythonLibraryBuilder(BuildTargetFactory.newInstance("//:lib")) .setSrcs( SourceList.ofUnnamedSources( ImmutableSortedSet.of(new FakeSourcePath("prebuilt.so")))) .setDeps(ImmutableSortedSet.of(cxxLibraryBuilder.getTarget())); PythonBuckConfig config = new PythonBuckConfig(FakeBuckConfig.builder().build(), new AlwaysFoundExecutableFinder()) { @Override public NativeLinkStrategy getNativeLinkStrategy() { return NativeLinkStrategy.MERGED; } }; PythonBinaryBuilder pythonBinaryBuilder = new PythonBinaryBuilder( BuildTargetFactory.newInstance("//:bin"), config, PythonTestUtils.PYTHON_PLATFORMS, CxxPlatformUtils.DEFAULT_PLATFORM, CxxPlatformUtils.DEFAULT_PLATFORMS); pythonBinaryBuilder.setMainModule("main"); pythonBinaryBuilder.setDeps(ImmutableSortedSet.of(pythonLibraryBuilder.getTarget())); TargetGraph targetGraph = TargetGraphFactory.newInstance( transitiveCxxLibraryBuilder.build(), cxxLibraryBuilder.build(), pythonLibraryBuilder.build(), pythonBinaryBuilder.build()); ProjectFilesystem filesystem = new FakeProjectFilesystem(); BuildRuleResolver resolver = new BuildRuleResolver(targetGraph, new DefaultTargetNodeToBuildRuleTransformer()); transitiveCxxLibraryBuilder.build(resolver, filesystem, targetGraph); cxxLibraryBuilder.build(resolver, filesystem, targetGraph); pythonLibraryBuilder.build(resolver, filesystem, targetGraph); PythonBinary binary = pythonBinaryBuilder.build(resolver, filesystem, targetGraph); assertThat( Iterables.transform(binary.getComponents().getNativeLibraries().keySet(), Object::toString), Matchers.hasItem("libtransitive_cxx.so")); } @Test public void packageStyleParam() throws Exception { BuildRuleResolver resolver = new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer()); PythonBinary pythonBinary = PythonBinaryBuilder.create(BuildTargetFactory.newInstance("//:bin")) .setMainModule("main") .setPackageStyle(PythonBuckConfig.PackageStyle.INPLACE) .build(resolver); assertThat(pythonBinary, Matchers.instanceOf(PythonInPlaceBinary.class)); resolver = new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer()); pythonBinary = PythonBinaryBuilder.create(BuildTargetFactory.newInstance("//:bin")) .setMainModule("main") .setPackageStyle(PythonBuckConfig.PackageStyle.STANDALONE) .build(resolver); assertThat(pythonBinary, Matchers.instanceOf(PythonPackagedBinary.class)); } @Test public void preloadLibraries() throws Exception { for (final NativeLinkStrategy strategy : NativeLinkStrategy.values()) { CxxLibraryBuilder cxxLibraryBuilder = new CxxLibraryBuilder(BuildTargetFactory.newInstance("//:dep")) .setSrcs(ImmutableSortedSet.of(SourceWithFlags.of(new FakeSourcePath("test.c")))); PythonBuckConfig config = new PythonBuckConfig( FakeBuckConfig.builder().build(), new AlwaysFoundExecutableFinder()) { @Override public NativeLinkStrategy getNativeLinkStrategy() { return strategy; } }; PythonBinaryBuilder binaryBuilder = new PythonBinaryBuilder( BuildTargetFactory.newInstance("//:bin"), config, PythonTestUtils.PYTHON_PLATFORMS, CxxPlatformUtils.DEFAULT_PLATFORM, CxxPlatformUtils.DEFAULT_PLATFORMS); binaryBuilder.setMainModule("main"); binaryBuilder.setPreloadDeps(ImmutableSortedSet.of(cxxLibraryBuilder.getTarget())); BuildRuleResolver resolver = new BuildRuleResolver( TargetGraphFactory.newInstance(cxxLibraryBuilder.build(), binaryBuilder.build()), new DefaultTargetNodeToBuildRuleTransformer()); cxxLibraryBuilder.build(resolver); PythonBinary binary = binaryBuilder.build(resolver); assertThat("Using " + strategy, binary.getPreloadLibraries(), Matchers.hasItems("libdep.so")); assertThat( "Using " + strategy, binary.getComponents().getNativeLibraries().keySet(), Matchers.hasItems(Paths.get("libdep.so"))); } } @Test public void pexExecutorRuleIsAddedToParseTimeDeps() throws Exception { ShBinaryBuilder pexExecutorBuilder = new ShBinaryBuilder(BuildTargetFactory.newInstance("//:pex_executor")) .setMain(new FakeSourcePath("run.sh")); PythonBinaryBuilder builder = new PythonBinaryBuilder( BuildTargetFactory.newInstance("//:bin"), new PythonBuckConfig( FakeBuckConfig.builder() .setSections( ImmutableMap.of( "python", ImmutableMap.of( "path_to_pex_executer", pexExecutorBuilder.getTarget().toString()))) .build(), new AlwaysFoundExecutableFinder()), PythonTestUtils.PYTHON_PLATFORMS, CxxPlatformUtils.DEFAULT_PLATFORM, CxxPlatformUtils.DEFAULT_PLATFORMS); builder.setMainModule("main").setPackageStyle(PythonBuckConfig.PackageStyle.STANDALONE); assertThat(builder.build().getExtraDeps(), Matchers.hasItem(pexExecutorBuilder.getTarget())); } @Test public void linkerFlagsUsingMergedNativeLinkStrategy() throws Exception { CxxLibraryBuilder cxxDepBuilder = new CxxLibraryBuilder(BuildTargetFactory.newInstance("//:dep")) .setSrcs(ImmutableSortedSet.of(SourceWithFlags.of(new FakeSourcePath("dep.c")))); CxxLibraryBuilder cxxBuilder = new CxxLibraryBuilder(BuildTargetFactory.newInstance("//:cxx")) .setSrcs(ImmutableSortedSet.of(SourceWithFlags.of(new FakeSourcePath("cxx.c")))) .setDeps(ImmutableSortedSet.of(cxxDepBuilder.getTarget())); PythonBuckConfig config = new PythonBuckConfig(FakeBuckConfig.builder().build(), new AlwaysFoundExecutableFinder()) { @Override public NativeLinkStrategy getNativeLinkStrategy() { return NativeLinkStrategy.MERGED; } }; PythonBinaryBuilder binaryBuilder = new PythonBinaryBuilder( BuildTargetFactory.newInstance("//:bin"), config, PythonTestUtils.PYTHON_PLATFORMS, CxxPlatformUtils.DEFAULT_PLATFORM, CxxPlatformUtils.DEFAULT_PLATFORMS); binaryBuilder.setLinkerFlags(ImmutableList.of("-flag")); binaryBuilder.setMainModule("main"); binaryBuilder.setDeps(ImmutableSortedSet.of(cxxBuilder.getTarget())); BuildRuleResolver resolver = new BuildRuleResolver( TargetGraphFactory.newInstance( cxxDepBuilder.build(), cxxBuilder.build(), binaryBuilder.build()), new DefaultTargetNodeToBuildRuleTransformer()); SourcePathResolver pathResolver = new SourcePathResolver(new SourcePathRuleFinder(resolver)); cxxDepBuilder.build(resolver); cxxBuilder.build(resolver); PythonBinary binary = binaryBuilder.build(resolver); for (SourcePath path : binary.getComponents().getNativeLibraries().values()) { CxxLink link = resolver .getRuleOptionalWithType(((BuildTargetSourcePath) path).getTarget(), CxxLink.class) .get(); assertThat(Arg.stringify(link.getArgs(), pathResolver), Matchers.hasItem("-flag")); } } @Test public void explicitDepOnlinkWholeLibPullsInSharedLibrary() throws Exception { for (final NativeLinkStrategy strategy : NativeLinkStrategy.values()) { ProjectFilesystem filesystem = new AllExistingProjectFilesystem(); CxxLibraryBuilder cxxLibraryBuilder = new CxxLibraryBuilder(BuildTargetFactory.newInstance("//:dep1")) .setSrcs(ImmutableSortedSet.of(SourceWithFlags.of(new FakeSourcePath("test.c")))) .setForceStatic(true); PrebuiltCxxLibraryBuilder prebuiltCxxLibraryBuilder = new PrebuiltCxxLibraryBuilder(BuildTargetFactory.newInstance("//:dep2")) .setForceStatic(true); PythonBuckConfig config = new PythonBuckConfig( FakeBuckConfig.builder().build(), new AlwaysFoundExecutableFinder()) { @Override public NativeLinkStrategy getNativeLinkStrategy() { return strategy; } }; PythonBinaryBuilder binaryBuilder = new PythonBinaryBuilder( BuildTargetFactory.newInstance("//:bin"), config, PythonTestUtils.PYTHON_PLATFORMS, CxxPlatformUtils.DEFAULT_PLATFORM, CxxPlatformUtils.DEFAULT_PLATFORMS); binaryBuilder.setMainModule("main"); binaryBuilder.setDeps( ImmutableSortedSet.of( cxxLibraryBuilder.getTarget(), prebuiltCxxLibraryBuilder.getTarget())); TargetGraph targetGraph = TargetGraphFactory.newInstance( cxxLibraryBuilder.build(), prebuiltCxxLibraryBuilder.build(), binaryBuilder.build()); BuildRuleResolver resolver = new BuildRuleResolver(targetGraph, new DefaultTargetNodeToBuildRuleTransformer()); cxxLibraryBuilder.build(resolver, filesystem, targetGraph); prebuiltCxxLibraryBuilder.build(resolver, filesystem, targetGraph); PythonBinary binary = binaryBuilder.build(resolver, filesystem, targetGraph); assertThat( "Using " + strategy, binary.getComponents().getNativeLibraries().keySet(), Matchers.hasItems(Paths.get("libdep1.so"), Paths.get("libdep2.so"))); } } @Test public void transitiveDepsOfPreloadDepsAreExcludedFromMergedNativeLinkStrategy() throws Exception { CxxLibraryBuilder transitiveCxxDepBuilder = new CxxLibraryBuilder(BuildTargetFactory.newInstance("//:transitive_dep")) .setSrcs( ImmutableSortedSet.of(SourceWithFlags.of(new FakeSourcePath("transitive_dep.c")))); CxxLibraryBuilder cxxDepBuilder = new CxxLibraryBuilder(BuildTargetFactory.newInstance("//:dep")) .setSrcs(ImmutableSortedSet.of(SourceWithFlags.of(new FakeSourcePath("dep.c")))) .setDeps(ImmutableSortedSet.of(transitiveCxxDepBuilder.getTarget())); CxxLibraryBuilder cxxBuilder = new CxxLibraryBuilder(BuildTargetFactory.newInstance("//:cxx")) .setSrcs(ImmutableSortedSet.of(SourceWithFlags.of(new FakeSourcePath("cxx.c")))) .setDeps(ImmutableSortedSet.of(cxxDepBuilder.getTarget())); CxxLibraryBuilder preloadCxxBuilder = new CxxLibraryBuilder(BuildTargetFactory.newInstance("//:preload_cxx")) .setSrcs(ImmutableSortedSet.of(SourceWithFlags.of(new FakeSourcePath("preload_cxx.c")))) .setDeps(ImmutableSortedSet.of(transitiveCxxDepBuilder.getTarget())); PythonBuckConfig config = new PythonBuckConfig(FakeBuckConfig.builder().build(), new AlwaysFoundExecutableFinder()) { @Override public NativeLinkStrategy getNativeLinkStrategy() { return NativeLinkStrategy.MERGED; } }; PythonBinaryBuilder binaryBuilder = new PythonBinaryBuilder( BuildTargetFactory.newInstance("//:bin"), config, PythonTestUtils.PYTHON_PLATFORMS, CxxPlatformUtils.DEFAULT_PLATFORM, CxxPlatformUtils.DEFAULT_PLATFORMS); binaryBuilder.setMainModule("main"); binaryBuilder.setDeps(ImmutableSortedSet.of(cxxBuilder.getTarget())); binaryBuilder.setPreloadDeps(ImmutableSet.of(preloadCxxBuilder.getTarget())); BuildRuleResolver resolver = new BuildRuleResolver( TargetGraphFactory.newInstance( transitiveCxxDepBuilder.build(), cxxDepBuilder.build(), cxxBuilder.build(), preloadCxxBuilder.build(), binaryBuilder.build()), new DefaultTargetNodeToBuildRuleTransformer()); transitiveCxxDepBuilder.build(resolver); cxxDepBuilder.build(resolver); cxxBuilder.build(resolver); preloadCxxBuilder.build(resolver); PythonBinary binary = binaryBuilder.build(resolver); assertThat( Iterables.transform(binary.getComponents().getNativeLibraries().keySet(), Object::toString), Matchers.containsInAnyOrder( "libomnibus.so", "libcxx.so", "libpreload_cxx.so", "libtransitive_dep.so")); } @Test public void pexBuilderAddedToParseTimeDeps() { final BuildTarget pexBuilder = BuildTargetFactory.newInstance("//:pex_builder"); PythonBuckConfig config = new PythonBuckConfig(FakeBuckConfig.builder().build(), new AlwaysFoundExecutableFinder()) { @Override public Optional<BuildTarget> getPexExecutorTarget() { return Optional.of(pexBuilder); } }; PythonBinaryBuilder inplaceBinary = new PythonBinaryBuilder( BuildTargetFactory.newInstance("//:bin"), config, PythonTestUtils.PYTHON_PLATFORMS, CxxPlatformUtils.DEFAULT_PLATFORM, CxxPlatformUtils.DEFAULT_PLATFORMS) .setPackageStyle(PythonBuckConfig.PackageStyle.INPLACE); assertThat(inplaceBinary.findImplicitDeps(), Matchers.not(Matchers.hasItem(pexBuilder))); PythonBinaryBuilder standaloneBinary = new PythonBinaryBuilder( BuildTargetFactory.newInstance("//:bin"), config, PythonTestUtils.PYTHON_PLATFORMS, CxxPlatformUtils.DEFAULT_PLATFORM, CxxPlatformUtils.DEFAULT_PLATFORMS) .setPackageStyle(PythonBuckConfig.PackageStyle.STANDALONE); assertThat(standaloneBinary.findImplicitDeps(), Matchers.hasItem(pexBuilder)); } @Test public void pexToolBuilderAddedToRuntimeDeps() throws Exception { BuildRuleResolver resolver = new BuildRuleResolver( TargetGraphFactory.newInstance(), new DefaultTargetNodeToBuildRuleTransformer()); ShBinary pyTool = new ShBinaryBuilder(BuildTargetFactory.newInstance("//:py_tool")) .setMain(new FakeSourcePath("run.sh")) .build(resolver); PythonBuckConfig config = new PythonBuckConfig(FakeBuckConfig.builder().build(), new AlwaysFoundExecutableFinder()) { @Override public Optional<Tool> getPexExecutor(BuildRuleResolver resolver) { return Optional.of(pyTool.getExecutableCommand()); } }; PythonBinary standaloneBinary = new PythonBinaryBuilder( BuildTargetFactory.newInstance("//:bin"), config, PythonTestUtils.PYTHON_PLATFORMS, CxxPlatformUtils.DEFAULT_PLATFORM, CxxPlatformUtils.DEFAULT_PLATFORMS) .setMainModule("hello") .setPackageStyle(PythonBuckConfig.PackageStyle.STANDALONE) .build(resolver); assertThat( standaloneBinary.getRuntimeDeps().collect(MoreCollectors.toImmutableSet()), Matchers.hasItem(pyTool.getBuildTarget())); } @Test public void targetGraphOnlyDepsDoNotAffectRuleKey() throws Exception { ProjectFilesystem filesystem = new FakeProjectFilesystem(); for (PythonBuckConfig.PackageStyle packageStyle : PythonBuckConfig.PackageStyle.values()) { // First, calculate the rule key of a python binary with no deps. PythonBinaryBuilder pythonBinaryBuilder = PythonBinaryBuilder.create(BuildTargetFactory.newInstance("//:bin")) .setMainModule("main") .setPackageStyle(packageStyle); TargetGraph targetGraph = TargetGraphFactory.newInstance(pythonBinaryBuilder.build()); BuildRuleResolver resolver = new BuildRuleResolver(targetGraph, new DefaultTargetNodeToBuildRuleTransformer()); PythonBinary pythonBinaryWithoutDep = pythonBinaryBuilder.build(resolver, filesystem, targetGraph); RuleKey ruleKeyWithoutDep = calculateRuleKey(resolver, pythonBinaryWithoutDep); // Next, calculate the rule key of a python binary with a deps on another binary. CxxBinaryBuilder cxxBinaryBuilder = new CxxBinaryBuilder(BuildTargetFactory.newInstance("//:dep")); pythonBinaryBuilder.setDeps(ImmutableSortedSet.of(cxxBinaryBuilder.getTarget())); targetGraph = TargetGraphFactory.newInstance(cxxBinaryBuilder.build(), pythonBinaryBuilder.build()); resolver = new BuildRuleResolver(targetGraph, new DefaultTargetNodeToBuildRuleTransformer()); cxxBinaryBuilder.build(resolver, filesystem, targetGraph); PythonBinary pythonBinaryWithDep = pythonBinaryBuilder.build(resolver, filesystem, targetGraph); RuleKey ruleKeyWithDep = calculateRuleKey(resolver, pythonBinaryWithDep); // Verify that the rule keys are identical. assertThat(ruleKeyWithoutDep, Matchers.equalTo(ruleKeyWithDep)); } } @Test public void platformDeps() throws Exception { SourcePath libASrc = new FakeSourcePath("libA.py"); PythonLibraryBuilder libraryABuilder = PythonLibraryBuilder.createBuilder(BuildTargetFactory.newInstance("//:libA")) .setSrcs(SourceList.ofUnnamedSources(ImmutableSortedSet.of(libASrc))); SourcePath libBSrc = new FakeSourcePath("libB.py"); PythonLibraryBuilder libraryBBuilder = PythonLibraryBuilder.createBuilder(BuildTargetFactory.newInstance("//:libB")) .setSrcs(SourceList.ofUnnamedSources(ImmutableSortedSet.of(libBSrc))); PythonBinaryBuilder binaryBuilder = PythonBinaryBuilder.create(BuildTargetFactory.newInstance("//:bin")) .setMainModule("main") .setPlatformDeps( PatternMatchedCollection.<ImmutableSortedSet<BuildTarget>>builder() .add( Pattern.compile( CxxPlatformUtils.DEFAULT_PLATFORM.getFlavor().toString(), Pattern.LITERAL), ImmutableSortedSet.of(libraryABuilder.getTarget())) .add( Pattern.compile("matches nothing", Pattern.LITERAL), ImmutableSortedSet.of(libraryBBuilder.getTarget())) .build()); TargetGraph targetGraph = TargetGraphFactory.newInstance( libraryABuilder.build(), libraryBBuilder.build(), binaryBuilder.build()); BuildRuleResolver resolver = new BuildRuleResolver(targetGraph, new DefaultTargetNodeToBuildRuleTransformer()); PythonBinary binary = (PythonBinary) resolver.requireRule(binaryBuilder.getTarget()); assertThat( binary.getComponents().getModules().values(), Matchers.allOf(Matchers.hasItem(libASrc), Matchers.not(Matchers.hasItem(libBSrc)))); } @Test public void cxxPlatform() throws Exception { CxxPlatform platformA = CxxPlatformUtils.DEFAULT_PLATFORM.withFlavor(InternalFlavor.of("platA")); CxxPlatform platformB = CxxPlatformUtils.DEFAULT_PLATFORM.withFlavor(InternalFlavor.of("platB")); FlavorDomain<CxxPlatform> cxxPlatforms = FlavorDomain.from("C/C++ platform", ImmutableList.of(platformA, platformB)); SourcePath libASrc = new FakeSourcePath("libA.py"); PythonLibraryBuilder libraryABuilder = new PythonLibraryBuilder( BuildTargetFactory.newInstance("//:libA"), PythonTestUtils.PYTHON_PLATFORMS, cxxPlatforms) .setSrcs(SourceList.ofUnnamedSources(ImmutableSortedSet.of(libASrc))); SourcePath libBSrc = new FakeSourcePath("libB.py"); PythonLibraryBuilder libraryBBuilder = new PythonLibraryBuilder( BuildTargetFactory.newInstance("//:libB"), PythonTestUtils.PYTHON_PLATFORMS, cxxPlatforms) .setSrcs(SourceList.ofUnnamedSources(ImmutableSortedSet.of(libBSrc))); PythonBinaryBuilder binaryBuilder = new PythonBinaryBuilder( BuildTargetFactory.newInstance("//:bin"), PythonTestUtils.PYTHON_CONFIG, PythonTestUtils.PYTHON_PLATFORMS, CxxPlatformUtils.DEFAULT_PLATFORM, cxxPlatforms) .setMainModule("main") .setCxxPlatform(platformA.getFlavor()) .setPlatformDeps( PatternMatchedCollection.<ImmutableSortedSet<BuildTarget>>builder() .add( Pattern.compile(platformA.getFlavor().toString(), Pattern.LITERAL), ImmutableSortedSet.of(libraryABuilder.getTarget())) .add( Pattern.compile(platformB.getFlavor().toString(), Pattern.LITERAL), ImmutableSortedSet.of(libraryBBuilder.getTarget())) .build()); TargetGraph targetGraph = TargetGraphFactory.newInstance( libraryABuilder.build(), libraryBBuilder.build(), binaryBuilder.build()); BuildRuleResolver resolver = new BuildRuleResolver(targetGraph, new DefaultTargetNodeToBuildRuleTransformer()); PythonBinary binary = (PythonBinary) resolver.requireRule(binaryBuilder.getTarget()); assertThat( binary.getComponents().getModules().values(), Matchers.allOf(Matchers.hasItem(libASrc), Matchers.not(Matchers.hasItem(libBSrc)))); } private RuleKey calculateRuleKey(BuildRuleResolver ruleResolver, BuildRule rule) { SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(ruleResolver); DefaultRuleKeyFactory ruleKeyFactory = new DefaultRuleKeyFactory( new RuleKeyFieldLoader(0), new StackedFileHashCache( ImmutableList.of( DefaultFileHashCache.createDefaultFileHashCache(rule.getProjectFilesystem()))), new SourcePathResolver(ruleFinder), ruleFinder); return ruleKeyFactory.build(rule); } }