Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reproducible build #189

Merged
merged 2 commits into from
May 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
75 changes: 26 additions & 49 deletions core/src/main/java/org/moditect/Moditect.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@

import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.NumberFormat;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
Expand All @@ -35,100 +34,78 @@ public class Moditect {

public static void main(String[] args) throws Exception {
CliArgs cliArgs = new CliArgs();
new JCommander( cliArgs, args );
new JCommander(cliArgs, args);

new AddModuleInfo( null, null, null, null, cliArgs.outputDirecory, cliArgs.jvmVersion, cliArgs.overwriteExistingFiles, cliArgs.timestamp ).run();
new AddModuleInfo(null, null, null, null, cliArgs.outputDirecory, cliArgs.jvmVersion, cliArgs.overwriteExistingFiles, cliArgs.timestamp).run();
}

@Parameters(separators = "=")
private static class CliArgs {

@Parameter(
names = "--module-info",
required = true,
description = "Path to the module-info.java descriptor",
converter = PathConverter.class
)
@Parameter(names = "--module-info", required = true, description = "Path to the module-info.java descriptor", converter = PathConverter.class)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this actually more readable? Not that I'm interested in having a formatting style discussion, but could we configure it so it doesn't join wrapped lines? At least there's such an option in the Eclipse formatter. If it's not doable here for some reason, let's go with the proposed change.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took the formatting configuration from the moditect-org-parent project

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, can we then perhaps add the "don't join wrapped lines" config there?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I can tell it's already there

<setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="false"/>

private Path moduleInfo;

@Parameter(
names = "--output-directory",
required = true,
description = "Path to a directory for storing the modularized JAR",
converter = PathConverter.class
)
@Parameter(names = "--output-directory", required = true, description = "Path to a directory for storing the modularized JAR", converter = PathConverter.class)
private Path outputDirecory;

@Parameter(
names = "--jvm-version",
required = false,
description = "The JVM version for which to add the module-info.java descriptor, "
+ "or \"base\" to add the descriptor to the root of the jarfile (default: 9)"
)
@Parameter(names = "--jvm-version", required = false, description = "The JVM version for which to add the module-info.java descriptor, "
+ "or \"base\" to add the descriptor to the root of the jarfile (default: 9)")
private String jvmVersion;

@Parameter(
names = "--overwrite-existing-files",
required = false,
description = "Whether to overwrite existing files or not"
)
@Parameter(names = "--overwrite-existing-files", required = false, description = "Whether to overwrite existing files or not")
private boolean overwriteExistingFiles;

@Parameter(
names = "--timestamp",
required = false,
description = "Timestamp used when writing archive entries",
converter = InstantConverter.class
)
@Parameter(names = "--timestamp", required = false, description = "Timestamp used when writing archive entries", converter = InstantConverter.class)
private Instant timestamp;
}

private static class PathConverter implements IStringConverter<Path> {

@Override
public Path convert(String value) {
return Paths.get( value );
return Paths.get(value);
}
}

private static class InstantConverter implements IStringConverter<Instant> {

private static final Instant DATE_MIN = Instant.parse( "1980-01-01T00:00:02Z" );
private static final Instant DATE_MAX = Instant.parse( "2099-12-31T23:59:59Z" );
private static final Instant DATE_MIN = Instant.parse("1980-01-01T00:00:02Z");
private static final Instant DATE_MAX = Instant.parse("2099-12-31T23:59:59Z");

@Override
public Instant convert(String value) {
if ( value == null ) {
if (value == null) {
return null;
}

// Number representing seconds since the epoch
if ( !value.isEmpty() && isNumeric( value ) ) {
return Instant.ofEpochSecond( Long.parseLong( value.trim() ) );
if (!value.isEmpty() && isNumeric(value)) {
return Instant.ofEpochSecond(Long.parseLong(value.trim()));
}

try {
// Parse the date in UTC such as '2011-12-03T10:15:30Z' or with an offset '2019-10-05T20:37:42+06:00'.
final Instant date = OffsetDateTime.parse( value )
.withOffsetSameInstant( ZoneOffset.UTC ).truncatedTo( ChronoUnit.SECONDS ).toInstant();
final Instant date = OffsetDateTime.parse(value)
.withOffsetSameInstant(ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS).toInstant();

if ( date.isBefore( DATE_MIN ) || date.isAfter( DATE_MAX ) ) {
throw new IllegalArgumentException( "'" + date + "' is not within the valid range "
+ DATE_MIN + " to " + DATE_MAX );
if (date.isBefore(DATE_MIN) || date.isAfter(DATE_MAX)) {
throw new IllegalArgumentException("'" + date + "' is not within the valid range "
+ DATE_MIN + " to " + DATE_MAX);
}
return date;
}
catch ( DateTimeParseException pe ) {
throw new IllegalArgumentException( "Invalid project.build.outputTimestamp value '" + value + "'",
pe );
catch (DateTimeParseException pe) {
throw new IllegalArgumentException("Invalid project.build.outputTimestamp value '" + value + "'",
pe);
}
}

private boolean isNumeric( String str ) {
private boolean isNumeric(String str) {
try {
Long.parseLong( str.trim() );
Long.parseLong(str.trim());
return true;
} catch( NumberFormatException e ) {
}
catch (NumberFormatException e) {
return false;
}
}
Expand Down
123 changes: 62 additions & 61 deletions core/src/main/java/org/moditect/commands/AddModuleInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ public class AddModuleInfo {
private final boolean overwriteExistingFiles;
private final Instant timestamp;

public AddModuleInfo(String moduleInfoSource, String mainClass, String version, Path inputJar, Path outputDirectory, String jvmVersion, boolean overwriteExistingFiles, Instant timestamp) {
public AddModuleInfo(String moduleInfoSource, String mainClass, String version, Path inputJar, Path outputDirectory, String jvmVersion,
boolean overwriteExistingFiles, Instant timestamp) {
this.moduleInfoSource = moduleInfoSource;
this.mainClass = mainClass;
this.version = version;
Expand All @@ -74,90 +75,90 @@ public AddModuleInfo(String moduleInfoSource, String mainClass, String version,
}
}
catch (NumberFormatException e) {
throw new IllegalArgumentException("Invalid JVM Version: " + jvmVersion + ". Allowed values are 'base' and integer values >= 9." );
throw new IllegalArgumentException("Invalid JVM Version: " + jvmVersion + ". Allowed values are 'base' and integer values >= 9.");
}
}
this.overwriteExistingFiles = overwriteExistingFiles;
this.timestamp = timestamp;
}

public void run() {
if ( Files.isDirectory( inputJar ) ) {
throw new IllegalArgumentException( "Input JAR must not be a directory" );
if (Files.isDirectory(inputJar)) {
throw new IllegalArgumentException("Input JAR must not be a directory");
}

if ( !Files.exists( outputDirectory ) ) {
throw new IllegalArgumentException( "Output directory doesn't exist: " + outputDirectory);
if (!Files.exists(outputDirectory)) {
throw new IllegalArgumentException("Output directory doesn't exist: " + outputDirectory);
}

Path outputJar = outputDirectory.resolve( inputJar.getFileName() );
Path outputJar = outputDirectory.resolve(inputJar.getFileName());

if ( Files.exists( outputJar ) && !overwriteExistingFiles ) {
if (Files.exists(outputJar) && !overwriteExistingFiles) {
throw new RuntimeException(
"File " + outputJar + " already exists; either set 'overwriteExistingFiles' to true or specify another output directory" );
"File " + outputJar + " already exists; either set 'overwriteExistingFiles' to true or specify another output directory");
}

try {
Files.copy(inputJar, outputJar, StandardCopyOption.REPLACE_EXISTING);
}
catch(IOException e) {
throw new RuntimeException( "Couldn't copy JAR file", e );
catch (IOException e) {
throw new RuntimeException("Couldn't copy JAR file", e);
}

ModuleDeclaration module = ModuleInfoCompiler.parseModuleInfo( moduleInfoSource );
byte[] clazz = ModuleInfoCompiler.compileModuleInfo( module, mainClass, version );
ModuleDeclaration module = ModuleInfoCompiler.parseModuleInfo(moduleInfoSource);
byte[] clazz = ModuleInfoCompiler.compileModuleInfo(module, mainClass, version);

Map<String, String> env = new HashMap<>();
env.put( "create", "true" );
URI uri = URI.create( "jar:" + outputJar.toUri() );

try (FileSystem zipfs = FileSystems.newFileSystem( uri, env ) ) {
if (jvmVersion == null) {
Path path = zipfs.getPath("module-info.class");
Files.write(path, clazz,
StandardOpenOption.CREATE,
StandardOpenOption.WRITE,
StandardOpenOption.TRUNCATE_EXISTING );
Files.setLastModifiedTime( path, toFileTime(timestamp) );
}
else {
Path path = zipfs.getPath( "META-INF/versions", jvmVersion.toString(), "module-info.class" );
Files.createDirectories( path.getParent() );
Files.write( path, clazz,
StandardOpenOption.CREATE,
StandardOpenOption.WRITE,
StandardOpenOption.TRUNCATE_EXISTING );
FileTime lastModifiedTime = toFileTime( timestamp );
// module-info.class
Files.setLastModifiedTime( path, lastModifiedTime );
// jvmVersion
Files.setLastModifiedTime( path.getParent(), lastModifiedTime );
// versions
Files.setLastModifiedTime( path.getParent().getParent(), lastModifiedTime );

Path manifestPath = zipfs.getPath( "META-INF/MANIFEST.MF" );
Manifest manifest;
if ( Files.exists( manifestPath ) ) {
manifest = new Manifest( Files.newInputStream( manifestPath ) );
}
else {
manifest = new Manifest();
manifest.getMainAttributes().put( Attributes.Name.MANIFEST_VERSION, "1.0" );
}

manifest.getMainAttributes().put( new Attributes.Name("Multi-Release"), "true" );
try (OutputStream manifestOs = Files.newOutputStream( manifestPath, StandardOpenOption.TRUNCATE_EXISTING )) {
manifest.write( manifestOs );
}
Files.setLastModifiedTime( manifestPath, lastModifiedTime );
}
}
catch(IOException e) {
throw new RuntimeException( "Couldn't add module-info.class to JAR", e );
env.put("create", "true");
URI uri = URI.create("jar:" + outputJar.toUri());

try (FileSystem zipfs = FileSystems.newFileSystem(uri, env)) {
if (jvmVersion == null) {
Path path = zipfs.getPath("module-info.class");
Files.write(path, clazz,
StandardOpenOption.CREATE,
StandardOpenOption.WRITE,
StandardOpenOption.TRUNCATE_EXISTING);
Files.setLastModifiedTime(path, toFileTime(timestamp));
}
else {
Path path = zipfs.getPath("META-INF/versions", jvmVersion.toString(), "module-info.class");
Files.createDirectories(path.getParent());
Files.write(path, clazz,
StandardOpenOption.CREATE,
StandardOpenOption.WRITE,
StandardOpenOption.TRUNCATE_EXISTING);
FileTime lastModifiedTime = toFileTime(timestamp);
// module-info.class
Files.setLastModifiedTime(path, lastModifiedTime);
// jvmVersion
Files.setLastModifiedTime(path.getParent(), lastModifiedTime);
// versions
Files.setLastModifiedTime(path.getParent().getParent(), lastModifiedTime);

Path manifestPath = zipfs.getPath("META-INF/MANIFEST.MF");
Manifest manifest;
if (Files.exists(manifestPath)) {
manifest = new Manifest(Files.newInputStream(manifestPath));
}
else {
manifest = new Manifest();
manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
}

manifest.getMainAttributes().put(new Attributes.Name("Multi-Release"), "true");
try (OutputStream manifestOs = Files.newOutputStream(manifestPath, StandardOpenOption.TRUNCATE_EXISTING)) {
manifest.write(manifestOs);
}
Files.setLastModifiedTime(manifestPath, lastModifiedTime);
}
}
catch (IOException e) {
throw new RuntimeException("Couldn't add module-info.class to JAR", e);
}
}

private FileTime toFileTime( Instant timestamp ) {
return FileTime.from( timestamp != null ? timestamp : Instant.now() );
private FileTime toFileTime(Instant timestamp) {
return FileTime.from(timestamp != null ? timestamp : Instant.now());
}
}