462 lines
15 KiB
Dart
462 lines
15 KiB
Dart
// 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 `android` directory in the repo, 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 `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:yaml/yaml.dart';
|
|
|
|
void main(List<String> arguments) {
|
|
const usageMessage =
|
|
"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';
|
|
|
|
const ignoreFilename = '.ignore-locking.md';
|
|
|
|
final 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,
|
|
)
|
|
..addOption(
|
|
'ignore-locking',
|
|
help: 'Reason to disable gradle dependency locking. A reason must be given.',
|
|
)
|
|
..addFlag('stop-ignoring', help: 'Delete the ignore lockfile if it exists');
|
|
|
|
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;
|
|
|
|
final ignoreLocking = args['ignore-locking'] != null;
|
|
final String ignoreReason = (args['ignore-locking'] as String?) ?? '';
|
|
// This is an explicit flag that insures the ignore
|
|
// lockfile isn't deleted unless specified. This should prevent
|
|
// automated scripts from deleting the file when they shouldn't.
|
|
final bool stopIgnoring = (args['stop-ignoring'] as bool?) ?? false;
|
|
|
|
if (ignoreLocking && ignoreReason.isEmpty) {
|
|
stderr.writeln('A reason must be provided for --ignore-locking.');
|
|
stderr.writeln(usageMessage);
|
|
exit(1);
|
|
}
|
|
|
|
if (ignoreLocking && stopIgnoring) {
|
|
stderr.writeln(
|
|
'Both --ignore-locking and --stop-ignoring cannot be used on the same invocation.',
|
|
);
|
|
stderr.writeln(usageMessage);
|
|
exit(1);
|
|
}
|
|
|
|
const FileSystem fileSystem = LocalFileSystem();
|
|
|
|
final Directory repoRoot = (() {
|
|
final String repoRootPath = exec('git', const <String>['rev-parse', '--show-toplevel']).trim();
|
|
final Directory repoRoot = fileSystem.directory(repoRootPath);
|
|
if (!repoRoot.existsSync()) {
|
|
throw StateError("Expected $repoRoot to exist but it didn't!");
|
|
}
|
|
return repoRoot;
|
|
})();
|
|
|
|
final Iterable<Directory> androidDirectories = discoverAndroidDirectories(repoRoot);
|
|
|
|
final File exclusionFile = repoRoot
|
|
.childDirectory('dev')
|
|
.childDirectory('tools')
|
|
.childDirectory('bin')
|
|
.childDirectory('config')
|
|
.childFile('lockfile_exclusion.yaml');
|
|
|
|
// Load the exclusion set, or make an empty exclusion set.
|
|
final Set<String> exclusionSet;
|
|
if (useExclusion) {
|
|
exclusionSet = HashSet<String>.from(
|
|
((loadYaml(exclusionFile.readAsStringSync()) ?? YamlList()) as YamlList)
|
|
.toList()
|
|
.cast<String>()
|
|
.map((String s) => '${repoRoot.path}/$s'),
|
|
);
|
|
print('Loaded exclusion file from ${exclusionFile.path}.');
|
|
} else {
|
|
exclusionSet = <String>{};
|
|
print('Running without exclusion.');
|
|
}
|
|
|
|
for (final androidDirectory in androidDirectories) {
|
|
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;
|
|
}
|
|
|
|
late File rootBuildGradle;
|
|
if (androidDirectory.childFile('build.gradle').existsSync()) {
|
|
rootBuildGradle = androidDirectory.childFile('build.gradle');
|
|
} else if (androidDirectory.childFile('build.gradle.kts').existsSync()) {
|
|
rootBuildGradle = androidDirectory.childFile('build.gradle.kts');
|
|
} else {
|
|
print('${androidDirectory.childFile('build.gradle').path}(.kts) does not exist - skipping');
|
|
continue;
|
|
}
|
|
|
|
late File settingsGradle;
|
|
if (androidDirectory.childFile('settings.gradle').existsSync()) {
|
|
settingsGradle = androidDirectory.childFile('settings.gradle');
|
|
} else if (androidDirectory.childFile('settings.gradle.kts').existsSync()) {
|
|
settingsGradle = androidDirectory.childFile('settings.gradle.kts');
|
|
} else {
|
|
print(
|
|
'${androidDirectory.childFile('settings.gradle').path}(.kts) 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}');
|
|
|
|
final File ignoreFile = androidDirectory.childFile(ignoreFilename);
|
|
if (ignoreLocking) {
|
|
print('Writing ignore file in ${ignoreFile.path}');
|
|
ignoreFile.writeAsStringSync(ignoreReason);
|
|
// When ignoring locking, we do not want to actually generate
|
|
// the lockfiles
|
|
continue;
|
|
} else if (stopIgnoring && ignoreFile.existsSync()) {
|
|
ignoreFile.deleteSync();
|
|
}
|
|
|
|
try {
|
|
androidDirectory.childFile('buildscript-gradle.lockfile').deleteSync();
|
|
} on FileSystemException {
|
|
// noop
|
|
}
|
|
|
|
if (gradleGeneration) {
|
|
// Write file content corresponding to original file language.
|
|
if (rootBuildGradle.basename.endsWith('.kts')) {
|
|
rootBuildGradle.writeAsStringSync(rootGradleKtsFileContent);
|
|
} else {
|
|
rootBuildGradle.writeAsStringSync(rootGradleFileContent);
|
|
}
|
|
|
|
if (settingsGradle.basename.endsWith('.kts')) {
|
|
settingsGradle.writeAsStringSync(settingsGradleKtsFileContent);
|
|
} else {
|
|
settingsGradle.writeAsStringSync(settingGradleFileContent);
|
|
}
|
|
|
|
wrapperGradle.writeAsStringSync(wrapperGradleFileContent);
|
|
}
|
|
|
|
final String appDirectory = androidDirectory.parent.absolute.path;
|
|
|
|
// Fetch pub dependencies.
|
|
final String flutterPath = repoRoot.childDirectory('bin').childFile('flutter').path;
|
|
exec(flutterPath, <String>['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(flutterPath, <String>['build', 'apk', '--config-only'], workingDirectory: appDirectory);
|
|
}
|
|
|
|
// Generate lock files.
|
|
exec(gradleWrapper.absolute.path, <String>[
|
|
':generateLockfiles',
|
|
], workingDirectory: androidDirectory.absolute.path);
|
|
|
|
print('Processed');
|
|
}
|
|
}
|
|
|
|
String exec(String cmd, List<String> 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);
|
|
}
|
|
return result.stdout as String;
|
|
}
|
|
|
|
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.layout.buildDirectory.value(rootProject.layout.buildDirectory.dir("../../build").get())
|
|
|
|
subprojects {
|
|
project.layout.buildDirectory.value(rootProject.layout.buildDirectory.dir(project.name).get())
|
|
}
|
|
subprojects {
|
|
project.evaluationDependsOn(':app')
|
|
dependencyLocking {
|
|
ignoredDependencies.add('io.flutter:*')
|
|
lockFile = file("${rootProject.projectDir}/project-${project.name}.lockfile")
|
|
def ignoreFile = file("${rootProject.projectDir}/.ignore-locking.md")
|
|
if (!ignoreFile.exists() && !project.hasProperty('local-engine-repo')) {
|
|
lockAllConfigurations()
|
|
}
|
|
}
|
|
}
|
|
|
|
tasks.register("clean", Delete) {
|
|
delete rootProject.layout.buildDirectory
|
|
}
|
|
''';
|
|
|
|
const String settingGradleFileContent = 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.11.1" apply false
|
|
id "org.jetbrains.kotlin.android" version "2.2.20" apply false
|
|
}
|
|
|
|
include ":app"
|
|
''';
|
|
|
|
// Consider updating this file to reflect the latest updates to app templates
|
|
// when performing batch updates (this file is modeled after
|
|
// root_app/android/build.gradle.kts).
|
|
// After modification verify formatting with ktlint.
|
|
const String rootGradleKtsFileContent = 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.
|
|
|
|
allprojects {
|
|
repositories {
|
|
google()
|
|
mavenCentral()
|
|
}
|
|
}
|
|
|
|
rootProject.layout.buildDirectory.value(
|
|
rootProject.layout.buildDirectory
|
|
.dir("../../build")
|
|
.get()
|
|
)
|
|
|
|
subprojects {
|
|
project.layout.buildDirectory.value(
|
|
rootProject.layout.buildDirectory
|
|
.dir(project.name)
|
|
.get()
|
|
)
|
|
}
|
|
subprojects {
|
|
project.evaluationDependsOn(":app")
|
|
dependencyLocking {
|
|
ignoredDependencies.add("io.flutter:*")
|
|
lockFile = file("${rootProject.projectDir}/project-${project.name}.lockfile")
|
|
var ignoreFile = file("${rootProject.projectDir}/.ignore-locking.md")
|
|
if (!ignoreFile.exists() && !project.hasProperty("local-engine-repo")) {
|
|
lockAllConfigurations()
|
|
}
|
|
}
|
|
}
|
|
|
|
tasks.register<Delete>("clean") {
|
|
delete(rootProject.layout.buildDirectory)
|
|
}
|
|
''';
|
|
|
|
// Consider updating this file to reflect the latest updates to app templates
|
|
// when performing batch updates (this file is modeled after
|
|
// root_app/android/settings.gradle.kts).
|
|
const String settingsGradleKtsFileContent = 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 {
|
|
val flutterSdkPath =
|
|
run {
|
|
val properties = java.util.Properties()
|
|
file("local.properties").inputStream().use { properties.load(it) }
|
|
val flutterSdkPath = properties.getProperty("flutter.sdk")
|
|
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
|
|
flutterSdkPath
|
|
}
|
|
|
|
includeBuild("$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.11.1" apply false
|
|
id("org.jetbrains.kotlin.android") version "2.2.20" 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.14-all.zip
|
|
''';
|
|
|
|
Iterable<Directory> discoverAndroidDirectories(Directory repoRoot) {
|
|
return repoRoot
|
|
.listSync()
|
|
.whereType<Directory>()
|
|
// Exclude the top-level "engine/" directory, which is not covered by the the tool.
|
|
.where((Directory directory) => directory.basename != 'engine')
|
|
// ... and then recurse into every directory (other than the excluded directory).
|
|
.expand((Directory directory) => directory.listSync(recursive: true))
|
|
.whereType<Directory>()
|
|
// These directories are build artifacts which are not part of source control.
|
|
.where(
|
|
(Directory directory) =>
|
|
!directory.path.contains('/build/') && !directory.path.contains('.symlinks'),
|
|
)
|
|
// ... where the directory ultimately is named "android".
|
|
.where((FileSystemEntity entity) => entity.basename == 'android');
|
|
}
|