mirror of
https://github.com/flutter/packages.git
synced 2025-08-14 01:43:59 +08:00
[various] Adds Android namespace (#3791)
Adds a `namespace` attribute to the Android build.gradle, for compatibility with Android Gradle Plugin 8.0, and adds tooling to enforce that it's there. This is necessary for plugins now; for examples this isn't needed yet, but since it will be needed to eventually update the apps to AGP 8.0 anyway, it's easiest to just enforce it everywhere now.
This commit is contained in:
@ -36,31 +36,169 @@ class GradleCheckCommand extends PackageLoopingCommand {
|
||||
const String exampleDirName = 'example';
|
||||
final bool isExample = package.directory.basename == exampleDirName ||
|
||||
package.directory.parent.basename == exampleDirName;
|
||||
if (!_validateBuildGradle(package, isExample: isExample)) {
|
||||
if (!_validateBuildGradles(package, isExample: isExample)) {
|
||||
return PackageResult.fail();
|
||||
}
|
||||
return PackageResult.success();
|
||||
}
|
||||
|
||||
bool _validateBuildGradle(RepositoryPackage package,
|
||||
bool _validateBuildGradles(RepositoryPackage package,
|
||||
{required bool isExample}) {
|
||||
// Currently the only check is not relevant to examples; checks that apply
|
||||
// to both plugins and examples should go above here.
|
||||
if (!isExample) {
|
||||
print('${indentation}Validating android/build.gradle.');
|
||||
final String contents = package
|
||||
.platformDirectory(FlutterPlatform.android)
|
||||
.childFile('build.gradle')
|
||||
.readAsStringSync();
|
||||
final List<String> lines = contents.split('\n');
|
||||
final Directory androidDir =
|
||||
package.platformDirectory(FlutterPlatform.android);
|
||||
final File topLevelGradleFile = _getBuildGradleFile(androidDir);
|
||||
|
||||
if (!lines.any((String line) =>
|
||||
line.contains('languageVersion') &&
|
||||
!line.trim().startsWith('//')) &&
|
||||
!lines.any((String line) =>
|
||||
line.contains('sourceCompatibility') &&
|
||||
!line.trim().startsWith('//'))) {
|
||||
const String errorMessage = '''
|
||||
// This is tracked as a variable rather than a sequence of &&s so that all
|
||||
// failures are reported at once, not just the first one.
|
||||
bool succeeded = true;
|
||||
if (isExample) {
|
||||
if (!_validateExampleTopLevelBuildGradle(package, topLevelGradleFile)) {
|
||||
succeeded = false;
|
||||
}
|
||||
|
||||
final File appGradleFile =
|
||||
_getBuildGradleFile(androidDir.childDirectory('app'));
|
||||
if (!_validateExampleAppBuildGradle(package, appGradleFile)) {
|
||||
succeeded = false;
|
||||
}
|
||||
} else {
|
||||
succeeded = _validatePluginBuildGradle(package, topLevelGradleFile);
|
||||
}
|
||||
|
||||
return succeeded;
|
||||
}
|
||||
|
||||
// Returns the gradle file in the given directory.
|
||||
File _getBuildGradleFile(Directory dir) => dir.childFile('build.gradle');
|
||||
|
||||
// Returns the main/AndroidManifest.xml file for the given package.
|
||||
File _getMainAndroidManifest(RepositoryPackage package,
|
||||
{required bool isExample}) {
|
||||
final Directory androidDir =
|
||||
package.platformDirectory(FlutterPlatform.android);
|
||||
final Directory baseDir =
|
||||
isExample ? androidDir.childDirectory('app') : androidDir;
|
||||
return baseDir
|
||||
.childDirectory('src')
|
||||
.childDirectory('main')
|
||||
.childFile('AndroidManifest.xml');
|
||||
}
|
||||
|
||||
/// Validates the build.gradle file for a plugin
|
||||
/// (some_plugin/android/build.gradle).
|
||||
bool _validatePluginBuildGradle(RepositoryPackage package, File gradleFile) {
|
||||
print('${indentation}Validating '
|
||||
'${getRelativePosixPath(gradleFile, from: package.directory)}.');
|
||||
final String contents = gradleFile.readAsStringSync();
|
||||
final List<String> lines = contents.split('\n');
|
||||
|
||||
// This is tracked as a variable rather than a sequence of &&s so that all
|
||||
// failures are reported at once, not just the first one.
|
||||
bool succeeded = true;
|
||||
if (!_validateNamespace(package, contents, isExample: false)) {
|
||||
succeeded = false;
|
||||
}
|
||||
if (!_validateSourceCompatibilityVersion(lines)) {
|
||||
succeeded = false;
|
||||
}
|
||||
return succeeded;
|
||||
}
|
||||
|
||||
/// Validates the top-level build.gradle for an example app (e.g.,
|
||||
/// some_package/example/android/build.gradle).
|
||||
bool _validateExampleTopLevelBuildGradle(
|
||||
RepositoryPackage package, File gradleFile) {
|
||||
print('${indentation}Validating '
|
||||
'${getRelativePosixPath(gradleFile, from: package.directory)}.');
|
||||
// TODO(stuartmorgan): Move the -Xlint validation from lint_android_command
|
||||
// to here.
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Validates the app-level build.gradle for an example app (e.g.,
|
||||
/// some_package/example/android/app/build.gradle).
|
||||
bool _validateExampleAppBuildGradle(
|
||||
RepositoryPackage package, File gradleFile) {
|
||||
print('${indentation}Validating '
|
||||
'${getRelativePosixPath(gradleFile, from: package.directory)}.');
|
||||
final String contents = gradleFile.readAsStringSync();
|
||||
|
||||
// This is tracked as a variable rather than a sequence of &&s so that all
|
||||
// failures are reported at once, not just the first one.
|
||||
bool succeeded = true;
|
||||
if (!_validateNamespace(package, contents, isExample: true)) {
|
||||
succeeded = false;
|
||||
}
|
||||
return succeeded;
|
||||
}
|
||||
|
||||
/// Validates that [gradleContents] sets a namespace, which is required for
|
||||
/// compatibility with apps that use AGP 8+.
|
||||
bool _validateNamespace(RepositoryPackage package, String gradleContents,
|
||||
{required bool isExample}) {
|
||||
final RegExp namespaceRegex =
|
||||
RegExp('^\\s*namespace\\s+[\'"](.*?)[\'"]', multiLine: true);
|
||||
final RegExpMatch? namespaceMatch =
|
||||
namespaceRegex.firstMatch(gradleContents);
|
||||
if (namespaceMatch == null) {
|
||||
const String errorMessage = '''
|
||||
build.gradle must set a "namespace":
|
||||
|
||||
android {
|
||||
namespace 'dev.flutter.foo'
|
||||
}
|
||||
|
||||
The value must match the "package" attribute in AndroidManifest.xml, if one is
|
||||
present. For more information, see:
|
||||
https://developer.android.com/build/publish-library/prep-lib-release#choose-namespace
|
||||
''';
|
||||
|
||||
printError(
|
||||
'$indentation${errorMessage.split('\n').join('\n$indentation')}');
|
||||
return false;
|
||||
} else {
|
||||
return _validateNamespaceMatchesManifest(package,
|
||||
isExample: isExample, namespace: namespaceMatch.group(1)!);
|
||||
}
|
||||
}
|
||||
|
||||
/// Validates that the given namespace matches the manifest package of
|
||||
/// [package] (if any; a package does not need to be in the manifest in cases
|
||||
/// where compatibility with AGP <7 is no longer required).
|
||||
///
|
||||
/// Prints an error and returns false if validation fails.
|
||||
bool _validateNamespaceMatchesManifest(RepositoryPackage package,
|
||||
{required bool isExample, required String namespace}) {
|
||||
final RegExp manifestPackageRegex = RegExp(r'package\s*=\s*"(.*?)"');
|
||||
final String manifestContents =
|
||||
_getMainAndroidManifest(package, isExample: isExample)
|
||||
.readAsStringSync();
|
||||
final RegExpMatch? packageMatch =
|
||||
manifestPackageRegex.firstMatch(manifestContents);
|
||||
if (packageMatch != null && namespace != packageMatch.group(1)) {
|
||||
final String errorMessage = '''
|
||||
build.gradle "namespace" must match the "package" attribute in AndroidManifest.xml, if one is present.
|
||||
build.gradle namespace: "$namespace"
|
||||
AndroidMastifest.xml package: "${packageMatch.group(1)}"
|
||||
''';
|
||||
printError(
|
||||
'$indentation${errorMessage.split('\n').join('\n$indentation')}');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Checks for a source compatibiltiy version, so that it's explicit rather
|
||||
/// than using whatever the client's local toolchaing defaults to (which can
|
||||
/// lead to compile errors that show up for clients, but not in CI).
|
||||
bool _validateSourceCompatibilityVersion(List<String> gradleLines) {
|
||||
if (!gradleLines.any((String line) =>
|
||||
line.contains('languageVersion') &&
|
||||
!line.trim().startsWith('//')) &&
|
||||
!gradleLines.any((String line) =>
|
||||
line.contains('sourceCompatibility') &&
|
||||
!line.trim().startsWith('//'))) {
|
||||
const String errorMessage = '''
|
||||
build.gradle must set an explicit Java compatibility version.
|
||||
|
||||
This can be done either via "sourceCompatibility":
|
||||
@ -81,12 +219,10 @@ See:
|
||||
https://docs.gradle.org/current/userguide/java_plugin.html#toolchain_and_compatibility
|
||||
for more details.''';
|
||||
|
||||
printError(
|
||||
'$indentation${errorMessage.split('\n').join('\n$indentation')}');
|
||||
return false;
|
||||
}
|
||||
printError(
|
||||
'$indentation${errorMessage.split('\n').join('\n$indentation')}');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user