/* * Copyright (C) 2014 The Android Open Source Project * * 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 com.android.build.gradle.tasks; import com.android.SdkConstants; import com.android.annotations.NonNull; import com.android.build.gradle.internal.TaskManager; import com.android.build.gradle.internal.core.GradleVariantConfiguration; import com.android.build.gradle.internal.scope.ConventionMappingHelper; import com.android.build.gradle.internal.scope.GlobalScope; import com.android.build.gradle.internal.scope.TaskConfigAction; import com.android.build.gradle.internal.scope.VariantScope; import com.android.build.gradle.internal.tasks.AbstractAndroidCompile; import com.android.build.gradle.internal.tasks.FileSupplier; import com.android.build.gradle.internal.variant.ApplicationVariantData; import com.android.build.gradle.tasks.factory.AbstractCompilesUtil; import com.android.builder.core.AndroidBuilder; import com.android.builder.tasks.Job; import com.android.builder.tasks.JobContext; import com.android.builder.tasks.Task; import com.android.ide.common.process.LoggedProcessOutputHandler; import com.android.ide.common.process.ProcessException; import com.android.sdklib.BuildToolInfo; import com.android.sdklib.repository.FullRevision; import com.google.common.base.Charsets; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.io.Files; import org.gradle.api.Project; import org.gradle.api.file.FileCollection; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputFile; import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.OutputFile; import org.gradle.api.tasks.ParallelizableTask; import org.gradle.api.tasks.TaskAction; import java.io.File; import java.io.IOException; import java.util.Collection; import java.util.List; import java.util.concurrent.Callable; /** * Jack task. */ @ParallelizableTask public class JackTask extends AbstractAndroidCompile implements FileSupplier, BinaryFileProviderTask, JavaResourcesProvider { public static final FullRevision JACK_MIN_REV = new FullRevision(21, 1, 0); private AndroidBuilder androidBuilder; private boolean isVerbose; private boolean isDebugLog; private Collection<File> packagedLibraries; private Collection<File> proguardFiles; private Collection<File> jarJarRuleFiles; private boolean debug; private File tempFolder; private File jackFile; private File javaResourcesFolder; private File mappingFile; private boolean multiDexEnabled; private int minSdkVersion; private String javaMaxHeapSize; private File incrementalDir; @Override @TaskAction public void compile() { final Job<Void> job = new Job<Void>(getName(), new Task<Void>() { @Override public void run(@NonNull Job<Void> job, @NonNull JobContext<Void> context) throws IOException { try { JackTask.this.doMinification(); } catch (ProcessException e) { throw new IOException(e); } } }); try { SimpleWorkQueue.push(job); // wait for the task completion. job.await(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(e); } } private void doMinification() throws ProcessException, IOException { if (System.getenv("USE_JACK_API") != null) { androidBuilder.convertByteCodeUsingJackApis( getDestinationDir(), getJackFile(), getClasspath().getFiles(), getPackagedLibraries(), getSource().getFiles(), getProguardFiles(), getMappingFile(), getJarJarRuleFiles(), getIncrementalDir(), getJavaResourcesFolder(), isMultiDexEnabled(), getMinSdkVersion()); } else { // no incremental support through command line so far. androidBuilder.convertByteCodeWithJack( getDestinationDir(), getJackFile(), computeBootClasspath(), getPackagedLibraries(), computeEcjOptionFile(), getProguardFiles(), getMappingFile(), getJarJarRuleFiles(), isMultiDexEnabled(), getMinSdkVersion(), isDebugLog, getJavaMaxHeapSize(), new LoggedProcessOutputHandler(androidBuilder.getLogger())); } } private File computeEcjOptionFile() throws IOException { File folder = getTempFolder(); //noinspection ResultOfMethodCallIgnored folder.mkdirs(); File file = new File(folder, "ecj-options.txt"); StringBuilder sb = new StringBuilder(); for (File sourceFile : getSource().getFiles()) { sb.append(sourceFile.getAbsolutePath()).append("\n"); } //noinspection ResultOfMethodCallIgnored file.getParentFile().mkdirs(); Files.write(sb.toString(), file, Charsets.UTF_8); return file; } private String computeBootClasspath() { return Joiner.on(':').join( Iterables.transform(getClasspath().getFiles(), GET_ABSOLUTE_PATH)); } private static final Function<File, String> GET_ABSOLUTE_PATH = new Function<File, String>() { @Override public String apply(File file) { return file.getAbsolutePath(); } }; @InputFile public File getJackExe() { return new File( androidBuilder.getTargetInfo().getBuildTools().getPath(BuildToolInfo.PathId.JACK)); } public AndroidBuilder getAndroidBuilder() { return androidBuilder; } public void setAndroidBuilder(AndroidBuilder androidBuilder) { this.androidBuilder = androidBuilder; } public boolean getIsVerbose() { return isVerbose; } public void setIsVerbose(boolean isVerbose) { this.isVerbose = isVerbose; } public boolean getIsDebugLog() { return isDebugLog; } public void setIsDebugLog(boolean isDebugLog) { this.isDebugLog = isDebugLog; } @InputFiles public Collection<File> getPackagedLibraries() { return packagedLibraries; } public void setPackagedLibraries(Collection<File> packagedLibraries) { this.packagedLibraries = packagedLibraries; } @InputFiles @Optional public Collection<File> getProguardFiles() { return proguardFiles; } public void setProguardFiles(Collection<File> proguardFiles) { this.proguardFiles = proguardFiles; } @InputFiles @Optional public Collection<File> getJarJarRuleFiles() { return jarJarRuleFiles; } public void setJarJarRuleFiles(Collection<File> jarJarRuleFiles) { this.jarJarRuleFiles = jarJarRuleFiles; } @Input public boolean getDebug() { return debug; } public void setDebug(boolean debug) { this.debug = debug; } public File getTempFolder() { return tempFolder; } public void setTempFolder(File tempFolder) { this.tempFolder = tempFolder; } @OutputFile public File getJackFile() { return jackFile; } public void setJackFile(File jackFile) { this.jackFile = jackFile; } @OutputFile @Optional public File getMappingFile() { return mappingFile; } public void setMappingFile(File mappingFile) { this.mappingFile = mappingFile; } @Input public boolean isMultiDexEnabled() { return multiDexEnabled; } public void setMultiDexEnabled(boolean multiDexEnabled) { this.multiDexEnabled = multiDexEnabled; } @Input public int getMinSdkVersion() { return minSdkVersion; } public void setMinSdkVersion(int minSdkVersion) { this.minSdkVersion = minSdkVersion; } @Input @Optional public String getJavaMaxHeapSize() { return javaMaxHeapSize; } public void setJavaMaxHeapSize(String javaMaxHeapSize) { this.javaMaxHeapSize = javaMaxHeapSize; } @Input @Optional public File getIncrementalDir() { return incrementalDir; } public void setIncrementalDir(File incrementalDir) { this.incrementalDir = incrementalDir; } @Input @Optional public File getJavaResourcesFolder() { return javaResourcesFolder; } public void setJavaResourcesFolder(File javaResourcesFolder) { this.javaResourcesFolder = javaResourcesFolder; } @NonNull @Override public ImmutableList<JavaResourcesLocation> getJavaResourcesLocations() { return ImmutableList.of( new JavaResourcesLocation(Type.FOLDER, getDestinationDir())); } @Override @NonNull public BinaryFileProviderTask.Artifact getArtifact() { return new BinaryFileProviderTask.Artifact( BinaryFileProviderTask.BinaryArtifactType.JACK, getJackFile()); } // ----- FileSupplierTask ---- @NonNull @Override public org.gradle.api.Task getTask() { return this; } @Override public File get() { return getMappingFile(); } public static class ConfigAction implements TaskConfigAction<JackTask> { private final VariantScope scope; private final boolean isVerbose; private final boolean isDebugLog; public ConfigAction(VariantScope scope, boolean isVerbose, boolean isDebugLog) { this.scope = scope; this.isVerbose = isVerbose; this.isDebugLog = isDebugLog; } @Override public String getName() { return scope.getTaskName("compile", "JavaWithJack"); } @Override public Class<JackTask> getType() { return JackTask.class; } @Override public void execute(JackTask jackTask) { jackTask.setIsVerbose(isVerbose); jackTask.setIsDebugLog(isDebugLog); GlobalScope globalScope = scope.getGlobalScope(); jackTask.androidBuilder = globalScope.getAndroidBuilder(); jackTask.setJavaMaxHeapSize( globalScope.getExtension().getDexOptions().getJavaMaxHeapSize()); jackTask.setSource(scope.getVariantData().getJavaSources()); final GradleVariantConfiguration config = scope.getVariantData().getVariantConfiguration(); jackTask.setMultiDexEnabled(config.isMultiDexEnabled()); jackTask.setMinSdkVersion(config.getMinSdkVersion().getApiLevel()); jackTask.incrementalDir = scope.getJackIncrementalDir(); // if the tested variant is an app, add its classpath. For the libraries, // it's done automatically since the classpath includes the library output as a normal // dependency. if (scope.getTestedVariantData() instanceof ApplicationVariantData) { ConventionMappingHelper.map(jackTask, "classpath", new Callable<FileCollection>() { @Override public FileCollection call() throws Exception { Project project = scope.getGlobalScope().getProject(); return project.fileTree(scope.getJillRuntimeLibrariesDir()).plus( project.fileTree( scope.getTestedVariantData().getScope() .getJillRuntimeLibrariesDir())).plus( project.fileTree( scope.getTestedVariantData().getScope().getJackClassesZip() )); } }); } else { ConventionMappingHelper.map(jackTask, "classpath", new Callable<FileCollection>() { @Override public FileCollection call() throws Exception { return scope.getGlobalScope().getProject().fileTree( scope.getJillRuntimeLibrariesDir()); } }); } ConventionMappingHelper.map(jackTask, "packagedLibraries", new Callable<Collection<File>>() { @Override public Collection<File> call() throws Exception { return scope.getGlobalScope().getProject() .fileTree(scope.getJillPackagedLibrariesDir()).getFiles(); } }); jackTask.setDestinationDir(scope.getJackDestinationDir()); jackTask.setJackFile(scope.getJackClassesZip()); jackTask.setTempFolder(new File(scope.getGlobalScope().getIntermediatesDir(), "/tmp/jack/" + scope.getVariantConfiguration().getDirName())); jackTask.setJavaResourcesFolder(scope.getJavaResourcesDestinationDir()); scope.setJavaResourcesProvider(jackTask); if (config.isMinifyEnabled()) { ConventionMappingHelper.map(jackTask, "proguardFiles", new Callable<List<File>>() { @Override public List<File> call() throws Exception { // since all the output use the same resources, we can use the first output // to query for a proguard file. File sdkDir = scope.getGlobalScope().getSdkHandler().getAndCheckSdkFolder(); File defaultProguardFile = new File(sdkDir, SdkConstants.FD_TOOLS + File.separatorChar + SdkConstants.FD_PROGUARD + File.separatorChar + TaskManager.DEFAULT_PROGUARD_CONFIG_FILE); List<File> proguardFiles = config.getProguardFiles(true /*includeLibs*/, ImmutableList.of(defaultProguardFile)); File proguardResFile = scope.getProcessAndroidResourcesProguardOutputFile(); proguardFiles.add(proguardResFile); // for tested app, we only care about their aapt config since the base // configs are the same files anyway. if (scope.getTestedVariantData() != null) { proguardResFile = scope.getTestedVariantData().getScope() .getProcessAndroidResourcesProguardOutputFile(); proguardFiles.add(proguardResFile); } return proguardFiles; } }); jackTask.mappingFile = new File(scope.getProguardOutputFolder(), "mapping.txt"); } ConventionMappingHelper.map(jackTask, "jarJarRuleFiles", new Callable<List<File>>() { @Override public List<File> call() throws Exception { List<File> jarJarRuleFiles = Lists.newArrayListWithCapacity( config.getJarJarRuleFiles().size()); Project project = scope.getGlobalScope().getProject(); for (File file: config.getJarJarRuleFiles()) { jarJarRuleFiles.add(project.file(file)); } return jarJarRuleFiles; } }); AbstractCompilesUtil.configureLanguageLevel( jackTask, scope.getGlobalScope().getExtension().getCompileOptions(), scope.getGlobalScope().getExtension().getCompileSdkVersion() ); scope.getVariantData().jackTask = jackTask; scope.getVariantData().javaCompilerTask = jackTask; scope.getVariantData().mappingFileProviderTask = jackTask; scope.getVariantData().binayFileProviderTask = jackTask; } } }