// 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.rules.cpp;

import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.actions.AbstractAction;
import com.google.devtools.build.lib.actions.ActionAnalysisMetadata;
import com.google.devtools.build.lib.actions.ActionExecutionContext;
import com.google.devtools.build.lib.actions.ActionExecutionException;
import com.google.devtools.build.lib.actions.ActionOwner;
import com.google.devtools.build.lib.actions.Actions;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.Executor;
import com.google.devtools.build.lib.actions.ResourceSet;
import com.google.devtools.build.lib.actions.Root;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.util.Fingerprint;
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.io.IOException;

/**
 * Creates mangled symlinks in the solib directory for all shared libraries.
 * Libraries that have a potential to contain SONAME field rely on the mangled
 * symlink to the parent directory instead.
 *
 * Such symlinks are used by the linker to ensure that all rpath entries can be
 * specified relative to the $ORIGIN.
 */
@Immutable
public final class SolibSymlinkAction extends AbstractAction {
  private final Artifact library;
  private final Path target;
  private final Artifact symlink;

  private SolibSymlinkAction(ActionOwner owner, Artifact library, Artifact symlink) {
    super(owner, ImmutableList.of(library), ImmutableList.of(symlink));

    Preconditions.checkArgument(Link.SHARED_LIBRARY_FILETYPES.matches(library.getFilename()));
    this.library = Preconditions.checkNotNull(library);
    this.symlink = Preconditions.checkNotNull(symlink);
    this.target = library.getPath();
  }

  @Override
  protected void deleteOutputs(Path execRoot) throws IOException {
    // Do not delete outputs if action does not intend to do anything.
    if (target != null) {
      super.deleteOutputs(execRoot);
    }
  }

  @Override
  public void execute(
      ActionExecutionContext actionExecutionContext) throws ActionExecutionException {
    Path mangledPath = symlink.getPath();
    try {
      FileSystemUtils.createDirectoryAndParents(mangledPath.getParentDirectory());
      mangledPath.createSymbolicLink(target);
    } catch (IOException e) {
      throw new ActionExecutionException("failed to create _solib symbolic link '"
          + symlink.prettyPrint() + "' to target '" + target + "'", e, this, false);
    }
  }

  @Override
  public Artifact getPrimaryInput() {
    return library;
  }

  @Override
  public Artifact getPrimaryOutput() {
    return symlink;
  }

  @Override
  public ResourceSet estimateResourceConsumption(Executor executor) {
    return ResourceSet.ZERO;
  }

  @Override
  protected String computeKey() {
    Fingerprint f = new Fingerprint();
    f.addPath(symlink.getPath());
    if (target != null) {
      f.addPath(target);
    }
    return f.hexDigestAndReset();
  }

  @Override
  public String getMnemonic() { return "SolibSymlink"; }

  @Override
  protected String getRawProgressMessage() { return null; }

  /**
   * Replaces shared library artifact with mangled symlink and creates related
   * symlink action. For artifacts that should retain filename (e.g. libraries
   * with SONAME tag), link is created to the parent directory instead.
   *
   * This action is performed to minimize number of -rpath entries used during
   * linking process (by essentially "collecting" as many shared libraries as
   * possible in the single directory), since we will be paying quadratic price
   * for each additional entry on the -rpath.
   *
   * @param ruleContext rule context, that requested symlink.
   * @param library Shared library artifact that needs to be mangled.
   * @param preserveName whether to preserve the name of the library
   * @param prefixConsumer whether to prefix the output artifact name with the label of the
   *     consumer
   * @return mangled symlink artifact.
   */
  public static Artifact getDynamicLibrarySymlink(final RuleContext ruleContext,
                                                       final Artifact library,
                                                       boolean preserveName,
                                                       boolean prefixConsumer,
                                                       BuildConfiguration configuration) {
    PathFragment mangledName = getMangledName(
        ruleContext, library.getRootRelativePath(), preserveName, prefixConsumer,
        configuration.getFragment(CppConfiguration.class));
    return getDynamicLibrarySymlinkInternal(
        ruleContext, library, mangledName, configuration);
  }

