From 20faa4d1d8f22954c694ff430accc9b0d43c7de1 Mon Sep 17 00:00:00 2001 From: Romain Manni-Bucau Date: Tue, 9 Jun 2020 09:23:23 +0200 Subject: [PATCH] ensure exclusions win over inclusions for scanning using maven shared utils and adding a flag to have a fast scan --- .travis.yml | 12 ++- license-maven-plugin/pom.xml | 7 +- .../plugin/license/AbstractLicenseMojo.java | 4 +- .../maven/plugin/license/util/Selection.java | 58 +++++++++++- .../plugin/license/util/SelectionTest.java | 93 ++++++++++++++++++- 5 files changed, 162 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index c3e84c051..dfbf078e3 100755 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,16 @@ dist: trusty - +sudo: false language: java -script: mvn clean install --quiet -B +before_cache: + - find $HOME/.m2/repository/ -name *SNAPSHOT | xargs rm -Rf + +cache: + timeout: 1000 + directories: + - "$HOME/.m2" + +script: mvn clean install --quiet -B -Dmaven.artifact.threads=64 -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn install: /bin/true diff --git a/license-maven-plugin/pom.xml b/license-maven-plugin/pom.xml index 5271b43d5..b24f16ccd 100755 --- a/license-maven-plugin/pom.xml +++ b/license-maven-plugin/pom.xml @@ -122,7 +122,12 @@ org.apache.maven maven-settings-builder - + + + org.apache.maven.shared + maven-shared-utils + 3.2.1 + org.springframework diff --git a/license-maven-plugin/src/main/java/com/mycila/maven/plugin/license/AbstractLicenseMojo.java b/license-maven-plugin/src/main/java/com/mycila/maven/plugin/license/AbstractLicenseMojo.java index 0cb1a9166..38cfce2c0 100755 --- a/license-maven-plugin/src/main/java/com/mycila/maven/plugin/license/AbstractLicenseMojo.java +++ b/license-maven-plugin/src/main/java/com/mycila/maven/plugin/license/AbstractLicenseMojo.java @@ -601,7 +601,9 @@ private Map mergeProperties(final LicenseSet licenseSet, final D private String[] listSelectedFiles(final LicenseSet licenseSet) { final boolean useDefaultExcludes = (licenseSet.useDefaultExcludes != null ? licenseSet.useDefaultExcludes : defaultUseDefaultExcludes); - final Selection selection = new Selection(firstNonNull(licenseSet.basedir, defaultBasedir), licenseSet.includes, buildExcludes(licenseSet), useDefaultExcludes); + final Selection selection = new Selection( + firstNonNull(licenseSet.basedir, defaultBasedir), licenseSet.includes, buildExcludes(licenseSet), useDefaultExcludes, + getLog()); debug("From: %s", firstNonNull(licenseSet.basedir, defaultBasedir)); debug("Including: %s", deepToString(selection.getIncluded())); debug("Excluding: %s", deepToString(selection.getExcluded())); diff --git a/license-maven-plugin/src/main/java/com/mycila/maven/plugin/license/util/Selection.java b/license-maven-plugin/src/main/java/com/mycila/maven/plugin/license/util/Selection.java index 66486cc3a..e42a7ca5d 100755 --- a/license-maven-plugin/src/main/java/com/mycila/maven/plugin/license/util/Selection.java +++ b/license-maven-plugin/src/main/java/com/mycila/maven/plugin/license/util/Selection.java @@ -16,14 +16,18 @@ package com.mycila.maven.plugin.license.util; import com.mycila.maven.plugin.license.Default; -import org.codehaus.plexus.util.DirectoryScanner; +import org.apache.maven.plugin.logging.Log; +import org.apache.maven.shared.utils.io.DirectoryScanner; +import org.apache.maven.shared.utils.io.MatchPatterns; +import org.apache.maven.shared.utils.io.ScanConductor; import java.io.File; import java.util.ArrayList; -import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.List; +import java.util.concurrent.TimeUnit; -import static java.util.Arrays.*; import static java.util.Arrays.asList; /** @@ -35,13 +39,18 @@ public final class Selection { private final File basedir; private final String[] included; private final String[] excluded; + private final Log log; + private final String[] userExcluded; private DirectoryScanner scanner; - public Selection(File basedir, String[] included, String[] excluded, boolean useDefaultExcludes) { + public Selection(File basedir, String[] included, String[] excluded, boolean useDefaultExcludes, + final Log log) { this.basedir = basedir; + this.log = log; String[] overrides = buildOverrideInclusions(useDefaultExcludes, included); this.included = buildInclusions(included, overrides); + this.userExcluded = excluded; this.excluded = buildExclusions(useDefaultExcludes, excluded, overrides); } @@ -50,6 +59,11 @@ public String[] getSelectedFiles() { return scanner.getIncludedFiles(); } + // for tests + DirectoryScanner getScanner() { + return scanner; + } + public File getBasedir() { return basedir; } @@ -64,7 +78,27 @@ public String[] getExcluded() { private void scanIfneeded() { if (scanner == null) { + final boolean debugEnabled = log.isDebugEnabled(); + final String[] folderExcludes = findFolderExcludes(); + final MatchPatterns excludePatterns = MatchPatterns.from(folderExcludes); + if (debugEnabled) { + log.debug("Starting to visit '" + basedir + "' with excludes: " + asList(folderExcludes)); + } scanner = new DirectoryScanner(); + scanner.setScanConductor(new ScanConductor() { + @Override + public ScanAction visitDirectory(final String name, final File directory) { + if (excludePatterns.matches(name, true)) { + return ScanAction.NO_RECURSE; + } + return ScanAction.CONTINUE; + } + + @Override + public ScanAction visitFile(final String name, final File file) { + return ScanAction.CONTINUE; + } + }); scanner.setBasedir(basedir); scanner.setIncludes(included); scanner.setExcludes(excluded); @@ -72,6 +106,21 @@ private void scanIfneeded() { } } + private String[] findFolderExcludes() { // less we keep, less overhead we get so we only use user excludes there + final List excludes = new ArrayList(excluded.length / 2 /*estimate*/); + for (final String exclude : (userExcluded != null ? userExcluded : excluded)) { + if (isFolderExclusion(exclude)) { + excludes.add(exclude); + } + } + Collections.reverse(excludes); // assume user ones are more important than the set of defaults we appended + return excludes.toArray(new String[0]); + } + + private boolean isFolderExclusion(final String exclude) { + return exclude.endsWith(File.separator + "**"); + } + private static String[] buildExclusions(boolean useDefaultExcludes, String[] excludes, String[] overrides) { List exclusions = new ArrayList(); if (useDefaultExcludes) { @@ -106,5 +155,4 @@ private static String[] buildOverrideInclusions(boolean useDefaultExcludes, Stri overrides.retainAll(asList(includes)); return overrides.toArray(new String[0]); } - } diff --git a/license-maven-plugin/src/test/java/com/mycila/maven/plugin/license/util/SelectionTest.java b/license-maven-plugin/src/test/java/com/mycila/maven/plugin/license/util/SelectionTest.java index 2ed46dc01..288906ac8 100755 --- a/license-maven-plugin/src/test/java/com/mycila/maven/plugin/license/util/SelectionTest.java +++ b/license-maven-plugin/src/test/java/com/mycila/maven/plugin/license/util/SelectionTest.java @@ -16,21 +16,35 @@ package com.mycila.maven.plugin.license.util; import com.mycila.maven.plugin.license.Default; +import org.apache.maven.plugin.logging.Log; +import org.apache.maven.plugin.logging.SystemStreamLog; +import org.apache.maven.shared.utils.io.DirectoryScanner; import org.junit.Test; import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.UUID; +import static java.util.Arrays.asList; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; /** * @author Mathieu Carbou (mathieu.carbou@gmail.com) */ public final class SelectionTest { + private final Log log = new SystemStreamLog(); + @Test public void test_default_select_all() { - Selection selection = new Selection(new File("."), new String[0], new String[0], false); + Selection selection = new Selection(new File("."), new String[0], new String[0], false, log); assertEquals(selection.getExcluded().length, 0); assertEquals(selection.getIncluded().length, 1); assertTrue(selection.getSelectedFiles().length > 0); @@ -38,7 +52,7 @@ public void test_default_select_all() { @Test public void test_limit_inclusion() { - Selection selection = new Selection(new File("."), new String[]{"toto"}, new String[]{"tata"}, false); + Selection selection = new Selection(new File("."), new String[]{"toto"}, new String[]{"tata"}, false, log); assertEquals(selection.getExcluded().length, 1); assertEquals(selection.getIncluded().length, 1); assertEquals(selection.getSelectedFiles().length, 0); @@ -46,10 +60,83 @@ public void test_limit_inclusion() { @Test public void test_limit_inclusion_and_check_default_excludes() { - Selection selection = new Selection(new File("."), new String[]{"toto"}, new String[0], true); + Selection selection = new Selection(new File("."), new String[]{"toto"}, new String[0], true, log); assertEquals(selection.getExcluded().length, Default.EXCLUDES.length); // default exludes from Scanner and Selection + toto assertEquals(selection.getIncluded().length, 1); assertEquals(selection.getSelectedFiles().length, 0); assertTrue(Arrays.asList(selection.getExcluded()).containsAll(Arrays.asList(Default.EXCLUDES))); } + + @Test + public void test_exclusions_respect_with_fastScan() throws IOException { + SystemStreamLog log = new SystemStreamLog() { + @Override + public boolean isDebugEnabled() { + return true; + } + }; + File root = createAFakeProject(log); + Selection selection = new Selection(root, + new String[] { "**" + File.separator + "*.txt" }, + new String[] {"target" + File.separator + "**", "module/**/target" + File.separator + "**"}, false, + log); + + selection.getSelectedFiles(); // triggers scan and scanner build + String debugMessage = buildDebugMessage(selection.getScanner()); + assertIncludedFilesInFakeProject(selection, debugMessage); + assertEquals(debugMessage, 0, selection.getScanner().getExcludedFiles().length); + } + + private String buildDebugMessage(DirectoryScanner scanner) { + return "excludedDirs=" + asList(scanner.getExcludedDirectories()) + ",\n" + + "excludedFiles=" + asList(scanner.getExcludedFiles()) + ",\n" + + "includedDirsFiles=" + asList(scanner.getIncludedDirectories()) + ",\n" + + "includedFiles=" + asList(scanner.getIncludedFiles()) + ",\n" + + "notIncludedDirs=" + asList(scanner.getNotIncludedDirectories()) + ",\n" + + "notIncludedFiles=" + asList(scanner.getNotIncludedFiles()) + ",\n" + + "diskFiles=" + listFiles(scanner.getBasedir(), new ArrayList()); + } + + private Collection listFiles(File basedir, Collection files) { + files.add(basedir); + for (File f : basedir.listFiles()) { + if (f.isDirectory()) { + listFiles(f, files); + } else { + files.add(f); + } + } + return files; + } + + private void assertIncludedFilesInFakeProject(Selection selection, String debugMessage) { + List selected = new ArrayList(asList(selection.getSelectedFiles())); + Collections.sort(selected); + assertEquals(debugMessage, asList("included.txt", "module/src/main/java/not-ignored.txt", "module/sub/subsub/src/main/java/not-ignored.txt"), selected); + } + + private File createAFakeProject(Log log) throws IOException { + File temp = new File("target/workdir_" + UUID.randomUUID().toString()); + touch(new File(temp, "included.txt"), log); + touch(new File(temp, "target/ignored.txt"), log); + touch(new File(temp, "module/src/main/java/not-ignored.txt"), log); + touch(new File(temp, "module/target/ignored.txt"), log); + touch(new File(temp, "module/sub/subsub/src/main/java/not-ignored.txt"), log); + touch(new File(temp, "module/sub/subsub/target/foo/not-ignored.txt"), log); + return temp; + } + + private void touch(File newFile, Log log) throws IOException { + final File parentFile = newFile.getParentFile(); + if (parentFile != null && !parentFile.isDirectory() && !parentFile.mkdirs()) { + fail("Can't create '" + parentFile + "'"); + } + final FileWriter w = new FileWriter(newFile); + w.write("touched"); + w.close(); + if (!newFile.exists()) { + fail("Can't create " + newFile); + } + log.debug("Created '" + newFile.getAbsolutePath() + "'"); + } }