Skip to content

Commit c93552c

Browse files
magicusAndrew Leonard
and
Andrew Leonard
committedDec 2, 2021
8277069: [REDO] JDK-8276743 Make openjdk build Zip Archive generation "reproducible"
Co-authored-by: Andrew Leonard <aleonard@openjdk.org> Co-authored-by: Magnus Ihse Bursie <ihse@openjdk.org> Reviewed-by: erikj
1 parent b8ac0d2 commit c93552c

File tree

4 files changed

+262
-7
lines changed

4 files changed

+262
-7
lines changed
 

‎make/Main.gmk

+5-5
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ $(eval $(call SetupTarget, vscode-project-ccls, \
324324
# aren't built until after libjava and libjvm are available to link to.
325325
$(eval $(call SetupTarget, demos-jdk, \
326326
MAKEFILE := CompileDemos, \
327-
DEPS := java.base-libs exploded-image, \
327+
DEPS := java.base-libs exploded-image buildtools-jdk, \
328328
))
329329

330330
$(eval $(call SetupTarget, test-image-demos-jdk, \
@@ -383,12 +383,12 @@ bootcycle-images:
383383

384384
$(eval $(call SetupTarget, zip-security, \
385385
MAKEFILE := ZipSecurity, \
386-
DEPS := java.base-java java.security.jgss-java java.security.jgss-libs, \
386+
DEPS := buildtools-jdk java.base-java java.security.jgss-java java.security.jgss-libs, \
387387
))
388388

389389
$(eval $(call SetupTarget, zip-source, \
390390
MAKEFILE := ZipSource, \
391-
DEPS := gensrc, \
391+
DEPS := buildtools-jdk gensrc, \
392392
))
393393

394394
$(eval $(call SetupTarget, jrtfs-jar, \
@@ -508,13 +508,13 @@ $(eval $(call SetupTarget, docs-jdk-index, \
508508
$(eval $(call SetupTarget, docs-zip, \
509509
MAKEFILE := Docs, \
510510
TARGET := docs-zip, \
511-
DEPS := docs-jdk, \
511+
DEPS := docs-jdk buildtools-jdk, \
512512
))
513513

514514
$(eval $(call SetupTarget, docs-specs-zip, \
515515
MAKEFILE := Docs, \
516516
TARGET := docs-specs-zip, \
517-
DEPS := docs-jdk-specs, \
517+
DEPS := docs-jdk-specs buildtools-jdk, \
518518
))
519519

520520
$(eval $(call SetupTarget, update-build-docs, \

‎make/ToolsJdk.gmk

+2
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ TOOL_GENERATECACERTS = $(JAVA_SMALL) -cp $(BUILDTOOLS_OUTPUTDIR)/jdk_tools_class
8282
TOOL_GENERATEEMOJIDATA = $(JAVA_SMALL) -cp $(BUILDTOOLS_OUTPUTDIR)/jdk_tools_classes \
8383
build.tools.generateemojidata.GenerateEmojiData
8484

85+
TOOL_MAKEZIPREPRODUCIBLE = $(JAVA_SMALL) -cp $(BUILDTOOLS_OUTPUTDIR)/jdk_tools_classes \
86+
build.tools.makezipreproducible.MakeZipReproducible
8587

8688
# TODO: There are references to the jdwpgen.jar in jdk/make/netbeans/jdwpgen/build.xml
8789
# and nbproject/project.properties in the same dir. Needs to be looked at.

‎make/common/ZipArchive.gmk

+24-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#
2-
# Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved.
2+
# Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved.
33
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
#
55
# This code is free software; you can redistribute it and/or modify it
@@ -26,6 +26,9 @@
2626
ifndef _ZIP_ARCHIVE_GMK
2727
_ZIP_ARCHIVE_GMK := 1
2828

29+
# Depends on build tools for MakeZipReproducible
30+
include ../ToolsJdk.gmk
31+
2932
ifeq (,$(_MAKEBASE_GMK))
3033
$(error You must include MakeBase.gmk prior to including ZipArchive.gmk)
3134
endif
@@ -51,6 +54,8 @@ endif
5154
# FOLLOW_SYMLINKS - Set to explicitly follow symlinks. Affects performance of
5255
# finding files.
5356
# ZIP_OPTIONS extra options to pass to zip
57+
# REPRODUCIBLE override ENABLE_REPRODUCIBLE_BUILD (to make zip reproducible or not)
58+
5459
SetupZipArchive = $(NamedParamsMacroTemplate)
5560
define SetupZipArchiveBody
5661

@@ -124,6 +129,10 @@ define SetupZipArchiveBody
124129
) \
125130
)
126131

132+
ifeq ($$($1_REPRODUCIBLE), )
133+
$1_REPRODUCIBLE := $$(ENABLE_REPRODUCIBLE_BUILD)
134+
endif
135+
127136
# Use a slightly shorter name for logging, but with enough path to identify this zip.
128137
$1_NAME:=$$(subst $$(OUTPUTDIR)/,,$$($1_ZIP))
129138

@@ -134,6 +143,8 @@ define SetupZipArchiveBody
134143
# dir is very small.
135144
# If zip has nothing to do, it returns 12 and would fail the build. Check for 12
136145
# and only fail if it's not.
146+
# For reproducible builds set the zip access & modify times to SOURCE_DATE_EPOCH
147+
# by using a ziptmp folder to generate final zip from using MakeZipReproducible.
137148
$$($1_ZIP) : $$($1_ALL_SRCS) $$($1_EXTRA_DEPS)
138149
$$(call LogWarn, Updating $$($1_NAME))
139150
$$(call MakeTargetDir)
@@ -163,7 +174,18 @@ define SetupZipArchiveBody
163174
$$($1_ZIP_EXCLUDES_$$s) \
164175
|| test "$$$$?" = "12" \
165176
))$$(NEWLINE) \
166-
) true \
177+
) true
178+
ifeq ($$($1_REPRODUCIBLE), true)
179+
$$(call ExecuteWithLog, \
180+
$$(SUPPORT_OUTPUTDIR)/makezipreproducible/$$(patsubst $$(OUTPUTDIR)/%,%, $$@), \
181+
($(RM) $$(SUPPORT_OUTPUTDIR)/ziptmp/$1/tmp.zip && \
182+
$(MKDIR) -p $$(SUPPORT_OUTPUTDIR)/ziptmp/$1 && \
183+
$(TOOL_MAKEZIPREPRODUCIBLE) -f $$(SUPPORT_OUTPUTDIR)/ziptmp/$1/tmp.zip \
184+
-t $(SOURCE_DATE_EPOCH) $$@ && \
185+
$(RM) $$@ && \
186+
$(MV) $$(SUPPORT_OUTPUTDIR)/ziptmp/$1/tmp.zip $$@ \
187+
))
188+
endif
167189
$(TOUCH) $$@
168190

169191
# Add zip to target list
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
/*
2+
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
package build.tools.makezipreproducible;
25+
26+
import java.io.*;
27+
import java.nio.file.*;
28+
import java.nio.file.attribute.BasicFileAttributes;
29+
import java.nio.channels.Channels;
30+
import java.nio.channels.FileChannel;
31+
import java.util.*;
32+
import java.util.zip.ZipEntry;
33+
import java.util.zip.ZipException;
34+
import java.util.zip.ZipFile;
35+
import java.util.zip.ZipInputStream;
36+
import java.util.zip.ZipOutputStream;
37+
38+
/**
39+
* Generate a zip file in a "reproducible" manner from the input zip file.
40+
* Standard zip tools rely on OS file list querying whose ordering can vary
41+
* by platform architecture, this class ensures the zip entries are ordered
42+
* and also supports SOURCE_DATE_EPOCH timestamps.
43+
*/
44+
public class MakeZipReproducible {
45+
String input_file = null;
46+
String fname = null;
47+
String zname = "";
48+
long timestamp = -1L;
49+
boolean verbose = false;
50+
51+
// Keep a sorted Set of ZipEntrys to be processed, so that the zip is reproducible
52+
SortedMap<String, ZipEntry> entries = new TreeMap<String, ZipEntry>();
53+
54+
private boolean ok;
55+
56+
public MakeZipReproducible() {
57+
}
58+
59+
public synchronized boolean run(String args[]) {
60+
ok = true;
61+
if (!parseArgs(args)) {
62+
return false;
63+
}
64+
try {
65+
zname = fname.replace(File.separatorChar, '/');
66+
if (zname.startsWith("./")) {
67+
zname = zname.substring(2);
68+
}
69+
70+
if (verbose) System.out.println("Input zip file: " + input_file);
71+
72+
File inFile = new File(input_file);
73+
if (!inFile.exists()) {
74+
error("Input zip file does not exist");
75+
ok = false;
76+
} else {
77+
File zipFile = new File(fname);
78+
// Check archive to create does not exist
79+
if (!zipFile.exists()) {
80+
// Process input ZipEntries
81+
ok = processInputEntries(inFile);
82+
if (ok) {
83+
try (FileOutputStream out = new FileOutputStream(fname)) {
84+
ok = create(inFile, new BufferedOutputStream(out, 4096));
85+
}
86+
} else {
87+
}
88+
} else {
89+
error("Target zip file "+fname+" already exists.");
90+
ok = false;
91+
}
92+
}
93+
} catch (IOException e) {
94+
fatalError(e);
95+
ok = false;
96+
} catch (Error ee) {
97+
ee.printStackTrace();
98+
ok = false;
99+
} catch (Throwable t) {
100+
t.printStackTrace();
101+
ok = false;
102+
}
103+
return ok;
104+
}
105+
106+
boolean parseArgs(String args[]) {
107+
try {
108+
boolean parsingIncludes = false;
109+
boolean parsingExcludes = false;
110+
int count = 0;
111+
while(count < args.length) {
112+
if (args[count].startsWith("-")) {
113+
String flag = args[count].substring(1);
114+
switch (flag.charAt(0)) {
115+
case 'f':
116+
fname = args[++count];
117+
break;
118+
case 't':
119+
// SOURCE_DATE_EPOCH timestamp specified
120+
timestamp = Long.parseLong(args[++count]) * 1000;
121+
break;
122+
case 'v':
123+
verbose = true;
124+
break;
125+
default:
126+
error(String.format("Illegal option -%s", String.valueOf(flag.charAt(0))));
127+
usageError();
128+
return false;
129+
}
130+
} else {
131+
// input zip file
132+
if (input_file != null) {
133+
error("Input zip file already specified");
134+
usageError();
135+
return false;
136+
}
137+
input_file = args[count];
138+
}
139+
count++;
140+
}
141+
} catch (ArrayIndexOutOfBoundsException e) {
142+
usageError();
143+
return false;
144+
} catch (NumberFormatException e) {
145+
usageError();
146+
return false;
147+
}
148+
if (fname == null) {
149+
error("-f <outputArchiveName> must be specified");
150+
usageError();
151+
return false;
152+
}
153+
// If no files specified then default to current directory
154+
if (input_file == null) {
155+
error("No input zip file specified");
156+
usageError();
157+
return false;
158+
}
159+
160+
return true;
161+
}
162+
163+
// Process input zip file and add to sorted entries set
164+
boolean processInputEntries(File inFile) throws IOException {
165+
ZipFile zipFile = new ZipFile(inFile);
166+
zipFile.stream().forEach(entry -> entries.put(entry.getName(), entry));
167+
168+
return true;
169+
}
170+
171+
// Create new zip from entries
172+
boolean create(File inFile, OutputStream out) throws IOException
173+
{
174+
try (ZipFile zipFile = new ZipFile(inFile);
175+
ZipOutputStream zos = new ZipOutputStream(out)) {
176+
for (Map.Entry<String, ZipEntry> entry : entries.entrySet()) {
177+
ZipEntry zipEntry = entry.getValue();
178+
if (zipEntry.getSize() > 0) {
179+
try (InputStream eis = zipFile.getInputStream(zipEntry)) {
180+
addEntry(zos, zipEntry, eis);
181+
}
182+
} else {
183+
addEntry(zos, zipEntry, null);
184+
}
185+
}
186+
}
187+
return true;
188+
}
189+
190+
// Add Entry and data to Zip
191+
void addEntry(ZipOutputStream zos, ZipEntry entry, InputStream entryInputStream) throws IOException {
192+
if (verbose) {
193+
System.out.println("Adding: "+entry.getName());
194+
}
195+
196+
// Set to specified timestamp if set otherwise leave as original lastModified time
197+
if (timestamp != -1L) {
198+
entry.setTime(timestamp);
199+
}
200+
201+
zos.putNextEntry(entry);
202+
if (entry.getSize() > 0 && entryInputStream != null) {
203+
entryInputStream.transferTo(zos);
204+
}
205+
zos.closeEntry();
206+
}
207+
208+
void usageError() {
209+
error(
210+
"Usage: MakeZipReproducible [-v] [-t <SOURCE_DATE_EPOCH>] -f <output_zip_file> <input_zip_file>\n" +
211+
"Options:\n" +
212+
" -v verbose output\n" +
213+
" -f specify archive file name to create\n" +
214+
" -t specific SOURCE_DATE_EPOCH value to use for timestamps\n" +
215+
" input_zip_file re-written as a reproducible zip output_zip_file.\n");
216+
}
217+
218+
void fatalError(Exception e) {
219+
e.printStackTrace();
220+
}
221+
222+
protected void error(String s) {
223+
System.err.println(s);
224+
}
225+
226+
public static void main(String args[]) {
227+
MakeZipReproducible z = new MakeZipReproducible();
228+
System.exit(z.run(args) ? 0 : 1);
229+
}
230+
}
231+

0 commit comments

Comments
 (0)
Please sign in to comment.