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