Support for app flavors in flutter tooling, #11676 retake (#11734)

This commit is contained in:
Mikkel Nygaard Ravn 2017-08-23 10:55:35 +02:00 committed by GitHub
parent e843cb342d
commit 9496e6dfa7
60 changed files with 2042 additions and 154 deletions

View File

@ -0,0 +1,14 @@
// Copyright 2017 The Chromium Authors. All rights reserved.
// 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_devicelab/framework/adb.dart';
import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/tasks/integration_tests.dart';
Future<Null> main() async {
deviceOperatingSystem = DeviceOperatingSystem.android;
await task(createFlavorsTest());
}

View File

@ -0,0 +1,14 @@
// Copyright 2017 The Chromium Authors. All rights reserved.
// 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_devicelab/framework/adb.dart';
import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/tasks/integration_tests.dart';
Future<Null> main() async {
deviceOperatingSystem = DeviceOperatingSystem.ios;
await task(createFlavorsTest());
}

View File

@ -0,0 +1,14 @@
// Copyright 2017 The Chromium Authors. All rights reserved.
// 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_devicelab/framework/adb.dart';
import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/tasks/integration_tests.dart';
Future<Null> main() async {
deviceOperatingSystem = DeviceOperatingSystem.android;
await task(createFlavorsTest());
}

View File

@ -23,6 +23,14 @@ TaskFunction createPlatformInteractionTest() {
); );
} }
TaskFunction createFlavorsTest() {
return new DriverTest(
'${flutterDirectory.path}/dev/integration_tests/flavors',
'lib/main.dart',
extraOptions: <String>['--flavor', 'paid']
);
}
TaskFunction createPlatformChannelSampleTest() { TaskFunction createPlatformChannelSampleTest() {
return new DriverTest( return new DriverTest(
'${flutterDirectory.path}/examples/platform_channel', '${flutterDirectory.path}/examples/platform_channel',
@ -32,10 +40,16 @@ TaskFunction createPlatformChannelSampleTest() {
class DriverTest { class DriverTest {
DriverTest(this.testDirectory, this.testTarget); DriverTest(
this.testDirectory,
this.testTarget, {
this.extraOptions = const <String>[],
}
);
final String testDirectory; final String testDirectory;
final String testTarget; final String testTarget;
final List<String> extraOptions;
Future<TaskResult> call() { Future<TaskResult> call() {
return inDirectory(testDirectory, () async { return inDirectory(testDirectory, () async {
@ -46,14 +60,15 @@ class DriverTest {
if (deviceOperatingSystem == DeviceOperatingSystem.ios) if (deviceOperatingSystem == DeviceOperatingSystem.ios)
await prepareProvisioningCertificates(testDirectory); await prepareProvisioningCertificates(testDirectory);
final List<String> options = <String>[
await flutter('drive', options: <String>[
'-v', '-v',
'-t', '-t',
testTarget, testTarget,
'-d', '-d',
deviceId, deviceId,
]); ];
options.addAll(extraOptions);
await flutter('drive', options: options);
return new TaskResult.success(null); return new TaskResult.success(null);
}); });

View File

@ -59,6 +59,13 @@ tasks:
stage: devicelab stage: devicelab
required_agent_capabilities: ["has-android-device"] required_agent_capabilities: ["has-android-device"]
flavors_test:
description: >
Checks that flavored builds work on Android.
stage: devicelab
required_agent_capabilities: ["has-android-device"]
flaky: true
channels_integration_test: channels_integration_test:
description: > description: >
Checks that platform channels work on Android. Checks that platform channels work on Android.
@ -161,6 +168,13 @@ tasks:
# iOS on-device tests # iOS on-device tests
flavors_test_ios:
description: >
Checks that flavored builds work on iOS.
stage: devicelab_ios
required_agent_capabilities: ["has-ios-device"]
flaky: true
channels_integration_test_ios: channels_integration_test_ios:
description: > description: >
Checks that platform channels work on iOS. Checks that platform channels work on iOS.
@ -242,6 +256,13 @@ tasks:
# Tests running on Windows host # Tests running on Windows host
flavors_test_win:
description: >
Checks that flavored builds work on Windows.
stage: devicelab_win
required_agent_capabilities: ["windows"]
flaky: true
channels_integration_test_win: channels_integration_test_win:
description: > description: >
Checks that platform channels work when app is launched from Windows. Checks that platform channels work when app is launched from Windows.

View File

@ -0,0 +1,10 @@
.DS_Store
.atom/
.idea
.packages
.pub/
build/
ios/.generated/
packages
pubspec.lock
.flutter-plugins

View File

@ -0,0 +1,3 @@
# flavors
Integration test of build flavors (Android product flavors, Xcode schemes).

View File

@ -0,0 +1,9 @@
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
GeneratedPluginRegistrant.java

View File

@ -0,0 +1,62 @@
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withInputStream { stream ->
localProperties.load(stream)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
apply plugin: 'com.android.application'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 25
buildToolsVersion '25.0.3'
lintOptions {
disable 'InvalidPackage'
}
defaultConfig {
applicationId "com.yourcompany.flavors"
minSdkVersion 16
targetSdkVersion 25
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
signingConfig signingConfigs.debug
}
}
aaptOptions {
// TODO(goderbauer): remove when https://github.com/flutter/flutter/issues/8986 is resolved.
if(System.getenv("FLUTTER_CI_WIN")) {
println "AAPT cruncher disabled when running on Win CI."
cruncherEnabled false
}
}
productFlavors {
free {}
paid {}
}
}
flutter {
source '../..'
}
dependencies {
androidTestCompile 'com.android.support:support-annotations:25.4.0'
androidTestCompile 'com.android.support.test:runner:0.5'
androidTestCompile 'com.android.support.test:rules:0.5'
}

View File

@ -0,0 +1,21 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.yourcompany.flavors">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:name="io.flutter.app.FlutterApplication"
android:label="flavors">
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -0,0 +1,22 @@
package com.yourcompany.flavors;
import android.os.Bundle;
import io.flutter.app.FlutterActivity;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugins.GeneratedPluginRegistrant;
public class MainActivity extends FlutterActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
new MethodChannel(getFlutterView(), "flavor").setMethodCallHandler(new MethodChannel.MethodCallHandler() {
@Override
public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
result.success(BuildConfig.FLAVOR);
}
});
}
}

View File

@ -0,0 +1,31 @@
buildscript {
repositories {
jcenter()
maven {
url "https://maven.google.com"
}
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
}
}
allprojects {
repositories {
jcenter()
maven {
url "https://maven.google.com"
}
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
project.evaluationDependsOn(':app')
}
task clean(type: Delete) {
delete rootProject.buildDir
}

View File

@ -0,0 +1 @@
org.gradle.jvmargs=-Xmx1536M

View File

@ -0,0 +1,6 @@
#Fri Jun 23 08:50:38 CEST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip

View File

@ -0,0 +1,15 @@
include ':app'
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
def plugins = new Properties()
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
if (pluginsFile.exists()) {
pluginsFile.withInputStream { stream -> plugins.load(stream) }
}
plugins.each { name, path ->
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
include ":$name"
project(":$name").projectDir = pluginDirectory
}

View File

@ -0,0 +1,41 @@
.idea/
.vagrant/
.sconsign.dblite
.svn/
.DS_Store
*.swp
profile
DerivedData/
build/
GeneratedPluginRegistrant.h
GeneratedPluginRegistrant.m
*.pbxuser
*.mode1v3
*.mode2v3
*.perspectivev3
!default.pbxuser
!default.mode1v3
!default.mode2v3
!default.perspectivev3
xcuserdata
*.moved-aside
*.pyc
*sync/
Icon?
.tags*
/Flutter/app.flx
/Flutter/app.zip
/Flutter/App.framework
/Flutter/Flutter.framework
/Flutter/Generated.xcconfig
/ServiceDefinitions.json
Pods/

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>arm64</string>
</array>
<key>MinimumOSVersion</key>
<string>8.0</string>
</dict>
</plist>

View File

@ -0,0 +1 @@
#include "Generated.xcconfig"

View File

@ -0,0 +1 @@
#include "Generated.xcconfig"

View File

@ -0,0 +1,569 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; };
9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; };
9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB31CF90195004384FC /* Generated.xcconfig */; };
9740EEBB1CF902C7004384FC /* app.flx in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB71CF902C7004384FC /* app.flx */; };
978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */,
9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
9740EEB71CF902C7004384FC /* app.flx */ = {isa = PBXFileReference; lastKnownFileType = file; name = app.flx; path = Flutter/app.flx; sourceTree = "<group>"; };
9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
9740EEB71CF902C7004384FC /* app.flx */,
3B80C3931E831B6300D905FE /* App.framework */,
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEBA1CF902C7004384FC /* Flutter.framework */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */,
7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */,
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
97C146F11CF9000F007C117D /* Supporting Files */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
);
path = Runner;
sourceTree = "<group>";
};
97C146F11CF9000F007C117D /* Supporting Files */ = {
isa = PBXGroup;
children = (
97C146F21CF9000F007C117D /* main.m */,
);
name = "Supporting Files";
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0830;
ORGANIZATIONNAME = "The Chromium Authors";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
9740EEBB1CF902C7004384FC /* app.flx in Resources */,
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */,
97C146F31CF9000F007C117D /* main.m in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
744E33D21F45D145007AB5E2 /* Debug Paid */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
PRODUCT_FLAVOR = paid;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = "Debug Paid";
};
744E33D31F45D145007AB5E2 /* Debug Paid */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ARCHS = arm64;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
DEVELOPMENT_TEAM = AQ7UHDBEXJ;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = com.yourcompany.flavors;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = "Debug Paid";
};
744E33D41F45D159007AB5E2 /* Release Paid */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
PRODUCT_FLAVOR = paid;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = "Release Paid";
};
744E33D51F45D159007AB5E2 /* Release Paid */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ARCHS = arm64;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
DEVELOPMENT_TEAM = AQ7UHDBEXJ;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = com.yourcompany.flavors;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = "Release Paid";
};
97C147031CF9000F007C117D /* Debug Free */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
PRODUCT_FLAVOR = free;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = "Debug Free";
};
97C147041CF9000F007C117D /* Release Free */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
PRODUCT_FLAVOR = free;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = "Release Free";
};
97C147061CF9000F007C117D /* Debug Free */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ARCHS = arm64;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
DEVELOPMENT_TEAM = AQ7UHDBEXJ;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = com.yourcompany.flavors;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = "Debug Free";
};
97C147071CF9000F007C117D /* Release Free */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ARCHS = arm64;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
DEVELOPMENT_TEAM = AQ7UHDBEXJ;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = com.yourcompany.flavors;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = "Release Free";
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug Free */,
744E33D21F45D145007AB5E2 /* Debug Paid */,
97C147041CF9000F007C117D /* Release Free */,
744E33D41F45D159007AB5E2 /* Release Paid */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = "Release Free";
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug Free */,
744E33D31F45D145007AB5E2 /* Debug Paid */,
97C147071CF9000F007C117D /* Release Free */,
744E33D51F45D159007AB5E2 /* Release Paid */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = "Release Free";
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>

View File

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0830"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug Free"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug Free"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release Free"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug Free">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release Free"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0830"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug Paid"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug Paid"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release Paid"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug Paid">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release Paid"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>

View File

@ -0,0 +1,6 @@
#import <UIKit/UIKit.h>
#import <Flutter/Flutter.h>
@interface AppDelegate : FlutterAppDelegate
@end

View File

@ -0,0 +1,19 @@
#include "AppDelegate.h"
#include "GeneratedPluginRegistrant.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[GeneratedPluginRegistrant registerWithRegistry:self];
// Override point for customization after application launch.
FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController;
FlutterMethodChannel* flavorChannel = [FlutterMethodChannel methodChannelWithName:@"flavor" binaryMessenger:controller];
[flavorChannel setMethodCallHandler:^(FlutterMethodCall *call, FlutterResult result) {
NSString* flavor = (NSString*)[[NSBundle mainBundle].infoDictionary valueForKey:@"Flavor"];
result(flavor);
}];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
@end

View File

@ -0,0 +1,98 @@
{
"images" : [
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "1x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "3x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "76x76",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "76x76",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "83.5x83.5",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

View File

@ -0,0 +1,5 @@
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.

View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>

View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>flavors</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>Flavor</key>
<string>${PRODUCT_FLAVOR}</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>arm64</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</plist>

View File

@ -0,0 +1,9 @@
#import <UIKit/UIKit.h>
#import <Flutter/Flutter.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}

View File

@ -0,0 +1,34 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_driver/driver_extension.dart';
void main() {
enableFlutterDriverExtension();
runApp(new Center(child: new Flavor()));
}
class Flavor extends StatefulWidget {
@override
_FlavorState createState() => new _FlavorState();
}
class _FlavorState extends State<Flavor> {
String _flavor;
@override
void initState() {
super.initState();
new MethodChannel('flavor').invokeMethod('getFlavor').then((String flavor) {
setState(() {
_flavor = flavor;
});
});
}
@override
Widget build(BuildContext context) {
return _flavor == null
? new Text('Awaiting flavor...')
: new Text(_flavor, key: const ValueKey<String>('flavor'));
}
}

View File

@ -0,0 +1,10 @@
name: flavors
description: Integration test for build flavors.
dependencies:
flutter:
sdk: flutter
flutter_driver:
sdk: flutter
flutter:
uses-material-design: true

View File

@ -0,0 +1,26 @@
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_driver/flutter_driver.dart';
import 'package:test/test.dart';
void main() {
group('flavors suite', () {
FlutterDriver driver;
setUpAll(() async {
driver = await FlutterDriver.connect();
});
test('check flavor', () async {
final SerializableFinder flavorField = find.byValueKey('flavor');
final String flavor = await driver.getText(flavorField);
expect(flavor, 'paid');
});
tearDownAll(() async {
driver?.close();
});
});
}

View File

@ -338,8 +338,7 @@ class AndroidDevice extends Device {
@override @override
Future<LaunchResult> startApp( Future<LaunchResult> startApp(
ApplicationPackage package, ApplicationPackage package, {
BuildMode mode, {
String mainPath, String mainPath,
String route, String route,
DebuggingOptions debuggingOptions, DebuggingOptions debuggingOptions,
@ -352,7 +351,7 @@ class AndroidDevice extends Device {
if (!await _checkForSupportedAdbVersion() || !await _checkForSupportedAndroidVersion()) if (!await _checkForSupportedAdbVersion() || !await _checkForSupportedAndroidVersion())
return new LaunchResult.failed(); return new LaunchResult.failed();
if (await targetPlatform != TargetPlatform.android_arm && mode != BuildMode.debug) { if (await targetPlatform != TargetPlatform.android_arm && !debuggingOptions.buildInfo.isDebug) {
printError('Profile and release builds are only supported on ARM targets.'); printError('Profile and release builds are only supported on ARM targets.');
return new LaunchResult.failed(); return new LaunchResult.failed();
} }
@ -361,7 +360,7 @@ class AndroidDevice extends Device {
printTrace('Building APK'); printTrace('Building APK');
await buildApk( await buildApk(
target: mainPath, target: mainPath,
buildMode: debuggingOptions.buildMode, buildInfo: debuggingOptions.buildInfo,
kernelPath: kernelPath, kernelPath: kernelPath,
); );
// Package has been built, so we can get the updated application ID and // Package has been built, so we can get the updated application ID and
@ -408,7 +407,7 @@ class AndroidDevice extends Device {
if (debuggingOptions.enableSoftwareRendering) if (debuggingOptions.enableSoftwareRendering)
cmd.addAll(<String>['--ez', 'enable-software-rendering', 'true']); cmd.addAll(<String>['--ez', 'enable-software-rendering', 'true']);
if (debuggingOptions.debuggingEnabled) { if (debuggingOptions.debuggingEnabled) {
if (debuggingOptions.buildMode == BuildMode.debug) if (debuggingOptions.buildInfo.isDebug)
cmd.addAll(<String>['--ez', 'enable-checked-mode', 'true']); cmd.addAll(<String>['--ez', 'enable-checked-mode', 'true']);
if (debuggingOptions.startPaused) if (debuggingOptions.startPaused)
cmd.addAll(<String>['--ez', 'start-paused', 'true']); cmd.addAll(<String>['--ez', 'start-paused', 'true']);
@ -435,13 +434,13 @@ class AndroidDevice extends Device {
try { try {
Uri observatoryUri, diagnosticUri; Uri observatoryUri, diagnosticUri;
if (debuggingOptions.buildMode == BuildMode.debug) { if (debuggingOptions.buildInfo.isDebug) {
final List<Uri> deviceUris = await Future.wait( final List<Uri> deviceUris = await Future.wait(
<Future<Uri>>[observatoryDiscovery.uri, diagnosticDiscovery.uri] <Future<Uri>>[observatoryDiscovery.uri, diagnosticDiscovery.uri]
); );
observatoryUri = deviceUris[0]; observatoryUri = deviceUris[0];
diagnosticUri = deviceUris[1]; diagnosticUri = deviceUris[1];
} else if (debuggingOptions.buildMode == BuildMode.profile) { } else if (debuggingOptions.buildInfo.isProfile) {
observatoryUri = await observatoryDiscovery.uri; observatoryUri = await observatoryDiscovery.uri;
} }

View File

@ -23,8 +23,9 @@ const String gradleManifestPath = 'android/app/src/main/AndroidManifest.xml';
const String gradleAppOutV1 = 'android/app/build/outputs/apk/app-debug.apk'; const String gradleAppOutV1 = 'android/app/build/outputs/apk/app-debug.apk';
const String gradleAppOutDirV1 = 'android/app/build/outputs/apk'; const String gradleAppOutDirV1 = 'android/app/build/outputs/apk';
const String gradleVersion = '3.3'; const String gradleVersion = '3.3';
final RegExp _assembleTaskPattern = new RegExp(r'assemble([^:]+): task ');
String _cachedGradleAppOutDirV2; GradleProject _cachedGradleProject;
String _cachedGradleExecutable; String _cachedGradleExecutable;
enum FlutterPluginVersion { enum FlutterPluginVersion {
@ -58,6 +59,8 @@ FlutterPluginVersion get flutterPluginVersion {
return FlutterPluginVersion.none; return FlutterPluginVersion.none;
} }
/// Returns the path to the apk file created by [buildGradleProject], relative
/// to current directory.
Future<String> getGradleAppOut() async { Future<String> getGradleAppOut() async {
switch (flutterPluginVersion) { switch (flutterPluginVersion) {
case FlutterPluginVersion.none: case FlutterPluginVersion.none:
@ -67,19 +70,19 @@ Future<String> getGradleAppOut() async {
case FlutterPluginVersion.managed: case FlutterPluginVersion.managed:
// Fall through. The managed plugin matches plugin v2 for now. // Fall through. The managed plugin matches plugin v2 for now.
case FlutterPluginVersion.v2: case FlutterPluginVersion.v2:
return '${await _getGradleAppOutDirV2()}/app.apk'; return fs.path.relative(fs.path.join((await _gradleProject()).apkDirectory, 'app.apk'));
} }
return null; return null;
} }
Future<String> _getGradleAppOutDirV2() async { Future<GradleProject> _gradleProject() async {
_cachedGradleAppOutDirV2 ??= await _calculateGradleAppOutDirV2(); _cachedGradleProject ??= await _readGradleProject();
return _cachedGradleAppOutDirV2; return _cachedGradleProject;
} }
// Note: Dependencies are resolved and possibly downloaded as a side-effect // Note: Dependencies are resolved and possibly downloaded as a side-effect
// of calculating the app properties using Gradle. This may take minutes. // of calculating the app properties using Gradle. This may take minutes.
Future<String> _calculateGradleAppOutDirV2() async { Future<GradleProject> _readGradleProject() async {
final String gradle = await _ensureGradle(); final String gradle = await _ensureGradle();
updateLocalProperties(); updateLocalProperties();
try { try {
@ -90,28 +93,20 @@ Future<String> _calculateGradleAppOutDirV2() async {
environment: _gradleEnv, environment: _gradleEnv,
); );
final String properties = runResult.stdout.trim(); final String properties = runResult.stdout.trim();
String buildDir = properties final GradleProject project = new GradleProject.fromAppProperties(properties);
.split('\n')
.firstWhere((String s) => s.startsWith('buildDir: '))
.substring('buildDir: '.length)
.trim();
final String currentDirectory = fs.currentDirectory.path;
if (buildDir.startsWith(currentDirectory)) {
// Relativize path, snip current directory + separating '/'.
buildDir = buildDir.substring(currentDirectory.length + 1);
}
status.stop(); status.stop();
return '$buildDir/outputs/apk'; return project;
} catch (e) { } catch (e) {
printError('Error running gradle: $e'); printError('Error running gradle: $e');
} }
// Fall back to the default // Fall back to the default
return gradleAppOutDirV1; return new GradleProject(<String>['debug', 'profile', 'release'], <String>[], gradleAppOutDirV1);
} }
String _locateProjectGradlew({ bool ensureExecutable: true }) { String _locateProjectGradlew({ bool ensureExecutable: true }) {
final String path = fs.path.join( final String path = fs.path.join(
'android', platform.isWindows ? 'gradlew.bat' : 'gradlew' 'android',
platform.isWindows ? 'gradlew.bat' : 'gradlew',
); );
if (fs.isFileSync(path)) { if (fs.isFileSync(path)) {
@ -164,7 +159,7 @@ 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, String buildMode}) { void updateLocalProperties({String projectPath, BuildInfo buildInfo}) {
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'));
@ -183,8 +178,8 @@ void updateLocalProperties({String projectPath, String buildMode}) {
settings.values['flutter.sdk'] = escapedRoot; settings.values['flutter.sdk'] = escapedRoot;
changed = true; changed = true;
} }
if (buildMode != null && settings.values['flutter.buildMode'] != buildMode) { if (buildInfo != null && settings.values['flutter.buildMode'] != buildInfo.modeName) {
settings.values['flutter.buildMode'] = buildMode; settings.values['flutter.buildMode'] = buildInfo.modeName;
changed = true; changed = true;
} }
@ -192,13 +187,12 @@ void updateLocalProperties({String projectPath, String buildMode}) {
settings.writeContents(localProperties); settings.writeContents(localProperties);
} }
Future<Null> buildGradleProject(BuildMode buildMode, String target, String kernelPath) async { Future<Null> buildGradleProject(BuildInfo buildInfo, String target, String kernelPath) async {
// Update the local.properties file with the build mode. // Update the local.properties file with the build mode.
// 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.
final String buildModeName = getModeName(buildMode); updateLocalProperties(buildInfo: buildInfo);
updateLocalProperties(buildMode: buildModeName);
injectPlugins(); injectPlugins();
@ -212,7 +206,7 @@ Future<Null> buildGradleProject(BuildMode buildMode, String target, String kerne
case FlutterPluginVersion.managed: case FlutterPluginVersion.managed:
// Fall through. Managed plugin builds the same way as plugin v2. // Fall through. Managed plugin builds the same way as plugin v2.
case FlutterPluginVersion.v2: case FlutterPluginVersion.v2:
return _buildGradleProjectV2(gradle, buildModeName, target, kernelPath); return _buildGradleProjectV2(gradle, buildInfo, target, kernelPath);
} }
} }
@ -234,21 +228,25 @@ Future<Null> _buildGradleProjectV1(String gradle) async {
printStatus('Built $gradleAppOutV1 (${getSizeAsMB(apkFile.lengthSync())}).'); printStatus('Built $gradleAppOutV1 (${getSizeAsMB(apkFile.lengthSync())}).');
} }
File findApkFile(String buildDirectory, String buildModeName) { Future<Null> _buildGradleProjectV2(String gradle, BuildInfo buildInfo, String target, String kernelPath) async {
final String apkFilename = 'app-$buildModeName.apk'; final GradleProject project = await _gradleProject();
File apkFile = fs.file('$buildDirectory/$apkFilename'); final String assembleTask = project.assembleTaskFor(buildInfo);
if (apkFile.existsSync()) if (assembleTask == null) {
return apkFile; printError('');
apkFile = fs.file('$buildDirectory/$buildModeName/$apkFilename'); printError('The Gradle project does not define a task suitable for the requested build.');
if (apkFile.existsSync()) if (!project.buildTypes.contains(buildInfo.modeName)) {
return apkFile; printError('Review the android/app/build.gradle file and ensure it defines a ${buildInfo.modeName} build type.');
return null; } else {
if (project.productFlavors.isEmpty) {
printError('The android/app/build.gradle file does not define any custom product flavors.');
printError('You cannot use the --flavor option.');
} else {
printError('The android/app/build.gradle file defines product flavors: ${project.productFlavors.join(', ')}');
printError('You must specify a --flavor option to select one of them.');
}
throwToolExit('Gradle build aborted.');
}
} }
Future<Null> _buildGradleProjectV2(String gradle, String buildModeName, String target, String kernelPath) async {
final String assembleTask = "assemble${toTitleCase(buildModeName)}";
// Run 'gradlew assemble<BuildMode>'.
final Status status = logger.startProgress('Running \'gradlew $assembleTask\'...', expectSlowOperation: true); final Status status = logger.startProgress('Running \'gradlew $assembleTask\'...', expectSlowOperation: true);
final String gradlePath = fs.file(gradle).absolute.path; final String gradlePath = fs.file(gradle).absolute.path;
final List<String> command = <String>[gradlePath]; final List<String> command = <String>[gradlePath];
@ -266,7 +264,7 @@ Future<Null> _buildGradleProjectV2(String gradle, String buildModeName, String t
if (kernelPath != null) if (kernelPath != null)
command.add('-Pkernel=$kernelPath'); command.add('-Pkernel=$kernelPath');
command.add(assembleTask); command.add(assembleTask);
final int exitcode = await runCommandAndStreamOutput( final int exitCode = await runCommandAndStreamOutput(
command, command,
workingDirectory: 'android', workingDirectory: 'android',
allowReentrantFlutter: true, allowReentrantFlutter: true,
@ -274,21 +272,33 @@ Future<Null> _buildGradleProjectV2(String gradle, String buildModeName, String t
); );
status.stop(); status.stop();
if (exitcode != 0) if (exitCode != 0)
throwToolExit('Gradle build failed: $exitcode', exitCode: exitcode); throwToolExit('Gradle build failed: $exitCode', exitCode: exitCode);
final String buildDirectory = await _getGradleAppOutDirV2(); final File apkFile = _findApkFile(project, buildInfo);
final File apkFile = findApkFile(buildDirectory, buildModeName);
if (apkFile == null) if (apkFile == null)
throwToolExit('Gradle build failed to produce an Android package.'); throwToolExit('Gradle build failed to produce an Android package.');
// Copy the APK to app.apk, so `flutter run`, `flutter install`, etc. can find it. // Copy the APK to app.apk, so `flutter run`, `flutter install`, etc. can find it.
apkFile.copySync('$buildDirectory/app.apk'); apkFile.copySync(fs.path.join(project.apkDirectory, 'app.apk'));
printTrace('calculateSha: $buildDirectory/app.apk'); printTrace('calculateSha: ${project.apkDirectory}/app.apk');
final File apkShaFile = fs.file('$buildDirectory/app.apk.sha1'); final File apkShaFile = fs.file(fs.path.join(project.apkDirectory, 'app.apk.sha1'));
apkShaFile.writeAsStringSync(calculateSha(apkFile)); apkShaFile.writeAsStringSync(calculateSha(apkFile));
printStatus('Built ${apkFile.path} (${getSizeAsMB(apkFile.lengthSync())}).'); printStatus('Built ${fs.path.relative(apkFile.path)} (${getSizeAsMB(apkFile.lengthSync())}).');
}
File _findApkFile(GradleProject project, BuildInfo buildInfo) {
final String apkFileName = project.apkFileFor(buildInfo);
if (apkFileName == null)
return null;
File apkFile = fs.file(fs.path.join(project.apkDirectory, apkFileName));
if (apkFile.existsSync())
return apkFile;
apkFile = fs.file(fs.path.join(project.apkDirectory, buildInfo.modeName, apkFileName));
if (apkFile.existsSync())
return apkFile;
return null;
} }
Map<String, String> get _gradleEnv { Map<String, String> get _gradleEnv {
@ -299,3 +309,83 @@ Map<String, String> get _gradleEnv {
} }
return env; return env;
} }
class GradleProject {
GradleProject(this.buildTypes, this.productFlavors, this.apkDirectory);
factory GradleProject.fromAppProperties(String properties) {
// Extract build directory.
final String buildDir = properties
.split('\n')
.firstWhere((String s) => s.startsWith('buildDir: '))
.substring('buildDir: '.length)
.trim();
// Extract build types and product flavors.
final Set<String> variants = new Set<String>();
properties.split('\n').forEach((String s) {
final Match match = _assembleTaskPattern.matchAsPrefix(s);
if (match != null) {
final String variant = match.group(1).toLowerCase();
if (!variant.endsWith('test'))
variants.add(variant);
}
});
final Set<String> buildTypes = new Set<String>();
final Set<String> productFlavors = new Set<String>();
for (final String variant1 in variants) {
for (final String variant2 in variants) {
if (variant2.startsWith(variant1) && variant2 != variant1) {
final String buildType = variant2.substring(variant1.length);
if (variants.contains(buildType)) {
buildTypes.add(buildType);
productFlavors.add(variant1);
}
}
}
}
if (productFlavors.isEmpty)
buildTypes.addAll(variants);
return new GradleProject(
buildTypes.toList(),
productFlavors.toList(),
fs.path.normalize(fs.path.join(buildDir, 'outputs', 'apk')),
);
}
final List<String> buildTypes;
final List<String> productFlavors;
final String apkDirectory;
String _buildTypeFor(BuildInfo buildInfo) {
if (buildTypes.contains(buildInfo.modeName))
return buildInfo.modeName;
return null;
}
String _productFlavorFor(BuildInfo buildInfo) {
if (buildInfo.flavor == null)
return productFlavors.isEmpty ? '' : null;
else if (productFlavors.contains(buildInfo.flavor.toLowerCase()))
return buildInfo.flavor.toLowerCase();
else
return null;
}
String assembleTaskFor(BuildInfo buildInfo) {
final String buildType = _buildTypeFor(buildInfo);
final String productFlavor = _productFlavorFor(buildInfo);
if (buildType == null || productFlavor == null)
return null;
return 'assemble${toTitleCase(productFlavor)}${toTitleCase(buildType)}';
}
String apkFileFor(BuildInfo buildInfo) {
final String buildType = _buildTypeFor(buildInfo);
final String productFlavor = _productFlavorFor(buildInfo);
if (buildType == null || productFlavor == null)
return null;
final String flavorString = productFlavor.isEmpty ? '' : '-' + productFlavor;
return 'app$flavorString-$buildType.apk';
}
}

View File

@ -228,7 +228,7 @@ class BuildableIOSApp extends IOSApp {
bool get isSwift => buildSettings?.containsKey('SWIFT_VERSION'); bool get isSwift => buildSettings?.containsKey('SWIFT_VERSION');
String _buildAppPath(String type) { String _buildAppPath(String type) {
return fs.path.join(getIosBuildDirectory(), 'Release-$type', kBundleName); return fs.path.join(getIosBuildDirectory(), type, kBundleName);
} }
} }

View File

@ -8,10 +8,42 @@ import 'base/platform.dart';
import 'base/utils.dart'; import 'base/utils.dart';
import 'globals.dart'; import 'globals.dart';
enum BuildType { /// Information about a build to be performed or used.
prebuilt, class BuildInfo {
release, const BuildInfo(this.mode, this.flavor);
debug,
final BuildMode mode;
/// Represents a custom Android product flavor or an Xcode scheme, null for
/// using the default.
///
/// If not null, the Gradle build task will be `assembleFlavorMode` (e.g.
/// `assemblePaidRelease`), and the Xcode build configuration will be
/// Mode-Flavor (e.g. Release-Paid).
final String flavor;
static const BuildInfo debug = const BuildInfo(BuildMode.debug, null);
static const BuildInfo profile = const BuildInfo(BuildMode.profile, null);
static const BuildInfo release = const BuildInfo(BuildMode.release, null);
/// Returns whether a debug build is requested.
///
/// Exactly one of [isDebug], [isProfile], or [isRelease] is true.
bool get isDebug => mode == BuildMode.debug;
/// Returns whether a profile build is requested.
///
/// Exactly one of [isDebug], [isProfile], or [isRelease] is true.
bool get isProfile => mode == BuildMode.profile;
/// Returns whether a release build is requested.
///
/// Exactly one of [isDebug], [isProfile], or [isRelease] is true.
bool get isRelease => mode == BuildMode.release;
bool get usesAot => isAotBuildMode(mode);
bool get supportsEmulator => isEmulatorBuildMode(mode);
bool get supportsSimulator => isEmulatorBuildMode(mode);
String get modeName => getModeName(mode);
} }
/// The type of build - `debug`, `profile`, or `release`. /// The type of build - `debug`, `profile`, or `release`.

View File

@ -35,6 +35,7 @@ class BuildApkCommand extends BuildSubCommand {
BuildApkCommand() { BuildApkCommand() {
usesTargetOption(); usesTargetOption();
addBuildModeFlags(); addBuildModeFlags();
usesFlavorOption();
usesPubOption(); usesPubOption();
} }
@ -51,14 +52,14 @@ class BuildApkCommand extends BuildSubCommand {
Future<Null> runCommand() async { Future<Null> runCommand() async {
await super.runCommand(); await super.runCommand();
final BuildMode buildMode = getBuildMode(); final BuildInfo buildInfo = getBuildInfo();
await buildApk(buildMode: buildMode, target: targetFile); await buildApk(buildInfo: buildInfo, target: targetFile);
} }
} }
Future<Null> buildApk({ Future<Null> buildApk({
String target, String target,
BuildMode buildMode: BuildMode.debug, BuildInfo buildInfo: BuildInfo.debug,
String kernelPath, String kernelPath,
}) async { }) async {
if (!isProjectUsingGradle()) { if (!isProjectUsingGradle()) {
@ -80,5 +81,5 @@ Future<Null> buildApk({
throwToolExit('Try re-installing or updating your Android SDK.'); throwToolExit('Try re-installing or updating your Android SDK.');
} }
return buildGradleProject(buildMode, target, kernelPath); return buildGradleProject(buildInfo, target, kernelPath);
} }

View File

@ -15,6 +15,7 @@ import 'build.dart';
class BuildIOSCommand extends BuildSubCommand { class BuildIOSCommand extends BuildSubCommand {
BuildIOSCommand() { BuildIOSCommand() {
usesTargetOption(); usesTargetOption();
usesFlavorOption();
usesPubOption(); usesPubOption();
argParser.addFlag('debug', argParser.addFlag('debug',
negatable: false, negatable: false,
@ -56,17 +57,17 @@ class BuildIOSCommand extends BuildSubCommand {
printStatus('Warning: Building for device with codesigning disabled. You will ' printStatus('Warning: Building for device with codesigning disabled. You will '
'have to manually codesign before deploying to device.'); 'have to manually codesign before deploying to device.');
} }
final BuildInfo buildInfo = getBuildInfo();
if (forSimulator && !isEmulatorBuildMode(getBuildMode())) if (forSimulator && !buildInfo.supportsSimulator)
throwToolExit('${toTitleCase(getModeName(getBuildMode()))} mode is not supported for emulators.'); throwToolExit('${toTitleCase(buildInfo.modeName)} mode is not supported for simulators.');
final String logTarget = forSimulator ? 'simulator' : 'device'; final String logTarget = forSimulator ? 'simulator' : 'device';
final String typeName = artifacts.getEngineType(TargetPlatform.ios, getBuildMode()); final String typeName = artifacts.getEngineType(TargetPlatform.ios, buildInfo.mode);
printStatus('Building $app for $logTarget ($typeName)...'); printStatus('Building $app for $logTarget ($typeName)...');
final XcodeBuildResult result = await buildXcodeProject( final XcodeBuildResult result = await buildXcodeProject(
app: app, app: app,
mode: getBuildMode(), buildInfo: buildInfo,
target: targetFile, target: targetFile,
buildForDevice: !forSimulator, buildForDevice: !forSimulator,
codesign: shouldCodesign codesign: shouldCodesign

View File

@ -182,7 +182,7 @@ class CreateCommand extends FlutterCommand {
updateXcodeGeneratedProperties( updateXcodeGeneratedProperties(
projectPath: appPath, projectPath: appPath,
mode: BuildMode.debug, buildInfo: BuildInfo.debug,
target: flx.defaultMainPath, target: flx.defaultMainPath,
hasPlugins: generatePlugin, hasPlugins: generatePlugin,
); );

View File

@ -302,6 +302,7 @@ class AppDomain extends Domain {
final bool useTestFonts = _getBoolArg(args, 'useTestFonts') ?? false; final bool useTestFonts = _getBoolArg(args, 'useTestFonts') ?? false;
final String route = _getStringArg(args, 'route'); final String route = _getStringArg(args, 'route');
final String mode = _getStringArg(args, 'mode'); final String mode = _getStringArg(args, 'mode');
final String flavor = _getStringArg(args, 'flavor');
final String target = _getStringArg(args, 'target'); final String target = _getStringArg(args, 'target');
final bool enableHotReload = _getBoolArg(args, 'hot') ?? kHotReloadDefault; final bool enableHotReload = _getBoolArg(args, 'hot') ?? kHotReloadDefault;
@ -312,13 +313,13 @@ class AppDomain extends Domain {
if (!fs.isDirectorySync(projectDirectory)) if (!fs.isDirectorySync(projectDirectory))
throw "'$projectDirectory' does not exist"; throw "'$projectDirectory' does not exist";
final BuildMode buildMode = getBuildModeForName(mode) ?? BuildMode.debug; final BuildInfo buildInfo = new BuildInfo(getBuildModeForName(mode) ?? BuildMode.debug, flavor);
DebuggingOptions options; DebuggingOptions options;
if (buildMode == BuildMode.release) { if (buildInfo.isRelease) {
options = new DebuggingOptions.disabled(buildMode); options = new DebuggingOptions.disabled(buildInfo);
} else { } else {
options = new DebuggingOptions.enabled( options = new DebuggingOptions.enabled(
buildMode, buildInfo,
startPaused: startPaused, startPaused: startPaused,
useTestFonts: useTestFonts, useTestFonts: useTestFonts,
); );
@ -349,8 +350,8 @@ class AppDomain extends Domain {
String packagesFilePath, String packagesFilePath,
String projectAssets, String projectAssets,
}) async { }) async {
if (await device.isLocalEmulator && !isEmulatorBuildMode(options.buildMode)) if (await device.isLocalEmulator && !options.buildInfo.supportsEmulator)
throw '${toTitleCase(getModeName(options.buildMode))} mode is not supported for emulators.'; throw '${toTitleCase(options.buildInfo.modeName)} mode is not supported for emulators.';
// We change the current working directory for the duration of the `start` command. // We change the current working directory for the duration of the `start` command.
final Directory cwd = fs.currentDirectory; final Directory cwd = fs.currentDirectory;

View File

@ -8,7 +8,6 @@ import '../application_package.dart';
import '../base/common.dart'; import '../base/common.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/process.dart'; import '../base/process.dart';
import '../build_info.dart';
import '../cache.dart'; import '../cache.dart';
import '../dart/package_map.dart'; import '../dart/package_map.dart';
import '../dart/sdk.dart'; import '../dart/sdk.dart';
@ -28,7 +27,7 @@ import 'run.dart';
/// as the `--target` option (defaults to `lib/main.dart`). It then looks for a /// as the `--target` option (defaults to `lib/main.dart`). It then looks for a
/// corresponding test file within the `test_driver` directory. The test file is /// corresponding test file within the `test_driver` directory. The test file is
/// expected to have the same name but contain the `_test.dart` suffix. The /// expected to have the same name but contain the `_test.dart` suffix. The
/// `_test.dart` file would generall be a Dart program that uses /// `_test.dart` file would generally be a Dart program that uses
/// `package:flutter_driver` and exercises your application. Most commonly it /// `package:flutter_driver` and exercises your application. Most commonly it
/// is a test written using `package:test`, but you are free to use something /// is a test written using `package:test`, but you are free to use something
/// else. /// else.
@ -113,7 +112,7 @@ class DriveCommand extends RunCommandBase {
if (argResults['use-existing-app'] == null) { if (argResults['use-existing-app'] == null) {
printStatus('Starting application: $targetFile'); printStatus('Starting application: $targetFile');
if (getBuildMode() == BuildMode.release) { if (getBuildInfo().isRelease) {
// This is because we need VM service to be able to drive the app. // This is because we need VM service to be able to drive the app.
throwToolExit( throwToolExit(
'Flutter Driver does not support running in release mode.\n' 'Flutter Driver does not support running in release mode.\n'
@ -267,11 +266,10 @@ Future<LaunchResult> _startApp(DriveCommand command) async {
final LaunchResult result = await command.device.startApp( final LaunchResult result = await command.device.startApp(
package, package,
command.getBuildMode(),
mainPath: mainPath, mainPath: mainPath,
route: command.route, route: command.route,
debuggingOptions: new DebuggingOptions.enabled( debuggingOptions: new DebuggingOptions.enabled(
command.getBuildMode(), command.getBuildInfo(),
startPaused: true, startPaused: true,
observatoryPort: command.observatoryPort, observatoryPort: command.observatoryPort,
diagnosticPort: command.diagnosticPort, diagnosticPort: command.diagnosticPort,

View File

@ -122,7 +122,7 @@ class FuchsiaReloadCommand extends FlutterCommand {
flutterDevice.observatoryUris = observatoryUris; flutterDevice.observatoryUris = observatoryUris;
final HotRunner hotRunner = new HotRunner( final HotRunner hotRunner = new HotRunner(
<FlutterDevice>[flutterDevice], <FlutterDevice>[flutterDevice],
debuggingOptions: new DebuggingOptions.enabled(getBuildMode()), debuggingOptions: new DebuggingOptions.enabled(getBuildInfo()),
target: _target, target: _target,
projectRootPath: _fuchsiaProjectPath, projectRootPath: _fuchsiaProjectPath,
packagesFilePath: _dotPackagesPath packagesFilePath: _dotPackagesPath

View File

@ -23,6 +23,7 @@ abstract class RunCommandBase extends FlutterCommand {
// Used by run and drive commands. // Used by run and drive commands.
RunCommandBase() { RunCommandBase() {
addBuildModeFlags(defaultToRelease: false); addBuildModeFlags(defaultToRelease: false);
usesFlavorOption();
argParser.addFlag('trace-startup', argParser.addFlag('trace-startup',
negatable: true, negatable: true,
defaultsTo: false, defaultsTo: false,
@ -208,7 +209,7 @@ class RunCommand extends RunCommandBase {
bool shouldUseHotMode() { bool shouldUseHotMode() {
final bool hotArg = argResults['hot'] ?? false; final bool hotArg = argResults['hot'] ?? false;
final bool shouldUseHotMode = hotArg; final bool shouldUseHotMode = hotArg;
return (getBuildMode() == BuildMode.debug) && shouldUseHotMode; return getBuildInfo().isDebug && shouldUseHotMode;
} }
bool get runningWithPrebuiltApplication => bool get runningWithPrebuiltApplication =>
@ -228,11 +229,12 @@ class RunCommand extends RunCommandBase {
} }
DebuggingOptions _createDebuggingOptions() { DebuggingOptions _createDebuggingOptions() {
if (getBuildMode() == BuildMode.release) { final BuildInfo buildInfo = getBuildInfo();
return new DebuggingOptions.disabled(getBuildMode()); if (buildInfo.isRelease) {
return new DebuggingOptions.disabled(buildInfo);
} else { } else {
return new DebuggingOptions.enabled( return new DebuggingOptions.enabled(
getBuildMode(), buildInfo,
startPaused: argResults['start-paused'], startPaused: argResults['start-paused'],
useTestFonts: argResults['use-test-fonts'], useTestFonts: argResults['use-test-fonts'],
enableSoftwareRendering: argResults['enable-software-rendering'], enableSoftwareRendering: argResults['enable-software-rendering'],

View File

@ -235,8 +235,7 @@ abstract class Device {
/// for iOS device deployment. Set to false if stdin cannot be read from while /// for iOS device deployment. Set to false if stdin cannot be read from while
/// attempting to start the app. /// attempting to start the app.
Future<LaunchResult> startApp( Future<LaunchResult> startApp(
ApplicationPackage package, ApplicationPackage package, {
BuildMode mode, {
String mainPath, String mainPath,
String route, String route,
DebuggingOptions debuggingOptions, DebuggingOptions debuggingOptions,
@ -316,7 +315,7 @@ abstract class Device {
} }
class DebuggingOptions { class DebuggingOptions {
DebuggingOptions.enabled(this.buildMode, { DebuggingOptions.enabled(this.buildInfo, {
this.startPaused: false, this.startPaused: false,
this.enableSoftwareRendering: false, this.enableSoftwareRendering: false,
this.useTestFonts: false, this.useTestFonts: false,
@ -324,7 +323,7 @@ class DebuggingOptions {
this.diagnosticPort this.diagnosticPort
}) : debuggingEnabled = true; }) : debuggingEnabled = true;
DebuggingOptions.disabled(this.buildMode) : DebuggingOptions.disabled(this.buildInfo) :
debuggingEnabled = false, debuggingEnabled = false,
useTestFonts = false, useTestFonts = false,
startPaused = false, startPaused = false,
@ -334,7 +333,7 @@ class DebuggingOptions {
final bool debuggingEnabled; final bool debuggingEnabled;
final BuildMode buildMode; final BuildInfo buildInfo;
final bool startPaused; final bool startPaused;
final bool enableSoftwareRendering; final bool enableSoftwareRendering;
final bool useTestFonts; final bool useTestFonts;

View File

@ -59,8 +59,7 @@ class FuchsiaDevice extends Device {
@override @override
Future<LaunchResult> startApp( Future<LaunchResult> startApp(
ApplicationPackage app, ApplicationPackage app, {
BuildMode mode, {
String mainPath, String mainPath,
String route, String route,
DebuggingOptions debuggingOptions, DebuggingOptions debuggingOptions,

View File

@ -164,8 +164,7 @@ class IOSDevice extends Device {
@override @override
Future<LaunchResult> startApp( Future<LaunchResult> startApp(
ApplicationPackage app, ApplicationPackage app, {
BuildMode mode, {
String mainPath, String mainPath,
String route, String route,
DebuggingOptions debuggingOptions, DebuggingOptions debuggingOptions,
@ -182,7 +181,7 @@ class IOSDevice extends Device {
// Step 1: Build the precompiled/DBC application if necessary. // Step 1: Build the precompiled/DBC application if necessary.
final XcodeBuildResult buildResult = await buildXcodeProject( final XcodeBuildResult buildResult = await buildXcodeProject(
app: app, app: app,
mode: mode, buildInfo: debuggingOptions.buildInfo,
target: mainPath, target: mainPath,
buildForDevice: true, buildForDevice: true,
usesTerminalUi: usesTerminalUi, usesTerminalUi: usesTerminalUi,
@ -267,7 +266,7 @@ class IOSDevice extends Device {
final Future<Uri> forwardObservatoryUri = observatoryDiscovery.uri; final Future<Uri> forwardObservatoryUri = observatoryDiscovery.uri;
Future<Uri> forwardDiagnosticUri; Future<Uri> forwardDiagnosticUri;
if (debuggingOptions.buildMode == BuildMode.debug) { if (debuggingOptions.buildInfo.isDebug) {
forwardDiagnosticUri = diagnosticDiscovery.uri; forwardDiagnosticUri = diagnosticDiscovery.uri;
} else { } else {
forwardDiagnosticUri = new Future<Uri>.value(null); forwardDiagnosticUri = new Future<Uri>.value(null);

View File

@ -220,7 +220,7 @@ bool _xcodeVersionCheckValid(int major, int minor) {
Future<XcodeBuildResult> buildXcodeProject({ Future<XcodeBuildResult> buildXcodeProject({
BuildableIOSApp app, BuildableIOSApp app,
BuildMode mode, BuildInfo buildInfo,
String target: flx.defaultMainPath, String target: flx.defaultMainPath,
bool buildForDevice, bool buildForDevice,
bool codesign: true, bool codesign: true,
@ -234,6 +234,35 @@ Future<XcodeBuildResult> buildXcodeProject({
return new XcodeBuildResult(success: false); return new XcodeBuildResult(success: false);
} }
final XcodeProjectInfo projectInfo = new XcodeProjectInfo.fromProjectSync(app.appDirectory);
if (!projectInfo.targets.contains('Runner')) {
printError('The Xcode project does not define target "Runner" which is needed by Flutter tooling.');
printError('Open Xcode to fix the problem:');
printError(' open ios/Runner.xcworkspace');
return new XcodeBuildResult(success: false);
}
final String scheme = projectInfo.schemeFor(buildInfo);
if (scheme == null) {
printError('');
if (projectInfo.definesCustomSchemes) {
printError('The Xcode project defines schemes: ${projectInfo.schemes.join(', ')}');
printError('You must specify a --flavor option to select one of them.');
} else {
printError('The Xcode project does not define custom schemes.');
printError('You cannot use the --flavor option.');
}
return new XcodeBuildResult(success: false);
}
final String configuration = projectInfo.buildConfigurationFor(buildInfo, scheme);
if (configuration == null) {
printError('');
printError('The Xcode project defines build configurations: ${projectInfo.buildConfigurations.join(', ')}');
printError('Flutter expects a build configuration named ${XcodeProjectInfo.expectedBuildConfigurationFor(buildInfo, scheme)} or similar.');
printError('Open Xcode to fix the problem:');
printError(' open ios/Runner.xcworkspace');
return new XcodeBuildResult(success: false);
}
String developmentTeam; String developmentTeam;
if (codesign && buildForDevice) if (codesign && buildForDevice)
developmentTeam = await getCodeSigningIdentityDevelopmentTeam(iosApp: app, usesTerminalUi: usesTerminalUi); developmentTeam = await getCodeSigningIdentityDevelopmentTeam(iosApp: app, usesTerminalUi: usesTerminalUi);
@ -247,13 +276,13 @@ Future<XcodeBuildResult> buildXcodeProject({
if (hasFlutterPlugins) if (hasFlutterPlugins)
await cocoaPods.processPods( await cocoaPods.processPods(
appIosDir: appDirectory, appIosDir: appDirectory,
iosEngineDir: flutterFrameworkDir(mode), iosEngineDir: flutterFrameworkDir(buildInfo.mode),
isSwift: app.isSwift, isSwift: app.isSwift,
); );
updateXcodeGeneratedProperties( updateXcodeGeneratedProperties(
projectPath: fs.currentDirectory.path, projectPath: fs.currentDirectory.path,
mode: mode, buildInfo: buildInfo,
target: target, target: target,
hasPlugins: hasFlutterPlugins hasPlugins: hasFlutterPlugins
); );
@ -264,7 +293,8 @@ Future<XcodeBuildResult> buildXcodeProject({
'xcodebuild', 'xcodebuild',
'clean', 'clean',
'build', 'build',
'-configuration', 'Release', '-configuration', configuration,
'-scheme', scheme,
'ONLY_ACTIVE_ARCH=YES', 'ONLY_ACTIVE_ARCH=YES',
]; ];
@ -276,7 +306,6 @@ Future<XcodeBuildResult> buildXcodeProject({
if (fs.path.extension(entity.path) == '.xcworkspace') { if (fs.path.extension(entity.path) == '.xcworkspace') {
commands.addAll(<String>[ commands.addAll(<String>[
'-workspace', fs.path.basename(entity.path), '-workspace', fs.path.basename(entity.path),
'-scheme', fs.path.basenameWithoutExtension(entity.path),
"BUILD_DIR=${fs.path.absolute(getIosBuildDirectory())}", "BUILD_DIR=${fs.path.absolute(getIosBuildDirectory())}",
]); ]);
break; break;
@ -306,7 +335,6 @@ Future<XcodeBuildResult> buildXcodeProject({
allowReentrantFlutter: true allowReentrantFlutter: true
); );
status.stop(); status.stop();
if (result.exitCode != 0) { if (result.exitCode != 0) {
printStatus('Failed to build iOS app'); printStatus('Failed to build iOS app');
if (result.stderr.isNotEmpty) { if (result.stderr.isNotEmpty) {
@ -328,12 +356,17 @@ Future<XcodeBuildResult> buildXcodeProject({
), ),
); );
} else { } else {
// Look for 'clean build/Release-iphoneos/Runner.app'. // Look for 'clean build/<configuration>-<sdk>/Runner.app'.
final RegExp regexp = new RegExp(r' clean (\S*\.app)$', multiLine: true); final RegExp regexp = new RegExp(r' clean (.*\.app)$', multiLine: true);
final Match match = regexp.firstMatch(result.stdout); final Match match = regexp.firstMatch(result.stdout);
String outputDir; String outputDir;
if (match != null) if (match != null) {
outputDir = fs.path.join(app.appDirectory, match.group(1)); final String actualOutputDir = match.group(1).replaceAll('\\ ', ' ');
// Copy app folder to a place where other tools can find it without knowing
// the BuildInfo.
outputDir = actualOutputDir.replaceFirst('/$configuration-', '/');
copyDirectorySync(fs.directory(actualOutputDir), fs.directory(outputDir));
}
return new XcodeBuildResult(success: true, output: outputDir); return new XcodeBuildResult(success: true, output: outputDir);
} }
} }
@ -356,7 +389,9 @@ Future<Null> diagnoseXcodeBuildFailure(XcodeBuildResult result, BuildableIOSApp
printError(noDevelopmentTeamInstruction, emphasis: true); printError(noDevelopmentTeamInstruction, emphasis: true);
return; return;
} }
if (app.id?.contains('com.yourcompany') ?? false) { if (result.xcodeBuildExecution != null &&
result.xcodeBuildExecution.buildForPhysicalDevice &&
app.id?.contains('com.yourcompany') ?? false) {
printError(''); printError('');
printError('It appears that your application still contains the default signing identifier.'); printError('It appears that your application still contains the default signing identifier.');
printError("Try replacing 'com.yourcompany' with your signing id in Xcode:"); printError("Try replacing 'com.yourcompany' with your signing id in Xcode:");

View File

@ -306,8 +306,7 @@ class IOSSimulator extends Device {
@override @override
Future<LaunchResult> startApp( Future<LaunchResult> startApp(
ApplicationPackage app, ApplicationPackage app, {
BuildMode mode, {
String mainPath, String mainPath,
String route, String route,
DebuggingOptions debuggingOptions, DebuggingOptions debuggingOptions,
@ -321,7 +320,7 @@ class IOSSimulator extends Device {
printTrace('Building ${app.name} for $id.'); printTrace('Building ${app.name} for $id.');
try { try {
await _setupUpdatedApplicationBundle(app); await _setupUpdatedApplicationBundle(app, debuggingOptions.buildInfo.flavor);
} on ToolExit catch (e) { } on ToolExit catch (e) {
printError(e.message); printError(e.message);
return new LaunchResult.failed(); return new LaunchResult.failed();
@ -343,7 +342,7 @@ class IOSSimulator extends Device {
} }
if (debuggingOptions.debuggingEnabled) { if (debuggingOptions.debuggingEnabled) {
if (debuggingOptions.buildMode == BuildMode.debug) if (debuggingOptions.buildInfo.isDebug)
args.add('--enable-checked-mode'); args.add('--enable-checked-mode');
if (debuggingOptions.startPaused) if (debuggingOptions.startPaused)
args.add('--start-paused'); args.add('--start-paused');
@ -395,17 +394,17 @@ class IOSSimulator extends Device {
return criteria.reduce((bool a, bool b) => a && b); return criteria.reduce((bool a, bool b) => a && b);
} }
Future<Null> _setupUpdatedApplicationBundle(ApplicationPackage app) async { Future<Null> _setupUpdatedApplicationBundle(ApplicationPackage app, String flavor) async {
await _sideloadUpdatedAssetsForInstalledApplicationBundle(app); await _sideloadUpdatedAssetsForInstalledApplicationBundle(app);
if (!await _applicationIsInstalledAndRunning(app)) if (!await _applicationIsInstalledAndRunning(app))
return _buildAndInstallApplicationBundle(app); return _buildAndInstallApplicationBundle(app, flavor);
} }
Future<Null> _buildAndInstallApplicationBundle(ApplicationPackage app) async { Future<Null> _buildAndInstallApplicationBundle(ApplicationPackage app, String flavor) async {
// Step 1: Build the Xcode project. // Step 1: Build the Xcode project.
// The build mode for the simulator is always debug. // The build mode for the simulator is always debug.
final XcodeBuildResult buildResult = await buildXcodeProject(app: app, mode: BuildMode.debug, buildForDevice: false); final XcodeBuildResult buildResult = await buildXcodeProject(app: app, buildInfo: new BuildInfo(BuildMode.debug, flavor), buildForDevice: false);
if (!buildResult.success) if (!buildResult.success)
throwToolExit('Could not build the application for the simulator.'); throwToolExit('Could not build the application for the simulator.');

View File

@ -7,6 +7,7 @@ import 'package:meta/meta.dart';
import '../artifacts.dart'; import '../artifacts.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/process.dart'; import '../base/process.dart';
import '../base/utils.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../cache.dart'; import '../cache.dart';
import '../globals.dart'; import '../globals.dart';
@ -20,7 +21,7 @@ String flutterFrameworkDir(BuildMode mode) {
void updateXcodeGeneratedProperties({ void updateXcodeGeneratedProperties({
@required String projectPath, @required String projectPath,
@required BuildMode mode, @required BuildInfo buildInfo,
@required String target, @required String target,
@required bool hasPlugins, @required bool hasPlugins,
}) { }) {
@ -38,21 +39,21 @@ void updateXcodeGeneratedProperties({
localsBuffer.writeln('FLUTTER_TARGET=$target'); localsBuffer.writeln('FLUTTER_TARGET=$target');
// The runtime mode for the current build. // The runtime mode for the current build.
localsBuffer.writeln('FLUTTER_BUILD_MODE=${getModeName(mode)}'); localsBuffer.writeln('FLUTTER_BUILD_MODE=${buildInfo.modeName}');
// The build outputs directory, relative to FLUTTER_APPLICATION_PATH. // The build outputs directory, relative to FLUTTER_APPLICATION_PATH.
localsBuffer.writeln('FLUTTER_BUILD_DIR=${getBuildDirectory()}'); localsBuffer.writeln('FLUTTER_BUILD_DIR=${getBuildDirectory()}');
localsBuffer.writeln('SYMROOT=\${SOURCE_ROOT}/../${getIosBuildDirectory()}'); localsBuffer.writeln('SYMROOT=\${SOURCE_ROOT}/../${getIosBuildDirectory()}');
localsBuffer.writeln('FLUTTER_FRAMEWORK_DIR=${flutterFrameworkDir(mode)}'); localsBuffer.writeln('FLUTTER_FRAMEWORK_DIR=${flutterFrameworkDir(buildInfo.mode)}');
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}');
} }
// Add dependency to CocoaPods' generated project only if plugns are used. // Add dependency to CocoaPods' generated project only if plugins are used.
if (hasPlugins) if (hasPlugins)
localsBuffer.writeln('#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"'); localsBuffer.writeln('#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"');
@ -74,7 +75,6 @@ Map<String, String> getXcodeBuildSettings(String xcodeProjPath, String target) {
return settings; return settings;
} }
/// Substitutes variables in [str] with their values from the specified Xcode /// Substitutes variables in [str] with their values from the specified Xcode
/// project and target. /// project and target.
String substituteXcodeVariables(String str, Map<String, String> xcodeBuildSettings) { String substituteXcodeVariables(String str, Map<String, String> xcodeBuildSettings) {
@ -84,3 +84,109 @@ String substituteXcodeVariables(String str, Map<String, String> xcodeBuildSettin
return str.replaceAllMapped(_varExpr, (Match m) => xcodeBuildSettings[m[1]] ?? m[0]); return str.replaceAllMapped(_varExpr, (Match m) => xcodeBuildSettings[m[1]] ?? m[0]);
} }
/// Information about an Xcode project.
///
/// Represents the output of `xcodebuild -list`.
class XcodeProjectInfo {
XcodeProjectInfo(this.targets, this.buildConfigurations, this.schemes);
factory XcodeProjectInfo.fromProjectSync(String projectPath) {
final String out = runCheckedSync(<String>[
'/usr/bin/xcodebuild', '-list',
], workingDirectory: projectPath);
return new XcodeProjectInfo.fromXcodeBuildOutput(out);
}
factory XcodeProjectInfo.fromXcodeBuildOutput(String output) {
final List<String> targets = <String>[];
final List<String> buildConfigurations = <String>[];
final List<String> schemes = <String>[];
List<String> collector;
for (String line in output.split('\n')) {
if (line.isEmpty) {
collector = null;
continue;
} else if (line.endsWith('Targets:')) {
collector = targets;
continue;
} else if (line.endsWith('Build Configurations:')) {
collector = buildConfigurations;
continue;
} else if (line.endsWith('Schemes:')) {
collector = schemes;
continue;
}
collector?.add(line.trim());
}
return new XcodeProjectInfo(targets, buildConfigurations, schemes);
}
final List<String> targets;
final List<String> buildConfigurations;
final List<String> schemes;
bool get definesCustomTargets => !(targets.contains('Runner') && targets.length == 1);
bool get definesCustomSchemes => !(schemes.contains('Runner') && schemes.length == 1);
bool get definesCustomBuildConfigurations {
return !(buildConfigurations.contains('Debug') &&
buildConfigurations.contains('Release') &&
buildConfigurations.length == 2);
}
/// The expected scheme for [buildInfo].
static String expectedSchemeFor(BuildInfo buildInfo) {
return toTitleCase(buildInfo.flavor ?? 'runner');
}
/// The expected build configuration for [buildInfo] and [scheme].
static String expectedBuildConfigurationFor(BuildInfo buildInfo, String scheme) {
final String baseConfiguration = _baseConfigurationFor(buildInfo);
if (buildInfo.flavor == null)
return baseConfiguration;
else
return baseConfiguration + '-$scheme';
}
/// Returns unique scheme matching [buildInfo], or null, if there is no unique
/// best match.
String schemeFor(BuildInfo buildInfo) {
final String expectedScheme = expectedSchemeFor(buildInfo);
if (schemes.contains(expectedScheme))
return expectedScheme;
return _uniqueMatch(schemes, (String candidate) {
return candidate.toLowerCase() == expectedScheme.toLowerCase();
});
}
/// Returns unique build configuration matching [buildInfo] and [scheme], or
/// null, if there is no unique best match.
String buildConfigurationFor(BuildInfo buildInfo, String scheme) {
final String expectedConfiguration = expectedBuildConfigurationFor(buildInfo, scheme);
if (buildConfigurations.contains(expectedConfiguration))
return expectedConfiguration;
final String baseConfiguration = _baseConfigurationFor(buildInfo);
return _uniqueMatch(buildConfigurations, (String candidate) {
candidate = candidate.toLowerCase();
if (buildInfo.flavor == null)
return candidate == expectedConfiguration.toLowerCase();
else
return candidate.contains(baseConfiguration.toLowerCase()) && candidate.contains(scheme.toLowerCase());
});
}
static String _baseConfigurationFor(BuildInfo buildInfo) => buildInfo.isDebug ? 'Debug' : 'Release';
static String _uniqueMatch(Iterable<String> strings, bool matches(String s)) {
final List<String> options = strings.where(matches).toList();
if (options.length == 1)
return options.first;
else
return null;
}
@override
String toString() {
return 'XcodeProjectInfo($targets, $buildConfigurations, $schemes)';
}
}

View File

@ -208,7 +208,7 @@ class FlutterDevice {
bool shouldBuild, bool shouldBuild,
}) async { }) async {
final bool prebuiltMode = hotRunner.applicationBinary != null; final bool prebuiltMode = hotRunner.applicationBinary != null;
final String modeName = getModeName(hotRunner.debuggingOptions.buildMode); final String modeName = hotRunner.debuggingOptions.buildInfo.modeName;
printStatus('Launching ${getDisplayPath(hotRunner.mainPath)} on ${device.name} in $modeName mode...'); printStatus('Launching ${getDisplayPath(hotRunner.mainPath)} on ${device.name} in $modeName mode...');
final TargetPlatform targetPlatform = await device.targetPlatform; final TargetPlatform targetPlatform = await device.targetPlatform;
@ -234,7 +234,6 @@ class FlutterDevice {
final bool hasDirtyDependencies = hotRunner.hasDirtyDependencies(this); final bool hasDirtyDependencies = hotRunner.hasDirtyDependencies(this);
final Future<LaunchResult> futureResult = device.startApp( final Future<LaunchResult> futureResult = device.startApp(
package, package,
hotRunner.debuggingOptions.buildMode,
mainPath: hotRunner.mainPath, mainPath: hotRunner.mainPath,
debuggingOptions: hotRunner.debuggingOptions, debuggingOptions: hotRunner.debuggingOptions,
platformArgs: platformArgs, platformArgs: platformArgs,
@ -268,7 +267,7 @@ class FlutterDevice {
applicationBinary: coldRunner.applicationBinary applicationBinary: coldRunner.applicationBinary
); );
final String modeName = getModeName(coldRunner.debuggingOptions.buildMode); final String modeName = coldRunner.debuggingOptions.buildInfo.modeName;
final bool prebuiltMode = coldRunner.applicationBinary != null; final bool prebuiltMode = coldRunner.applicationBinary != null;
if (coldRunner.mainPath == null) { if (coldRunner.mainPath == null) {
assert(prebuiltMode); assert(prebuiltMode);
@ -295,7 +294,6 @@ class FlutterDevice {
final bool hasDirtyDependencies = coldRunner.hasDirtyDependencies(this); final bool hasDirtyDependencies = coldRunner.hasDirtyDependencies(this);
final LaunchResult result = await device.startApp( final LaunchResult result = await device.startApp(
package, package,
coldRunner.debuggingOptions.buildMode,
mainPath: coldRunner.mainPath, mainPath: coldRunner.mainPath,
debuggingOptions: coldRunner.debuggingOptions, debuggingOptions: coldRunner.debuggingOptions,
platformArgs: platformArgs, platformArgs: platformArgs,
@ -378,9 +376,9 @@ abstract class ResidentRunner {
AssetBundle _assetBundle; AssetBundle _assetBundle;
AssetBundle get assetBundle => _assetBundle; AssetBundle get assetBundle => _assetBundle;
bool get isRunningDebug => debuggingOptions.buildMode == BuildMode.debug; bool get isRunningDebug => debuggingOptions.buildInfo.isDebug;
bool get isRunningProfile => debuggingOptions.buildMode == BuildMode.profile; bool get isRunningProfile => debuggingOptions.buildInfo.isProfile;
bool get isRunningRelease => debuggingOptions.buildMode == BuildMode.release; bool get isRunningRelease => debuggingOptions.buildInfo.isRelease;
bool get supportsServiceProtocol => isRunningDebug || isRunningProfile; bool get supportsServiceProtocol => isRunningDebug || isRunningProfile;
/// Start the app and keep the process running during its lifetime. /// Start the app and keep the process running during its lifetime.

View File

@ -133,6 +133,19 @@ abstract class FlutterCommand extends Command<Null> {
return _defaultBuildMode; return _defaultBuildMode;
} }
void usesFlavorOption() {
argParser.addOption(
'flavor',
help: 'Build a custom app flavor as defined by platform-specific build setup.\n'
'Supports the use of product flavors in Android Gradle scripts.\n'
'Supports the use of custom Xcode schemes.'
);
}
BuildInfo getBuildInfo() {
return new BuildInfo(getBuildMode(), argResults['flavor']);
}
void setupApplicationPackages() { void setupApplicationPackages() {
applicationPackages ??= new ApplicationPackageStore(); applicationPackages ??= new ApplicationPackageStore();
} }

View File

@ -2,33 +2,89 @@
// 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 'package:file/file.dart';
import 'package:file/memory.dart';
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/build_info.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
import '../src/context.dart';
const String _kBuildDirectory = '/build/app/outputs'; const String _kBuildDirectory = '/build/app/outputs';
void main() { void main() {
FileSystem fs; group('gradle project', () {
GradleProject projectFrom(String properties) => new GradleProject.fromAppProperties(properties);
setUp(() { test('should extract build directory from app properties', () {
fs = new MemoryFileSystem(); final GradleProject project = projectFrom('''
fs.directory('$_kBuildDirectory/release').createSync(recursive: true); someProperty: someValue
fs.file('$_kBuildDirectory/app-debug.apk').createSync(); buildDir: /Users/some/apps/hello/build/app
fs.file('$_kBuildDirectory/release/app-release.apk').createSync(); someOtherProperty: someOtherValue
''');
expect(project.apkDirectory, fs.path.normalize('/Users/some/apps/hello/build/app/outputs/apk'));
}); });
test('should extract default build variants from app properties', () {
group('gradle', () { final GradleProject project = projectFrom('''
testUsingContext('findApkFile', () { someProperty: someValue
expect(findApkFile(_kBuildDirectory, 'debug').path, assemble: task ':app:assemble'
'/build/app/outputs/app-debug.apk'); assembleAndroidTest: task ':app:assembleAndroidTest'
expect(findApkFile(_kBuildDirectory, 'release').path, assembleDebug: task ':app:assembleDebug'
'/build/app/outputs/release/app-release.apk'); assembleProfile: task ':app:assembleProfile'
}, overrides: <Type, Generator>{ assembleRelease: task ':app:assembleRelease'
FileSystem: () => fs, buildDir: /Users/some/apps/hello/build/app
someOtherProperty: someOtherValue
''');
expect(project.buildTypes, <String>['debug', 'profile', 'release']);
expect(project.productFlavors, isEmpty);
});
test('should extract custom build variants from app properties', () {
final GradleProject project = projectFrom('''
someProperty: someValue
assemble: task ':app:assemble'
assembleAndroidTest: task ':app:assembleAndroidTest'
assembleDebug: task ':app:assembleDebug'
assembleFree: task ':app:assembleFree'
assembleFreeAndroidTest: task ':app:assembleFreeAndroidTest'
assembleFreeDebug: task ':app:assembleFreeDebug'
assembleFreeProfile: task ':app:assembleFreeProfile'
assembleFreeRelease: task ':app:assembleFreeRelease'
assemblePaid: task ':app:assemblePaid'
assemblePaidAndroidTest: task ':app:assemblePaidAndroidTest'
assemblePaidDebug: task ':app:assemblePaidDebug'
assemblePaidProfile: task ':app:assemblePaidProfile'
assemblePaidRelease: task ':app:assemblePaidRelease'
assembleProfile: task ':app:assembleProfile'
assembleRelease: task ':app:assembleRelease'
buildDir: /Users/some/apps/hello/build/app
someOtherProperty: someOtherValue
''');
expect(project.buildTypes, <String>['debug', 'profile', 'release']);
expect(project.productFlavors, <String>['free', 'paid']);
});
test('should provide apk file name for default build types', () {
final GradleProject project = new GradleProject(<String>['debug', 'profile', 'release'], <String>[], '/some/dir');
expect(project.apkFileFor(BuildInfo.debug), 'app-debug.apk');
expect(project.apkFileFor(BuildInfo.profile), 'app-profile.apk');
expect(project.apkFileFor(BuildInfo.release), 'app-release.apk');
expect(project.apkFileFor(const BuildInfo(BuildMode.release, 'unknown')), isNull);
});
test('should provide apk file name for flavored build types', () {
final GradleProject project = new GradleProject(<String>['debug', 'profile', 'release'], <String>['free', 'paid'], '/some/dir');
expect(project.apkFileFor(const BuildInfo(BuildMode.debug, 'free')), 'app-free-debug.apk');
expect(project.apkFileFor(const BuildInfo(BuildMode.release, 'paid')), 'app-paid-release.apk');
expect(project.apkFileFor(const BuildInfo(BuildMode.release, 'unknown')), isNull);
});
test('should provide assemble task name for default build types', () {
final GradleProject project = new GradleProject(<String>['debug', 'profile', 'release'], <String>[], '/some/dir');
expect(project.assembleTaskFor(BuildInfo.debug), 'assembleDebug');
expect(project.assembleTaskFor(BuildInfo.profile), 'assembleProfile');
expect(project.assembleTaskFor(BuildInfo.release), 'assembleRelease');
expect(project.assembleTaskFor(const BuildInfo(BuildMode.release, 'unknown')), isNull);
});
test('should provide assemble task name for flavored build types', () {
final GradleProject project = new GradleProject(<String>['debug', 'profile', 'release'], <String>['free', 'paid'], '/some/dir');
expect(project.assembleTaskFor(const BuildInfo(BuildMode.debug, 'free')), 'assembleFreeDebug');
expect(project.assembleTaskFor(const BuildInfo(BuildMode.release, 'paid')), 'assemblePaidRelease');
expect(project.assembleTaskFor(const BuildInfo(BuildMode.release, 'unknown')), isNull);
}); });
}); });
} }

View File

@ -0,0 +1,120 @@
import 'package:test/test.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/ios/xcodeproj.dart';
void main() {
group('Xcode project properties', () {
test('properties from default project can be parsed', () {
final String output = '''
Information about project "Runner":
Targets:
Runner
Build Configurations:
Debug
Release
If no build configuration is specified and -scheme is not passed then "Release" is used.
Schemes:
Runner
''';
final XcodeProjectInfo info = new XcodeProjectInfo.fromXcodeBuildOutput(output);
expect(info.targets, <String>['Runner']);
expect(info.schemes, <String>['Runner']);
expect(info.buildConfigurations, <String>['Debug', 'Release']);
});
test('properties from project with custom schemes can be parsed', () {
final String output = '''
Information about project "Runner":
Targets:
Runner
Build Configurations:
Debug (Free)
Debug (Paid)
Release (Free)
Release (Paid)
If no build configuration is specified and -scheme is not passed then "Release (Free)" is used.
Schemes:
Free
Paid
''';
final XcodeProjectInfo info = new XcodeProjectInfo.fromXcodeBuildOutput(output);
expect(info.targets, <String>['Runner']);
expect(info.schemes, <String>['Free', 'Paid']);
expect(info.buildConfigurations, <String>['Debug (Free)', 'Debug (Paid)', 'Release (Free)', 'Release (Paid)']);
});
test('expected scheme for non-flavored build is Runner', () {
expect(XcodeProjectInfo.expectedSchemeFor(BuildInfo.debug), 'Runner');
expect(XcodeProjectInfo.expectedSchemeFor(BuildInfo.profile), 'Runner');
expect(XcodeProjectInfo.expectedSchemeFor(BuildInfo.release), 'Runner');
});
test('expected build configuration for non-flavored build is derived from BuildMode', () {
expect(XcodeProjectInfo.expectedBuildConfigurationFor(BuildInfo.debug, 'Runner'), 'Debug');
expect(XcodeProjectInfo.expectedBuildConfigurationFor(BuildInfo.profile, 'Runner'), 'Release');
expect(XcodeProjectInfo.expectedBuildConfigurationFor(BuildInfo.release, 'Runner'), 'Release');
});
test('expected scheme for flavored build is the title-cased flavor', () {
expect(XcodeProjectInfo.expectedSchemeFor(const BuildInfo(BuildMode.debug, 'hello')), 'Hello');
expect(XcodeProjectInfo.expectedSchemeFor(const BuildInfo(BuildMode.profile, 'HELLO')), 'HELLO');
expect(XcodeProjectInfo.expectedSchemeFor(const BuildInfo(BuildMode.release, 'Hello')), 'Hello');
});
test('expected build configuration for flavored build is Mode-Flavor', () {
expect(XcodeProjectInfo.expectedBuildConfigurationFor(const BuildInfo(BuildMode.debug, 'hello'), 'Hello'), 'Debug-Hello');
expect(XcodeProjectInfo.expectedBuildConfigurationFor(const BuildInfo(BuildMode.profile, 'HELLO'), 'Hello'), 'Release-Hello');
expect(XcodeProjectInfo.expectedBuildConfigurationFor(const BuildInfo(BuildMode.release, 'Hello'), 'Hello'), 'Release-Hello');
});
test('scheme for default project is Runner', () {
final XcodeProjectInfo info = new XcodeProjectInfo(<String>['Runner'], <String>['Debug', 'Release'], <String>['Runner']);
expect(info.schemeFor(BuildInfo.debug), 'Runner');
expect(info.schemeFor(BuildInfo.profile), 'Runner');
expect(info.schemeFor(BuildInfo.release), 'Runner');
expect(info.schemeFor(const BuildInfo(BuildMode.debug, 'unknown')), isNull);
});
test('build configuration for default project is matched against BuildMode', () {
final XcodeProjectInfo info = new XcodeProjectInfo(<String>['Runner'], <String>['Debug', 'Release'], <String>['Runner']);
expect(info.buildConfigurationFor(BuildInfo.debug, 'Runner'), 'Debug');
expect(info.buildConfigurationFor(BuildInfo.profile, 'Runner'), 'Release');
expect(info.buildConfigurationFor(BuildInfo.release, 'Runner'), 'Release');
});
test('scheme for project with custom schemes is matched against flavor', () {
final XcodeProjectInfo info = new XcodeProjectInfo(
<String>['Runner'],
<String>['Debug (Free)', 'Debug (Paid)', 'Release (Free)', 'Release (Paid)'],
<String>['Free', 'Paid'],
);
expect(info.schemeFor(const BuildInfo(BuildMode.debug, 'free')), 'Free');
expect(info.schemeFor(const BuildInfo(BuildMode.profile, 'Free')), 'Free');
expect(info.schemeFor(const BuildInfo(BuildMode.release, 'paid')), 'Paid');
expect(info.schemeFor(const BuildInfo(BuildMode.debug, null)), isNull);
expect(info.schemeFor(const BuildInfo(BuildMode.debug, 'unknown')), isNull);
});
test('build configuration for project with custom schemes is matched against BuildMode and flavor', () {
final XcodeProjectInfo info = new XcodeProjectInfo(
<String>['Runner'],
<String>['debug (free)', 'Debug paid', 'release - Free', 'Release-Paid'],
<String>['Free', 'Paid'],
);
expect(info.buildConfigurationFor(const BuildInfo(BuildMode.debug, 'free'), 'Free'), 'debug (free)');
expect(info.buildConfigurationFor(const BuildInfo(BuildMode.debug, 'Paid'), 'Paid'), 'Debug paid');
expect(info.buildConfigurationFor(const BuildInfo(BuildMode.profile, 'FREE'), 'Free'), 'release - Free');
expect(info.buildConfigurationFor(const BuildInfo(BuildMode.release, 'paid'), 'Paid'), 'Release-Paid');
});
test('build configuration for project with inconsistent naming is null', () {
final XcodeProjectInfo info = new XcodeProjectInfo(
<String>['Runner'],
<String>['Debug-F', 'Dbg Paid', 'Rel Free', 'Release Full'],
<String>['Free', 'Paid'],
);
expect(info.buildConfigurationFor(const BuildInfo(BuildMode.debug, 'Free'), 'Free'), null);
expect(info.buildConfigurationFor(const BuildInfo(BuildMode.profile, 'Free'), 'Free'), null);
expect(info.buildConfigurationFor(const BuildInfo(BuildMode.release, 'Paid'), 'Paid'), null);
});
});
}