blob: a72752983cec0926b69603568a66e0e65a7efef1 [file] [log] [blame]
/*
* 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.model;
import static com.android.build.gradle.model.AndroidComponentModelPlugin.COMPONENT_NAME;
import com.android.build.gradle.internal.NdkHandler;
import com.android.build.gradle.internal.NdkOptionsHelper;
import com.android.build.gradle.internal.ProductFlavorCombo;
import com.android.build.gradle.internal.core.Abi;
import com.android.build.gradle.internal.scope.VariantScope;
import com.android.build.gradle.managed.BuildType;
import com.android.build.gradle.managed.NdkConfig;
import com.android.build.gradle.managed.ProductFlavor;
import com.android.build.gradle.ndk.internal.NdkConfiguration;
import com.android.build.gradle.ndk.internal.NdkExtensionConvention;
import com.android.build.gradle.ndk.internal.NdkNamingScheme;
import com.android.build.gradle.ndk.internal.ToolchainConfiguration;
import com.android.builder.core.BuilderConstants;
import com.android.builder.core.VariantConfiguration;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import org.gradle.api.Action;
import org.gradle.api.BuildableModelElement;
import org.gradle.api.InvalidUserDataException;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.internal.project.ProjectIdentifier;
import org.gradle.api.specs.Spec;
import org.gradle.api.tasks.TaskContainer;
import org.gradle.language.c.plugins.CPlugin;
import org.gradle.language.cpp.plugins.CppPlugin;
import org.gradle.model.Defaults;
import org.gradle.model.Finalize;
import org.gradle.model.Model;
import org.gradle.model.ModelMap;
import org.gradle.model.Mutate;
import org.gradle.model.Path;
import org.gradle.model.RuleSource;
import org.gradle.model.Validate;
import org.gradle.nativeplatform.BuildTypeContainer;
import org.gradle.nativeplatform.FlavorContainer;
import org.gradle.nativeplatform.NativeBinarySpec;
import org.gradle.nativeplatform.NativeLibraryBinarySpec;
import org.gradle.nativeplatform.NativeLibrarySpec;
import org.gradle.nativeplatform.SharedLibraryBinarySpec;
import org.gradle.nativeplatform.toolchain.NativeToolChainRegistry;
import org.gradle.platform.base.BinaryContainer;
import org.gradle.platform.base.BinarySpec;
import org.gradle.platform.base.ComponentSpecContainer;
import org.gradle.platform.base.PlatformContainer;
import org.gradle.platform.base.binary.BaseBinarySpec;
import java.io.File;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* Plugin for Android NDK applications.
*/
public class NdkComponentModelPlugin implements Plugin<Project> {
private Project project;
@Override
public void apply(Project project) {
this.project = project;
project.getPluginManager().apply(AndroidComponentModelPlugin.class);
project.getPluginManager().apply(CPlugin.class);
project.getPluginManager().apply(CppPlugin.class);
}
@SuppressWarnings({"MethodMayBeStatic", "unused"})
public static class Rules extends RuleSource {
@Mutate
public void initializeNdkConfig(@Path("android.ndk") NdkConfig ndk) {
NdkOptionsHelper.init(ndk);
ndk.setModuleName("");
ndk.setToolchain("");
ndk.setToolchainVersion("");
ndk.setStl("");
ndk.setRenderscriptNdkMode(false);
}
@Finalize
public void setDefaultNdkExtensionValue(@Path("android.ndk") NdkConfig ndkConfig) {
NdkExtensionConvention.setExtensionDefault(ndkConfig);
}
@Validate
public void checkNdkDir(NdkHandler ndkHandler, @Path("android.ndk") NdkConfig ndkConfig) {
if (!ndkConfig.getModuleName().isEmpty() && !ndkHandler.isNdkDirConfigured()) {
throw new InvalidUserDataException(
"NDK location not found. Define location with ndk.dir in the "
+ "local.properties file or with an ANDROID_NDK_HOME environment "
+ "variable.");
}
if (ndkHandler.isNdkDirConfigured()) {
if (!ndkHandler.getNdkDirectory().exists()) {
throw new InvalidUserDataException(
"Specified NDK location does not exists. Please ensure ndk.dir in "
+ "local.properties file or ANDROID_NDK_HOME is configured "
+ "correctly.");
}
}
}
@Mutate
public void addDefaultNativeSourceSet(
@Path("android.sources") AndroidComponentModelSourceSet sources) {
sources.addDefaultSourceSet("jni", AndroidLanguageSourceSet.class);
}
@Model(ModelConstants.NDK_HANDLER)
public NdkHandler ndkHandler(
ProjectIdentifier projectId,
@Path("android.compileSdkVersion") String compileSdkVersion,
@Path("android.ndk") NdkConfig ndkConfig) {
while (projectId.getParentIdentifier() != null) {
projectId = projectId.getParentIdentifier();
}
return new NdkHandler(projectId.getProjectDir(), compileSdkVersion,
ndkConfig.getToolchain(), ndkConfig.getToolchainVersion());
}
@Defaults
public void initBuildTypeNdk(@Path("android.buildTypes") ModelMap<BuildType> buildTypes) {
buildTypes.beforeEach(new Action<BuildType>() {
@Override
public void execute(BuildType buildType) {
NdkOptionsHelper.init(buildType.getNdk());
}
});
buildTypes.named(
BuilderConstants.DEBUG,
new Action<BuildType>() {
@Override
public void execute(BuildType buildType) {
if (buildType.getNdk().getDebuggable() == null) {
buildType.getNdk().setDebuggable(true);
}
}
});
}
@Defaults
public void initProductFlavorNdk(
@Path("android.productFlavors") ModelMap<ProductFlavor> productFlavors) {
productFlavors.beforeEach(new Action<ProductFlavor>() {
@Override
public void execute(ProductFlavor productFlavor) {
NdkOptionsHelper.init(productFlavor.getNdk());
}
});
}
@Mutate
public void createAndroidPlatforms(PlatformContainer platforms, NdkHandler ndkHandler) {
if (!ndkHandler.isNdkDirConfigured()) {
return;
}
// Create android platforms.
ToolchainConfiguration.configurePlatforms(platforms, ndkHandler);
}
@Mutate
public void createToolchains(
NativeToolChainRegistry toolchainRegistry,
@Path("android.ndk") NdkConfig ndkConfig,
NdkHandler ndkHandler) {
if (!ndkHandler.isNdkDirConfigured()) {
return;
}
// Create toolchain for each ABI.
ToolchainConfiguration.configureToolchain(
toolchainRegistry,
ndkConfig.getToolchain(),
ndkHandler);
}
@Mutate
public void createNativeBuildTypes(BuildTypeContainer nativeBuildTypes,
@Path("android.buildTypes") ModelMap<BuildType> androidBuildTypes) {
for (BuildType buildType : androidBuildTypes.values()) {
nativeBuildTypes.maybeCreate(buildType.getName());
}
}
@Mutate
public void createNativeFlavors(FlavorContainer nativeFlavors,
List<ProductFlavorCombo<ProductFlavor>> androidFlavorGroups) {
if (androidFlavorGroups.isEmpty()) {
// Create empty native flavor to override Gradle's default name.
nativeFlavors.maybeCreate("");
} else {
for (ProductFlavorCombo group : androidFlavorGroups) {
nativeFlavors.maybeCreate(group.getName());
}
}
}
@Mutate
public void createNativeLibrary(
final ComponentSpecContainer specs,
@Path("android.ndk") final NdkConfig ndkConfig,
final NdkHandler ndkHandler,
@Path("android.sources") final AndroidComponentModelSourceSet sources,
@Path("buildDir") final File buildDir) {
if (!ndkHandler.isNdkDirConfigured()) {
return;
}
if (!ndkConfig.getModuleName().isEmpty()) {
specs.create(
ndkConfig.getModuleName(),
NativeLibrarySpec.class,
new Action<NativeLibrarySpec>() {
@Override
public void execute(final NativeLibrarySpec nativeLib) {
((DefaultAndroidComponentSpec) specs.get(COMPONENT_NAME))
.setNativeLibrary(nativeLib);
NdkConfiguration.configureProperties(
nativeLib,
sources,
buildDir,
ndkHandler);
}
});
DefaultAndroidComponentSpec androidSpecs =
(DefaultAndroidComponentSpec) specs.get(COMPONENT_NAME);
androidSpecs.setNativeLibrary(
(NativeLibrarySpec) specs.get(ndkConfig.getModuleName()));
}
}
@Mutate
public void createAdditionalTasksForNatives(
final ModelMap<Task> tasks,
ModelMap<AndroidComponentSpec> specs,
@Path("android.ndk") final NdkConfig ndkConfig,
final NdkHandler ndkHandler,
BinaryContainer binaries,
@Path("buildDir") final File buildDir) {
if (!ndkHandler.isNdkDirConfigured()) {
return;
}
final DefaultAndroidComponentSpec androidSpec =
(DefaultAndroidComponentSpec) specs.get(COMPONENT_NAME);
if (androidSpec.getNativeLibrary() != null) {
binaries.withType(DefaultAndroidBinary.class, new Action<DefaultAndroidBinary>() {
@Override
public void execute(DefaultAndroidBinary binary) {
for (NativeBinarySpec nativeBinary : binary.getNativeBinaries()) {
NdkConfiguration.createTasks(
tasks,
(SharedLibraryBinarySpec) nativeBinary,
buildDir,
binary.getMergedNdkConfig(),
ndkHandler);
}
}
});
}
}
@Mutate
public void configureNativeBinary(
BinaryContainer binaries,
ComponentSpecContainer specs,
@Path("android.ndk") final NdkConfig ndkConfig,
@Path("buildDir") final File buildDir,
final NdkHandler ndkHandler) {
if (!ndkConfig.getModuleName().isEmpty()) {
final NativeLibrarySpec library = specs.withType(NativeLibrarySpec.class)
.get(ndkConfig.getModuleName());
binaries.withType(
DefaultAndroidBinary.class,
new Action<DefaultAndroidBinary>() {
@Override
public void execute(DefaultAndroidBinary binary) {
binary.computeMergedNdk(
ndkConfig,
binary.getProductFlavors(),
binary.getBuildType());
Collection<SharedLibraryBinarySpec> nativeBinaries =
getNativeBinaries(
library,
binary.getBuildType(),
binary.getProductFlavors());
for (SharedLibraryBinarySpec nativeBin : nativeBinaries) {
if (binary.getMergedNdkConfig().getAbiFilters().isEmpty() ||
binary.getMergedNdkConfig().getAbiFilters().contains(
nativeBin.getTargetPlatform().getName())) {
NdkConfiguration.configureBinary(
nativeBin,
buildDir,
binary.getMergedNdkConfig(),
ndkHandler);
binary.getNativeBinaries().add(nativeBin);
}
}
}
});
}
}
@Finalize
public void attachNativeTasksToAndroidBinary(ModelMap<AndroidBinary> binaries) {
binaries.afterEach(new Action<AndroidBinary>() {
@Override
public void execute(AndroidBinary androidBinary) {
DefaultAndroidBinary binary = (DefaultAndroidBinary) androidBinary;
for (NativeLibraryBinarySpec nativeBinary : binary.getNativeBinaries()) {
if (binary.getTargetAbi().isEmpty() || binary.getTargetAbi().contains(
nativeBinary.getTargetPlatform().getName())) {
binary.getBuildTask().dependsOn(NdkNamingScheme.getNdkBuildTaskName(nativeBinary));
}
}
}
});
}
@Mutate
public void removeNativeBinaryFromAssembleTask(ModelMap<AndroidComponentSpec> components) {
// Setting each native binary to not buildable to prevent the native tasks to be
// automatically added to the "assemble" task.
components.afterEach(new Action<AndroidComponentSpec>() {
@Override
public void execute(AndroidComponentSpec spec) {
NativeLibrarySpec nativeLibrary =
((DefaultAndroidComponentSpec)spec).getNativeLibrary();
if (nativeLibrary != null) {
nativeLibrary.getBinaries().afterEach(
new Action<BinarySpec>() {
@Override
public void execute(BinarySpec binary) {
((BaseBinarySpec) binary).setBuildable(false);
}
});
}
}
});
}
/**
* Remove unintended tasks created by Gradle native plugin from task list.
*
* Gradle native plugins creates static library tasks automatically. This method removes
* them to avoid cluttering the task list.
*/
@Mutate
public void hideNativeTasks(TaskContainer tasks, BinaryContainer binaries) {
// Gradle do not support a way to remove created tasks. The best workaround is to clear the
// group of the task and have another task depends on it. Therefore, we have to create
// a dummy task to depend on all the tasks that we do not want to show up on the task
// list. The dummy task dependsOn itself, effectively making it non-executable and
// invisible unless the --all option is use.
final Task nonExecutableTask = tasks.create("nonExecutableTask");
nonExecutableTask.dependsOn(nonExecutableTask);
nonExecutableTask
.setDescription("Dummy task to hide other unwanted tasks in the task list.");
binaries.withType(NativeLibraryBinarySpec.class, new Action<NativeLibraryBinarySpec>() {
@Override
public void execute(NativeLibraryBinarySpec binary) {
Task buildTask = binary.getBuildTask();
nonExecutableTask.dependsOn(buildTask);
buildTask.setGroup(null);
}
});
}
}
public static void configureScopeForNdk(VariantScope scope) {
VariantConfiguration config = scope.getVariantConfiguration();
ImmutableSet.Builder<File> builder = ImmutableSet.builder();
for (Abi abi : NdkHandler.getAbiList()) {
scope.addNdkDebuggableLibraryFolders(
abi,
new File(
scope.getGlobalScope().getBuildDir(),
NdkNamingScheme.getDebugLibraryDirectoryName(
config.getBuildType().getName(),
config.getFlavorName(),
abi.getName())));
// Return the parent directory of the binaries' output.
// If output directory is "/path/to/lib/platformName". We want to return
// "/path/to/lib".
builder.add(new File(
scope.getGlobalScope().getBuildDir(),
NdkNamingScheme.getOutputDirectoryName(
config.getBuildType().getName(),
config.getFlavorName(),
abi.getName())).getParentFile());
}
scope.setNdkSoFolder(builder.build());
}
private static Collection<SharedLibraryBinarySpec> getNativeBinaries(
NativeLibrarySpec library,
final BuildType buildType,
final List<ProductFlavor> productFlavors) {
final ProductFlavorCombo<ProductFlavor> flavorGroup =
new ProductFlavorCombo<ProductFlavor>(productFlavors);
return ImmutableList.copyOf(Iterables.filter(
library.getBinaries().withType(SharedLibraryBinarySpec.class).values(),
new Predicate<SharedLibraryBinarySpec>() {
@Override
public boolean apply(SharedLibraryBinarySpec binary) {
return binary.getBuildType().getName().equals(buildType.getName())
&& (binary.getFlavor().getName().equals(flavorGroup.getName())
|| (productFlavors.isEmpty()
&& binary.getFlavor().getName().equals("default")));
}
}));
}
/**
* Return library binaries for a VariantConfiguration.
*/
public Collection<? extends BuildableModelElement> getBinaries(final VariantConfiguration variantConfig) {
if (variantConfig.getType().isForTesting()) {
// Do not return binaries for test variants as test source set is not supported at the
// moment.
return Collections.emptyList();
}
BinaryContainer binaries = (BinaryContainer) project.getExtensions().getByName("binaries");
return binaries.withType(AndroidBinary.class).matching(
new Spec<AndroidBinary>() {
@Override
public boolean isSatisfiedBy(AndroidBinary binary) {
return (binary.getName().equals(variantConfig.getFullName()));
}
}
);
}
}