/* * 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.jvm.java; import com.facebook.buck.rules.AbstractBuildRule; import com.facebook.buck.rules.BuildContext; import com.facebook.buck.rules.BuildRule; import com.facebook.buck.rules.BuildRuleParams; import com.facebook.buck.rules.BuildableContext; import com.facebook.buck.rules.ExplicitBuildTargetSourcePath; import com.facebook.buck.rules.SourcePath; import com.facebook.buck.rules.SourcePathResolver; import com.facebook.buck.step.Step; import com.facebook.buck.step.fs.MkdirStep; import com.facebook.buck.util.MoreCollectors; import com.facebook.buck.util.RichStream; import com.google.common.base.Function; import com.google.common.base.Preconditions; import com.google.common.base.Suppliers; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Ordering; import com.google.common.collect.Sets; import java.nio.file.Path; import java.util.Objects; import java.util.Optional; /** * A {@link BuildRule} used to have the provided {@link JavaLibrary} published to a maven repository * * @see #create */ public class MavenUberJar extends AbstractBuildRule implements MavenPublishable { private final Optional<String> mavenCoords; private final Optional<SourcePath> mavenPomTemplate; private final TraversedDeps traversedDeps; private MavenUberJar( TraversedDeps traversedDeps, BuildRuleParams params, Optional<String> mavenCoords, Optional<SourcePath> mavenPomTemplate) { super(params); this.traversedDeps = traversedDeps; this.mavenCoords = mavenCoords; this.mavenPomTemplate = mavenPomTemplate; } private static BuildRuleParams adjustParams(BuildRuleParams params, TraversedDeps traversedDeps) { return params.copyReplacingDeclaredAndExtraDeps( Suppliers.ofInstance( ImmutableSortedSet.copyOf(Ordering.natural(), traversedDeps.packagedDeps)), Suppliers.ofInstance(ImmutableSortedSet.of())); } /** * Will traverse transitive dependencies of {@code rootRule}, separating those that do and don't * have maven coordinates. Those that do will be considered maven-external dependencies. They will * be returned by {@link #getMavenDeps} and will end up being specified as dependencies in * pom.xml. Others will be packaged in the same jar as if they are just a part of the one * published item. */ public static MavenUberJar create( JavaLibrary rootRule, BuildRuleParams params, Optional<String> mavenCoords, Optional<SourcePath> mavenPomTemplate) { TraversedDeps traversedDeps = TraversedDeps.traverse(ImmutableSet.of(rootRule)); return new MavenUberJar( traversedDeps, adjustParams(params, traversedDeps), mavenCoords, mavenPomTemplate); } @Override public ImmutableList<Step> getBuildSteps( BuildContext context, BuildableContext buildableContext) { Path pathToOutput = context.getSourcePathResolver().getRelativePath(getSourcePathToOutput()); MkdirStep mkOutputDirStep = MkdirStep.of(getProjectFilesystem(), pathToOutput.getParent()); JarDirectoryStep mergeOutputsStep = new JarDirectoryStep( getProjectFilesystem(), pathToOutput, toOutputPaths(context.getSourcePathResolver(), traversedDeps.packagedDeps), /* mainClass */ null, /* manifestFile */ null); return ImmutableList.of(mkOutputDirStep, mergeOutputsStep); } private static ImmutableSortedSet<Path> toOutputPaths( SourcePathResolver pathResolver, Iterable<? extends BuildRule> rules) { return RichStream.from(rules) .map(BuildRule::getSourcePathToOutput) .filter(Objects::nonNull) .map(pathResolver::getAbsolutePath) .collect(MoreCollectors.toImmutableSortedSet()); } @Override public SourcePath getSourcePathToOutput() { return new ExplicitBuildTargetSourcePath( getBuildTarget(), DefaultJavaLibrary.getOutputJarPath(getBuildTarget(), getProjectFilesystem())); } @Override public Optional<String> getMavenCoords() { return mavenCoords; } @Override public Optional<SourcePath> getPomTemplate() { return mavenPomTemplate; } @Override public Iterable<HasMavenCoordinates> getMavenDeps() { return traversedDeps.mavenDeps; } @Override public Iterable<BuildRule> getPackagedDependencies() { return traversedDeps.packagedDeps; } public static class SourceJar extends JavaSourceJar implements MavenPublishable { private final TraversedDeps traversedDeps; private final Optional<SourcePath> mavenPomTemplate; public SourceJar( BuildRuleParams params, ImmutableSortedSet<SourcePath> srcs, Optional<String> mavenCoords, Optional<SourcePath> mavenPomTemplate, TraversedDeps traversedDeps) { super(params, srcs, mavenCoords); this.traversedDeps = traversedDeps; this.mavenPomTemplate = mavenPomTemplate; } public static SourceJar create( BuildRuleParams params, ImmutableSortedSet<SourcePath> topLevelSrcs, Optional<String> mavenCoords, Optional<SourcePath> mavenPomTemplate) { TraversedDeps traversedDeps = TraversedDeps.traverse(params.getBuildDeps()); params = adjustParams(params, traversedDeps); ImmutableSortedSet<SourcePath> sourcePaths = FluentIterable.from(traversedDeps.packagedDeps) .filter(HasSources.class) .transformAndConcat( new Function<HasSources, Iterable<SourcePath>>() { @Override public Iterable<SourcePath> apply(HasSources input) { return input.getSources(); } }) .append(topLevelSrcs) .toSortedSet(Ordering.natural()); return new SourceJar(params, sourcePaths, mavenCoords, mavenPomTemplate, traversedDeps); } @Override public Optional<SourcePath> getPomTemplate() { return mavenPomTemplate; } @Override public Iterable<HasMavenCoordinates> getMavenDeps() { return traversedDeps.mavenDeps; } @Override public Iterable<BuildRule> getPackagedDependencies() { return traversedDeps.packagedDeps; } } private static class TraversedDeps { public final Iterable<HasMavenCoordinates> mavenDeps; public final Iterable<BuildRule> packagedDeps; private TraversedDeps( Iterable<HasMavenCoordinates> mavenDeps, Iterable<BuildRule> packagedDeps) { this.mavenDeps = mavenDeps; this.packagedDeps = packagedDeps; } private static TraversedDeps traverse(ImmutableSet<? extends BuildRule> roots) { ImmutableSortedSet.Builder<HasMavenCoordinates> depsCollector = ImmutableSortedSet.naturalOrder(); ImmutableSortedSet.Builder<JavaLibrary> candidates = ImmutableSortedSet.naturalOrder(); for (final BuildRule root : roots) { Preconditions.checkState(root instanceof HasClasspathEntries); candidates.addAll( ((HasClasspathEntries) root) .getTransitiveClasspathDeps() .stream() .filter(buildRule -> !root.equals(buildRule)) .iterator()); } ImmutableSortedSet.Builder<JavaLibrary> removals = ImmutableSortedSet.naturalOrder(); for (JavaLibrary javaLibrary : candidates.build()) { if (HasMavenCoordinates.isMavenCoordsPresent(javaLibrary)) { depsCollector.add(javaLibrary); removals.addAll(javaLibrary.getTransitiveClasspathDeps()); } } return new TraversedDeps( /* mavenDeps */ depsCollector.build(), /* packagedDeps */ Sets.union( roots, Sets.difference(candidates.build(), removals.build()))); } } }