diff options
author | 2017-12-22 00:56:58 -0800 | |
---|---|---|
committer | 2017-12-22 00:58:31 -0800 | |
commit | e63a867feb2483851b5169894ff7b3c0bdb26581 (patch) | |
tree | 68306264ab9dc8f2383f987a42fb768b2d6436a8 /src/main/java/com/google/devtools/build | |
parent | 85e8d51c72c5dcb4d1f6a8f3186149339ca59fac (diff) |
Redo FileType to reduce generated garbage.
* Change FileType to no longer assume it operates on just the base name (it can now be given a full path).
* Move the responsibility to specific classes (Artifact, Path, PathFragment) to decide how they want to offer up a string that includes the file name.
* Flip the order in which users are expected to check Artifact type, from FileType#matches(Artifact) to Artifact#isFileType(FileType). This looks natural and should encourage developers to use efficient file type checking methods.
* Change CppCompileAction to use the new API.
RELNOTES: None
PiperOrigin-RevId: 179903239
Diffstat (limited to 'src/main/java/com/google/devtools/build')
11 files changed, 309 insertions, 227 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/BUILD b/src/main/java/com/google/devtools/build/lib/BUILD index 499c3b5114..c48d1efc27 100644 --- a/src/main/java/com/google/devtools/build/lib/BUILD +++ b/src/main/java/com/google/devtools/build/lib/BUILD @@ -97,11 +97,10 @@ java_library( java_library( name = "base-util", srcs = [ - "util/StringCanonicalizer.java", "util/VarInt.java", ], + exports = [":string_util"], deps = [ - "//src/main/java/com/google/devtools/build/lib/concurrent", "//third_party:guava", ], ) @@ -161,19 +160,24 @@ java_library( "util/BlazeClock.java", "util/Clock.java", "util/ExitCode.java", + "util/FileType.java", + "util/FileTypeSet.java", "util/JavaClock.java", "util/OS.java", "util/ProcessUtils.java", "util/SingleLineFormatter.java", "util/StringCanonicalizer.java", "util/StringTrie.java", + "util/StringUtil.java", "util/VarInt.java", ], ), exports = [ "//src/main/java/com/google/devtools/build/lib:base-util", "//src/main/java/com/google/devtools/build/lib:exitcode-external", + "//src/main/java/com/google/devtools/build/lib:filetype", "//src/main/java/com/google/devtools/build/lib:os_util", + "//src/main/java/com/google/devtools/build/lib:string_util", "//src/main/java/com/google/devtools/build/lib/clock", "//src/main/java/com/google/devtools/build/lib/collect", ], @@ -196,6 +200,31 @@ java_library( ) java_library( + name = "filetype", + srcs = [ + "util/FileType.java", + "util/FileTypeSet.java", + ], + deps = [ + ":string_util", + "//third_party:guava", + "//third_party:jsr305", + ], +) + +java_library( + name = "string_util", + srcs = [ + "util/StringCanonicalizer.java", + "util/StringUtil.java", + ], + deps = [ + "//src/main/java/com/google/devtools/build/lib/concurrent", + "//third_party:guava", + ], +) + +java_library( name = "exitcode-external", srcs = [ "util/ExitCode.java", diff --git a/src/main/java/com/google/devtools/build/lib/actions/Artifact.java b/src/main/java/com/google/devtools/build/lib/actions/Artifact.java index e47d0e3bd6..7468de1759 100644 --- a/src/main/java/com/google/devtools/build/lib/actions/Artifact.java +++ b/src/main/java/com/google/devtools/build/lib/actions/Artifact.java @@ -38,6 +38,7 @@ import com.google.devtools.build.lib.skylarkinterface.SkylarkValue; import com.google.devtools.build.lib.syntax.EvalUtils; import com.google.devtools.build.lib.syntax.EvalUtils.ComparisonException; import com.google.devtools.build.lib.util.FileType; +import com.google.devtools.build.lib.util.FileTypeSet; import com.google.devtools.build.lib.vfs.Path; import com.google.devtools.build.lib.vfs.PathFragment; import java.util.ArrayList; @@ -48,67 +49,73 @@ import java.util.Objects; import javax.annotation.Nullable; /** - * An Artifact represents a file used by the build system, whether it's a source - * file or a derived (output) file. Not all Artifacts have a corresponding - * FileTarget object in the <code>build.lib.packages</code> API: for example, - * low-level intermediaries internal to a given rule, such as a Java class files - * or C++ object files. However all FileTargets have a corresponding Artifact. + * An Artifact represents a file used by the build system, whether it's a source file or a derived + * (output) file. Not all Artifacts have a corresponding FileTarget object in the <code> + * build.lib.packages</code> API: for example, low-level intermediaries internal to a given rule, + * such as a Java class files or C++ object files. However all FileTargets have a corresponding + * Artifact. * - * <p>In any given call to SkyframeExecutor#buildArtifacts(), no two Artifacts in the - * action graph may refer to the same path. + * <p>In any given call to SkyframeExecutor#buildArtifacts(), no two Artifacts in the action graph + * may refer to the same path. + * + * <p>Artifacts generally fall into two classifications, source and derived, but there exist a few + * other cases that are fuzzy and difficult to classify. The following cases exist: * - * <p>Artifacts generally fall into two classifications, source and derived, but - * there exist a few other cases that are fuzzy and difficult to classify. The - * following cases exist: * <ul> - * <li>Well-formed source Artifacts will have null generating Actions and a root - * that is orthogonal to execRoot. (With the root coming from the package path.) - * <li>Well-formed derived Artifacts will have non-null generating Actions, and - * a root that is below execRoot. - * <li>Symlinked include source Artifacts under the output/include tree will - * appear to be derived artifacts with null generating Actions. - * <li>Some derived Artifacts, mostly in the genfiles tree and mostly discovered - * during include validation, will also have null generating Actions. + * <li>Well-formed source Artifacts will have null generating Actions and a root that is + * orthogonal to execRoot. (With the root coming from the package path.) + * <li>Well-formed derived Artifacts will have non-null generating Actions, and a root that is + * below execRoot. + * <li>Symlinked include source Artifacts under the output/include tree will appear to be derived + * artifacts with null generating Actions. + * <li>Some derived Artifacts, mostly in the genfiles tree and mostly discovered during include + * validation, will also have null generating Actions. * </ul> * - * In the usual case, an Artifact represents a single file. However, an Artifact may - * also represent the following: + * In the usual case, an Artifact represents a single file. However, an Artifact may also represent + * the following: + * * <ul> - * <li>A TreeArtifact, which is a directory containing a tree of unknown {@link Artifact}s. - * In the future, Actions will be able to examine these files as inputs and declare them as outputs - * at execution time, but this is not yet implemented. This is used for Actions where - * the inputs and/or outputs might not be discoverable except during Action execution. - * <li>A directory of unknown contents, but not a TreeArtifact. - * This is a legacy facility and should not be used by any new rule implementations. - * In particular, the file system cache integrity checks fail for directories. - * <li>An 'aggregating middleman' special Artifact, which may be expanded using a - * {@link ArtifactExpander} at Action execution time. This is used by a handful of rules to save - * memory. - * <li>A 'constant metadata' special Artifact. These represent real files, changes to which are - * ignored by the build system. They are useful for files which change frequently but do not affect - * the result of a build, such as timestamp files. - * <li>A 'Fileset' special Artifact. This is a legacy type of Artifact and should not be used - * by new rule implementations. + * <li>A TreeArtifact, which is a directory containing a tree of unknown {@link Artifact}s. In the + * future, Actions will be able to examine these files as inputs and declare them as outputs + * at execution time, but this is not yet implemented. This is used for Actions where the + * inputs and/or outputs might not be discoverable except during Action execution. + * <li>A directory of unknown contents, but not a TreeArtifact. This is a legacy facility and + * should not be used by any new rule implementations. In particular, the file system cache + * integrity checks fail for directories. + * <li>An 'aggregating middleman' special Artifact, which may be expanded using a {@link + * ArtifactExpander} at Action execution time. This is used by a handful of rules to save + * memory. + * <li>A 'constant metadata' special Artifact. These represent real files, changes to which are + * ignored by the build system. They are useful for files which change frequently but do not + * affect the result of a build, such as timestamp files. + * <li>A 'Fileset' special Artifact. This is a legacy type of Artifact and should not be used by + * new rule implementations. * </ul> - * <p>This class is "theoretically" final; it should not be subclassed except by - * {@link SpecialArtifact}. + * + * <p>This class is "theoretically" final; it should not be subclassed except by {@link + * SpecialArtifact}. */ @Immutable -@SkylarkModule(name = "File", - category = SkylarkModuleCategory.BUILTIN, - doc = "<p>This type represents a file or directory used by the build system. It can be " - + "either a source file or a derived file produced by a rule.</p>" - + "<p>The File constructor is private, so you cannot call it directly to create new " - + "Files. If you have a Skylark rule that needs to create a new File, you have two options:" - + "<ul>" - + "<li>use <a href='actions.html#declare_file'>ctx.actions.declare_file</a> " - + "or <a href='actions.html#declare_directory'>ctx.actions.declare_directory</a> to " - + "declare a new file in the rule implementation.</li>" - + "<li>add the label to the attrs (if it's an input) or the outputs (if it's an output)." - + " Then you can access the File through the rule's " - + "<a href='ctx.html#outputs'>ctx.outputs</a>.") +@SkylarkModule( + name = "File", + category = SkylarkModuleCategory.BUILTIN, + doc = + "<p>This type represents a file or directory used by the build system. It can be " + + "either a source file or a derived file produced by a rule.</p>" + + "<p>The File constructor is private, so you cannot call it directly to create new " + + "Files. If you have a Skylark rule that needs to create a new File, " + + "you have two options:" + + "<ul>" + + "<li>use <a href='actions.html#declare_file'>ctx.actions.declare_file</a> " + + "or <a href='actions.html#declare_directory'>ctx.actions.declare_directory</a> to " + + "declare a new file in the rule implementation.</li>" + + "<li>add the label to the attrs (if it's an input) or the outputs (if it's an output)." + + " Then you can access the File through the rule's " + + "<a href='ctx.html#outputs'>ctx.outputs</a>." +) public class Artifact - implements FileType.HasFilename, ActionInput, SkylarkValue, Comparable<Object> { + implements FileType.HasFileType, ActionInput, SkylarkValue, Comparable<Object> { /** Compares artifact according to their exec paths. Sorts null values first. */ @SuppressWarnings("ReferenceEquality") // "a == b" is an optimization @@ -281,7 +288,6 @@ public class Artifact /** * Returns the base file name of this artifact, similar to basename(1). */ - @Override @SkylarkCallable(name = "basename", structField = true, doc = "The base name of this file. This is the name of the file inside the directory.") public final String getFilename() { @@ -294,6 +300,27 @@ public class Artifact } /** + * Checks whether this artifact is of the supplied file type. + * + * <p>Prefer this method to pulling out strings from the Artifact and passing to {@link + * FileType#matches(String)} manually. This method has been optimized to generate a minimum of + * garbage. + */ + public boolean isFileType(FileType fileType) { + return fileType.matches(this); + } + + /** Checks whether this artifact is any of the supplied file types. */ + public boolean isAnyFileType(FileTypeSet fileTypeSet) { + return fileTypeSet.matches(filePathForFileTypeMatcher()); + } + + @Override + public String filePathForFileTypeMatcher() { + return getExecPath().filePathForFileTypeMatcher(); + } + + /** * Returns the artifact owner. May be null. */ @Nullable public final Label getOwner() { diff --git a/src/main/java/com/google/devtools/build/lib/analysis/configuredtargets/FileConfiguredTarget.java b/src/main/java/com/google/devtools/build/lib/analysis/configuredtargets/FileConfiguredTarget.java index 4a009fe326..30e9f2f203 100644 --- a/src/main/java/com/google/devtools/build/lib/analysis/configuredtargets/FileConfiguredTarget.java +++ b/src/main/java/com/google/devtools/build/lib/analysis/configuredtargets/FileConfiguredTarget.java @@ -35,11 +35,11 @@ import com.google.devtools.build.lib.packages.Provider; import com.google.devtools.build.lib.util.FileType; /** - * A ConfiguredTarget for a source FileTarget. (Generated files use a - * subclass, OutputFileConfiguredTarget.) + * A ConfiguredTarget for a source FileTarget. (Generated files use a subclass, + * OutputFileConfiguredTarget.) */ public abstract class FileConfiguredTarget extends AbstractConfiguredTarget - implements FileType.HasFilename, LicensesProvider { + implements FileType.HasFileType, LicensesProvider { private final Artifact artifact; private final TransitiveInfoProviderMap providers; @@ -78,12 +78,16 @@ public abstract class FileConfiguredTarget extends AbstractConfiguredTarget /** * Returns the file name of this file target. */ - @Override public String getFilename() { return getTarget().getFilename(); } @Override + public String filePathForFileTypeMatcher() { + return getFilename(); + } + + @Override public <P extends TransitiveInfoProvider> P getProvider(Class<P> provider) { AnalysisUtils.checkProvider(provider); return providers.getProvider(provider); diff --git a/src/main/java/com/google/devtools/build/lib/packages/FileTarget.java b/src/main/java/com/google/devtools/build/lib/packages/FileTarget.java index a488eff5bc..51cdb1afa0 100644 --- a/src/main/java/com/google/devtools/build/lib/packages/FileTarget.java +++ b/src/main/java/com/google/devtools/build/lib/packages/FileTarget.java @@ -17,14 +17,14 @@ package com.google.devtools.build.lib.packages; import com.google.common.base.Preconditions; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.packages.License.DistributionType; -import com.google.devtools.build.lib.util.FileType.HasFilename; +import com.google.devtools.build.lib.util.FileType; import java.util.Set; /** - * Common superclass for InputFile and OutputFile which provides implementation - * for the file operations in common. + * Common superclass for InputFile and OutputFile which provides implementation for the file + * operations in common. */ -public abstract class FileTarget implements Target, HasFilename { +public abstract class FileTarget implements Target, FileType.HasFileType { protected final Package pkg; protected final Label label; @@ -37,7 +37,6 @@ public abstract class FileTarget implements Target, HasFilename { this.label = label; } - @Override public String getFilename() { return label.getName(); } @@ -58,6 +57,11 @@ public abstract class FileTarget implements Target, HasFilename { } @Override + public String filePathForFileTypeMatcher() { + return getFilename(); + } + + @Override public String toString() { return getTargetKind() + "(" + getLabel() + ")"; // Just for debugging } diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileAction.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileAction.java index 4a97d11181..4f7bce06a0 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileAction.java +++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileAction.java @@ -381,7 +381,7 @@ public class CppCompileAction extends AbstractAction // discarded as orphans. // This is strictly better than marking all transitive modules as inputs, which would also // effectively disable orphan detection for .pcm files. - if (CppFileTypes.CPP_MODULE.matches(outputFile.getFilename())) { + if (outputFile.isFileType(CppFileTypes.CPP_MODULE)) { return ImmutableSet.of(outputFile); } return super.getMandatoryOutputs(); @@ -447,7 +447,7 @@ public class CppCompileAction extends AbstractAction if (shouldPruneModules) { Set<Artifact> initialResultSet = Sets.newLinkedHashSet(initialResult); - if (CppFileTypes.CPP_MODULE.matches(sourceFile.getFilename())) { + if (sourceFile.isFileType(CppFileTypes.CPP_MODULE)) { usedModules = ImmutableSet.of(sourceFile); initialResultSet.add(sourceFile); } else { @@ -485,7 +485,7 @@ public class CppCompileAction extends AbstractAction value, "Owner %s of %s not in graph %s", artifact.getArtifactOwner(), artifact, skyKey); // We can get the generating action here because #canRemoveAfterExecution is overridden. Preconditions.checkState( - CppFileTypes.CPP_MODULE.matches(artifact.getFilename()), + artifact.isFileType(CppFileTypes.CPP_MODULE), "Non-module? %s (%s %s)", artifact, this, @@ -493,7 +493,7 @@ public class CppCompileAction extends AbstractAction CppCompileAction action = (CppCompileAction) value.getGeneratingActionDangerousReadJavadoc(artifact); for (Artifact input : action.getInputs()) { - if (CppFileTypes.CPP_MODULE.matches(input.getFilename())) { + if (input.isFileType(CppFileTypes.CPP_MODULE)) { additionalModules.add(input); } } @@ -631,7 +631,7 @@ public class CppCompileAction extends AbstractAction @Override public Artifact getMainIncludeScannerSource() { - return CppFileTypes.CPP_MODULE_MAP.matches(getSourceFile().getPath()) + return getSourceFile().isFileType(CppFileTypes.CPP_MODULE_MAP) ? Iterables.getFirst(context.getHeaderModuleSrcs(), null) : getSourceFile(); } @@ -639,7 +639,7 @@ public class CppCompileAction extends AbstractAction @Override public Collection<Artifact> getIncludeScannerSources() { NestedSetBuilder<Artifact> builder = NestedSetBuilder.stableOrder(); - if (CppFileTypes.CPP_MODULE_MAP.matches(getSourceFile().getPath())) { + if (getSourceFile().isFileType(CppFileTypes.CPP_MODULE_MAP)) { // If this is an action that compiles the header module itself, the source we build is the // module map, and we need to include-scan all headers that are referenced in the module map. // We need to do include scanning as long as we want to support building code bases that are @@ -702,7 +702,7 @@ public class CppCompileAction extends AbstractAction public boolean canRemoveAfterExecution() { // Module-generating actions are needed because the action may be retrieved in // #discoverInputsStage2. - return !CppFileTypes.CPP_MODULE.matches(getPrimaryOutput().getFilename()); + return !getPrimaryOutput().isFileType(CppFileTypes.CPP_MODULE); } @Override @@ -962,7 +962,7 @@ public class CppCompileAction extends AbstractAction Iterable<Artifact> potentialModules) { ImmutableList.Builder<String> usedModulePaths = ImmutableList.builder(); for (Artifact input : potentialModules) { - if (CppFileTypes.CPP_MODULE.matches(input.getFilename())) { + if (input.isFileType(CppFileTypes.CPP_MODULE)) { usedModulePaths.add(input.getExecPathString()); } } @@ -1242,7 +1242,7 @@ public class CppCompileAction extends AbstractAction */ private void ensureCoverageNotesFilesExist() throws ActionExecutionException { for (Artifact output : getOutputs()) { - if (CppFileTypes.COVERAGE_NOTES.matches(output.getFilename()) // ".gcno" + if (output.isFileType(CppFileTypes.COVERAGE_NOTES) // ".gcno" && !output.getPath().exists()) { try { FileSystemUtils.createEmptyFile(output.getPath()); @@ -1281,8 +1281,8 @@ public class CppCompileAction extends AbstractAction @Override public String getMnemonic() { - if (CppFileTypes.OBJC_SOURCE.matches(sourceFile.getExecPath()) - || CppFileTypes.OBJCPP_SOURCE.matches(sourceFile.getExecPath())) { + if (sourceFile.isFileType(CppFileTypes.OBJC_SOURCE) + || sourceFile.isFileType(CppFileTypes.OBJCPP_SOURCE)) { return "ObjcCompile"; } else { return "CppCompile"; diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppFileTypes.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppFileTypes.java index c63f7d8e00..0c7fa7d795 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppFileTypes.java +++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppFileTypes.java @@ -42,44 +42,52 @@ public final class CppFileTypes { public static final FileType CPP_TEXTUAL_INCLUDE = FileType.of(".inc"); public static final FileType PIC_PREPROCESSED_C = FileType.of(".pic.i"); - public static final FileType PREPROCESSED_C = new FileType() { - final String ext = ".i"; - @Override - public boolean apply(String filename) { - return filename.endsWith(ext) && !PIC_PREPROCESSED_C.matches(filename); - } - @Override - public List<String> getExtensions() { - return ImmutableList.of(ext); - } - }; + public static final FileType PREPROCESSED_C = + new FileType() { + final String ext = ".i"; + + @Override + public boolean apply(String path) { + return path.endsWith(ext) && !PIC_PREPROCESSED_C.matches(path); + } + + @Override + public List<String> getExtensions() { + return ImmutableList.of(ext); + } + }; public static final FileType PIC_PREPROCESSED_CPP = FileType.of(".pic.ii"); - public static final FileType PREPROCESSED_CPP = new FileType() { - final String ext = ".ii"; - @Override - public boolean apply(String filename) { - return filename.endsWith(ext) && !PIC_PREPROCESSED_CPP.matches(filename); - } - @Override - public List<String> getExtensions() { - return ImmutableList.of(ext); - } - }; + public static final FileType PREPROCESSED_CPP = + new FileType() { + final String ext = ".ii"; + + @Override + public boolean apply(String path) { + return path.endsWith(ext) && !PIC_PREPROCESSED_CPP.matches(path); + } + + @Override + public List<String> getExtensions() { + return ImmutableList.of(ext); + } + }; public static final FileType ASSEMBLER_WITH_C_PREPROCESSOR = FileType.of(".S"); public static final FileType PIC_ASSEMBLER = FileType.of(".pic.s"); - public static final FileType ASSEMBLER = new FileType() { - final String ext = ".s"; - @Override - public boolean apply(String filename) { - return (filename.endsWith(ext) && !PIC_ASSEMBLER.matches(filename)) - || filename.endsWith(".asm"); - } - @Override - public List<String> getExtensions() { - return ImmutableList.of(ext, ".asm"); - } - }; + public static final FileType ASSEMBLER = + new FileType() { + final String ext = ".s"; + + @Override + public boolean apply(String path) { + return (path.endsWith(ext) && !PIC_ASSEMBLER.matches(path)) || path.endsWith(".asm"); + } + + @Override + public List<String> getExtensions() { + return ImmutableList.of(ext, ".asm"); + } + }; public static final FileType PIC_ARCHIVE = FileType.of(".pic.a"); public static final FileType ARCHIVE = @@ -87,9 +95,9 @@ public final class CppFileTypes { final List<String> extensions = ImmutableList.of(".a", ".lib"); @Override - public boolean apply(String filename) { + public boolean apply(String path) { for (String ext : extensions) { - if (filename.endsWith(ext) && !PIC_ARCHIVE.matches(filename)) { + if (path.endsWith(ext) && !PIC_ARCHIVE.matches(path)) { return true; } } @@ -103,30 +111,36 @@ public final class CppFileTypes { }; public static final FileType ALWAYS_LINK_PIC_LIBRARY = FileType.of(".pic.lo"); - public static final FileType ALWAYS_LINK_LIBRARY = new FileType() { - final String ext = ".lo"; - @Override - public boolean apply(String filename) { - return filename.endsWith(ext) && !ALWAYS_LINK_PIC_LIBRARY.matches(filename); - } - @Override - public List<String> getExtensions() { - return ImmutableList.of(ext); - } - }; + public static final FileType ALWAYS_LINK_LIBRARY = + new FileType() { + final String ext = ".lo"; + + @Override + public boolean apply(String path) { + return path.endsWith(ext) && !ALWAYS_LINK_PIC_LIBRARY.matches(path); + } + + @Override + public List<String> getExtensions() { + return ImmutableList.of(ext); + } + }; public static final FileType PIC_OBJECT_FILE = FileType.of(".pic.o"); - public static final FileType OBJECT_FILE = new FileType() { - final String ext = ".o"; - @Override - public boolean apply(String filename) { - return filename.endsWith(ext) && !PIC_OBJECT_FILE.matches(filename); - } - @Override - public List<String> getExtensions() { - return ImmutableList.of(ext); - } - }; + public static final FileType OBJECT_FILE = + new FileType() { + final String ext = ".o"; + + @Override + public boolean apply(String path) { + return path.endsWith(ext) && !PIC_OBJECT_FILE.matches(path); + } + + @Override + public List<String> getExtensions() { + return ImmutableList.of(ext); + } + }; // Minimized bitcode file emitted by the ThinLTO compile step and used just for LTO indexing. public static final FileType LTO_INDEXING_OBJECT_FILE = FileType.of(".indexing.o"); @@ -144,19 +158,20 @@ public final class CppFileTypes { // libmylib.so.2 or libmylib.so.2.10. private static final Pattern VERSIONED_SHARED_LIBRARY_PATTERN = Pattern.compile("^.+\\.so(\\.\\d+)+$"); - public static final FileType VERSIONED_SHARED_LIBRARY = new FileType() { - @Override - public boolean apply(String filename) { - // Because regex matching can be slow, we first do a quick digit check on the final - // character before risking the full-on regex match. This should eliminate the performance - // hit on practically every non-qualifying file type. - if (Character.isDigit(filename.charAt(filename.length() - 1))) { - return VERSIONED_SHARED_LIBRARY_PATTERN.matcher(filename).matches(); - } else { - return false; + public static final FileType VERSIONED_SHARED_LIBRARY = + new FileType() { + @Override + public boolean apply(String path) { + // Because regex matching can be slow, we first do a quick digit check on the final + // character before risking the full-on regex match. This should eliminate the performance + // hit on practically every non-qualifying file type. + if (Character.isDigit(path.charAt(path.length() - 1))) { + return VERSIONED_SHARED_LIBRARY_PATTERN.matcher(path).matches(); + } else { + return false; + } } - } - }; + }; public static final FileType COVERAGE_NOTES = FileType.of(".gcno"); public static final FileType COVERAGE_DATA = FileType.of(".gcda"); diff --git a/src/main/java/com/google/devtools/build/lib/util/FileType.java b/src/main/java/com/google/devtools/build/lib/util/FileType.java index b4cc4b6bc3..e2f9eb390a 100644 --- a/src/main/java/com/google/devtools/build/lib/util/FileType.java +++ b/src/main/java/com/google/devtools/build/lib/util/FileType.java @@ -19,8 +19,6 @@ import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; -import com.google.devtools.build.lib.vfs.Path; -import com.google.devtools.build.lib.vfs.PathFragment; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -32,19 +30,22 @@ import javax.annotation.concurrent.Immutable; @Immutable public abstract class FileType implements Predicate<String> { // A special file type - public static final FileType NO_EXTENSION = new FileType() { - @Override - public boolean apply(String filename) { - return filename.lastIndexOf('.') == -1; - } - }; + public static final FileType NO_EXTENSION = + new FileType() { + @Override + public boolean apply(String path) { + int lastSlashIndex = path.lastIndexOf('/'); + return path.indexOf('.', lastSlashIndex + 1) == -1; + } + }; public static FileType of(final String ext) { return new FileType() { @Override - public boolean apply(String filename) { - return filename.endsWith(ext); + public boolean apply(String path) { + return path.endsWith(ext); } + @Override public List<String> getExtensions() { return ImmutableList.of(ext); @@ -55,14 +56,15 @@ public abstract class FileType implements Predicate<String> { public static FileType of(final Iterable<String> extensions) { return new FileType() { @Override - public boolean apply(String filename) { + public boolean apply(String path) { for (String ext : extensions) { - if (filename.endsWith(ext)) { + if (path.endsWith(ext)) { return true; } } return false; } + @Override public List<String> getExtensions() { return ImmutableList.copyOf(extensions); @@ -79,12 +81,9 @@ public abstract class FileType implements Predicate<String> { return getExtensions().toString(); } - /** - * Returns true if the the filename matches. The filename should be a basename (the filename - * component without a path) for performance reasons. - */ + /** Returns true if the file matches. Subclasses are expected to handle a full path. */ @Override - public abstract boolean apply(String filename); + public abstract boolean apply(String path); /** * Get a list of filename extensions this matcher handles. The first entry in the list (if @@ -97,35 +96,29 @@ public abstract class FileType implements Predicate<String> { return ImmutableList.of(); } - /** Return true if a file name is matched by the FileType */ - public boolean matches(String filename) { - int slashIndex = filename.lastIndexOf('/'); - if (slashIndex != -1) { - filename = filename.substring(slashIndex + 1); - } - return apply(filename); - } - - /** Return true if a file referred by path is matched by the FileType */ - public boolean matches(Path path) { - return apply(path.getBaseName()); + /** Return true if a file path is matched by this FileType */ + @Deprecated + public boolean matches(String path) { + return apply(path); } - /** Return true if a file referred by fragment is matched by the FileType */ - public boolean matches(PathFragment fragment) { - return apply(fragment.getBaseName()); + /** Return true if the item is matched by this FileType */ + public boolean matches(HasFileType item) { + return apply(item.filePathForFileTypeMatcher()); } // Check FileTypes - /** - * An interface for entities that have a filename. - */ - public interface HasFilename { + /** An interface for entities that have a file type. */ + public interface HasFileType { /** - * Returns the filename of this entity. + * Return a file path that ends with the file name. + * + * <p>The path will be used by {@link FileType} for matching. An example valid implementation + * could return the full path of the file, or just the file name, depending on what can + * efficiently be provided. */ - String getFilename(); + String filePathForFileTypeMatcher(); } /** @@ -133,12 +126,12 @@ public abstract class FileType implements Predicate<String> { * * <p>At least one FileType must be specified. */ - public static <T extends HasFilename> boolean contains(final Iterable<T> items, - FileType... fileTypes) { + public static <T extends HasFileType> boolean contains( + final Iterable<T> items, FileType... fileTypes) { Preconditions.checkState(fileTypes.length > 0, "Must specify at least one file type"); final FileTypeSet fileTypeSet = FileTypeSet.of(fileTypes); for (T item : items) { - if (fileTypeSet.matches(item.getFilename())) { + if (fileTypeSet.matches(item.filePathForFileTypeMatcher())) { return true; } } @@ -150,32 +143,31 @@ public abstract class FileType implements Predicate<String> { * * <p>At least one FileType must be specified. */ - public static <T extends HasFilename> boolean contains(T item, FileType... fileTypes) { - return FileTypeSet.of(fileTypes).matches(item.getFilename()); + public static <T extends HasFileType> boolean contains(T item, FileType... fileTypes) { + return FileTypeSet.of(fileTypes).matches(item.filePathForFileTypeMatcher()); } - - private static <T extends HasFilename> Predicate<T> typeMatchingPredicateFor( + private static <T extends HasFileType> Predicate<T> typeMatchingPredicateFor( final FileType matchingType) { - return item -> matchingType.matches(item.getFilename()); + return item -> matchingType.matches(item.filePathForFileTypeMatcher()); } - private static <T extends HasFilename> Predicate<T> typeMatchingPredicateFor( + private static <T extends HasFileType> Predicate<T> typeMatchingPredicateFor( final FileTypeSet matchingTypes) { - return item -> matchingTypes.matches(item.getFilename()); + return item -> matchingTypes.matches(item.filePathForFileTypeMatcher()); } - private static <T extends HasFilename> Predicate<T> typeMatchingPredicateFrom( + private static <T extends HasFileType> Predicate<T> typeMatchingPredicateFrom( final Predicate<String> fileTypePredicate) { - return item -> fileTypePredicate.apply(item.getFilename()); + return item -> fileTypePredicate.apply(item.filePathForFileTypeMatcher()); } /** * A filter for Iterable<? extends HasFileType> that returns only those whose FileType matches the * specified Predicate. */ - public static <T extends HasFilename> Iterable<T> filter(final Iterable<T> items, - final Predicate<String> predicate) { + public static <T extends HasFileType> Iterable<T> filter( + final Iterable<T> items, final Predicate<String> predicate) { return Iterables.filter(items, typeMatchingPredicateFrom(predicate)); } @@ -183,8 +175,8 @@ public abstract class FileType implements Predicate<String> { * A filter for Iterable<? extends HasFileType> that returns only those of the specified file * types. */ - public static <T extends HasFilename> Iterable<T> filter(final Iterable<T> items, - FileType... fileTypes) { + public static <T extends HasFileType> Iterable<T> filter( + final Iterable<T> items, FileType... fileTypes) { return filter(items, FileTypeSet.of(fileTypes)); } @@ -192,8 +184,8 @@ public abstract class FileType implements Predicate<String> { * A filter for Iterable<? extends HasFileType> that returns only those of the specified file * types. */ - public static <T extends HasFilename> Iterable<T> filter(final Iterable<T> items, - FileTypeSet fileTypes) { + public static <T extends HasFileType> Iterable<T> filter( + final Iterable<T> items, FileTypeSet fileTypes) { return Iterables.filter(items, typeMatchingPredicateFor(fileTypes)); } @@ -201,8 +193,8 @@ public abstract class FileType implements Predicate<String> { * A filter for Iterable<? extends HasFileType> that returns only those of the specified file * type. */ - public static <T extends HasFilename> Iterable<T> filter(final Iterable<T> items, - FileType fileType) { + public static <T extends HasFileType> Iterable<T> filter( + final Iterable<T> items, FileType fileType) { return Iterables.filter(items, typeMatchingPredicateFor(fileType)); } @@ -210,18 +202,17 @@ public abstract class FileType implements Predicate<String> { * A filter for Iterable<? extends HasFileType> that returns everything except the specified file * type. */ - public static <T extends HasFilename> Iterable<T> except(final Iterable<T> items, - FileType fileType) { + public static <T extends HasFileType> Iterable<T> except( + final Iterable<T> items, FileType fileType) { return Iterables.filter(items, Predicates.not(typeMatchingPredicateFor(fileType))); } - /** * A filter for List<? extends HasFileType> that returns only those of the specified file types. * The result is a mutable list, computed eagerly; see {@link #filter} for a lazy variant. */ - public static <T extends HasFilename> List<T> filterList(final Iterable<T> items, - FileType... fileTypes) { + public static <T extends HasFileType> List<T> filterList( + final Iterable<T> items, FileType... fileTypes) { if (fileTypes.length > 0) { return filterList(items, FileTypeSet.of(fileTypes)); } else { @@ -233,11 +224,11 @@ public abstract class FileType implements Predicate<String> { * A filter for List<? extends HasFileType> that returns only those of the specified file type. * The result is a mutable list, computed eagerly. */ - public static <T extends HasFilename> List<T> filterList(final Iterable<T> items, - final FileType fileType) { + public static <T extends HasFileType> List<T> filterList( + final Iterable<T> items, final FileType fileType) { List<T> result = new ArrayList<>(); for (T item : items) { - if (fileType.matches(item.getFilename())) { + if (fileType.matches(item.filePathForFileTypeMatcher())) { result.add(item); } } @@ -248,11 +239,11 @@ public abstract class FileType implements Predicate<String> { * A filter for List<? extends HasFileType> that returns only those of the specified file types. * The result is a mutable list, computed eagerly. */ - public static <T extends HasFilename> List<T> filterList(final Iterable<T> items, - final FileTypeSet fileTypeSet) { + public static <T extends HasFileType> List<T> filterList( + final Iterable<T> items, final FileTypeSet fileTypeSet) { List<T> result = new ArrayList<>(); for (T item : items) { - if (fileTypeSet.matches(item.getFilename())) { + if (fileTypeSet.matches(item.filePathForFileTypeMatcher())) { result.add(item); } } diff --git a/src/main/java/com/google/devtools/build/lib/util/FileTypeSet.java b/src/main/java/com/google/devtools/build/lib/util/FileTypeSet.java index d708526387..9b3b2e46fe 100644 --- a/src/main/java/com/google/devtools/build/lib/util/FileTypeSet.java +++ b/src/main/java/com/google/devtools/build/lib/util/FileTypeSet.java @@ -17,11 +17,9 @@ import com.google.common.base.Predicate; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; - import java.util.ArrayList; import java.util.Arrays; import java.util.List; - import javax.annotation.concurrent.Immutable; /** @@ -38,23 +36,26 @@ public class FileTypeSet implements Predicate<String> { public String toString() { return "any files"; } + @Override public boolean matches(String filename) { return true; } + @Override public List<String> getExtensions() { - return ImmutableList.<String>of(); + return ImmutableList.of(); } }; /** A predicate that matches no files. */ public static final FileTypeSet NO_FILE = - new FileTypeSet(ImmutableList.<FileType>of()) { + new FileTypeSet(ImmutableList.of()) { @Override public String toString() { return "no files"; } + @Override public boolean matches(String filename) { return false; @@ -105,13 +106,9 @@ public class FileTypeSet implements Predicate<String> { } /** Returns true if the filename can be matched by any FileType in this set. */ - public boolean matches(String filename) { - int slashIndex = filename.lastIndexOf('/'); - if (slashIndex != -1) { - filename = filename.substring(slashIndex + 1); - } + public boolean matches(String path) { for (FileType type : types) { - if (type.apply(filename)) { + if (type.apply(path)) { return true; } } @@ -124,8 +121,8 @@ public class FileTypeSet implements Predicate<String> { } @Override - public boolean apply(String filename) { - return matches(filename); + public boolean apply(String path) { + return matches(path); } /** Returns the list of possible file extensions for this file type. Can be empty. */ diff --git a/src/main/java/com/google/devtools/build/lib/vfs/BUILD b/src/main/java/com/google/devtools/build/lib/vfs/BUILD index 88c7c86490..f6d2eb427b 100644 --- a/src/main/java/com/google/devtools/build/lib/vfs/BUILD +++ b/src/main/java/com/google/devtools/build/lib/vfs/BUILD @@ -19,6 +19,7 @@ java_library( srcs = PATH_FRAGMENT_SOURCES, deps = [ "//src/main/java/com/google/devtools/build/lib:base-util", + "//src/main/java/com/google/devtools/build/lib:filetype", "//src/main/java/com/google/devtools/build/lib:os_util", "//src/main/java/com/google/devtools/build/lib:skylarkinterface", "//src/main/java/com/google/devtools/build/lib/concurrent", @@ -44,6 +45,7 @@ java_library( deps = [ ":pathfragment", "//src/main/java/com/google/devtools/build/lib:base-util", + "//src/main/java/com/google/devtools/build/lib:filetype", "//src/main/java/com/google/devtools/build/lib:os_util", "//src/main/java/com/google/devtools/build/lib:skylarkinterface", "//src/main/java/com/google/devtools/build/lib/clock", diff --git a/src/main/java/com/google/devtools/build/lib/vfs/Path.java b/src/main/java/com/google/devtools/build/lib/vfs/Path.java index 06a884b4a9..1d6c57c730 100644 --- a/src/main/java/com/google/devtools/build/lib/vfs/Path.java +++ b/src/main/java/com/google/devtools/build/lib/vfs/Path.java @@ -18,6 +18,7 @@ import com.google.common.base.Predicate; import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe; import com.google.devtools.build.lib.skylarkinterface.SkylarkPrintable; import com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter; +import com.google.devtools.build.lib.util.FileType; import com.google.devtools.build.lib.util.StringCanonicalizer; import com.google.devtools.build.lib.vfs.FileSystem.HashFunction; import java.io.File; @@ -52,7 +53,8 @@ import java.util.Objects; * <p>{@link FileSystem} implementations maintain pointers into this graph. */ @ThreadSafe -public class Path implements Comparable<Path>, Serializable, SkylarkPrintable { +public class Path + implements Comparable<Path>, Serializable, SkylarkPrintable, FileType.HasFileType { /** Filesystem-specific factory for {@link Path} objects. */ public static interface PathFactory { @@ -428,6 +430,11 @@ public class Path implements Comparable<Path>, Serializable, SkylarkPrintable { printer.append(getPathString()); } + @Override + public String filePathForFileTypeMatcher() { + return name; + } + /** * Returns the path as a string. */ diff --git a/src/main/java/com/google/devtools/build/lib/vfs/PathFragment.java b/src/main/java/com/google/devtools/build/lib/vfs/PathFragment.java index 05e4ee86c8..6f6dfbf819 100644 --- a/src/main/java/com/google/devtools/build/lib/vfs/PathFragment.java +++ b/src/main/java/com/google/devtools/build/lib/vfs/PathFragment.java @@ -26,6 +26,7 @@ import com.google.devtools.build.lib.skyframe.serialization.SerializationExcepti import com.google.devtools.build.lib.skyframe.serialization.strings.StringCodecs; import com.google.devtools.build.lib.skylarkinterface.SkylarkPrintable; import com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter; +import com.google.devtools.build.lib.util.FileType; import com.google.devtools.build.lib.util.OS; import com.google.devtools.build.lib.util.StringCanonicalizer; import com.google.protobuf.CodedInputStream; @@ -55,7 +56,7 @@ import java.util.Set; @javax.annotation.concurrent.Immutable @ThreadSafe public abstract class PathFragment - implements Comparable<PathFragment>, Serializable, SkylarkPrintable { + implements Comparable<PathFragment>, Serializable, SkylarkPrintable, FileType.HasFileType { private static final Helper HELPER = OS.getCurrent() == OS.WINDOWS ? WindowsPathFragment.HELPER : UnixPathFragment.HELPER; @@ -751,6 +752,11 @@ public abstract class PathFragment printer.append(getPathString()); } + @Override + public String filePathForFileTypeMatcher() { + return getBaseName(); + } + private static class PathFragmentCodec implements ObjectCodec<PathFragment> { private final ObjectCodec<String> stringCodec = StringCodecs.asciiOptimized(); |