Skip to content

Commit 6645e00

Browse files
committed
Fixes #1522, add render-dependencies mojo
1 parent bc1893f commit 6645e00

File tree

3 files changed

+380
-0
lines changed

3 files changed

+380
-0
lines changed
Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
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.util.Collections;
32+
import java.util.Comparator;
33+
import java.util.List;
34+
import java.util.Objects;
35+
import java.util.Properties;
36+
import java.util.function.Function;
37+
import java.util.stream.Collectors;
38+
39+
import org.apache.maven.artifact.Artifact;
40+
import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
41+
import org.apache.maven.execution.MavenSession;
42+
import org.apache.maven.plugin.MojoExecutionException;
43+
import org.apache.maven.plugins.annotations.LifecyclePhase;
44+
import org.apache.maven.plugins.annotations.Mojo;
45+
import org.apache.maven.plugins.annotations.Parameter;
46+
import org.apache.maven.plugins.annotations.ResolutionScope;
47+
import org.apache.maven.plugins.dependency.utils.ResolverUtil;
48+
import org.apache.maven.project.MavenProject;
49+
import org.apache.maven.project.MavenProjectHelper;
50+
import org.apache.maven.project.ProjectBuilder;
51+
import org.apache.maven.shared.artifact.filter.collection.ArtifactsFilter;
52+
import org.apache.velocity.VelocityContext;
53+
import org.apache.velocity.app.VelocityEngine;
54+
import org.apache.velocity.tools.generic.CollectionTool;
55+
import org.sonatype.plexus.build.incremental.BuildContext;
56+
57+
import static java.util.Optional.ofNullable;
58+
59+
/**
60+
* This goal renders dependencies based on a velocity template.
61+
*
62+
* @since 3.8.2
63+
*/
64+
@Mojo(
65+
name = "render-dependencies",
66+
requiresDependencyResolution = ResolutionScope.TEST,
67+
defaultPhase = LifecyclePhase.GENERATE_SOURCES,
68+
threadSafe = true)
69+
public class RenderDependenciesMojo extends AbstractDependencyFilterMojo {
70+
@Parameter(property = "outputEncoding", defaultValue = "${project.reporting.outputEncoding}")
71+
private String outputEncoding;
72+
73+
/**
74+
* The file to write the rendered template string. If undefined, it just prints the classpath as [INFO].
75+
*/
76+
@Parameter(property = "mdep.outputFile")
77+
private File outputFile;
78+
79+
/**
80+
* If not null or empty it will attach the artifact with this classifier.
81+
*/
82+
@Parameter(property = "mdep.classifier", defaultValue = "template")
83+
private String classifier;
84+
85+
/**
86+
* velocity template to use to render the output file.
87+
*/
88+
@Parameter(property = "mdep.template", defaultValue = "<set the template>")
89+
private String template;
90+
91+
private final MavenProjectHelper projectHelper;
92+
93+
@Inject
94+
protected RenderDependenciesMojo(
95+
MavenSession session,
96+
BuildContext buildContext,
97+
MavenProject project,
98+
ResolverUtil resolverUtil,
99+
ProjectBuilder projectBuilder,
100+
ArtifactHandlerManager artifactHandlerManager,
101+
MavenProjectHelper projectHelper) {
102+
super(session, buildContext, project, resolverUtil, projectBuilder, artifactHandlerManager);
103+
this.projectHelper = projectHelper;
104+
}
105+
106+
/**
107+
* Main entry into mojo.
108+
*
109+
* @throws MojoExecutionException with a message if an error occurs
110+
*/
111+
@Override
112+
protected void doExecute() throws MojoExecutionException {
113+
// sort them to ease template work and ensure it is deterministic
114+
final List<Artifact> artifacts =
115+
ofNullable(getResolvedDependencies(true)).orElseGet(Collections::emptySet).stream()
116+
.sorted(Comparator.comparing(Artifact::getGroupId)
117+
.thenComparing(Artifact::getArtifactId)
118+
.thenComparing(Artifact::getBaseVersion)
119+
.thenComparing(orEmpty(Artifact::getClassifier))
120+
.thenComparing(orEmpty(Artifact::getType)))
121+
.collect(Collectors.toList());
122+
123+
if (artifacts.isEmpty()) {
124+
getLog().warn("No dependencies found.");
125+
}
126+
127+
final String rendered = render(artifacts);
128+
129+
if (outputFile == null) {
130+
getLog().info(rendered);
131+
} else {
132+
store(rendered, outputFile);
133+
}
134+
if (classifier != null && !classifier.isEmpty()) {
135+
attachFile(rendered);
136+
}
137+
}
138+
139+
/**
140+
* Do render the template.
141+
* @param artifacts input.
142+
* @return the template rendered.
143+
*/
144+
private String render(final List<Artifact> artifacts) {
145+
final Properties props = new Properties();
146+
props.setProperty("runtime.strict_mode.enable", "true");
147+
148+
final VelocityEngine ve = new VelocityEngine(props);
149+
ve.init();
150+
151+
final VelocityContext context = new VelocityContext();
152+
context.put("artifacts", artifacts);
153+
context.put("sorter", new CollectionTool());
154+
155+
// Merge template + context
156+
final StringWriter writer = new StringWriter();
157+
try {
158+
ve.evaluate(context, writer, "tpl-" + Math.abs(hashCode()), template);
159+
} finally {
160+
try {
161+
writer.close();
162+
} catch (final IOException e) {
163+
// no-op, not possible
164+
}
165+
}
166+
167+
return writer.toString();
168+
}
169+
170+
/**
171+
* Trivial null protection impl for comparing callback.
172+
* @param getter nominal getter.
173+
* @return a comparer of getter defaulting on empty if getter value is null.
174+
*/
175+
private Comparator<Artifact> orEmpty(final Function<Artifact, String> getter) {
176+
return Comparator.comparing(a -> ofNullable(getter.apply(a)).orElse(""));
177+
}
178+
179+
/**
180+
* @param content the rendered template
181+
* @throws MojoExecutionException in case of an error
182+
*/
183+
protected void attachFile(final String content) throws MojoExecutionException {
184+
final File attachedFile;
185+
if (outputFile == null) {
186+
attachedFile = new File(getProject().getBuild().getDirectory(), classifier);
187+
store(content, attachedFile);
188+
} else { // already written
189+
attachedFile = outputFile;
190+
}
191+
projectHelper.attachArtifact(getProject(), attachedFile, classifier);
192+
}
193+
194+
/**
195+
* Stores the specified string into that file.
196+
*
197+
* @param content the string to write into the file
198+
*/
199+
private void store(final String content, final File out) throws MojoExecutionException {
200+
// make sure the parent path exists.
201+
final Path parent = out.toPath().getParent();
202+
if (parent != null) {
203+
try {
204+
Files.createDirectories(parent);
205+
} catch (final IOException e) {
206+
throw new MojoExecutionException(e);
207+
}
208+
}
209+
210+
final String encoding = Objects.toString(outputEncoding, StandardCharsets.UTF_8.name());
211+
try (Writer w = Files.newBufferedWriter(out.toPath(), Charset.forName(encoding))) {
212+
w.write(content);
213+
getLog().info("Wrote file '" + out + "'.");
214+
} catch (final IOException ex) {
215+
throw new MojoExecutionException("Error while writing to file '" + out, ex);
216+
}
217+
}
218+
219+
@Override
220+
protected ArtifactsFilter getMarkedArtifactFilter() {
221+
return null;
222+
}
223+
224+
public void setOutputEncoding(final String outputEncoding) {
225+
this.outputEncoding = outputEncoding;
226+
}
227+
228+
public void setOutputFile(final File outputFile) {
229+
this.outputFile = outputFile;
230+
}
231+
232+
public void setClassifier(final String classifier) {
233+
this.classifier = classifier;
234+
}
235+
236+
public void setTemplate(final String template) {
237+
this.template = template;
238+
}
239+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
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 java.io.File;
22+
import java.util.Set;
23+
24+
import org.apache.maven.artifact.Artifact;
25+
import org.apache.maven.execution.MavenSession;
26+
import org.apache.maven.plugins.dependency.AbstractDependencyMojoTestCase;
27+
import org.apache.maven.plugins.dependency.testUtils.stubs.DependencyProjectStub;
28+
import org.apache.maven.project.MavenProject;
29+
30+
import static org.assertj.core.api.Assertions.assertThat;
31+
32+
public class TestRenderDependenciesMojo extends AbstractDependencyMojoTestCase {
33+
private RenderDependenciesMojo mojo;
34+
35+
@Override
36+
protected String getTestDirectoryName() {
37+
return "render-dependencies";
38+
}
39+
40+
@Override
41+
protected boolean shouldCreateFiles() {
42+
return true;
43+
}
44+
45+
@Override
46+
protected void setUp() throws Exception {
47+
super.setUp();
48+
49+
final MavenProject project = new DependencyProjectStub();
50+
getContainer().addComponent(project, MavenProject.class.getName());
51+
52+
final MavenSession session = newMavenSession(project);
53+
getContainer().addComponent(session, MavenSession.class.getName());
54+
55+
final File testPom = new File(
56+
getBasedir(), "target/test-classes/unit/" + getTestDirectoryName() + "-test/plugin-config.xml");
57+
mojo = (RenderDependenciesMojo) lookupMojo(getTestDirectoryName(), testPom);
58+
}
59+
60+
/**
61+
* Tests the rendering.
62+
* Note that this is a real life example of using the mojo to generate a CRD for a SparkApplication.
63+
* It is useful when combined with JIB for example since several versions of the CRD do not support wildcard for
64+
* the classpath(s).
65+
*/
66+
public void testRender() throws Exception {
67+
final File rendered = new File(testDir, "render-dependencies.testRender.txt");
68+
69+
final MavenProject project = mojo.getProject();
70+
final Set<Artifact> artifacts = stubFactory.getScopedArtifacts();
71+
final Set<Artifact> directArtifacts = stubFactory.getReleaseAndSnapshotArtifacts();
72+
artifacts.addAll(directArtifacts);
73+
project.setArtifacts(artifacts);
74+
project.setDependencyArtifacts(directArtifacts);
75+
76+
mojo.setTemplate("deps:\n"
77+
+ " jars:\n"
78+
+ "#foreach($dep in $sorter.sort($artifacts, [\"artifactId:asc\"]))\n"
79+
+ "#set($type = $dep.type)\n"
80+
+ "#if(!$type || $type.trim().isEmpty())\n"
81+
+ " #set($type = \"jar\")\n"
82+
+ "#end\n"
83+
+ "#set($classifierSuffix = \"\")\n"
84+
+ "#if($dep.classifier && !$dep.classifier.trim().isEmpty())\n"
85+
+ " #set($classifierSuffix = \"-$dep.classifier\")\n"
86+
+ "#end\n"
87+
+ " - local:///opt/test/libs/$dep.artifactId-$dep.baseVersion$classifierSuffix.$type\n"
88+
+ "#end");
89+
mojo.setOutputFile(rendered);
90+
mojo.execute();
91+
92+
assertThat(rendered)
93+
.content()
94+
.isEqualTo("deps:\n"
95+
+ " jars:\n"
96+
+ " - local:///opt/test/libs/compile-1.0.jar\n"
97+
+ " - local:///opt/test/libs/provided-1.0.jar\n"
98+
+ " - local:///opt/test/libs/release-1.0.jar\n"
99+
+ " - local:///opt/test/libs/runtime-1.0.jar\n"
100+
+ " - local:///opt/test/libs/snapshot-2.0-SNAPSHOT.jar\n"
101+
+ " - local:///opt/test/libs/system-1.0.jar\n"
102+
+ " - local:///opt/test/libs/test-1.0.jar\n");
103+
}
104+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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+
-->
20+
<project>
21+
<build>
22+
<plugins>
23+
<plugin>
24+
<artifactId>maven-dependency-plugin</artifactId>
25+
<configuration>
26+
</configuration>
27+
</plugin>
28+
</plugins>
29+
</build>
30+
<dependencies>
31+
<dependency>
32+
<groupId>org.apache.maven</groupId>
33+
<artifactId>maven-artifact</artifactId>
34+
<version>2.0.4</version>
35+
</dependency>
36+
</dependencies>
37+
</project>

0 commit comments

Comments
 (0)