// Copyright 2014 The Bazel Authors. All rights reserved.
//
// 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.google.devtools.build.lib.analysis;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
import com.google.devtools.build.lib.analysis.SourceManifestAction.ManifestType;
import com.google.devtools.build.lib.analysis.actions.ActionConstructionContext;
import com.google.devtools.build.lib.analysis.actions.CommandLine;
import com.google.devtools.build.lib.analysis.actions.SymlinkTreeAction;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
import com.google.devtools.build.lib.analysis.config.RunUnder;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.packages.TargetUtils;
import com.google.devtools.build.lib.util.Preconditions;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * This class manages the creation of the runfiles symlink farms.
 *
 * <p>For executables that might depend on the existence of files at run-time, we create a symlink
 * farm: a directory which contains symlinks to the right locations for those runfiles.
 *
 * <p>The runfiles symlink farm serves two purposes. The first is to allow programs (and
 * programmers) to refer to files using their workspace-relative paths, regardless of whether the
 * files were source files or generated files, and regardless of which part of the package path they
 * came from. The second purpose is to ensure that all run-time dependencies are explicitly declared
 * in the BUILD files; programs may only use files which the build system knows that they depend on.
 *
 * <p>The symlink farm contains a MANIFEST file which describes its contents. The MANIFEST file
 * lists the names and contents of all of the symlinks in the symlink farm. For efficiency, Blaze's
 * dependency analysis ignores the actual symlinks and just looks at the MANIFEST file. It is an
 * invariant that the MANIFEST file should accurately represent the contents of the symlinks
 * whenever the MANIFEST file is present. build_runfile_links.py preserves this invariant (modulo
 * bugs - currently it has a bug where it may fail to preserve that invariant if it gets
 * interrupted). So the Blaze dependency analysis looks only at the MANIFEST file, rather than at
 * the individual symlinks.
 *
 * <p>We create an Artifact for the MANIFEST file and a RunfilesAction Action to create it. This
 * action does not depend on any other Artifacts.
 *
 * <p>When building an executable and running it, there are three things which must be built: the
 * executable itself, the runfiles symlink farm (represented in the action graph by the Artifact for
 * its MANIFEST), and the files pointed to by the symlinks in the symlink farm. To avoid redundancy
 * in the dependency analysis, we create a Middleman Artifact which depends on all of these. Actions
 * which will run an executable should depend on this Middleman Artifact.
 */
@Immutable
public final class RunfilesSupport {
  private static final String RUNFILES_DIR_EXT = ".runfiles";

  private final Runfiles runfiles;

  private final Artifact runfilesInputManifest;
  private final Artifact runfilesManifest;
  private final Artifact runfilesMiddleman;
  private final Artifact sourcesManifest;
  private final Artifact owningExecutable;
  private final boolean createSymlinks;
  private final CommandLine args;

  /**
   * Creates the RunfilesSupport helper with the given executable and runfiles.
   *
   * @param ruleContext the rule context to create the runfiles support for
   * @param executable the executable for whose runfiles this runfiles support is responsible, may
   *     be null
   * @param runfiles the runfiles
   */
  private RunfilesSupport(
      RuleContext ruleContext,
      Artifact executable,
      Runfiles runfiles,
      CommandLine args) {
    owningExecutable = Preconditions.checkNotNull(executable);
    boolean createManifest = ruleContext.getConfiguration().buildRunfilesManifests();
    createSymlinks = createManifest && ruleContext.getConfiguration().buildRunfiles();

    // Adding run_under target to the runfiles manifest so it would become part
    // of runfiles tree and would be executable everywhere.
    RunUnder runUnder = ruleContext.getConfiguration().getRunUnder();
    if (runUnder != null && runUnder.getLabel() != null
        && TargetUtils.isTestRule(ruleContext.getRule())) {
      TransitiveInfoCollection runUnderTarget =
          ruleContext.getPrerequisite(":run_under", Mode.DATA);
      runfiles = new Runfiles.Builder(
          ruleContext.getWorkspaceName(), ruleContext.getConfiguration().legacyExternalRunfiles())
          .merge(getRunfiles(runUnderTarget, ruleContext.getWorkspaceName()))
          .merge(runfiles)
          .build();
    }
    this.runfiles = runfiles;

    Preconditions.checkState(!runfiles.isEmpty());

    Map<PathFragment, Artifact> symlinks = getRunfilesSymlinks();
    if (executable != null && !symlinks.containsValue(executable)) {
      throw new IllegalStateException("main program " + executable + " not included in runfiles");
    }

    Artifact artifactsMiddleman = createArtifactsMiddleman(ruleContext, runfiles.getAllArtifacts());
    if (createManifest) {
      runfilesInputManifest = createRunfilesInputManifestArtifact(ruleContext);
      runfilesManifest = createRunfilesAction(ruleContext, runfiles, artifactsMiddleman);
    } else {
      runfilesInputManifest = runfilesManifest =
          createManifestMiddleman(ruleContext, runfiles, artifactsMiddleman);
    }
    runfilesMiddleman = createRunfilesMiddleman(ruleContext, artifactsMiddleman, runfilesManifest);
    sourcesManifest = createSourceManifest(ruleContext, runfiles);

    this.args = args;
  }

