/*
* Copyright (C) 2013 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.internal.compiler;
import static com.android.SdkConstants.EXT_BC;
import static com.android.SdkConstants.FN_RENDERSCRIPT_V8_JAR;
import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.common.internal.LoggedErrorException;
import com.android.ide.common.internal.WaitableExecutor;
import com.android.ide.common.process.ProcessException;
import com.android.ide.common.process.ProcessExecutor;
import com.android.ide.common.process.ProcessInfoBuilder;
import com.android.ide.common.process.ProcessOutputHandler;
import com.android.ide.common.process.ProcessResult;
import com.android.sdklib.BuildToolInfo;
import com.google.common.collect.Maps;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
/**
* Compiles Renderscript files.
*/
public class RenderScriptProcessor {
// ABI list, as pairs of (android-ABI, toolchain-ABI)
private static final class Abi {
@NonNull
private final String mDevice;
@NonNull
private final String mToolchain;
@NonNull
private final BuildToolInfo.PathId mLinker;
@NonNull
private final String[] mLinkerArgs;
Abi(@NonNull String device,
@NonNull String toolchain,
@NonNull BuildToolInfo.PathId linker,
@NonNull String... linkerArgs) {
mDevice = device;
mToolchain = toolchain;
mLinker = linker;
mLinkerArgs = linkerArgs;
}
}
private static final Abi[] ABIS = {
new Abi("armeabi-v7a", "armv7-none-linux-gnueabi", BuildToolInfo.PathId.LD_ARM,
"-dynamic-linker", "/system/bin/linker", "-X", "-m", "armelf_linux_eabi"),
new Abi("mips", "mipsel-unknown-linux", BuildToolInfo.PathId.LD_MIPS, "-EL"),
new Abi("x86", "i686-unknown-linux", BuildToolInfo.PathId.LD_X86, "-m", "elf_i386") };
public static final String RS_DEPS = "rsDeps";
@NonNull
private final List<File> mSourceFolders;
@NonNull
private final List<File> mImportFolders;
@NonNull
private final File mSourceOutputDir;
@NonNull
private final File mResOutputDir;
@NonNull
private final File mObjOutputDir;
@NonNull
private final File mLibOutputDir;
@NonNull
private final BuildToolInfo mBuildToolInfo;
private final int mTargetApi;
private final int mOptimizationLevel;
private final boolean mNdkMode;
private final boolean mSupportMode;
private final Set<String> mAbiFilters;
private final File mRsLib;
private final Map<String, File> mLibClCore = Maps.newHashMap();
public RenderScriptProcessor(
@NonNull List<File> sourceFolders,
@NonNull List<File> importFolders,
@NonNull File sourceOutputDir,
@NonNull File resOutputDir,
@NonNull File objOutputDir,
@NonNull File libOutputDir,
@NonNull BuildToolInfo buildToolInfo,
int targetApi,
boolean debugBuild,
int optimizationLevel,
boolean ndkMode,
boolean supportMode,
@Nullable Set<String> abiFilters) {
mSourceFolders = sourceFolders;
mImportFolders = importFolders;
mSourceOutputDir = sourceOutputDir;
mResOutputDir = resOutputDir;
mObjOutputDir = objOutputDir;
mLibOutputDir = libOutputDir;
mBuildToolInfo = buildToolInfo;
mTargetApi = targetApi;
mOptimizationLevel = optimizationLevel;
mNdkMode = ndkMode;
mSupportMode = supportMode;
mAbiFilters = abiFilters;
if (supportMode) {
File rs = new File(mBuildToolInfo.getLocation(), "renderscript");
mRsLib = new File(rs, "lib");
File bcFolder = new File(mRsLib, "bc");
for (Abi abi : ABIS) {
mLibClCore.put(abi.mDevice,
new File(bcFolder, abi.mDevice + File.separatorChar + "libclcore.bc"));
}
} else {
mRsLib = null;
}
}
public static File getSupportJar(String buildToolsFolder) {
return new File(buildToolsFolder, "renderscript/lib/" + FN_RENDERSCRIPT_V8_JAR);
}
public static File getSupportNativeLibFolder(String buildToolsFolder) {
File rs = new File(buildToolsFolder, "renderscript");
File lib = new File(rs, "lib");
return new File(lib, "packaged");
}
public void build(
@NonNull ProcessExecutor processExecutor,
@NonNull ProcessOutputHandler processOutputHandler)
throws InterruptedException, ProcessException, LoggedErrorException, IOException {
// gather the files to compile
FileGatherer fileGatherer = new FileGatherer();
SourceSearcher searcher = new SourceSearcher(mSourceFolders, "rs", "fs");
searcher.setUseExecutor(false);
searcher.search(fileGatherer);
List<File> renderscriptFiles = fileGatherer.getFiles();
if (renderscriptFiles.isEmpty()) {
return;
}
// get the env var
Map<String, String> env = Maps.newHashMap();
if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN) {
env.put("DYLD_LIBRARY_PATH", mBuildToolInfo.getLocation().getAbsolutePath());
} else if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_LINUX) {
env.put("LD_LIBRARY_PATH", mBuildToolInfo.getLocation().getAbsolutePath());
}
doMainCompilation(renderscriptFiles, processExecutor, processOutputHandler, env);
if (mSupportMode) {
createSupportFiles(processExecutor, processOutputHandler, env);
}
}
private void doMainCompilation(
@NonNull List<File> inputFiles,
@NonNull ProcessExecutor processExecutor,
@NonNull ProcessOutputHandler processOutputHandler,
@NonNull Map<String, String> env)
throws ProcessException {
ProcessInfoBuilder builder = new ProcessInfoBuilder();
String renderscript = mBuildToolInfo.getPath(BuildToolInfo.PathId.LLVM_RS_CC);
if (renderscript == null || !new File(renderscript).isFile()) {
throw new IllegalStateException(BuildToolInfo.PathId.LLVM_RS_CC + " is missing");
}
String rsPath = mBuildToolInfo.getPath(BuildToolInfo.PathId.ANDROID_RS);
String rsClangPath = mBuildToolInfo.getPath(BuildToolInfo.PathId.ANDROID_RS_CLANG);
// the renderscript compiler doesn't expect the top res folder,
// but the raw folder directly.
File rawFolder = new File(mResOutputDir, SdkConstants.FD_RES_RAW);
// compile all the files in a single pass
builder.setExecutable(renderscript);
builder.addEnvironments(env);
// Due to a device side bug, let's not enable this at this time.
// if (mDebugBuild) {
// command.add("-g");
// }
builder.addArgs("-O");
builder.addArgs(Integer.toString(mOptimizationLevel));
// add all import paths
builder.addArgs("-I");
builder.addArgs(rsPath);
builder.addArgs("-I");
builder.addArgs(rsClangPath);
for (File importPath : mImportFolders) {
if (importPath.isDirectory()) {
builder.addArgs("-I");
builder.addArgs(importPath.getAbsolutePath());
}
}
if (mSupportMode) {
builder.addArgs("-rs-package-name=android.support.v8.renderscript");
}
// source output
builder.addArgs("-p");
builder.addArgs(mSourceOutputDir.getAbsolutePath());
if (mNdkMode) {
builder.addArgs("-reflect-c++");
}
// res output
builder.addArgs("-o");
builder.addArgs(rawFolder.getAbsolutePath());
builder.addArgs("-target-api");
int targetApi = mTargetApi < 11 ? 11 : mTargetApi;
targetApi = (mSupportMode && targetApi < 18) ? 18 : targetApi;
builder.addArgs(Integer.toString(targetApi));
// input files
for (File sourceFile : inputFiles) {
builder.addArgs(sourceFile.getAbsolutePath());
}
ProcessResult result = processExecutor.execute(
builder.createProcess(), processOutputHandler);
result.rethrowFailure().assertNormalExitValue();
}
private void createSupportFiles(
@NonNull final ProcessExecutor processExecutor,
@NonNull final ProcessOutputHandler processOutputHandler,
@NonNull final Map<String, String> env)
throws IOException, InterruptedException, LoggedErrorException, ProcessException {
// get the generated BC files.
File rawFolder = new File(mResOutputDir, SdkConstants.FD_RES_RAW);
SourceSearcher searcher = new SourceSearcher(
Collections.singletonList(rawFolder), EXT_BC);
FileGatherer fileGatherer = new FileGatherer();
searcher.search(fileGatherer);
WaitableExecutor<Void> mExecutor = new WaitableExecutor<Void>();
for (final File bcFile : fileGatherer.getFiles()) {
String name = bcFile.getName();
final String objName = name.replaceAll("\\.bc", ".o");
final String soName = "librs." + name.replaceAll("\\.bc", ".so");
for (final Abi abi : ABIS) {
if (mAbiFilters != null && !mAbiFilters.contains(abi.mDevice)) {
continue;
}
// make sure the dest folders exist
final File objAbiFolder = new File(mObjOutputDir, abi.mDevice);
if (!objAbiFolder.isDirectory() && !objAbiFolder.mkdirs()) {
throw new IOException("Unable to create dir " + objAbiFolder.getAbsolutePath());
}
final File libAbiFolder = new File(mLibOutputDir, abi.mDevice);
if (!libAbiFolder.isDirectory() && !libAbiFolder.mkdirs()) {
throw new IOException("Unable to create dir " + libAbiFolder.getAbsolutePath());
}
mExecutor.execute(new Callable<Void>() {
@Override
public Void call() throws Exception {
File objFile = createSupportObjFile(
bcFile,
abi,
objName,
objAbiFolder,
processExecutor,
processOutputHandler,
env);
createSupportLibFile(
objFile,
abi,
soName,
libAbiFolder,
processExecutor,
processOutputHandler,
env);
return null;
}
});
}
}
mExecutor.waitForTasksWithQuickFail(true /*cancelRemaining*/);
}
private File createSupportObjFile(
@NonNull File bcFile,
@NonNull Abi abi,
@NonNull String objName,
@NonNull File objAbiFolder,
@NonNull ProcessExecutor processExecutor,
@NonNull ProcessOutputHandler processOutputHandler,
@NonNull Map<String, String> env) throws ProcessException {
ProcessInfoBuilder builder = new ProcessInfoBuilder();
builder.setExecutable(mBuildToolInfo.getPath(BuildToolInfo.PathId.BCC_COMPAT));
builder.addEnvironments(env);
builder.addArgs("-O" + Integer.toString(mOptimizationLevel));
File outFile = new File(objAbiFolder, objName);
builder.addArgs("-o", outFile.getAbsolutePath());
builder.addArgs("-fPIC");
builder.addArgs("-shared");
builder.addArgs("-rt-path", mLibClCore.get(abi.mDevice).getAbsolutePath());
builder.addArgs("-mtriple", abi.mToolchain);
builder.addArgs(bcFile.getAbsolutePath());
processExecutor.execute(
builder.createProcess(), processOutputHandler)
.rethrowFailure().assertNormalExitValue();
return outFile;
}
private void createSupportLibFile(
@NonNull File objFile,
@NonNull Abi abi,
@NonNull String soName,
@NonNull File libAbiFolder,
@NonNull ProcessExecutor processExecutor,
@NonNull ProcessOutputHandler processOutputHandler,
@NonNull Map<String, String> env) throws ProcessException {
File intermediatesFolder = new File(mRsLib, "intermediates");
File intermediatesAbiFolder = new File(intermediatesFolder, abi.mDevice);
File packagedFolder = new File(mRsLib, "packaged");
File packagedAbiFolder = new File(packagedFolder, abi.mDevice);
ProcessInfoBuilder builder = new ProcessInfoBuilder();
builder.setExecutable(mBuildToolInfo.getPath(abi.mLinker));
builder.addEnvironments(env);
builder.addArgs("--eh-frame-hdr")
.addArgs(abi.mLinkerArgs)
.addArgs("-shared", "-Bsymbolic", "-z", "noexecstack", "-z", "relro", "-z", "now");
File outFile = new File(libAbiFolder, soName);
builder.addArgs("-o", outFile.getAbsolutePath());
builder.addArgs(
"-L" + intermediatesAbiFolder.getAbsolutePath(),
"-L" + packagedAbiFolder.getAbsolutePath(),
"-soname",
soName,
objFile.getAbsolutePath(),
new File(intermediatesAbiFolder, "libcompiler_rt.a").getAbsolutePath(),
"-lRSSupport",
"-lm",
"-lc");
processExecutor.execute(
builder.createProcess(), processOutputHandler)
.rethrowFailure().assertNormalExitValue();
}
}