com.facebook.buck.features.go.GoTestDescription.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.buck.features.go.GoTestDescription.java

Source

/*
 * Copyright 2015-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.features.go;

import com.facebook.buck.core.cell.CellPathResolver;
import com.facebook.buck.core.description.MetadataProvidingDescription;
import com.facebook.buck.core.description.arg.CommonDescriptionArg;
import com.facebook.buck.core.description.arg.HasContacts;
import com.facebook.buck.core.description.arg.HasDeclaredDeps;
import com.facebook.buck.core.description.arg.HasSrcs;
import com.facebook.buck.core.description.arg.HasTestTimeout;
import com.facebook.buck.core.description.attr.ImplicitDepsInferringDescription;
import com.facebook.buck.core.exceptions.HumanReadableException;
import com.facebook.buck.core.model.BuildTarget;
import com.facebook.buck.core.model.Flavor;
import com.facebook.buck.core.model.Flavored;
import com.facebook.buck.core.model.InternalFlavor;
import com.facebook.buck.core.model.targetgraph.BuildRuleCreationContextWithTargetGraph;
import com.facebook.buck.core.model.targetgraph.DescriptionWithTargetGraph;
import com.facebook.buck.core.rules.ActionGraphBuilder;
import com.facebook.buck.core.rules.BuildRule;
import com.facebook.buck.core.rules.BuildRuleParams;
import com.facebook.buck.core.rules.SourcePathRuleFinder;
import com.facebook.buck.core.rules.common.BuildableSupport;
import com.facebook.buck.core.rules.impl.NoopBuildRuleWithDeclaredAndExtraDeps;
import com.facebook.buck.core.sourcepath.SourcePath;
import com.facebook.buck.core.sourcepath.resolver.SourcePathResolver;
import com.facebook.buck.core.sourcepath.resolver.impl.DefaultSourcePathResolver;
import com.facebook.buck.core.toolchain.ToolchainProvider;
import com.facebook.buck.core.toolchain.tool.Tool;
import com.facebook.buck.core.util.immutables.BuckStyleImmutable;
import com.facebook.buck.cxx.toolchain.CxxPlatforms;
import com.facebook.buck.cxx.toolchain.linker.Linker;
import com.facebook.buck.features.go.GoListStep.ListType;
import com.facebook.buck.io.filesystem.ProjectFilesystem;
import com.facebook.buck.rules.macros.LocationMacroExpander;
import com.facebook.buck.rules.macros.Macro;
import com.facebook.buck.rules.macros.MacroExpander;
import com.facebook.buck.rules.macros.StringWithMacros;
import com.facebook.buck.rules.macros.StringWithMacrosConverter;
import com.facebook.buck.test.config.TestBuckConfig;
import com.facebook.buck.versions.Version;
import com.facebook.buck.versions.VersionRoot;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableCollection;
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.Maps;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Optional;
import org.immutables.value.Value;

public class GoTestDescription implements DescriptionWithTargetGraph<GoTestDescriptionArg>, Flavored,
        MetadataProvidingDescription<GoTestDescriptionArg>,
        ImplicitDepsInferringDescription<GoTestDescription.AbstractGoTestDescriptionArg>,
        VersionRoot<GoTestDescriptionArg> {

    private static final Flavor TEST_LIBRARY_FLAVOR = InternalFlavor.of("test-library");
    public static final ImmutableList<MacroExpander<? extends Macro, ?>> MACRO_EXPANDERS = ImmutableList
            .of(new LocationMacroExpander());

    private final GoBuckConfig goBuckConfig;
    private final ToolchainProvider toolchainProvider;

    public GoTestDescription(GoBuckConfig goBuckConfig, ToolchainProvider toolchainProvider) {
        this.goBuckConfig = goBuckConfig;
        this.toolchainProvider = toolchainProvider;
    }

    @Override
    public Class<GoTestDescriptionArg> getConstructorArgType() {
        return GoTestDescriptionArg.class;
    }

    @Override
    public boolean hasFlavors(ImmutableSet<Flavor> flavors) {
        return getGoToolchain().getPlatformFlavorDomain().containsAnyOf(flavors)
                || flavors.contains(TEST_LIBRARY_FLAVOR);
    }

    @Override
    public <U> Optional<U> createMetadata(BuildTarget buildTarget, ActionGraphBuilder graphBuilder,
            CellPathResolver cellRoots, GoTestDescriptionArg args,
            Optional<ImmutableMap<BuildTarget, Version>> selectedVersions, Class<U> metadataClass) {
        Optional<GoPlatform> platform = getGoToolchain().getPlatformFlavorDomain().getValue(buildTarget);

        if (metadataClass.isAssignableFrom(GoLinkable.class)
                && buildTarget.getFlavors().contains(TEST_LIBRARY_FLAVOR)) {
            Preconditions.checkState(platform.isPresent());

            Path packageName = getGoPackageName(graphBuilder, buildTarget, args);

            SourcePath output = graphBuilder.requireRule(buildTarget).getSourcePathToOutput();
            return Optional.of(metadataClass
                    .cast(GoLinkable.builder().setGoLinkInput(ImmutableMap.of(packageName, output)).build()));
        } else if (buildTarget.getFlavors().contains(GoDescriptors.TRANSITIVE_LINKABLES_FLAVOR)
                && buildTarget.getFlavors().contains(TEST_LIBRARY_FLAVOR)) {
            Preconditions.checkState(platform.isPresent());

            ImmutableSet<BuildTarget> deps;
            if (args.getLibrary().isPresent()) {
                GoLibraryDescriptionArg libraryArg = graphBuilder
                        .requireMetadata(args.getLibrary().get(), GoLibraryDescriptionArg.class).get();
                deps = ImmutableSortedSet.<BuildTarget>naturalOrder().addAll(args.getDeps())
                        .addAll(libraryArg.getDeps()).build();
            } else {
                deps = args.getDeps();
            }

            return Optional.of(metadataClass.cast(GoDescriptors.requireTransitiveGoLinkables(buildTarget,
                    graphBuilder, platform.get(), deps, /* includeSelf */ true)));
        } else {
            return Optional.empty();
        }
    }

    private GoTestMain requireTestMainGenRule(BuildTarget buildTarget, ProjectFilesystem projectFilesystem,
            BuildRuleParams params, ActionGraphBuilder graphBuilder, GoPlatform platform,
            ImmutableSet<SourcePath> srcs, ImmutableMap<Path, ImmutableMap<String, Path>> coverVariables,
            GoTestCoverStep.Mode coverageMode, Path packageName) {
        Tool testMainGenerator = GoDescriptors.getTestMainGenerator(goBuckConfig,
                // Since TestMainGenRule produces a go binary that is later exec'd
                // to produce a go test, we want it to use the platform of the
                // current machine, not whatever was specified in the rule or config.
                // That way, tests can be generated properly on this machine.
                platform.withGoOs(AbstractGoPlatformFactory.getDefaultOs())
                        .withGoArch(AbstractGoPlatformFactory.getDefaultArch()),
                buildTarget, projectFilesystem, params, graphBuilder);

        SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(graphBuilder);

        GoTestMain generatedTestMain = new GoTestMain(
                buildTarget.withAppendedFlavors(InternalFlavor.of("test-main-src")), projectFilesystem,
                params.withDeclaredDeps(ImmutableSortedSet.<BuildRule>naturalOrder()
                        .addAll(BuildableSupport.getDepsCollection(testMainGenerator, ruleFinder)).build()),
                testMainGenerator, srcs, packageName, platform, coverVariables, coverageMode);
        graphBuilder.addToIndex(generatedTestMain);
        return generatedTestMain;
    }

    @Override
    public BuildRule createBuildRule(BuildRuleCreationContextWithTargetGraph context, BuildTarget buildTarget,
            BuildRuleParams params, GoTestDescriptionArg args) {
        GoPlatform platform = GoDescriptors.getPlatformForRule(getGoToolchain(), this.goBuckConfig, buildTarget,
                args);

        ActionGraphBuilder graphBuilder = context.getActionGraphBuilder();
        SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(graphBuilder);
        SourcePathResolver pathResolver = DefaultSourcePathResolver.from(ruleFinder);
        ProjectFilesystem projectFilesystem = context.getProjectFilesystem();

        GoTestCoverStep.Mode coverageMode;
        ImmutableSortedSet.Builder<BuildRule> extraDeps = ImmutableSortedSet.naturalOrder();
        ImmutableSet.Builder<SourcePath> srcs;
        ImmutableMap<String, Path> coverVariables;

        ImmutableSet.Builder<SourcePath> rawSrcs = ImmutableSet.builder();
        rawSrcs.addAll(args.getSrcs());
        if (args.getLibrary().isPresent()) {
            GoLibraryDescriptionArg libraryArg = graphBuilder
                    .requireMetadata(args.getLibrary().get(), GoLibraryDescriptionArg.class).get();

            rawSrcs.addAll(libraryArg.getSrcs());
        }
        if (args.getCoverageMode().isPresent()) {
            coverageMode = args.getCoverageMode().get();
            GoTestCoverStep.Mode coverage = coverageMode;

            GoTestCoverSource coverSource = (GoTestCoverSource) graphBuilder.computeIfAbsent(
                    buildTarget.withAppendedFlavors(InternalFlavor.of("gen-cover")),
                    target -> new GoTestCoverSource(target, projectFilesystem, ruleFinder, pathResolver, platform,
                            rawSrcs.build(), platform.getCover(), coverage));

            coverVariables = coverSource.getVariables();
            srcs = ImmutableSet.builder();
            srcs.addAll(coverSource.getCoveredSources()).addAll(coverSource.getTestSources());
            extraDeps.add(coverSource);
        } else {
            srcs = rawSrcs;
            coverVariables = ImmutableMap.of();
            coverageMode = GoTestCoverStep.Mode.NONE;
        }

        if (buildTarget.getFlavors().contains(TEST_LIBRARY_FLAVOR)) {
            return createTestLibrary(buildTarget, projectFilesystem,
                    params.copyAppendingExtraDeps(extraDeps.build()), graphBuilder, srcs.build(), args, platform);
        }

        GoBinary testMain = createTestMainRule(buildTarget, projectFilesystem,
                params.copyAppendingExtraDeps(extraDeps.build()), graphBuilder, srcs.build(), coverVariables,
                coverageMode, args, platform);
        graphBuilder.addToIndex(testMain);

        StringWithMacrosConverter macrosConverter = StringWithMacrosConverter.builder().setBuildTarget(buildTarget)
                .setCellPathResolver(context.getCellPathResolver()).setExpanders(MACRO_EXPANDERS).build();

        return new GoTest(buildTarget, projectFilesystem,
                params.withDeclaredDeps(ImmutableSortedSet.of(testMain)).withoutExtraDeps(), testMain,
                args.getLabels(), args.getContacts(),
                args.getTestRuleTimeoutMs().map(Optional::of).orElse(
                        goBuckConfig.getDelegate().getView(TestBuckConfig.class).getDefaultTestRuleTimeoutMs()),
                ImmutableMap
                        .copyOf(Maps.transformValues(args.getEnv(), x -> macrosConverter.convert(x, graphBuilder))),
                args.getRunTestSeparately(), args.getResources(), coverageMode);
    }

    private GoBinary createTestMainRule(BuildTarget buildTarget, ProjectFilesystem projectFilesystem,
            BuildRuleParams params, ActionGraphBuilder graphBuilder, ImmutableSet<SourcePath> srcs,
            ImmutableMap<String, Path> coverVariables, GoTestCoverStep.Mode coverageMode, GoTestDescriptionArg args,
            GoPlatform platform) {
        Path packageName = getGoPackageName(graphBuilder, buildTarget, args);
        boolean createResourcesSymlinkTree = goBuckConfig.getDelegate().getView(TestBuckConfig.class)
                .getExternalTestRunner().isPresent();

        BuildRule testLibrary = new NoopBuildRuleWithDeclaredAndExtraDeps(
                buildTarget.withAppendedFlavors(TEST_LIBRARY_FLAVOR), projectFilesystem, params);
        graphBuilder.addToIndex(testLibrary);

        BuildRule generatedTestMain = requireTestMainGenRule(buildTarget, projectFilesystem, params, graphBuilder,
                platform, srcs, ImmutableMap.of(packageName, coverVariables), coverageMode, packageName);

        GoBinary testMain = GoDescriptors.createGoBinaryRule(
                buildTarget.withAppendedFlavors(InternalFlavor.of("test-main")), projectFilesystem,
                params.withDeclaredDeps(ImmutableSortedSet.of(testLibrary))
                        .withExtraDeps(ImmutableSortedSet.of(generatedTestMain)),
                graphBuilder, goBuckConfig, args.getLinkStyle().orElse(Linker.LinkableDepType.STATIC_PIC),
                args.getLinkMode(), ImmutableSet.of(generatedTestMain.getSourcePathToOutput()),
                createResourcesSymlinkTree ? args.getResources() : ImmutableSortedSet.of(), args.getCompilerFlags(),
                args.getAssemblerFlags(), args.getLinkerFlags(), args.getExternalLinkerFlags(), platform);
        graphBuilder.addToIndex(testMain);
        return testMain;
    }

    private Path getGoPackageName(ActionGraphBuilder graphBuilder, BuildTarget target, GoTestDescriptionArg args) {
        target = target.withFlavors(); // remove flavors.

        if (args.getLibrary().isPresent()) {
            Optional<GoLibraryDescriptionArg> libraryArg = graphBuilder.requireMetadata(args.getLibrary().get(),
                    GoLibraryDescriptionArg.class);
            if (!libraryArg.isPresent()) {
                throw new HumanReadableException("Library specified in %s (%s) is not a go_library rule.", target,
                        args.getLibrary().get());
            }

            if (args.getPackageName().isPresent()) {
                throw new HumanReadableException(
                        "Test target %s specifies both library and package_name - only one should be specified",
                        target);
            }

            if (!libraryArg.get().getTests().contains(target)) {
                throw new HumanReadableException(
                        "go internal test target %s is not listed in `tests` of library %s", target,
                        args.getLibrary().get());
            }

            return libraryArg.get().getPackageName().map(Paths::get)
                    .orElse(goBuckConfig.getDefaultPackageName(args.getLibrary().get()));
        } else if (args.getPackageName().isPresent()) {
            return Paths.get(args.getPackageName().get());
        } else {
            Path packageName = goBuckConfig.getDefaultPackageName(target);
            return packageName.resolveSibling(packageName.getFileName() + "_test");
        }
    }

    private GoCompile createTestLibrary(BuildTarget buildTarget, ProjectFilesystem projectFilesystem,
            BuildRuleParams params, ActionGraphBuilder graphBuilder, ImmutableSet<SourcePath> srcs,
            GoTestDescriptionArg args, GoPlatform platform) {
        Path packageName = getGoPackageName(graphBuilder, buildTarget, args);
        GoCompile testLibrary;
        if (args.getLibrary().isPresent()) {
            // We should have already type-checked the arguments in the base rule.
            GoLibraryDescriptionArg libraryArg = graphBuilder
                    .requireMetadata(args.getLibrary().get(), GoLibraryDescriptionArg.class).get();

            BuildRuleParams testTargetParams = params.withDeclaredDeps(
                    () -> ImmutableSortedSet.<BuildRule>naturalOrder().addAll(params.getDeclaredDeps().get())
                            .addAll(graphBuilder.getAllRules(libraryArg.getDeps())).build())
                    .withExtraDeps(() -> {
                        SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(graphBuilder);
                        return ImmutableSortedSet.<BuildRule>naturalOrder().addAll(params.getExtraDeps().get())
                                // Make sure to include dynamically generated sources as deps.
                                .addAll(ruleFinder.filterBuildRuleInputs(libraryArg.getSrcs())).build();
                    });
            testLibrary = GoDescriptors.createGoCompileRule(buildTarget, projectFilesystem, testTargetParams,
                    graphBuilder, goBuckConfig, packageName,
                    ImmutableSet.<SourcePath>builder().addAll(srcs).build(),
                    ImmutableList.<String>builder().addAll(libraryArg.getCompilerFlags())
                            .addAll(args.getCompilerFlags()).build(),
                    ImmutableList.<String>builder().addAll(libraryArg.getAssemblerFlags())
                            .addAll(args.getAssemblerFlags()).build(),
                    platform,
                    testTargetParams.getDeclaredDeps().get().stream().map(BuildRule::getBuildTarget)
                            .collect(ImmutableList.toImmutableList()),
                    ImmutableList.of(), Arrays.asList(ListType.GoFiles, ListType.TestGoFiles));
        } else {
            testLibrary = GoDescriptors.createGoCompileRule(buildTarget, projectFilesystem, params, graphBuilder,
                    goBuckConfig, packageName, srcs, args.getCompilerFlags(), args.getAssemblerFlags(), platform,
                    params.getDeclaredDeps().get().stream().map(BuildRule::getBuildTarget)
                            .collect(ImmutableList.toImmutableList()),
                    ImmutableList.of(),
                    Arrays.asList(ListType.GoFiles, ListType.TestGoFiles, ListType.XTestGoFiles));
        }

        return testLibrary;
    }

    @Override
    public void findDepsForTargetFromConstructorArgs(BuildTarget buildTarget, CellPathResolver cellRoots,
            AbstractGoTestDescriptionArg constructorArg, ImmutableCollection.Builder<BuildTarget> extraDepsBuilder,
            ImmutableCollection.Builder<BuildTarget> targetGraphOnlyDepsBuilder) {
        // Add the C/C++ platform parse time deps.
        GoPlatform platform = GoDescriptors.getPlatformForRule(getGoToolchain(), this.goBuckConfig, buildTarget,
                constructorArg);
        targetGraphOnlyDepsBuilder.addAll(CxxPlatforms.getParseTimeDeps(platform.getCxxPlatform()));
    }

    private GoToolchain getGoToolchain() {
        return toolchainProvider.getByName(GoToolchain.DEFAULT_NAME, GoToolchain.class);
    }

    @BuckStyleImmutable
    @Value.Immutable
    interface AbstractGoTestDescriptionArg
            extends CommonDescriptionArg, HasContacts, HasDeclaredDeps, HasSrcs, HasTestTimeout, HasGoLinkable {
        Optional<BuildTarget> getLibrary();

        Optional<String> getPackageName();

        Optional<GoTestCoverStep.Mode> getCoverageMode();

        ImmutableMap<String, StringWithMacros> getEnv();

        @Value.Default
        default boolean getRunTestSeparately() {
            return false;
        }
    }
}