  /**
   * Returns the executable owning this RunfilesSupport. Only use from Skylark.
   */
  public Artifact getExecutable() {
    return owningExecutable;
  }

  /**
   * Returns the exec path of the directory where the runfiles contained in this
   * RunfilesSupport are generated. When the owning rule has no executable,
   * returns null.
   */
  public PathFragment getRunfilesDirectoryExecPath() {
    PathFragment executablePath = owningExecutable.getExecPath();
    return executablePath.getParentDirectory().getChild(
        executablePath.getBaseName() + RUNFILES_DIR_EXT);
  }

  /** @return whether or not runfiles symlinks should be created */
  public boolean getCreateSymlinks() {
    return createSymlinks;
  }

  public Runfiles getRunfiles() {
    return runfiles;
  }

  /**
   * For executable programs, the .runfiles_manifest file outside of the
   * runfiles symlink farm; otherwise, returns null.
   *
   * <p>The MANIFEST file represents the contents of all of the symlinks in the
   * symlink farm. For efficiency, Blaze's dependency analysis ignores the
   * actual symlinks and just looks at the MANIFEST file. It is an invariant
   * that the MANIFEST file should accurately represent the contents of the
   * symlinks whenever the MANIFEST file is present.
   */
  public Artifact getRunfilesInputManifest() {
    return runfilesInputManifest;
  }

  private Artifact createRunfilesInputManifestArtifact(RuleContext context) {
    // The executable may be null for emptyRunfiles
    PathFragment relativePath = (owningExecutable != null)
        ? owningExecutable.getRootRelativePath()
        : context.getPackageDirectory().getRelative(context.getLabel().getName());
    String basename = relativePath.getBaseName();
    PathFragment inputManifestPath = relativePath.replaceName(basename + ".runfiles_manifest");
    return context.getDerivedArtifact(inputManifestPath,
        context.getConfiguration().getBinDirectory(context.getRule().getRepository()));
  }

  /**
   * For executable programs, returns the MANIFEST file in the runfiles
   * symlink farm, if blaze is run with --build_runfile_links; returns
   * the .runfiles_manifest file outside of the symlink farm, if blaze
   * is run with --nobuild_runfile_links.
   * <p>
   * Beware: In most cases {@link #getRunfilesInputManifest} is the more
   * appropriate function.
   */
  public Artifact getRunfilesManifest() {
    return runfilesManifest;
  }

  /**
   * For executable programs, the root directory of the runfiles symlink farm;
   * otherwise, returns null.
   */
  public Path getRunfilesDirectory() {
    return FileSystemUtils.replaceExtension(getRunfilesInputManifest().getPath(), RUNFILES_DIR_EXT);
  }

  /**
   * Returns the files pointed to by the symlinks in the runfiles symlink farm. This method is slow.
   */
  @VisibleForTesting
  public Collection<Artifact> getRunfilesSymlinkTargets() {
    return getRunfilesSymlinks().values();
  }

  /**
   * Returns the names of the symlinks in the runfiles symlink farm as a Set of PathFragments. This
   * method is slow.
   */
  // We should make this VisibleForTesting, but it is still used by TestHelper
  public Set<PathFragment> getRunfilesSymlinkNames() {
    return getRunfilesSymlinks().keySet();
  }

  /**
   * Returns the names of the symlinks in the runfiles symlink farm as a Set of PathFragments. This
   * method is slow.
   */
  @VisibleForTesting
  public Map<PathFragment, Artifact> getRunfilesSymlinks() {
    return runfiles.asMapWithoutRootSymlinks();
  }

