|  | import static groovy.io.FileType.FILES | 
|  |  | 
|  | import com.android.builder.model.AndroidProject | 
|  | import com.android.build.OutputFile | 
|  | import groovy.json.JsonSlurper | 
|  | import java.nio.file.Path | 
|  | import java.nio.file.Paths | 
|  | import java.util.regex.Matcher | 
|  | import java.util.regex.Pattern | 
|  | import org.apache.tools.ant.taskdefs.condition.Os | 
|  | import org.gradle.api.DefaultTask | 
|  | import org.gradle.api.GradleException | 
|  | import org.gradle.api.Project | 
|  | import org.gradle.api.Plugin | 
|  | import org.gradle.api.Task | 
|  | import org.gradle.api.file.CopySpec | 
|  | import org.gradle.api.file.FileCollection | 
|  | import org.gradle.api.tasks.Copy | 
|  | import org.gradle.api.tasks.InputFiles | 
|  | import org.gradle.api.tasks.OutputDirectory | 
|  | import org.gradle.api.tasks.TaskAction | 
|  | import org.gradle.api.tasks.bundling.Jar | 
|  |  | 
|  | buildscript { | 
|  | repositories { | 
|  | google() | 
|  | jcenter() | 
|  | } | 
|  | dependencies { | 
|  | classpath 'com.android.tools.build:gradle:3.5.0' | 
|  | } | 
|  | } | 
|  |  | 
|  | android { | 
|  | compileOptions { | 
|  | sourceCompatibility 1.8 | 
|  | targetCompatibility 1.8 | 
|  | } | 
|  | } | 
|  |  | 
|  | apply plugin: FlutterPlugin | 
|  |  | 
|  | class FlutterPlugin implements Plugin<Project> { | 
|  | private static final String MAVEN_REPO      = "https://storage.googleapis.com/download.flutter.io"; | 
|  |  | 
|  | // The platforms that can be passed to the `--Ptarget-platform` flag. | 
|  | private static final String PLATFORM_ARM32  = "android-arm"; | 
|  | private static final String PLATFORM_ARM64  = "android-arm64"; | 
|  | private static final String PLATFORM_X86    = "android-x86"; | 
|  | private static final String PLATFORM_X86_64 = "android-x64"; | 
|  |  | 
|  | // The ABI architectures. | 
|  | private static final String ARCH_ARM32      = "armeabi-v7a"; | 
|  | private static final String ARCH_ARM64      = "arm64-v8a"; | 
|  | private static final String ARCH_X86        = "x86"; | 
|  | private static final String ARCH_X86_64     = "x86_64"; | 
|  |  | 
|  | // Maps platforms to ABI architectures. | 
|  | private static final Map PLATFORM_ARCH_MAP = [ | 
|  | (PLATFORM_ARM32)    : ARCH_ARM32, | 
|  | (PLATFORM_ARM64)    : ARCH_ARM64, | 
|  | (PLATFORM_X86)      : ARCH_X86, | 
|  | (PLATFORM_X86_64)   : ARCH_X86_64, | 
|  | ] | 
|  |  | 
|  | // The version code that gives each ABI a value. | 
|  | // For each APK variant, use the following versions to override the version of the Universal APK. | 
|  | // Otherwise, the Play Store will complain that the APK variants have the same version. | 
|  | private static final Map ABI_VERSION = [ | 
|  | (ARCH_ARM32)        : 1, | 
|  | (ARCH_ARM64)        : 2, | 
|  | (ARCH_X86)          : 3, | 
|  | (ARCH_X86_64)       : 4, | 
|  | ] | 
|  |  | 
|  | // When split is enabled, multiple APKs are generated per each ABI. | 
|  | private static final List DEFAULT_PLATFORMS = [ | 
|  | PLATFORM_ARM32, | 
|  | PLATFORM_ARM64, | 
|  | ] | 
|  |  | 
|  | // The name prefix for flutter builds.  This is used to identify gradle tasks | 
|  | // where we expect the flutter tool to provide any error output, and skip the | 
|  | // standard Gradle error output in the FlutterEventLogger. If you change this, | 
|  | // be sure to change any instances of this string in symbols in the code below | 
|  | // to match. | 
|  | static final String FLUTTER_BUILD_PREFIX = "flutterBuild" | 
|  |  | 
|  | private Project project | 
|  | private Map baseJar = [:] | 
|  | private File flutterRoot | 
|  | private File flutterExecutable | 
|  | private String localEngine | 
|  | private String localEngineSrcPath | 
|  | private Properties localProperties | 
|  | private String engineVersion | 
|  |  | 
|  | @Override | 
|  | void apply(Project project) { | 
|  | this.project = project | 
|  |  | 
|  | project.extensions.create("flutter", FlutterExtension) | 
|  | project.afterEvaluate this.&addFlutterTasks | 
|  | // By default, assembling APKs generates fat APKs if multiple platforms are passed. | 
|  | // Configuring split per ABI allows to generate separate APKs for each abi. | 
|  | // This is a noop when building a bundle. | 
|  | if (shouldSplitPerAbi()) { | 
|  | project.android { | 
|  | splits { | 
|  | abi { | 
|  | // Enables building multiple APKs per ABI. | 
|  | enable true | 
|  | // Resets the list of ABIs that Gradle should create APKs for to none. | 
|  | reset() | 
|  | // Specifies that we do not want to also generate a universal APK that includes all ABIs. | 
|  | universalApk false | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | getTargetPlatforms().each { targetArch -> | 
|  | String abiValue = PLATFORM_ARCH_MAP[targetArch] | 
|  | project.android { | 
|  | if (shouldSplitPerAbi()) { | 
|  | splits { | 
|  | abi { | 
|  | include abiValue | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | String flutterRootPath = resolveProperty("flutter.sdk", System.env.FLUTTER_ROOT) | 
|  | if (flutterRootPath == null) { | 
|  | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file or with a FLUTTER_ROOT environment variable.") | 
|  | } | 
|  | flutterRoot = project.file(flutterRootPath) | 
|  | if (!flutterRoot.isDirectory()) { | 
|  | throw new GradleException("flutter.sdk must point to the Flutter SDK directory") | 
|  | } | 
|  |  | 
|  | engineVersion = useLocalEngine() | 
|  | ? "+" // Match any version since there's only one. | 
|  | : "1.0.0-" + Paths.get(flutterRoot.absolutePath, "bin", "internal", "engine.version").toFile().text.trim() | 
|  |  | 
|  | String flutterExecutableName = Os.isFamily(Os.FAMILY_WINDOWS) ? "flutter.bat" : "flutter" | 
|  | flutterExecutable = Paths.get(flutterRoot.absolutePath, "bin", flutterExecutableName).toFile(); | 
|  |  | 
|  | // Add custom build types. | 
|  | project.android.buildTypes { | 
|  | profile { | 
|  | initWith debug | 
|  | if (it.hasProperty("matchingFallbacks")) { | 
|  | matchingFallbacks = ["debug", "release"] | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | if (shouldShrinkResources(project)) { | 
|  | String flutterProguardRules = Paths.get(flutterRoot.absolutePath, "packages", "flutter_tools", | 
|  | "gradle", "flutter_proguard_rules.pro") | 
|  | project.android.buildTypes { | 
|  | release { | 
|  | // Enables code shrinking, obfuscation, and optimization for only | 
|  | // your project's release build type. | 
|  | minifyEnabled true | 
|  | // Enables resource shrinking, which is performed by the | 
|  | // Android Gradle plugin. | 
|  | // NOTE: The resource shrinker can't be used for libraries. | 
|  | shrinkResources isBuiltAsApp(project) | 
|  | // Fallback to `android/app/proguard-rules.pro`. | 
|  | // This way, custom Proguard rules can be configured as needed. | 
|  | proguardFiles project.android.getDefaultProguardFile("proguard-android.txt"), flutterProguardRules, "proguard-rules.pro" | 
|  | } | 
|  | } | 
|  | } | 
|  | if (useLocalEngine()) { | 
|  | // This is required to pass the local engine to flutter build aot. | 
|  | String engineOutPath = project.property('local-engine-out') | 
|  | File engineOut = project.file(engineOutPath) | 
|  | if (!engineOut.isDirectory()) { | 
|  | throw new GradleException('local-engine-out must point to a local engine build') | 
|  | } | 
|  | localEngine = engineOut.name | 
|  | localEngineSrcPath = engineOut.parentFile.parent | 
|  | } | 
|  | project.android.buildTypes.each this.&addFlutterDependencies | 
|  | project.android.buildTypes.whenObjectAdded this.&addFlutterDependencies | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Adds the dependencies required by the Flutter project. | 
|  | * This includes: | 
|  | *    1. The embedding | 
|  | *    2. libflutter.so | 
|  | */ | 
|  | void addFlutterDependencies(buildType) { | 
|  | String flutterBuildMode = buildModeFor(buildType) | 
|  | if (!supportsBuildMode(flutterBuildMode)) { | 
|  | return | 
|  | } | 
|  | String repository = useLocalEngine() | 
|  | ? project.property('local-engine-repo') | 
|  | : MAVEN_REPO | 
|  |  | 
|  | project.rootProject.allprojects { | 
|  | repositories { | 
|  | maven { | 
|  | url repository | 
|  | } | 
|  | } | 
|  | } | 
|  | // Add the embedding dependency. | 
|  | addApiDependencies(project, buildType.name, | 
|  | "io.flutter:flutter_embedding_$flutterBuildMode:$engineVersion") | 
|  |  | 
|  | List<String> platforms = getTargetPlatforms().collect() | 
|  | // Debug mode includes x86 and x64, which are commonly used in emulators. | 
|  | if (flutterBuildMode == "debug" && !useLocalEngine()) { | 
|  | platforms.add("android-x86") | 
|  | platforms.add("android-x64") | 
|  | } | 
|  | platforms.each { platform -> | 
|  | String arch = PLATFORM_ARCH_MAP[platform].replace("-", "_") | 
|  | // Add the `libflutter.so` dependency. | 
|  | addApiDependencies(project, buildType.name, | 
|  | "io.flutter:${arch}_$flutterBuildMode:$engineVersion") | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns the directory where the plugins are built. | 
|  | */ | 
|  | private File getPluginBuildDir() { | 
|  | // Module projects specify this flag to include plugins in the same repo as the module project. | 
|  | if (project.ext.has("pluginBuildDir")) { | 
|  | return project.ext.get("pluginBuildDir") | 
|  | } | 
|  | return project.buildDir | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Configures the Flutter plugin dependencies. | 
|  | * | 
|  | * The plugins are added to pubspec.yaml. Then, upon running `flutter pub get`, | 
|  | * the tool generates a `.flutter-plugins` file, which contains a 1:1 map to each plugin location. | 
|  | * Finally, the project's `settings.gradle` loads each plugin's android directory as a subproject. | 
|  | */ | 
|  | private void configurePlugins() { | 
|  | if (!buildPluginAsAar()) { | 
|  | getPluginList().each this.&configurePluginProject | 
|  | getPluginDependencies().each this.&configurePluginDependencies | 
|  | return | 
|  | } | 
|  | project.repositories { | 
|  | maven { | 
|  | url "${getPluginBuildDir()}/outputs/repo" | 
|  | } | 
|  | } | 
|  | getPluginList().each { pluginName, pluginPath -> | 
|  | configurePluginAar(pluginName, pluginPath, project) | 
|  | } | 
|  | } | 
|  |  | 
|  | private static final Pattern GROUP_PATTERN = ~/group\s+\'(.+)\'/ | 
|  | private static final Pattern PROJECT_NAME_PATTERN = ~/rootProject\.name\s+=\s+\'(.+)\'/ | 
|  |  | 
|  | // Adds the plugin AAR dependency to the app project. | 
|  | private void configurePluginAar(String pluginName, String pluginPath, Project project) { | 
|  | // Extract the group id from the plugin's build.gradle. | 
|  | // This is `group '<group-id>'` | 
|  | File pluginBuildFile = project.file(Paths.get(pluginPath, "android", "build.gradle")); | 
|  | if (!pluginBuildFile.exists()) { | 
|  | throw new GradleException("Plugin $pluginName doesn't have the required file $pluginBuildFile.") | 
|  | } | 
|  |  | 
|  | Matcher groupParts = GROUP_PATTERN.matcher(pluginBuildFile.text) | 
|  | assert groupParts.count == 1 | 
|  | assert groupParts.hasGroup() | 
|  | String groupId = groupParts[0][1] | 
|  |  | 
|  | // Extract the artifact name from the plugin's settings.gradle. | 
|  | // This is `rootProject.name = '<artifact-name>'` | 
|  | File pluginSettings = project.file(Paths.get(pluginPath, "android", "settings.gradle")); | 
|  | if (!pluginSettings.exists()) { | 
|  | throw new GradleException("Plugin $pluginName doesn't have the required file $pluginSettings.") | 
|  | } | 
|  | Matcher projectNameParts = PROJECT_NAME_PATTERN.matcher(pluginSettings.text) | 
|  | assert projectNameParts.count == 1 | 
|  | assert projectNameParts.hasGroup() | 
|  | String artifactId = "${projectNameParts[0][1]}_release" | 
|  |  | 
|  | assert !groupId.empty | 
|  | project.dependencies.add("api", "$groupId:$artifactId:+") | 
|  | } | 
|  |  | 
|  | // Adds the plugin project dependency to the app project . | 
|  | private void configurePluginProject(String pluginName, String _) { | 
|  | Project pluginProject = project.rootProject.findProject(":$pluginName") | 
|  | if (pluginProject == null) { | 
|  | project.logger.error("Plugin project :$pluginName not found. Please update settings.gradle.") | 
|  | return | 
|  | } | 
|  | // Add plugin dependency to the app project. | 
|  | project.dependencies { | 
|  | implementation pluginProject | 
|  | } | 
|  | Closure addEmbeddingCompileOnlyDependency = { buildType -> | 
|  | String flutterBuildMode = buildModeFor(buildType) | 
|  | // In AGP 3.5, the embedding must be added as an API implementation, | 
|  | // so java8 features are desugared against the runtime classpath. | 
|  | // For more, see https://github.com/flutter/flutter/issues/40126 | 
|  | if (!supportsBuildMode(flutterBuildMode)) { | 
|  | return | 
|  | } | 
|  | addApiDependencies( | 
|  | pluginProject, | 
|  | buildType.name, | 
|  | "io.flutter:flutter_embedding_$flutterBuildMode:$engineVersion" | 
|  | ) | 
|  | } | 
|  | pluginProject.afterEvaluate { | 
|  | pluginProject.android.buildTypes { | 
|  | profile { | 
|  | initWith debug | 
|  | } | 
|  | } | 
|  | pluginProject.android.buildTypes.each addEmbeddingCompileOnlyDependency | 
|  | pluginProject.android.buildTypes.whenObjectAdded addEmbeddingCompileOnlyDependency | 
|  | } | 
|  | } | 
|  |  | 
|  | // Returns `true` if the given path contains an `android/build.gradle` file. | 
|  | // | 
|  | // TODO(egarciad): Fix https://github.com/flutter/flutter/issues/39657. | 
|  | // Android Studio may create empty android directories due to the logic in <app>/android/settings.gradle, | 
|  | // which imports all plugins regardless of whether they support the android platform. | 
|  | private Boolean doesSupportAndroidPlatform(String path) { | 
|  | File editableAndroidProject = new File(path, 'android' + File.separator + 'build.gradle') | 
|  | return editableAndroidProject.exists() | 
|  | } | 
|  |  | 
|  | // Add the dependencies on other plugin projects to the plugin project. | 
|  | // A plugin A can depend on plugin B. As a result, this dependency must be surfaced by | 
|  | // making the Gradle plugin project A depend on the Gradle plugin project B. | 
|  | private void configurePluginDependencies(Object dependencyObject) { | 
|  | assert dependencyObject.name instanceof String | 
|  | Project pluginProject = project.rootProject.findProject(":${dependencyObject.name}") | 
|  | if (pluginProject == null || | 
|  | !doesSupportAndroidPlatform(pluginProject.projectDir.parentFile.path)) { | 
|  | return | 
|  | } | 
|  | assert dependencyObject.dependencies instanceof List | 
|  | dependencyObject.dependencies.each { pluginDependencyName -> | 
|  | assert pluginDependencyName instanceof String | 
|  | if (pluginDependencyName.empty) { | 
|  | return | 
|  | } | 
|  | Project dependencyProject = project.rootProject.findProject(":$pluginDependencyName") | 
|  | if (dependencyProject == null || | 
|  | !doesSupportAndroidPlatform(dependencyProject.projectDir.parentFile.path)) { | 
|  | return | 
|  | } | 
|  | // Wait for the Android plugin to load and add the dependency to the plugin project. | 
|  | pluginProject.afterEvaluate { | 
|  | pluginProject.dependencies { | 
|  | implementation dependencyProject | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | private Properties getPluginList() { | 
|  | File pluginsFile = new File(project.projectDir.parentFile.parentFile, '.flutter-plugins') | 
|  | Properties allPlugins = readPropertiesIfExist(pluginsFile) | 
|  | Properties androidPlugins = new Properties() | 
|  | allPlugins.each { name, path -> | 
|  | if (doesSupportAndroidPlatform(path)) { | 
|  | androidPlugins.setProperty(name, path) | 
|  | } | 
|  | // TODO(amirh): log an error if this plugin was specified to be an Android | 
|  | // plugin according to the new schema, and was missing a build.gradle file. | 
|  | // https://github.com/flutter/flutter/issues/40784 | 
|  | } | 
|  | return androidPlugins | 
|  | } | 
|  |  | 
|  | // Gets the plugins dependencies from `.flutter-plugins-dependencies`. | 
|  | private List getPluginDependencies() { | 
|  | // Consider a `.flutter-plugins-dependencies` file with the following content: | 
|  | // { | 
|  | //     "dependencyGraph": [ | 
|  | //       { | 
|  | //         "name": "plugin-a", | 
|  | //         "dependencies": ["plugin-b","plugin-c"] | 
|  | //       }, | 
|  | //       { | 
|  | //         "name": "plugin-b", | 
|  | //         "dependencies": ["plugin-c"] | 
|  | //       }, | 
|  | //       { | 
|  | //         "name": "plugin-c", | 
|  | //         "dependencies": []' | 
|  | //       } | 
|  | //     ] | 
|  | //  } | 
|  | // | 
|  | // This means, `plugin-a` depends on `plugin-b` and `plugin-c`. | 
|  | // `plugin-b` depends on `plugin-c`. | 
|  | // `plugin-c` doesn't depend on anything. | 
|  | File pluginsDependencyFile = new File(project.projectDir.parentFile.parentFile, '.flutter-plugins-dependencies') | 
|  | if (pluginsDependencyFile.exists()) { | 
|  | def object = new JsonSlurper().parseText(pluginsDependencyFile.text) | 
|  | assert object instanceof Map | 
|  | assert object.dependencyGraph instanceof List | 
|  | return object.dependencyGraph | 
|  | } | 
|  | return [] | 
|  | } | 
|  |  | 
|  | private static String toCammelCase(List<String> parts) { | 
|  | if (parts.empty) { | 
|  | return "" | 
|  | } | 
|  | return "${parts[0]}${parts[1..-1].collect { it.capitalize() }.join('')}" | 
|  | } | 
|  |  | 
|  | private String resolveProperty(String name, String defaultValue) { | 
|  | if (localProperties == null) { | 
|  | localProperties = readPropertiesIfExist(new File(project.projectDir.parentFile, "local.properties")) | 
|  | } | 
|  | String result | 
|  | if (project.hasProperty(name)) { | 
|  | result = project.property(name) | 
|  | } | 
|  | if (result == null) { | 
|  | result = localProperties.getProperty(name) | 
|  | } | 
|  | if (result == null) { | 
|  | result = defaultValue | 
|  | } | 
|  | return result | 
|  | } | 
|  |  | 
|  | private static Properties readPropertiesIfExist(File propertiesFile) { | 
|  | Properties result = new Properties() | 
|  | if (propertiesFile.exists()) { | 
|  | propertiesFile.withReader('UTF-8') { reader -> result.load(reader) } | 
|  | } | 
|  | return result | 
|  | } | 
|  |  | 
|  | private List<String> getTargetPlatforms() { | 
|  | if (!project.hasProperty('target-platform')) { | 
|  | return DEFAULT_PLATFORMS | 
|  | } | 
|  | return project.property('target-platform').split(',').collect { | 
|  | if (!PLATFORM_ARCH_MAP[it]) { | 
|  | throw new GradleException("Invalid platform: $it.") | 
|  | } | 
|  | return it | 
|  | } | 
|  | } | 
|  |  | 
|  | private Boolean shouldSplitPerAbi() { | 
|  | if (project.hasProperty('split-per-abi')) { | 
|  | return project.property('split-per-abi').toBoolean() | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | private Boolean useLocalEngine() { | 
|  | return project.hasProperty('local-engine-repo') | 
|  | } | 
|  |  | 
|  | private Boolean isVerbose() { | 
|  | if (project.hasProperty('verbose')) { | 
|  | return project.property('verbose').toBoolean() | 
|  | } | 
|  | return false | 
|  | } | 
|  |  | 
|  | /// Whether to build the debug app in "fast-start" mode. | 
|  | private Boolean isFastStart() { | 
|  | if (project.hasProperty("fast-start")) { | 
|  | return project.property("fast-start").toBoolean() | 
|  | } | 
|  | return false | 
|  | } | 
|  |  | 
|  |  | 
|  | private static Boolean shouldShrinkResources(Project project) { | 
|  | if (project.hasProperty("shrink")) { | 
|  | return project.property("shrink").toBoolean() | 
|  | } | 
|  | return false | 
|  | } | 
|  |  | 
|  | private static Boolean isBuiltAsApp(Project project) { | 
|  | // Projects are built as applications when the they use the `com.android.application` | 
|  | // plugin. | 
|  | return project.plugins.hasPlugin("com.android.application"); | 
|  | } | 
|  |  | 
|  | private static Boolean buildPluginAsAar() { | 
|  | return System.getProperty('build-plugins-as-aars') == 'true' | 
|  | } | 
|  |  | 
|  | // Returns true if the build mode is supported by the current call to Gradle. | 
|  | // This only relevant when using a local engine. Because the engine | 
|  | // is built for a specific mode, the call to Gradle must match that mode. | 
|  | private Boolean supportsBuildMode(String flutterBuildMode) { | 
|  | if (!useLocalEngine()) { | 
|  | return true; | 
|  | } | 
|  | assert project.hasProperty('local-engine-build-mode') | 
|  | // Don't configure dependencies for a build mode that the local engine | 
|  | // doesn't support. | 
|  | return project.property('local-engine-build-mode') == flutterBuildMode | 
|  | } | 
|  |  | 
|  | private void addCompileOnlyDependency(Project project, String variantName, Object dependency, Closure config = null) { | 
|  | if (project.state.failure) { | 
|  | return | 
|  | } | 
|  | String configuration; | 
|  | if (project.getConfigurations().findByName("compileOnly")) { | 
|  | configuration = "${variantName}CompileOnly"; | 
|  | } else { | 
|  | configuration = "${variantName}Provided"; | 
|  | } | 
|  | project.dependencies.add(configuration, dependency, config) | 
|  | } | 
|  |  | 
|  | private static void addApiDependencies(Project project, String variantName, Object dependency, Closure config = null) { | 
|  | String configuration; | 
|  | // `compile` dependencies are now `api` dependencies. | 
|  | if (project.getConfigurations().findByName("api")) { | 
|  | configuration = "${variantName}Api"; | 
|  | } else { | 
|  | configuration = "${variantName}Compile"; | 
|  | } | 
|  | project.dependencies.add(configuration, dependency, config) | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns a Flutter build mode suitable for the specified Android buildType. | 
|  | * | 
|  | * Note: The BuildType DSL type is not public, and is therefore omitted from the signature. | 
|  | * | 
|  | * @return "debug", "profile", or "release" (fall-back). | 
|  | */ | 
|  | private static String buildModeFor(buildType) { | 
|  | if (buildType.name == "profile") { | 
|  | return "profile" | 
|  | } else if (buildType.debuggable) { | 
|  | return "debug" | 
|  | } | 
|  | return "release" | 
|  | } | 
|  |  | 
|  | private static String getEngineArtifactDirName(buildType, targetArch) { | 
|  | if (buildType.name == "profile") { | 
|  | return "${targetArch}-profile" | 
|  | } else if (buildType.debuggable) { | 
|  | return "${targetArch}" | 
|  | } | 
|  | return "${targetArch}-release" | 
|  | } | 
|  |  | 
|  | private void addFlutterTasks(Project project) { | 
|  | if (project.state.failure) { | 
|  | return | 
|  | } | 
|  | if (project.flutter.source == null) { | 
|  | throw new GradleException("Must provide Flutter source directory") | 
|  | } | 
|  | String target = project.flutter.target | 
|  | if (target == null) { | 
|  | target = 'lib/main.dart' | 
|  | } | 
|  | if (project.hasProperty('target')) { | 
|  | target = project.property('target') | 
|  | } | 
|  | String[] fileSystemRootsValue = null | 
|  | if (project.hasProperty('filesystem-roots')) { | 
|  | fileSystemRootsValue = project.property('filesystem-roots').split('\\|') | 
|  | } | 
|  | String fileSystemSchemeValue = null | 
|  | if (project.hasProperty('filesystem-scheme')) { | 
|  | fileSystemSchemeValue = project.property('filesystem-scheme') | 
|  | } | 
|  | Boolean trackWidgetCreationValue = false | 
|  | if (project.hasProperty('track-widget-creation')) { | 
|  | trackWidgetCreationValue = project.property('track-widget-creation').toBoolean() | 
|  | } | 
|  | String extraFrontEndOptionsValue = null | 
|  | if (project.hasProperty('extra-front-end-options')) { | 
|  | extraFrontEndOptionsValue = project.property('extra-front-end-options') | 
|  | } | 
|  | String extraGenSnapshotOptionsValue = null | 
|  | if (project.hasProperty('extra-gen-snapshot-options')) { | 
|  | extraGenSnapshotOptionsValue = project.property('extra-gen-snapshot-options') | 
|  | } | 
|  | String splitDebugInfoValue = null | 
|  | if (project.hasProperty('split-debug-info')) { | 
|  | splitDebugInfoValue = project.property('split-debug-info') | 
|  | } | 
|  | Boolean dartObfuscationValue = false | 
|  | if (project.hasProperty('dart-obfuscation')) { | 
|  | dartObfuscationValue = project.property('dart-obfuscation').toBoolean(); | 
|  | } | 
|  | Boolean treeShakeIconsOptionsValue = false | 
|  | if (project.hasProperty('tree-shake-icons')) { | 
|  | treeShakeIconsOptionsValue = project.property('tree-shake-icons').toBoolean() | 
|  | } | 
|  | String dartDefinesValue = null | 
|  | if (project.hasProperty('dart-defines')) { | 
|  | dartDefinesValue = project.property('dart-defines') | 
|  | } | 
|  | def targetPlatforms = getTargetPlatforms() | 
|  | def addFlutterDeps = { variant -> | 
|  | if (shouldSplitPerAbi()) { | 
|  | variant.outputs.each { output -> | 
|  | // Assigns the new version code to versionCodeOverride, which changes the version code | 
|  | // for only the output APK, not for the variant itself. Skipping this step simply | 
|  | // causes Gradle to use the value of variant.versionCode for the APK. | 
|  | // For more, see https://developer.android.com/studio/build/configure-apk-splits | 
|  | def abiVersionCode = ABI_VERSION.get(output.getFilter(OutputFile.ABI)) | 
|  | if (abiVersionCode != null) { | 
|  | output.versionCodeOverride = | 
|  | abiVersionCode * 1000 + variant.versionCode | 
|  | } | 
|  | } | 
|  | } | 
|  | String variantBuildMode = buildModeFor(variant.buildType) | 
|  | String taskName = toCammelCase(["compile", FLUTTER_BUILD_PREFIX, variant.name]) | 
|  | FlutterTask compileTask = project.tasks.create(name: taskName, type: FlutterTask) { | 
|  | flutterRoot this.flutterRoot | 
|  | flutterExecutable this.flutterExecutable | 
|  | buildMode variantBuildMode | 
|  | localEngine this.localEngine | 
|  | localEngineSrcPath this.localEngineSrcPath | 
|  | targetPath target | 
|  | verbose isVerbose() | 
|  | fastStart isFastStart() | 
|  | fileSystemRoots fileSystemRootsValue | 
|  | fileSystemScheme fileSystemSchemeValue | 
|  | trackWidgetCreation trackWidgetCreationValue | 
|  | targetPlatformValues = targetPlatforms | 
|  | sourceDir project.file(project.flutter.source) | 
|  | intermediateDir project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}/") | 
|  | extraFrontEndOptions extraFrontEndOptionsValue | 
|  | extraGenSnapshotOptions extraGenSnapshotOptionsValue | 
|  | splitDebugInfo splitDebugInfoValue | 
|  | treeShakeIcons treeShakeIconsOptionsValue | 
|  | dartObfuscation dartObfuscationValue | 
|  | dartDefines dartDefinesValue | 
|  | doLast { | 
|  | project.exec { | 
|  | if (Os.isFamily(Os.FAMILY_WINDOWS)) { | 
|  | commandLine('cmd', '/c', "attrib -r ${assetsDirectory}/* /s") | 
|  | } else { | 
|  | commandLine('chmod', '-R', 'u+w', assetsDirectory) | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | File libJar = project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}/libs.jar") | 
|  | Task packFlutterAppAotTask = project.tasks.create(name: "packLibs${FLUTTER_BUILD_PREFIX}${variant.name.capitalize()}", type: Jar) { | 
|  | destinationDir libJar.parentFile | 
|  | archiveName libJar.name | 
|  | dependsOn compileTask | 
|  | targetPlatforms.each { targetPlatform -> | 
|  | String abi = PLATFORM_ARCH_MAP[targetPlatform] | 
|  | from("${compileTask.intermediateDir}/${abi}") { | 
|  | include "*.so" | 
|  | // Move `app.so` to `lib/<abi>/libapp.so` | 
|  | rename { String filename -> | 
|  | return "lib/${abi}/lib${filename}" | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | addApiDependencies(project, variant.name, project.files { | 
|  | packFlutterAppAotTask | 
|  | }) | 
|  | // We build an AAR when this property is defined. | 
|  | boolean isBuildingAar = project.hasProperty('is-plugin') | 
|  | // In add to app scenarios, a Gradle project contains a `:flutter` and `:app` project. | 
|  | // We know that `:flutter` is used as a subproject when these tasks exists and we aren't building an AAR. | 
|  | Task packageAssets = project.tasks.findByPath(":flutter:package${variant.name.capitalize()}Assets") | 
|  | Task cleanPackageAssets = project.tasks.findByPath(":flutter:cleanPackage${variant.name.capitalize()}Assets") | 
|  | boolean isUsedAsSubproject = packageAssets && cleanPackageAssets && !isBuildingAar | 
|  | Task copyFlutterAssetsTask = project.tasks.create( | 
|  | name: "copyFlutterAssets${variant.name.capitalize()}", | 
|  | type: Copy, | 
|  | ) { | 
|  | dependsOn compileTask | 
|  | with compileTask.assets | 
|  | if (isUsedAsSubproject) { | 
|  | dependsOn packageAssets | 
|  | dependsOn cleanPackageAssets | 
|  | into packageAssets.outputDir | 
|  | return | 
|  | } | 
|  | // `variant.mergeAssets` will be removed at the end of 2019. | 
|  | def mergeAssets = variant.hasProperty("mergeAssetsProvider") ? | 
|  | variant.mergeAssetsProvider.get() : variant.mergeAssets | 
|  | dependsOn mergeAssets | 
|  | dependsOn "clean${mergeAssets.name.capitalize()}" | 
|  | mergeAssets.mustRunAfter("clean${mergeAssets.name.capitalize()}") | 
|  | into mergeAssets.outputDir | 
|  | } | 
|  | if (!isUsedAsSubproject) { | 
|  | def variantOutput = variant.outputs.first() | 
|  | def processResources = variantOutput.hasProperty("processResourcesProvider") ? | 
|  | variantOutput.processResourcesProvider.get() : variantOutput.processResources | 
|  | processResources.dependsOn(copyFlutterAssetsTask) | 
|  | return | 
|  | } | 
|  | // Flutter module included as a subproject in add to app. | 
|  | String hostAppProjectName = project.rootProject.hasProperty('flutter.hostAppProjectName') ? project.rootProject.property('flutter.hostAppProjectName')  : "app" | 
|  | Project appProject = project.rootProject.findProject(":${hostAppProjectName}") | 
|  | assert appProject != null : "Project :${hostAppProjectName} doesn't exist. To custom the host app project name, set `org.gradle.project.flutter.hostAppProjectName=<project-name>` in gradle.properties." | 
|  | appProject.afterEvaluate { | 
|  | assert appProject.android != null | 
|  | appProject.android.applicationVariants.all { appProjectVariant -> | 
|  | // Find a compatible application variant in the host app. | 
|  | // | 
|  | // For example, consider a host app that defines the following variants: | 
|  | // | ----------------- | ----------------------------- | | 
|  | // |   Build Variant   |   Flutter Equivalent Variant  | | 
|  | // | ----------------- | ----------------------------- | | 
|  | // |   freeRelease     |   relese                      | | 
|  | // |   freeDebug       |   debug                       | | 
|  | // |   freeDevelop     |   debug                       | | 
|  | // |   profile         |   profile                     | | 
|  | // | ----------------- | ----------------------------- | | 
|  | // | 
|  | // This mapping is based on the following rules: | 
|  | // 1. If the host app build variant name is `profile` then the equivalent | 
|  | //    Flutter variant is `profile`. | 
|  | // 2. If the host app build variant is debuggable | 
|  | //    (e.g. `buildType.debuggable = true`), then the equivalent Flutter | 
|  | //    variant is `debug`. | 
|  | // 3. Otherwise, the equivalent Flutter variant is `release`. | 
|  | if (buildModeFor(appProjectVariant.buildType) != variantBuildMode) { | 
|  | return | 
|  | } | 
|  | Task mergeAssets = project | 
|  | .tasks | 
|  | .findByPath(":${hostAppProjectName}:merge${appProjectVariant.name.capitalize()}Assets") | 
|  | assert mergeAssets | 
|  | mergeAssets.dependsOn(copyFlutterAssetsTask) | 
|  | } | 
|  | } | 
|  | } | 
|  | if (project.android.hasProperty("applicationVariants")) { | 
|  | project.android.applicationVariants.all { variant -> | 
|  | addFlutterDeps(variant) | 
|  | // Copy the output APKs into a known location, so `flutter run` or `flutter build apk` | 
|  | // can discover them. By default, this is `<app-dir>/build/app/outputs/flutter-apk/<filename>.apk`. | 
|  | // | 
|  | // The filename consists of `app<-abi>?<-flavor-name>?-<build-mode>.apk`. | 
|  | // Where: | 
|  | //   * `abi` can be `armeabi-v7a|arm64-v8a|x86|x86_64` only if the flag `split-per-abi` is set. | 
|  | //   * `flavor-name` is the flavor used to build the app in lower case if the assemble task is called. | 
|  | //   * `build-mode` can be `release|debug|profile`. | 
|  | variant.outputs.all { output -> | 
|  | // `assemble` became `assembleProvider` in AGP 3.3.0. | 
|  | def assembleTask = variant.hasProperty("assembleProvider") | 
|  | ? variant.assembleProvider.get() | 
|  | : variant.assemble | 
|  | assembleTask.doLast { | 
|  | // `packageApplication` became `packageApplicationProvider` in AGP 3.3.0. | 
|  | def outputDirectory = variant.hasProperty("packageApplicationProvider") | 
|  | ? variant.packageApplicationProvider.get().outputDirectory | 
|  | : variant.packageApplication.outputDirectory | 
|  | //  `outputDirectory` is a `DirectoryProperty` in AGP 4.1. | 
|  | String outputDirectoryStr = outputDirectory.metaClass.respondsTo(outputDirectory, "get") | 
|  | ? outputDirectory.get() | 
|  | : outputDirectory | 
|  | String filename = "app" | 
|  | String abi = output.getFilter(OutputFile.ABI) | 
|  | if (abi != null && !abi.isEmpty()) { | 
|  | filename += "-${abi}" | 
|  | } | 
|  | if (variant.flavorName != null && !variant.flavorName.isEmpty()) { | 
|  | filename += "-${variant.flavorName.toLowerCase()}" | 
|  | } | 
|  | filename += "-${buildModeFor(variant.buildType)}" | 
|  | project.copy { | 
|  | from new File("$outputDirectoryStr/${output.outputFileName}") | 
|  | into new File("${project.buildDir}/outputs/flutter-apk"); | 
|  | rename { | 
|  | return "${filename}.apk" | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } else { | 
|  | project.android.libraryVariants.all addFlutterDeps | 
|  | } | 
|  | configurePlugins() | 
|  | } | 
|  | } | 
|  |  | 
|  | class FlutterExtension { | 
|  | String source | 
|  | String target | 
|  | } | 
|  |  | 
|  | abstract class BaseFlutterTask extends DefaultTask { | 
|  | File flutterRoot | 
|  | File flutterExecutable | 
|  | String buildMode | 
|  | @Optional @Input | 
|  | String localEngine | 
|  | @Optional @Input | 
|  | String localEngineSrcPath | 
|  | @Input | 
|  | Boolean fastStart | 
|  | @Input | 
|  | String targetPath | 
|  | @Optional | 
|  | Boolean verbose | 
|  | @Optional @Input | 
|  | String[] fileSystemRoots | 
|  | @Optional @Input | 
|  | String fileSystemScheme | 
|  | @Input | 
|  | Boolean trackWidgetCreation | 
|  | @Optional @Input | 
|  | List<String> targetPlatformValues | 
|  | File sourceDir | 
|  | File intermediateDir | 
|  | @Optional @Input | 
|  | String extraFrontEndOptions | 
|  | @Optional @Input | 
|  | String extraGenSnapshotOptions | 
|  | @Optional @Input | 
|  | String splitDebugInfo | 
|  | @Optional @Input | 
|  | Boolean treeShakeIcons | 
|  | @Optional @Input | 
|  | Boolean dartObfuscation | 
|  | @Optional @Input | 
|  | String dartDefines | 
|  |  | 
|  | @OutputFiles | 
|  | FileCollection getDependenciesFiles() { | 
|  | FileCollection depfiles = project.files() | 
|  |  | 
|  | // Includes all sources used in the flutter compilation. | 
|  | depfiles += project.files("${intermediateDir}/flutter_build.d") | 
|  | return depfiles | 
|  | } | 
|  |  | 
|  | void buildBundle() { | 
|  | if (!sourceDir.isDirectory()) { | 
|  | throw new GradleException("Invalid Flutter source directory: ${sourceDir}") | 
|  | } | 
|  |  | 
|  | intermediateDir.mkdirs() | 
|  |  | 
|  | // Compute the rule name for flutter assemble. To speed up builds that contain | 
|  | // multiple ABIs, the target name is used to communicate which ones are required | 
|  | // rather than the TargetPlatform. This allows multiple builds to share the same | 
|  | // cache. | 
|  | String[] ruleNames; | 
|  | if (buildMode == "debug") { | 
|  | if (fastStart) { | 
|  | ruleNames = ["faststart_android_application"] | 
|  | } else { | 
|  | ruleNames = ["debug_android_application"] | 
|  | } | 
|  | } else { | 
|  | ruleNames = targetPlatformValues.collect { "android_aot_bundle_${buildMode}_$it" } | 
|  | } | 
|  | project.exec { | 
|  | logging.captureStandardError LogLevel.ERROR | 
|  | executable flutterExecutable.absolutePath | 
|  | workingDir sourceDir | 
|  | if (localEngine != null) { | 
|  | args "--local-engine", localEngine | 
|  | args "--local-engine-src-path", localEngineSrcPath | 
|  | } | 
|  | if (verbose) { | 
|  | args "--verbose" | 
|  | } else { | 
|  | args "--quiet" | 
|  | } | 
|  | args "assemble" | 
|  | args "--depfile", "${intermediateDir}/flutter_build.d" | 
|  | args "--output", "${intermediateDir}" | 
|  | if (!fastStart || buildMode != "debug") { | 
|  | args "-dTargetFile=${targetPath}" | 
|  | } else { | 
|  | args "-dTargetFile=${Paths.get(flutterRoot.absolutePath, "examples", "splash", "lib", "main.dart")}" | 
|  | } | 
|  | args "-dTargetPlatform=android" | 
|  | args "-dBuildMode=${buildMode}" | 
|  | if (extraFrontEndOptions != null) { | 
|  | args "-dExtraFrontEndOptions=${extraFrontEndOptions}" | 
|  | } | 
|  | if (splitDebugInfo != null) { | 
|  | args "-dSplitDebugInfo=${splitDebugInfo}" | 
|  | } | 
|  | if (treeShakeIcons == true) { | 
|  | args "-dTreeShakeIcons=true" | 
|  | } | 
|  | if (dartObfuscation == true) { | 
|  | args "-dDartObfuscation=true" | 
|  | } | 
|  | if (dartDefines != null) { | 
|  | args "--DartDefines=${dartDefines}" | 
|  | } | 
|  | if (extraGenSnapshotOptions != null) { | 
|  | args "--ExtraGenSnapshotOptions=${extraGenSnapshotOptions}" | 
|  | } | 
|  | args ruleNames | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | class FlutterTask extends BaseFlutterTask { | 
|  | @OutputDirectory | 
|  | File getOutputDirectory() { | 
|  | return intermediateDir | 
|  | } | 
|  |  | 
|  | String getAssetsDirectory() { | 
|  | return "${outputDirectory}/flutter_assets" | 
|  | } | 
|  |  | 
|  | CopySpec getAssets() { | 
|  | return project.copySpec { | 
|  | from "${intermediateDir}" | 
|  | include "flutter_assets/**" // the working dir and its files | 
|  | } | 
|  | } | 
|  |  | 
|  | CopySpec getSnapshots() { | 
|  | return project.copySpec { | 
|  | from "${intermediateDir}" | 
|  |  | 
|  | if (buildMode == 'release' || buildMode == 'profile') { | 
|  | targetPlatformValues.each { | 
|  | include "${PLATFORM_ARCH_MAP[targetArch]}/app.so" | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | FileCollection readDependencies(File dependenciesFile, Boolean inputs) { | 
|  | if (dependenciesFile.exists()) { | 
|  | // Dependencies file has Makefile syntax: | 
|  | //   <target> <files>: <source> <files> <separated> <by> <non-escaped space> | 
|  | String depText = dependenciesFile.text | 
|  | // So we split list of files by non-escaped(by backslash) space, | 
|  | def matcher = depText.split(': ')[inputs ? 1 : 0] =~ /(\\ |[^\s])+/ | 
|  | // then we replace all escaped spaces with regular spaces | 
|  | def depList = matcher.collect{it[0].replaceAll("\\\\ ", " ")} | 
|  | return project.files(depList) | 
|  | } | 
|  | return project.files(); | 
|  | } | 
|  |  | 
|  | @InputFiles | 
|  | FileCollection getSourceFiles() { | 
|  | FileCollection sources = project.files() | 
|  | for (File depfile in getDependenciesFiles()) { | 
|  | sources += readDependencies(depfile, true) | 
|  | } | 
|  | return sources + project.files('pubspec.yaml') | 
|  | } | 
|  |  | 
|  | @OutputFiles | 
|  | FileCollection getOutputFiles() { | 
|  | FileCollection sources = project.files() | 
|  | for (File depfile in getDependenciesFiles()) { | 
|  | sources += readDependencies(depfile, false) | 
|  | } | 
|  | return sources | 
|  | } | 
|  |  | 
|  | @TaskAction | 
|  | void build() { | 
|  | buildBundle() | 
|  | } | 
|  | } |