IJI-772 reproducibility test: build date time supplied externally for JPS project artifacts

GitOrigin-RevId: b3e778ab6ba2ecb547c988bc267939884427031c
This commit is contained in:
Dmitriy.Panov
2021-12-23 01:51:25 +03:00
committed by intellij-monorepo-bot
parent 505c26d0aa
commit cec73c0390
3 changed files with 59 additions and 3 deletions

View File

@@ -50,4 +50,9 @@ public interface GlobalOptions {
* This will allow JPS process to access bundle's resources and provide localized error/warning/diagnostic messages
*/
String LANGUAGE_BUNDLE = "jps.language.bundle";
/**
* See https://reproducible-builds.org/specs/source-date-epoch/
*/
String BUILD_DATE_IN_SECONDS = "SOURCE_DATE_EPOCH";
}

View File

@@ -15,6 +15,7 @@ import com.intellij.util.io.ZipUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.api.GlobalOptions;
import org.jetbrains.jps.builders.BuildOutputConsumer;
import org.jetbrains.jps.builders.JpsBuildBundle;
import org.jetbrains.jps.builders.logging.ProjectBuilderLogger;
@@ -44,6 +45,7 @@ public final class JarsBuilder {
private Map<JarInfo, File> myBuiltJars;
private final BuildOutputConsumer myOutputConsumer;
private final ArtifactOutputToSourceMapping myOutSrcMapping;
private final @Nullable Long buildDateInMillis;
public JarsBuilder(Set<JarInfo> jarsToBuild, CompileContext context, BuildOutputConsumer outputConsumer,
ArtifactOutputToSourceMapping outSrcMapping) {
@@ -55,6 +57,11 @@ public final class JarsBuilder {
}
myJarsToBuild = evaluator.getJars();
myContext = context;
String buildDateInSeconds = context.getBuilderParameter(GlobalOptions.BUILD_DATE_IN_SECONDS);
if (buildDateInSeconds == null) {
buildDateInSeconds = System.getenv(GlobalOptions.BUILD_DATE_IN_SECONDS);
}
buildDateInMillis = buildDateInSeconds != null ? Long.valueOf(buildDateInSeconds) * 1000 : null;
}
public boolean buildJars() throws IOException, ProjectBuildException {
@@ -282,7 +289,7 @@ public final class JarsBuilder {
private void extractFileAndAddToJar(final JarOutputStream jarOutputStream, final JarBasedArtifactRootDescriptor root,
final String relativeOutputPath, final Set<? super String> writtenPaths)
throws IOException {
final long timestamp = FSOperations.lastModified(root.getRootFile());
final long timestamp = buildDateInMillis != null ? buildDateInMillis : FSOperations.lastModified(root.getRootFile());
root.processEntries(new JarBasedArtifactRootDescriptor.EntryProcessor() {
@Override
public void process(@Nullable InputStream inputStream, @NotNull String relativePath, ZipEntry entry) throws IOException {
@@ -369,7 +376,7 @@ public final class JarsBuilder {
}
private static String addParentDirectories(JarOutputStream jarOutputStream, Set<? super String> writtenPaths, String relativePath) throws IOException {
private String addParentDirectories(JarOutputStream jarOutputStream, Set<? super String> writtenPaths, String relativePath) throws IOException {
while (StringUtil.startsWithChar(relativePath, '/')) {
relativePath = relativePath.substring(1);
}
@@ -384,10 +391,13 @@ public final class JarsBuilder {
return relativePath;
}
private static void addDirectoryEntry(final ZipOutputStream output, @NonNls final String relativePath, Set<? super String> writtenPaths) throws IOException {
private void addDirectoryEntry(final ZipOutputStream output, @NonNls final String relativePath, Set<? super String> writtenPaths) throws IOException {
if (!writtenPaths.add(relativePath)) return;
ZipEntry e = new ZipEntry(relativePath);
if (buildDateInMillis != null) {
e.setTime(buildDateInMillis);
}
e.setMethod(ZipEntry.STORED);
e.setSize(0);
e.setCrc(0);

View File

@@ -7,6 +7,7 @@ import com.intellij.util.PathUtil
import com.intellij.util.io.directoryContent
import com.intellij.util.io.systemIndependentPath
import com.intellij.util.io.zipFile
import org.jetbrains.jps.api.GlobalOptions
import org.jetbrains.jps.builders.CompileScopeTestBuilder
import org.jetbrains.jps.incremental.artifacts.LayoutElementTestUtil.archive
import org.jetbrains.jps.incremental.artifacts.LayoutElementTestUtil.root
@@ -18,11 +19,19 @@ import java.io.BufferedOutputStream
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.security.DigestInputStream
import java.security.MessageDigest
import java.util.*
import java.util.concurrent.TimeUnit
import java.util.jar.JarFile
import java.util.zip.CRC32
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import java.util.zip.ZipOutputStream
import kotlin.io.path.inputStream
class ArtifactBuilderTest : ArtifactBuilderTestCase() {
fun testFileCopy() {
@@ -306,6 +315,38 @@ class ArtifactBuilderTest : ArtifactBuilderTestCase() {
}})
}
private fun Path.checksum(): String = inputStream().buffered().use { input ->
val digest = MessageDigest.getInstance("SHA-256")
DigestInputStream(input, digest).use {
var bytesRead = 0
val buffer = ByteArray(1024 * 8)
while (bytesRead != -1) {
bytesRead = it.read(buffer)
}
}
Base64.getEncoder().encodeToString(digest.digest())
}
fun `test jars build reproducibility`() {
myBuildParams[GlobalOptions.BUILD_DATE_IN_SECONDS] = (System.currentTimeMillis() / 1000).toString()
val jar = root().archive("a.jar")
.extractedDir(createXJarFile(), "/dir")
.let { addArtifact("a", it).outputPath }
?.let { Paths.get(it).resolve("a.jar") }
requireNotNull(jar)
val checksums = (1..2).map {
// sleeping more than a second ensures different last modification time
// for the next iteration jar if the build date isn't provided or ignored
TimeUnit.SECONDS.sleep(2)
FileUtil.delete(jar.parent)
assert(!Files.exists(jar))
buildAll()
assert(Files.exists(jar))
jar.checksum()
}.distinct()
assert(checksums.count() == 1)
}
fun `test no duplicated directory entries for extracted directory packed into JAR file`() {
val zipPath = createXJarFile()
val a = addArtifact("a", root().archive("a.jar").extractedDir(zipPath, ""))