// 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. *
* The serialized format read and written is equivalent and compatible with the * ".d" file produced by the -MM for a given out (.o) file. *
* The file format looks like: * *
* {outfile}: \ * {infile} \ * {include} \ * ... \ * {include} ** * @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
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(); } }