org.gradle.util.JarUtils.groovy Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gradle-api Show documentation
Show all versions of gradle-api Show documentation
Gradle 6.9.1 API redistribution.
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gradle.util
import org.gradle.test.fixtures.archive.JarTestFixture
import org.gradle.util.internal.JarUtil
import java.nio.charset.StandardCharsets
import java.time.LocalDateTime
import java.time.ZoneId
import java.util.jar.Attributes
import java.util.jar.JarEntry
import java.util.jar.JarFile
import java.util.jar.JarOutputStream
import java.util.jar.Manifest
import java.util.zip.ZipEntry
class JarUtils {
static def jarWithContents(Map contents) {
def out = new ByteArrayOutputStream()
def jarOut = new JarOutputStream(out)
try {
contents.each { file, fileContents ->
def zipEntry = new ZipEntry(file)
zipEntry.setTime(0)
jarOut.putNextEntry(zipEntry)
jarOut << fileContents
}
} finally {
jarOut.close()
}
return out.toByteArray()
}
/**
* Builds a JAR file with a DSL. The JAR file uses deterministic timestamps. The Jar manifest must be configured explicitly, as a first step.
*
*
* jar("path/to/file.jar") {
* manifest { // can be withoutManifest()
* mainAttributes.putValue("Multi-Release", "true")
* }
*
* entry("Foo.class", fooBytes)
* versionedEntry(11, "Foo.class", fooBytesForJava11)
* }
*
*
* @param jarFile the jar file to create
* @param closure the configuration of the JAR file
* @return the path to the created JAR file.
*/
static File jar(File jarFile, @DelegatesTo(value = JarBuilder, strategy = Closure.DELEGATE_FIRST) Closure> closure) {
try (def out = jarFile.newOutputStream(); def jarOut = new JarOutputStream(out)) {
closure.setDelegate(new JarBuilder(jarOut))
closure()
}
jarFile
}
/**
* DSL helper to build the JAR file. Start building with configuring the manifest ({@link JarBuilder#manifest(groovy.lang.Closure)}
* or specifying the lack thereof ({@link JarBuilder#withoutManifest()}.
*/
static class JarBuilder {
/**
* @see org.gradle.api.internal.file.archive.ZipCopyAction#CONSTANT_TIME_FOR_ZIP_ENTRIES
*/
private static final long CONSTANT_TIME_FOR_ZIP_ENTRIES = LocalDateTime.parse("1980-02-01T00:00:00.00").atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()
private final JarOutputStream jarOut
private boolean hasConfiguredManifest
JarBuilder(JarOutputStream jarOut) {
this.jarOut = jarOut
}
/**
* Specifies that this JAR has no manifest.
*/
void withoutManifest() {
assert !hasConfiguredManifest: "Already written the manifest"
hasConfiguredManifest = true
}
/**
* Configures the JAR manifest.
*/
void manifest(@DelegatesTo(value = ManifestWithDsl, strategy = Closure.DELEGATE_FIRST) Closure> closure) {
assert !hasConfiguredManifest: "Already written the manifest"
def man = new ManifestWithDsl()
man.mainAttributes.put(Attributes.Name.MANIFEST_VERSION, "1.0")
closure.setDelegate(man)
closure()
jarOut.putNextEntry(newEntryWithFixedTime(JarFile.MANIFEST_NAME))
man.write(jarOut)
hasConfiguredManifest = true
this
}
private static JarEntry newEntryWithFixedTime(String path) {
return new JarEntry(path).tap {
// setTimeLocal() is better but it is Java 9+.
setTime(CONSTANT_TIME_FOR_ZIP_ENTRIES)
}
}
/**
* Writes a versioned flavor of a JAR entry. The {@code path} should be a non-versioned path, e.g. {@code path/to/Foo.class}.
* @param version the java version (9, 10, etc.), must be >8
* @param path the path of the entry (non-versioned)
* @param bytes the body of the entry
*/
void versionedEntry(int version, String path, byte[] bytes) {
entry(JarTestFixture.toVersionedPath(version, path), bytes)
}
/**
* Writes a versioned flavor of a JAR entry. The {@code path} should be a non-versioned path, e.g. {@code path/to/Foo.class}.
* @param version the java version (9, 10, etc.), must be >8
* @param path the path of the entry (non-versioned)
* @param body the body of the entry which is encoded with UTF-8
*/
void versionedEntry(int version, String path, String body) {
entry(JarTestFixture.toVersionedPath(version, path), body)
}
/**
* Writes a JAR entry.
* @param path the path of the entry
* @param bytes the body of the entry
*/
void entry(String path, byte[] bytes) {
checkManifestWritten()
jarOut.putNextEntry(newEntryWithFixedTime(path))
jarOut.write(bytes)
}
/**
* Writes a JAR entry.
* @param path the path of the entry
* @param body the body of the entry which is encoded with UTF-8
*/
void entry(String path, String body) {
entry(path, body.getBytes(StandardCharsets.UTF_8))
}
private void checkManifestWritten() {
assert hasConfiguredManifest: "Must have manifest before entries. Use withoutManifest() to skip it explicitly."
}
}
/**
* A Manifest with additional goodies to make its configuration more pleasant.
*/
static class ManifestWithDsl extends Manifest {
/**
* Configures this Manifest to be Multi-Release.
*/
void multiRelease() {
mainAttributes.putValue(JarUtil.MULTI_RELEASE_ATTRIBUTE, "true")
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy