| // 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.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; | |
| import java.io.PrintStream; | |
| import java.nio.charset.StandardCharsets; | |
| import java.util.ArrayList; | |
| import java.util.Collection; | |
| import java.util.Collections; | |
| import java.util.regex.Matcher; | |
| import java.util.regex.Pattern; | |
| /** | |
| * 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 PathFragment}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 { | |
| private static final Pattern DOTD_MERGED_LINE_SEPARATOR = Pattern.compile("\\\\[\n\r]+"); | |
| private static final Pattern DOTD_LINE_SEPARATOR = Pattern.compile("[\n\r]+"); | |
| private static final Pattern DOTD_DEP = Pattern.compile("(?:[^\\s\\\\]++|\\\\ |\\\\)+"); | |
| /** | |
| * The set of dependent files that this DependencySet embodies. May be | |
| * relative or absolute PathFragments. A tree set is used to ensure that we | |
| * write them out in a consistent order. | |
| */ | |
| private final Collection<PathFragment> 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 PathFragment form | |
| * from this DependencySet instance. | |
| */ | |
| public Collection<PathFragment> getDependencies() { | |
| return Collections.unmodifiableCollection(dependencies); | |
| } | |
| /** | |
| * Adds a given collection of dependencies in Path form to this DependencySet | |
| * instance. Paths are converted to root-relative | |
| */ | |
| public void addDependencies(Collection<Path> deps) { | |
| for (Path d : deps) { | |
| addDependency(d.relativeTo(root)); | |
| } | |
| } | |
| /** | |
| * Adds a given dependency in PathFragment form to this DependencySet | |
| * instance. | |
| */ | |
| public void addDependency(PathFragment dep) { | |
| dependencies.add(Preconditions.checkNotNull(dep)); | |
| } | |
| /** | |
| * 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 { | |
| if (content.length > 0 && content[content.length - 1] != '\n') { | |
| throw new IOException("File does not end in a newline"); | |
| } | |
| // true if there is a CR in the input. | |
| boolean cr = content.length > 0 && content[0] == '\r'; | |
| // true if there is more than one line in the input, not counting \-wrapped lines. | |
| boolean multiline = false; | |
| byte prevByte = ' '; | |
| for (int i = 1; i < content.length; i++) { | |
| byte b = content[i]; | |
| if (cr || b == '\r') { | |
| // CR found, abort since our little loop here does not deal with CR/LFs. | |
| cr = true; | |
| break; | |
| } | |
| if (b == '\n') { | |
| // Merge lines wrapped using backslashes. | |
| if (prevByte == '\\') { | |
| content[i] = ' '; | |
| content[i - 1] = ' '; | |
| } else { | |
| multiline = true; | |
| } | |
| } | |
| prevByte = b; | |
| } | |
| if (!cr && content.length > 0 && content[content.length - 1] == '\n') { | |
| content[content.length - 1] = ' '; | |
| } | |
| String s = new String(content, StandardCharsets.UTF_8); | |
| if (cr) { | |
| s = DOTD_MERGED_LINE_SEPARATOR.matcher(s).replaceAll(" ").trim(); | |
| multiline = true; | |
| } | |
| return process(s, multiline); | |
| } | |
| private DependencySet process(String contents, boolean multiline) { | |
| String[] lines; | |
| if (!multiline) { | |
| // Microoptimization: skip the usually unnecessary expensive-ish splitting step if there is | |
| // only one target. This saves about 20% of CPU time. | |
| lines = new String[] { contents }; | |
| } else { | |
| lines = DOTD_LINE_SEPARATOR.split(contents); | |
| } | |
| for (String line : lines) { | |
| // Split off output file name. | |
| int pos = line.indexOf(':'); | |
| if (pos == -1) { | |
| continue; | |
| } | |
| outputFileName = line.substring(0, pos); | |
| String deps = line.substring(pos + 1); | |
| Matcher m = DOTD_DEP.matcher(deps); | |
| while (m.find()) { | |
| String token = m.group(); | |
| // Process escaped spaces. | |
| if (token.contains("\\ ")) { | |
| token = token.replace("\\ ", " "); | |
| } | |
| dependencies.add(new PathFragment(token).normalize()); | |
| } | |
| } | |
| 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 (PathFragment 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(); | |
| } | |
| } |