/* * 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 com.facebook.buck.rules.TestCellBuilder.createCellRoots; import static org.junit.Assert.assertEquals; 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.CxxLibrary; import com.facebook.buck.cxx.CxxLibraryBuilder; import com.facebook.buck.cxx.CxxLink; import com.facebook.buck.cxx.CxxPlatformUtils; import com.facebook.buck.cxx.CxxTestUtils; import com.facebook.buck.cxx.Linker; import com.facebook.buck.cxx.NativeLinkTarget; import com.facebook.buck.cxx.NativeLinkTargetMode; import com.facebook.buck.cxx.NativeLinkable; import com.facebook.buck.cxx.NativeLinkableInput; import com.facebook.buck.cxx.PrebuiltCxxLibraryBuilder; import com.facebook.buck.io.ExecutableFinder; 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.DefaultTargetNodeToBuildRuleTransformer; import com.facebook.buck.rules.FakeSourcePath; 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.args.Arg; import com.facebook.buck.rules.coercer.PatternMatchedCollection; import com.facebook.buck.rules.macros.StringWithMacrosUtils; import com.facebook.buck.testutil.FakeProjectFilesystem; import com.facebook.buck.testutil.TargetGraphFactory; import com.facebook.buck.util.MoreCollectors; 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 java.nio.file.Paths; import java.util.Optional; import java.util.regex.Pattern; import org.hamcrest.Matchers; import org.junit.Test; public class CxxPythonExtensionDescriptionTest { 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.empty()); private static final BuildTarget PYTHON3_DEP_TARGET = BuildTargetFactory.newInstance("//:python3_dep"); private static final PythonPlatform PY3 = PythonPlatform.of( InternalFlavor.of("py3"), new PythonEnvironment(Paths.get("python3"), PythonVersion.of("CPython", "3.5")), Optional.empty()); @Test public void createBuildRuleBaseModule() throws Exception { ProjectFilesystem filesystem = new FakeProjectFilesystem(); BuildTarget target = BuildTargetFactory.newInstance("//:target"); CxxPythonExtensionBuilder builder = new CxxPythonExtensionBuilder( target, FlavorDomain.of("Python Platform", PY2, PY3), new CxxBuckConfig(FakeBuckConfig.builder().build()), CxxTestUtils.createDefaultPlatforms()); TargetGraph targetGraph = TargetGraphFactory.newInstance(builder.build()); BuildRuleResolver resolver = new BuildRuleResolver(targetGraph, new DefaultTargetNodeToBuildRuleTransformer()); CxxPythonExtension normal = builder.build(resolver, filesystem, targetGraph); PythonPackageComponents normalComps = normal.getPythonPackageComponents(PY2, CxxPlatformUtils.DEFAULT_PLATFORM); assertEquals( ImmutableSet.of( target .getBasePath() .resolve(CxxPythonExtensionDescription.getExtensionName(target.getShortName()))), normalComps.getModules().keySet()); // Verify that explicitly setting works. BuildTarget target2 = BuildTargetFactory.newInstance("//:target2#py2"); String name = "blah"; CxxPythonExtensionBuilder baseModuleBuilder = new CxxPythonExtensionBuilder( target2, FlavorDomain.of("Python Platform", PY2, PY3), new CxxBuckConfig(FakeBuckConfig.builder().build()), CxxTestUtils.createDefaultPlatforms()) .setBaseModule(name); targetGraph = TargetGraphFactory.newInstance(baseModuleBuilder.build()); resolver = new BuildRuleResolver(targetGraph, new DefaultTargetNodeToBuildRuleTransformer()); CxxPythonExtension baseModule = baseModuleBuilder.build(resolver, filesystem, targetGraph); PythonPackageComponents baseModuleComps = baseModule.getPythonPackageComponents(PY2, CxxPlatformUtils.DEFAULT_PLATFORM); assertEquals( ImmutableSet.of( Paths.get(name) .resolve(CxxPythonExtensionDescription.getExtensionName(target2.getShortName()))), baseModuleComps.getModules().keySet()); } @Test public void createBuildRuleNativeLinkableDep() throws Exception { ProjectFilesystem filesystem = new FakeProjectFilesystem(); BuildTarget target = BuildTargetFactory.newInstance("//:target"); // Setup a C/C++ library that we'll depend on form the C/C++ binary description. BuildTarget cxxLibraryTarget = BuildTargetFactory.newInstance("//:dep"); CxxLibraryBuilder cxxLibraryBuilder = new CxxLibraryBuilder(cxxLibraryTarget) .setSrcs( ImmutableSortedSet.of( SourceWithFlags.of(new FakeSourcePath("something.cpp"), ImmutableList.of()))); CxxPythonExtensionBuilder builder = new CxxPythonExtensionBuilder( target, FlavorDomain.of("Python Platform", PY2, PY3), new CxxBuckConfig(FakeBuckConfig.builder().build()), CxxTestUtils.createDefaultPlatforms()) .setDeps(ImmutableSortedSet.of(cxxLibraryTarget)); TargetGraph targetGraph = TargetGraphFactory.newInstance(cxxLibraryBuilder.build(), builder.build()); BuildRuleResolver resolver = new BuildRuleResolver(targetGraph, new DefaultTargetNodeToBuildRuleTransformer()); SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(resolver); CxxLibrary dep = (CxxLibrary) cxxLibraryBuilder.build(resolver, filesystem, targetGraph); CxxPythonExtension extension = builder.build(resolver, filesystem, targetGraph); NativeLinkableInput depInput = dep.getNativeLinkableInput( CxxPlatformUtils.DEFAULT_PLATFORM, Linker.LinkableDepType.SHARED); // Verify that the shared library dep propagated to the link rule. extension.getPythonPackageComponents(PY2, CxxPlatformUtils.DEFAULT_PLATFORM); BuildRule rule = resolver.getRule( CxxPythonExtensionDescription.getExtensionTarget( target, PY2.getFlavor(), CxxPlatformUtils.DEFAULT_PLATFORM.getFlavor())); assertThat( rule.getBuildDeps(), Matchers.hasItems( FluentIterable.from(depInput.getArgs()) .transformAndConcat(arg -> arg.getDeps(ruleFinder)) .toArray(BuildRule.class))); } @Test public void createBuildRulePythonPackageable() throws Exception { ProjectFilesystem filesystem = new FakeProjectFilesystem(); BuildTarget target = BuildTargetFactory.newInstance("//:target"); CxxPythonExtensionBuilder builder = new CxxPythonExtensionBuilder( target, FlavorDomain.of("Python Platform", PY2, PY3), new CxxBuckConfig(FakeBuckConfig.builder().build()), CxxTestUtils.createDefaultPlatforms()); TargetGraph targetGraph = TargetGraphFactory.newInstance(builder.build()); BuildRuleResolver resolver = new BuildRuleResolver(targetGraph, new DefaultTargetNodeToBuildRuleTransformer()); CxxPythonExtension extension = builder.build(resolver, filesystem, targetGraph); // Verify that we get the expected view from the python packageable interface. PythonPackageComponents actualComponent = extension.getPythonPackageComponents(PY2, CxxPlatformUtils.DEFAULT_PLATFORM); BuildRule rule = resolver.getRule( CxxPythonExtensionDescription.getExtensionTarget( target, PY2.getFlavor(), CxxPlatformUtils.DEFAULT_PLATFORM.getFlavor())); PythonPackageComponents expectedComponents = PythonPackageComponents.of( ImmutableMap.of( target .getBasePath() .resolve(CxxPythonExtensionDescription.getExtensionName(target.getShortName())), rule.getSourcePathToOutput()), ImmutableMap.of(), ImmutableMap.of(), ImmutableSet.of(), Optional.of(false)); assertEquals(expectedComponents, actualComponent); } @Test public void findDepsFromParamsAddsPythonDep() throws Exception { ProjectFilesystem filesystem = new FakeProjectFilesystem(); BuildTarget target = BuildTargetFactory.newInstance("//:target"); CxxPythonExtensionDescription desc = new CxxPythonExtensionBuilder( target, FlavorDomain.of( "Python Platform", PY2.withCxxLibrary(PYTHON2_DEP_TARGET), PY3.withCxxLibrary(PYTHON3_DEP_TARGET)), new CxxBuckConfig(FakeBuckConfig.builder().build()), CxxTestUtils.createDefaultPlatforms()) .build() .getDescription(); CxxPythonExtensionDescriptionArg constructorArg = CxxPythonExtensionDescriptionArg.builder().setName("target").build(); ImmutableSortedSet.Builder<BuildTarget> builder = ImmutableSortedSet.naturalOrder(); desc.findDepsForTargetFromConstructorArgs( BuildTargetFactory.newInstance("//foo:bar"), createCellRoots(filesystem), constructorArg, builder, ImmutableSortedSet.naturalOrder()); assertThat(builder.build(), Matchers.contains(PYTHON2_DEP_TARGET, PYTHON3_DEP_TARGET)); } @Test public void py2AndPy3PropagateToLinkRules() throws Exception { ProjectFilesystem filesystem = new FakeProjectFilesystem(); PrebuiltCxxLibraryBuilder python2Builder = new PrebuiltCxxLibraryBuilder(PYTHON2_DEP_TARGET) .setHeaderOnly(true) .setExportedLinkerFlags(ImmutableList.of("-lpython2")); PrebuiltCxxLibraryBuilder python3Builder = new PrebuiltCxxLibraryBuilder(PYTHON3_DEP_TARGET) .setHeaderOnly(true) .setExportedLinkerFlags(ImmutableList.of("-lpython3")); PythonPlatform py2 = PY2.withCxxLibrary(PYTHON2_DEP_TARGET); PythonPlatform py3 = PY3.withCxxLibrary(PYTHON3_DEP_TARGET); BuildTarget target = BuildTargetFactory.newInstance("//:target"); CxxPythonExtensionBuilder builder = new CxxPythonExtensionBuilder( target, FlavorDomain.of("Python Platform", py2, py3), new CxxBuckConfig(FakeBuckConfig.builder().build()), CxxTestUtils.createDefaultPlatforms()); TargetGraph targetGraph = TargetGraphFactory.newInstance( python2Builder.build(), python3Builder.build(), builder.build()); BuildRuleResolver resolver = new BuildRuleResolver(targetGraph, new DefaultTargetNodeToBuildRuleTransformer()); SourcePathResolver pathResolver = new SourcePathResolver(new SourcePathRuleFinder(resolver)); python2Builder.build(resolver, filesystem, targetGraph); python3Builder.build(resolver, filesystem, targetGraph); CxxPythonExtension extension = builder.build(resolver, filesystem, targetGraph); // Get the py2 extension, and verify it pulled in the py2 lib but not the py3 lib. CxxLink py2Ext = (CxxLink) extension.getExtension(py2, CxxPlatformUtils.DEFAULT_PLATFORM); assertThat( Arg.stringify(py2Ext.getArgs(), pathResolver), Matchers.allOf(Matchers.hasItem("-lpython2"), Matchers.not(Matchers.hasItem("-lpython3")))); // Get the py3 extension, and verify it pulled in the py3 lib but not the py2 lib. CxxLink py3Ext = (CxxLink) extension.getExtension(py3, CxxPlatformUtils.DEFAULT_PLATFORM); assertThat( Arg.stringify(py3Ext.getArgs(), pathResolver), Matchers.allOf(Matchers.hasItem("-lpython3"), Matchers.not(Matchers.hasItem("-lpython2")))); } @Test public void nativeLinkTargetMode() throws Exception { BuildRuleResolver resolver = new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer()); CxxPythonExtensionBuilder builder = new CxxPythonExtensionBuilder( BuildTargetFactory.newInstance("//:rule"), FlavorDomain.of("Python Platform", PY2, PY3), new CxxBuckConfig(FakeBuckConfig.builder().build()), CxxTestUtils.createDefaultPlatforms()); CxxPythonExtension rule = builder.build(resolver); NativeLinkTarget nativeLinkTarget = rule.getNativeLinkTarget(PY2); assertThat( nativeLinkTarget.getNativeLinkTargetMode(CxxPlatformUtils.DEFAULT_PLATFORM), Matchers.equalTo(NativeLinkTargetMode.library())); } @Test public void nativeLinkTargetDeps() throws Exception { BuildRuleResolver resolver = new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer()); CxxLibrary dep = (CxxLibrary) new CxxLibraryBuilder(BuildTargetFactory.newInstance("//:dep")).build(resolver); CxxPythonExtensionBuilder builder = new CxxPythonExtensionBuilder( BuildTargetFactory.newInstance("//:rule"), FlavorDomain.of("Python Platform", PY2, PY3), new CxxBuckConfig(FakeBuckConfig.builder().build()), CxxTestUtils.createDefaultPlatforms()); CxxPythonExtension rule = builder.setDeps(ImmutableSortedSet.of(dep.getBuildTarget())).build(resolver); NativeLinkTarget nativeLinkTarget = rule.getNativeLinkTarget(PY2); assertThat( ImmutableList.copyOf( nativeLinkTarget.getNativeLinkTargetDeps(CxxPlatformUtils.DEFAULT_PLATFORM)), Matchers.<NativeLinkable>hasItem(dep)); } @Test public void nativeLinkTargetDepsIncludePlatformCxxLibrary() throws Exception { ProjectFilesystem filesystem = new FakeProjectFilesystem(); CxxLibraryBuilder python2Builder = new CxxLibraryBuilder(PYTHON2_DEP_TARGET); PythonPlatform platform = PY2.withCxxLibrary(PYTHON2_DEP_TARGET); CxxPythonExtensionBuilder builder = new CxxPythonExtensionBuilder( BuildTargetFactory.newInstance("//:rule"), FlavorDomain.of("Python Platform", platform), new CxxBuckConfig(FakeBuckConfig.builder().build()), CxxTestUtils.createDefaultPlatforms()); TargetGraph targetGraph = TargetGraphFactory.newInstance(python2Builder.build(), builder.build()); BuildRuleResolver resolver = new BuildRuleResolver(targetGraph, new DefaultTargetNodeToBuildRuleTransformer()); python2Builder.build(resolver, filesystem, targetGraph); CxxPythonExtension rule = builder.build(resolver, filesystem, targetGraph); NativeLinkTarget nativeLinkTarget = rule.getNativeLinkTarget(platform); assertThat( ImmutableList.copyOf( nativeLinkTarget.getNativeLinkTargetDeps(CxxPlatformUtils.DEFAULT_PLATFORM)), Matchers.hasItem((NativeLinkable) resolver.getRule(PYTHON2_DEP_TARGET))); } @Test public void nativeLinkTargetInput() throws Exception { CxxPythonExtensionBuilder builder = new CxxPythonExtensionBuilder( BuildTargetFactory.newInstance("//:rule"), FlavorDomain.of("Python Platform", PY2, PY3), new CxxBuckConfig(FakeBuckConfig.builder().build()), CxxTestUtils.createDefaultPlatforms()); builder.setLinkerFlags(ImmutableList.of(StringWithMacrosUtils.format("--flag"))); BuildRuleResolver resolver = new BuildRuleResolver( TargetGraphFactory.newInstance(builder.build()), new DefaultTargetNodeToBuildRuleTransformer()); SourcePathResolver pathResolver = new SourcePathResolver(new SourcePathRuleFinder(resolver)); CxxPythonExtension rule = builder.build(resolver); NativeLinkTarget nativeLinkTarget = rule.getNativeLinkTarget(PY2); NativeLinkableInput input = nativeLinkTarget.getNativeLinkTargetInput(CxxPlatformUtils.DEFAULT_PLATFORM); assertThat(Arg.stringify(input.getArgs(), pathResolver), Matchers.hasItems("--flag")); } @Test public void platformDeps() throws Exception { BuildRuleResolver resolver = new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer()); CxxLibrary dep = (CxxLibrary) new CxxLibraryBuilder(BuildTargetFactory.newInstance("//:dep")).build(resolver); CxxPythonExtensionBuilder builder = new CxxPythonExtensionBuilder( BuildTargetFactory.newInstance("//:rule"), FlavorDomain.of("Python Platform", PY2, PY3), new CxxBuckConfig(FakeBuckConfig.builder().build()), CxxTestUtils.createDefaultPlatforms()); CxxPythonExtension rule = builder .setPlatformDeps( PatternMatchedCollection.<ImmutableSortedSet<BuildTarget>>builder() .add( Pattern.compile(PY2.getFlavor().toString()), ImmutableSortedSet.of(dep.getBuildTarget())) .build()) .build(resolver); NativeLinkTarget py2NativeLinkTarget = rule.getNativeLinkTarget(PY2); assertThat( ImmutableList.copyOf( py2NativeLinkTarget.getNativeLinkTargetDeps(CxxPlatformUtils.DEFAULT_PLATFORM)), Matchers.<NativeLinkable>hasItem(dep)); NativeLinkTarget py3NativeLinkTarget = rule.getNativeLinkTarget(PY3); assertThat( ImmutableList.copyOf( py3NativeLinkTarget.getNativeLinkTargetDeps(CxxPlatformUtils.DEFAULT_PLATFORM)), Matchers.not(Matchers.<NativeLinkable>hasItem(dep))); } @Test public void platformDepsSeparateLinkage() throws Exception { PythonBuckConfig pythonBuckConfig = new PythonBuckConfig(FakeBuckConfig.builder().build(), new ExecutableFinder()); FlavorDomain<PythonPlatform> pythonPlatforms = FlavorDomain.of("Python Platform", PY2, PY3); CxxLibraryBuilder depBuilder = new CxxLibraryBuilder(BuildTargetFactory.newInstance("//:dep")) .setSrcs(ImmutableSortedSet.of(SourceWithFlags.of(new FakeSourcePath("test.c")))); CxxPythonExtensionBuilder extensionBuilder = new CxxPythonExtensionBuilder( BuildTargetFactory.newInstance("//:rule"), pythonPlatforms, new CxxBuckConfig(FakeBuckConfig.builder().build()), CxxTestUtils.createDefaultPlatforms()) .setPlatformDeps( PatternMatchedCollection.<ImmutableSortedSet<BuildTarget>>builder() .add( Pattern.compile(PY2.getFlavor().toString()), ImmutableSortedSet.of(depBuilder.getTarget())) .build()); PythonBinaryBuilder binary2Builder = new PythonBinaryBuilder( BuildTargetFactory.newInstance("//:bin2"), pythonBuckConfig, pythonPlatforms, CxxPlatformUtils.DEFAULT_PLATFORM, CxxTestUtils.createDefaultPlatforms()) .setMainModule("test") .setPlatform(PY2.getFlavor().toString()) .setDeps(ImmutableSortedSet.of(extensionBuilder.getTarget())); PythonBinaryBuilder binary3Builder = new PythonBinaryBuilder( BuildTargetFactory.newInstance("//:bin3"), pythonBuckConfig, pythonPlatforms, CxxPlatformUtils.DEFAULT_PLATFORM, CxxTestUtils.createDefaultPlatforms()) .setMainModule("test") .setPlatform(PY3.getFlavor().toString()) .setDeps(ImmutableSortedSet.of(extensionBuilder.getTarget())); BuildRuleResolver resolver = new BuildRuleResolver( TargetGraphFactory.newInstance( depBuilder.build(), extensionBuilder.build(), binary2Builder.build()), new DefaultTargetNodeToBuildRuleTransformer()); depBuilder.build(resolver); extensionBuilder.build(resolver); PythonBinary binary2 = binary2Builder.build(resolver); PythonBinary binary3 = binary3Builder.build(resolver); assertThat( binary2.getComponents().getNativeLibraries().keySet(), Matchers.contains(Paths.get("libdep.so"))); assertThat( binary3.getComponents().getNativeLibraries().keySet(), Matchers.not(Matchers.contains(Paths.get("libdep.so")))); } @Test public void runtimeDeps() throws Exception { BuildRuleResolver resolver = new BuildRuleResolver( TargetGraphFactory.newInstance( new CxxBinaryBuilder(BuildTargetFactory.newInstance("//:dep#sandbox")).build()), new DefaultTargetNodeToBuildRuleTransformer()); BuildTarget depTarget = BuildTargetFactory.newInstance("//:dep"); BuildRule cxxBinary = new CxxBinaryBuilder(depTarget).build(resolver); CxxPythonExtension cxxPythonExtension = new CxxPythonExtensionBuilder( BuildTargetFactory.newInstance("//:ext"), FlavorDomain.of("Python Platform", PY2, PY3), new CxxBuckConfig(FakeBuckConfig.builder().build()), CxxTestUtils.createDefaultPlatforms()) .setDeps(ImmutableSortedSet.of(cxxBinary.getBuildTarget())) .build(resolver); assertThat( cxxPythonExtension.getRuntimeDeps().collect(MoreCollectors.toImmutableSet()), Matchers.hasItem(cxxBinary.getBuildTarget())); } @Test public void moduleName() throws Exception { BuildRuleResolver resolver = new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer()); CxxPythonExtension cxxPythonExtension = new CxxPythonExtensionBuilder( BuildTargetFactory.newInstance("//:ext"), FlavorDomain.of("Python Platform", PY2, PY3), new CxxBuckConfig(FakeBuckConfig.builder().build()), CxxTestUtils.createDefaultPlatforms()) .setModuleName("blah") .build(resolver); assertThat( cxxPythonExtension.getModule().toString(), Matchers.endsWith(CxxPythonExtensionDescription.getExtensionName("blah"))); } }