Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Maven CLI: add startWith matching into recipes detection #36578

Merged
merged 1 commit into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,11 +4,14 @@
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;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledOnOs;
import org.junit.jupiter.api.condition.OS;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvFileSource;

Expand All @@ -30,9 +33,69 @@ 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"));
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);
}

@Test
@EnabledOnOs({ OS.WINDOWS })
void testToKeyWindows() {
String key = toKey(Path.of("C:\\a\\d\\"),
Path.of("C:\\a\\b\\quarkus-updates\\org.apache.camel.quarkus.camel-quarkus\\"));
assertEquals("..: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"));
}
}