1 package org.apache.maven.archiver;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import javax.lang.model.SourceVersion;
23
24 import java.io.File;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.nio.file.attribute.FileTime;
28 import java.time.Instant;
29 import java.time.OffsetDateTime;
30 import java.time.ZoneOffset;
31 import java.time.format.DateTimeParseException;
32 import java.time.temporal.ChronoUnit;
33 import java.util.ArrayList;
34 import java.util.Collections;
35 import java.util.Date;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.Optional;
39 import java.util.Properties;
40 import java.util.Set;
41 import java.util.jar.Attributes;
42
43 import org.apache.maven.artifact.Artifact;
44 import org.apache.maven.artifact.DependencyResolutionRequiredException;
45 import org.apache.maven.artifact.versioning.ArtifactVersion;
46 import org.apache.maven.artifact.versioning.OverConstrainedVersionException;
47 import org.apache.maven.execution.MavenSession;
48 import org.apache.maven.project.MavenProject;
49 import org.codehaus.plexus.archiver.jar.JarArchiver;
50 import org.codehaus.plexus.archiver.jar.Manifest;
51 import org.codehaus.plexus.archiver.jar.ManifestException;
52 import org.codehaus.plexus.interpolation.InterpolationException;
53 import org.codehaus.plexus.interpolation.Interpolator;
54 import org.codehaus.plexus.interpolation.PrefixAwareRecursionInterceptor;
55 import org.codehaus.plexus.interpolation.PrefixedObjectValueSource;
56 import org.codehaus.plexus.interpolation.PrefixedPropertiesValueSource;
57 import org.codehaus.plexus.interpolation.RecursionInterceptor;
58 import org.codehaus.plexus.interpolation.StringSearchInterpolator;
59 import org.codehaus.plexus.interpolation.ValueSource;
60 import org.codehaus.plexus.util.StringUtils;
61
62 import static org.apache.maven.archiver.ManifestConfiguration.CLASSPATH_LAYOUT_TYPE_CUSTOM;
63 import static org.apache.maven.archiver.ManifestConfiguration.CLASSPATH_LAYOUT_TYPE_REPOSITORY;
64 import static org.apache.maven.archiver.ManifestConfiguration.CLASSPATH_LAYOUT_TYPE_SIMPLE;
65
66
67
68
69
70
71
72
73 public class MavenArchiver
74 {
75
76 private static final String CREATED_BY = "Maven Archiver";
77
78
79
80
81 public static final String SIMPLE_LAYOUT =
82 "${artifact.artifactId}-${artifact.version}${dashClassifier?}.${artifact.extension}";
83
84
85
86
87 public static final String REPOSITORY_LAYOUT =
88 "${artifact.groupIdPath}/${artifact.artifactId}/" + "${artifact.baseVersion}/${artifact.artifactId}-"
89 + "${artifact.version}${dashClassifier?}.${artifact.extension}";
90
91
92
93
94 public static final String SIMPLE_LAYOUT_NONUNIQUE =
95 "${artifact.artifactId}-${artifact.baseVersion}${dashClassifier?}.${artifact.extension}";
96
97
98
99
100 public static final String REPOSITORY_LAYOUT_NONUNIQUE =
101 "${artifact.groupIdPath}/${artifact.artifactId}/" + "${artifact.baseVersion}/${artifact.artifactId}-"
102 + "${artifact.baseVersion}${dashClassifier?}.${artifact.extension}";
103
104 private static final Instant DATE_MIN = Instant.parse( "1980-01-01T00:00:02Z" );
105
106 private static final Instant DATE_MAX = Instant.parse( "2099-12-31T23:59:59Z" );
107
108 private static final List<String> ARTIFACT_EXPRESSION_PREFIXES;
109
110 static
111 {
112 List<String> artifactExpressionPrefixes = new ArrayList<>();
113 artifactExpressionPrefixes.add( "artifact." );
114
115 ARTIFACT_EXPRESSION_PREFIXES = artifactExpressionPrefixes;
116 }
117
118 static boolean isValidModuleName( String name )
119 {
120 return SourceVersion.isName( name );
121 }
122
123 private JarArchiver archiver;
124
125 private File archiveFile;
126
127 private String createdBy;
128
129 private boolean buildJdkSpecDefaultEntry = true;
130
131
132
133
134
135
136
137
138
139
140
141 public Manifest getManifest( MavenSession session, MavenProject project, MavenArchiveConfiguration config )
142 throws ManifestException, DependencyResolutionRequiredException
143 {
144 boolean hasManifestEntries = !config.isManifestEntriesEmpty();
145 Map<String, String> entries =
146 hasManifestEntries ? config.getManifestEntries() : Collections.emptyMap();
147
148 Manifest manifest = getManifest( session, project, config.getManifest(), entries );
149
150
151 if ( hasManifestEntries )
152 {
153
154 for ( Map.Entry<String, String> entry : entries.entrySet() )
155 {
156 String key = entry.getKey();
157 String value = entry.getValue();
158 Manifest.Attribute attr = manifest.getMainSection().getAttribute( key );
159 if ( key.equals( Attributes.Name.CLASS_PATH.toString() ) && attr != null )
160 {
161
162
163
164 attr.setValue( value + " " + attr.getValue() );
165 }
166 else
167 {
168 addManifestAttribute( manifest, key, value );
169 }
170 }
171 }
172
173
174 if ( !config.isManifestSectionsEmpty() )
175 {
176 for ( ManifestSection section : config.getManifestSections() )
177 {
178 Manifest.Section theSection = new Manifest.Section();
179 theSection.setName( section.getName() );
180
181 if ( !section.isManifestEntriesEmpty() )
182 {
183 Map<String, String> sectionEntries = section.getManifestEntries();
184
185 for ( Map.Entry<String, String> entry : sectionEntries.entrySet() )
186 {
187 String key = entry.getKey();
188 String value = entry.getValue();
189 Manifest.Attribute attr = new Manifest.Attribute( key, value );
190 theSection.addConfiguredAttribute( attr );
191 }
192 }
193
194 manifest.addConfiguredSection( theSection );
195 }
196 }
197
198 return manifest;
199 }
200
201
202
203
204
205
206
207
208
209
210
211 public Manifest getManifest( MavenProject project, ManifestConfiguration config )
212 throws ManifestException, DependencyResolutionRequiredException
213 {
214 return getManifest( null, project, config, Collections.emptyMap() );
215 }
216
217
218
219
220
221
222
223
224
225
226
227
228 public Manifest getManifest( MavenSession mavenSession, MavenProject project, ManifestConfiguration config )
229 throws ManifestException, DependencyResolutionRequiredException
230 {
231 return getManifest( mavenSession, project, config, Collections.emptyMap() );
232 }
233
234 private void addManifestAttribute( Manifest manifest, Map<String, String> map, String key, String value )
235 throws ManifestException
236 {
237 if ( map.containsKey( key ) )
238 {
239 return;
240 }
241 addManifestAttribute( manifest, key, value );
242 }
243
244 private void addManifestAttribute( Manifest manifest, String key, String value )
245 throws ManifestException
246 {
247 if ( !StringUtils.isEmpty( value ) )
248 {
249 Manifest.Attribute attr = new Manifest.Attribute( key, value );
250 manifest.addConfiguredAttribute( attr );
251 }
252 else
253 {
254
255
256 Manifest.Attribute attr = new Manifest.Attribute( key, "" );
257 manifest.addConfiguredAttribute( attr );
258 }
259 }
260
261
262
263
264
265
266
267
268
269
270
271
272
273 protected Manifest getManifest( MavenSession session, MavenProject project, ManifestConfiguration config,
274 Map<String, String> entries )
275 throws ManifestException, DependencyResolutionRequiredException
276 {
277
278
279 Manifest m = new Manifest();
280
281 if ( config.isAddDefaultEntries() )
282 {
283 handleDefaultEntries( m, entries );
284 }
285
286
287 if ( config.isAddBuildEnvironmentEntries() )
288 {
289 handleBuildEnvironmentEntries( session, m, entries );
290 }
291
292 if ( config.isAddClasspath() )
293 {
294 StringBuilder classpath = new StringBuilder();
295
296 List<String> artifacts = project.getRuntimeClasspathElements();
297 String classpathPrefix = config.getClasspathPrefix();
298 String layoutType = config.getClasspathLayoutType();
299 String layout = config.getCustomClasspathLayout();
300
301 Interpolator interpolator = new StringSearchInterpolator();
302
303 for ( String artifactFile : artifacts )
304 {
305 File f = new File( artifactFile );
306 if ( f.getAbsoluteFile().isFile() )
307 {
308 Artifact artifact = findArtifactWithFile( project.getArtifacts(), f );
309
310 if ( classpath.length() > 0 )
311 {
312 classpath.append( " " );
313 }
314 classpath.append( classpathPrefix );
315
316
317
318 if ( artifact == null || layoutType == null )
319 {
320 classpath.append( f.getName() );
321 }
322 else
323 {
324 List<ValueSource> valueSources = new ArrayList<>();
325
326 handleExtraExpression( artifact, valueSources );
327
328 for ( ValueSource vs : valueSources )
329 {
330 interpolator.addValueSource( vs );
331 }
332
333 RecursionInterceptor recursionInterceptor =
334 new PrefixAwareRecursionInterceptor( ARTIFACT_EXPRESSION_PREFIXES );
335
336 try
337 {
338 switch ( layoutType )
339 {
340 case CLASSPATH_LAYOUT_TYPE_SIMPLE:
341 if ( config.isUseUniqueVersions() )
342 {
343 classpath.append( interpolator.interpolate( SIMPLE_LAYOUT,
344 recursionInterceptor ) );
345 }
346 else
347 {
348 classpath.append( interpolator.interpolate( SIMPLE_LAYOUT_NONUNIQUE,
349 recursionInterceptor ) );
350 }
351 break;
352 case CLASSPATH_LAYOUT_TYPE_REPOSITORY:
353
354
355
356 if ( config.isUseUniqueVersions() )
357 {
358 classpath.append( interpolator.interpolate( REPOSITORY_LAYOUT,
359 recursionInterceptor ) );
360 }
361 else
362 {
363 classpath.append( interpolator.interpolate( REPOSITORY_LAYOUT_NONUNIQUE,
364 recursionInterceptor ) );
365 }
366 break;
367 case CLASSPATH_LAYOUT_TYPE_CUSTOM:
368 if ( layout == null )
369 {
370 throw new ManifestException( CLASSPATH_LAYOUT_TYPE_CUSTOM
371 + " layout type was declared, but custom layout expression was not"
372 + " specified. Check your <archive><manifest><customLayout/>"
373 + " element." );
374 }
375
376 classpath.append( interpolator.interpolate( layout, recursionInterceptor ) );
377 break;
378 default:
379 throw new ManifestException( "Unknown classpath layout type: '" + layoutType
380 + "'. Check your <archive><manifest><layoutType/> element." );
381 }
382 }
383 catch ( InterpolationException e )
384 {
385 ManifestException error =
386 new ManifestException( "Error interpolating artifact path for classpath entry: "
387 + e.getMessage() );
388
389 error.initCause( e );
390 throw error;
391 }
392 finally
393 {
394 for ( ValueSource vs : valueSources )
395 {
396 interpolator.removeValuesSource( vs );
397 }
398 }
399 }
400 }
401 }
402
403 if ( classpath.length() > 0 )
404 {
405
406
407 addManifestAttribute( m, "Class-Path", classpath.toString() );
408 }
409 }
410
411 if ( config.isAddDefaultSpecificationEntries() )
412 {
413 handleSpecificationEntries( project, entries, m );
414 }
415
416 if ( config.isAddDefaultImplementationEntries() )
417 {
418 handleImplementationEntries( project, entries, m );
419 }
420
421 String mainClass = config.getMainClass();
422 if ( mainClass != null && !"".equals( mainClass ) )
423 {
424 addManifestAttribute( m, entries, "Main-Class", mainClass );
425 }
426
427 if ( config.isAddExtensions() )
428 {
429 handleExtensions( project, entries, m );
430 }
431
432 addCustomEntries( m, entries, config );
433
434 return m;
435 }
436
437 private void handleExtraExpression( Artifact artifact, List<ValueSource> valueSources )
438 {
439 valueSources.add( new PrefixedObjectValueSource( ARTIFACT_EXPRESSION_PREFIXES, artifact,
440 true ) );
441 valueSources.add( new PrefixedObjectValueSource( ARTIFACT_EXPRESSION_PREFIXES,
442 artifact.getArtifactHandler(), true ) );
443
444 Properties extraExpressions = new Properties();
445
446
447 if ( !artifact.isSnapshot() )
448 {
449 extraExpressions.setProperty( "baseVersion", artifact.getVersion() );
450 }
451
452 extraExpressions.setProperty( "groupIdPath", artifact.getGroupId().replace( '.', '/' ) );
453 if ( StringUtils.isNotEmpty( artifact.getClassifier() ) )
454 {
455 extraExpressions.setProperty( "dashClassifier", "-" + artifact.getClassifier() );
456 extraExpressions.setProperty( "dashClassifier?", "-" + artifact.getClassifier() );
457 }
458 else
459 {
460 extraExpressions.setProperty( "dashClassifier", "" );
461 extraExpressions.setProperty( "dashClassifier?", "" );
462 }
463 valueSources.add( new PrefixedPropertiesValueSource( ARTIFACT_EXPRESSION_PREFIXES,
464 extraExpressions, true ) );
465 }
466
467 private void handleExtensions( MavenProject project, Map<String, String> entries, Manifest m )
468 throws ManifestException
469 {
470
471 StringBuilder extensionsList = new StringBuilder();
472 Set<Artifact> artifacts = project.getArtifacts();
473
474 for ( Artifact artifact : artifacts )
475 {
476 if ( !Artifact.SCOPE_TEST.equals( artifact.getScope() ) )
477 {
478 if ( "jar".equals( artifact.getType() ) )
479 {
480 if ( extensionsList.length() > 0 )
481 {
482 extensionsList.append( " " );
483 }
484 extensionsList.append( artifact.getArtifactId() );
485 }
486 }
487 }
488
489 if ( extensionsList.length() > 0 )
490 {
491 addManifestAttribute( m, entries, "Extension-List", extensionsList.toString() );
492 }
493
494 for ( Artifact artifact : artifacts )
495 {
496
497
498 if ( "jar".equals( artifact.getType() ) )
499 {
500 String artifactId = artifact.getArtifactId().replace( '.', '_' );
501 String ename = artifactId + "-Extension-Name";
502 addManifestAttribute( m, entries, ename, artifact.getArtifactId() );
503 String iname = artifactId + "-Implementation-Version";
504 addManifestAttribute( m, entries, iname, artifact.getVersion() );
505
506 if ( artifact.getRepository() != null )
507 {
508 iname = artifactId + "-Implementation-URL";
509 String url = artifact.getRepository().getUrl() + "/" + artifact;
510 addManifestAttribute( m, entries, iname, url );
511 }
512 }
513 }
514 }
515
516 private void handleImplementationEntries( MavenProject project, Map<String, String> entries, Manifest m )
517 throws ManifestException
518 {
519 addManifestAttribute( m, entries, "Implementation-Title", project.getName() );
520 addManifestAttribute( m, entries, "Implementation-Version", project.getVersion() );
521
522 if ( project.getOrganization() != null )
523 {
524 addManifestAttribute( m, entries, "Implementation-Vendor", project.getOrganization().getName() );
525 }
526 }
527
528 private void handleSpecificationEntries( MavenProject project, Map<String, String> entries, Manifest m )
529 throws ManifestException
530 {
531 addManifestAttribute( m, entries, "Specification-Title", project.getName() );
532
533 try
534 {
535 ArtifactVersion version = project.getArtifact().getSelectedVersion();
536 String specVersion = String.format( "%s.%s", version.getMajorVersion(), version.getMinorVersion() );
537 addManifestAttribute( m, entries, "Specification-Version", specVersion );
538 }
539 catch ( OverConstrainedVersionException e )
540 {
541 throw new ManifestException( "Failed to get selected artifact version to calculate"
542 + " the specification version: " + e.getMessage() );
543 }
544
545 if ( project.getOrganization() != null )
546 {
547 addManifestAttribute( m, entries, "Specification-Vendor", project.getOrganization().getName() );
548 }
549 }
550
551 private void addCustomEntries( Manifest m, Map<String, String> entries, ManifestConfiguration config )
552 throws ManifestException
553 {
554
555
556
557
558
559 if ( config.getPackageName() != null )
560 {
561 addManifestAttribute( m, entries, "Package", config.getPackageName() );
562 }
563 }
564
565
566
567
568
569
570 public JarArchiver getArchiver()
571 {
572 return archiver;
573 }
574
575
576
577
578
579
580 public void setArchiver( JarArchiver archiver )
581 {
582 this.archiver = archiver;
583 }
584
585
586
587
588
589
590 public void setOutputFile( File outputFile )
591 {
592 archiveFile = outputFile;
593 }
594
595
596
597
598
599
600
601
602
603
604
605
606 public void createArchive( MavenSession session, MavenProject project,
607 MavenArchiveConfiguration archiveConfiguration )
608 throws ManifestException, IOException,
609 DependencyResolutionRequiredException
610 {
611
612
613 MavenProject workingProject = project.clone();
614
615 boolean forced = archiveConfiguration.isForced();
616 if ( archiveConfiguration.isAddMavenDescriptor() )
617 {
618
619
620
621
622
623
624
625
626
627
628
629 if ( workingProject.getArtifact().isSnapshot() )
630 {
631 workingProject.setVersion( workingProject.getArtifact().getVersion() );
632 }
633
634 String groupId = workingProject.getGroupId();
635
636 String artifactId = workingProject.getArtifactId();
637
638 archiver.addFile( project.getFile(), "META-INF/maven/" + groupId + "/" + artifactId + "/pom.xml" );
639
640
641
642
643
644 File customPomPropertiesFile = archiveConfiguration.getPomPropertiesFile();
645 File dir = new File( workingProject.getBuild().getDirectory(), "maven-archiver" );
646 File pomPropertiesFile = new File( dir, "pom.properties" );
647
648 new PomPropertiesUtil().createPomProperties( session, workingProject, archiver,
649 customPomPropertiesFile, pomPropertiesFile, forced );
650 }
651
652
653
654
655
656 archiver.setMinimalDefaultManifest( true );
657
658 File manifestFile = archiveConfiguration.getManifestFile();
659
660 if ( manifestFile != null )
661 {
662 archiver.setManifest( manifestFile );
663 }
664
665 Manifest manifest = getManifest( session, workingProject, archiveConfiguration );
666
667
668 archiver.addConfiguredManifest( manifest );
669
670 archiver.setCompress( archiveConfiguration.isCompress() );
671
672 archiver.setRecompressAddedZips( archiveConfiguration.isRecompressAddedZips() );
673
674 archiver.setIndex( archiveConfiguration.isIndex() );
675
676 archiver.setDestFile( archiveFile );
677
678
679 if ( archiveConfiguration.getManifest().isAddClasspath() )
680 {
681 List<String> artifacts = project.getRuntimeClasspathElements();
682 for ( String artifact : artifacts )
683 {
684 File f = new File( artifact );
685 archiver.addConfiguredIndexJars( f );
686 }
687 }
688
689 archiver.setForced( forced );
690 if ( !archiveConfiguration.isForced() && archiver.isSupportingForced() )
691 {
692
693
694
695 }
696
697 String automaticModuleName = manifest.getMainSection().getAttributeValue( "Automatic-Module-Name" );
698 if ( automaticModuleName != null )
699 {
700 if ( !isValidModuleName( automaticModuleName ) )
701 {
702 throw new ManifestException( "Invalid automatic module name: '" + automaticModuleName + "'" );
703 }
704 }
705
706
707 archiver.createArchive();
708 }
709
710 private void handleDefaultEntries( Manifest m, Map<String, String> entries )
711 throws ManifestException
712 {
713 String createdBy = this.createdBy;
714 if ( createdBy == null )
715 {
716 createdBy = createdBy( CREATED_BY, "org.apache.maven", "maven-archiver" );
717 }
718 addManifestAttribute( m, entries, "Created-By", createdBy );
719 if ( buildJdkSpecDefaultEntry )
720 {
721 addManifestAttribute( m, entries, "Build-Jdk-Spec", System.getProperty( "java.specification.version" ) );
722 }
723 }
724
725 private void handleBuildEnvironmentEntries( MavenSession session, Manifest m, Map<String, String> entries )
726 throws ManifestException
727 {
728 addManifestAttribute( m, entries, "Build-Tool",
729 session != null ? session.getSystemProperties().getProperty( "maven.build.version" ) : "Apache Maven" );
730 addManifestAttribute( m, entries, "Build-Jdk", String.format( "%s (%s)", System.getProperty( "java.version" ),
731 System.getProperty( "java.vendor" ) ) );
732 addManifestAttribute( m, entries, "Build-Os", String.format( "%s (%s; %s)", System.getProperty( "os.name" ),
733 System.getProperty( "os.version" ), System.getProperty( "os.arch" ) ) );
734 }
735
736 private Artifact findArtifactWithFile( Set<Artifact> artifacts, File file )
737 {
738 for ( Artifact artifact : artifacts )
739 {
740
741 if ( artifact.getFile() != null )
742 {
743 if ( artifact.getFile().equals( file ) )
744 {
745 return artifact;
746 }
747 }
748 }
749 return null;
750 }
751
752 private static String getCreatedByVersion( String groupId, String artifactId )
753 {
754 final Properties properties = loadOptionalProperties( MavenArchiver.class.getResourceAsStream(
755 "/META-INF/maven/" + groupId + "/" + artifactId + "/pom.properties" ) );
756
757 return properties.getProperty( "version" );
758 }
759
760 private static Properties loadOptionalProperties( final InputStream inputStream )
761 {
762 Properties properties = new Properties();
763 if ( inputStream != null )
764 {
765 try ( InputStream in = inputStream )
766 {
767 properties.load( in );
768 }
769 catch ( IllegalArgumentException | IOException ex )
770 {
771
772 }
773 }
774 return properties;
775 }
776
777
778
779
780
781
782
783
784
785 public void setCreatedBy( String description, String groupId, String artifactId )
786 {
787 createdBy = createdBy( description, groupId, artifactId );
788 }
789
790 private String createdBy( String description, String groupId, String artifactId )
791 {
792 String createdBy = description;
793 String version = getCreatedByVersion( groupId, artifactId );
794 if ( version != null )
795 {
796 createdBy += " " + version;
797 }
798 return createdBy;
799 }
800
801
802
803
804
805
806
807
808
809 public void setBuildJdkSpecDefaultEntry( boolean buildJdkSpecDefaultEntry )
810 {
811 this.buildJdkSpecDefaultEntry = buildJdkSpecDefaultEntry;
812 }
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827 @Deprecated
828 public Date parseOutputTimestamp( String outputTimestamp )
829 {
830 return parseBuildOutputTimestamp( outputTimestamp ).map( Date::from ).orElse( null );
831 }
832
833
834
835
836
837
838
839
840
841
842 @Deprecated
843 public Date configureReproducible( String outputTimestamp )
844 {
845 configureReproducibleBuild( outputTimestamp );
846 return parseOutputTimestamp( outputTimestamp );
847 }
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862 public static Optional<Instant> parseBuildOutputTimestamp( String outputTimestamp )
863 {
864
865 if ( outputTimestamp == null )
866 {
867 return Optional.empty();
868 }
869
870
871 if ( StringUtils.isNotEmpty( outputTimestamp ) && StringUtils.isNumeric( outputTimestamp ) )
872 {
873 return Optional.of( Instant.ofEpochSecond( Long.parseLong( outputTimestamp ) ) );
874 }
875
876
877
878 if ( outputTimestamp.length() < 2 )
879 {
880 return Optional.empty();
881 }
882
883 try
884 {
885
886 final Instant date = OffsetDateTime.parse( outputTimestamp )
887 .withOffsetSameInstant( ZoneOffset.UTC ).truncatedTo( ChronoUnit.SECONDS ).toInstant();
888
889 if ( date.isBefore( DATE_MIN ) || date.isAfter( DATE_MAX ) )
890 {
891 throw new IllegalArgumentException( "'" + date + "' is not within the valid range "
892 + DATE_MIN + " to " + DATE_MAX );
893 }
894 return Optional.of( date );
895 }
896 catch ( DateTimeParseException pe )
897 {
898 throw new IllegalArgumentException( "Invalid project.build.outputTimestamp value '" + outputTimestamp + "'",
899 pe );
900 }
901 }
902
903
904
905
906
907
908
909
910 public void configureReproducibleBuild( String outputTimestamp )
911 {
912 parseBuildOutputTimestamp( outputTimestamp )
913 .map( FileTime::from )
914 .ifPresent( modifiedTime -> getArchiver().configureReproducibleBuild( modifiedTime ) );
915 }
916 }