Skip to content

Commit

Permalink
Maven CLI: add wildcard matching into recipes detection
Browse files Browse the repository at this point in the history
  • Loading branch information
JiriOndrusek committed Oct 27, 2023
1 parent d110270 commit 8b7d1cc
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -56,7 +57,7 @@ public static FetchResult fetchRecipes(MessageWriter log, MavenArtifactResolver
final Artifact artifact = artifactResolver.resolve(DependencyUtils.toArtifact(gav)).getArtifact();
final ResourceLoader resourceLoader = ResourceLoaders.resolveFileResourceLoader(
artifact.getFile());
final List<String> recipes = fetchRecipesAsList(resourceLoader, "quarkus-updates", recipeDirectoryNames);
final Map<String, String> recipes = fetchUpdateRecipes(resourceLoader, "quarkus-updates", recipeDirectoryNames);
final Properties props = resourceLoader.loadResourceAsPath("quarkus-updates/", p -> {
final Properties properties = new Properties();
final Path propPath = p.resolve("recipes.properties");
Expand All @@ -78,7 +79,7 @@ public static FetchResult fetchRecipes(MessageWriter log, MavenArtifactResolver
buildTool,
propRewritePluginVersion));
return new FetchResult(artifact.getGroupId() + ":" + artifact.getArtifactId() + ":" + artifact.getVersion(),
recipes, propRewritePluginVersion);
new ArrayList<>(recipes.values()), propRewritePluginVersion);
} catch (BootstrapMavenException e) {
throw new RuntimeException("Failed to resolve artifact: " + gav, e);
} catch (IOException e) {
Expand Down Expand Up @@ -133,38 +134,55 @@ static boolean shouldApplyRecipe(String recipeFileName, String currentVersion, S
return currentAVersion.compareTo(recipeAVersion) < 0 && targetAVersion.compareTo(recipeAVersion) >= 0;
}

static List<String> fetchRecipesAsList(ResourceLoader resourceLoader, String location,
static Map<String, String> fetchUpdateRecipes(ResourceLoader resourceLoader, String location,
Map<String, String[]> recipeDirectoryNames) throws IOException {
return resourceLoader.loadResourceAsPath(location,
path -> {
try (final Stream<Path> pathStream = Files.walk(path)) {
return pathStream
.filter(Files::isDirectory)
.flatMap(dir -> {
String key = toKey(path.relativize(dir).toString());
String versions[] = recipeDirectoryNames.get(key);
if (versions != null && versions.length != 0) {
try {
Stream<Path> recipePath = Files.walk(dir);
return recipePath
.filter(p -> p.getFileName().toString().matches("^\\d\\H+.ya?ml$"))
.filter(p -> shouldApplyRecipe(p.getFileName().toString(),
versions[0], versions[1]))
.map(p -> {
try {
return new String(Files.readAllBytes(p));
} catch (IOException e) {
throw new RuntimeException("Error reading file: " + p, e);
}
})
.onClose(() -> recipePath.close());
} catch (IOException e) {
throw new RuntimeException("Error traversing directory: " + dir, e);
}
}
return null;

}).filter(Objects::nonNull).collect(Collectors.toList());
.flatMap(dir -> applyStartsWith(toKey(path, dir), recipeDirectoryNames).stream()
.flatMap(key -> {
String versions[] = recipeDirectoryNames.get(key);
if (versions != null && versions.length != 0) {
try {
Stream<Path> recipePath = Files.walk(dir);
return recipePath
.filter(p -> p.getFileName().toString()
.matches("^\\d\\H+.ya?ml$"))
.filter(p -> shouldApplyRecipe(p.getFileName().toString(),
versions[0], versions[1]))
.map(p -> {
try {
return new String[] { p.toString(),
new String(Files.readAllBytes(p)) };
} catch (IOException e) {
throw new RuntimeException("Error reading file: " + p,
e);
}
})
.onClose(() -> recipePath.close());
} catch (IOException e) {
throw new RuntimeException("Error traversing directory: " + dir, e);
}
}
return null;
}))
.filter(Objects::nonNull)
//results are collected to the map, because there could be duplicated matches in case of wildcard matching
.collect(Collectors.toMap(
sa -> sa[0],
sa -> sa[1],
(v1, v2) -> {
//Recipe with the same path already loaded. This can happen because of wildcards
//in case the content differs (which can not happen in the current impl),
//content is amended
if (!v1.equals(v2)) {
return v1 + "\n" + v2;
}
return v1;
},
LinkedHashMap::new));
} catch (IOException e) {
throw new RuntimeException("Error traversing base directory", e);
}
Expand All @@ -177,9 +195,20 @@ private static String toKey(ExtensionUpdateInfo dep) {
dep.getCurrentDep().getArtifact().getArtifactId());
}

static String toKey(String directory) {
return directory
static String toKey(Path parentDir, Path recipeDir) {
var _path = parentDir.relativize(recipeDir).toString();
return _path
.replaceAll("(^[/\\\\])|([/\\\\]$)", "")
.replaceAll("[/\\\\]", ":");
}

static List<String> applyStartsWith(String key, Map<String, String[]> recipeDirectoryNames) {
//list for all keys, that matches dir (could be more items in case of wildcard at the end
List<String> matchedRecipeKeys;
//Current implementation detects whether key starts with an existing recipe folder
return recipeDirectoryNames.keySet().stream()
.filter(k -> k.startsWith(key))
.collect(Collectors.toList());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import static org.junit.jupiter.api.Assertions.*;

import java.io.IOException;
import java.nio.file.Path;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
Expand All @@ -30,9 +31,64 @@ void testShouldLoadRecipesFromTheDirectory() throws IOException {
recipeDirectoryNames.put("core", new String[] { "2.7", "3.1" });
recipeDirectoryNames.put("org.apache.camel.quarkus:camel-quarkus-core", new String[] { "2.7", "3.0" });
ClassPathResourceLoader resourceLoader = new ClassPathResourceLoader();
List<String> recipes = fetchRecipesAsList(resourceLoader, "dir/quarkus-update", recipeDirectoryNames);
Map<String, String> recipes = fetchUpdateRecipes(resourceLoader, "dir/quarkus-update", recipeDirectoryNames);
int noOfRecipes = recipes.size();
assertEquals(3, noOfRecipes);
}

@Test
void testShouldLoadRecipesFromTheDirectoryWithWildcard() throws IOException {
Map<String, String[]> recipeDirectoryNames = new LinkedHashMap<>();
recipeDirectoryNames.put("core", new String[] { "2.7", "3.1" });
recipeDirectoryNames.put("org.apache.camel.quarkus:camel-quarkus-file", new String[] { "2.7", "3.0" });
ClassPathResourceLoader resourceLoader = new ClassPathResourceLoader();
Map<String, String> recipes = fetchUpdateRecipes(resourceLoader, "dir/quarkus-update", recipeDirectoryNames);
int noOfRecipes = recipes.size();
assertEquals(3, noOfRecipes);
}

@Test
void testShouldLoadDuplicatedRecipesFromTheDirectoryWithWildcard() throws IOException {
Map<String, String[]> recipeDirectoryNames = new LinkedHashMap<>();
recipeDirectoryNames.put("core", new String[] { "2.7", "3.1" });
recipeDirectoryNames.put("org.apache.camel.quarkus:camel-quarkus-file", new String[] { "2.7", "3.1" });
recipeDirectoryNames.put("org.apache.camel.quarkus:camel-quarkus-ftp", new String[] { "2.7", "3.1" });
recipeDirectoryNames.put("org.apache.camel.quarkus:camel-quarkus-fhir", new String[] { "2.7", "3.1" });
ClassPathResourceLoader resourceLoader = new ClassPathResourceLoader();
Map<String, String> recipes = fetchUpdateRecipes(resourceLoader, "dir/quarkus-update", recipeDirectoryNames);
int noOfRecipes = recipes.size();
assertEquals(3, noOfRecipes);
}

@Test
void testToKey() {
String key = toKey(Path.of("/home/app"),
Path.of("/home/app/target/classes/quarkus-updates/org.apache.camel.quarkus.camel-quarkus-*"));

Check failure on line 66 in independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdatesRepositoryTest.java

View workflow job for this annotation

GitHub Actions / Build summary for 8b7d1cc9b0029239017a3936706ffc74be47e5ef

JVM Tests - JDK 17 Windows

java.nio.file.InvalidPathException: Illegal char <*> at index 80: /home/app/target/classes/quarkus-updates/org.apache.camel.quarkus.camel-quarkus-* at java.base/sun.nio.fs.WindowsPathParser.normalize(WindowsPathParser.java:182) at java.base/sun.nio.fs.WindowsPathParser.parse(WindowsPathParser.java:153)
Raw output
java.nio.file.InvalidPathException: Illegal char <*> at index 80: /home/app/target/classes/quarkus-updates/org.apache.camel.quarkus.camel-quarkus-*
	at java.base/sun.nio.fs.WindowsPathParser.normalize(WindowsPathParser.java:182)
	at java.base/sun.nio.fs.WindowsPathParser.parse(WindowsPathParser.java:153)
	at java.base/sun.nio.fs.WindowsPathParser.parse(WindowsPathParser.java:77)
	at java.base/sun.nio.fs.WindowsPath.parse(WindowsPath.java:92)
	at java.base/sun.nio.fs.WindowsFileSystem.getPath(WindowsFileSystem.java:232)
	at java.base/java.nio.file.Path.of(Path.java:147)
	at io.quarkus.devtools.project.update.rewrite.QuarkusUpdatesRepositoryTest.testToKey(QuarkusUpdatesRepositoryTest.java:66)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
assertEquals("target:classes:quarkus-updates:org.apache.camel.quarkus.camel-quarkus-*", key);

key = toKey(Path.of("/home/second-app"),
Path.of("/home/app/target/classes/quarkus-updates/org.apache.camel.quarkus.camel-quarkus-*"));
assertEquals("..:app:target:classes:quarkus-updates:org.apache.camel.quarkus.camel-quarkus-*", key);

key = toKey(Path.of("C:\\a\\d\\"), Path.of("C:\\a\\b\\quarkus-updates\\org.apache.camel.quarkus.camel-quarkus-*\\"));
assertEquals("..:C::a:b:quarkus-updates:org.apache.camel.quarkus.camel-quarkus-*", key);
}

@Test
void testApplyStartsWith() {
Map<String, String[]> recipeDirectoryNames = new LinkedHashMap<>();
recipeDirectoryNames.put("core", new String[] { "2.7", "3.1" });
recipeDirectoryNames.put("org.apache.camel.quarkus:camel-something1", new String[] { "2.7", "3.1" });
recipeDirectoryNames.put("org.apache.camel.quarkus:camel-quarkus-file", new String[] { "2.7", "3.1" });
recipeDirectoryNames.put("org.apache.camel.quarkus:camel-quarkus-ftp", new String[] { "2.7", "3.1" });
recipeDirectoryNames.put("org.apache.camel.quarkus:camel-quarkus-fhir", new String[] { "2.7", "3.1" });

List<String> matchedKeys = applyStartsWith("org.apache.camel.quarkus:camel-quarkus", recipeDirectoryNames);
assertEquals(3, matchedKeys.size());
assertTrue(!matchedKeys.contains("org.apache.camel.quarkus:camel-quarkus"));

matchedKeys = applyStartsWith("org.apache.camel.quarkus:camel", recipeDirectoryNames);
assertEquals(4, matchedKeys.size());
assertTrue(!matchedKeys.contains("org.apache.camel.quarkus:camel"));
}
}

0 comments on commit 8b7d1cc

Please sign in to comment.