  /**
   * Returns both runfiles artifacts and "conditional" artifacts that may be part of a
   * Runfiles PruningManifest. This means the returned set may be an overapproximation of the
   * actual set of runfiles (see {@link Runfiles.PruningManifest}).
   */
  public Iterable<Artifact> getRunfilesArtifactsWithoutMiddlemen() {
    return runfiles.getArtifactsWithoutMiddlemen();
  }

  /**
   * Returns the name of the workspace that the build is occurring in.
   */
  public PathFragment getWorkspaceName() {
    return runfiles.getSuffix();
  }

  /**
   * Returns the middleman artifact that depends on getExecutable(),
   * getRunfilesManifest(), and getRunfilesSymlinkTargets(). Anything which
   * needs to actually run the executable should depend on this.
   */
  public Artifact getRunfilesMiddleman() {
    return runfilesMiddleman;
  }

  /**
   * Returns the Sources manifest.
   */
  public Artifact getSourceManifest() {
    return sourcesManifest;
  }

  private Artifact createArtifactsMiddleman(ActionConstructionContext context,
      Iterable<Artifact> allRunfilesArtifacts) {
    return context.getAnalysisEnvironment().getMiddlemanFactory().createRunfilesMiddleman(
        context.getActionOwner(), owningExecutable, allRunfilesArtifacts,
        context.getConfiguration().getMiddlemanDirectory(context.getRule().getRepository()),
        "runfiles_artifacts");
  }

  private Artifact createRunfilesMiddleman(ActionConstructionContext context,
      Artifact artifactsMiddleman, Artifact outputManifest) {
    return context.getAnalysisEnvironment().getMiddlemanFactory().createRunfilesMiddleman(
        context.getActionOwner(), owningExecutable,
        ImmutableList.of(artifactsMiddleman, outputManifest),
        context.getConfiguration().getMiddlemanDirectory(context.getRule().getRepository()),
        "runfiles");
  }

  /**
   * Creates a runfiles action for all of the specified files, and returns the
   * output artifact (the artifact for the MANIFEST file).
   *
   * <p>The "runfiles" action creates a symlink farm that links all the runfiles
   * (which may come from different places, e.g. different package paths,
   * generated files, etc.) into a single tree, so that programs can access them
   * using the workspace-relative name.
   */
  private Artifact createRunfilesAction(ActionConstructionContext context, Runfiles runfiles,
      Artifact artifactsMiddleman) {
    // Compute the names of the runfiles directory and its MANIFEST file.
    Artifact inputManifest = getRunfilesInputManifest();
    context.getAnalysisEnvironment().registerAction(
        SourceManifestAction.forRunfiles(
            ManifestType.SOURCE_SYMLINKS, context.getActionOwner(), inputManifest, runfiles));

    if (!createSymlinks) {
      // Just return the manifest if that's all the build calls for.
      return inputManifest;
    }

    PathFragment runfilesDir = FileSystemUtils.replaceExtension(inputManifest.getRootRelativePath(),
        RUNFILES_DIR_EXT);
    PathFragment outputManifestPath = runfilesDir.getRelative("MANIFEST");

    BuildConfiguration config = context.getConfiguration();
    Artifact outputManifest = context.getDerivedArtifact(
        outputManifestPath, config.getBinDirectory(context.getRule().getRepository()));
    context
        .getAnalysisEnvironment()
        .registerAction(
            new SymlinkTreeAction(
                context.getActionOwner(),
                inputManifest,
                artifactsMiddleman,
                outputManifest,
                /*filesetTree=*/ false,
                config.getLocalShellEnvironment(),
                config.runfilesEnabled()));
    return outputManifest;
  }

  /**
   * Creates a middleman artifact which substitutes for the input and
   * output manifests when manifest files are disabled.
   */
  private Artifact createManifestMiddleman(ActionConstructionContext context, Runfiles runfiles,
      Artifact artifactsMiddleman) {
    // Deps which are otherwise omitted when we don't build the manifest.
    // These may include "pruning manifests" which are needed at execution time.
    Collection<Artifact> manifestDeps = SourceManifestAction.getDependencies(runfiles);
    if (manifestDeps.isEmpty()) {
      // Can't create a runfiles middleman from zero artifacts.
      return artifactsMiddleman;
    }
    return context.getAnalysisEnvironment().getMiddlemanFactory().createRunfilesMiddleman(
        context.getActionOwner(), owningExecutable, SourceManifestAction.getDependencies(runfiles),
        context.getConfiguration().getMiddlemanDirectory(context.getRule().getRepository()),
        "runfiles_manifest");
  }

