8272984: javadoc support for reproducible builds

Reviewed-by: hannesw
This commit is contained in:
Jonathan Gibbons 2022-01-31 22:54:18 +00:00
parent ee3be0bb56
commit 96d0df72db
10 changed files with 255 additions and 21 deletions
src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets
test/langtools/jdk/javadoc

@ -1,5 +1,5 @@
/*
* Copyright (c) 1998, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 2022, 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
@ -25,6 +25,7 @@
package jdk.javadoc.internal.doclets.formats.html;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.EnumSet;
@ -166,6 +167,11 @@ public class HtmlConfiguration extends BaseConfiguration {
*/
public final Set<ConditionalPage> conditionalPages;
/**
* The build date, to be recorded in generated files.
*/
private ZonedDateTime buildDate;
/**
* Constructs the full configuration needed by the doclet, including
* the format-specific part, defined in this class, and the format-independent
@ -215,6 +221,7 @@ public class HtmlConfiguration extends BaseConfiguration {
conditionalPages = EnumSet.noneOf(ConditionalPage.class);
}
protected void initConfiguration(DocletEnvironment docEnv,
Function<String, String> resourceKeyMapper) {
super.initConfiguration(docEnv, resourceKeyMapper);
@ -223,7 +230,6 @@ public class HtmlConfiguration extends BaseConfiguration {
}
private final Runtime.Version docletVersion;
public final Date startTime = new Date();
@Override
public Runtime.Version getDocletVersion() {
@ -259,6 +265,10 @@ public class HtmlConfiguration extends BaseConfiguration {
if (!options.validateOptions()) {
return false;
}
ZonedDateTime zdt = options.date();
buildDate = zdt != null ? zdt : ZonedDateTime.now();
if (!getSpecifiedTypeElements().isEmpty()) {
Map<String, PackageElement> map = new HashMap<>();
PackageElement pkg;
@ -279,6 +289,13 @@ public class HtmlConfiguration extends BaseConfiguration {
return true;
}
/**
* {@return the date to be recorded in generated files}
*/
public ZonedDateTime getBuildDate() {
return buildDate;
}
/**
* Decide the page which will appear first in the right-hand frame. It will
* be "overview-summary.html" if "-overview" option is used or no

@ -1,5 +1,5 @@
/*
* Copyright (c) 1998, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 2022, 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
@ -453,7 +453,7 @@ public class HtmlDocletWriter {
throws DocFileIOException {
List<DocPath> additionalStylesheets = configuration.getAdditionalStylesheets();
additionalStylesheets.addAll(localStylesheets);
Head head = new Head(path, configuration.getDocletVersion(), configuration.startTime)
Head head = new Head(path, configuration.getDocletVersion(), configuration.getBuildDate())
.setTimestamp(!options.noTimestamp())
.setDescription(description)
.setGenerator(getGenerator(getClass()))

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 2022, 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
@ -74,7 +74,7 @@ public class IndexRedirectWriter extends HtmlDocletWriter {
* @throws DocFileIOException if there is a problem generating the file
*/
private void generateIndexFile() throws DocFileIOException {
Head head = new Head(path, configuration.getDocletVersion(), configuration.startTime)
Head head = new Head(path, configuration.getDocletVersion(), configuration.getBuildDate())
.setTimestamp(!options.noTimestamp())
.setDescription("index redirect")
.setGenerator(getGenerator(getClass()))

@ -1,5 +1,5 @@
/*
* Copyright (c) 2001, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2001, 2022, 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
@ -232,7 +232,7 @@ public class SourceToHTMLConverter {
* @param path the path for the file.
*/
private void writeToFile(Content body, DocPath path, TypeElement te) throws DocFileIOException {
Head head = new Head(path, configuration.getDocletVersion(), configuration.startTime)
Head head = new Head(path, configuration.getDocletVersion(), configuration.getBuildDate())
// .setTimestamp(!options.notimestamp) // temporary: compatibility!
.setTitle(resources.getText("doclet.Window_Source_title"))
// .setCharset(options.charset) // temporary: compatibility!

@ -27,12 +27,14 @@ package jdk.javadoc.internal.doclets.formats.html.markup;
import java.io.IOException;
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import jdk.javadoc.internal.doclets.toolkit.Content;
import jdk.javadoc.internal.doclets.toolkit.util.DocPath;
@ -50,7 +52,7 @@ import jdk.javadoc.internal.doclets.toolkit.util.DocPaths;
*/
public class Head extends Content {
private final Runtime.Version docletVersion;
private final Date generatedDate;
private final ZonedDateTime generatedDate;
private final DocPath pathToRoot;
private String title;
private String charset;
@ -78,7 +80,7 @@ public class Head extends Content {
* @param path the path for the file that will include this HEAD element
* @param docletVersion the doclet version
*/
public Head(DocPath path, Runtime.Version docletVersion, Date generatedDate) {
public Head(DocPath path, Runtime.Version docletVersion, ZonedDateTime generatedDate) {
this.docletVersion = docletVersion;
this.generatedDate = generatedDate;
pathToRoot = path.parent().invert();
@ -279,8 +281,8 @@ public class Head extends Content {
}
if (showTimestamp) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
tree.add(HtmlTree.META("dc.created", dateFormat.format(generatedDate)));
DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd");
tree.add(HtmlTree.META("dc.created", generatedDate.format(dateFormat)));
}
if (description != null) {
@ -309,11 +311,14 @@ public class Head extends Content {
return tree;
}
private Comment getGeneratedBy(boolean timestamp, Date now) {
private Comment getGeneratedBy(boolean timestamp, ZonedDateTime buildDate) {
String text = "Generated by javadoc"; // marker string, deliberately not localized
text += " (" + docletVersion.feature() + ")";
if (timestamp) {
text += " on " + now;
DateTimeFormatter fmt =
DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss zzz yyyy").withLocale(Locale.US);
text += " on " + buildDate.format(fmt);
}
return new Comment(text);
}

@ -1,5 +1,5 @@
#
# Copyright (c) 2010, 2021, Oracle and/or its affiliates. All rights reserved.
# Copyright (c) 2010, 2022, 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
@ -169,6 +169,12 @@ doclet.systemPropertiesSummary=System Properties Summary
doclet.Window_Source_title=Source code
doclet.Window_Help_title=API Help
# 0: a date
doclet.Option_date_out_of_range=value for ''--date'' out of range: {0}
# 0: a date
doclet.Option_date_not_valid=value for ''--date'' not valid: {0}
doclet.help.main_heading=\
JavaDoc Help
doclet.help.navigation.head=\
@ -420,6 +426,12 @@ doclet.usage.windowtitle.parameters=\
doclet.usage.windowtitle.description=\
Browser window title for the documentation
doclet.usage.date.parameters=\
<date-and-time>
doclet.usage.date.description=\
Specifies the value to be used to timestamp the generated\n\
pages, in ISO 8601 format
doclet.usage.doctitle.parameters=\
<html-code>
doclet.usage.doctitle.description=\

@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2022, 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
@ -30,8 +30,16 @@ import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
@ -82,6 +90,12 @@ public abstract class BaseOptions {
*/
private final LinkedHashSet<List<String>> customTagStrs = new LinkedHashSet<>();
/**
* Argument for command-line option {@code --date}.
* {@code null} if option not given.
*/
private ZonedDateTime date;
/**
* Argument for command-line option {@code -d}.
* Destination directory name, in which doclet will generate the entire
@ -338,6 +352,33 @@ public abstract class BaseOptions {
}
},
new XOption(resources, "--date", 1) {
// Valid --date range: within ten years of now
private static final ZonedDateTime now = ZonedDateTime.now();
static final ZonedDateTime DATE_MIN = now.minusYears(10);
static final ZonedDateTime DATE_MAX = now.plusYears(10);
@Override
public boolean process(String opt, List<String> args) {
if (noTimestamp) {
messages.error("doclet.Option_conflict", "--date", "-notimestamp");
return false;
}
String arg = args.get(0);
try {
date = ZonedDateTime.parse(arg, DateTimeFormatter.ISO_ZONED_DATE_TIME);
if (date.isBefore(DATE_MIN) || date.isAfter(DATE_MAX)) {
messages.error("doclet.Option_date_out_of_range", arg);
return false;
}
return true;
} catch (DateTimeParseException x) {
messages.error("doclet.Option_date_not_valid", arg);
return false;
}
}
},
new Option(resources, "-docencoding", 1) {
@Override
public boolean process(String opt, List<String> args) {
@ -471,6 +512,10 @@ public abstract class BaseOptions {
@Override
public boolean process(String opt, List<String> args) {
noTimestamp = true;
if (date != null) {
messages.error("doclet.Option_conflict", "--date", "-notimestamp");
return false;
}
return true;
}
},
@ -739,6 +784,13 @@ public abstract class BaseOptions {
return customTagStrs;
}
/**
* Argument for command-line option {@code --date}.
*/
public ZonedDateTime date() {
return date;
}
/**
* Argument for command-line option {@code -d}.
* Destination directory name, in which doclet will generate the entire

@ -0,0 +1,147 @@
/*
* Copyright (c) 2022, 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
* 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.
*/
/*
* @test
* @bug 8272984
* @summary javadoc support for SOURCE_DATE_EPOCH
* @library /tools/lib ../../lib
* @modules jdk.javadoc/jdk.javadoc.internal.tool
* @build toolbox.ToolBox javadoc.tester.*
* @run main TestDateOption
*/
import java.nio.file.Path;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.Locale;
import javadoc.tester.JavadocTester;
import toolbox.ToolBox;
public class TestDateOption extends JavadocTester {
/**
* The entry point of the test.
*
* @param args the array of command line arguments
* @throws Exception if the test fails
*/
public static void main(String... args) throws Exception {
TestDateOption tester = new TestDateOption();
tester.runTests(m -> new Object[] { Path.of(m.getName()) });
}
ToolBox tb = new ToolBox();
@Test
public void testDateOption(Path base) throws Exception {
ZonedDateTime zdt = ZonedDateTime.now(); // uses current date, time, timezone etc
// adjust the calendar to some date before the default used by javadoc (i.e. today/now)
// set a specific time, such as 10 to 3. (Rupert Brooke, Grantchester)
ZonedDateTime testDate = zdt.minusDays(100)
.withHour(14)
.withMinute(50)
.withSecond(0);
out.println("Test Date: '" + testDate + "'");
Path srcDir = base.resolve("src");
tb.writeJavaFiles(srcDir, """
package p;
/** Comment. */
public interface I { }
""");
Path outDir = base.resolve("out");
javadoc("-d", outDir.toString(),
"-sourcepath", srcDir.toString(),
"--date", testDate.toString(),
"p");
checkExit(Exit.OK);
int featureVersion = Runtime.version().feature();
// The following format is as used by javadoc; it is the historical format used by Date.toString()
DateTimeFormatter fmt =
DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss zzz yyyy").withLocale(Locale.US);
String generatedByStamp = testDate.format(fmt);
String generatedBy = String.format("<!-- Generated by javadoc (%d) on %s -->",
featureVersion, generatedByStamp);
DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String dcCreatedStamp = testDate.format(dateFormat);
String dcCreated = String.format("""
<meta name="dc.created" content="%s">""",
dcCreatedStamp);
// check the timestamps in all generated HTML files
for (Path file : tb.findFiles(".html", outputDir)) {
checkOutput(outputDir.relativize(file).toString(), true,
generatedBy,
dcCreated);
}
}
@Test
public void testBadDateOption(Path base) throws Exception {
Path srcDir = base.resolve("src");
tb.writeJavaFiles(srcDir, """
package p;
/** Comment. */
public interface I { }
""");
Path outDir = base.resolve("out");
javadoc("-d", outDir.toString(),
"-sourcepath", srcDir.toString(),
"--date", "NOT A DATE",
"p");
checkExit(Exit.CMDERR);
checkOutput(Output.OUT, true,
"error: value for '--date' not valid: NOT A DATE");
}
@Test
public void testInvalidDateOption(Path base) throws Exception {
Path srcDir = base.resolve("src");
tb.writeJavaFiles(srcDir, """
package p;
/** Comment. */
public interface I { }
""");
Path outDir = base.resolve("out");
javadoc("-d", outDir.toString(),
"-sourcepath", srcDir.toString(),
"--date", new Date(0).toInstant().toString(),
"p");
checkExit(Exit.CMDERR);
checkOutput(Output.OUT, true,
"error: value for '--date' out of range: 1970-01-01T00:00:00Z");
}
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2003, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2003, 2022, 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
@ -98,6 +98,7 @@ public class TestXOption extends JavadocTester {
"-Xmaxwarns ",
"-Xdocrootparent ",
"-Xdoclint ",
"-Xdoclint:");
"-Xdoclint:",
"--date ");
}
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2021, 2022, 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
@ -66,7 +66,7 @@ public class CheckManPageOptions {
static final PrintStream out = System.err;
List<String> MISSING_IN_MAN_PAGE = List.of();
List<String> MISSING_IN_MAN_PAGE = List.of("--date");
void run(String... args) throws Exception {
var file = args.length == 0 ? findDefaultFile() : Path.of(args[0]);