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() {
return new DriverTest(
'${flutterDirectory.path}/examples/platform_channel',
@ -32,10 +40,16 @@ TaskFunction createPlatformChannelSampleTest() {
class DriverTest {
DriverTest(this.testDirectory, this.testTarget);
DriverTest(
this.testDirectory,
this.testTarget, {
this.extraOptions = const <String>[],
}
);
final String testDirectory;
final String testTarget;
final List<String> extraOptions;
Future<TaskResult> call() {
return inDirectory(testDirectory, () async {
@ -46,14 +60,15 @@ class DriverTest {
if (deviceOperatingSystem == DeviceOperatingSystem.ios)
await prepareProvisioningCertificates(testDirectory);
await flutter('drive', options: <String>[
final List<String> options = <String>[
'-v',
'-t',
testTarget,
'-d',
deviceId,
]);
];
options.addAll(extraOptions);
await flutter('drive', options: options);
return new TaskResult.success(null);
});

View File

@ -59,6 +59,13 @@ tasks:
stage: devicelab
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:
description: >
Checks that platform channels work on Android.
@ -161,6 +168,13 @@ tasks:
# 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:
description: >
Checks that platform channels work on iOS.
@ -242,6 +256,13 @@ tasks:
# 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:
description: >
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
Future<LaunchResult> startApp(
ApplicationPackage package,
BuildMode mode, {
ApplicationPackage package, {
String mainPath,
String route,
DebuggingOptions debuggingOptions,
@ -352,7 +351,7 @@ class AndroidDevice extends Device {
if (!await _checkForSupportedAdbVersion() || !await _checkForSupportedAndroidVersion())
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.');
return new LaunchResult.failed();
}
@ -361,7 +360,7 @@ class AndroidDevice extends Device {
printTrace('Building APK');
await buildApk(
target: mainPath,
buildMode: debuggingOptions.buildMode,
buildInfo: debuggingOptions.buildInfo,
kernelPath: kernelPath,
);
// Package has been built, so we can get the updated application ID and
@ -408,7 +407,7 @@ class AndroidDevice extends Device {
if (debuggingOptions.enableSoftwareRendering)
cmd.addAll(<String>['--ez', 'enable-software-rendering', 'true']);
if (debuggingOptions.debuggingEnabled) {
if (debuggingOptions.buildMode == BuildMode.debug)
if (debuggingOptions.buildInfo.isDebug)
cmd.addAll(<String>['--ez', 'enable-checked-mode', 'true']);
if (debuggingOptions.startPaused)
cmd.addAll(<String>['--ez', 'start-paused', 'true']);
@ -435,13 +434,13 @@ class AndroidDevice extends Device {
try {
Uri observatoryUri, diagnosticUri;
if (debuggingOptions.buildMode == BuildMode.debug) {
if (debuggingOptions.buildInfo.isDebug) {
final List<Uri> deviceUris = await Future.wait(
<Future<Uri>>[observatoryDiscovery.uri, diagnosticDiscovery.uri]
);
observatoryUri = deviceUris[0];
diagnosticUri = deviceUris[1];
} else if (debuggingOptions.buildMode == BuildMode.profile) {
} else if (debuggingOptions.buildInfo.isProfile) {
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 gradleAppOutDirV1 = 'android/app/build/outputs/apk';
const String gradleVersion = '3.3';
final RegExp _assembleTaskPattern = new RegExp(r'assemble([^:]+): task ');
String _cachedGradleAppOutDirV2;
GradleProject _cachedGradleProject;
String _cachedGradleExecutable;
enum FlutterPluginVersion {
@ -58,6 +59,8 @@ FlutterPluginVersion get flutterPluginVersion {
return FlutterPluginVersion.none;
}
/// Returns the path to the apk file created by [buildGradleProject], relative
/// to current directory.
Future<String> getGradleAppOut() async {
switch (flutterPluginVersion) {
case FlutterPluginVersion.none:
@ -67,19 +70,19 @@ Future<String> getGradleAppOut() async {
case FlutterPluginVersion.managed:
// Fall through. The managed plugin matches plugin v2 for now.
case FlutterPluginVersion.v2:
return '${await _getGradleAppOutDirV2()}/app.apk';
return fs.path.relative(fs.path.join((await _gradleProject()).apkDirectory, 'app.apk'));
}
return null;
}
Future<String> _getGradleAppOutDirV2() async {
_cachedGradleAppOutDirV2 ??= await _calculateGradleAppOutDirV2();
return _cachedGradleAppOutDirV2;
Future<GradleProject> _gradleProject() async {
_cachedGradleProject ??= await _readGradleProject();
return _cachedGradleProject;
}
// Note: Dependencies are resolved and possibly downloaded as a side-effect
// of calculating the app properties using Gradle. This may take minutes.
Future<String> _calculateGradleAppOutDirV2() async {
Future<GradleProject> _readGradleProject() async {
final String gradle = await _ensureGradle();
updateLocalProperties();
try {
@ -90,28 +93,20 @@ Future<String> _calculateGradleAppOutDirV2() async {
environment: _gradleEnv,
);
final String properties = runResult.stdout.trim();
String buildDir = 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);
}
final GradleProject project = new GradleProject.fromAppProperties(properties);
status.stop();
return '$buildDir/outputs/apk';
return project;
} catch (e) {
printError('Error running gradle: $e');
}
// Fall back to the default
return gradleAppOutDirV1;
return new GradleProject(<String>['debug', 'profile', 'release'], <String>[], gradleAppOutDirV1);
}
String _locateProjectGradlew({ bool ensureExecutable: true }) {
final String path = fs.path.join(
'android', platform.isWindows ? 'gradlew.bat' : 'gradlew'
'android',
platform.isWindows ? 'gradlew.bat' : 'gradlew',
);
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.
void updateLocalProperties({String projectPath, String buildMode}) {
void updateLocalProperties({String projectPath, BuildInfo buildInfo}) {
final File localProperties = (projectPath == null)
? fs.file(fs.path.join('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;
changed = true;
}
if (buildMode != null && settings.values['flutter.buildMode'] != buildMode) {
settings.values['flutter.buildMode'] = buildMode;
if (buildInfo != null && settings.values['flutter.buildMode'] != buildInfo.modeName) {
settings.values['flutter.buildMode'] = buildInfo.modeName;
changed = true;
}
@ -192,13 +187,12 @@ void updateLocalProperties({String projectPath, String buildMode}) {
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.
// FlutterPlugin v1 reads local.properties to determine build mode. Plugin v2
// uses the standard Android way to determine what to build, but we still
// update local.properties, in case we want to use it in the future.
final String buildModeName = getModeName(buildMode);
updateLocalProperties(buildMode: buildModeName);
updateLocalProperties(buildInfo: buildInfo);
injectPlugins();
@ -212,7 +206,7 @@ Future<Null> buildGradleProject(BuildMode buildMode, String target, String kerne
case FlutterPluginVersion.managed:
// Fall through. Managed plugin builds the same way as plugin 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())}).');
}
File findApkFile(String buildDirectory, String buildModeName) {
final String apkFilename = 'app-$buildModeName.apk';
File apkFile = fs.file('$buildDirectory/$apkFilename');
if (apkFile.existsSync())
return apkFile;
apkFile = fs.file('$buildDirectory/$buildModeName/$apkFilename');
if (apkFile.existsSync())
return apkFile;
return null;
}
Future<Null> _buildGradleProjectV2(String gradle, String buildModeName, String target, String kernelPath) async {
final String assembleTask = "assemble${toTitleCase(buildModeName)}";
// Run 'gradlew assemble<BuildMode>'.
Future<Null> _buildGradleProjectV2(String gradle, BuildInfo buildInfo, String target, String kernelPath) async {
final GradleProject project = await _gradleProject();
final String assembleTask = project.assembleTaskFor(buildInfo);
if (assembleTask == null) {
printError('');
printError('The Gradle project does not define a task suitable for the requested build.');
if (!project.buildTypes.contains(buildInfo.modeName)) {
printError('Review the android/app/build.gradle file and ensure it defines a ${buildInfo.modeName} build type.');
} 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.');
}
}
final Status status = logger.startProgress('Running \'gradlew $assembleTask\'...', expectSlowOperation: true);
final String gradlePath = fs.file(gradle).absolute.path;
final List<String> command = <String>[gradlePath];
@ -266,7 +264,7 @@ Future<Null> _buildGradleProjectV2(String gradle, String buildModeName, String t
if (kernelPath != null)
command.add('-Pkernel=$kernelPath');
command.add(assembleTask);
final int exitcode = await runCommandAndStreamOutput(
final int exitCode = await runCommandAndStreamOutput(
command,
workingDirectory: 'android',
allowReentrantFlutter: true,
@ -274,21 +272,33 @@ Future<Null> _buildGradleProjectV2(String gradle, String buildModeName, String t
);
status.stop();
if (exitcode != 0)
throwToolExit('Gradle build failed: $exitcode', exitCode: exitcode);
if (exitCode != 0)
throwToolExit('Gradle build failed: $exitCode', exitCode: exitCode);
final String buildDirectory = await _getGradleAppOutDirV2();
final File apkFile = findApkFile(buildDirectory, buildModeName);
final File apkFile = _findApkFile(project, buildInfo);
if (apkFile == null)
throwToolExit('Gradle build failed to produce an Android package.');
// 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');
final File apkShaFile = fs.file('$buildDirectory/app.apk.sha1');
printTrace('calculateSha: ${project.apkDirectory}/app.apk');
final File apkShaFile = fs.file(fs.path.join(project.apkDirectory, 'app.apk.sha1'));
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 {
@ -299,3 +309,83 @@ Map<String, String> get _gradleEnv {
}
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');
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 'globals.dart';
enum BuildType {
prebuilt,
release,
debug,
/// Information about a build to be performed or used.
class BuildInfo {
const BuildInfo(this.mode, this.flavor);
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`.

View File

@ -35,6 +35,7 @@ class BuildApkCommand extends BuildSubCommand {
BuildApkCommand() {
usesTargetOption();
addBuildModeFlags();
usesFlavorOption();
usesPubOption();
}
@ -51,14 +52,14 @@ class BuildApkCommand extends BuildSubCommand {
Future<Null> runCommand() async {
await super.runCommand();
final BuildMode buildMode = getBuildMode();
await buildApk(buildMode: buildMode, target: targetFile);
final BuildInfo buildInfo = getBuildInfo();
await buildApk(buildInfo: buildInfo, target: targetFile);
}
}
Future<Null> buildApk({
String target,
BuildMode buildMode: BuildMode.debug,
BuildInfo buildInfo: BuildInfo.debug,
String kernelPath,
}) async {
if (!isProjectUsingGradle()) {
@ -80,5 +81,5 @@ Future<Null> buildApk({
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 {
BuildIOSCommand() {
usesTargetOption();
usesFlavorOption();
usesPubOption();
argParser.addFlag('debug',
negatable: false,
@ -56,17 +57,17 @@ class BuildIOSCommand extends BuildSubCommand {
printStatus('Warning: Building for device with codesigning disabled. You will '
'have to manually codesign before deploying to device.');
}
if (forSimulator && !isEmulatorBuildMode(getBuildMode()))
throwToolExit('${toTitleCase(getModeName(getBuildMode()))} mode is not supported for emulators.');
final BuildInfo buildInfo = getBuildInfo();
if (forSimulator && !buildInfo.supportsSimulator)
throwToolExit('${toTitleCase(buildInfo.modeName)} mode is not supported for simulators.');
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)...');
final XcodeBuildResult result = await buildXcodeProject(
app: app,
mode: getBuildMode(),
buildInfo: buildInfo,
target: targetFile,
buildForDevice: !forSimulator,
codesign: shouldCodesign

View File

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

View File

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

View File

@ -8,7 +8,6 @@ import '../application_package.dart';
import '../base/common.dart';
import '../base/file_system.dart';
import '../base/process.dart';
import '../build_info.dart';
import '../cache.dart';
import '../dart/package_map.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
/// 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
/// `_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
/// is a test written using `package:test`, but you are free to use something
/// else.
@ -113,7 +112,7 @@ class DriveCommand extends RunCommandBase {
if (argResults['use-existing-app'] == null) {
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.
throwToolExit(
'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(
package,
command.getBuildMode(),
mainPath: mainPath,
route: command.route,
debuggingOptions: new DebuggingOptions.enabled(
command.getBuildMode(),
command.getBuildInfo(),
startPaused: true,
observatoryPort: command.observatoryPort,
diagnosticPort: command.diagnosticPort,

View File

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

View File

@ -23,6 +23,7 @@ abstract class RunCommandBase extends FlutterCommand {
// Used by run and drive commands.
RunCommandBase() {
addBuildModeFlags(defaultToRelease: false);
usesFlavorOption();
argParser.addFlag('trace-startup',
negatable: true,
defaultsTo: false,
@ -208,7 +209,7 @@ class RunCommand extends RunCommandBase {
bool shouldUseHotMode() {
final bool hotArg = argResults['hot'] ?? false;
final bool shouldUseHotMode = hotArg;
return (getBuildMode() == BuildMode.debug) && shouldUseHotMode;
return getBuildInfo().isDebug && shouldUseHotMode;
}
bool get runningWithPrebuiltApplication =>
@ -228,11 +229,12 @@ class RunCommand extends RunCommandBase {
}
DebuggingOptions _createDebuggingOptions() {
if (getBuildMode() == BuildMode.release) {
return new DebuggingOptions.disabled(getBuildMode());
final BuildInfo buildInfo = getBuildInfo();
if (buildInfo.isRelease) {
return new DebuggingOptions.disabled(buildInfo);
} else {
return new DebuggingOptions.enabled(
getBuildMode(),
buildInfo,
startPaused: argResults['start-paused'],
useTestFonts: argResults['use-test-fonts'],
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
/// attempting to start the app.
Future<LaunchResult> startApp(
ApplicationPackage package,
BuildMode mode, {
ApplicationPackage package, {
String mainPath,
String route,
DebuggingOptions debuggingOptions,
@ -316,7 +315,7 @@ abstract class Device {
}
class DebuggingOptions {
DebuggingOptions.enabled(this.buildMode, {
DebuggingOptions.enabled(this.buildInfo, {
this.startPaused: false,
this.enableSoftwareRendering: false,
this.useTestFonts: false,
@ -324,7 +323,7 @@ class DebuggingOptions {
this.diagnosticPort
}) : debuggingEnabled = true;
DebuggingOptions.disabled(this.buildMode) :
DebuggingOptions.disabled(this.buildInfo) :
debuggingEnabled = false,
useTestFonts = false,
startPaused = false,
@ -334,7 +333,7 @@ class DebuggingOptions {
final bool debuggingEnabled;
final BuildMode buildMode;
final BuildInfo buildInfo;
final bool startPaused;
final bool enableSoftwareRendering;
final bool useTestFonts;

View File

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

View File

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

View File

@ -220,7 +220,7 @@ bool _xcodeVersionCheckValid(int major, int minor) {
Future<XcodeBuildResult> buildXcodeProject({
BuildableIOSApp app,
BuildMode mode,
BuildInfo buildInfo,
String target: flx.defaultMainPath,
bool buildForDevice,
bool codesign: true,
@ -234,6 +234,35 @@ Future<XcodeBuildResult> buildXcodeProject({
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;
if (codesign && buildForDevice)
developmentTeam = await getCodeSigningIdentityDevelopmentTeam(iosApp: app, usesTerminalUi: usesTerminalUi);
@ -247,13 +276,13 @@ Future<XcodeBuildResult> buildXcodeProject({
if (hasFlutterPlugins)
await cocoaPods.processPods(
appIosDir: appDirectory,
iosEngineDir: flutterFrameworkDir(mode),
iosEngineDir: flutterFrameworkDir(buildInfo.mode),
isSwift: app.isSwift,
);
updateXcodeGeneratedProperties(
projectPath: fs.currentDirectory.path,
mode: mode,
buildInfo: buildInfo,
target: target,
hasPlugins: hasFlutterPlugins
);
@ -264,7 +293,8 @@ Future<XcodeBuildResult> buildXcodeProject({
'xcodebuild',
'clean',
'build',
'-configuration', 'Release',
'-configuration', configuration,
'-scheme', scheme,
'ONLY_ACTIVE_ARCH=YES',
];
@ -276,7 +306,6 @@ Future<XcodeBuildResult> buildXcodeProject({
if (fs.path.extension(entity.path) == '.xcworkspace') {
commands.addAll(<String>[
'-workspace', fs.path.basename(entity.path),
'-scheme', fs.path.basenameWithoutExtension(entity.path),
"BUILD_DIR=${fs.path.absolute(getIosBuildDirectory())}",
]);
break;
@ -306,7 +335,6 @@ Future<XcodeBuildResult> buildXcodeProject({
allowReentrantFlutter: true
);
status.stop();
if (result.exitCode != 0) {
printStatus('Failed to build iOS app');
if (result.stderr.isNotEmpty) {
@ -328,13 +356,18 @@ Future<XcodeBuildResult> buildXcodeProject({
),
);
} else {
// Look for 'clean build/Release-iphoneos/Runner.app'.
final RegExp regexp = new RegExp(r' clean (\S*\.app)$', multiLine: true);
// Look for 'clean build/<configuration>-<sdk>/Runner.app'.
final RegExp regexp = new RegExp(r' clean (.*\.app)$', multiLine: true);
final Match match = regexp.firstMatch(result.stdout);
String outputDir;
if (match != null)
outputDir = fs.path.join(app.appDirectory, match.group(1));
return new XcodeBuildResult(success:true, output: outputDir);
if (match != null) {
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);
}
}
@ -356,7 +389,9 @@ Future<Null> diagnoseXcodeBuildFailure(XcodeBuildResult result, BuildableIOSApp
printError(noDevelopmentTeamInstruction, emphasis: true);
return;
}
if (app.id?.contains('com.yourcompany') ?? false) {
if (result.xcodeBuildExecution != null &&
result.xcodeBuildExecution.buildForPhysicalDevice &&
app.id?.contains('com.yourcompany') ?? false) {
printError('');
printError('It appears that your application still contains the default signing identifier.');
printError("Try replacing 'com.yourcompany' with your signing id in Xcode:");

View File

@ -306,8 +306,7 @@ class IOSSimulator extends Device {
@override
Future<LaunchResult> startApp(
ApplicationPackage app,
BuildMode mode, {
ApplicationPackage app, {
String mainPath,
String route,
DebuggingOptions debuggingOptions,
@ -321,7 +320,7 @@ class IOSSimulator extends Device {
printTrace('Building ${app.name} for $id.');
try {
await _setupUpdatedApplicationBundle(app);
await _setupUpdatedApplicationBundle(app, debuggingOptions.buildInfo.flavor);
} on ToolExit catch (e) {
printError(e.message);
return new LaunchResult.failed();
@ -343,7 +342,7 @@ class IOSSimulator extends Device {
}
if (debuggingOptions.debuggingEnabled) {
if (debuggingOptions.buildMode == BuildMode.debug)
if (debuggingOptions.buildInfo.isDebug)
args.add('--enable-checked-mode');
if (debuggingOptions.startPaused)
args.add('--start-paused');
@ -395,17 +394,17 @@ class IOSSimulator extends Device {
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);
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.
// 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)
throwToolExit('Could not build the application for the simulator.');

View File

@ -7,6 +7,7 @@ import 'package:meta/meta.dart';
import '../artifacts.dart';
import '../base/file_system.dart';
import '../base/process.dart';
import '../base/utils.dart';
import '../build_info.dart';
import '../cache.dart';
import '../globals.dart';
@ -20,7 +21,7 @@ String flutterFrameworkDir(BuildMode mode) {
void updateXcodeGeneratedProperties({
@required String projectPath,
@required BuildMode mode,
@required BuildInfo buildInfo,
@required String target,
@required bool hasPlugins,
}) {
@ -38,21 +39,21 @@ void updateXcodeGeneratedProperties({
localsBuffer.writeln('FLUTTER_TARGET=$target');
// 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.
localsBuffer.writeln('FLUTTER_BUILD_DIR=${getBuildDirectory()}');
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) {
final LocalEngineArtifacts localEngineArtifacts = artifacts;
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)
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;
}
/// Substitutes variables in [str] with their values from the specified Xcode
/// project and target.
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]);
}
/// 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,
}) async {
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...');
final TargetPlatform targetPlatform = await device.targetPlatform;
@ -234,7 +234,6 @@ class FlutterDevice {
final bool hasDirtyDependencies = hotRunner.hasDirtyDependencies(this);
final Future<LaunchResult> futureResult = device.startApp(
package,
hotRunner.debuggingOptions.buildMode,
mainPath: hotRunner.mainPath,
debuggingOptions: hotRunner.debuggingOptions,
platformArgs: platformArgs,
@ -268,7 +267,7 @@ class FlutterDevice {
applicationBinary: coldRunner.applicationBinary
);
final String modeName = getModeName(coldRunner.debuggingOptions.buildMode);
final String modeName = coldRunner.debuggingOptions.buildInfo.modeName;
final bool prebuiltMode = coldRunner.applicationBinary != null;
if (coldRunner.mainPath == null) {
assert(prebuiltMode);
@ -295,7 +294,6 @@ class FlutterDevice {
final bool hasDirtyDependencies = coldRunner.hasDirtyDependencies(this);
final LaunchResult result = await device.startApp(
package,
coldRunner.debuggingOptions.buildMode,
mainPath: coldRunner.mainPath,
debuggingOptions: coldRunner.debuggingOptions,
platformArgs: platformArgs,
@ -378,9 +376,9 @@ abstract class ResidentRunner {
AssetBundle _assetBundle;
AssetBundle get assetBundle => _assetBundle;
bool get isRunningDebug => debuggingOptions.buildMode == BuildMode.debug;
bool get isRunningProfile => debuggingOptions.buildMode == BuildMode.profile;
bool get isRunningRelease => debuggingOptions.buildMode == BuildMode.release;
bool get isRunningDebug => debuggingOptions.buildInfo.isDebug;
bool get isRunningProfile => debuggingOptions.buildInfo.isProfile;
bool get isRunningRelease => debuggingOptions.buildInfo.isRelease;
bool get supportsServiceProtocol => isRunningDebug || isRunningProfile;
/// 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;
}
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() {
applicationPackages ??= new ApplicationPackageStore();
}

View File

@ -2,33 +2,89 @@
// Use of this source code is governed by a BSD-style license that can be
// 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/base/file_system.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:test/test.dart';
import '../src/context.dart';
const String _kBuildDirectory = '/build/app/outputs';
void main() {
FileSystem fs;
group('gradle project', () {
GradleProject projectFrom(String properties) => new GradleProject.fromAppProperties(properties);
setUp(() {
fs = new MemoryFileSystem();
fs.directory('$_kBuildDirectory/release').createSync(recursive: true);
fs.file('$_kBuildDirectory/app-debug.apk').createSync();
fs.file('$_kBuildDirectory/release/app-release.apk').createSync();
});
group('gradle', () {
testUsingContext('findApkFile', () {
expect(findApkFile(_kBuildDirectory, 'debug').path,
'/build/app/outputs/app-debug.apk');
expect(findApkFile(_kBuildDirectory, 'release').path,
'/build/app/outputs/release/app-release.apk');
}, overrides: <Type, Generator>{
FileSystem: () => fs,
test('should extract build directory from app properties', () {
final GradleProject project = projectFrom('''
someProperty: someValue
buildDir: /Users/some/apps/hello/build/app
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', () {
final GradleProject project = projectFrom('''
someProperty: someValue
assemble: task ':app:assemble'
assembleAndroidTest: task ':app:assembleAndroidTest'
assembleDebug: task ':app:assembleDebug'
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, 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);
});
});
}