/* * 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())); } } ); } }