Commit c65e9d19 authored by Ralph Bergmann's avatar Ralph Bergmann Committed by Todd Volkert

add version to pubspec.yaml (#16857)

Uses the `version` property from the `pubspec.yaml` file to set the corresponding fields in the `local.properties` file respectively in the `Generated.xcconfig` file.

The `--build-name` and `--build-number` options have changed. Now they trump the `version` property from the `pubspec.yaml` file.

If the `version` property is not set and the  `--build-name` and `--build-number` options are not provided, the build command will not change the `local.properties` / `Generated.xcconfig` file.
parent f9860480
...@@ -3,9 +3,6 @@ ...@@ -3,9 +3,6 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async'; import 'dart:async';
import 'dart:convert';
import 'package:meta/meta.dart';
import '../android/android_sdk.dart'; import '../android/android_sdk.dart';
import '../artifacts.dart'; import '../artifacts.dart';
...@@ -17,7 +14,9 @@ import '../base/platform.dart'; ...@@ -17,7 +14,9 @@ import '../base/platform.dart';
import '../base/process.dart'; import '../base/process.dart';
import '../base/utils.dart'; import '../base/utils.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../bundle.dart' as bundle;
import '../cache.dart'; import '../cache.dart';
import '../flutter_manifest.dart';
import '../globals.dart'; import '../globals.dart';
import 'android_sdk.dart'; import 'android_sdk.dart';
import 'android_studio.dart'; import 'android_studio.dart';
...@@ -95,7 +94,7 @@ Future<GradleProject> _gradleProject() async { ...@@ -95,7 +94,7 @@ Future<GradleProject> _gradleProject() async {
// of calculating the app properties using Gradle. This may take minutes. // of calculating the app properties using Gradle. This may take minutes.
Future<GradleProject> _readGradleProject() async { Future<GradleProject> _readGradleProject() async {
final String gradle = await _ensureGradle(); final String gradle = await _ensureGradle();
updateLocalProperties(); await updateLocalProperties();
try { try {
final Status status = logger.startProgress('Resolving dependencies...', expectSlowOperation: true); final Status status = logger.startProgress('Resolving dependencies...', expectSlowOperation: true);
final RunResult runResult = await runCheckedAsync( final RunResult runResult = await runCheckedAsync(
...@@ -198,10 +197,13 @@ distributionUrl=https\\://services.gradle.org/distributions/gradle-$gradleVersio ...@@ -198,10 +197,13 @@ distributionUrl=https\\://services.gradle.org/distributions/gradle-$gradleVersio
} }
/// Create android/local.properties if needed, and update Flutter settings. /// Create android/local.properties if needed, and update Flutter settings.
void updateLocalProperties({String projectPath, BuildInfo buildInfo}) { Future<void> updateLocalProperties({String projectPath, BuildInfo buildInfo}) async {
final File localProperties = (projectPath == null) final File localProperties = (projectPath == null)
? fs.file(fs.path.join('android', 'local.properties')) ? fs.file(fs.path.join('android', 'local.properties'))
: fs.file(fs.path.join(projectPath, '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; bool changed = false;
SettingsFile settings; SettingsFile settings;
...@@ -225,85 +227,39 @@ void updateLocalProperties({String projectPath, BuildInfo buildInfo}) { ...@@ -225,85 +227,39 @@ void updateLocalProperties({String projectPath, BuildInfo buildInfo}) {
changed = true; changed = true;
} }
if (changed) FlutterManifest manifest;
settings.writeContents(localProperties); try {
} manifest = await FlutterManifest.createFromPath(flutterManifest);
} catch (error) {
Future<Null> findAndReplaceVersionProperties({@required BuildInfo buildInfo}) { throwToolExit('Failed to load pubspec.yaml: $error');
assert(buildInfo != null, 'buildInfo can\'t be null');
final Completer<Null> completer = new Completer<Null>();
// 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 String buildName = buildInfo?.buildName ?? manifest.buildName;
final File appGradleTmp = fs.file(fs.path.join('android', 'app', 'build.gradle.tmp')); if (buildName != null) {
appGradleTmp.createSync(); settings.values['flutter.versionName'] = buildName;
changed = true;
if (appGradle.existsSync() && appGradleTmp.existsSync()) {
final Stream<List<int>> 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 final int buildNumber = buildInfo?.buildNumber ?? manifest.buildNumber;
if (buildInfo.buildName != null) { if (buildNumber != null) {
if (line.contains(new RegExp(r'^[ |\t]*(versionName)[ =\t]*\"[0-9.]*"'))) { settings.values['flutter.versionCode'] = '$buildNumber';
return line.splitMapJoin(new RegExp(r'(versionName)[ =\t]*\"[0-9.]*"'), onMatch: (Match m) { changed = true;
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; if (changed)
settings.writeContents(localProperties);
} }
Future<Null> buildGradleProject(BuildInfo buildInfo, String target) async { Future<Null> 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 // FlutterPlugin v1 reads local.properties to determine build mode. Plugin v2
// uses the standard Android way to determine what to build, but we still // 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. // update local.properties, in case we want to use it in the future.
updateLocalProperties(buildInfo: buildInfo); // Version name and number are provided by the pubspec.yaml file
await findAndReplaceVersionProperties(buildInfo: buildInfo); // 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(); final String gradle = await _ensureGradle();
......
...@@ -289,11 +289,11 @@ To edit platform code in an IDE see https://flutter.io/platform-plugins/#edit-co ...@@ -289,11 +289,11 @@ To edit platform code in an IDE see https://flutter.io/platform-plugins/#edit-co
if (argResults['pub']) { if (argResults['pub']) {
await pubGet(context: PubContext.create, directory: appPath, offline: argResults['offline']); 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) if (android_sdk.androidSdk != null)
gradle.updateLocalProperties(projectPath: appPath); await gradle.updateLocalProperties(projectPath: appPath);
return generatedCount; return generatedCount;
} }
......
...@@ -82,13 +82,13 @@ class PackagesGetCommand extends FlutterCommand { ...@@ -82,13 +82,13 @@ class PackagesGetCommand extends FlutterCommand {
await _runPubGet(target); await _runPubGet(target);
final FlutterProject rootProject = new FlutterProject(fs.directory(target)); final FlutterProject rootProject = new FlutterProject(fs.directory(target));
rootProject.ensureReadyForPlatformSpecificTooling(); await rootProject.ensureReadyForPlatformSpecificTooling();
// Get/upgrade packages in example app as well // Get/upgrade packages in example app as well
if (rootProject.hasExampleApp) { if (rootProject.hasExampleApp) {
final FlutterProject exampleProject = rootProject.example; final FlutterProject exampleProject = rootProject.example;
await _runPubGet(exampleProject.directory.path); await _runPubGet(exampleProject.directory.path);
exampleProject.ensureReadyForPlatformSpecificTooling(); await exampleProject.ensureReadyForPlatformSpecificTooling();
} }
} }
} }
......
...@@ -13,6 +13,8 @@ import 'base/file_system.dart'; ...@@ -13,6 +13,8 @@ import 'base/file_system.dart';
import 'cache.dart'; import 'cache.dart';
import 'globals.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. /// A wrapper around the `flutter` section in the `pubspec.yaml` file.
class FlutterManifest { class FlutterManifest {
FlutterManifest._(); FlutterManifest._();
...@@ -51,6 +53,36 @@ class FlutterManifest { ...@@ -51,6 +53,36 @@ class FlutterManifest {
String get appName => _descriptor['name'] ?? ''; 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 { bool get usesMaterialDesign {
return _flutterDescriptor['uses-material-design'] ?? false; return _flutterDescriptor['uses-material-design'] ?? false;
} }
......
...@@ -242,7 +242,7 @@ Future<XcodeBuildResult> buildXcodeProject({ ...@@ -242,7 +242,7 @@ Future<XcodeBuildResult> buildXcodeProject({
final Directory appDirectory = fs.directory(app.appDirectory); final Directory appDirectory = fs.directory(app.appDirectory);
await _addServicesToBundle(appDirectory); await _addServicesToBundle(appDirectory);
updateGeneratedXcodeProperties( await updateGeneratedXcodeProperties(
projectPath: fs.currentDirectory.path, projectPath: fs.currentDirectory.path,
buildInfo: buildInfo, buildInfo: buildInfo,
targetOverride: targetOverride, targetOverride: targetOverride,
...@@ -272,53 +272,6 @@ Future<XcodeBuildResult> buildXcodeProject({ ...@@ -272,53 +272,6 @@ Future<XcodeBuildResult> buildXcodeProject({
await fingerprinter.writeFingerprint(); 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(
<String>[
'/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(
<String>[
'/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<String> buildCommands = <String>[ final List<String> buildCommands = <String>[
'/usr/bin/env', '/usr/bin/env',
'xcrun', 'xcrun',
......
...@@ -2,9 +2,12 @@ ...@@ -2,9 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import '../artifacts.dart'; import '../artifacts.dart';
import '../base/common.dart';
import '../base/context.dart'; import '../base/context.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/io.dart'; import '../base/io.dart';
...@@ -15,6 +18,7 @@ import '../base/utils.dart'; ...@@ -15,6 +18,7 @@ import '../base/utils.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../bundle.dart' as bundle; import '../bundle.dart' as bundle;
import '../cache.dart'; import '../cache.dart';
import '../flutter_manifest.dart';
import '../globals.dart'; import '../globals.dart';
final RegExp _settingExpr = new RegExp(r'(\w+)\s*=\s*(.*)$'); final RegExp _settingExpr = new RegExp(r'(\w+)\s*=\s*(.*)$');
...@@ -30,11 +34,11 @@ String _generatedXcodePropertiesPath(String projectPath) { ...@@ -30,11 +34,11 @@ String _generatedXcodePropertiesPath(String projectPath) {
/// Writes default Xcode properties files in the Flutter project at [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. /// if project is an iOS project and such files do not already exist.
void generateXcodeProperties(String projectPath) { Future<void> generateXcodeProperties(String projectPath) async {
if (fs.isDirectorySync(fs.path.join(projectPath, 'ios'))) { if (fs.isDirectorySync(fs.path.join(projectPath, 'ios'))) {
if (fs.file(_generatedXcodePropertiesPath(projectPath)).existsSync()) if (fs.file(_generatedXcodePropertiesPath(projectPath)).existsSync())
return; return;
updateGeneratedXcodeProperties( await updateGeneratedXcodeProperties(
projectPath: projectPath, projectPath: projectPath,
buildInfo: BuildInfo.debug, buildInfo: BuildInfo.debug,
targetOverride: bundle.defaultMainPath, targetOverride: bundle.defaultMainPath,
...@@ -47,12 +51,12 @@ void generateXcodeProperties(String projectPath) { ...@@ -47,12 +51,12 @@ void generateXcodeProperties(String projectPath) {
/// ///
/// targetOverride: Optional parameter, if null or unspecified the default value /// targetOverride: Optional parameter, if null or unspecified the default value
/// from xcode_backend.sh is used 'lib/main.dart'. /// from xcode_backend.sh is used 'lib/main.dart'.
void updateGeneratedXcodeProperties({ Future<void> updateGeneratedXcodeProperties({
@required String projectPath, @required String projectPath,
@required BuildInfo buildInfo, @required BuildInfo buildInfo,
String targetOverride, String targetOverride,
@required bool previewDart2, @required bool previewDart2,
}) { }) async {
final StringBuffer localsBuffer = new StringBuffer(); final StringBuffer localsBuffer = new StringBuffer();
localsBuffer.writeln('// This is a generated file; do not edit or check into version control.'); localsBuffer.writeln('// This is a generated file; do not edit or check into version control.');
...@@ -77,6 +81,24 @@ void updateGeneratedXcodeProperties({ ...@@ -77,6 +81,24 @@ void updateGeneratedXcodeProperties({
localsBuffer.writeln('FLUTTER_FRAMEWORK_DIR=${flutterFrameworkDir(buildInfo.mode)}'); 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) { if (artifacts is LocalEngineArtifacts) {
final LocalEngineArtifacts localEngineArtifacts = artifacts; final LocalEngineArtifacts localEngineArtifacts = artifacts;
localsBuffer.writeln('LOCAL_ENGINE=${localEngineArtifacts.engineOutPath}'); localsBuffer.writeln('LOCAL_ENGINE=${localEngineArtifacts.engineOutPath}');
......
...@@ -55,12 +55,12 @@ class FlutterProject { ...@@ -55,12 +55,12 @@ class FlutterProject {
/// Generates project files necessary to make Gradle builds work on Android /// Generates project files necessary to make Gradle builds work on Android
/// and CocoaPods+Xcode work on iOS, for app projects only /// and CocoaPods+Xcode work on iOS, for app projects only
void ensureReadyForPlatformSpecificTooling() { Future<void> ensureReadyForPlatformSpecificTooling() async {
if (!directory.existsSync() || hasExampleApp) { if (!directory.existsSync() || hasExampleApp) {
return; return;
} }
injectPlugins(directory: directory.path); injectPlugins(directory: directory.path);
generateXcodeProperties(directory.path); await generateXcodeProperties(directory.path);
} }
} }
......
...@@ -329,7 +329,7 @@ abstract class FlutterCommand extends Command<Null> { ...@@ -329,7 +329,7 @@ abstract class FlutterCommand extends Command<Null> {
if (shouldRunPub) { if (shouldRunPub) {
await pubGet(context: PubContext.getVerifyContext(name)); await pubGet(context: PubContext.getVerifyContext(name));
new FlutterProject(fs.currentDirectory).ensureReadyForPlatformSpecificTooling(); await new FlutterProject(fs.currentDirectory).ensureReadyForPlatformSpecificTooling();
} }
setupApplicationPackages(); setupApplicationPackages();
......
...@@ -11,6 +11,16 @@ if (flutterRoot == null) { ...@@ -11,6 +11,16 @@ if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 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: 'com.android.application'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
...@@ -26,8 +36,8 @@ android { ...@@ -26,8 +36,8 @@ android {
applicationId "{{androidIdentifier}}" applicationId "{{androidIdentifier}}"
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 27 targetSdkVersion 27
versionCode 1 versionCode flutterVersionCode.toInteger()
versionName "1.0" versionName flutterVersionName
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
} }
......
...@@ -11,6 +11,16 @@ if (flutterRoot == null) { ...@@ -11,6 +11,16 @@ if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 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: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
...@@ -31,8 +41,8 @@ android { ...@@ -31,8 +41,8 @@ android {
applicationId "{{androidIdentifier}}" applicationId "{{androidIdentifier}}"
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 27 targetSdkVersion 27
versionCode 1 versionCode flutterVersionCode.toInteger()
versionName "1.0" versionName flutterVersionName
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
} }
......
...@@ -370,7 +370,7 @@ ...@@ -370,7 +370,7 @@
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
...@@ -393,7 +393,7 @@ ...@@ -393,7 +393,7 @@
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
......
...@@ -368,7 +368,7 @@ ...@@ -368,7 +368,7 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
...@@ -396,7 +396,7 @@ ...@@ -396,7 +396,7 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
......
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.0</string> <string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1</string> <string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
<true/> <true/>
<key>UILaunchStoryboardName</key> <key>UILaunchStoryboardName</key>
......
name: {{projectName}} name: {{projectName}}
description: {{description}} 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: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
......
...@@ -2,16 +2,20 @@ ...@@ -2,16 +2,20 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async';
import 'package:flutter_tools/src/android/gradle.dart'; import 'package:flutter_tools/src/android/gradle.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
import '../src/common.dart'; import '../src/common.dart';
import '../src/context.dart';
void main() { void main() {
group('gradle build', () { 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; Exception shouldBeToolExit;
try { try {
// We'd like to always set androidSdk to null and test updateLocalProperties. But that's // We'd like to always set androidSdk to null and test updateLocalProperties. But that's
...@@ -21,7 +25,7 @@ void main() { ...@@ -21,7 +25,7 @@ void main() {
// This test is written to fail if our bots get Android SDKs in the future: shouldBeToolExit // 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 // will be null and our expectation would fail. That would remind us to make these tests
// hermetic before adding Android SDKs to the bots. // hermetic before adding Android SDKs to the bots.
updateLocalProperties(); await updateLocalProperties();
} on Exception catch (e) { } on Exception catch (e) {
shouldBeToolExit = e; shouldBeToolExit = e;
} }
...@@ -126,4 +130,181 @@ someOtherProperty: someOtherValue ...@@ -126,4 +130,181 @@ someOtherProperty: someOtherValue
expect(project.assembleTaskFor(const BuildInfo(BuildMode.release, 'unknown')), isNull); 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<String> 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<void> 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',
);
});
});
} }
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async';
import 'package:file/file.dart'; import 'package:file/file.dart';
import 'package:file/memory.dart'; import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
...@@ -359,6 +361,118 @@ flutter: ...@@ -359,6 +361,118 @@ flutter:
final FlutterManifest flutterManifest = await FlutterManifest.createFromString(manifest); final FlutterManifest flutterManifest = await FlutterManifest.createFromString(manifest);
expect(flutterManifest.isEmpty, false); expect(flutterManifest.isEmpty, false);
}); });
Future<void> 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', () { group('FlutterManifest with MemoryFileSystem', () {
...@@ -371,8 +485,7 @@ dependencies: ...@@ -371,8 +485,7 @@ dependencies:
flutter: flutter:
'''; ''';
final FlutterManifest flutterManifest = await FlutterManifest final FlutterManifest flutterManifest = await FlutterManifest.createFromString(manifest);
.createFromString(manifest);
expect(flutterManifest.isEmpty, false); expect(flutterManifest.isEmpty, false);
} }
......
...@@ -2,11 +2,15 @@ ...@@ -2,11 +2,15 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async';
import 'package:file/memory.dart'; import 'package:file/memory.dart';
import 'package:flutter_tools/src/artifacts.dart'; import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/build_info.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:flutter_tools/src/ios/xcodeproj.dart';
import 'package:mockito/mockito.dart'; import 'package:mockito/mockito.dart';
import 'package:platform/platform.dart'; import 'package:platform/platform.dart';
...@@ -277,14 +281,14 @@ Information about project "Runner": ...@@ -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.getArtifactPath(Artifact.flutterFramework, TargetPlatform.ios, any)).thenReturn('engine');
when(mockArtifacts.engineOutPath).thenReturn(fs.path.join('out', 'ios_profile_arm')); when(mockArtifacts.engineOutPath).thenReturn(fs.path.join('out', 'ios_profile_arm'));
const BuildInfo buildInfo = const BuildInfo(BuildMode.debug, null, const BuildInfo buildInfo = const BuildInfo(BuildMode.debug, null,
previewDart2: true, previewDart2: true,
targetPlatform: TargetPlatform.ios, targetPlatform: TargetPlatform.ios,
); );
updateGeneratedXcodeProperties( await updateGeneratedXcodeProperties(
projectPath: 'path/to/project', projectPath: 'path/to/project',
buildInfo: buildInfo, buildInfo: buildInfo,
previewDart2: true, previewDart2: true,
...@@ -297,14 +301,14 @@ Information about project "Runner": ...@@ -297,14 +301,14 @@ Information about project "Runner":
expect(contents.contains('ARCHS=armv7'), isTrue); 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.getArtifactPath(Artifact.flutterFramework, TargetPlatform.ios, any)).thenReturn('engine');
when(mockArtifacts.engineOutPath).thenReturn(fs.path.join('out', 'ios_profile')); when(mockArtifacts.engineOutPath).thenReturn(fs.path.join('out', 'ios_profile'));
const BuildInfo buildInfo = const BuildInfo(BuildMode.debug, null, const BuildInfo buildInfo = const BuildInfo(BuildMode.debug, null,
previewDart2: true, previewDart2: true,
targetPlatform: TargetPlatform.ios, targetPlatform: TargetPlatform.ios,
); );
updateGeneratedXcodeProperties( await updateGeneratedXcodeProperties(
projectPath: 'path/to/project', projectPath: 'path/to/project',
buildInfo: buildInfo, buildInfo: buildInfo,
previewDart2: true, previewDart2: true,
...@@ -317,6 +321,185 @@ Information about project "Runner": ...@@ -317,6 +321,185 @@ Information about project "Runner":
expect(contents.contains('ARCHS=arm64'), isTrue); 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<String> 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<String> properties = file
.readAsLinesSync()
.where((String line) => line.startsWith('$key='))
.map((String line) => line.split('=')[1])
.toList();
return properties.isEmpty ? null : properties.first;
}
Future<void> 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) { Platform fakePlatform(String name) {
......
...@@ -20,23 +20,23 @@ void main() { ...@@ -20,23 +20,23 @@ void main() {
group('ensure ready for platform-specific tooling', () { group('ensure ready for platform-specific tooling', () {
testInMemory('does nothing, if project is not created', () async { testInMemory('does nothing, if project is not created', () async {
final FlutterProject project = someProject(); final FlutterProject project = someProject();
project.ensureReadyForPlatformSpecificTooling(); await project.ensureReadyForPlatformSpecificTooling();
expect(project.directory.existsSync(), isFalse); expect(project.directory.existsSync(), isFalse);
}); });
testInMemory('does nothing in plugin or package root project', () async { testInMemory('does nothing in plugin or package root project', () async {
final FlutterProject project = aPluginProject(); final FlutterProject project = aPluginProject();
project.ensureReadyForPlatformSpecificTooling(); await project.ensureReadyForPlatformSpecificTooling();
expect(project.ios.directory.childFile('Runner/GeneratedPluginRegistrant.h').existsSync(), isFalse); expect(project.ios.directory.childFile('Runner/GeneratedPluginRegistrant.h').existsSync(), isFalse);
expect(project.ios.directory.childFile('Flutter/Generated.xcconfig').existsSync(), isFalse); expect(project.ios.directory.childFile('Flutter/Generated.xcconfig').existsSync(), isFalse);
}); });
testInMemory('injects plugins', () async { testInMemory('injects plugins', () async {
final FlutterProject project = aProjectWithIos(); final FlutterProject project = aProjectWithIos();
project.ensureReadyForPlatformSpecificTooling(); await project.ensureReadyForPlatformSpecificTooling();
expect(project.ios.directory.childFile('Runner/GeneratedPluginRegistrant.h').existsSync(), isTrue); expect(project.ios.directory.childFile('Runner/GeneratedPluginRegistrant.h').existsSync(), isTrue);
}); });
testInMemory('generates Xcode configuration', () async { testInMemory('generates Xcode configuration', () async {
final FlutterProject project = aProjectWithIos(); final FlutterProject project = aProjectWithIos();
project.ensureReadyForPlatformSpecificTooling(); await project.ensureReadyForPlatformSpecificTooling();
expect(project.ios.directory.childFile('Flutter/Generated.xcconfig').existsSync(), isTrue); expect(project.ios.directory.childFile('Flutter/Generated.xcconfig').existsSync(), isTrue);
}); });
}); });
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment