Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions make/ToolsJdk.gmk
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ TOOL_GENERATECACERTS = $(JAVA_SMALL) -cp $(BUILDTOOLS_OUTPUTDIR)/jdk_tools_class
TOOL_GENERATEEMOJIDATA = $(JAVA_SMALL) -cp $(BUILDTOOLS_OUTPUTDIR)/jdk_tools_classes \
build.tools.generateemojidata.GenerateEmojiData

TOOL_GENERATEZIP = $(JAVA_SMALL) -cp $(BUILDTOOLS_OUTPUTDIR)/jdk_tools_classes \
build.tools.generatezip.GenerateZip

# TODO: There are references to the jdwpgen.jar in jdk/make/netbeans/jdwpgen/build.xml
# and nbproject/project.properties in the same dir. Needs to be looked at.
Expand Down
18 changes: 15 additions & 3 deletions make/common/ZipArchive.gmk
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#
# Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved.
# Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#
# This code is free software; you can redistribute it and/or modify it
Expand All @@ -26,6 +26,8 @@
ifndef _ZIP_ARCHIVE_GMK
_ZIP_ARCHIVE_GMK := 1

include ../ToolsJdk.gmk

ifeq (,$(_MAKEBASE_GMK))
$(error You must include MakeBase.gmk prior to including ZipArchive.gmk)
endif
Expand Down Expand Up @@ -134,6 +136,8 @@ define SetupZipArchiveBody
# dir is very small.
# If zip has nothing to do, it returns 12 and would fail the build. Check for 12
# and only fail if it's not.
# For reproducible builds the zip does not support SOURCE_DATE_EPOCH and will not
# produce determinsitic output, instead use GenerateZip jdk tool to create final zip.
$$($1_ZIP) : $$($1_ALL_SRCS) $$($1_EXTRA_DEPS)
$$(call LogWarn, Updating $$($1_NAME))
$$(call MakeTargetDir)
Expand All @@ -156,14 +160,22 @@ define SetupZipArchiveBody
) \
) \
)
$(RM) -r $$(SUPPORT_OUTPUTDIR)/ziptmp/$1 && $(MKDIR) -p $$(SUPPORT_OUTPUTDIR)/ziptmp/$1/files
$$(foreach s,$$($1_SRC_SLASH), $$(call ExecuteWithLog, \
$$(SUPPORT_OUTPUTDIR)/zip/$$(patsubst $$(OUTPUTDIR)/%,%, $$@), \
(cd $$s && $(ZIPEXE) -qru $$($1_ZIP_OPTIONS) $$@ . \
(cd $$s && $(ZIPEXE) -qru $$($1_ZIP_OPTIONS) $$(SUPPORT_OUTPUTDIR)/ziptmp/$1/tmp.zip . \
$$($1_ZIP_INCLUDES) $$($1_ZIP_EXCLUDES) -x \*_the.\* \
$$($1_ZIP_EXCLUDES_$$s) \
|| test "$$$$?" = "12" \
))$$(NEWLINE) \
) true \
) true
$$(call ExecuteWithLog, \
$$(SUPPORT_OUTPUTDIR)/generatezip/$$(patsubst $$(OUTPUTDIR)/%,%, $$@), \
(cd $$(SUPPORT_OUTPUTDIR)/ziptmp/$1/files && \
$(RM) $$@ && \
$(UNZIP) -q $$(SUPPORT_OUTPUTDIR)/ziptmp/$1/tmp.zip && \
$(TOOL_GENERATEZIP) -f $$@ . \
))$$(NEWLINE) \
$(TOUCH) $$@

# Add zip to target list
Expand Down
299 changes: 299 additions & 0 deletions make/jdk/src/classes/build/tools/generatezip/GenerateZip.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,299 @@
/*
* Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved.
Copy link
Member

Choose a reason for hiding this comment

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

Hello @andrew-m-leonard, for a new file, the year should just be stated once. So just 2021,

* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/

package build.tools.generatezip;

import java.io.*;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

/**
* Generate a zip file in a "reproducible" manner from the input files or directory.
* Standard zip tools rely on OS file list querying whose ordering varies by platform architecture,
* this class ensures the zip entries are sorted and also supports SOURCE_DATE_EPOCH timestamps.
*/
public class GenerateZip {
String fname = null;
String zname = "";
List<String> files = new ArrayList<>();;
boolean verbose = false;

Set<File> entries = new LinkedHashSet<>();

private boolean ok;

/* Cache SOURCE_DATE_EPOCH if set for reproducible Jar content */
private static long sourceDateEpochMillis = -1;
static {
String env = System.getenv("SOURCE_DATE_EPOCH");
if (env != null) {
try {
long value = Long.parseLong(env);
// SOURCE_DATE_EPOCH is in seconds
sourceDateEpochMillis = value*1000;
} catch(NumberFormatException e) {
sourceDateEpochMillis = -1;
}
}
}

public GenerateZip() {
}

public synchronized boolean run(String args[]) {
ok = true;
if (!parseArgs(args)) {
return false;
}
try {
zname = fname.replace(File.separatorChar, '/');
if (zname.startsWith("./")) {
zname = zname.substring(2);
}

if (verbose) System.out.println("Files or directories to zip: "+files);

File zipFile = new File(fname);
// Check archive to create does not exist
if (!zipFile.exists()) {
// Process Files
for(String file : files) {
Path filepath = Paths.get(file);
processFiles(filepath);
}

FileOutputStream out = new FileOutputStream(fname);
boolean createOk = create(new BufferedOutputStream(out, 4096));
if (ok) {
ok = createOk;
}
out.close();
Comment on lines +91 to +96
Copy link
Contributor

Choose a reason for hiding this comment

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

could be a try-with-resource block

} else {
error("Target zip file "+fname+" already exists.");
ok = false;
}
} catch (IOException e) {
fatalError(e);
ok = false;
} catch (Error ee) {
ee.printStackTrace();
ok = false;
} catch (Throwable t) {
t.printStackTrace();
ok = false;
}
return ok;
}

boolean parseArgs(String args[]) {
try {
boolean parsingIncludes = false;
boolean parsingExcludes = false;
int count = 0;
while(count < args.length) {
if (args[count].startsWith("-")) {
String flag = args[count].substring(1);
switch (flag.charAt(0)) {
case 'f':
fname = args[++count];
break;
case 'v':
verbose = true;
break;
default:
error(String.format("Illegal option -%s", String.valueOf(flag.charAt(0))));
usageError();
return false;
}
} else {
// file or dir to zip
files.add(args[count]);
}
count++;
}
} catch (ArrayIndexOutOfBoundsException e) {
usageError();
return false;
}
if (fname == null) {
error(String.format("-f <archiveName> must be specified"));
usageError();
return false;
}
// If no files specified then default to current directory
if (files.size() == 0) {
error("No input directory or files were specified");
usageError();
return false;
}

return true;
}

// Walk tree matching files and adding to entries list
void processFiles(Path path) throws IOException {
File fpath = path.toFile();
boolean pathIsDir = fpath.isDirectory();

// Keep a sorted Set of files to be processed, so that the Jmod is reproducible
// as Files.walkFileTree order is not defined
SortedMap<String, Path> filesToProcess = new TreeMap<String, Path>();

Files.walkFileTree(path, Set.of(FileVisitOption.FOLLOW_LINKS),
Integer.MAX_VALUE, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException
{
Path relPath;
String name;
if (pathIsDir) {
relPath = path.relativize(file);
name = relPath.toString();
} else {
relPath = file;
name = file.toString();
}
filesToProcess.put(name, file);
return FileVisitResult.CONTINUE;
}
});

// Process files in sorted order
Iterator<Map.Entry<String, Path>> itr = filesToProcess.entrySet().iterator();
while(itr.hasNext()) {
Map.Entry<String, Path> entry = itr.next();
Comment on lines +189 to +191
Copy link
Contributor

Choose a reason for hiding this comment

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

could be for (Map.Entry<String, Path> entry : filesToProcess.entrySet())

String name = entry.getKey();
Path filepath = entry.getValue();

File f = filepath.toFile();
entries.add(f);
}
}

// Create new zip from entries
boolean create(OutputStream out) throws IOException
{
try (ZipOutputStream zos = new ZipOutputStream(out)) {
for (File file: entries) {
addFile(zos, file);
}
}
return true;
}

// Ensure a consistent entry name format
String entryName(String name) {
name = name.replace(File.separatorChar, '/');

if (name.startsWith("/")) {
name = name.substring(1);
} else if (name.startsWith("./")) {
name = name.substring(2);
}
return name;
}

// Add File to Zip
void addFile(ZipOutputStream zos, File file) throws IOException {
String name = file.getPath();
boolean isDir = file.isDirectory();
if (isDir) {
name = name.endsWith(File.separator) ? name : (name + File.separator);
}
name = entryName(name);

if (name.equals("") || name.equals(".") || name.equals(zname)) {
return;
}

long size = isDir ? 0 : file.length();

if (verbose) {
System.out.println("Adding: "+name);
}

ZipEntry e = new ZipEntry(name);
// If we are adding a new entry and SOURCE_DATE_EPOCH is set then use that time
if (sourceDateEpochMillis != -1) {
e.setTime(sourceDateEpochMillis);
} else {
e.setTime(file.lastModified());
}
if (size == 0) {
e.setMethod(ZipEntry.STORED);
e.setSize(0);
e.setCrc(0);
}
zos.putNextEntry(e);
if (!isDir) {
byte[] buf = new byte[8192];
int len;
InputStream is = new BufferedInputStream(new FileInputStream(file));
while ((len = is.read(buf, 0, buf.length)) != -1) {
zos.write(buf, 0, len);
}
is.close();
Comment on lines +258 to +262
Copy link
Contributor

Choose a reason for hiding this comment

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

try-with-resource candidate + in.transferTo(zos)

}
zos.closeEntry();
}

void usageError() {
error(
"Usage: GenerateZip [-v] -f <zip_file> <files_or_directories>\n" +
"Options:\n" +
" -v verbose output\n" +
" -f specify archive file name to create\n" +
"If any file is a directory then it is processed recursively.\n");
}

void fatalError(Exception e) {
e.printStackTrace();
}

protected void error(String s) {
System.err.println(s);
}

public static String[] parse(String[] args)
{
ArrayList<String> newArgs = new ArrayList<String>(args.length);
for (int i = 0; i < args.length; i++) {
String arg = args[i];
newArgs.add(arg);
}
return newArgs.toArray(new String[newArgs.size()]);
Comment on lines +286 to +291
Copy link
Contributor

Choose a reason for hiding this comment

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

something might be missing here. It just adds args to a list and makes it an array again + If the language level allows it toArray(String[]::new) could be used.

}

public static void main(String args[]) {
GenerateZip z = new GenerateZip();
System.exit(z.run(args) ? 0 : 1);
}
}

Loading