/*
* Copyright (C) 2015 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.builder.core;
import static com.google.common.base.Preconditions.checkState;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.common.process.JavaProcessInfo;
import com.android.ide.common.process.ProcessEnvBuilder;
import com.android.ide.common.process.ProcessException;
import com.android.ide.common.process.ProcessInfoBuilder;
import com.android.sdklib.BuildToolInfo;
import com.android.sdklib.repository.FullRevision;
import com.google.common.base.Charsets;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.io.Files;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
/**
* A builder to create a dex-specific ProcessInfoBuilder
*/
public class DexProcessBuilder extends ProcessEnvBuilder<DexProcessBuilder> {
private static final FullRevision MIN_BUILD_TOOLS_REVISION_FOR_DEX_INPUT_LIST = new FullRevision(21, 0, 0);
private static final FullRevision MIN_MULTIDEX_BUILD_TOOLS_REV = new FullRevision(21, 0, 0);
private static final FullRevision MIN_MULTI_THREADED_DEX_BUILD_TOOLS_REV = new FullRevision(22, 0, 2);
@NonNull
private final File mOutputFile;
private boolean mVerbose = false;
private boolean mIncremental = false;
private boolean mNoOptimize = false;
private boolean mMultiDex = false;
private File mMainDexList = null;
private Set<File> mInputs = Sets.newHashSet();
private File mTempInputFolder = null;
private List<String> mAdditionalParams = null;
public DexProcessBuilder(@NonNull File outputFile) {
mOutputFile = outputFile;
}
@NonNull
public DexProcessBuilder setVerbose(boolean verbose) {
mVerbose = verbose;
return this;
}
@NonNull
public DexProcessBuilder setIncremental(boolean incremental) {
mIncremental = incremental;
return this;
}
@NonNull
public DexProcessBuilder setNoOptimize(boolean noOptimize) {
mNoOptimize = noOptimize;
return this;
}
@NonNull
public DexProcessBuilder setMultiDex(boolean multiDex) {
mMultiDex = multiDex;
return this;
}
@NonNull
public DexProcessBuilder setMainDexList(File mainDexList) {
mMainDexList = mainDexList;
return this;
}
@NonNull
public DexProcessBuilder addInput(File input) {
mInputs.add(input);
return this;
}
@NonNull
public DexProcessBuilder addInputs(@NonNull Collection<File> inputs) {
mInputs.addAll(inputs);
return this;
}
@NonNull
public DexProcessBuilder setTempInputFolder(File tempInputFolder) {
mTempInputFolder = tempInputFolder;
return this;
}
@NonNull
public DexProcessBuilder additionalParameters(@NonNull List<String> params) {
if (mAdditionalParams == null) {
mAdditionalParams = Lists.newArrayListWithExpectedSize(params.size());
}
mAdditionalParams.addAll(params);
return this;
}
@NonNull
public JavaProcessInfo build(
@NonNull BuildToolInfo buildToolInfo,
@NonNull DexOptions dexOptions) throws ProcessException {
checkState(
!mMultiDex
|| buildToolInfo.getRevision().compareTo(MIN_MULTIDEX_BUILD_TOOLS_REV) >= 0,
"Multi dex requires Build Tools " +
MIN_MULTIDEX_BUILD_TOOLS_REV.toString() +
" / Current: " +
buildToolInfo.getRevision().toShortString());
ProcessInfoBuilder builder = new ProcessInfoBuilder();
builder.addEnvironments(mEnvironment);
String dx = buildToolInfo.getPath(BuildToolInfo.PathId.DX_JAR);
if (dx == null || !new File(dx).isFile()) {
throw new IllegalStateException("dx.jar is missing");
}
builder.setClasspath(dx);
builder.setMain("com.android.dx.command.Main");
if (dexOptions.getJavaMaxHeapSize() != null) {
builder.addJvmArg("-Xmx" + dexOptions.getJavaMaxHeapSize());
} else {
builder.addJvmArg("-Xmx1024M");
}
builder.addArgs("--dex");
if (mVerbose) {
builder.addArgs("--verbose");
}
if (dexOptions.getJumboMode()) {
builder.addArgs("--force-jumbo");
}
if (mIncremental) {
builder.addArgs("--incremental", "--no-strict");
}
if (mNoOptimize) {
builder.addArgs("--no-optimize");
}
// only change thread count is build tools is 22.0.2+
if (buildToolInfo.getRevision().compareTo(MIN_MULTI_THREADED_DEX_BUILD_TOOLS_REV) >= 0) {
Integer threadCount = dexOptions.getThreadCount();
if (threadCount == null) {
builder.addArgs("--num-threads=4");
} else {
builder.addArgs("--num-threads=" + threadCount);
}
}
if (mMultiDex) {
builder.addArgs("--multi-dex");
if (mMainDexList != null ) {
builder.addArgs("--main-dex-list", mMainDexList.getAbsolutePath());
}
}
if (mAdditionalParams != null) {
for (String arg : mAdditionalParams) {
builder.addArgs(arg);
}
}
builder.addArgs("--output", mOutputFile.getAbsolutePath());
// input
builder.addArgs(getFilesToAdd(buildToolInfo));
return builder.createJavaProcess();
}
@NonNull
private List<String> getFilesToAdd(@NonNull BuildToolInfo buildToolInfo) throws
ProcessException {
// remove non-existing files.
Set<File> existingFiles = Sets.filter(mInputs, new Predicate<File>() {
@Override
public boolean apply(@Nullable File input) {
return input != null && input.exists();
}
});
if (existingFiles.isEmpty()) {
throw new ProcessException("No files to pass to dex.");
}
// sort the inputs
List<File> sortedList = Lists.newArrayList(existingFiles);
Collections.sort(sortedList, new Comparator<File>() {
@Override
public int compare(File file, File file2) {
boolean file2IsDir = file2.isDirectory();
if (file.isDirectory()) {
return file2IsDir ? 0 : -1;
} else if (file2IsDir) {
return 1;
}
long diff = file.length() - file2.length();
return diff > 0 ? 1 : (diff < 0 ? -1 : 0);
}
});
// convert to String-based paths.
List<String> filePathList = Lists.newArrayListWithCapacity(sortedList.size());
for (File f : sortedList) {
filePathList.add(f.getAbsolutePath());
}
if (mTempInputFolder != null && buildToolInfo.getRevision()
.compareTo(MIN_BUILD_TOOLS_REVISION_FOR_DEX_INPUT_LIST) >= 0) {
File inputListFile = new File(mTempInputFolder, "inputList.txt");
// Write each library line by line to file
try {
Files.asCharSink(inputListFile, Charsets.UTF_8).writeLines(filePathList);
} catch (IOException e) {
throw new ProcessException(e);
}
return Collections.singletonList("--input-list=" + inputListFile.getAbsolutePath());
} else {
return filePathList;
}
}
}