  /**
   * Creates an {@link Artifact} which writes the "sources only" manifest file.
   *
   * @param context the owner for the manifest action
   * @param runfiles the runfiles
   * @return the Artifact representing the file write action.
   */
  private Artifact createSourceManifest(ActionConstructionContext context, Runfiles runfiles) {
    // Put the sources only manifest next to the MANIFEST file but call it SOURCES.
    PathFragment executablePath = owningExecutable.getRootRelativePath();
    PathFragment sourcesManifestPath = executablePath.getParentDirectory().getChild(
        executablePath.getBaseName() + ".runfiles.SOURCES");
    Artifact sourceOnlyManifest = context.getDerivedArtifact(
        sourcesManifestPath,
        context.getConfiguration().getBinDirectory(context.getRule().getRepository()));
    context.getAnalysisEnvironment().registerAction(SourceManifestAction.forRunfiles(
        ManifestType.SOURCES_ONLY, context.getActionOwner(), sourceOnlyManifest, runfiles));
    return sourceOnlyManifest;
  }

  /**
   * Helper method that returns a collection of artifacts that are necessary for the runfiles of the
   * given target. Note that the runfile symlink tree is never built, so this may include artifacts
   * that end up not being used (see {@link Runfiles}).
   *
   * @return the Runfiles object
   */
  private static Runfiles getRunfiles(TransitiveInfoCollection target, String workspaceName) {
    RunfilesProvider runfilesProvider = target.getProvider(RunfilesProvider.class);
    if (runfilesProvider != null) {
      return runfilesProvider.getDefaultRunfiles();
    } else {
      return new Runfiles.Builder(workspaceName)
          .addTransitiveArtifacts(target.getProvider(FilesToRunProvider.class).getFilesToRun())
          .build();
    }
  }

  /** Returns the unmodifiable list of expanded and tokenized 'args' attribute values. */
  public CommandLine getArgs() {
    return args;
  }

  /**
   * Creates and returns a {@link RunfilesSupport} object for the given rule and executable. Note
   * that this method calls back into the passed in rule to obtain the runfiles.
   */
  public static RunfilesSupport withExecutable(
      RuleContext ruleContext, Runfiles runfiles, Artifact executable) {
    return new RunfilesSupport(
        ruleContext,
        executable,
        runfiles,
        computeArgs(ruleContext, CommandLine.EMPTY, ImmutableList.<MakeVariableSupplier>of()));
  }

  /**
   * Creates and returns a {@link RunfilesSupport} object for the given rule and executable. Note
   * that this method calls back into the passed in rule to obtain the runfiles.
   */
  public static RunfilesSupport withExecutable(
      RuleContext ruleContext, Runfiles runfiles, Artifact executable, List<String> appendingArgs) {
    return new RunfilesSupport(
        ruleContext,
        executable,
        runfiles,
        computeArgs(
            ruleContext, CommandLine.of(appendingArgs), ImmutableList.<MakeVariableSupplier>of()));
  }

  /**
   * Creates and returns a {@link RunfilesSupport} object for the given rule, executable, runfiles
   * and args.
   */
  public static RunfilesSupport withExecutable(
      RuleContext ruleContext, Runfiles runfiles, Artifact executable, CommandLine appendingArgs) {
    return new RunfilesSupport(
        ruleContext,
        executable,
        runfiles,
        computeArgs(ruleContext, appendingArgs, ImmutableList.<MakeVariableSupplier>of()));
  }

  public static RunfilesSupport withExecutable(
      RuleContext ruleContext,
      Runfiles runfiles,
      Artifact executable,
      ImmutableList<? extends MakeVariableSupplier> makeVariableSuppliers) {
    return new RunfilesSupport(
        ruleContext,
        executable,
        runfiles,
        computeArgs(ruleContext, CommandLine.EMPTY, makeVariableSuppliers));
  }

  private static CommandLine computeArgs(
      RuleContext ruleContext,
      CommandLine additionalArgs,
      ImmutableList<? extends MakeVariableSupplier> makeVariableSuppliers) {
    return CommandLine.concat(
        ruleContext.getTokenizedStringListAttr("args", makeVariableSuppliers), additionalArgs);
  }
}
