| // Copyright 2013 The Flutter Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'dart:math' as math; |
| |
| import 'package:collection/collection.dart'; |
| import 'package:file/file.dart'; |
| import 'package:meta/meta.dart'; |
| import 'package:pub_semver/pub_semver.dart'; |
| |
| import 'common/core.dart'; |
| import 'common/output_utils.dart'; |
| import 'common/package_looping_command.dart'; |
| import 'common/plugin_utils.dart'; |
| import 'common/repository_package.dart'; |
| |
| /// The lowest `ext.kotlin_version` that example apps are allowed to use. |
| @visibleForTesting |
| final Version minKotlinVersion = Version(1, 7, 10); |
| |
| /// A command to enforce gradle file conventions and best practices. |
| class GradleCheckCommand extends PackageLoopingCommand { |
| /// Creates an instance of the gradle check command. |
| GradleCheckCommand(super.packagesDir, {super.gitDir}); |
| |
| static const int _minimumJavaVersion = 17; |
| |
| @override |
| final String name = 'gradle-check'; |
| |
| @override |
| List<String> get aliases => <String>['check-gradle']; |
| |
| @override |
| final String description = |
| 'Checks that gradle files follow repository conventions.'; |
| |
| @override |
| bool get hasLongOutput => false; |
| |
| @override |
| PackageLoopingType get packageLoopingType => |
| PackageLoopingType.includeAllSubpackages; |
| |
| @override |
| Future<PackageResult> runForPackage(RepositoryPackage package) async { |
| if (!package.platformDirectory(FlutterPlatform.android).existsSync()) { |
| return PackageResult.skip('No android/ directory.'); |
| } |
| |
| const exampleDirName = 'example'; |
| final bool isExample = |
| package.directory.basename == exampleDirName || |
| package.directory.parent.basename == exampleDirName; |
| if (!_validateBuildGradles(package, isExample: isExample)) { |
| return PackageResult.fail(); |
| } |
| return PackageResult.success(); |
| } |
| |
| bool _validateBuildGradles( |
| RepositoryPackage package, { |
| required bool isExample, |
| }) { |
| final Directory androidDir = package.platformDirectory( |
| FlutterPlatform.android, |
| ); |
| final File topLevelGradleFile = _getBuildGradleFile(androidDir); |
| |
| // 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. |
| var succeeded = true; |
| if (isExample) { |
| if (!_validateExampleTopLevelBuildGradle(package, topLevelGradleFile)) { |
| succeeded = false; |
| } |
| final File topLevelSettingsGradleFile = _getSettingsGradleFile( |
| androidDir, |
| ); |
| if (!_validateExampleTopLevelSettingsGradle( |
| package, |
| topLevelSettingsGradleFile, |
| )) { |
| 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 settings gradle file in the given directory. |
| File _getSettingsGradleFile(Directory dir) => |
| dir.childFile('settings.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'); |
| } |
| |
| bool _isCommented(String line) => line.trim().startsWith('//'); |
| |
| /// 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. |
| var succeeded = true; |
| if (!_validateNamespace(package, contents, isExample: false)) { |
| succeeded = false; |
| } |
| if (!_validateCompatibilityVersions(lines)) { |
| succeeded = false; |
| } |
| if (!_validateKotlinJvmCompatibility(lines)) { |
| succeeded = false; |
| } |
| if (!_validateJavaKotlinCompileOptionsAlignment(lines)) { |
| succeeded = false; |
| } |
| if (!_validateGradleDrivenLintConfig(package, lines)) { |
| succeeded = false; |
| } |
| if (!_validateCompileSdkUsage(package, lines)) { |
| succeeded = false; |
| } |
| return succeeded; |
| } |
| |
| /// Documentation url for Artifact hub implementation in flutter repo's. |
| @visibleForTesting |
| static const String artifactHubDocumentationString = |
| r'https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure'; |
| |
| /// String printed as example of valid example root build.gradle repository |
| /// configuration that enables artifact hub env variable. |
| @visibleForTesting |
| static const String exampleRootGradleArtifactHubString = |
| ''' |
| // See $artifactHubDocumentationString for more info. |
| def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' |
| if (System.getenv().containsKey(artifactRepoKey)) { |
| println "Using artifact hub" |
| maven { url System.getenv(artifactRepoKey) } |
| } |
| '''; |
| |
| /// Validates that [gradleLines] reads and uses a artifiact hub repository |
| /// when ARTIFACT_HUB_REPOSITORY is set. |
| /// |
| /// Required in root gradle file. |
| bool _validateArtifactHubUsage( |
| RepositoryPackage example, |
| List<String> gradleLines, |
| ) { |
| // Gradle variable name used to hold environment variable string. |
| const keyVariable = 'artifactRepoKey'; |
| final keyPresentRegex = RegExp( |
| '$keyVariable' |
| r"\s+=\s+'ARTIFACT_HUB_REPOSITORY'", |
| ); |
| final documentationPresentRegex = RegExp( |
| r'github\.com.*flutter.*blob.*Plugins-and-Packages-repository-structure.*gradle-structure', |
| ); |
| final keyReadRegex = RegExp( |
| r'if.*System\.getenv.*\.containsKey.*' |
| '$keyVariable', |
| ); |
| final keyUsedRegex = RegExp( |
| r'maven.*url.*System\.getenv\(' |
| '$keyVariable', |
| ); |
| |
| final bool keyPresent = gradleLines.any( |
| (String line) => keyPresentRegex.hasMatch(line), |
| ); |
| final bool documentationPresent = gradleLines.any( |
| (String line) => documentationPresentRegex.hasMatch(line), |
| ); |
| final bool keyRead = gradleLines.any( |
| (String line) => keyReadRegex.hasMatch(line), |
| ); |
| final bool keyUsed = gradleLines.any( |
| (String line) => keyUsedRegex.hasMatch(line), |
| ); |
| |
| if (!(documentationPresent && keyPresent && keyRead && keyUsed)) { |
| printError( |
| 'Failed Artifact Hub validation. Include the following in ' |
| 'example root build.gradle:\n$exampleRootGradleArtifactHubString', |
| ); |
| } |
| |
| return keyPresent && documentationPresent && keyRead && keyUsed; |
| } |
| |
| /// Validates the top-level settings.gradle for an example app (e.g., |
| /// some_package/example/android/settings.gradle). |
| bool _validateExampleTopLevelSettingsGradle( |
| RepositoryPackage package, |
| File gradleSettingsFile, |
| ) { |
| print( |
| '${indentation}Validating ' |
| '${getRelativePosixPath(gradleSettingsFile, from: package.directory)}.', |
| ); |
| final String contents = gradleSettingsFile.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. |
| var succeeded = true; |
| if (!_validateArtifactHubSettingsUsage(package, lines)) { |
| succeeded = false; |
| } |
| return succeeded; |
| } |
| |
| /// String printed as a valid example of settings.gradle repository |
| /// configuration that enables artifact hub env variable. |
| @visibleForTesting |
| static String exampleSettingsArtifactHubString = ''' |
| plugins { |
| id "dev.flutter.flutter-plugin-loader" version "1.0.0" |
| // ...other plugins |
| id "com.google.cloud.artifactregistry.gradle-plugin" version "2.2.1" |
| } |
| '''; |
| |
| /// Validates that [gradleLines] reads and uses a artifiact hub repository |
| /// when ARTIFACT_HUB_REPOSITORY is set. |
| /// |
| /// Required in root gradle file. |
| bool _validateArtifactHubSettingsUsage( |
| RepositoryPackage example, |
| List<String> gradleLines, |
| ) { |
| final documentationPresentRegex = RegExp( |
| r'github\.com.*flutter.*blob.*Plugins-and-Packages-repository-structure.*gradle-structure', |
| ); |
| final artifactRegistryPluginApplyRegex = RegExp( |
| r'id.*com\.google\.cloud\.artifactregistry\.gradle-plugin.*version.*\b\d+\.\d+\.\d+\b', |
| ); |
| |
| final bool documentationPresent = gradleLines.any( |
| (String line) => documentationPresentRegex.hasMatch(line), |
| ); |
| final bool declarativeArtifactRegistryApplied = gradleLines.any( |
| (String line) => artifactRegistryPluginApplyRegex.hasMatch(line), |
| ); |
| final bool validArtifactConfiguration = |
| documentationPresent && declarativeArtifactRegistryApplied; |
| |
| if (!validArtifactConfiguration) { |
| printError('Failed Artifact Hub validation.'); |
| if (!documentationPresent) { |
| printError( |
| 'The link to the Artifact Hub documentation is missing. Include the following in ' |
| 'example root settings.gradle:\n// See $artifactHubDocumentationString for more info.', |
| ); |
| } |
| if (!declarativeArtifactRegistryApplied) { |
| printError( |
| 'Include the following in ' |
| 'example root settings.gradle:\n$exampleSettingsArtifactHubString', |
| ); |
| } |
| } |
| return validArtifactConfiguration; |
| } |
| |
| /// 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)}.', |
| ); |
| 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. |
| var succeeded = true; |
| if (!_validateJavacLintConfig(package, lines)) { |
| succeeded = false; |
| } |
| if (!_validateKotlinVersion(package, lines)) { |
| succeeded = false; |
| } |
| if (!_validateArtifactHubUsage(package, lines)) { |
| succeeded = false; |
| } |
| return succeeded; |
| } |
| |
| /// 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. |
| var 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, |
| }) { |
| // Regex to validate that the following namespace definition |
| // is found (where the single quotes can be single or double): |
| // - namespace = 'dev.flutter.foo' |
| final nameSpaceRegex = RegExp( |
| '^\\s*namespace\\s+=\\s*[\'"](.*?)[\'"]', |
| multiLine: true, |
| ); |
| final RegExpMatch? nameSpaceRegexMatch = nameSpaceRegex.firstMatch( |
| gradleContents, |
| ); |
| |
| if (nameSpaceRegexMatch == null) { |
| const 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: nameSpaceRegexMatch.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 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 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 _validateCompatibilityVersions(List<String> gradleLines) { |
| final bool hasLanguageVersion = gradleLines.any( |
| (String line) => line.contains('languageVersion') && !_isCommented(line), |
| ); |
| final bool hasCompabilityVersions = |
| gradleLines.any( |
| (String line) => |
| line.contains('sourceCompatibility = JavaVersion.VERSION_') && |
| !_isCommented(line), |
| ) && |
| // Newer toolchains default targetCompatibility to the same value as |
| // sourceCompatibility, but older toolchains require it to be set |
| // explicitly. The exact version cutoff (and of which piece of the |
| // toolchain; likely AGP) is unknown; for context see |
| // https://github.com/flutter/flutter/issues/125482 |
| gradleLines.any( |
| (String line) => |
| line.contains('targetCompatibility = JavaVersion.VERSION_') && |
| !_isCommented(line), |
| ); |
| if (!hasLanguageVersion && !hasCompabilityVersions) { |
| const javaErrorMessage = |
| ''' |
| build.gradle(.kts) must set an explicit Java compatibility version. |
| |
| This can be done either via "sourceCompatibility"/"targetCompatibility": |
| android { |
| compileOptions { |
| sourceCompatibility = JavaVersion.VERSION_$_minimumJavaVersion |
| targetCompatibility = JavaVersion.VERSION_$_minimumJavaVersion |
| } |
| } |
| |
| or "toolchain": |
| java { |
| toolchain { |
| languageVersion = JavaLanguageVersion.of($_minimumJavaVersion) |
| } |
| } |
| |
| See: |
| https://docs.gradle.org/current/userguide/java_plugin.html#toolchain_and_compatibility |
| for more details.'''; |
| |
| printError( |
| '$indentation${javaErrorMessage.split('\n').join('\n$indentation')}', |
| ); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool _validateKotlinJvmCompatibility(List<String> gradleLines) { |
| bool isKotlinOptions(String line) => |
| line.contains('kotlinOptions') && !_isCommented(line); |
| final bool hasKotlinOptions = gradleLines.any(isKotlinOptions); |
| final bool kotlinOptionsUsesJavaVersion = gradleLines.any( |
| (String line) => |
| line.contains('jvmTarget = JavaVersion.VERSION_') && |
| !_isCommented(line), |
| ); |
| // Either does not set kotlinOptions or does and uses non-string based syntax. |
| if (hasKotlinOptions && !kotlinOptionsUsesJavaVersion) { |
| // Bad lines contains the first 4 lines including the kotlinOptions section. |
| var badLines = ''; |
| final int startIndex = gradleLines.indexOf( |
| gradleLines.firstWhere(isKotlinOptions), |
| ); |
| for ( |
| var i = startIndex; |
| i < math.min(startIndex + 4, gradleLines.length); |
| i++ |
| ) { |
| badLines += '${gradleLines[i]}\n'; |
| } |
| final kotlinErrorMessage = |
| ''' |
| If build.gradle(.kts) sets jvmTarget then it must use JavaVersion syntax. |
| Good: |
| android { |
| kotlinOptions { |
| jvmTarget = JavaVersion.VERSION_$_minimumJavaVersion.toString() |
| } |
| } |
| BAD: |
| $badLines |
| '''; |
| printError( |
| '$indentation${kotlinErrorMessage.split('\n').join('\n$indentation')}', |
| ); |
| return false; |
| } |
| // No error condition. |
| return true; |
| } |
| |
| bool _validateJavaKotlinCompileOptionsAlignment(List<String> gradleLines) { |
| final javaVersions = <String>[]; |
| // Some java versions have the format VERSION_1_8 but we dont need to handle those |
| // because they are below the minimum. |
| final javaVersionMatcher = RegExp( |
| r'JavaVersion.VERSION_(?<javaVersion>\d+)', |
| ); |
| for (final line in gradleLines) { |
| final RegExpMatch? match = javaVersionMatcher.firstMatch(line); |
| if (!_isCommented(line) && match != null) { |
| final String? foundVersion = match.namedGroup('javaVersion'); |
| if (foundVersion != null) { |
| javaVersions.add(foundVersion); |
| } |
| } |
| } |
| if (javaVersions.isNotEmpty) { |
| final int version = int.parse(javaVersions.first); |
| if (!javaVersions.every((String element) => element == '$version')) { |
| const javaVersionAlignmentError = ''' |
| If build.gradle(.kts) uses JavaVersion.* versions must be the same. |
| '''; |
| printError( |
| '$indentation${javaVersionAlignmentError.split('\n').join('\n$indentation')}', |
| ); |
| return false; |
| } |
| |
| if (version < _minimumJavaVersion) { |
| final minimumJavaVersionError = |
| ''' |
| build.gradle(.kts) uses "JavaVersion.VERSION_$version". |
| Which is below the minimum required. Use at least "JavaVersion.VERSION_$_minimumJavaVersion". |
| '''; |
| printError( |
| '$indentation${minimumJavaVersionError.split('\n').join('\n$indentation')}', |
| ); |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| /// Returns whether the given gradle content is configured to enable all |
| /// Gradle-driven lints (those checked by ./gradlew lint) and treat them as |
| /// errors. |
| bool _validateGradleDrivenLintConfig( |
| RepositoryPackage package, |
| List<String> gradleLines, |
| ) { |
| final List<String> gradleBuildContents = package |
| .platformDirectory(FlutterPlatform.android) |
| .childFile('build.gradle') |
| .readAsLinesSync(); |
| if (!gradleBuildContents.any( |
| (String line) => |
| line.contains('checkAllWarnings = true') && !_isCommented(line), |
| ) || |
| !gradleBuildContents.any( |
| (String line) => |
| line.contains('warningsAsErrors = true') && !_isCommented(line), |
| )) { |
| printError( |
| '${indentation}This package is not configured to enable all ' |
| 'Gradle-driven lint warnings and treat them as errors. ' |
| 'Please add the following to the lintOptions section of ' |
| 'android/build.gradle:', |
| ); |
| print(''' |
| checkAllWarnings = true |
| warningsAsErrors = true |
| '''); |
| return false; |
| } |
| return true; |
| } |
| |
| bool _validateCompileSdkUsage( |
| RepositoryPackage package, |
| List<String> gradleLines, |
| ) { |
| final linePattern = RegExp(r'^\s*compileSdk.*\s+='); |
| final legacySettingPattern = RegExp(r'^\s*compileSdkVersion'); |
| final String? compileSdkLine = gradleLines.firstWhereOrNull( |
| (String line) => linePattern.hasMatch(line), |
| ); |
| |
| if (compileSdkLine == null) { |
| // Equals regex not found check for method pattern. |
| final compileSpacePattern = RegExp(r'^\s*compileSdk'); |
| final String? methodAssignmentLine = gradleLines.firstWhereOrNull( |
| (String line) => compileSpacePattern.hasMatch(line), |
| ); |
| if (methodAssignmentLine == null) { |
| printError('${indentation}No compileSdk or compileSdkVersion found.'); |
| } else { |
| printError( |
| '${indentation}No "compileSdk =" found. Please use property assignment.', |
| ); |
| } |
| return false; |
| } |
| if (legacySettingPattern.hasMatch(compileSdkLine)) { |
| printError( |
| '${indentation}Please replace the deprecated ' |
| '"compileSdkVersion" setting with the newer "compileSdk"', |
| ); |
| return false; |
| } |
| if (compileSdkLine.contains('flutter.compileSdkVersion')) { |
| final Pubspec pubspec = package.parsePubspec(); |
| final VersionConstraint? flutterConstraint = |
| pubspec.environment['flutter']; |
| final Version? minFlutterVersion = |
| flutterConstraint != null && flutterConstraint is VersionRange |
| ? flutterConstraint.min |
| : null; |
| if (minFlutterVersion == null) { |
| printError( |
| '${indentation}Unable to find a Flutter SDK version ' |
| 'constraint. Use of flutter.compileSdkVersion requires a minimum ' |
| 'Flutter version of 3.27', |
| ); |
| return false; |
| } |
| if (minFlutterVersion < Version(3, 27, 0)) { |
| printError( |
| '${indentation}Use of flutter.compileSdkVersion requires a ' |
| 'minimum Flutter version of 3.27, but this package currently ' |
| 'supports $minFlutterVersion.\n' |
| "${indentation}Please update the package's minimum Flutter SDK " |
| 'version to at least 3.27.', |
| ); |
| return false; |
| } |
| } else { |
| // Extract compileSdkVersion and check if it is higher than flutter.compileSdkVersion. |
| final numericVersionPattern = RegExp(r'=\s*(\d+)'); |
| final RegExpMatch? versionMatch = numericVersionPattern.firstMatch( |
| compileSdkLine, |
| ); |
| |
| if (versionMatch != null) { |
| final int compileSdkVersion = int.parse(versionMatch.group(1)!); |
| const minCompileSdkVersion = 36; |
| |
| if (compileSdkVersion < minCompileSdkVersion) { |
| printError( |
| '${indentation}compileSdk version $compileSdkVersion is too low. ' |
| 'Minimum required version is $minCompileSdkVersion.\n' |
| "${indentation}Please update this package's compileSdkVersion to at least " |
| '$minCompileSdkVersion or use flutter.compileSdkVersion.', |
| ); |
| return false; |
| } |
| } else { |
| printError('${indentation}Unable to parse compileSdk version number.'); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /// Validates whether the given [example]'s gradle content is configured to |
| /// build its plugin target with javac lints enabled and treated as errors, |
| /// if the enclosing package is a plugin. |
| /// |
| /// This can only be called on example packages. (Plugin packages should not |
| /// be configured this way, since it would affect clients.) |
| /// |
| /// If [example]'s enclosing package is not a plugin package, this just |
| /// returns true. |
| bool _validateJavacLintConfig( |
| RepositoryPackage example, |
| List<String> gradleLines, |
| ) { |
| final RepositoryPackage enclosingPackage = example.getEnclosingPackage()!; |
| if (!pluginSupportsPlatform( |
| platformAndroid, |
| enclosingPackage, |
| requiredMode: PlatformSupport.inline, |
| )) { |
| return true; |
| } |
| final String enclosingPackageName = enclosingPackage.directory.basename; |
| |
| // The check here is intentionally somewhat loose, to allow for the |
| // possibility of variations (e.g., not using Xlint:all in some cases, or |
| // passing other arguments). |
| if (!(gradleLines.any( |
| (String line) => line.contains('project(":$enclosingPackageName")'), |
| ) && |
| gradleLines.any( |
| (String line) => |
| line.contains('options.compilerArgs') && |
| line.contains('-Xlint') && |
| line.contains('-Werror'), |
| ))) { |
| printError( |
| 'The example ' |
| '"${getRelativePosixPath(example.directory, from: enclosingPackage.directory)}" ' |
| 'is not configured to treat javac lints and warnings as errors. ' |
| 'Please add the following to its build.gradle:', |
| ); |
| print(''' |
| gradle.projectsEvaluated { |
| project(":$enclosingPackageName") { |
| tasks.withType(JavaCompile) { |
| options.compilerArgs << "-Xlint:all" << "-Werror" |
| } |
| } |
| } |
| '''); |
| return false; |
| } |
| return true; |
| } |
| |
| /// Validates whether the given [example] has its Kotlin version set to at |
| /// least a minimum value, if it is set at all. |
| bool _validateKotlinVersion( |
| RepositoryPackage example, |
| List<String> gradleLines, |
| ) { |
| final kotlinVersionRegex = RegExp(r"ext\.kotlin_version\s*=\s*'([\d.]+)'"); |
| RegExpMatch? match; |
| if (gradleLines.any((String line) { |
| match = kotlinVersionRegex.firstMatch(line); |
| return match != null; |
| })) { |
| final version = Version.parse(match!.group(1)!); |
| if (version < minKotlinVersion) { |
| printError( |
| 'build.gradle sets "ext.kotlin_version" to "$version". The ' |
| 'minimum Kotlin version that can be specified is ' |
| '$minKotlinVersion, for compatibility with modern dependencies.', |
| ); |
| return false; |
| } |
| } |
| return true; |
| } |
| } |