/* * Copyright 2012-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.shell; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.model.HasOutputName; import com.facebook.buck.rules.AbstractBuildRuleWithResolver; import com.facebook.buck.rules.AddToRuleKey; 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.HasRuntimeDeps; import com.facebook.buck.rules.SourcePath; import com.facebook.buck.rules.SourcePathResolver; import com.facebook.buck.rules.SourcePathRuleFinder; import com.facebook.buck.step.Step; import com.facebook.buck.step.fs.CopyStep; import com.facebook.buck.step.fs.MkdirStep; import com.facebook.buck.step.fs.RmStep; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import java.nio.file.Path; import java.util.Optional; import java.util.stream.Stream; /** * Export a file so that it can be easily referenced by other {@link * com.facebook.buck.rules.BuildRule}s. There are several valid ways of using export_file (all * examples in a build file located at "path/to/buck/BUCK"). * * <p>The most common usage of export_file is: * * <pre> * export_file(name = 'some-file.html') * </pre> * * This is equivalent to: * * <pre> * export_file(name = 'some-file.html', * src = 'some-file.html', * out = 'some-file.html') * </pre> * * This results in "//path/to/buck:some-file.html" as the rule, and will export the file * "some-file.html" as "some-file.html". * * <pre> * export_file( * name = 'foobar.html', * src = 'some-file.html', * ) * </pre> * * Is equivalent to: * * <pre> * export_file(name = 'foobar.html', src = 'some-file.html', out = 'foobar.html') * </pre> * * Finally, it's possible to refer to the exported file with a logical name, while controlling the * actual file name. For example: * * <pre> * export_file(name = 'ie-exports', * src = 'some-file.js', * out = 'some-file-ie.js', * ) * </pre> * * As a rule of thumb, if the "out" parameter is missing, the "name" parameter is used as the name * of the file to be saved. */ // TODO(simons): Extend to also allow exporting a rule. public class ExportFile extends AbstractBuildRuleWithResolver implements HasOutputName, HasRuntimeDeps { private final SourcePathRuleFinder ruleFinder; @AddToRuleKey private final String name; @AddToRuleKey private final ExportFileDescription.Mode mode; @AddToRuleKey private final SourcePath src; public ExportFile( BuildRuleParams buildRuleParams, SourcePathRuleFinder ruleFinder, SourcePathResolver resolver, String name, ExportFileDescription.Mode mode, SourcePath src) { super(buildRuleParams, resolver); this.ruleFinder = ruleFinder; this.name = name; this.mode = mode; this.src = src; } @VisibleForTesting SourcePath getSource() { return src; } private Path getCopiedPath() { Preconditions.checkState(mode == ExportFileDescription.Mode.COPY); return getProjectFilesystem() .getBuckPaths() .getGenDir() .resolve(getBuildTarget().getBasePath()) .resolve(name); } @Override public ImmutableList<Step> getBuildSteps( BuildContext context, BuildableContext buildableContext) { SourcePathResolver resolver = context.getSourcePathResolver(); // This file is copied rather than symlinked so that when it is included in an archive zip and // unpacked on another machine, it is an ordinary file in both scenarios. ImmutableList.Builder<Step> builder = ImmutableList.builder(); if (mode == ExportFileDescription.Mode.COPY) { Path out = getCopiedPath(); builder.add(MkdirStep.of(getProjectFilesystem(), out.getParent())); builder.add(RmStep.of(getProjectFilesystem(), out).withRecursive(true)); if (resolver.getFilesystem(src).isDirectory(resolver.getRelativePath(src))) { builder.add( CopyStep.forDirectory( getProjectFilesystem(), resolver.getAbsolutePath(src), out, CopyStep.DirectoryMode.CONTENTS_ONLY)); } else { builder.add(CopyStep.forFile(getProjectFilesystem(), resolver.getAbsolutePath(src), out)); } buildableContext.recordArtifact(out); } return builder.build(); } @Override public SourcePath getSourcePathToOutput() { // In reference mode, we just return the relative path to the source, as we've already verified // that our filesystem matches that of the source. In copy mode, we return the path we've // allocated for the copy. return mode == ExportFileDescription.Mode.REFERENCE ? src : new ExplicitBuildTargetSourcePath(getBuildTarget(), getCopiedPath()); } @Override public String getOutputName() { return name; } @Override public Stream<BuildTarget> getRuntimeDeps() { // When using reference mode, we need to make sure that any build rule that builds the source // is built when we are, so accomplish this by exporting it as a runtime dep. Optional<BuildRule> rule = ruleFinder.getRule(src); return mode == ExportFileDescription.Mode.REFERENCE && rule.isPresent() ? Stream.of(rule.get().getBuildTarget()) : Stream.empty(); } }