diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart index 9825ffdcecc..b686e31b3e9 100644 --- a/packages/flutter_tools/lib/src/android/gradle.dart +++ b/packages/flutter_tools/lib/src/android/gradle.dart @@ -3,9 +3,6 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:convert'; - -import 'package:meta/meta.dart'; import '../android/android_sdk.dart'; import '../artifacts.dart'; @@ -17,7 +14,9 @@ import '../base/platform.dart'; import '../base/process.dart'; import '../base/utils.dart'; import '../build_info.dart'; +import '../bundle.dart' as bundle; import '../cache.dart'; +import '../flutter_manifest.dart'; import '../globals.dart'; import 'android_sdk.dart'; import 'android_studio.dart'; @@ -95,7 +94,7 @@ Future _gradleProject() async { // of calculating the app properties using Gradle. This may take minutes. Future _readGradleProject() async { final String gradle = await _ensureGradle(); - updateLocalProperties(); + await updateLocalProperties(); try { final Status status = logger.startProgress('Resolving dependencies...', expectSlowOperation: true); final RunResult runResult = await runCheckedAsync( @@ -198,10 +197,13 @@ distributionUrl=https\\://services.gradle.org/distributions/gradle-$gradleVersio } /// Create android/local.properties if needed, and update Flutter settings. -void updateLocalProperties({String projectPath, BuildInfo buildInfo}) { +Future updateLocalProperties({String projectPath, BuildInfo buildInfo}) async { final File localProperties = (projectPath == null) ? fs.file(fs.path.join('android', 'local.properties')) : fs.file(fs.path.join(projectPath, 'android', 'local.properties')); + final String flutterManifest = (projectPath == null) + ? fs.path.join(bundle.defaultManifestPath) + : fs.path.join(projectPath, bundle.defaultManifestPath); bool changed = false; SettingsFile settings; @@ -225,85 +227,39 @@ void updateLocalProperties({String projectPath, BuildInfo buildInfo}) { changed = true; } + FlutterManifest manifest; + try { + manifest = await FlutterManifest.createFromPath(flutterManifest); + } catch (error) { + throwToolExit('Failed to load pubspec.yaml: $error'); + } + + final String buildName = buildInfo?.buildName ?? manifest.buildName; + if (buildName != null) { + settings.values['flutter.versionName'] = buildName; + changed = true; + } + + final int buildNumber = buildInfo?.buildNumber ?? manifest.buildNumber; + if (buildNumber != null) { + settings.values['flutter.versionCode'] = '$buildNumber'; + changed = true; + } + if (changed) settings.writeContents(localProperties); } -Future findAndReplaceVersionProperties({@required BuildInfo buildInfo}) { - assert(buildInfo != null, 'buildInfo can\'t be null'); - final Completer completer = new Completer(); - - // early return, if nothing has to be changed - if (buildInfo.buildNumber == null && buildInfo.buildName == null) { - completer.complete(); - return completer.future; - } - - final File appGradle = fs.file(fs.path.join('android', 'app', 'build.gradle')); - final File appGradleTmp = fs.file(fs.path.join('android', 'app', 'build.gradle.tmp')); - appGradleTmp.createSync(); - - if (appGradle.existsSync() && appGradleTmp.existsSync()) { - final Stream> inputStream = appGradle.openRead(); - final IOSink sink = appGradleTmp.openWrite(); - - inputStream.transform(utf8.decoder) - .transform(const LineSplitter()) - .map((String line) { - - // find and replace build number - if (buildInfo.buildNumber != null) { - if (line.contains(new RegExp(r'^[ |\t]*(versionCode)[ =\t]*\d*'))) { - return line.splitMapJoin(new RegExp(r'(versionCode)[ =\t]*\d*'), onMatch: (Match m) { - return 'versionCode ${buildInfo.buildNumber}'; - }); - } - } - - // find and replace build name - if (buildInfo.buildName != null) { - if (line.contains(new RegExp(r'^[ |\t]*(versionName)[ =\t]*\"[0-9.]*"'))) { - return line.splitMapJoin(new RegExp(r'(versionName)[ =\t]*\"[0-9.]*"'), onMatch: (Match m) { - return 'versionName "${buildInfo.buildName}"'; - }); - } - } - return line; - }) - .listen((String line) { - sink.writeln(line); - }, - onDone: () { - sink.close(); - try { - final File gradleOld = appGradle.renameSync(fs.path.join('android', 'app', 'build.gradle.old')); - appGradleTmp.renameSync(fs.path.join('android', 'app', 'build.gradle')); - gradleOld.deleteSync(); - completer.complete(); - } catch (error) { - printError('Failed to change version properties. $error'); - completer.completeError('Failed to change version properties. $error'); - } - }, - onError: (dynamic error, StackTrace stack) { - printError('Failed to change version properties. ${error.toString()}'); - sink.close(); - appGradleTmp.deleteSync(); - completer.completeError('Failed to change version properties. ${error.toString()}', stack); - }, - ); - } - - return completer.future; -} - Future buildGradleProject(BuildInfo buildInfo, String target) async { - // Update the local.properties file with the build mode. + // Update the local.properties file with the build mode, version name and code. // FlutterPlugin v1 reads local.properties to determine build mode. Plugin v2 // uses the standard Android way to determine what to build, but we still // update local.properties, in case we want to use it in the future. - updateLocalProperties(buildInfo: buildInfo); - await findAndReplaceVersionProperties(buildInfo: buildInfo); + // Version name and number are provided by the pubspec.yaml file + // and can be overwritten with flutter build command. + // The default Gradle script reads the version name and number + // from the local.properties file. + await updateLocalProperties(buildInfo: buildInfo); final String gradle = await _ensureGradle(); diff --git a/packages/flutter_tools/lib/src/commands/create.dart b/packages/flutter_tools/lib/src/commands/create.dart index 4a7eb6a8076..e8c49ff1a80 100644 --- a/packages/flutter_tools/lib/src/commands/create.dart +++ b/packages/flutter_tools/lib/src/commands/create.dart @@ -289,11 +289,11 @@ To edit platform code in an IDE see https://flutter.io/platform-plugins/#edit-co if (argResults['pub']) { await pubGet(context: PubContext.create, directory: appPath, offline: argResults['offline']); - new FlutterProject(fs.directory(appPath)).ensureReadyForPlatformSpecificTooling(); + await new FlutterProject(fs.directory(appPath)).ensureReadyForPlatformSpecificTooling(); } if (android_sdk.androidSdk != null) - gradle.updateLocalProperties(projectPath: appPath); + await gradle.updateLocalProperties(projectPath: appPath); return generatedCount; } diff --git a/packages/flutter_tools/lib/src/commands/packages.dart b/packages/flutter_tools/lib/src/commands/packages.dart index 7d61ed21dcf..471e0a6a080 100644 --- a/packages/flutter_tools/lib/src/commands/packages.dart +++ b/packages/flutter_tools/lib/src/commands/packages.dart @@ -82,13 +82,13 @@ class PackagesGetCommand extends FlutterCommand { await _runPubGet(target); final FlutterProject rootProject = new FlutterProject(fs.directory(target)); - rootProject.ensureReadyForPlatformSpecificTooling(); + await rootProject.ensureReadyForPlatformSpecificTooling(); // Get/upgrade packages in example app as well if (rootProject.hasExampleApp) { final FlutterProject exampleProject = rootProject.example; await _runPubGet(exampleProject.directory.path); - exampleProject.ensureReadyForPlatformSpecificTooling(); + await exampleProject.ensureReadyForPlatformSpecificTooling(); } } } diff --git a/packages/flutter_tools/lib/src/flutter_manifest.dart b/packages/flutter_tools/lib/src/flutter_manifest.dart index 0c26c477971..c1518393417 100644 --- a/packages/flutter_tools/lib/src/flutter_manifest.dart +++ b/packages/flutter_tools/lib/src/flutter_manifest.dart @@ -13,6 +13,8 @@ import 'base/file_system.dart'; import 'cache.dart'; import 'globals.dart'; +final RegExp _versionPattern = new RegExp(r'^(\d+)(\.(\d+)(\.(\d+))?)?(\+(\d+))?$'); + /// A wrapper around the `flutter` section in the `pubspec.yaml` file. class FlutterManifest { FlutterManifest._(); @@ -51,6 +53,36 @@ class FlutterManifest { String get appName => _descriptor['name'] ?? ''; + /// The version String from the `pubspec.yaml` file. + /// Can be null if it isn't set or has a wrong format. + String get appVersion { + final String version = _descriptor['version']?.toString(); + if (version != null && _versionPattern.hasMatch(version)) + return version; + else + return null; + } + + /// The build version name from the `pubspec.yaml` file. + /// Can be null if version isn't set or has a wrong format. + String get buildName { + if (appVersion != null && appVersion.contains('+')) + return appVersion.split('+')?.elementAt(0); + else + return appVersion; + } + + /// The build version number from the `pubspec.yaml` file. + /// Can be null if version isn't set or has a wrong format. + int get buildNumber { + if (appVersion != null && appVersion.contains('+')) { + final String value = appVersion.split('+')?.elementAt(1); + return value == null ? null : int.tryParse(value); + } else { + return null; + } + } + bool get usesMaterialDesign { return _flutterDescriptor['uses-material-design'] ?? false; } diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart index fa2d3e59765..c4c14536bc8 100644 --- a/packages/flutter_tools/lib/src/ios/mac.dart +++ b/packages/flutter_tools/lib/src/ios/mac.dart @@ -242,7 +242,7 @@ Future buildXcodeProject({ final Directory appDirectory = fs.directory(app.appDirectory); await _addServicesToBundle(appDirectory); - updateGeneratedXcodeProperties( + await updateGeneratedXcodeProperties( projectPath: fs.currentDirectory.path, buildInfo: buildInfo, targetOverride: targetOverride, @@ -272,53 +272,6 @@ Future buildXcodeProject({ await fingerprinter.writeFingerprint(); } - // If buildNumber is not specified, keep the project untouched. - if (buildInfo.buildNumber != null) { - final Status buildNumberStatus = - logger.startProgress('Setting CFBundleVersion...', expectSlowOperation: true); - try { - final RunResult buildNumberResult = await runAsync( - [ - '/usr/bin/env', - 'xcrun', - 'agvtool', - 'new-version', - '-all', - buildInfo.buildNumber.toString(), - ], - workingDirectory: app.appDirectory, - ); - if (buildNumberResult.exitCode != 0) { - throwToolExit('Xcode failed to set new version\n${buildNumberResult.stderr}'); - } - } finally { - buildNumberStatus.stop(); - } - } - - // If buildName is not specified, keep the project untouched. - if (buildInfo.buildName != null) { - final Status buildNameStatus = - logger.startProgress('Setting CFBundleShortVersionString...', expectSlowOperation: true); - try { - final RunResult buildNameResult = await runAsync( - [ - '/usr/bin/env', - 'xcrun', - 'agvtool', - 'new-marketing-version', - buildInfo.buildName, - ], - workingDirectory: app.appDirectory, - ); - if (buildNameResult.exitCode != 0) { - throwToolExit('Xcode failed to set new marketing version\n${buildNameResult.stderr}'); - } - } finally { - buildNameStatus.stop(); - } - } - final List buildCommands = [ '/usr/bin/env', 'xcrun', diff --git a/packages/flutter_tools/lib/src/ios/xcodeproj.dart b/packages/flutter_tools/lib/src/ios/xcodeproj.dart index 7d00695159b..38ba2468321 100644 --- a/packages/flutter_tools/lib/src/ios/xcodeproj.dart +++ b/packages/flutter_tools/lib/src/ios/xcodeproj.dart @@ -2,9 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:async'; + import 'package:meta/meta.dart'; import '../artifacts.dart'; +import '../base/common.dart'; import '../base/context.dart'; import '../base/file_system.dart'; import '../base/io.dart'; @@ -15,6 +18,7 @@ import '../base/utils.dart'; import '../build_info.dart'; import '../bundle.dart' as bundle; import '../cache.dart'; +import '../flutter_manifest.dart'; import '../globals.dart'; final RegExp _settingExpr = new RegExp(r'(\w+)\s*=\s*(.*)$'); @@ -30,11 +34,11 @@ String _generatedXcodePropertiesPath(String projectPath) { /// Writes default Xcode properties files in the Flutter project at [projectPath], /// if project is an iOS project and such files do not already exist. -void generateXcodeProperties(String projectPath) { +Future generateXcodeProperties(String projectPath) async { if (fs.isDirectorySync(fs.path.join(projectPath, 'ios'))) { if (fs.file(_generatedXcodePropertiesPath(projectPath)).existsSync()) return; - updateGeneratedXcodeProperties( + await updateGeneratedXcodeProperties( projectPath: projectPath, buildInfo: BuildInfo.debug, targetOverride: bundle.defaultMainPath, @@ -47,12 +51,12 @@ void generateXcodeProperties(String projectPath) { /// /// targetOverride: Optional parameter, if null or unspecified the default value /// from xcode_backend.sh is used 'lib/main.dart'. -void updateGeneratedXcodeProperties({ +Future updateGeneratedXcodeProperties({ @required String projectPath, @required BuildInfo buildInfo, String targetOverride, @required bool previewDart2, -}) { +}) async { final StringBuffer localsBuffer = new StringBuffer(); localsBuffer.writeln('// This is a generated file; do not edit or check into version control.'); @@ -77,6 +81,24 @@ void updateGeneratedXcodeProperties({ localsBuffer.writeln('FLUTTER_FRAMEWORK_DIR=${flutterFrameworkDir(buildInfo.mode)}'); + final String flutterManifest = fs.path.join(projectPath, bundle.defaultManifestPath); + FlutterManifest manifest; + try { + manifest = await FlutterManifest.createFromPath(flutterManifest); + } catch (error) { + throwToolExit('Failed to load pubspec.yaml: $error'); + } + + final String buildName = buildInfo?.buildName ?? manifest.buildName; + if (buildName != null) { + localsBuffer.writeln('FLUTTER_BUILD_NAME=$buildName'); + } + + final int buildNumber = buildInfo?.buildNumber ?? manifest.buildNumber; + if (buildNumber != null) { + localsBuffer.writeln('FLUTTER_BUILD_NUMBER=$buildNumber'); + } + if (artifacts is LocalEngineArtifacts) { final LocalEngineArtifacts localEngineArtifacts = artifacts; localsBuffer.writeln('LOCAL_ENGINE=${localEngineArtifacts.engineOutPath}'); diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart index 76056d7ab23..f1d2b94bf57 100644 --- a/packages/flutter_tools/lib/src/project.dart +++ b/packages/flutter_tools/lib/src/project.dart @@ -55,12 +55,12 @@ class FlutterProject { /// Generates project files necessary to make Gradle builds work on Android /// and CocoaPods+Xcode work on iOS, for app projects only - void ensureReadyForPlatformSpecificTooling() { + Future ensureReadyForPlatformSpecificTooling() async { if (!directory.existsSync() || hasExampleApp) { return; } injectPlugins(directory: directory.path); - generateXcodeProperties(directory.path); + await generateXcodeProperties(directory.path); } } diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart index e707d7cf035..2699a77d9e2 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart @@ -329,7 +329,7 @@ abstract class FlutterCommand extends Command { if (shouldRunPub) { await pubGet(context: PubContext.getVerifyContext(name)); - new FlutterProject(fs.currentDirectory).ensureReadyForPlatformSpecificTooling(); + await new FlutterProject(fs.currentDirectory).ensureReadyForPlatformSpecificTooling(); } setupApplicationPackages(); diff --git a/packages/flutter_tools/templates/create/android-java.tmpl/app/build.gradle.tmpl b/packages/flutter_tools/templates/create/android-java.tmpl/app/build.gradle.tmpl index 868a8e2df6f..dffea9a3836 100644 --- a/packages/flutter_tools/templates/create/android-java.tmpl/app/build.gradle.tmpl +++ b/packages/flutter_tools/templates/create/android-java.tmpl/app/build.gradle.tmpl @@ -11,6 +11,16 @@ if (flutterRoot == null) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") } +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + throw new GradleException("versionCode not found. Define flutter.versionCode in the local.properties file.") +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + throw new GradleException("versionName not found. Define flutter.versionName in the local.properties file.") +} + apply plugin: 'com.android.application' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" @@ -26,8 +36,8 @@ android { applicationId "{{androidIdentifier}}" minSdkVersion 16 targetSdkVersion 27 - versionCode 1 - versionName "1.0" + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } diff --git a/packages/flutter_tools/templates/create/android-kotlin.tmpl/app/build.gradle.tmpl b/packages/flutter_tools/templates/create/android-kotlin.tmpl/app/build.gradle.tmpl index ef35e7e048d..db8cdb6f1c9 100644 --- a/packages/flutter_tools/templates/create/android-kotlin.tmpl/app/build.gradle.tmpl +++ b/packages/flutter_tools/templates/create/android-kotlin.tmpl/app/build.gradle.tmpl @@ -11,6 +11,16 @@ if (flutterRoot == null) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") } +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + throw new GradleException("versionCode not found. Define flutter.versionCode in the local.properties file.") +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + throw new GradleException("versionName not found. Define flutter.versionName in the local.properties file.") +} + apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" @@ -31,8 +41,8 @@ android { applicationId "{{androidIdentifier}}" minSdkVersion 16 targetSdkVersion 27 - versionCode 1 - versionName "1.0" + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } diff --git a/packages/flutter_tools/templates/create/ios-objc.tmpl/Runner.xcodeproj/project.pbxproj.tmpl b/packages/flutter_tools/templates/create/ios-objc.tmpl/Runner.xcodeproj/project.pbxproj.tmpl index 30edc239f3a..5cd8e04fc13 100644 --- a/packages/flutter_tools/templates/create/ios-objc.tmpl/Runner.xcodeproj/project.pbxproj.tmpl +++ b/packages/flutter_tools/templates/create/ios-objc.tmpl/Runner.xcodeproj/project.pbxproj.tmpl @@ -370,7 +370,7 @@ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -393,7 +393,7 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", diff --git a/packages/flutter_tools/templates/create/ios-swift.tmpl/Runner.xcodeproj/project.pbxproj.tmpl b/packages/flutter_tools/templates/create/ios-swift.tmpl/Runner.xcodeproj/project.pbxproj.tmpl index 2363bd595d9..1187089ad15 100644 --- a/packages/flutter_tools/templates/create/ios-swift.tmpl/Runner.xcodeproj/project.pbxproj.tmpl +++ b/packages/flutter_tools/templates/create/ios-swift.tmpl/Runner.xcodeproj/project.pbxproj.tmpl @@ -368,7 +368,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -396,7 +396,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", diff --git a/packages/flutter_tools/templates/create/ios.tmpl/Runner/Info.plist.tmpl b/packages/flutter_tools/templates/create/ios.tmpl/Runner/Info.plist.tmpl index 58723263289..3a4012f89ab 100644 --- a/packages/flutter_tools/templates/create/ios.tmpl/Runner/Info.plist.tmpl +++ b/packages/flutter_tools/templates/create/ios.tmpl/Runner/Info.plist.tmpl @@ -15,11 +15,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.0 + $(FLUTTER_BUILD_NAME) CFBundleSignature ???? CFBundleVersion - 1 + $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS UILaunchStoryboardName diff --git a/packages/flutter_tools/templates/create/pubspec.yaml.tmpl b/packages/flutter_tools/templates/create/pubspec.yaml.tmpl index d469b19f2a8..807cbfad59c 100644 --- a/packages/flutter_tools/templates/create/pubspec.yaml.tmpl +++ b/packages/flutter_tools/templates/create/pubspec.yaml.tmpl @@ -1,6 +1,14 @@ name: {{projectName}} description: {{description}} +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# Read more about versioning at semver.org. +version: 1.0.0+1 + dependencies: flutter: sdk: flutter diff --git a/packages/flutter_tools/test/android/gradle_test.dart b/packages/flutter_tools/test/android/gradle_test.dart index 5da37d59bbe..c03af9632c2 100644 --- a/packages/flutter_tools/test/android/gradle_test.dart +++ b/packages/flutter_tools/test/android/gradle_test.dart @@ -2,16 +2,20 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:async'; + import 'package:flutter_tools/src/android/gradle.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/build_info.dart'; +import 'package:flutter_tools/src/cache.dart'; import 'package:test/test.dart'; import '../src/common.dart'; +import '../src/context.dart'; void main() { group('gradle build', () { - test('do not crash if there is no Android SDK', () { + test('do not crash if there is no Android SDK', () async { Exception shouldBeToolExit; try { // We'd like to always set androidSdk to null and test updateLocalProperties. But that's @@ -21,7 +25,7 @@ void main() { // This test is written to fail if our bots get Android SDKs in the future: shouldBeToolExit // will be null and our expectation would fail. That would remind us to make these tests // hermetic before adding Android SDKs to the bots. - updateLocalProperties(); + await updateLocalProperties(); } on Exception catch (e) { shouldBeToolExit = e; } @@ -126,4 +130,181 @@ someOtherProperty: someOtherValue expect(project.assembleTaskFor(const BuildInfo(BuildMode.release, 'unknown')), isNull); }); }); + + group('Gradle local.properties', () { + Directory temp; + + setUp(() { + Cache.disableLocking(); + temp = fs.systemTempDirectory.createTempSync('flutter_tools'); + }); + + tearDown(() { + temp.deleteSync(recursive: true); + }); + + Future createMinimalProject(String manifest) async { + final Directory directory = temp.childDirectory('android_project'); + final File manifestFile = directory.childFile('pubspec.yaml'); + manifestFile.createSync(recursive: true); + manifestFile.writeAsStringSync(manifest); + + return directory.path; + } + + String propertyFor(String key, File file) { + return file + .readAsLinesSync() + .where((String line) => line.startsWith('$key=')) + .map((String line) => line.split('=')[1]) + .first; + } + + Future checkBuildVersion({ + String manifest, + BuildInfo buildInfo, + String expectedBuildName, + String expectedBuildNumber, + }) async { + final String projectPath = await createMinimalProject(manifest); + + try { + await updateLocalProperties(projectPath: projectPath, buildInfo: buildInfo); + + final String propertiesPath = fs.path.join(projectPath, 'android', 'local.properties'); + final File localPropertiesFile = fs.file(propertiesPath); + + expect(propertyFor('flutter.versionName', localPropertiesFile), expectedBuildName); + expect(propertyFor('flutter.versionCode', localPropertiesFile), expectedBuildNumber); + } on Exception { + // Android SDK not found, skip test + } + } + + testUsingContext('extract build name and number from pubspec.yaml', () async { + const String manifest = ''' +name: test +version: 1.0.0+1 +dependencies: + flutter: + sdk: flutter +flutter: +'''; + + const BuildInfo buildInfo = const BuildInfo(BuildMode.release, null); + await checkBuildVersion( + manifest: manifest, + buildInfo: buildInfo, + expectedBuildName: '1.0.0', + expectedBuildNumber: '1', + ); + }); + + testUsingContext('extract build name from pubspec.yaml', () async { + const String manifest = ''' +name: test +version: 1.0.0 +dependencies: + flutter: + sdk: flutter +flutter: +'''; + const BuildInfo buildInfo = const BuildInfo(BuildMode.release, null); + await checkBuildVersion( + manifest: manifest, + buildInfo: buildInfo, + expectedBuildName: '1.0.0', + expectedBuildNumber: null, + ); + }); + + testUsingContext('allow build info to override build name', () async { + const String manifest = ''' +name: test +version: 1.0.0+1 +dependencies: + flutter: + sdk: flutter +flutter: +'''; + const BuildInfo buildInfo = const BuildInfo(BuildMode.release, null, buildName: '1.0.2'); + await checkBuildVersion( + manifest: manifest, + buildInfo: buildInfo, + expectedBuildName: '1.0.2', + expectedBuildNumber: '1', + ); + }); + + testUsingContext('allow build info to override build number', () async { + const String manifest = ''' +name: test +version: 1.0.0+1 +dependencies: + flutter: + sdk: flutter +flutter: +'''; + const BuildInfo buildInfo = const BuildInfo(BuildMode.release, null, buildNumber: 3); + await checkBuildVersion( + manifest: manifest, + buildInfo: buildInfo, + expectedBuildName: '1.0.0', + expectedBuildNumber: '3', + ); + }); + + testUsingContext('allow build info to override build name and number', () async { + const String manifest = ''' +name: test +version: 1.0.0+1 +dependencies: + flutter: + sdk: flutter +flutter: +'''; + const BuildInfo buildInfo = const BuildInfo(BuildMode.release, null, buildName: '1.0.2', buildNumber: 3); + await checkBuildVersion( + manifest: manifest, + buildInfo: buildInfo, + expectedBuildName: '1.0.2', + expectedBuildNumber: '3', + ); + }); + + testUsingContext('allow build info to override build name and set number', () async { + const String manifest = ''' +name: test +version: 1.0.0 +dependencies: + flutter: + sdk: flutter +flutter: +'''; + const BuildInfo buildInfo = const BuildInfo(BuildMode.release, null, buildName: '1.0.2', buildNumber: 3); + await checkBuildVersion( + manifest: manifest, + buildInfo: buildInfo, + expectedBuildName: '1.0.2', + expectedBuildNumber: '3', + ); + }); + + testUsingContext('allow build info to set build name and number', () async { + const String manifest = ''' +name: test +dependencies: + flutter: + sdk: flutter +flutter: +'''; + const BuildInfo buildInfo = const BuildInfo(BuildMode.release, null, buildName: '1.0.2', buildNumber: 3); + await checkBuildVersion( + manifest: manifest, + buildInfo: buildInfo, + expectedBuildName: '1.0.2', + expectedBuildNumber: '3', + ); + }); + }); } diff --git a/packages/flutter_tools/test/flutter_manifest_test.dart b/packages/flutter_tools/test/flutter_manifest_test.dart index 75be31cbde2..91c42cc6b6b 100644 --- a/packages/flutter_tools/test/flutter_manifest_test.dart +++ b/packages/flutter_tools/test/flutter_manifest_test.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:async'; + import 'package:file/file.dart'; import 'package:file/memory.dart'; import 'package:flutter_tools/src/base/file_system.dart'; @@ -359,6 +361,118 @@ flutter: final FlutterManifest flutterManifest = await FlutterManifest.createFromString(manifest); expect(flutterManifest.isEmpty, false); }); + + Future checkManifestVersion({ + String manifest, + String expectedAppVersion, + String expectedBuildName, + int expectedBuildNumber, + }) async { + final FlutterManifest flutterManifest = await FlutterManifest.createFromString(manifest); + expect(flutterManifest.appVersion, expectedAppVersion); + expect(flutterManifest.buildName, expectedBuildName); + expect(flutterManifest.buildNumber, expectedBuildNumber); + } + + test('parses major.minor.patch+build version clause', () async { + const String manifest = ''' +name: test +version: 1.0.0+2 +dependencies: + flutter: + sdk: flutter +flutter: +'''; + await checkManifestVersion( + manifest: manifest, + expectedAppVersion: '1.0.0+2', + expectedBuildName: '1.0.0', + expectedBuildNumber: 2, + ); + }); + + test('parses major.minor+build version clause', () async { + const String manifest = ''' +name: test +version: 1.0+2 +dependencies: + flutter: + sdk: flutter +flutter: +'''; + await checkManifestVersion( + manifest: manifest, + expectedAppVersion: '1.0+2', + expectedBuildName: '1.0', + expectedBuildNumber: 2, + ); + }); + + test('parses major+build version clause', () async { + const String manifest = ''' +name: test +version: 1+2 +dependencies: + flutter: + sdk: flutter +flutter: +'''; + await checkManifestVersion( + manifest: manifest, + expectedAppVersion: '1+2', + expectedBuildName: '1', + expectedBuildNumber: 2, + ); + }); + + test('parses major version clause', () async { + const String manifest = ''' +name: test +version: 1 +dependencies: + flutter: + sdk: flutter +flutter: +'''; + await checkManifestVersion( + manifest: manifest, + expectedAppVersion: '1', + expectedBuildName: '1', + expectedBuildNumber: null, + ); + }); + + test('parses empty version clause', () async { + const String manifest = ''' +name: test +version: +dependencies: + flutter: + sdk: flutter +flutter: +'''; + await checkManifestVersion( + manifest: manifest, + expectedAppVersion: null, + expectedBuildName: null, + expectedBuildNumber: null, + ); + }); + test('parses no version clause', () async { + const String manifest = ''' +name: test +dependencies: + flutter: + sdk: flutter +flutter: +'''; + await checkManifestVersion( + manifest: manifest, + expectedAppVersion: null, + expectedBuildName: null, + expectedBuildNumber: null, + ); + }); }); group('FlutterManifest with MemoryFileSystem', () { @@ -371,8 +485,7 @@ dependencies: flutter: '''; - final FlutterManifest flutterManifest = await FlutterManifest - .createFromString(manifest); + final FlutterManifest flutterManifest = await FlutterManifest.createFromString(manifest); expect(flutterManifest.isEmpty, false); } diff --git a/packages/flutter_tools/test/ios/xcodeproj_test.dart b/packages/flutter_tools/test/ios/xcodeproj_test.dart index dd75f93988c..95f74ba0f27 100644 --- a/packages/flutter_tools/test/ios/xcodeproj_test.dart +++ b/packages/flutter_tools/test/ios/xcodeproj_test.dart @@ -2,11 +2,15 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:async'; + import 'package:file/memory.dart'; import 'package:flutter_tools/src/artifacts.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/build_info.dart'; +import 'package:flutter_tools/src/bundle.dart' as bundle; +import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/ios/xcodeproj.dart'; import 'package:mockito/mockito.dart'; import 'package:platform/platform.dart'; @@ -277,14 +281,14 @@ Information about project "Runner": }); } - testUsingOsxContext('sets ARCHS=armv7 when armv7 local engine is set', () { + testUsingOsxContext('sets ARCHS=armv7 when armv7 local engine is set', () async { when(mockArtifacts.getArtifactPath(Artifact.flutterFramework, TargetPlatform.ios, any)).thenReturn('engine'); when(mockArtifacts.engineOutPath).thenReturn(fs.path.join('out', 'ios_profile_arm')); const BuildInfo buildInfo = const BuildInfo(BuildMode.debug, null, previewDart2: true, targetPlatform: TargetPlatform.ios, ); - updateGeneratedXcodeProperties( + await updateGeneratedXcodeProperties( projectPath: 'path/to/project', buildInfo: buildInfo, previewDart2: true, @@ -297,14 +301,14 @@ Information about project "Runner": expect(contents.contains('ARCHS=armv7'), isTrue); }); - testUsingOsxContext('sets ARCHS=armv7 when armv7 local engine is set', () { + testUsingOsxContext('sets ARCHS=armv7 when armv7 local engine is set', () async { when(mockArtifacts.getArtifactPath(Artifact.flutterFramework, TargetPlatform.ios, any)).thenReturn('engine'); when(mockArtifacts.engineOutPath).thenReturn(fs.path.join('out', 'ios_profile')); const BuildInfo buildInfo = const BuildInfo(BuildMode.debug, null, previewDart2: true, targetPlatform: TargetPlatform.ios, ); - updateGeneratedXcodeProperties( + await updateGeneratedXcodeProperties( projectPath: 'path/to/project', buildInfo: buildInfo, previewDart2: true, @@ -317,6 +321,185 @@ Information about project "Runner": expect(contents.contains('ARCHS=arm64'), isTrue); }); }); + + group('Xcode Generated.xcconfig', () { + Directory temp; + + setUp(() { + Cache.disableLocking(); + temp = fs.systemTempDirectory.createTempSync('flutter_tools'); + }); + + tearDown(() { + temp.deleteSync(recursive: true); + }); + + Future createMinimalProject(String manifest) async { + final Directory directory = temp.childDirectory('ios_project'); + final File manifestFile = directory.childFile('pubspec.yaml'); + manifestFile.createSync(recursive: true); + manifestFile.writeAsStringSync(manifest); + + return directory.path; + } + + String propertyFor(String key, File file) { + final List properties = file + .readAsLinesSync() + .where((String line) => line.startsWith('$key=')) + .map((String line) => line.split('=')[1]) + .toList(); + return properties.isEmpty ? null : properties.first; + } + + Future checkBuildVersion({ + String manifest, + BuildInfo buildInfo, + String expectedBuildName, + String expectedBuildNumber, + }) async { + final String projectPath = await createMinimalProject(manifest); + + await updateGeneratedXcodeProperties( + projectPath: projectPath, + buildInfo: buildInfo, + targetOverride: bundle.defaultMainPath, + previewDart2: false, + ); + + final String propertiesPath = fs.path.join(projectPath, 'ios', 'Flutter', 'Generated.xcconfig'); + final File localPropertiesFile = fs.file(propertiesPath); + + expect(propertyFor('FLUTTER_BUILD_NAME', localPropertiesFile), expectedBuildName); + expect(propertyFor('FLUTTER_BUILD_NUMBER', localPropertiesFile), expectedBuildNumber); + } + + testUsingContext('extract build name and number from pubspec.yaml', () async { + const String manifest = ''' +name: test +version: 1.0.0+1 +dependencies: + flutter: + sdk: flutter +flutter: +'''; + + const BuildInfo buildInfo = const BuildInfo(BuildMode.release, null); + await checkBuildVersion( + manifest: manifest, + buildInfo: buildInfo, + expectedBuildName: '1.0.0', + expectedBuildNumber: '1', + ); + }); + + testUsingContext('extract build name from pubspec.yaml', () async { + const String manifest = ''' +name: test +version: 1.0.0 +dependencies: + flutter: + sdk: flutter +flutter: +'''; + const BuildInfo buildInfo = const BuildInfo(BuildMode.release, null); + await checkBuildVersion( + manifest: manifest, + buildInfo: buildInfo, + expectedBuildName: '1.0.0', + expectedBuildNumber: null, + ); + }); + + testUsingContext('allow build info to override build name', () async { + const String manifest = ''' +name: test +version: 1.0.0+1 +dependencies: + flutter: + sdk: flutter +flutter: +'''; + const BuildInfo buildInfo = const BuildInfo(BuildMode.release, null, buildName: '1.0.2'); + await checkBuildVersion( + manifest: manifest, + buildInfo: buildInfo, + expectedBuildName: '1.0.2', + expectedBuildNumber: '1', + ); + }); + + testUsingContext('allow build info to override build number', () async { + const String manifest = ''' +name: test +version: 1.0.0+1 +dependencies: + flutter: + sdk: flutter +flutter: +'''; + const BuildInfo buildInfo = const BuildInfo(BuildMode.release, null, buildNumber: 3); + await checkBuildVersion( + manifest: manifest, + buildInfo: buildInfo, + expectedBuildName: '1.0.0', + expectedBuildNumber: '3', + ); + }); + + testUsingContext('allow build info to override build name and number', () async { + const String manifest = ''' +name: test +version: 1.0.0+1 +dependencies: + flutter: + sdk: flutter +flutter: +'''; + const BuildInfo buildInfo = const BuildInfo(BuildMode.release, null, buildName: '1.0.2', buildNumber: 3); + await checkBuildVersion( + manifest: manifest, + buildInfo: buildInfo, + expectedBuildName: '1.0.2', + expectedBuildNumber: '3', + ); + }); + + testUsingContext('allow build info to override build name and set number', () async { + const String manifest = ''' +name: test +version: 1.0.0 +dependencies: + flutter: + sdk: flutter +flutter: +'''; + const BuildInfo buildInfo = const BuildInfo(BuildMode.release, null, buildName: '1.0.2', buildNumber: 3); + await checkBuildVersion( + manifest: manifest, + buildInfo: buildInfo, + expectedBuildName: '1.0.2', + expectedBuildNumber: '3', + ); + }); + + testUsingContext('allow build info to set build name and number', () async { + const String manifest = ''' +name: test +dependencies: + flutter: + sdk: flutter +flutter: +'''; + const BuildInfo buildInfo = const BuildInfo(BuildMode.release, null, buildName: '1.0.2', buildNumber: 3); + await checkBuildVersion( + manifest: manifest, + buildInfo: buildInfo, + expectedBuildName: '1.0.2', + expectedBuildNumber: '3', + ); + }); + }); } Platform fakePlatform(String name) { diff --git a/packages/flutter_tools/test/project_test.dart b/packages/flutter_tools/test/project_test.dart index 632018d55c2..89551874774 100644 --- a/packages/flutter_tools/test/project_test.dart +++ b/packages/flutter_tools/test/project_test.dart @@ -20,23 +20,23 @@ void main() { group('ensure ready for platform-specific tooling', () { testInMemory('does nothing, if project is not created', () async { final FlutterProject project = someProject(); - project.ensureReadyForPlatformSpecificTooling(); + await project.ensureReadyForPlatformSpecificTooling(); expect(project.directory.existsSync(), isFalse); }); testInMemory('does nothing in plugin or package root project', () async { final FlutterProject project = aPluginProject(); - project.ensureReadyForPlatformSpecificTooling(); + await project.ensureReadyForPlatformSpecificTooling(); expect(project.ios.directory.childFile('Runner/GeneratedPluginRegistrant.h').existsSync(), isFalse); expect(project.ios.directory.childFile('Flutter/Generated.xcconfig').existsSync(), isFalse); }); testInMemory('injects plugins', () async { final FlutterProject project = aProjectWithIos(); - project.ensureReadyForPlatformSpecificTooling(); + await project.ensureReadyForPlatformSpecificTooling(); expect(project.ios.directory.childFile('Runner/GeneratedPluginRegistrant.h').existsSync(), isTrue); }); testInMemory('generates Xcode configuration', () async { final FlutterProject project = aProjectWithIos(); - project.ensureReadyForPlatformSpecificTooling(); + await project.ensureReadyForPlatformSpecificTooling(); expect(project.ios.directory.childFile('Flutter/Generated.xcconfig').existsSync(), isTrue); }); });