Skip to content

Commit 23186d4

Browse files
authored
Fixes #1522, add render-dependencies mojo (#1523)
1 parent bc1893f commit 23186d4

File tree

7 files changed

+564
-1
lines changed

7 files changed

+564
-1
lines changed

pom.xml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ under the License.
2828
</parent>
2929

3030
<artifactId>maven-dependency-plugin</artifactId>
31-
<version>3.8.2-SNAPSHOT</version>
31+
<version>3.9.0-SNAPSHOT</version>
3232
<packaging>maven-plugin</packaging>
3333

3434
<name>Apache Maven Dependency Plugin</name>
@@ -277,6 +277,18 @@ under the License.
277277
<version>${slf4jVersion}</version>
278278
</dependency>
279279

280+
<!-- promote dependencies from transitive ones since we do use them in render-dependencies mojo -->
281+
<dependency>
282+
<groupId>org.apache.velocity</groupId>
283+
<artifactId>velocity-engine-core</artifactId>
284+
<version>2.4.1</version>
285+
</dependency>
286+
<dependency>
287+
<groupId>org.apache.velocity.tools</groupId>
288+
<artifactId>velocity-tools-generic</artifactId>
289+
<version>3.1</version>
290+
</dependency>
291+
280292
<!-- test -->
281293
<dependency>
282294
<groupId>org.apache.maven.resolver</groupId>
Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.maven.plugins.dependency.fromDependencies;
20+
21+
import javax.inject.Inject;
22+
23+
import java.io.File;
24+
import java.io.IOException;
25+
import java.io.StringWriter;
26+
import java.io.Writer;
27+
import java.nio.charset.Charset;
28+
import java.nio.charset.StandardCharsets;
29+
import java.nio.file.Files;
30+
import java.nio.file.Path;
31+
import java.nio.file.Paths;
32+
import java.util.Collections;
33+
import java.util.Comparator;
34+
import java.util.List;
35+
import java.util.Objects;
36+
import java.util.Properties;
37+
import java.util.function.Function;
38+
import java.util.stream.Collectors;
39+
40+
import org.apache.maven.artifact.Artifact;
41+
import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
42+
import org.apache.maven.execution.MavenSession;
43+
import org.apache.maven.plugin.MojoExecutionException;
44+
import org.apache.maven.plugins.annotations.LifecyclePhase;
45+
import org.apache.maven.plugins.annotations.Mojo;
46+
import org.apache.maven.plugins.annotations.Parameter;
47+
import org.apache.maven.plugins.annotations.ResolutionScope;
48+
import org.apache.maven.plugins.dependency.utils.ResolverUtil;
49+
import org.apache.maven.project.MavenProject;
50+
import org.apache.maven.project.MavenProjectHelper;
51+
import org.apache.maven.project.ProjectBuilder;
52+
import org.apache.maven.shared.artifact.filter.collection.ArtifactsFilter;
53+
import org.apache.velocity.Template;
54+
import org.apache.velocity.VelocityContext;
55+
import org.apache.velocity.app.VelocityEngine;
56+
import org.apache.velocity.tools.generic.CollectionTool;
57+
import org.sonatype.plexus.build.incremental.BuildContext;
58+
59+
import static java.util.Optional.ofNullable;
60+
61+
/**
62+
* This goal renders dependencies based on a velocity template.
63+
*
64+
* @since 3.9.0
65+
*/
66+
@Mojo(
67+
name = "render-dependencies",
68+
requiresDependencyResolution = ResolutionScope.TEST,
69+
defaultPhase = LifecyclePhase.GENERATE_SOURCES,
70+
threadSafe = true)
71+
public class RenderDependenciesMojo extends AbstractDependencyFilterMojo {
72+
/**
73+
* Encoding to write the rendered template.
74+
* @since 3.9.0
75+
*/
76+
@Parameter(property = "outputEncoding", defaultValue = "${project.reporting.outputEncoding}")
77+
private String outputEncoding;
78+
79+
/**
80+
* The file to write the rendered template string. If undefined, it just prints the classpath as [INFO].
81+
* @since 3.9.0
82+
*/
83+
@Parameter(property = "mdep.outputFile")
84+
private File outputFile;
85+
86+
/**
87+
* If not null or empty it will attach the artifact with this classifier.
88+
* @since 3.9.0
89+
*/
90+
@Parameter(property = "mdep.classifier", defaultValue = "template")
91+
private String classifier;
92+
93+
/**
94+
* Extension to use for the attached file if classifier is not null/empty.
95+
* @since 3.9.0
96+
*/
97+
@Parameter(property = "mdep.extension", defaultValue = "txt")
98+
private String extension;
99+
100+
/**
101+
* Velocity template to use to render the output file.
102+
* It can be inline or a file path.
103+
* @since 3.9.0
104+
*/
105+
@Parameter(property = "mdep.template", required = true)
106+
private String template;
107+
108+
private final MavenProjectHelper projectHelper;
109+
110+
@Inject
111+
protected RenderDependenciesMojo(
112+
MavenSession session,
113+
BuildContext buildContext,
114+
MavenProject project,
115+
ResolverUtil resolverUtil,
116+
ProjectBuilder projectBuilder,
117+
ArtifactHandlerManager artifactHandlerManager,
118+
MavenProjectHelper projectHelper) {
119+
super(session, buildContext, project, resolverUtil, projectBuilder, artifactHandlerManager);
120+
this.projectHelper = projectHelper;
121+
}
122+
123+
/**
124+
* Main entry into mojo.
125+
*
126+
* @throws MojoExecutionException with a message if an error occurs
127+
*/
128+
@Override
129+
protected void doExecute() throws MojoExecutionException {
130+
// sort them to ease template work and ensure it is deterministic
131+
final List<Artifact> artifacts =
132+
ofNullable(getResolvedDependencies(true)).orElseGet(Collections::emptySet).stream()
133+
.sorted(Comparator.comparing(Artifact::getGroupId)
134+
.thenComparing(Artifact::getArtifactId)
135+
.thenComparing(Artifact::getBaseVersion)
136+
.thenComparing(orEmpty(Artifact::getClassifier))
137+
.thenComparing(orEmpty(Artifact::getType)))
138+
.collect(Collectors.toList());
139+
140+
if (artifacts.isEmpty()) {
141+
getLog().warn("No dependencies found.");
142+
}
143+
144+
final String rendered = render(artifacts);
145+
146+
if (outputFile == null) {
147+
getLog().info(rendered);
148+
} else {
149+
store(rendered, outputFile);
150+
}
151+
if (classifier != null && !classifier.isEmpty()) {
152+
attachFile(rendered);
153+
}
154+
}
155+
156+
/**
157+
* Do render the template.
158+
* @param artifacts input.
159+
* @return the template rendered.
160+
*/
161+
private String render(final List<Artifact> artifacts) {
162+
final Path templatePath = getTemplatePath();
163+
final boolean fromFile = templatePath != null && Files.exists(templatePath);
164+
165+
final Properties props = new Properties();
166+
props.setProperty("runtime.strict_mode.enable", "true");
167+
if (fromFile) {
168+
props.setProperty(
169+
"resource.loader.file.path",
170+
templatePath.toAbsolutePath().getParent().toString());
171+
}
172+
173+
final VelocityEngine ve = new VelocityEngine(props);
174+
ve.init();
175+
176+
final VelocityContext context = new VelocityContext();
177+
context.put("artifacts", artifacts);
178+
context.put("sorter", new CollectionTool());
179+
180+
// Merge template + context
181+
final StringWriter writer = new StringWriter();
182+
try (StringWriter ignored = writer) {
183+
if (fromFile) {
184+
final Template template =
185+
ve.getTemplate(templatePath.getFileName().toString());
186+
template.merge(context, writer);
187+
} else {
188+
ve.evaluate(context, writer, "tpl-" + Math.abs(hashCode()), template);
189+
}
190+
} catch (final IOException e) {
191+
// no-op, not possible
192+
}
193+
194+
return writer.toString();
195+
}
196+
197+
private Path getTemplatePath() {
198+
try {
199+
return Paths.get(template);
200+
} catch (final RuntimeException re) {
201+
return null;
202+
}
203+
}
204+
205+
/**
206+
* Trivial null protection impl for comparing callback.
207+
* @param getter nominal getter.
208+
* @return a comparer of getter defaulting on empty if getter value is null.
209+
*/
210+
private Comparator<Artifact> orEmpty(final Function<Artifact, String> getter) {
211+
return Comparator.comparing(a -> ofNullable(getter.apply(a)).orElse(""));
212+
}
213+
214+
/**
215+
* @param content the rendered template
216+
* @throws MojoExecutionException in case of an error
217+
*/
218+
protected void attachFile(final String content) throws MojoExecutionException {
219+
final File attachedFile;
220+
if (outputFile == null) {
221+
attachedFile = new File(getProject().getBuild().getDirectory(), classifier);
222+
store(content, attachedFile);
223+
} else { // already written
224+
attachedFile = outputFile;
225+
}
226+
projectHelper.attachArtifact(getProject(), extension, classifier, attachedFile);
227+
}
228+
229+
/**
230+
* Stores the specified string into that file.
231+
*
232+
* @param content the string to write into the file
233+
*/
234+
private void store(final String content, final File out) throws MojoExecutionException {
235+
// make sure the parent path exists.
236+
final Path parent = out.toPath().getParent();
237+
if (parent != null) {
238+
try {
239+
Files.createDirectories(parent);
240+
} catch (final IOException e) {
241+
throw new MojoExecutionException(e);
242+
}
243+
}
244+
245+
final String encoding = Objects.toString(outputEncoding, StandardCharsets.UTF_8.name());
246+
try (Writer w = Files.newBufferedWriter(out.toPath(), Charset.forName(encoding))) {
247+
w.write(content);
248+
getLog().info("Wrote file '" + out + "'.");
249+
} catch (final IOException ex) {
250+
throw new MojoExecutionException("Error while writing to file '" + out, ex);
251+
}
252+
}
253+
254+
@Override
255+
protected ArtifactsFilter getMarkedArtifactFilter() {
256+
return null;
257+
}
258+
259+
public void setExtension(final String extension) {
260+
this.extension = extension;
261+
}
262+
263+
public void setOutputEncoding(final String outputEncoding) {
264+
this.outputEncoding = outputEncoding;
265+
}
266+
267+
public void setOutputFile(final File outputFile) {
268+
this.outputFile = outputFile;
269+
}
270+
271+
public void setClassifier(final String classifier) {
272+
this.classifier = classifier;
273+
}
274+
275+
public void setTemplate(final String template) {
276+
this.template = template;
277+
}
278+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
~~ Licensed to the Apache Software Foundation (ASF) under one
2+
~~ or more contributor license agreements. See the NOTICE file
3+
~~ distributed with this work for additional information
4+
~~ regarding copyright ownership. The ASF licenses this file
5+
~~ to you under the Apache License, Version 2.0 (the
6+
~~ "License"); you may not use this file except in compliance
7+
~~ with the License. You may obtain a copy of the License at
8+
~~
9+
~~ http://www.apache.org/licenses/LICENSE-2.0
10+
~~
11+
~~ Unless required by applicable law or agreed to in writing,
12+
~~ software distributed under the License is distributed on an
13+
~~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
~~ KIND, either express or implied. See the License for the
15+
~~ specific language governing permissions and limitations
16+
~~ under the License.
17+
18+
------
19+
Render a Velocity template
20+
------
21+
Allan Ramirez
22+
Brian Fox
23+
Stephen Connolly
24+
------
25+
2025-09-17
26+
------
27+
28+
Render a Velocity template
29+
30+
You can use <<<dependency:render-depedencies>>> mojo to a render a velocity template,
31+
with the <<artifacts>> (dependencies) as context:
32+
33+
+---+
34+
<project>
35+
[...]
36+
<build>
37+
<plugins>
38+
<plugin>
39+
<groupId>org.apache.maven.plugins</groupId>
40+
<artifactId>maven-dependency-plugin</artifactId>
41+
<version>${project.version}</version>
42+
<executions>
43+
<execution>
44+
<id>copy</id>
45+
<phase>process-resources</phase>
46+
<goals>
47+
<goal>render-dependencies</goal>
48+
</goals>
49+
<configuration>
50+
<template><![CDATA[
51+
deps:
52+
jars:
53+
#foreach($dep in $sorter.sort($artifacts, ["artifactId:asc"]))
54+
#set($type = $dep.type)
55+
#if(!$type || $type.trim().isEmpty())
56+
#set($type = "jar")
57+
#end
58+
#set($classifierSuffix = "")
59+
#if($dep.classifier && !$dep.classifier.trim().isEmpty())
60+
#set($classifierSuffix = "-$dep.classifier")
61+
#end
62+
- local:///opt/test/libs/$dep.artifactId-$dep.baseVersion$classifierSuffix.$type
63+
#end]]></template>
64+
</configuration>
65+
</execution>
66+
</executions>
67+
</plugin>
68+
</plugins>
69+
</build>
70+
[...]
71+
</project>
72+
+---+
73+
74+
Then after executing <<<mvn process-resources>>>, the template will be rendered.
75+
By default it is printed in the console but you can set <<outputFile>> to store it somewhere.
76+
77+
The resolution uses exactly the same mechanism than for <<dependency:build-classpath>> mojo.

0 commit comments

Comments
 (0)