/*
* 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.CommandLineRunner;
import com.android.ide.common.internal.LoggedErrorException;
import com.android.ide.common.internal.WaitableExecutor;
import com.android.sdklib.BuildToolInfo;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
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 boolean mDebugBuild;
private final int mOptimLevel;
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 optimLevel,
boolean ndkMode,
boolean supportMode,
@Nullable Set<String> abiFilters) {
mSourceFolders = sourceFolders;
mImportFolders = importFolders;
mSourceOutputDir = sourceOutputDir;
mResOutputDir = resOutputDir;
mObjOutputDir = objOutputDir;
mLibOutputDir = libOutputDir;
mBuildToolInfo = buildToolInfo;
mTargetApi = targetApi;
mDebugBuild = debugBuild;
mOptimLevel = optimLevel;
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 CommandLineRunner launcher)
throws IOException, InterruptedException, LoggedErrorException {
// 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, launcher, env);
if (mSupportMode) {
createSupportFiles(launcher, env);
}
}
private void doMainCompilation(
@NonNull List<File> inputFiles,
@NonNull CommandLineRunner launcher,
@NonNull Map<String, String> env)
throws IOException, InterruptedException, LoggedErrorException {
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
ArrayList<String> command = Lists.newArrayListWithExpectedSize(26);
command.add(renderscript);
// Due to a device side bug, let's not enable this at this time.
// if (mDebugBuild) {
// command.add("-g");
// }
command.add("-O");
command.add(Integer.toString(mOptimLevel));
// add all import paths
command.add("-I");
command.add(rsPath);
command.add("-I");
command.add(rsClangPath);
for (File importPath : mImportFolders) {
if (importPath.isDirectory()) {
command.add("-I");
command.add(importPath.getAbsolutePath());
}
}
if (mSupportMode) {
command.add("-rs-package-name=android.support.v8.renderscript");
}
// source output
command.add("-p");
command.add(mSourceOutputDir.getAbsolutePath());
if (mNdkMode) {
command.add("-reflect-c++");
}
// res output
command.add("-o");
command.add(rawFolder.getAbsolutePath());
command.add("-target-api");
int targetApi = mTargetApi < 11 ? 11 : mTargetApi;
targetApi = (mSupportMode && targetApi < 18) ? 18 : targetApi;
command.add(Integer.toString(targetApi));
// input files
for (File sourceFile : inputFiles) {
command.add(sourceFile.getAbsolutePath());
}
launcher.runCmdLine(command, env);
}
private void createSupportFiles(@NonNull final CommandLineRunner launcher,
@NonNull final Map<String, String> env)
throws IOException, InterruptedException, LoggedErrorException {
// 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,
launcher, env);
createSupportLibFile(objFile, abi, soName, libAbiFolder, launcher, env);
return null;
}
});
}
}
mExecutor.waitForTasksWithQuickFail(true /*cancelRemaining*/);
}
private File createSupportObjFile(
@NonNull File bcFile,
@NonNull Abi abi,
@NonNull String objName,
@NonNull File objAbiFolder,
@NonNull CommandLineRunner launcher,
@NonNull Map<String, String> env)
throws IOException, InterruptedException, LoggedErrorException {
List<String> args = Lists.newArrayListWithExpectedSize(10);
args.add(mBuildToolInfo.getPath(BuildToolInfo.PathId.BCC_COMPAT));
args.add("-O" + Integer.toString(mOptimLevel));
File outFile = new File(objAbiFolder, objName);
args.add("-o");
args.add(outFile.getAbsolutePath());
args.add("-fPIC");
args.add("-shared");
args.add("-rt-path");
args.add(mLibClCore.get(abi.mDevice).getAbsolutePath());
args.add("-mtriple");
args.add(abi.mToolchain);
args.add(bcFile.getAbsolutePath());
launcher.runCmdLine(args, env);
return outFile;
}
private void createSupportLibFile(
@NonNull File objFile,
@NonNull Abi abi,
@NonNull String soName,
@NonNull File libAbiFolder,
@NonNull CommandLineRunner launcher,
@NonNull Map<String, String> env)
throws IOException, InterruptedException, LoggedErrorException {
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);
List<String> args = Lists.newArrayListWithExpectedSize(26);
args.add(mBuildToolInfo.getPath(abi.mLinker));
args.add("--eh-frame-hdr");
Collections.addAll(args, abi.mLinkerArgs);
args.add("-shared");
args.add("-Bsymbolic");
args.add("-z");
args.add("noexecstack");
args.add("-z");
args.add("relro");
args.add("-z");
args.add("now");
File outFile = new File(libAbiFolder, soName);
args.add("-o");
args.add(outFile.getAbsolutePath());
args.add("-L" + intermediatesAbiFolder.getAbsolutePath());
args.add("-L" + packagedAbiFolder.getAbsolutePath());
args.add("-soname");
args.add(soName);
args.add(objFile.getAbsolutePath());
args.add(new File(intermediatesAbiFolder, "libcompiler_rt.a").getAbsolutePath());
args.add("-lRSSupport");
args.add("-lm");
args.add("-lc");
launcher.runCmdLine(args, env);
}
}