// Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // For each directory specified in the stdin, this script generates: // 1. The top-level build.gradle (android/build.gradle). // 2. The top level settings.gradle (android/settings.gradle). // 3. The gradle wrapper file (android/gradle/wrapper/gradle-wrapper.properties). // Then it generate the lockfiles for each Gradle project. // To regenerate these files, run `find . -type d -name 'android' | dart dev/tools/bin/generate_gradle_lockfiles.dart`. import 'dart:collection'; import 'dart:io'; import 'package:args/args.dart'; import 'package:file/file.dart'; import 'package:file/local.dart'; import 'package:path/path.dart' as path; import 'package:yaml/yaml.dart'; void main(List arguments) { const String usageMessage = "Usage: find . -type d -name 'android' | dart dev/tools/bin/generate_gradle_lockfiles.dart\n" 'If you would rather enter the files manually, just run `dart dev/tools/bin/generate_gradle_lockfiles.dart`,\n' "enter the absolute paths to the app's android directory, then press CTRL-D.\n" "If you don't wish to re-generate the settings.gradle, build.gradle, and gradle-wrapper.properties files,\n" 'add the flag `--no-gradle-generation`.\n' 'This tool automatically excludes a set of android subdirectories, defined at dev/tools/bin/config/lockfile_exclusion.yaml.\n' 'To disable this behavior, run with `--no-exclusion`.\n'; final ArgParser argParser = ArgParser() ..addFlag( 'gradle-generation', help: 'Re-generate gradle files in each processed directory.', defaultsTo: true, )..addFlag( 'exclusion', help: 'Run the script using the config file at ./configs/lockfile_exclusion.yaml to skip the specified subdirectories.', defaultsTo: true, ); ArgResults args; try { args = argParser.parse(arguments); } on FormatException catch (error) { stderr.writeln('${error.message}\n'); stderr.writeln(usageMessage); exit(1); } print(usageMessage); /// Re-generate gradle files in each processed directory. final bool gradleGeneration = (args['gradle-generation'] as bool?) ?? true; // Skip android subdirectories specified in the ./config/lockfile_exclusion.yaml file. final bool useExclusion = (args['exclusion'] as bool?) ?? true; const FileSystem fileSystem = LocalFileSystem(); final List androidDirectories = getFilesFromStdin(); final File exclusionFile = fileSystem .currentDirectory.childDirectory('dev').childDirectory('tools').childDirectory('bin') .childDirectory('config') .childFile('lockfile_exclusion.yaml'); // Load the exclusion set, or make an empty exclusion set. final Set exclusionSet; if (useExclusion) { exclusionSet = HashSet.from( (loadYaml(exclusionFile.readAsStringSync()) as YamlList) .toList() .cast() ); print('Loaded exclusion file from ${exclusionFile.path}.'); } else { exclusionSet = {}; print('Running without exclusion.'); } for (final String androidDirectoryPath in androidDirectories) { final Directory androidDirectory = fileSystem.directory(path.normalize(androidDirectoryPath)); if (!androidDirectory.existsSync()) { throw '$androidDirectory does not exist'; } if (exclusionSet.contains(androidDirectory.path)) { print('${androidDirectory.path} is included in the exclusion config file at ${exclusionFile.path} - skipping'); continue; } final File rootBuildGradle = androidDirectory.childFile('build.gradle'); if (!rootBuildGradle.existsSync()) { print('${rootBuildGradle.path} does not exist - skipping'); continue; } final File settingsGradle = androidDirectory.childFile('settings.gradle'); if (!settingsGradle.existsSync()) { print('${settingsGradle.path} does not exist - skipping'); continue; } final File wrapperGradle = androidDirectory .childDirectory('gradle') .childDirectory('wrapper') .childFile('gradle-wrapper.properties'); if (!wrapperGradle.existsSync()) { print('${wrapperGradle.path} does not exist - skipping'); continue; } if (settingsGradle.readAsStringSync().contains('include_flutter.groovy')) { print('${settingsGradle.path} add to app - skipping'); continue; } if (!androidDirectory.childDirectory('app').existsSync()) { print('${rootBuildGradle.path} is not an app - skipping'); continue; } if (!androidDirectory.parent.childFile('pubspec.yaml').existsSync()) { print('${rootBuildGradle.path} no pubspec.yaml in parent directory - skipping'); continue; } if (androidDirectory.parent.childFile('pubspec.yaml').readAsStringSync().contains('deferred-components')) { print('${rootBuildGradle.path} uses deferred components - skipping'); continue; } if (!androidDirectory.parent .childDirectory('lib') .childFile('main.dart') .existsSync()) { print('${rootBuildGradle.path} no main.dart under lib - skipping'); continue; } print('Processing ${androidDirectory.path}'); try { androidDirectory.childFile('buildscript-gradle.lockfile').deleteSync(); } on FileSystemException { // noop } if (gradleGeneration) { rootBuildGradle.writeAsStringSync(rootGradleFileContent); settingsGradle.writeAsStringSync(settingGradleFile); wrapperGradle.writeAsStringSync(wrapperGradleFileContent); } final String appDirectory = androidDirectory.parent.absolute.path; // Fetch pub dependencies. exec('flutter', ['pub', 'get'], workingDirectory: appDirectory); // Verify that the Gradlew wrapper exists. final File gradleWrapper = androidDirectory.childFile('gradlew'); // Generate Gradle wrapper if it doesn't exist. if (!gradleWrapper.existsSync()) { exec( 'flutter', ['build', 'apk', '--config-only'], workingDirectory: appDirectory, ); } // Generate lock files. exec( gradleWrapper.absolute.path, [':generateLockfiles'], workingDirectory: androidDirectory.absolute.path, ); print('Processed'); } } List getFilesFromStdin() { final List files = []; while (true) { final String? file = stdin.readLineSync(); if (file == null) { break; } files.add(file); } return files; } void exec( String cmd, List args, { String? workingDirectory, }) { final ProcessResult result = Process.runSync(cmd, args, workingDirectory: workingDirectory); if (result.exitCode != 0) { throw ProcessException( cmd, args, '${result.stdout}${result.stderr}', result.exitCode); } } const String rootGradleFileContent = r''' // Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // This file is auto generated. // To update all the build.gradle files in the Flutter repo, // See dev/tools/bin/generate_gradle_lockfiles.dart. allprojects { repositories { google() mavenCentral() } } rootProject.buildDir = '../build' subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { project.evaluationDependsOn(':app') dependencyLocking { ignoredDependencies.add('io.flutter:*') lockFile = file("${rootProject.projectDir}/project-${project.name}.lockfile") if (!project.hasProperty('local-engine-repo')) { lockAllConfigurations() } } } tasks.register("clean", Delete) { delete rootProject.buildDir } '''; const String settingGradleFile = r''' // Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // This file is auto generated. // To update all the settings.gradle files in the Flutter repo, // See dev/tools/bin/generate_gradle_lockfiles.dart. pluginManagement { def flutterSdkPath = { def properties = new Properties() file("local.properties").withInputStream { properties.load(it) } def flutterSdkPath = properties.getProperty("flutter.sdk") assert flutterSdkPath != null, "flutter.sdk not set in local.properties" return flutterSdkPath } settings.ext.flutterSdkPath = flutterSdkPath() includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") repositories { google() mavenCentral() gradlePluginPortal() } } buildscript { dependencyLocking { lockFile = file("${rootProject.projectDir}/buildscript-gradle.lockfile") lockAllConfigurations() } } plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" id "com.android.application" version "8.1.0" apply false id "org.jetbrains.kotlin.android" version "1.7.10" apply false } include ":app" '''; const String wrapperGradleFileContent = r''' distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip ''';