diff --git a/src/main/java/org/apache/maven/buildcache/checksum/MavenProjectInput.java b/src/main/java/org/apache/maven/buildcache/checksum/MavenProjectInput.java index a0b1c41a..8dacf334 100644 --- a/src/main/java/org/apache/maven/buildcache/checksum/MavenProjectInput.java +++ b/src/main/java/org/apache/maven/buildcache/checksum/MavenProjectInput.java @@ -58,16 +58,15 @@ import org.apache.maven.buildcache.RemoteCacheRepository; import org.apache.maven.buildcache.ScanConfigProperties; import org.apache.maven.buildcache.Xpp3DomUtils; +import org.apache.maven.buildcache.checksum.exclude.ExclusionResolver; import org.apache.maven.buildcache.hash.HashAlgorithm; import org.apache.maven.buildcache.hash.HashChecksum; import org.apache.maven.buildcache.xml.CacheConfig; import org.apache.maven.buildcache.xml.DtoUtils; import org.apache.maven.buildcache.xml.build.DigestItem; import org.apache.maven.buildcache.xml.build.ProjectsInputInfo; -import org.apache.maven.buildcache.xml.config.Exclude; import org.apache.maven.buildcache.xml.config.Include; import org.apache.maven.execution.MavenSession; -import org.apache.maven.model.Build; import org.apache.maven.model.Dependency; import org.apache.maven.model.Model; import org.apache.maven.model.Plugin; @@ -113,23 +112,10 @@ public class MavenProjectInput { * accepted */ private static final String CACHE_INPUT_NAME = "maven.build.cache.input"; - /** - * property name prefix to exclude files from input. smth like maven.build.cache.exclude.1 should be set in project - * props - */ - private static final String CACHE_EXCLUDE_NAME = "maven.build.cache.exclude"; /** * Flag to control if we should check values from plugin configs as file system objects */ private static final String CACHE_PROCESS_PLUGINS = "maven.build.cache.processPlugins"; - /** - * Glob prefix for path matchers - */ - private static final String GLOB_PX = "glob:"; - /** - * Glob suffix meaning "all files in this directory or any sub-directory" - */ - private static final String GLOB_SX_FULL_SUB_TREE = "/**"; private static final Logger LOGGER = LoggerFactory.getLogger(MavenProjectInput.class); @@ -139,16 +125,6 @@ public class MavenProjectInput { private final RepositorySystem repoSystem; private final CacheConfig config; private final PathIgnoringCaseComparator fileComparator; - /** - * A path matcher used only on directories - */ - private final List inputExcludeDirectoryPathMatchers = new ArrayList<>(); - /** - * A path matcher used on files and directories - */ - private final List inputExcludePathMatchers = new ArrayList<>(); - - private final List inputExcludePathMatcherString = new ArrayList<>(); private final NormalizedModelProvider normalizedModelProvider; private final MultiModuleSupport multiModuleSupport; private final ProjectInputCalculator projectInputCalculator; @@ -157,10 +133,8 @@ public class MavenProjectInput { * The project glob to use every time there is no override */ private final String projectGlob; - /** - * The glob representing the base directory - */ - private final String baseDirectoryGlobPrefix; + + private final ExclusionResolver exclusionResolver; private final boolean processPlugins; private final String tmpDir; @@ -190,96 +164,11 @@ public MavenProjectInput( Boolean.parseBoolean(properties.getProperty(CACHE_PROCESS_PLUGINS, config.isProcessPlugins())); this.tmpDir = System.getProperty("java.io.tmpdir"); - this.baseDirectoryGlobPrefix = baseDirPath.toString().replace("\\", "/") + "/"; - - addDefaultExcludeSection(project); - - List excludes = config.getGlobalExcludePaths(); - for (Exclude excludePath : excludes) { - addToExcludedSection(excludePath.getValue(), true); - } - - for (String propertyName : properties.stringPropertyNames()) { - if (propertyName.startsWith(CACHE_EXCLUDE_NAME)) { - String propertyValue = properties.getProperty(propertyName); - addToExcludedSection(propertyValue, true); - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug( - "Adding an excludePath from property '{}', value is '{}'", propertyName, propertyValue); - } - } - } - CacheUtils.debugPrintCollection( - LOGGER, inputExcludePathMatcherString, "List of excluded glob patterns", "Pattern"); + this.exclusionResolver = new ExclusionResolver(project, config); this.fileComparator = new PathIgnoringCaseComparator(); } - private void addDefaultExcludeSection(MavenProject project) { - - Build build = project.getBuild(); - Path buildDirectoryPath = normalizedPath(build.getDirectory()); - Path outputDirectoryPath = normalizedPath(build.getOutputDirectory()); - Path testOutputDirectoryPath = normalizedPath(build.getTestOutputDirectory()); - addToExcludedSection( - convertToPathMatcherFileSeperator(buildDirectoryPath.toString()) + GLOB_SX_FULL_SUB_TREE, - false); // target by default - - if (!outputDirectoryPath.startsWith(buildDirectoryPath)) { - addToExcludedSection( - convertToPathMatcherFileSeperator(outputDirectoryPath.toString()) + GLOB_SX_FULL_SUB_TREE, - false); // target/classes by default - } - if (!testOutputDirectoryPath.startsWith(buildDirectoryPath)) { - addToExcludedSection( - convertToPathMatcherFileSeperator(testOutputDirectoryPath.toString()) + GLOB_SX_FULL_SUB_TREE, - false); // target/test-classes by default - } - } - - private String convertToPathMatcherFileSeperator(String path) { - return path.replace("\\", "/"); - } - - /** - * Add a value from the excluded section list to the directories and/or the filenames ban list. - * @param excludedValue a value from the exclude list - */ - private void addToExcludedSection(String excludedValue, boolean addProjectBaseDir) { - - String pathMatcherGlob = GLOB_PX - + - // Add the base directory to any input directly coming from user configuration - (addProjectBaseDir ? baseDirectoryGlobPrefix : "") - + - // If the glob start with "/", we remove it since it's already added in the added basedir glob - (excludedValue.startsWith("/") ? excludedValue.substring(1) : excludedValue); - - // In order to skip unnecessary subtree dir walking, we use a different PathMatcher list for "directories" or - // "files + directories" - inputExcludePathMatchers.add(new TreeWalkerPathMatcher(pathMatcherGlob, false)); - - // Globs ending with "**" should end any sub-directory inspection - if (pathMatcherGlob.endsWith("**")) { - inputExcludeDirectoryPathMatchers.add(new TreeWalkerPathMatcher(pathMatcherGlob, true)); - } else { - inputExcludeDirectoryPathMatchers.add(new TreeWalkerPathMatcher(pathMatcherGlob, false)); - } - - // If the pattern ends with "/**", the directory exclude list gets an extra version without this suffix. - // ex : "/src/main/generated" does not match "**/generated/**". But every files in it will match. - // So we will use "**/generated" in the directory matching and avoid checking sub-files one by one - // The original pattern is still needed in case the input inspection starts too low - if (pathMatcherGlob.endsWith(GLOB_SX_FULL_SUB_TREE)) { - inputExcludeDirectoryPathMatchers.add(new TreeWalkerPathMatcher( - pathMatcherGlob.substring(0, (pathMatcherGlob.length() - GLOB_SX_FULL_SUB_TREE.length())), true)); - } - - // The string version is saved for detailed debug log purposes only. - inputExcludePathMatcherString.add(pathMatcherGlob); - } - public ProjectsInputInfo calculateChecksum() throws IOException { final long t0 = System.currentTimeMillis(); @@ -513,17 +402,13 @@ private void startWalk( throw new RuntimeException(e); } } else { - if (!entryMustBeSkipped(normalized)) { + if (!exclusionResolver.excludesPath(normalized)) { LOGGER.debug("Adding: {}", normalized); collectedFiles.add(normalized); } } } - private Path normalizedPath(String directory) { - return Paths.get(directory).normalize(); - } - private void collectFromPlugins(List files, HashSet visitedDirs) { List plugins = project.getBuild().getPlugins(); for (Plugin plugin : plugins) { @@ -578,7 +463,7 @@ public FileVisitResult preVisitDirectory(Path path, BasicFileAttributes basicFil } else if (!isReadable(path)) { LOGGER.debug("Skipping subtree (not readable): {}", path); return FileVisitResult.SKIP_SUBTREE; - } else if (isFilteredOutSubpath(path)) { + } else if (exclusionResolver.excludesPath(path)) { LOGGER.debug("Skipping subtree (blacklisted): {}", path); return FileVisitResult.SKIP_SUBTREE; } else if (visitedDirs.contains(currentDirKey)) { @@ -586,7 +471,7 @@ public FileVisitResult preVisitDirectory(Path path, BasicFileAttributes basicFil return FileVisitResult.SKIP_SUBTREE; } - walkDirectoryFiles(path, collectedFiles, key.getGlob(), entry -> entryMustBeSkipped(entry)); + walkDirectoryFiles(path, collectedFiles, key.getGlob(), entry -> exclusionResolver.excludesPath(entry)); if (!key.isRecursive()) { LOGGER.debug("Skipping subtree (non recursive): {}", path); @@ -605,10 +490,6 @@ public FileVisitResult visitFileFailed(Path path, IOException exc) throws IOExce }); } - private boolean entryMustBeSkipped(Path path) { - return inputExcludePathMatchers.stream().anyMatch(pathMatcher -> pathMatcher.matches(path)); - } - private void addInputsFromPluginConfigs( Object[] configurationChildren, PluginScanConfig scanConfig, @@ -715,10 +596,6 @@ private static boolean isReadable(Path entry) throws IOException { return Files.isReadable(entry); } - private boolean isFilteredOutSubpath(Path path) { - return inputExcludeDirectoryPathMatchers.stream().anyMatch(matcher -> matcher.stopTreeWalking(path)); - } - private SortedMap getMutableDependencies() throws IOException { SortedMap result = new TreeMap<>(); diff --git a/src/main/java/org/apache/maven/buildcache/checksum/TreeWalkerPathMatcher.java b/src/main/java/org/apache/maven/buildcache/checksum/TreeWalkerPathMatcher.java deleted file mode 100644 index 91a32148..00000000 --- a/src/main/java/org/apache/maven/buildcache/checksum/TreeWalkerPathMatcher.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.apache.maven.buildcache.checksum; - -import java.nio.file.FileSystems; -import java.nio.file.Path; -import java.nio.file.PathMatcher; - -/** - * A path matcher with some extra info - */ -public class TreeWalkerPathMatcher implements PathMatcher { - - /** - * True if the matching should stop exploring the directory tree further away - */ - private final boolean matchingSkipSubtree; - - /** - * Wrapped regular path matcher - */ - private final PathMatcher pathMatcher; - - public TreeWalkerPathMatcher(String pathMatcherGlob, boolean matchingSkipSubtree) { - pathMatcher = FileSystems.getDefault().getPathMatcher(pathMatcherGlob); - this.matchingSkipSubtree = matchingSkipSubtree; - } - - @Override - public boolean matches(Path path) { - return pathMatcher.matches(path); - } - - public boolean stopTreeWalking(Path path) { - return matchingSkipSubtree && pathMatcher.matches(path); - } -} diff --git a/src/main/java/org/apache/maven/buildcache/checksum/exclude/Exclusion.java b/src/main/java/org/apache/maven/buildcache/checksum/exclude/Exclusion.java new file mode 100644 index 00000000..f89bc361 --- /dev/null +++ b/src/main/java/org/apache/maven/buildcache/checksum/exclude/Exclusion.java @@ -0,0 +1,157 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.maven.buildcache.checksum.exclude; + +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.nio.file.PathMatcher; +import java.nio.file.Paths; + +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.maven.buildcache.xml.config.Exclude; + +public class Exclusion { + + /** + * Glob prefix for path matchers + */ + private static final String GLOB_PX = "glob:"; + + private static final String GLOB_ALL_PATHS = "**"; + private static final String GLOB_ALL_NAMES = "*"; + + /** + * Default glob + */ + private static final String DEFAULT_GLOB = GLOB_ALL_PATHS; + + private final Path absolutePath; + private final PathMatcher matcher; + + private final MatcherType matcherType; + + private final EntryType entryType; + + /** + * Denormalization to increase pathmatching resolution speed if the glob obviously match all files + */ + private boolean matchesAllNames; + + /** + * Denormalization to increase pathmatching resolution speed if the glob obviously match all paths + */ + private boolean matchesAllPaths; + + /** + * True if the configured value was already an absolute path + */ + private final boolean configuredAsAbsolute; + + public Exclusion(Path basedir, Exclude exclude) { + + if (StringUtils.isNotBlank(exclude.getValue())) { + Path candidate = Paths.get(FilenameUtils.separatorsToSystem(exclude.getValue())); + configuredAsAbsolute = candidate.isAbsolute(); + Path resolvedPath = configuredAsAbsolute ? candidate : basedir.resolve(candidate); + this.absolutePath = resolvedPath.toAbsolutePath().normalize(); + } else { + configuredAsAbsolute = false; + this.absolutePath = basedir; + } + // Unix style glob is correctly interpreted on windows by the corresponding pathMatcher implementation. + String unixStyleGlob = convertGlobToUnixStyle(exclude.getGlob()); + this.matcher = FileSystems.getDefault().getPathMatcher(GLOB_PX + unixStyleGlob); + this.matcherType = MatcherType.valueOf(exclude.getMatcherType().toUpperCase()); + this.entryType = EntryType.valueOf(exclude.getEntryType().toUpperCase()); + computeMatcherDenormalization(unixStyleGlob); + } + + public Exclusion(Path absolutePath, MatcherType resolutionType, EntryType entryType) { + this.configuredAsAbsolute = false; + this.absolutePath = absolutePath; + this.matcher = absolutePath.getFileSystem().getPathMatcher(GLOB_PX + DEFAULT_GLOB); + this.matcherType = resolutionType; + this.entryType = entryType; + computeMatcherDenormalization(DEFAULT_GLOB); + } + + private String convertGlobToUnixStyle(String glob) { + return glob.replace("\\\\", "/"); + } + + private void computeMatcherDenormalization(String glob) { + if (GLOB_ALL_PATHS.equals(glob)) { + matchesAllPaths = true; + } else if (GLOB_ALL_NAMES.equals(glob)) { + matchesAllNames = true; + } + } + + public Path getAbsolutePath() { + return absolutePath; + } + + public EntryType getEntryType() { + return entryType; + } + + /** + * True if the exclusion applies to the given path (does not indicate that the path is excluded) + * @param path a visited path + * @return true if the exclusion applies to the given path + */ + private boolean applies(Path path) { + return path.startsWith(this.absolutePath); + } + + public boolean excludesPath(Path parentPath, Path path) { + if (applies(path)) { + switch (matcherType) { + case FILENAME: + if (matchesAllPaths || matchesAllNames || matcher.matches(path.getFileName())) { + return true; + } + break; + case PATH: + // If path is configured relative, matching has to be done relatively to the project directory in + // order to be independent from + // the project location on the disk. + if (matchesAllPaths || matcher.matches(configuredAsAbsolute ? path : parentPath.relativize(path))) { + return true; + } + break; + default: + throw new RuntimeException("Exclusion resolution type not handled."); + } + } + return false; + } + + public enum MatcherType { + FILENAME, + PATH; + } + + public enum EntryType { + FILE, + DIRECTORY, + ALL; + } +} diff --git a/src/main/java/org/apache/maven/buildcache/checksum/exclude/ExclusionResolver.java b/src/main/java/org/apache/maven/buildcache/checksum/exclude/ExclusionResolver.java new file mode 100644 index 00000000..4ac8ef1c --- /dev/null +++ b/src/main/java/org/apache/maven/buildcache/checksum/exclude/ExclusionResolver.java @@ -0,0 +1,172 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.maven.buildcache.checksum.exclude; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +import org.apache.maven.buildcache.xml.CacheConfig; +import org.apache.maven.buildcache.xml.config.Exclude; +import org.apache.maven.model.Build; +import org.apache.maven.project.MavenProject; + +public class ExclusionResolver { + + /** + * property name prefix to exclude files from input. smth like maven.build.cache.exclude.value.1 should be set in project + * props + */ + private static final String PROJECT_PROPERTY_EXCLUDE_PREFIX = "maven.build.cache.exclude"; + + public static final String PROJECT_PROPERTY_EXCLUDE_VALUE = PROJECT_PROPERTY_EXCLUDE_PREFIX + ".value"; + public static final String PROJECT_PROPERTY_EXCLUDE_GLOB = PROJECT_PROPERTY_EXCLUDE_PREFIX + ".glob"; + public static final String PROJECT_PROPERTY_EXCLUDE_ENTRY_TYPE = PROJECT_PROPERTY_EXCLUDE_PREFIX + ".entryType"; + public static final String PROJECT_PROPERTY_EXCLUDE_MATCHER_TYPE = PROJECT_PROPERTY_EXCLUDE_PREFIX + ".matcherType"; + + /** + * Directories exclusions based on a glob + */ + private final List directoryExclusions = new ArrayList<>(); + /** + * Files exclusions based on a glob + */ + private final List filesExclusions = new ArrayList<>(); + /** + * Direct files exclusions (based on a path targeting a file) + */ + private final Set directFileExclusions = new HashSet<>(); + + private final Path projectBaseDirectory; + + public ExclusionResolver(MavenProject project, CacheConfig config) { + addDefaultExcludes(project); + Path baseDirectory = project.getBasedir().toPath().toAbsolutePath(); + projectBaseDirectory = baseDirectory; + + // Global exclusions + List excludes = config.getGlobalExcludePaths(); + for (Exclude exclude : excludes) { + addExclusion(baseDirectory, exclude); + } + + // Project specific exclusions + Properties properties = project.getProperties(); + Map propertyMap = new HashMap<>(); + for (String propertyName : properties.stringPropertyNames()) { + if (propertyName.startsWith(PROJECT_PROPERTY_EXCLUDE_VALUE)) { + String propertyKey = propertyName.substring(PROJECT_PROPERTY_EXCLUDE_VALUE.length()); + Exclude exclude = propertyMap.computeIfAbsent(propertyKey, key -> new Exclude()); + exclude.setValue(properties.getProperty(propertyName)); + } else if (propertyName.startsWith(PROJECT_PROPERTY_EXCLUDE_GLOB)) { + String propertyKey = propertyName.substring(PROJECT_PROPERTY_EXCLUDE_GLOB.length()); + Exclude exclude = propertyMap.computeIfAbsent(propertyKey, key -> new Exclude()); + exclude.setGlob(properties.getProperty(propertyName)); + } else if (propertyName.startsWith(PROJECT_PROPERTY_EXCLUDE_ENTRY_TYPE)) { + String propertyKey = propertyName.substring(PROJECT_PROPERTY_EXCLUDE_ENTRY_TYPE.length()); + Exclude exclude = propertyMap.computeIfAbsent(propertyKey, key -> new Exclude()); + exclude.setEntryType(properties.getProperty(propertyName)); + } else if (propertyName.startsWith(PROJECT_PROPERTY_EXCLUDE_MATCHER_TYPE)) { + String propertyKey = propertyName.substring(PROJECT_PROPERTY_EXCLUDE_MATCHER_TYPE.length()); + Exclude exclude = propertyMap.computeIfAbsent(propertyKey, key -> new Exclude()); + exclude.setMatcherType(properties.getProperty(propertyName)); + } + } + for (Exclude propertyExclude : propertyMap.values()) { + addExclusion(baseDirectory, propertyExclude); + } + } + + private void addExclusion(Path baseDirectory, Exclude exclude) { + Exclusion exclusion = new Exclusion(baseDirectory, exclude); + + if (!Files.exists(exclusion.getAbsolutePath())) { + // The file does not exist in this module, no time to waste by checking the exclusion while scanning the + // filesystem. + return; + } + + if (Files.isDirectory(exclusion.getAbsolutePath())) { + switch (exclusion.getEntryType()) { + case ALL: + directoryExclusions.add(exclusion); + filesExclusions.add(exclusion); + break; + case FILE: + filesExclusions.add(exclusion); + break; + case DIRECTORY: + directoryExclusions.add(exclusion); + break; + default: + throw new RuntimeException("Exclusion range not handled."); + } + } else { + directFileExclusions.add(exclusion.getAbsolutePath()); + } + } + + private void addDefaultExcludes(MavenProject project) { + Build build = project.getBuild(); + // target by default + Path buildDirectoryPath = absoluteNormalizedPath(build.getDirectory()); + // target/classes by default + Path outputDirectoryPath = absoluteNormalizedPath(build.getOutputDirectory()); + // target/test-classes by default + Path testOutputDirectoryPath = absoluteNormalizedPath(build.getTestOutputDirectory()); + + directoryExclusions.add( + new Exclusion(buildDirectoryPath, Exclusion.MatcherType.FILENAME, Exclusion.EntryType.ALL)); + + if (!outputDirectoryPath.startsWith(buildDirectoryPath)) { + directoryExclusions.add( + new Exclusion(outputDirectoryPath, Exclusion.MatcherType.FILENAME, Exclusion.EntryType.ALL)); + } + if (!testOutputDirectoryPath.startsWith(buildDirectoryPath)) { + directoryExclusions.add( + new Exclusion(testOutputDirectoryPath, Exclusion.MatcherType.FILENAME, Exclusion.EntryType.ALL)); + } + } + + private Path absoluteNormalizedPath(String directory) { + return Paths.get(directory).toAbsolutePath().normalize(); + } + + public boolean excludesPath(Path entryAbsolutePath) { + boolean isDirectory = Files.isDirectory(entryAbsolutePath); + // Check direct files exclusions + if (!isDirectory && directFileExclusions.contains(entryAbsolutePath)) { + return true; + } + List exclusionList = isDirectory ? directoryExclusions : filesExclusions; + for (Exclusion exclusion : exclusionList) { + if (exclusion.excludesPath(projectBaseDirectory, entryAbsolutePath)) { + return true; + } + } + return false; + } +} diff --git a/src/main/mdo/build-cache-config.mdo b/src/main/mdo/build-cache-config.mdo index 22a65de3..f55af5d3 100644 --- a/src/main/mdo/build-cache-config.mdo +++ b/src/main/mdo/build-cache-config.mdo @@ -816,36 +816,63 @@ under the License. Exclude - Paths are relative to each module basedir.
- Remember that "exclude" elements can also be added per project with the use of maven properties.
- More information about glob pattern syntax here. + + When referring to a directory, additional properties help adjust the scope of the exclusion: +
    +
  • glob - only entries matching this glob pattern are excluded
  • +
  • entryType - type of entries excluded from the scan
  • +
  • matcherType - entry property on which the glob matching is applied
  • +
+ By default, targeting a directory excludes everything underneath it.
+
+ Examples : +
    +
  • <exclude>src/main/doc</exclude> : excludes everything under : ${project.basedir}/src/main/doc
  • +
  • <exclude>src/main/java/package-info.java</exclude> : excludes this file : ${project.basedir}/src/main/java/package-info.java
  • +
  • <exclude glob="package-info.java"></exclude> : excludes all the files named package-info.java
  • +
  • <exclude glob="src/main/resources/**.txt" entryType="FILE" matcherType="PATH" ></exclude> : excludes all the text files located under the "resources" folder
  • +
+
+ Exclude elements can also be added per project with the use of maven properties.
+

+ ]]>
value String -
- Examples : -
    -
  • src/main/resources/some-folder/** : excludes all the files located under this path
  • -
  • **node_modules/** : excludes all files under a folder named node_modules placed anywhere in the project
  • -
  • **.{md,txt} : excludes all markdown and text files
  • -
- - ]]>
+ An absolute or relative path to a filename or a directory. In the later case, the exclusion is applied starting from this directory (can be beneficial in term of performances). If empty, the glob is applied on every file listed as a potential part of the checksum. +
+ + glob + String + ** + Entries are filtered by matching this glob (see matcherType property). + + + entryType + String + ALL + Type of entries filtered. One of FILE, DIRECTORY, ALL. Excluding a directory excludes its subfiles and subdirectories. + + + matcherType + String + FILENAME + The entry property matched for exclusion. One of FILENAME, PATH. For a path, if the exclusion value is empty or a relative path, matching is done relatively to the project folder.
Include - Paths are relative to each module basedir.
- Remember that "include" elements can also be added per project with the use of maven properties. - ]]>
+
+ Include elements can also be added per project with the use of maven properties.

+ ]]>
value String - Path is relative to each visited project base directory + Path to add to the checksum computation recursive @@ -856,7 +883,7 @@ under the License. glob String - Type of files to scan in this path. If not set, the global glob value is used. + Files in a directory are filtered by matching their names against this glob.
diff --git a/src/site/markdown/parameters.md b/src/site/markdown/parameters.md index ae7e2243..16269f1a 100644 --- a/src/site/markdown/parameters.md +++ b/src/site/markdown/parameters.md @@ -52,11 +52,11 @@ project properties: ``` -| Parameter | Description | -|---------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `maven.build.cache.input.glob` | Project specific glob to select sources. Overrides the global glob. | -| `maven.build.cache.input` | Additional source code locations. Relative paths calculated from the current project/module root
Example :
```src/main/scala```
```assembly-conf``` | -| `maven.build.cache.exclude` | Paths to exclude from source code search. Relative paths calculated from the current project/module root
Example :
```dist```
```src/main/javagen``` | -| `maven.build.cache.processPlugins` | Introspect plugins to find inputs or not. The default value is true. | -| `maven.build.cache.skipCache` | Skip looking up artifacts for a particular project in caches. The default value is false. | -| `maven.build.cache.restoreGeneratedSources` | Restore generated sources and directly attached files. The default value is true. | +| Parameter | Description | +|---------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `maven.build.cache.input.glob` | Project specific glob to select sources. Overrides the global glob. | +| `maven.build.cache.input` | Additional inputs

Example :
```src/main/scala```
```assembly-conf``` | +| `maven.build.cache.exclude.xxx` | Additional exclusion.

Example :
```src/main/java/package-info.java```
```src/main/resources```
```*.md```
Produce two project exclusions :
```src/main/java/package-info.java```
```src/main/resources``` | +| `maven.build.cache.processPlugins` | Introspect plugins to find inputs or not. The default value is true. | +| `maven.build.cache.skipCache` | Skip looking up artifacts for a particular project in caches. The default value is false. | +| `maven.build.cache.restoreGeneratedSources` | Restore generated sources and directly attached files. The default value is true. | diff --git a/src/test/java/org/apache/maven/buildcache/checksum/InputExclusionTest.java b/src/test/java/org/apache/maven/buildcache/checksum/InputExclusionTest.java new file mode 100644 index 00000000..ec3f2835 --- /dev/null +++ b/src/test/java/org/apache/maven/buildcache/checksum/InputExclusionTest.java @@ -0,0 +1,419 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.maven.buildcache.checksum; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Properties; + +import org.apache.maven.buildcache.checksum.exclude.Exclusion.EntryType; +import org.apache.maven.buildcache.checksum.exclude.Exclusion.MatcherType; +import org.apache.maven.buildcache.checksum.exclude.ExclusionResolver; +import org.apache.maven.buildcache.xml.CacheConfig; +import org.apache.maven.buildcache.xml.config.Exclude; +import org.apache.maven.model.Build; +import org.apache.maven.project.MavenProject; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Mockito; + +public class InputExclusionTest { + + @TempDir + private Path testFolder; + + /** + * Basic folder content exclusion + * @throws IOException + */ + @Test + public void exclusionByFolder() throws IOException { + FsTree fsTree = createFsTree(); + + // Exclude folder 1 + everything inside based on the starting path + ExclusionResolver exclusionResolver = + createExclusionResolver("folder1", "**", EntryType.ALL, MatcherType.FILENAME); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.txtFileRootFolder)); + Assertions.assertTrue(exclusionResolver.excludesPath(fsTree.folder1)); + Assertions.assertTrue(exclusionResolver.excludesPath(fsTree.javaFileFolder1)); + Assertions.assertTrue(exclusionResolver.excludesPath(fsTree.subFolder1)); + Assertions.assertTrue(exclusionResolver.excludesPath(fsTree.txtFileSubFolder1)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.folder2)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.txtFileFolder2)); + + // Exclude everything inside folder 1 based on the glob + exclusionResolver = createExclusionResolver("", "folder1/**", EntryType.ALL, MatcherType.PATH); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.txtFileRootFolder)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.folder1)); + Assertions.assertTrue(exclusionResolver.excludesPath(fsTree.javaFileFolder1)); + Assertions.assertTrue(exclusionResolver.excludesPath(fsTree.subFolder1)); + Assertions.assertTrue(exclusionResolver.excludesPath(fsTree.txtFileSubFolder1)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.folder2)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.txtFileFolder2)); + + // Exclusion on folder + exclusionResolver = createExclusionResolver("", "folder1", EntryType.DIRECTORY, MatcherType.PATH); + Assertions.assertTrue(exclusionResolver.excludesPath(fsTree.folder1)); + exclusionResolver = createExclusionResolver("", "folder1", EntryType.DIRECTORY, MatcherType.FILENAME); + Assertions.assertTrue(exclusionResolver.excludesPath(fsTree.folder1)); + exclusionResolver = createExclusionResolver("", "folder1", EntryType.FILE, MatcherType.FILENAME); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.folder1)); + } + + /** + * Files excluded by extension + * @throws IOException + */ + @Test + public void exclusionByFileExtension() throws IOException { + FsTree fsTree = createFsTree(); + + // Excludes all json files + ExclusionResolver exclusionResolver = + createExclusionResolver("", "*.json", EntryType.FILE, MatcherType.FILENAME); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.javaFileRootFolder)); + Assertions.assertTrue(exclusionResolver.excludesPath(fsTree.jsonFileRootFolder)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.javaFileFolder1)); + Assertions.assertTrue(exclusionResolver.excludesPath(fsTree.jsonFileFolder1)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.javaFileSubFolder1)); + Assertions.assertTrue(exclusionResolver.excludesPath(fsTree.jsonFileSubFolder1)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.javaFileFolder2)); + Assertions.assertTrue(exclusionResolver.excludesPath(fsTree.jsonFileFolder2)); + + // Excludes all json files under folder 1 + exclusionResolver = createExclusionResolver("folder1", "*.json", EntryType.FILE, MatcherType.FILENAME); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.javaFileRootFolder)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.jsonFileRootFolder)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.javaFileFolder1)); + Assertions.assertTrue(exclusionResolver.excludesPath(fsTree.jsonFileFolder1)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.javaFileSubFolder1)); + Assertions.assertTrue(exclusionResolver.excludesPath(fsTree.jsonFileSubFolder1)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.javaFileFolder2)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.jsonFileFolder2)); + } + + /** + * One file exclusion + * @throws IOException + */ + @Test + public void exclusionOfOneSpecificFile() throws IOException { + FsTree fsTree = createFsTree(); + + // Exclude the json file in subfolder 1 + ExclusionResolver exclusionResolver = createExclusionResolver( + "folder1/subfolder1/other-file.json", "**", EntryType.ALL, MatcherType.FILENAME); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.javaFileRootFolder)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.txtFileRootFolder)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.jsonFileRootFolder)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.javaFileFolder1)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.txtFileFolder1)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.jsonFileFolder1)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.javaFileSubFolder1)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.txtFileSubFolder1)); + Assertions.assertTrue(exclusionResolver.excludesPath(fsTree.jsonFileSubFolder1)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.javaFileFolder2)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.txtFileFolder2)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.jsonFileFolder2)); + + // Exclude the json file in subfolder 1 by glob v1 + exclusionResolver = + createExclusionResolver("folder1/subfolder1", "other-file.json", EntryType.ALL, MatcherType.FILENAME); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.javaFileRootFolder)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.txtFileRootFolder)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.jsonFileRootFolder)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.javaFileFolder1)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.txtFileFolder1)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.jsonFileFolder1)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.javaFileSubFolder1)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.txtFileSubFolder1)); + Assertions.assertTrue(exclusionResolver.excludesPath(fsTree.jsonFileSubFolder1)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.javaFileFolder2)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.txtFileFolder2)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.jsonFileFolder2)); + + // Exclude the json file in subfolder 1 by glob v2 + exclusionResolver = + createExclusionResolver("", "folder1/subfolder1/other-file.json", EntryType.ALL, MatcherType.PATH); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.javaFileRootFolder)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.txtFileRootFolder)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.jsonFileRootFolder)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.javaFileFolder1)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.txtFileFolder1)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.jsonFileFolder1)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.javaFileSubFolder1)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.txtFileSubFolder1)); + Assertions.assertTrue(exclusionResolver.excludesPath(fsTree.jsonFileSubFolder1)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.javaFileFolder2)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.txtFileFolder2)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.jsonFileFolder2)); + } + + /** + * One file exclusion with the windows (DOS) path and glob syntax + * @throws IOException + */ + @Test + public void exclusionOfOneSpecificFileWindowsStyle() throws IOException { + FsTree fsTree = createFsTree(); + + // Exclude the json file in subfolder 1 + ExclusionResolver exclusionResolver = createExclusionResolver( + "folder1\\subfolder1\\other-file.json", "**", EntryType.ALL, MatcherType.FILENAME); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.javaFileRootFolder)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.txtFileRootFolder)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.jsonFileRootFolder)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.javaFileFolder1)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.txtFileFolder1)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.jsonFileFolder1)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.javaFileSubFolder1)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.txtFileSubFolder1)); + Assertions.assertTrue(exclusionResolver.excludesPath(fsTree.jsonFileSubFolder1)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.javaFileFolder2)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.txtFileFolder2)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.jsonFileFolder2)); + + // Exclude the json file in subfolder 1 by glob v1 + exclusionResolver = + createExclusionResolver("folder1\\subfolder1", "other-file.json", EntryType.ALL, MatcherType.FILENAME); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.javaFileRootFolder)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.txtFileRootFolder)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.jsonFileRootFolder)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.javaFileFolder1)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.txtFileFolder1)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.jsonFileFolder1)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.javaFileSubFolder1)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.txtFileSubFolder1)); + Assertions.assertTrue(exclusionResolver.excludesPath(fsTree.jsonFileSubFolder1)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.javaFileFolder2)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.txtFileFolder2)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.jsonFileFolder2)); + + // Exclude the json file in subfolder 1 by glob v2 (\ is a meta character in glob syntax + in java syntax, so we + // need to double escape it) + exclusionResolver = createExclusionResolver( + "", "folder1\\\\subfolder1\\\\other-file.json", EntryType.ALL, MatcherType.PATH); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.javaFileRootFolder)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.txtFileRootFolder)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.jsonFileRootFolder)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.javaFileFolder1)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.txtFileFolder1)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.jsonFileFolder1)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.javaFileSubFolder1)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.txtFileSubFolder1)); + Assertions.assertTrue(exclusionResolver.excludesPath(fsTree.jsonFileSubFolder1)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.javaFileFolder2)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.txtFileFolder2)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.jsonFileFolder2)); + } + + /** + * Exclusion by a pattern in the filename + * @throws IOException + */ + @Test + public void exclusionByPatternInFilename() throws IOException { + FsTree fsTree = createFsTree(); + + // Excludes all files containing the string "my-f" in their filename + ExclusionResolver exclusionResolver = + createExclusionResolver("", "*my-f*", EntryType.ALL, MatcherType.FILENAME); + Assertions.assertTrue(exclusionResolver.excludesPath(fsTree.javaFileRootFolder)); + Assertions.assertTrue(exclusionResolver.excludesPath(fsTree.txtFileRootFolder)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.jsonFileRootFolder)); + Assertions.assertTrue(exclusionResolver.excludesPath(fsTree.javaFileFolder1)); + Assertions.assertTrue(exclusionResolver.excludesPath(fsTree.txtFileFolder1)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.jsonFileFolder1)); + Assertions.assertTrue(exclusionResolver.excludesPath(fsTree.javaFileSubFolder1)); + Assertions.assertTrue(exclusionResolver.excludesPath(fsTree.txtFileSubFolder1)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.jsonFileSubFolder1)); + Assertions.assertTrue(exclusionResolver.excludesPath(fsTree.javaFileFolder2)); + Assertions.assertTrue(exclusionResolver.excludesPath(fsTree.txtFileFolder2)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.jsonFileFolder2)); + + // Excludes all files containing the string "my-f" in their path + exclusionResolver = createExclusionResolver("", "**my-f*", EntryType.ALL, MatcherType.PATH); + Assertions.assertTrue(exclusionResolver.excludesPath(fsTree.javaFileRootFolder)); + Assertions.assertTrue(exclusionResolver.excludesPath(fsTree.txtFileRootFolder)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.jsonFileRootFolder)); + Assertions.assertTrue(exclusionResolver.excludesPath(fsTree.javaFileFolder1)); + Assertions.assertTrue(exclusionResolver.excludesPath(fsTree.txtFileFolder1)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.jsonFileFolder1)); + Assertions.assertTrue(exclusionResolver.excludesPath(fsTree.javaFileSubFolder1)); + Assertions.assertTrue(exclusionResolver.excludesPath(fsTree.txtFileSubFolder1)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.jsonFileSubFolder1)); + Assertions.assertTrue(exclusionResolver.excludesPath(fsTree.javaFileFolder2)); + Assertions.assertTrue(exclusionResolver.excludesPath(fsTree.txtFileFolder2)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.jsonFileFolder2)); + } + + /** + * Via project properties, excludes : + * - all files in folder 1 + * - the json file in subfolder 1 + * - txt files everywhere + * @throws IOException + */ + @Test + public void exclusionViaProjectProperties() throws IOException { + FsTree fsTree = createFsTree(); + + MavenProject mavenProject = Mockito.mock(MavenProject.class); + Mockito.when(mavenProject.getBasedir()).thenReturn(testFolder.toFile()); + Build build = Mockito.mock(Build.class); + Mockito.when(build.getDirectory()) + .thenReturn(testFolder.resolve("target").toString()); + Mockito.when(build.getOutputDirectory()) + .thenReturn(testFolder.resolve("target/classes").toString()); + Mockito.when(build.getTestOutputDirectory()) + .thenReturn(testFolder.resolve("target/test-classes").toString()); + Mockito.when(mavenProject.getBuild()).thenReturn(build); + + Properties properties = new Properties(); + // all files in the folder 1 + properties.setProperty(ExclusionResolver.PROJECT_PROPERTY_EXCLUDE_VALUE + ".1", ""); + properties.setProperty(ExclusionResolver.PROJECT_PROPERTY_EXCLUDE_GLOB + ".1", "folder1/*"); + properties.setProperty(ExclusionResolver.PROJECT_PROPERTY_EXCLUDE_ENTRY_TYPE + ".1", EntryType.FILE.toString()); + properties.setProperty( + ExclusionResolver.PROJECT_PROPERTY_EXCLUDE_MATCHER_TYPE + ".1", MatcherType.PATH.toString()); + // json file in subfolder 1 + properties.setProperty(ExclusionResolver.PROJECT_PROPERTY_EXCLUDE_VALUE + ".2", "folder1/subfolder1"); + properties.setProperty(ExclusionResolver.PROJECT_PROPERTY_EXCLUDE_GLOB + ".2", "other-file.json"); + properties.setProperty(ExclusionResolver.PROJECT_PROPERTY_EXCLUDE_ENTRY_TYPE + ".2", EntryType.FILE.toString()); + properties.setProperty( + ExclusionResolver.PROJECT_PROPERTY_EXCLUDE_MATCHER_TYPE + ".2", MatcherType.FILENAME.toString()); + // txt files everywhere + properties.setProperty(ExclusionResolver.PROJECT_PROPERTY_EXCLUDE_GLOB + ".3", "*.txt"); + Mockito.when(mavenProject.getProperties()).thenReturn(properties); + + ExclusionResolver exclusionResolver = createExclusionResolver(mavenProject); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.javaFileRootFolder)); + Assertions.assertTrue(exclusionResolver.excludesPath(fsTree.txtFileRootFolder)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.jsonFileRootFolder)); + Assertions.assertTrue(exclusionResolver.excludesPath(fsTree.javaFileFolder1)); + Assertions.assertTrue(exclusionResolver.excludesPath(fsTree.txtFileFolder1)); + Assertions.assertTrue(exclusionResolver.excludesPath(fsTree.jsonFileFolder1)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.javaFileSubFolder1)); + Assertions.assertTrue(exclusionResolver.excludesPath(fsTree.txtFileSubFolder1)); + Assertions.assertTrue(exclusionResolver.excludesPath(fsTree.jsonFileSubFolder1)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.javaFileFolder2)); + Assertions.assertTrue(exclusionResolver.excludesPath(fsTree.txtFileFolder2)); + Assertions.assertFalse(exclusionResolver.excludesPath(fsTree.jsonFileFolder2)); + } + + private FsTree createFsTree() throws IOException { + FsTree fsTree = new FsTree(); + fsTree.txtFileRootFolder = Files.createFile(testFolder.resolve("my-file.txt")); + fsTree.javaFileRootFolder = Files.createFile(testFolder.resolve("my-file.java")); + fsTree.jsonFileRootFolder = Files.createFile(testFolder.resolve("other-file.json")); + + fsTree.folder1 = Files.createDirectories(testFolder.resolve("folder1")); + fsTree.txtFileFolder1 = Files.createFile(fsTree.folder1.resolve("my-file.txt")); + fsTree.javaFileFolder1 = Files.createFile(fsTree.folder1.resolve("my-file.java")); + fsTree.jsonFileFolder1 = Files.createFile(fsTree.folder1.resolve("other-file.json")); + + fsTree.subFolder1 = Files.createDirectories(fsTree.folder1.resolve("subfolder1")); + fsTree.txtFileSubFolder1 = Files.createFile(fsTree.subFolder1.resolve("my-file.txt")); + fsTree.javaFileSubFolder1 = Files.createFile(fsTree.subFolder1.resolve("my-file.java")); + fsTree.jsonFileSubFolder1 = Files.createFile(fsTree.subFolder1.resolve("other-file.json")); + + fsTree.folder2 = Files.createDirectories(testFolder.resolve("folder2")); + fsTree.txtFileFolder2 = Files.createFile(fsTree.folder2.resolve("my-file.txt")); + fsTree.javaFileFolder2 = Files.createFile(fsTree.folder2.resolve("my-file.java")); + fsTree.jsonFileFolder2 = Files.createFile(fsTree.folder2.resolve("other-file.json")); + return fsTree; + } + + private ExclusionResolver createExclusionResolver(MavenProject mavenProject) { + + CacheConfig cacheConfig = Mockito.mock(CacheConfig.class); + Mockito.when(cacheConfig.getGlobalExcludePaths()).thenReturn(new ArrayList<>()); + ExclusionResolver resolver = new ExclusionResolver(mavenProject, cacheConfig); + return resolver; + } + + private ExclusionResolver createExclusionResolver( + String value, String glob, EntryType entryType, MatcherType matcherType) { + MavenProject mavenProject = Mockito.mock(MavenProject.class); + Mockito.when(mavenProject.getBasedir()).thenReturn(testFolder.toFile()); + Mockito.when(mavenProject.getProperties()).thenReturn(new Properties()); + + Build build = Mockito.mock(Build.class); + Mockito.when(build.getDirectory()) + .thenReturn(testFolder.resolve("target").toString()); + Mockito.when(build.getOutputDirectory()) + .thenReturn(testFolder.resolve("target/classes").toString()); + Mockito.when(build.getTestOutputDirectory()) + .thenReturn(testFolder.resolve("target/test-classes").toString()); + Mockito.when(mavenProject.getBuild()).thenReturn(build); + + Exclude exclude = new Exclude(); + exclude.setValue(value); + exclude.setGlob(glob); + exclude.setEntryType(entryType.toString()); + exclude.setMatcherType(matcherType.toString()); + + CacheConfig cacheConfig = Mockito.mock(CacheConfig.class); + Mockito.when(cacheConfig.getGlobalExcludePaths()).thenReturn(Arrays.asList(exclude)); + ExclusionResolver resolver = new ExclusionResolver(mavenProject, cacheConfig); + return resolver; + } + + /** + * Folders and files with the following hierarchy + * + * root + * - my-file.txt + * - my-file.java + * - other-file.json + * \-- folder 1 + * - my-file.txt + * - my-file.java + * - other-file.json + * \-- subfolder1 + * - my-file.txt + * - my-file.java + * - other-file.json + * \-- folder 2 + * - my-file.txt + * - my-file.java + * - other-file.json + */ + private class FsTree { + public Path txtFileRootFolder; + public Path javaFileRootFolder; + public Path jsonFileRootFolder; + public Path folder1; + public Path txtFileFolder1; + public Path javaFileFolder1; + public Path jsonFileFolder1; + public Path subFolder1; + public Path txtFileSubFolder1; + public Path javaFileSubFolder1; + public Path jsonFileSubFolder1; + public Path folder2; + public Path txtFileFolder2; + public Path javaFileFolder2; + public Path jsonFileFolder2; + } +} diff --git a/src/test/java/org/apache/maven/buildcache/its/IncludeExcludeTest.java b/src/test/java/org/apache/maven/buildcache/its/IncludeExcludeTest.java index e8fabb0d..e34b98fb 100644 --- a/src/test/java/org/apache/maven/buildcache/its/IncludeExcludeTest.java +++ b/src/test/java/org/apache/maven/buildcache/its/IncludeExcludeTest.java @@ -63,7 +63,7 @@ private void verifyLogs(Verifier verifier) throws VerificationException { verifier.verifyErrorFreeLog(); - // Verify that there is a line like "Found 7 input files." in the logs + // Verify that there is a line like "Found x input files." in the logs String foundXFiles = LogFileUtils.findFirstLineContainingTextsInLogs(verifier, "Found ", " input files."); Matcher m = NB_SRC_PATTERN.matcher(foundXFiles); diff --git a/src/test/projects/include-exclude/.mvn/maven-build-cache-config.xml b/src/test/projects/include-exclude/.mvn/maven-build-cache-config.xml index 0199156e..02a15bdd 100644 --- a/src/test/projects/include-exclude/.mvn/maven-build-cache-config.xml +++ b/src/test/projects/include-exclude/.mvn/maven-build-cache-config.xml @@ -25,20 +25,20 @@ under the License. third_folder_outside_src - - src/main/java/org/apache/maven/buildcache/not/** + + src/main/java/org/apache/maven/buildcache/not - **/excluded_by_full_filename.txt - - **/excluded_by_prefix* + + + - folder_outside_src/prefixed_should_NOT* - - /folder_outside_src/another_prefixed** + folder_outside_src + + - **third_folder_outside_src/* + - **/*.{xml,?s} + diff --git a/src/test/projects/include-exclude/folder_outside_src/subfolder2/another_not_scanned_file.js b/src/test/projects/include-exclude/folder_outside_src/subfolder2/another_not_scanned_file.js new file mode 100644 index 00000000..18f74ede --- /dev/null +++ b/src/test/projects/include-exclude/folder_outside_src/subfolder2/another_not_scanned_file.js @@ -0,0 +1 @@ +a_javascript_file content \ No newline at end of file diff --git a/src/test/projects/include-exclude/pom.xml b/src/test/projects/include-exclude/pom.xml index 4d22b820..d23c69a8 100644 --- a/src/test/projects/include-exclude/pom.xml +++ b/src/test/projects/include-exclude/pom.xml @@ -33,9 +33,14 @@ extraFile.txt second_folder_outside_src - **/excluded_subfolder/** + excluded_subfolder + DIRECTORY - folder_outside_src/this_one_should_NOT_be_scanned.txt + folder_outside_src + this_one_should_NOT_be_scanned.txt + + folder_outside_src\subfolder2 + another_not_scanned_file.js