   /**
   * Version of {@link #getDynamicLibrarySymlink} for the special case of C++ runtime libraries.
   * These are handled differently than other libraries: neither their names nor directories are
   * mangled, i.e. libstdc++.so.6 is symlinked from _solib_[arch]/libstdc++.so.6
   */
  public static Artifact getCppRuntimeSymlink(RuleContext ruleContext, Artifact library,
      String solibDirOverride, BuildConfiguration configuration) {
    PathFragment solibDir = new PathFragment(solibDirOverride != null
        ? solibDirOverride
        : configuration.getFragment(CppConfiguration.class).getSolibDirectory());
    PathFragment symlinkName = solibDir.getRelative(library.getRootRelativePath().getBaseName());
    return getDynamicLibrarySymlinkInternal(ruleContext, library, symlinkName, configuration);
  }

  /**
   * Internal implementation that takes a pre-determined symlink name; supports both the
   * generic {@link #getDynamicLibrarySymlink} and the specialized {@link #getCppRuntimeSymlink}.
   */
  private static Artifact getDynamicLibrarySymlinkInternal(RuleContext ruleContext,
      Artifact library, PathFragment symlinkName, BuildConfiguration configuration) {
    Preconditions.checkArgument(Link.SHARED_LIBRARY_FILETYPES.matches(library.getFilename()));
    Preconditions.checkArgument(!library.getRootRelativePath().getSegment(0).startsWith("_solib_"));

    // Ignore libraries that are already represented by the symlinks.
    Root root = configuration.getBinDirectory(ruleContext.getRule().getRepository());
    Artifact symlink = ruleContext.getShareableArtifact(symlinkName, root);
    ruleContext.registerAction(
        new SolibSymlinkAction(ruleContext.getActionOwner(), library, symlink));
    return symlink;
  }

  /**
   * Returns the name of the symlink that will be created for a library, given
   * its name.
   *
   * @param ruleContext rule context that requests symlink
   * @param libraryPath the root-relative path of the library
   * @param preserveName true if filename should be preserved
   * @param prefixConsumer true if the result should be prefixed with the label of the consumer
   * @returns root relative path name
   */
  public static PathFragment getMangledName(RuleContext ruleContext,
                                            PathFragment libraryPath,
                                            boolean preserveName,
                                            boolean prefixConsumer,
                                            CppConfiguration cppConfiguration) {
    String escapedRulePath = Actions.escapedPath(
        "_" + ruleContext.getLabel());
    String soname = getDynamicLibrarySoname(libraryPath, preserveName);
    PathFragment solibDir = new PathFragment(cppConfiguration.getSolibDirectory());
    if (preserveName) {
      String escapedLibraryPath =
          Actions.escapedPath("_" + libraryPath.getParentDirectory().getPathString());
      PathFragment mangledDir = solibDir.getRelative(prefixConsumer
          ? escapedRulePath + "__" + escapedLibraryPath
          : escapedLibraryPath);
      return mangledDir.getRelative(soname);
    } else {
      return solibDir.getRelative(prefixConsumer
          ? escapedRulePath + "__" + soname
          : soname);
    }
  }

  /**
   * Compute the SONAME to use for a dynamic library. This name is basically the
   * name of the shared library in its final symlinked location.
   *
   * @param libraryPath name of the shared library that needs to be mangled
   * @param preserveName true if filename should be preserved, false - mangled
   * @return soname to embed in the dynamic library
   */
  public static String getDynamicLibrarySoname(PathFragment libraryPath,
                                               boolean preserveName) {
    String mangledName;
    if (preserveName) {
      mangledName = libraryPath.getBaseName();
    } else {
      mangledName = "lib" + Actions.escapedPath(libraryPath.getPathString());
    }
    return mangledName;
  }

  @Override
  public boolean shouldReportPathPrefixConflict(ActionAnalysisMetadata action) {
    return false; // Always ignore path prefix conflict for the SolibSymlinkAction.
  }
}
