| // 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.util; | |
| import com.google.common.annotations.VisibleForTesting; | |
| import com.google.devtools.build.lib.vfs.FileSystemUtils; | |
| import com.google.devtools.build.lib.vfs.Path; | |
| import java.io.IOException; | |
| import java.io.PrintStream; | |
| import java.nio.charset.StandardCharsets; | |
| import java.util.ArrayList; | |
| import java.util.Collection; | |
| import java.util.Collections; | |
| /** | |
| * Representation of a set of file dependencies for a given output file. There | |
| * are generally one input dependency and a bunch of include dependencies. The | |
| * files are stored as {@code Path}s and may be relative or absolute. | |
| * <p> | |
| * The serialized format read and written is equivalent and compatible with the | |
| * ".d" file produced by the -MM for a given out (.o) file. | |
| * <p> | |
| * The file format looks like: | |
| * | |
| * <pre> | |
| * {outfile}: \ | |
| * {infile} \ | |
| * {include} \ | |
| * ... \ | |
| * {include} | |
| * </pre> | |
| * | |
| * @see "http://gcc.gnu.org/onlinedocs/gcc-4.2.1/gcc/Preprocessor-Options.html#Preprocessor-Options" | |
| */ | |
| public final class DependencySet { | |
| /** | |
| * The set of dependent files that this DependencySet embodies. They are all | |
| * Path with the same FileSystem A tree set is used to ensure that we | |
| * write them out in a consistent order. | |
| */ | |
| private final Collection<Path> dependencies = new ArrayList<>(); | |
| private final Path root; | |
| private String outputFileName; | |
| /** | |
| * Get output file name for which dependencies are included in this DependencySet. | |
| */ | |
| public String getOutputFileName() { | |
| return outputFileName; | |
| } | |
| public void setOutputFileName(String outputFileName) { | |
| this.outputFileName = outputFileName; | |
| } | |
| /** | |
| * Constructs a new empty DependencySet instance. | |
| */ | |
| public DependencySet(Path root) { | |
| this.root = root; | |
| } | |
| /** | |
| * Gets an unmodifiable view of the set of dependencies in {@link Path} form | |
| * from this DependencySet instance. | |
| */ | |
| public Collection<Path> getDependencies() { | |
| return Collections.unmodifiableCollection(dependencies); | |
| } | |
| /** | |
| * Adds a given collection of dependencies in Path form to this DependencySet | |
| * instance. Paths are converted to root-relative | |
| */ | |
| @VisibleForTesting // only called from DependencySetTest | |
| public void addDependencies(Collection<Path> deps) { | |
| for (Path d : deps) { | |
| Preconditions.checkArgument(d.startsWith(root)); | |
| dependencies.add(d); | |
| } | |
| } | |
| /** | |
| * Adds a given dependency to this DependencySet instance. | |
| */ | |
| private void addDependency(String dep) { | |
| Path depPath = root.getRelative(dep); | |
| dependencies.add(depPath); | |
| } | |
| /** | |
| * Reads a dotd file into this DependencySet instance. | |
| */ | |
| public DependencySet read(Path dotdFile) throws IOException { | |
| byte[] content = FileSystemUtils.readContent(dotdFile); | |
| try { | |
| return process(content); | |
| } catch (IOException e) { | |
| throw new IOException("Error processing " + dotdFile + ": " + e.getMessage()); | |
| } | |
| } | |
| /** | |
| * Parses a .d file. | |
| * | |
| * <p>Performance-critical! In large C++ builds there are lots of .d files to read, and some of | |
| * them reach into hundreds of kilobytes. | |
| */ | |
| public DependencySet process(byte[] content) throws IOException { | |
| final int n = content.length; | |
| if (n > 0 && content[n - 1] != '\n') { | |
| throw new IOException("File does not end in a newline"); | |
| // From now on, we can safely peek ahead one character when not at a newline. | |
| } | |
| // Our write position in content[]; we use the prefix as working space to build strings. | |
| int w = 0; | |
| // Have we seen a leading "mumble.o:" on this line yet? If not, we ignore | |
| // any dependencies we parse. This is bug-for-bug compatibility with our | |
| // MSVC wrapper, which generates invalid .d files :( | |
| boolean sawTarget = false; | |
| for (int r = 0; r < n; ) { | |
| final byte c = content[r++]; | |
| switch (c) { | |
| case ' ': | |
| // If we haven't yet seen the colon delimiting the target name, | |
| // keep scanning. We do this to cope with "foo.o : \" which is | |
| // valid Makefile syntax produced by the cuda compiler. | |
| if (sawTarget && w > 0) { | |
| addDependency(new String(content, 0, w, StandardCharsets.UTF_8)); | |
| w = 0; | |
| } | |
| continue; | |
| case '\r': | |
| // Ignore, should be followed by a \n. | |
| continue; | |
| case '\n': | |
| // This closes a filename. | |
| // (Arguably if !sawTarget && w > 0 we should report an error, | |
| // as that suggests the .d file is malformed.) | |
| if (sawTarget && w > 0) { | |
| addDependency(new String(content, 0, w, StandardCharsets.UTF_8)); | |
| } | |
| w = 0; | |
| sawTarget = false; // reset for new line | |
| continue; | |
| case ':': | |
| // Normally this indicates the target name, but it might be part of a | |
| // filename on Windows. Peek ahead at the next character. | |
| switch (content[r]) { | |
| case ' ': | |
| case '\n': | |
| case '\r': | |
| if (w > 0) { | |
| outputFileName = new String(content, 0, w, StandardCharsets.UTF_8); | |
| w = 0; | |
| sawTarget = true; | |
| } | |
| continue; | |
| default: | |
| content[w++] = c; // copy a colon to filename | |
| continue; | |
| } | |
| case '\\': | |
| // Peek ahead at the next character. | |
| switch (content[r]) { | |
| // Backslashes are taken literally except when followed by whitespace. | |
| // See the Windows tests for some of the nonsense we have to tolerate. | |
| case ' ': | |
| content[w++] = ' '; // copy a space to the filename | |
| ++r; // skip over the space | |
| continue; | |
| case '\n': | |
| ++r; // skip over the newline | |
| continue; | |
| case '\r': | |
| // One backslash can escape \r\n, so peek one more character. | |
| if (content[++r] == '\n') { | |
| ++r; | |
| } | |
| continue; | |
| default: | |
| content[w++] = c; // copy a backlash to the filename | |
| continue; | |
| } | |
| default: | |
| content[w++] = c; | |
| } | |
| } | |
| return this; | |
| } | |
| /** | |
| * Writes this DependencySet object for a specified output file under the root | |
| * dir, and with a given suffix. | |
| */ | |
| public void write(Path outFile, String suffix) throws IOException { | |
| Path dotdFile = | |
| outFile.getRelative(FileSystemUtils.replaceExtension(outFile.asFragment(), suffix)); | |
| PrintStream out = new PrintStream(dotdFile.getOutputStream()); | |
| try { | |
| out.print(outFile.relativeTo(root) + ": "); | |
| for (Path d : dependencies) { | |
| out.print(" \\\n " + d.getPathString()); // should already be root relative | |
| } | |
| out.println(); | |
| } finally { | |
| out.close(); | |
| } | |
| } | |
| @Override | |
| public boolean equals(Object other) { | |
| return other instanceof DependencySet | |
| && ((DependencySet) other).dependencies.equals(dependencies); | |
| } | |
| @Override | |
| public int hashCode() { | |
| return dependencies.hashCode(); | |
| } | |
| } |