/* * 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.jvm.java; import com.facebook.buck.io.MorePaths; import com.facebook.buck.io.ProjectFilesystem; import com.facebook.buck.jvm.core.JavaPackageFinder; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.model.BuildTargets; import com.facebook.buck.model.HasOutputName; import com.facebook.buck.rules.BuildRule; import com.facebook.buck.rules.BuildTargetSourcePath; import com.facebook.buck.rules.SourcePath; import com.facebook.buck.rules.SourcePathResolver; import com.facebook.buck.rules.SourcePathRuleFinder; import com.facebook.buck.step.ExecutionContext; import com.facebook.buck.step.Step; import com.facebook.buck.step.StepExecutionResult; import com.facebook.buck.step.fs.MkdirStep; import com.facebook.buck.step.fs.SymlinkFileStep; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import java.io.IOException; import java.nio.file.Path; import java.util.Collection; import java.util.Optional; public class CopyResourcesStep implements Step { private final ProjectFilesystem filesystem; private final SourcePathResolver resolver; private final SourcePathRuleFinder ruleFinder; private final BuildTarget target; private final Collection<? extends SourcePath> resources; private final Path outputDirectory; private final JavaPackageFinder javaPackageFinder; public CopyResourcesStep( ProjectFilesystem filesystem, SourcePathResolver resolver, SourcePathRuleFinder ruleFinder, BuildTarget target, Collection<? extends SourcePath> resources, Path outputDirectory, JavaPackageFinder javaPackageFinder) { this.filesystem = filesystem; this.resolver = resolver; this.ruleFinder = ruleFinder; this.target = target; this.resources = resources; this.outputDirectory = outputDirectory; this.javaPackageFinder = javaPackageFinder; } @Override public StepExecutionResult execute(ExecutionContext context) throws IOException, InterruptedException { for (Step step : buildSteps()) { StepExecutionResult result = step.execute(context); if (!result.isSuccess()) { return result; } } return StepExecutionResult.SUCCESS; } @VisibleForTesting ImmutableList<Step> buildSteps() { ImmutableList.Builder<Step> allSteps = ImmutableList.builder(); if (resources.isEmpty()) { return allSteps.build(); } String targetPackageDir = javaPackageFinder.findJavaPackage(target); for (SourcePath rawResource : resources) { // If the path to the file defining this rule were: // "first-party/orca/lib-http/tests/com/facebook/orca/BUCK" // // And the value of resource were: // "first-party/orca/lib-http/tests/com/facebook/orca/protocol/base/batch_exception1.txt" // // Assuming that `src_roots = tests` were in the [java] section of the .buckconfig file, // then javaPackageAsPath would be: // "com/facebook/orca/protocol/base/" // // And the path that we would want to copy to the classes directory would be: // "com/facebook/orca/protocol/base/batch_exception1.txt" // // Therefore, some path-wrangling is required to produce the correct string. Optional<BuildRule> underlyingRule = ruleFinder.getRule(rawResource); Path relativePathToResource = resolver.getRelativePath(rawResource); String resource; if (underlyingRule.isPresent()) { BuildTarget underlyingTarget = underlyingRule.get().getBuildTarget(); if (underlyingRule.get() instanceof HasOutputName) { resource = MorePaths.pathWithUnixSeparators( underlyingTarget .getBasePath() .resolve(((HasOutputName) underlyingRule.get()).getOutputName())); } else { Path genOutputParent = BuildTargets.getGenPath(filesystem, underlyingTarget, "%s").getParent(); Path scratchOutputParent = BuildTargets.getScratchPath(filesystem, underlyingTarget, "%s").getParent(); Optional<Path> outputPath = MorePaths.stripPrefix(relativePathToResource, genOutputParent) .map(Optional::of) .orElse(MorePaths.stripPrefix(relativePathToResource, scratchOutputParent)); Preconditions.checkState( outputPath.isPresent(), "%s is used as a resource but does not output to a default output directory", underlyingTarget.getFullyQualifiedName()); resource = MorePaths.pathWithUnixSeparators( underlyingTarget.getBasePath().resolve(outputPath.get())); } } else { resource = MorePaths.pathWithUnixSeparators(relativePathToResource); } Path javaPackageAsPath = javaPackageFinder.findJavaPackageFolder( outputDirectory.getFileSystem().getPath(resource)); Path relativeSymlinkPath; if ("".equals(javaPackageAsPath.toString())) { // In this case, the project root is acting as the default package, so the resource path // works fine. relativeSymlinkPath = relativePathToResource.getFileName(); } else { int lastIndex = resource.lastIndexOf( MorePaths.pathWithUnixSeparatorsAndTrailingSlash(javaPackageAsPath)); if (lastIndex < 0) { Preconditions.checkState( rawResource instanceof BuildTargetSourcePath, "If resource path %s does not contain %s, then it must be a BuildTargetSourcePath.", relativePathToResource, javaPackageAsPath); // Handle the case where we depend on the output of another BuildRule. In that case, just // grab the output and put in the same package as this target would be in. relativeSymlinkPath = outputDirectory .getFileSystem() .getPath( String.format( "%s%s%s", targetPackageDir, targetPackageDir.isEmpty() ? "" : "/", resolver.getRelativePath(rawResource).getFileName())); } else { relativeSymlinkPath = outputDirectory.getFileSystem().getPath(resource.substring(lastIndex)); } } Path target = outputDirectory.resolve(relativeSymlinkPath); allSteps.add(MkdirStep.of(filesystem, target.getParent())); allSteps.add( SymlinkFileStep.builder() .setFilesystem(filesystem) .setExistingFile(resolver.getAbsolutePath(rawResource)) .setDesiredLink(target) .build()); } return allSteps.build(); } @Override public String getShortName() { return "copy_resources"; } @Override public String getDescription(ExecutionContext context) { return String.format("%s of %s", getShortName(), target); } }