|  | // Copyright 2014 Google Inc. 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 static java.nio.charset.StandardCharsets.ISO_8859_1; | 
|  |  | 
|  | import com.google.common.annotations.VisibleForTesting; | 
|  | import com.google.common.collect.ImmutableList; | 
|  | import com.google.devtools.build.lib.actions.ActionOwner; | 
|  | import com.google.devtools.build.lib.actions.Artifact; | 
|  | import com.google.devtools.build.lib.actions.Executor; | 
|  | import com.google.devtools.build.lib.analysis.actions.AbstractFileWriteAction; | 
|  | import com.google.devtools.build.lib.events.EventHandler; | 
|  | import com.google.devtools.build.lib.util.Fingerprint; | 
|  | import com.google.devtools.build.lib.vfs.PathFragment; | 
|  |  | 
|  | import java.io.BufferedWriter; | 
|  | import java.io.IOException; | 
|  | import java.io.OutputStream; | 
|  | import java.io.OutputStreamWriter; | 
|  | import java.io.Writer; | 
|  | import java.util.ArrayList; | 
|  | import java.util.Collection; | 
|  | import java.util.Collections; | 
|  | import java.util.Comparator; | 
|  | import java.util.List; | 
|  | import java.util.Map; | 
|  |  | 
|  | import javax.annotation.Nullable; | 
|  |  | 
|  | /** | 
|  | * Action to create a manifest of input files for processing by a subsequent | 
|  | * build step (e.g. runfiles symlinking or archive building). | 
|  | * | 
|  | * <p>The manifest's format is specifiable by {@link ManifestType}, in | 
|  | * accordance with the needs of the calling functionality. | 
|  | * | 
|  | * <p>Note that this action carefully avoids building the manifest content in | 
|  | * memory. | 
|  | */ | 
|  | public class SourceManifestAction extends AbstractFileWriteAction { | 
|  |  | 
|  | private static final String GUID = "07459553-a3d0-4d37-9d78-18ed942470f4"; | 
|  |  | 
|  | /** | 
|  | * Interface for defining manifest formatting and reporting specifics. | 
|  | */ | 
|  | @VisibleForTesting | 
|  | interface ManifestWriter { | 
|  |  | 
|  | /** | 
|  | * Writes a single line of manifest output. | 
|  | * | 
|  | * @param manifestWriter the output stream | 
|  | * @param rootRelativePath path of an entry relative to the manifest's root | 
|  | * @param symlink (optional) symlink that resolves the above path | 
|  | */ | 
|  | void writeEntry(Writer manifestWriter, PathFragment rootRelativePath, | 
|  | @Nullable Artifact symlink) throws IOException; | 
|  |  | 
|  | /** | 
|  | * Fulfills {@link #ActionMetadata.getMnemonic()} | 
|  | */ | 
|  | String getMnemonic(); | 
|  |  | 
|  | /** | 
|  | * Fulfills {@link #AbstractAction.getRawProgressMessage()} | 
|  | */ | 
|  | String getRawProgressMessage(); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * The strategy we use to write manifest entries. | 
|  | */ | 
|  | private final ManifestWriter manifestWriter; | 
|  |  | 
|  | /** | 
|  | * The runfiles for which to create the symlink tree. | 
|  | */ | 
|  | private final Runfiles runfiles; | 
|  |  | 
|  | /** | 
|  | * Creates a new AbstractSourceManifestAction instance using latin1 encoding | 
|  | * to write the manifest file and with a specified root path for manifest entries. | 
|  | * | 
|  | * @param manifestWriter the strategy to use to write manifest entries | 
|  | * @param owner the action owner | 
|  | * @param output the file to which to write the manifest | 
|  | * @param runfiles runfiles | 
|  | */ | 
|  | private SourceManifestAction(ManifestWriter manifestWriter, ActionOwner owner, Artifact output, | 
|  | Runfiles runfiles) { | 
|  | super(owner, getDependencies(runfiles), output, false); | 
|  | this.manifestWriter = manifestWriter; | 
|  | this.runfiles = runfiles; | 
|  | } | 
|  |  | 
|  | @VisibleForTesting | 
|  | public void writeOutputFile(OutputStream out, EventHandler eventHandler) | 
|  | throws IOException { | 
|  | writeFile(out, runfiles.getRunfilesInputs(eventHandler, getOwner().getLocation())); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public DeterministicWriter newDeterministicWriter(EventHandler eventHandler, Executor executor) | 
|  | throws IOException { | 
|  | final Map<PathFragment, Artifact> runfilesInputs = | 
|  | runfiles.getRunfilesInputs(eventHandler, getOwner().getLocation()); | 
|  | return new DeterministicWriter() { | 
|  | @Override | 
|  | public void writeOutputFile(OutputStream out) throws IOException { | 
|  | writeFile(out, runfilesInputs); | 
|  | } | 
|  | }; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean isRemotable() { | 
|  | // There is little gain to remoting these, since they include absolute path names inline. | 
|  | return false; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns the input dependencies for this action. Note we don't need to create the symlink | 
|  | * target Artifacts before we write the output manifest, so this Action does not have to | 
|  | * depend on them. The only necessary dependencies are pruning manifests, which must be read | 
|  | * to properly prune the tree. | 
|  | */ | 
|  | private static Collection<Artifact> getDependencies(Runfiles runfiles) { | 
|  | ImmutableList.Builder<Artifact> builder = ImmutableList.builder(); | 
|  | for (Runfiles.PruningManifest manifest : runfiles.getPruningManifests()) { | 
|  | builder.add(manifest.getManifestFile()); | 
|  | } | 
|  | return builder.build(); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Sort the entries in both the normal and root manifests and write the output | 
|  | * file. | 
|  | * | 
|  | * @param out is the message stream to write errors to. | 
|  | * @param output The actual mapping of the output manifest. | 
|  | * @throws IOException | 
|  | */ | 
|  | private void writeFile(OutputStream out, Map<PathFragment, Artifact> output) throws IOException { | 
|  | Writer manifestFile = new BufferedWriter(new OutputStreamWriter(out, ISO_8859_1)); | 
|  |  | 
|  | Comparator<Map.Entry<PathFragment, Artifact>> fragmentComparator = | 
|  | new Comparator<Map.Entry<PathFragment, Artifact>>() { | 
|  | @Override | 
|  | public int compare(Map.Entry<PathFragment, Artifact> path1, | 
|  | Map.Entry<PathFragment, Artifact> path2) { | 
|  | return path1.getKey().compareTo(path2.getKey()); | 
|  | } | 
|  | }; | 
|  |  | 
|  | List<Map.Entry<PathFragment, Artifact>> sortedManifest = new ArrayList<>(output.entrySet()); | 
|  | Collections.sort(sortedManifest, fragmentComparator); | 
|  |  | 
|  | for (Map.Entry<PathFragment, Artifact> line : sortedManifest) { | 
|  | manifestWriter.writeEntry(manifestFile, line.getKey(), line.getValue()); | 
|  | } | 
|  |  | 
|  | manifestFile.flush(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public String getMnemonic() { | 
|  | return manifestWriter.getMnemonic(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | protected String getRawProgressMessage() { | 
|  | return manifestWriter.getRawProgressMessage() + " for " + getOwner().getLabel(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | protected String computeKey() { | 
|  | Fingerprint f = new Fingerprint(); | 
|  | f.addString(GUID); | 
|  | Map<PathFragment, Artifact> symlinks = runfiles.getSymlinksAsMap(); | 
|  | f.addInt(symlinks.size()); | 
|  | for (Map.Entry<PathFragment, Artifact> symlink : symlinks.entrySet()) { | 
|  | f.addPath(symlink.getKey()); | 
|  | f.addPath(symlink.getValue().getPath()); | 
|  | } | 
|  | Map<PathFragment, Artifact> rootSymlinks = runfiles.getRootSymlinksAsMap(); | 
|  | f.addInt(rootSymlinks.size()); | 
|  | for (Map.Entry<PathFragment, Artifact> rootSymlink : rootSymlinks.entrySet()) { | 
|  | f.addPath(rootSymlink.getKey()); | 
|  | f.addPath(rootSymlink.getValue().getPath()); | 
|  | } | 
|  |  | 
|  | for (Artifact artifact : runfiles.getArtifactsWithoutMiddlemen()) { | 
|  | f.addPath(artifact.getRootRelativePath()); | 
|  | f.addPath(artifact.getPath()); | 
|  | } | 
|  | return f.hexDigestAndReset(); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Supported manifest writing strategies. | 
|  | */ | 
|  | public static enum ManifestType implements ManifestWriter { | 
|  |  | 
|  | /** | 
|  | * Writes each line as: | 
|  | * | 
|  | * [rootRelativePath] [resolvingSymlink] | 
|  | * | 
|  | * <p>This strategy is suitable for creating an input manifest to a source view tree. Its | 
|  | * output is a valid input to {@link com.google.devtools.build.lib.analysis.SymlinkTreeAction}. | 
|  | */ | 
|  | SOURCE_SYMLINKS { | 
|  | @Override | 
|  | public void writeEntry(Writer manifestWriter, PathFragment rootRelativePath, Artifact symlink) | 
|  | throws IOException { | 
|  | manifestWriter.append(rootRelativePath.getPathString()); | 
|  | // This trailing whitespace is REQUIRED to process the single entry line correctly. | 
|  | manifestWriter.append(' '); | 
|  | if (symlink != null) { | 
|  | manifestWriter.append(symlink.getPath().getPathString()); | 
|  | } | 
|  | manifestWriter.append('\n'); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public String getMnemonic() { | 
|  | return "SourceSymlinkManifest"; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public String getRawProgressMessage() { | 
|  | return "Creating source manifest"; | 
|  | } | 
|  | }, | 
|  |  | 
|  | /** | 
|  | * Writes each line as: | 
|  | * | 
|  | * [rootRelativePath] | 
|  | * | 
|  | * <p>This strategy is suitable for an input into a packaging system (notably .par) that | 
|  | * consumes a list of all source files but needs that list to be constant with respect to | 
|  | * how the user has their client laid out on local disk. | 
|  | */ | 
|  | SOURCES_ONLY { | 
|  | @Override | 
|  | public void writeEntry(Writer manifestWriter, PathFragment rootRelativePath, Artifact symlink) | 
|  | throws IOException { | 
|  | manifestWriter.append(rootRelativePath.getPathString()); | 
|  | manifestWriter.append('\n'); | 
|  | manifestWriter.flush(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public String getMnemonic() { | 
|  | return "PackagingSourcesManifest"; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public String getRawProgressMessage() { | 
|  | return "Creating file sources list"; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /** Creates an action for the given runfiles. */ | 
|  | public static SourceManifestAction forRunfiles(ManifestType manifestType, ActionOwner owner, | 
|  | Artifact output, Runfiles runfiles) { | 
|  | return new SourceManifestAction(manifestType, owner, output, runfiles); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Builder class to construct {@link SourceManifestAction} instances. | 
|  | */ | 
|  | public static final class Builder { | 
|  | private final ManifestWriter manifestWriter; | 
|  | private final ActionOwner owner; | 
|  | private final Artifact output; | 
|  | private final Runfiles.Builder runfilesBuilder; | 
|  |  | 
|  | public Builder(String prefix, ManifestType manifestType, ActionOwner owner, Artifact output) { | 
|  | this.runfilesBuilder = new Runfiles.Builder(prefix); | 
|  | manifestWriter = manifestType; | 
|  | this.owner = owner; | 
|  | this.output = output; | 
|  | } | 
|  |  | 
|  | @VisibleForTesting | 
|  | Builder(String prefix, ManifestWriter manifestWriter, ActionOwner owner, Artifact output) { | 
|  | this.runfilesBuilder = new Runfiles.Builder(prefix); | 
|  | this.manifestWriter = manifestWriter; | 
|  | this.owner = owner; | 
|  | this.output = output; | 
|  | } | 
|  |  | 
|  | public SourceManifestAction build() { | 
|  | return new SourceManifestAction(manifestWriter, owner, output, runfilesBuilder.build()); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Adds a set of symlinks from the artifacts' root-relative paths to the | 
|  | * artifacts themselves. | 
|  | */ | 
|  | public Builder addSymlinks(Iterable<Artifact> artifacts) { | 
|  | runfilesBuilder.addArtifacts(artifacts); | 
|  | return this; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Adds a map of symlinks. | 
|  | */ | 
|  | public Builder addSymlinks(Map<PathFragment, Artifact> symlinks) { | 
|  | runfilesBuilder.addSymlinks(symlinks); | 
|  | return this; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Adds a single symlink. | 
|  | */ | 
|  | public Builder addSymlink(PathFragment link, Artifact target) { | 
|  | runfilesBuilder.addSymlink(link, target); | 
|  | return this; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * <p>Adds a mapping of Artifacts to the directory above the normal symlink | 
|  | * forest base. | 
|  | */ | 
|  | public Builder addRootSymlinks(Map<PathFragment, Artifact> rootSymlinks) { | 
|  | runfilesBuilder.addRootSymlinks(rootSymlinks); | 
|  | return this; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Set the empty files supplier for the manifest, see {@link Runfiles.EmptyFilesSupplier} | 
|  | * for more details. | 
|  | */ | 
|  | public Builder setEmptyFilesSupplier(Runfiles.EmptyFilesSupplier supplier) { | 
|  | runfilesBuilder.setEmptyFilesSupplier(supplier); | 
|  | return this; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Adds a runfiles pruning manifest. | 
|  | */ | 
|  | @VisibleForTesting | 
|  | Builder addPruningManifest(Runfiles.PruningManifest manifest) { | 
|  | runfilesBuilder.addPruningManifest(manifest); | 
|  | return this; | 
|  | } | 
|  | } | 
|  | } |