diff --git a/maven-resolver-tools/pom.xml b/maven-resolver-tools/pom.xml index 67a176e34..85725fb65 100644 --- a/maven-resolver-tools/pom.xml +++ b/maven-resolver-tools/pom.xml @@ -33,7 +33,6 @@ 17 - true @@ -41,68 +40,84 @@ org.apache.maven.resolver maven-resolver-api + provided org.apache.maven.resolver maven-resolver-spi + provided org.apache.maven.resolver maven-resolver-util + provided org.apache.maven.resolver maven-resolver-impl + provided org.apache.maven.resolver maven-resolver-named-locks + provided org.apache.maven.resolver maven-resolver-named-locks-hazelcast + provided org.apache.maven.resolver maven-resolver-named-locks-redisson + provided org.apache.maven.resolver maven-resolver-connector-basic + provided org.apache.maven.resolver maven-resolver-transport-apache + provided org.apache.maven.resolver maven-resolver-transport-classpath + provided org.apache.maven.resolver maven-resolver-transport-file + provided org.apache.maven.resolver maven-resolver-transport-jdk + provided org.apache.maven.resolver maven-resolver-transport-jetty + provided org.apache.maven.resolver maven-resolver-transport-wagon + provided org.apache.maven.resolver maven-resolver-generator-gnupg + provided - - org.slf4j - slf4j-api + org.apache.maven.resolver + maven-resolver-generator-sigstore + provided + org.jboss.forge.roaster roaster-api @@ -113,24 +128,42 @@ roaster-jdt 2.29.0.Final + + org.ow2.asm + asm + 9.7.1 + org.apache.velocity velocity-engine-core 2.4.1 - - + + org.codehaus.plexus + plexus-utils + + + info.picocli + picocli + 4.7.6 + org.slf4j - slf4j-simple - runtime + slf4j-api + + org.apache.commons commons-lang3 3.17.0 runtime + + org.slf4j + slf4j-nop + runtime + @@ -149,10 +182,26 @@ org.eclipse.aether.tools.CollectConfiguration + --mode=resolver + --templates=configuration.md + ${basedir}/.. + ${basedir}/../src/site/markdown/ + + + + + render-configuration-artifacts + + java + + verify + + org.eclipse.aether.tools.CollectConfiguration + + --mode=resolver + --templates=configuration.properties,configuration.yaml ${basedir}/.. - ${basedir}/../src/site/markdown/configuration.md - ${basedir}/target/maven-resolver.properties - ${basedir}/target/maven-resolver.yaml + ${basedir}/target/ @@ -164,7 +213,7 @@ 3.6.0 - attach + attach-configuration-artifacts attach-artifact @@ -173,11 +222,11 @@ properties - ${basedir}/target/maven-resolver.properties + ${basedir}/target/configuration.properties yaml - ${basedir}/target/maven-resolver.yaml + ${basedir}/target/configuration.yaml diff --git a/maven-resolver-tools/src/main/java/org/eclipse/aether/tools/CollectConfiguration.java b/maven-resolver-tools/src/main/java/org/eclipse/aether/tools/CollectConfiguration.java index 746e17cdf..f6d73415d 100644 --- a/maven-resolver-tools/src/main/java/org/eclipse/aether/tools/CollectConfiguration.java +++ b/maven-resolver-tools/src/main/java/org/eclipse/aether/tools/CollectConfiguration.java @@ -18,115 +18,291 @@ */ package org.eclipse.aether.tools; -import java.io.BufferedWriter; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.io.UncheckedIOException; +import java.io.Writer; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; -import java.util.TreeMap; +import java.util.concurrent.Callable; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.spi.ToolProvider; +import java.util.stream.Stream; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.VelocityEngine; import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader; +import org.codehaus.plexus.util.io.CachingWriter; import org.jboss.forge.roaster.Roaster; +import org.jboss.forge.roaster._shade.org.eclipse.jdt.core.dom.AST; +import org.jboss.forge.roaster._shade.org.eclipse.jdt.core.dom.ASTNode; +import org.jboss.forge.roaster._shade.org.eclipse.jdt.core.dom.Javadoc; import org.jboss.forge.roaster.model.JavaDoc; import org.jboss.forge.roaster.model.JavaDocCapable; import org.jboss.forge.roaster.model.JavaDocTag; import org.jboss.forge.roaster.model.JavaType; +import org.jboss.forge.roaster.model.impl.JavaDocImpl; import org.jboss.forge.roaster.model.source.FieldSource; import org.jboss.forge.roaster.model.source.JavaClassSource; +import org.jboss.forge.roaster.model.source.JavaDocSource; +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.Opcodes; +import picocli.CommandLine; -public class CollectConfiguration { - public static void main(String[] args) throws Exception { - Path start = Paths.get(args.length > 0 ? args[0] : "."); - Path output = Paths.get(args.length > 1 ? args[1] : "output"); - Path props = Paths.get(args.length > 2 ? args[2] : "props"); - Path yaml = Paths.get(args.length > 3 ? args[3] : "yaml"); - - TreeMap discoveredKeys = new TreeMap<>(); - Files.walk(start) - .map(Path::toAbsolutePath) - .filter(p -> p.getFileName().toString().endsWith(".java")) - .filter(p -> p.toString().contains("/src/main/java/")) - .filter(p -> !p.toString().endsWith("/module-info.java")) - .forEach(p -> { - JavaType type = parse(p); - if (type instanceof JavaClassSource javaClassSource) { - javaClassSource.getFields().stream() - .filter(CollectConfiguration::hasConfigurationSource) - .forEach(f -> { - Map constants = extractConstants(Paths.get(p.toString() - .replace("/src/main/java/", "/target/classes/") - .replace(".java", ".class"))); - - String name = f.getName(); - String key = constants.get(name); - String fqName = f.getOrigin().getCanonicalName() + "." + name; - String configurationType = getConfigurationType(f); - String defValue = getTag(f, "@configurationDefaultValue"); - if (defValue != null && defValue.startsWith("{@link #") && defValue.endsWith("}")) { - // constant "lookup" - String lookupValue = - constants.get(defValue.substring(8, defValue.length() - 1)); - if (lookupValue == null) { - // currently we hard fail if javadoc cannot be looked up - // workaround: at cost of redundancy, but declare constants in situ for now - // (in same class) - throw new IllegalArgumentException( - "Could not look up " + defValue + " for configuration " + fqName); - } - defValue = lookupValue; - } - if ("java.lang.Long".equals(configurationType) - && (defValue.endsWith("l") || defValue.endsWith("L"))) { - defValue = defValue.substring(0, defValue.length() - 1); - } - discoveredKeys.put( - key, - new ConfigurationKey( - key, - defValue, - fqName, - cleanseJavadoc(f), - nvl(getSince(f), ""), - getConfigurationSource(f), - configurationType, - toBoolean(getTag(f, "@configurationRepoIdSuffix")))); - }); - } - }); - - VelocityEngine velocityEngine = new VelocityEngine(); - Properties properties = new Properties(); - properties.setProperty("resource.loaders", "classpath"); - properties.setProperty("resource.loader.classpath.class", ClasspathResourceLoader.class.getName()); - velocityEngine.init(properties); - - VelocityContext context = new VelocityContext(); - context.put("keys", discoveredKeys.values()); - - try (BufferedWriter fileWriter = Files.newBufferedWriter(output)) { - velocityEngine.getTemplate("page.vm").merge(context, fileWriter); +@CommandLine.Command(name = "docgen", description = "Maven Documentation Generator") +public class CollectConfiguration implements Callable { + public static void main(String[] args) { + new CommandLine(new CollectConfiguration()).execute(args); + } + + protected static final String KEY = "key"; + + enum Mode { + maven, + resolver + } + + @CommandLine.Option( + names = {"-m", "--mode"}, + arity = "1", + paramLabel = "mode", + description = "The mode of generator (what is being scanned?), supported modes are 'maven', 'resolver'") + protected Mode mode; + + @CommandLine.Option( + names = {"-t", "--templates"}, + arity = "1", + split = ",", + paramLabel = "template", + description = "The template names to write content out without '.vm' extension") + protected List templates; + + @CommandLine.Parameters(index = "0", description = "The root directory to process sources from") + protected Path rootDirectory; + + @CommandLine.Parameters(index = "1", description = "The directory to generate output(s) to") + protected Path outputDirectory; + + @Override + public Integer call() { + try { + rootDirectory = rootDirectory.toAbsolutePath().normalize(); + outputDirectory = outputDirectory.toAbsolutePath().normalize(); + + ArrayList> discoveredKeys = new ArrayList<>(); + try (Stream stream = Files.walk(rootDirectory)) { + if (mode == Mode.maven) { + System.out.println("Processing Maven sources from " + rootDirectory); + stream.map(Path::toAbsolutePath) + .filter(p -> p.getFileName().toString().endsWith(".class")) + .filter(p -> p.toString().contains("/target/classes/")) + .forEach(p -> { + processMavenClass(p, discoveredKeys); + }); + } else if (mode == Mode.resolver) { + System.out.println("Processing Resolver sources from " + rootDirectory); + stream.map(Path::toAbsolutePath) + .filter(p -> p.getFileName().toString().endsWith(".java")) + .filter(p -> p.toString().contains("/src/main/java/")) + .filter(p -> !p.toString().endsWith("/module-info.java")) + .forEach(p -> processResolverClass(p, discoveredKeys)); + } else { + throw new IllegalStateException("Unsupported mode " + mode); + } + } + + Collections.sort(discoveredKeys, Comparator.comparing(e -> e.get(KEY))); + + Properties properties = new Properties(); + properties.setProperty("resource.loaders", "classpath"); + properties.setProperty("resource.loader.classpath.class", ClasspathResourceLoader.class.getName()); + VelocityEngine velocityEngine = new VelocityEngine(); + velocityEngine.init(properties); + + VelocityContext context = new VelocityContext(); + context.put("keys", discoveredKeys); + + for (String template : templates) { + Path output = outputDirectory.resolve(template); + System.out.println("Writing out to " + output); + try (Writer fileWriter = new CachingWriter(output, StandardCharsets.UTF_8)) { + velocityEngine.getTemplate(template + ".vm").merge(context, fileWriter); + } + } + return 0; + } catch (Exception e) { + e.printStackTrace(System.err); + return 1; } - try (BufferedWriter fileWriter = Files.newBufferedWriter(props)) { - velocityEngine.getTemplate("props.vm").merge(context, fileWriter); + } + + protected void processMavenClass(Path path, List> discoveredKeys) { + try { + ClassReader classReader = new ClassReader(Files.newInputStream(path)); + classReader.accept( + new ClassVisitor(Opcodes.ASM9) { + @Override + public FieldVisitor visitField( + int fieldAccess, + String fieldName, + String fieldDescriptor, + String fieldSignature, + Object fieldValue) { + return new FieldVisitor(Opcodes.ASM9) { + @Override + public AnnotationVisitor visitAnnotation( + String annotationDescriptor, boolean annotationVisible) { + if (annotationDescriptor.equals("Lorg/apache/maven/api/annotations/Config;")) { + return new AnnotationVisitor(Opcodes.ASM9) { + final Map values = new HashMap<>(); + + @Override + public void visit(String name, Object value) { + values.put(name, value); + } + + @Override + public void visitEnum(String name, String descriptor, String value) { + values.put(name, value); + } + + @Override + public void visitEnd() { + JavaType jtype = parse(Paths.get(path.toString() + .replace("/target/classes/", "/src/main/java/") + .replace(".class", ".java"))); + FieldSource f = + ((JavaClassSource) jtype).getField(fieldName); + + String fqName = null; + String desc = cloneJavadoc(f.getJavaDoc()) + .removeAllTags() + .getFullText() + .replace("*", "\\*"); + String since = getSince(f); + String source = + switch ((values.get("source") != null + ? (String) values.get("source") + : "USER_PROPERTIES") // TODO: enum + .toLowerCase()) { + case "model" -> "Model properties"; + case "user_properties" -> "User properties"; + default -> throw new IllegalStateException(); + }; + String type = + switch ((values.get("type") != null + ? (String) values.get("type") + : "java.lang.String")) { + case "java.lang.String" -> "String"; + case "java.lang.Integer" -> "Integer"; + case "java.lang.Boolean" -> "Boolean"; + default -> throw new IllegalStateException(); + }; + discoveredKeys.add(Map.of( + KEY, + fieldValue.toString(), + "defaultValue", + values.get("defaultValue") != null + ? values.get("defaultValue") + .toString() + : "", + "fqName", + nvl(fqName, ""), + "description", + desc, + "since", + nvl(since, ""), + "configurationSource", + source, + "configurationType", + type)); + } + }; + } + return null; + } + }; + } + }, + 0); + } catch (IOException e) { + throw new RuntimeException(e); } - try (BufferedWriter fileWriter = Files.newBufferedWriter(yaml)) { - velocityEngine.getTemplate("yaml.vm").merge(context, fileWriter); + } + + protected void processResolverClass(Path path, List> discoveredKeys) { + JavaType type = parse(path); + if (type instanceof JavaClassSource javaClassSource) { + javaClassSource.getFields().stream() + .filter(this::hasConfigurationSource) + .forEach(f -> { + Map constants = extractConstants(Paths.get(path.toString() + .replace("/src/main/java/", "/target/classes/") + .replace(".java", ".class"))); + + String name = f.getName(); + String key = constants.get(name); + String fqName = f.getOrigin().getCanonicalName() + "." + name; + String configurationType = getConfigurationType(f); + String defValue = getTag(f, "@configurationDefaultValue"); + if (defValue != null && defValue.startsWith("{@link #") && defValue.endsWith("}")) { + // constant "lookup" + String lookupValue = constants.get(defValue.substring(8, defValue.length() - 1)); + if (lookupValue == null) { + // currently we hard fail if javadoc cannot be looked up + // workaround: at cost of redundancy, but declare constants in situ for now + // (in same class) + throw new IllegalArgumentException( + "Could not look up " + defValue + " for configuration " + fqName); + } + defValue = lookupValue; + if ("java.lang.Long".equals(configurationType) + && (defValue.endsWith("l") || defValue.endsWith("L"))) { + defValue = defValue.substring(0, defValue.length() - 1); + } + } + discoveredKeys.add(Map.of( + KEY, + key, + "defaultValue", + nvl(defValue, ""), + "fqName", + fqName, + "description", + cleanseJavadoc(f), + "since", + nvl(getSince(f), ""), + "configurationSource", + getConfigurationSource(f), + "configurationType", + configurationType, + "supportRepoIdSuffix", + toYesNo(getTag(f, "@configurationRepoIdSuffix")))); + }); } } - private static String cleanseJavadoc(FieldSource javaClassSource) { + protected JavaDocSource cloneJavadoc(JavaDocSource javaDoc) { + Javadoc jd = (Javadoc) javaDoc.getInternal(); + return new JavaDocImpl<>(javaDoc.getOrigin(), (Javadoc) + ASTNode.copySubtree(AST.newAST(jd.getAST().apiLevel(), false), jd)); + } + + protected String cleanseJavadoc(FieldSource javaClassSource) { JavaDoc> javaDoc = javaClassSource.getJavaDoc(); String[] text = javaDoc.getFullText().split("\n"); StringBuilder result = new StringBuilder(); @@ -138,7 +314,7 @@ private static String cleanseJavadoc(FieldSource javaClassSourc return cleanseTags(result.toString()); } - private static String cleanseTags(String text) { + protected String cleanseTags(String text) { // {@code XXX} ->
XXX
// {@link XXX} -> ??? pre for now Pattern pattern = Pattern.compile("(\\{@\\w\\w\\w\\w (.+?)})"); @@ -159,7 +335,7 @@ private static String cleanseTags(String text) { return result.toString(); } - private static JavaType parse(Path path) { + protected JavaType parse(Path path) { try { return Roaster.parse(path.toFile()); } catch (IOException e) { @@ -167,85 +343,19 @@ private static JavaType parse(Path path) { } } - private static boolean toBoolean(String value) { - return ("yes".equalsIgnoreCase(value) || "true".equalsIgnoreCase(value)); - } - - /** - * Would be record, but... Velocity have no idea what it is nor how to handle it. - */ - public static class ConfigurationKey { - private final String key; - private final String defaultValue; - private final String fqName; - private final String description; - private final String since; - private final String configurationSource; - private final String configurationType; - private final boolean supportRepoIdSuffix; - - @SuppressWarnings("checkstyle:parameternumber") - public ConfigurationKey( - String key, - String defaultValue, - String fqName, - String description, - String since, - String configurationSource, - String configurationType, - boolean supportRepoIdSuffix) { - this.key = key; - this.defaultValue = defaultValue; - this.fqName = fqName; - this.description = description; - this.since = since; - this.configurationSource = configurationSource; - this.configurationType = configurationType; - this.supportRepoIdSuffix = supportRepoIdSuffix; - } - - public String getKey() { - return key; - } - - public String getDefaultValue() { - return defaultValue; - } - - public String getFqName() { - return fqName; - } - - public String getDescription() { - return description; - } - - public String getSince() { - return since; - } - - public String getConfigurationSource() { - return configurationSource; - } - - public String getConfigurationType() { - return configurationType; - } - - public boolean isSupportRepoIdSuffix() { - return supportRepoIdSuffix; - } + protected String toYesNo(String value) { + return "yes".equalsIgnoreCase(value) || "true".equalsIgnoreCase(value) ? "Yes" : "No"; } - private static String nvl(String string, String def) { + protected String nvl(String string, String def) { return string == null ? def : string; } - private static boolean hasConfigurationSource(JavaDocCapable javaDocCapable) { + protected boolean hasConfigurationSource(JavaDocCapable javaDocCapable) { return getTag(javaDocCapable, "@configurationSource") != null; } - private static String getConfigurationType(JavaDocCapable javaDocCapable) { + protected String getConfigurationType(JavaDocCapable javaDocCapable) { String type = getTag(javaDocCapable, "@configurationType"); if (type != null) { String linkPrefix = "{@link "; @@ -261,7 +371,7 @@ private static String getConfigurationType(JavaDocCapable javaDocCapable) { return nvl(type, "n/a"); } - private static String getConfigurationSource(JavaDocCapable javaDocCapable) { + protected String getConfigurationSource(JavaDocCapable javaDocCapable) { String source = getTag(javaDocCapable, "@configurationSource"); if ("{@link RepositorySystemSession#getConfigProperties()}".equals(source)) { return "Session Configuration"; @@ -272,7 +382,7 @@ private static String getConfigurationSource(JavaDocCapable javaDocCapable) { } } - private static String getSince(JavaDocCapable javaDocCapable) { + protected String getSince(JavaDocCapable javaDocCapable) { List tags; if (javaDocCapable != null) { if (javaDocCapable instanceof FieldSource fieldSource) { @@ -292,7 +402,7 @@ private static String getSince(JavaDocCapable javaDocCapable) { return null; } - private static String getTag(JavaDocCapable javaDocCapable, String tagName) { + protected String getTag(JavaDocCapable javaDocCapable, String tagName) { List tags; if (javaDocCapable != null) { if (javaDocCapable instanceof FieldSource fieldSource) { @@ -307,19 +417,19 @@ private static String getTag(JavaDocCapable javaDocCapable, String tagName) { return null; } - private static final Pattern CONSTANT_PATTERN = Pattern.compile(".*static final.* ([A-Z_]+) = (.*);"); + protected static final Pattern CONSTANT_PATTERN = Pattern.compile(".*static final.* ([A-Z_]+) = (.*);"); - private static final ToolProvider JAVAP = ToolProvider.findFirst("javap").orElseThrow(); + protected static final ToolProvider JAVAP = ToolProvider.findFirst("javap").orElseThrow(); /** * Builds "constant table" for one single class. - * + *

* Limitations: * - works only for single class (no inherited constants) * - does not work for fields that are Enum.name() * - more to come */ - private static Map extractConstants(Path file) { + protected static Map extractConstants(Path file) { StringWriter out = new StringWriter(); JAVAP.run(new PrintWriter(out), new PrintWriter(System.err), "-constants", file.toString()); Map result = new HashMap<>(); diff --git a/maven-resolver-tools/src/main/resources/page.vm b/maven-resolver-tools/src/main/resources/configuration.md.vm similarity index 95% rename from maven-resolver-tools/src/main/resources/page.vm rename to maven-resolver-tools/src/main/resources/configuration.md.vm index 46c565de2..60e6e8112 100644 --- a/maven-resolver-tools/src/main/resources/page.vm +++ b/maven-resolver-tools/src/main/resources/configuration.md.vm @@ -39,10 +39,6 @@ under the License. ]]# -#macro(yesno $val) - #if ($val) Yes #else No #end -#end - #macro(value $val) #if ($val) `$val` #else - #end #end @@ -50,7 +46,7 @@ under the License. | No | Key | Type | Description | Default Value | Since | Supports Repo ID suffix | Source | | --- | --- | --- | --- | --- | --- | --- | --- | #foreach($key in $keys) -| $foreach.count. | `$key.key` | `$key.configurationType` | $key.description | #value( $key.defaultValue ) | $key.since | #yesno( $key.supportRepoIdSuffix ) | $key.configurationSource | +| $foreach.count. | `$key.key` | `$key.configurationType` | $key.description | #value( $key.defaultValue ) | $key.since | $key.supportRepoIdSuffix | $key.configurationSource | #end #[[ diff --git a/maven-resolver-tools/src/main/resources/props.vm b/maven-resolver-tools/src/main/resources/configuration.properties.vm similarity index 98% rename from maven-resolver-tools/src/main/resources/props.vm rename to maven-resolver-tools/src/main/resources/configuration.properties.vm index dfa5a2b1a..33f2a5530 100644 --- a/maven-resolver-tools/src/main/resources/props.vm +++ b/maven-resolver-tools/src/main/resources/configuration.properties.vm @@ -43,6 +43,8 @@ props.${foreach.count}.defaultValue = ${key.defaultValue} #if( !${key.since.empty} ) props.${foreach.count}.since = ${key.since} #end +#if(${key.supportRepoIdSuffix}) props.${foreach.count}.supportRepoIdSuffix = ${key.supportRepoIdSuffix} +#end props.${foreach.count}.configurationSource = ${key.configurationSource} #end diff --git a/maven-resolver-tools/src/main/resources/yaml.vm b/maven-resolver-tools/src/main/resources/configuration.yaml.vm similarity index 97% rename from maven-resolver-tools/src/main/resources/yaml.vm rename to maven-resolver-tools/src/main/resources/configuration.yaml.vm index 6c97d8722..079330684 100644 --- a/maven-resolver-tools/src/main/resources/yaml.vm +++ b/maven-resolver-tools/src/main/resources/configuration.yaml.vm @@ -43,6 +43,8 @@ props: #if( !${key.since.empty} ) since: ${key.since} #end + #if(${key.supportRepoIdSuffix}) supportRepoIdSuffix: ${key.supportRepoIdSuffix} + #end configurationSource: ${key.configurationSource} #end diff --git a/pom.xml b/pom.xml index f325acee8..ce36f4396 100644 --- a/pom.xml +++ b/pom.xml @@ -201,6 +201,11 @@ maven-resolver-generator-gnupg ${project.version} + + org.apache.maven.resolver + maven-resolver-generator-sigstore + ${project.version} + org.apache.maven.resolver maven-resolver-supplier-mvn3 @@ -291,6 +296,12 @@ ${slf4jVersion} test + + org.slf4j + slf4j-nop + ${slf4jVersion} + test + com.google.jimfs diff --git a/src/site/markdown/configuration.md b/src/site/markdown/configuration.md index 8ca725dc4..13c94a973 100644 --- a/src/site/markdown/configuration.md +++ b/src/site/markdown/configuration.md @@ -22,7 +22,6 @@ under the License. - | No | Key | Type | Description | Default Value | Since | Supports Repo ID suffix | Source | | --- | --- | --- | --- | --- | --- | --- | --- | | 1. | `"aether.artifactResolver.postProcessor.trustedChecksums"` | `Boolean` | Is post processor enabled. | `false` | 1.9.0 | No | Session Configuration |