/
BaselineMojo.java
309 lines (265 loc) · 11 KB
/
BaselineMojo.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
package aQute.bnd.maven.baseline.plugin;
import static java.util.stream.Collectors.toList;
import static org.apache.maven.plugins.annotations.LifecyclePhase.VERIFY;
import java.io.File;
import java.io.IOException;
import java.util.Comparator;
import java.util.Formatter;
import java.util.List;
import java.util.ListIterator;
import java.util.Locale;
import java.util.Objects;
import aQute.bnd.differ.Baseline;
import aQute.bnd.differ.Baseline.BundleInfo;
import aQute.bnd.differ.Baseline.Info;
import aQute.bnd.differ.DiffPluginImpl;
import aQute.bnd.header.Parameters;
import aQute.bnd.osgi.Instructions;
import aQute.bnd.osgi.Jar;
import aQute.bnd.osgi.Processor;
import aQute.bnd.service.diff.Diff;
import aQute.bnd.version.MavenVersion;
import aQute.lib.io.IO;
import aQute.lib.strings.Strings;
import org.apache.maven.RepositoryUtils;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.eclipse.aether.RepositoryException;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.resolution.ArtifactRequest;
import org.eclipse.aether.resolution.ArtifactResolutionException;
import org.eclipse.aether.resolution.ArtifactResult;
import org.eclipse.aether.resolution.VersionRangeRequest;
import org.eclipse.aether.resolution.VersionRangeResolutionException;
import org.eclipse.aether.resolution.VersionRangeResult;
import org.eclipse.aether.version.Version;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Exports project dependencies to OSGi R5 index format.
*/
@Mojo(name = "baseline", defaultPhase = VERIFY, threadSafe = true)
public class BaselineMojo extends AbstractMojo {
private static final Logger logger = LoggerFactory.getLogger(BaselineMojo.class);
private static final String PACKAGING_POM = "pom";
@Parameter(defaultValue = "${project}", readonly = true, required = true)
private MavenProject project;
@Parameter(defaultValue = "${repositorySystemSession}", readonly = true, required = true)
private RepositorySystemSession session;
@Parameter(property = "bnd.baseline.fail.on.missing", defaultValue = "true")
private boolean failOnMissing;
@Parameter(property = "bnd.baseline.include.distribution.management", defaultValue = "true")
private boolean includeDistributionManagement;
@Parameter(property = "bnd.baseline.full.report", defaultValue = "false")
private boolean fullReport;
@Parameter(property = "bnd.baseline.continue.on.error", defaultValue = "false")
private boolean continueOnError;
@Parameter
private Base base;
@Parameter(required = false, property = "bnd.baseline.diffignores")
private List<String> diffignores;
@Parameter(required = false, defaultValue = "*", property = "bnd.baseline.diffpackages")
private List<String> diffpackages;
@Parameter(property = "bnd.baseline.skip", defaultValue = "false")
private boolean skip;
@Parameter(property = "bnd.baseline.releaseversions", defaultValue = "false")
private boolean releaseversions;
@Component
private RepositorySystem system;
@Parameter(property = "bnd.baseline.report.file", defaultValue = "${project.build.directory}/baseline/${project.build.finalName}.txt")
private File reportFile;
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
if (skip) {
logger.debug("skip project as configured");
return;
}
// Exit without generating anything if this is a pom-packaging project.
// Probably it's just a parent project.
if (PACKAGING_POM.equals(project.getPackaging())) {
logger.info("skip project with packaging=pom");
return;
}
Artifact artifact = RepositoryUtils.toArtifact(project.getArtifact());
List<RemoteRepository> aetherRepos = getRepositories(artifact);
setupBase(artifact);
try {
searchForBaseVersion(aetherRepos);
if (base.getVersion() != null && !base.getVersion()
.isEmpty()) {
ArtifactResult artifactResult = locateBaseJar(aetherRepos);
baselineAction(artifact.getFile(), artifactResult.getArtifact()
.getFile());
} else {
if (failOnMissing) {
throw new MojoFailureException("Unable to locate a previous version of the artifact");
} else {
logger.warn("No previous version of {} could be found to baseline against", artifact);
}
}
} catch (RepositoryException re) {
throw new MojoFailureException("Unable to locate a previous version of the artifact", re);
} catch (Exception e) {
throw new MojoExecutionException("An error occurred while calculating the baseline", e);
}
}
private List<RemoteRepository> getRepositories(Artifact artifact) {
List<RemoteRepository> aetherRepos = RepositoryUtils.toRepos(project.getRemoteArtifactRepositories());
if (includeDistributionManagement) {
RemoteRepository releaseDistroRepo;
if (artifact.isSnapshot()) {
MavenProject tmpClone = project.clone();
tmpClone.getArtifact()
.setVersion("1.0.0");
releaseDistroRepo = RepositoryUtils.toRepo(tmpClone.getDistributionManagementArtifactRepository());
} else {
releaseDistroRepo = RepositoryUtils.toRepo(project.getDistributionManagementArtifactRepository());
}
// Issue #2040:
// Don't fail on projects without distributionManagement
if (releaseDistroRepo != null) {
aetherRepos.add(0, releaseDistroRepo);
}
}
return aetherRepos;
}
private void setupBase(Artifact artifact) {
if (base == null) {
base = new Base();
}
if (base.getGroupId() == null || base.getGroupId()
.isEmpty()) {
base.setGroupId(project.getGroupId());
}
if (base.getArtifactId() == null || base.getArtifactId()
.isEmpty()) {
base.setArtifactId(project.getArtifactId());
}
if (base.getClassifier() == null || base.getClassifier()
.isEmpty()) {
base.setClassifier(artifact.getClassifier());
}
if (base.getExtension() == null || base.getExtension()
.isEmpty()) {
base.setExtension(artifact.getExtension());
}
if (base.getVersion() == null || base.getVersion()
.isEmpty()) {
base.setVersion("(," + artifact.getVersion() + ")");
}
logger.debug("Baselining against {}, fail on missing: {}", base, failOnMissing);
}
private void searchForBaseVersion(List<RemoteRepository> aetherRepos) throws VersionRangeResolutionException {
logger.info("Determining the baseline version for {} using repositories {}", base, aetherRepos);
Artifact toFind = new DefaultArtifact(base.getGroupId(), base.getArtifactId(), base.getClassifier(),
base.getExtension(), base.getVersion());
VersionRangeRequest request = new VersionRangeRequest(toFind, aetherRepos, "baseline");
VersionRangeResult versions = system.resolveVersionRange(session, request);
List<Version> found = versions.getVersions();
logger.debug("Found versions {}", found);
boolean onlyreleaseversions = releaseversions && (base.getVersion()
.startsWith("[")
|| base.getVersion()
.startsWith("("));
base.setVersion(null);
for (ListIterator<Version> li = found.listIterator(found.size()); li.hasPrevious();) {
String highest = li.previous()
.toString();
if (toFind.setVersion(highest)
.isSnapshot()) {
continue;
}
if (onlyreleaseversions) {
MavenVersion mavenVersion = MavenVersion.parseMavenString(highest);
if (mavenVersion.compareTo(mavenVersion.toReleaseVersion()) < 0) {
logger.debug("Version {} not considered since it is not a release version", highest);
continue; // not a release version
}
}
base.setVersion(highest);
break;
}
logger.info("The baseline version was found to be {}", base.getVersion());
}
private ArtifactResult locateBaseJar(List<RemoteRepository> aetherRepos) throws ArtifactResolutionException {
Artifact toFind = new DefaultArtifact(base.getGroupId(), base.getArtifactId(), base.getClassifier(),
base.getExtension(), base.getVersion());
return system.resolveArtifact(session, new ArtifactRequest(toFind, aetherRepos, "baseline"));
}
private void baselineAction(File bundle, File baseline) throws Exception, IOException {
IO.mkdirs(reportFile.getParentFile());
boolean failure = false;
try (Processor processor = new Processor();
Jar newer = new Jar(bundle);
Jar older = new Jar(baseline)) {
logger.info("Baseline bundle {} against baseline {}", bundle, baseline);
DiffPluginImpl differ = new DiffPluginImpl();
differ.setIgnore(new Parameters(Strings.join(diffignores), processor));
Baseline baseliner = new Baseline(processor, differ);
List<Info> infos = baseliner
.baseline(newer, older, new Instructions(new Parameters(Strings.join(diffpackages), processor)))
.stream()
.sorted(Comparator.comparing(info -> info.packageName))
.collect(toList());
BundleInfo bundleInfo = baseliner.getBundleInfo();
try (Formatter f = new Formatter(reportFile, "UTF-8", Locale.US)) {
String format = "%s %-50s %-10s %-10s %-10s %-10s %-10s %s\n";
f.format("===============================================================\n");
f.format(format, " ", "Name", "Type", "Delta", "New", "Old", "Suggest", "");
Diff diff = baseliner.getDiff();
f.format(format, bundleInfo.mismatch ? "*" : " ", bundleInfo.bsn, diff.getType(), diff.getDelta(),
newer.getVersion(), older.getVersion(),
bundleInfo.mismatch && Objects.nonNull(bundleInfo.suggestedVersion) ? bundleInfo.suggestedVersion
: "-",
"");
if (fullReport || bundleInfo.mismatch) {
f.format("%#2S\n", diff);
}
if (bundleInfo.mismatch) {
failure = true;
}
if (!infos.isEmpty()) {
f.format("===============================================================\n");
f.format(format, " ", "Name", "Type", "Delta", "New", "Old", "Suggest", "If Prov.");
for (Info info : infos) {
diff = info.packageDiff;
f.format(format, info.mismatch ? "*" : " ", diff.getName(), diff.getType(), diff.getDelta(),
info.newerVersion,
Objects.nonNull(info.olderVersion)
&& info.olderVersion.equals(aQute.bnd.version.Version.LOWEST) ? "-" : info.olderVersion,
Objects.nonNull(info.suggestedVersion)
&& info.suggestedVersion.compareTo(info.newerVersion) <= 0 ? "ok"
: info.suggestedVersion,
Objects.nonNull(info.suggestedIfProviders) ? info.suggestedIfProviders : "-");
if (fullReport || info.mismatch) {
f.format("%#2S\n", diff);
}
if (info.mismatch) {
failure = true;
}
}
}
}
}
if (failure) {
String msg = String.format("Baseline problems detected. See the report in %s.\n%s", reportFile,
IO.collect(reportFile));
if (continueOnError) {
logger.warn(msg);
} else {
throw new MojoFailureException(msg);
}
} else {
logger.info("Baseline check succeeded. See the report in {}.", reportFile);
}
}
}