org.gradle.jvm.tasks.api.ApiJar Maven / Gradle / Ivy
Show all versions of gradle-api Show documentation
/*
* Copyright 2015 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.jvm.tasks.api;
import com.google.common.collect.Lists;
import org.gradle.api.Incubating;
import org.gradle.api.internal.tasks.compile.ApiClassExtractor;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.SourceTask;
import org.gradle.api.tasks.TaskAction;
import org.gradle.internal.ErroringAction;
import org.objectweb.asm.ClassReader;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import static org.apache.commons.io.FileUtils.readFileToByteArray;
import static org.gradle.internal.FileUtils.hasExtension;
import static org.gradle.internal.IoActions.withResource;
/**
* Assembles an "API Jar" containing only the members of a library's public API.
*
* This task and the Jar it produces are designed primarily for internal use in support
* of Gradle's "compile avoidance" performance feature. The task is automatically included
* in the task graph for any JVM library that declares an {@code api { ... }}
* specification, and the resulting Jar will automatically be placed on the compile time
* classpath of projects that depend on the library in lieu of the library's complete
* so-called "Runtime Jar".
*
* Swapping the API Jar in for the Runtime Jar at compile time is what makes
* "compile avoidance" possible: because the contents of the API Jar change only when
* actual API changes are made, the API Jar passes Gradle's up-to-date checks, even if the
* implementation in the Runtime Jar has changed. Ultimately, this means that projects
* depending on the library in question will need to recompile potentially far less often.
*
*
* In order to ensure that API Jars change as infrequently as possible, this task and
* its supporting classes ensure that only actual public API members are included in the
* API Jar, and that the methods among those members are stripped of their implementation.
* Because the members included in API Jars exist only for compilation purposes, they need
* no actual implementation, and for this reason, all such methods throw
* {@link UnsupportedOperationException} in the unlikely event that they are present on
* the classpath and invoked at runtime.
*
* The inputs to this task are Java class files which must be provided via
* {@link org.gradle.api.tasks.TaskInputs}.
*
* @since 2.10
* @see org.gradle.jvm.plugins.JvmComponentPlugin
*/
@Incubating
public class ApiJar extends SourceTask {
private Set exportedPackages;
private File outputFile;
@Input
public Set getExportedPackages() {
return exportedPackages;
}
public void setExportedPackages(Set exportedPackages) {
this.exportedPackages = exportedPackages;
}
@OutputFile
public File getOutputFile() {
return outputFile;
}
public void setOutputFile(File outputFile) {
this.outputFile = outputFile;
}
@TaskAction
public void createApiJar() throws IOException {
// Make sure all entries are always written in the same order
final List sourceFiles = sortedSourceFiles();
final ApiClassExtractor apiClassExtractor = new ApiClassExtractor(getExportedPackages());
withResource(
new JarOutputStream(new BufferedOutputStream(new FileOutputStream(getOutputFile()), 65536)),
new ErroringAction() {
@Override
protected void doExecute(final JarOutputStream jos) throws Exception {
writeManifest(jos);
writeClasses(jos);
}
private void writeManifest(JarOutputStream jos) throws IOException {
writeEntry(jos, "META-INF/MANIFEST.MF", "Manifest-Version: 1.0\n".getBytes());
}
private void writeClasses(JarOutputStream jos) throws Exception {
for (File sourceFile : sourceFiles) {
if (!isClassFile(sourceFile)) {
continue;
}
ClassReader classReader = new ClassReader(readFileToByteArray(sourceFile));
if (!apiClassExtractor.shouldExtractApiClassFrom(classReader)) {
continue;
}
byte[] apiClassBytes = apiClassExtractor.extractApiClassFrom(classReader);
if (apiClassBytes == null) {
// Should be excluded
continue;
}
String internalClassName = classReader.getClassName();
String entryPath = internalClassName + ".class";
writeEntry(jos, entryPath, apiClassBytes);
}
}
private void writeEntry(JarOutputStream jos, String name, byte[] bytes) throws IOException {
JarEntry je = new JarEntry(name);
// Setting time to 0 because we need API jars to be identical independently of
// the timestamps of class files
je.setTime(0);
je.setSize(bytes.length);
jos.putNextEntry(je);
jos.write(bytes);
jos.closeEntry();
}
}
);
}
private boolean isClassFile(File file) {
return hasExtension(file, ".class");
}
private List sortedSourceFiles() {
List sourceFiles = Lists.newArrayList(getSource().getFiles());
Collections.sort(sourceFiles);
return sourceFiles;
}
}