1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.release.plugin.mojos;
18
19 import java.io.File;
20 import java.io.IOException;
21 import java.io.OutputStreamWriter;
22 import java.io.Writer;
23 import java.nio.charset.StandardCharsets;
24 import java.nio.file.Files;
25 import java.nio.file.Path;
26 import java.nio.file.Paths;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.List;
30
31 import org.apache.commons.io.FileUtils;
32 import org.apache.commons.io.file.PathUtils;
33 import org.apache.commons.lang3.StringUtils;
34 import org.apache.commons.release.plugin.SharedFunctions;
35 import org.apache.commons.release.plugin.velocity.HeaderHtmlVelocityDelegate;
36 import org.apache.commons.release.plugin.velocity.ReadmeHtmlVelocityDelegate;
37 import org.apache.maven.plugin.AbstractMojo;
38 import org.apache.maven.plugin.MojoExecutionException;
39 import org.apache.maven.plugin.MojoFailureException;
40 import org.apache.maven.plugin.logging.Log;
41 import org.apache.maven.plugins.annotations.Component;
42 import org.apache.maven.plugins.annotations.LifecyclePhase;
43 import org.apache.maven.plugins.annotations.Mojo;
44 import org.apache.maven.plugins.annotations.Parameter;
45 import org.apache.maven.project.MavenProject;
46 import org.apache.maven.scm.ScmException;
47 import org.apache.maven.scm.ScmFileSet;
48 import org.apache.maven.scm.command.add.AddScmResult;
49 import org.apache.maven.scm.command.checkin.CheckInScmResult;
50 import org.apache.maven.scm.command.checkout.CheckOutScmResult;
51 import org.apache.maven.scm.manager.BasicScmManager;
52 import org.apache.maven.scm.manager.ScmManager;
53 import org.apache.maven.scm.provider.ScmProvider;
54 import org.apache.maven.scm.provider.svn.repository.SvnScmProviderRepository;
55 import org.apache.maven.scm.provider.svn.svnexe.SvnExeScmProvider;
56 import org.apache.maven.scm.repository.ScmRepository;
57 import org.apache.maven.settings.Settings;
58 import org.apache.maven.settings.crypto.SettingsDecrypter;
59
60
61
62
63
64
65
66
67 @Mojo(name = "stage-distributions",
68 defaultPhase = LifecyclePhase.DEPLOY,
69 threadSafe = true,
70 aggregator = true)
71 public class CommonsDistributionStagingMojo extends AbstractMojo {
72
73
74 private static final String README_FILE_NAME = "README.html";
75
76
77 private static final String HEADER_FILE_NAME = "HEADER.html";
78
79
80 private static final String SIGNATURE_VALIDATOR_NAME = "signature-validator.sh";
81
82
83
84
85 @Parameter(defaultValue = "${project}", required = true)
86 private MavenProject project;
87
88
89
90
91
92 @Parameter(defaultValue = "${basedir}")
93 private File baseDir;
94
95
96 @Parameter(defaultValue = "${project.build.directory}/site", property = "commons.siteOutputDirectory")
97 private File siteDirectory;
98
99
100
101
102
103 @Parameter(defaultValue = "${project.build.directory}/commons-release-plugin", property = "commons.outputDirectory")
104 private File workingDirectory;
105
106
107
108
109
110 @Parameter(defaultValue = "${project.build.directory}/commons-release-plugin/scm",
111 property = "commons.distCheckoutDirectory")
112 private File distCheckoutDirectory;
113
114
115
116
117 @Parameter(defaultValue = "${basedir}/RELEASE-NOTES.txt", property = "commons.releaseNotesLocation")
118 private File releaseNotesFile;
119
120
121
122
123
124
125 @Parameter(property = "commons.release.dryRun", defaultValue = "false")
126 private Boolean dryRun;
127
128
129
130
131
132
133 @Parameter(defaultValue = "", property = "commons.distSvnStagingUrl")
134 private String distSvnStagingUrl;
135
136
137
138
139 @Parameter(defaultValue = "false", property = "commons.release.isDistModule")
140 private Boolean isDistModule;
141
142
143
144
145 @Parameter(property = "commons.release.version")
146 private String commonsReleaseVersion;
147
148
149
150
151 @Parameter(property = "commons.rc.version")
152 private String commonsRcVersion;
153
154
155
156
157
158 @Parameter(property = "commons.distServer")
159 private String distServer;
160
161
162
163
164 @Parameter(property = "user.name")
165 private String username;
166
167
168
169
170 @Parameter(property = "user.password")
171 private String password;
172
173
174
175
176 @Parameter(defaultValue = "${settings}", readonly = true, required = true)
177 private Settings settings;
178
179
180
181
182 @Component
183 private SettingsDecrypter settingsDecrypter;
184
185
186
187
188
189
190 private File distRcVersionDirectory;
191
192 @Override
193 public void execute() throws MojoExecutionException, MojoFailureException {
194 if (!isDistModule) {
195 getLog().info("This module is marked as a non distribution "
196 + "or assembly module, and the plugin will not run.");
197 return;
198 }
199 if (StringUtils.isEmpty(distSvnStagingUrl)) {
200 getLog().warn("commons.distSvnStagingUrl is not set, the commons-release-plugin will not run.");
201 return;
202 }
203 if (!workingDirectory.exists()) {
204 getLog().info("Current project contains no distributions. Not executing.");
205 return;
206 }
207 getLog().info("Preparing to stage distributions");
208 try {
209 final ScmManager scmManager = new BasicScmManager();
210 scmManager.setScmProvider("svn", new SvnExeScmProvider());
211 final ScmRepository repository = scmManager.makeScmRepository(distSvnStagingUrl);
212 final ScmProvider provider = scmManager.getProviderByRepository(repository);
213 final SvnScmProviderRepository providerRepository = (SvnScmProviderRepository) repository
214 .getProviderRepository();
215 SharedFunctions.setAuthentication(
216 providerRepository,
217 distServer,
218 settings,
219 settingsDecrypter,
220 username,
221 password
222 );
223 distRcVersionDirectory =
224 new File(distCheckoutDirectory, commonsReleaseVersion + "-" + commonsRcVersion);
225 if (!distCheckoutDirectory.exists()) {
226 SharedFunctions.initDirectory(getLog(), distCheckoutDirectory);
227 }
228 final ScmFileSet scmFileSet = new ScmFileSet(distCheckoutDirectory);
229 getLog().info("Checking out dist from: " + distSvnStagingUrl);
230 final CheckOutScmResult checkOutResult = provider.checkOut(repository, scmFileSet);
231 if (!checkOutResult.isSuccess()) {
232 throw new MojoExecutionException("Failed to checkout files from SCM: "
233 + checkOutResult.getProviderMessage() + " [" + checkOutResult.getCommandOutput() + "]");
234 }
235 final File copiedReleaseNotes = copyReleaseNotesToWorkingDirectory();
236 copyDistributionsIntoScmDirectoryStructureAndAddToSvn(copiedReleaseNotes,
237 provider, repository);
238 final List<File> filesToAdd = new ArrayList<>();
239 listNotHiddenFilesAndDirectories(distCheckoutDirectory, filesToAdd);
240 if (!dryRun) {
241 final ScmFileSet fileSet = new ScmFileSet(distCheckoutDirectory, filesToAdd);
242 final AddScmResult addResult = provider.add(
243 repository,
244 fileSet
245 );
246 if (!addResult.isSuccess()) {
247 throw new MojoExecutionException("Failed to add files to SCM: " + addResult.getProviderMessage()
248 + " [" + addResult.getCommandOutput() + "]");
249 }
250 getLog().info("Staging release: " + project.getArtifactId() + ", version: " + project.getVersion());
251 final CheckInScmResult checkInResult = provider.checkIn(
252 repository,
253 fileSet,
254 "Staging release: " + project.getArtifactId() + ", version: " + project.getVersion()
255 );
256 if (!checkInResult.isSuccess()) {
257 getLog().error("Committing dist files failed: " + checkInResult.getCommandOutput());
258 throw new MojoExecutionException(
259 "Committing dist files failed: " + checkInResult.getCommandOutput()
260 );
261 }
262 getLog().info("Committed revision " + checkInResult.getScmRevision());
263 } else {
264 getLog().info("[Dry run] Would have committed to: " + distSvnStagingUrl);
265 getLog().info(
266 "[Dry run] Staging release: " + project.getArtifactId() + ", version: " + project.getVersion());
267 }
268 } catch (final ScmException e) {
269 getLog().error("Could not commit files to dist: " + distSvnStagingUrl, e);
270 throw new MojoExecutionException("Could not commit files to dist: " + distSvnStagingUrl, e);
271 }
272 }
273
274
275
276
277
278
279 private void listNotHiddenFilesAndDirectories(final File directory, final List<File> files) {
280
281 final File[] fList = directory.listFiles();
282 for (final File file : fList) {
283 if (file.isFile() && !file.isHidden()) {
284 files.add(file);
285 } else if (file.isDirectory() && !file.isHidden()) {
286 files.add(file);
287 listNotHiddenFilesAndDirectories(file, files);
288 }
289 }
290 }
291
292
293
294
295
296
297
298
299
300
301
302
303 private File copyReleaseNotesToWorkingDirectory() throws MojoExecutionException {
304 SharedFunctions.initDirectory(getLog(), distRcVersionDirectory);
305 getLog().info("Copying RELEASE-NOTES.txt to working directory.");
306 final File copiedReleaseNotes = new File(distRcVersionDirectory, releaseNotesFile.getName());
307 SharedFunctions.copyFile(getLog(), releaseNotesFile, copiedReleaseNotes);
308 return copiedReleaseNotes;
309 }
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342 private List<File> copyDistributionsIntoScmDirectoryStructureAndAddToSvn(final File copiedReleaseNotes,
343 final ScmProvider provider,
344 final ScmRepository repository)
345 throws MojoExecutionException {
346 final List<File> workingDirectoryFiles = Arrays.asList(workingDirectory.listFiles());
347 final List<File> filesForMavenScmFileSet = new ArrayList<>();
348 final File scmBinariesRoot = new File(distRcVersionDirectory, "binaries");
349 final File scmSourceRoot = new File(distRcVersionDirectory, "source");
350 SharedFunctions.initDirectory(getLog(), scmBinariesRoot);
351 SharedFunctions.initDirectory(getLog(), scmSourceRoot);
352 File copy;
353 for (final File file : workingDirectoryFiles) {
354 if (file.getName().contains("src")) {
355 copy = new File(scmSourceRoot, file.getName());
356 SharedFunctions.copyFile(getLog(), file, copy);
357 filesForMavenScmFileSet.add(file);
358 } else if (file.getName().contains("bin")) {
359 copy = new File(scmBinariesRoot, file.getName());
360 SharedFunctions.copyFile(getLog(), file, copy);
361 filesForMavenScmFileSet.add(file);
362 } else if (StringUtils.containsAny(file.getName(), "scm", "sha256.properties", "sha512.properties")) {
363 getLog().debug("Not copying scm directory over to the scm directory because it is the scm directory.");
364
365 } else {
366 copy = new File(distCheckoutDirectory.getAbsolutePath(), file.getName());
367 SharedFunctions.copyFile(getLog(), file, copy);
368 filesForMavenScmFileSet.add(file);
369 }
370 }
371 filesForMavenScmFileSet.addAll(buildReadmeAndHeaderHtmlFiles());
372 filesForMavenScmFileSet.add(copySignatureValidatorScriptToScmDirectory());
373 filesForMavenScmFileSet.addAll(copySiteToScmDirectory());
374 return filesForMavenScmFileSet;
375 }
376
377
378
379
380
381
382
383
384 private File copySignatureValidatorScriptToScmDirectory() throws MojoExecutionException {
385 final Path scmTargetPath = Paths.get(distRcVersionDirectory.toString(), SIGNATURE_VALIDATOR_NAME);
386 final String name = "/resources/" + SIGNATURE_VALIDATOR_NAME;
387
388 try {
389 PathUtils.copyFile(getClass().getResource(name), scmTargetPath);
390 } catch (final Exception e) {
391 throw new MojoExecutionException(String.format("Failed to copy '%s' to '%s'", name, scmTargetPath), e);
392 }
393 return scmTargetPath.toFile();
394 }
395
396
397
398
399
400
401
402
403 private List<File> copySiteToScmDirectory() throws MojoExecutionException {
404 if (!siteDirectory.exists()) {
405 getLog().error("\"mvn site\" was not run before this goal, or a siteDirectory did not exist.");
406 throw new MojoExecutionException(
407 "\"mvn site\" was not run before this goal, or a siteDirectory did not exist."
408 );
409 }
410 final File siteInScm = new File(distRcVersionDirectory, "site");
411 try {
412 FileUtils.copyDirectory(siteDirectory, siteInScm);
413 } catch (final IOException e) {
414 throw new MojoExecutionException("Site copying failed", e);
415 }
416 return new ArrayList<>(FileUtils.listFiles(siteInScm, null, true));
417 }
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437 private List<File> buildReadmeAndHeaderHtmlFiles() throws MojoExecutionException {
438 final List<File> headerAndReadmeFiles = new ArrayList<>();
439 final File headerFile = new File(distRcVersionDirectory, HEADER_FILE_NAME);
440
441
442
443 try (Writer headerWriter = new OutputStreamWriter(Files.newOutputStream(headerFile.toPath()),
444 StandardCharsets.UTF_8)) {
445 HeaderHtmlVelocityDelegate.builder().build().render(headerWriter);
446 } catch (final IOException e) {
447 final String message = "Could not build HEADER html file " + headerFile;
448 getLog().error(message, e);
449 throw new MojoExecutionException(message, e);
450 }
451 headerAndReadmeFiles.add(headerFile);
452
453
454
455 final File readmeFile = new File(distRcVersionDirectory, README_FILE_NAME);
456 try (Writer readmeWriter = new OutputStreamWriter(Files.newOutputStream(readmeFile.toPath()),
457 StandardCharsets.UTF_8)) {
458
459 final ReadmeHtmlVelocityDelegate readmeHtmlVelocityDelegate = ReadmeHtmlVelocityDelegate.builder()
460 .withArtifactId(project.getArtifactId())
461 .withVersion(project.getVersion())
462 .withSiteUrl(project.getUrl())
463 .build();
464
465 readmeHtmlVelocityDelegate.render(readmeWriter);
466 } catch (final IOException e) {
467 final String message = "Could not build README html file " + readmeFile;
468 getLog().error(message, e);
469 throw new MojoExecutionException(message, e);
470 }
471 headerAndReadmeFiles.add(readmeFile);
472
473
474
475 headerAndReadmeFiles.addAll(copyHeaderAndReadmeToSubdirectories(headerFile, readmeFile));
476 return headerAndReadmeFiles;
477 }
478
479
480
481
482
483
484
485
486
487
488
489 private List<File> copyHeaderAndReadmeToSubdirectories(final File headerFile, final File readmeFile)
490 throws MojoExecutionException {
491 final List<File> symbolicLinkFiles = new ArrayList<>();
492 final File sourceRoot = new File(distRcVersionDirectory, "source");
493 final File binariesRoot = new File(distRcVersionDirectory, "binaries");
494 final File sourceHeaderFile = new File(sourceRoot, HEADER_FILE_NAME);
495 final File sourceReadmeFile = new File(sourceRoot, README_FILE_NAME);
496 final File binariesHeaderFile = new File(binariesRoot, HEADER_FILE_NAME);
497 final File binariesReadmeFile = new File(binariesRoot, README_FILE_NAME);
498 SharedFunctions.copyFile(getLog(), headerFile, sourceHeaderFile);
499 symbolicLinkFiles.add(sourceHeaderFile);
500 SharedFunctions.copyFile(getLog(), readmeFile, sourceReadmeFile);
501 symbolicLinkFiles.add(sourceReadmeFile);
502 SharedFunctions.copyFile(getLog(), headerFile, binariesHeaderFile);
503 symbolicLinkFiles.add(binariesHeaderFile);
504 SharedFunctions.copyFile(getLog(), readmeFile, binariesReadmeFile);
505 symbolicLinkFiles.add(binariesReadmeFile);
506 return symbolicLinkFiles;
507 }
508
509
510
511
512
513
514
515
516 protected void setBaseDir(final File baseDir) {
517 this.baseDir = baseDir;
518 }
519 }