Commit 7c3a45dc authored by Mikkel Nygaard Ravn's avatar Mikkel Nygaard Ravn Committed by GitHub

Revert "Support for app flavors in flutter tooling (#11676)" (#11729)

This reverts commit 8d07d3f6.
parent 8d07d3f6
// 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());
}
// 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());
}
// 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());
}
......@@ -23,14 +23,6 @@ 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',
......@@ -40,16 +32,10 @@ TaskFunction createPlatformChannelSampleTest() {
class DriverTest {
DriverTest(
this.testDirectory,
this.testTarget, {
this.extraOptions = const <String>[],
}
);
DriverTest(this.testDirectory, this.testTarget);
final String testDirectory;
final String testTarget;
final List<String> extraOptions;
Future<TaskResult> call() {
return inDirectory(testDirectory, () async {
......@@ -60,15 +46,14 @@ class DriverTest {
if (deviceOperatingSystem == DeviceOperatingSystem.ios)
await prepareProvisioningCertificates(testDirectory);
final List<String> options = <String>[
await flutter('drive', options: <String>[
'-v',
'-t',
testTarget,
'-d',
deviceId,
];
options.addAll(extraOptions);
await flutter('drive', options: options);
]);
return new TaskResult.success(null);
});
......
......@@ -59,13 +59,6 @@ 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.
......@@ -168,13 +161,6 @@ 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.
......@@ -256,13 +242,6 @@ 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.
......
.DS_Store
.atom/
.idea
.packages
.pub/
build/
ios/.generated/
packages
pubspec.lock
.flutter-plugins
# flavors
Integration test of build flavors (Android product flavors, Xcode schemes).
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
GeneratedPluginRegistrant.java
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'
}
<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>
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);
}
});
}
}
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
}
#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
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
}
.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/
<?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>
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>
<?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>
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>
#import <UIKit/UIKit.h>
#import <Flutter/Flutter.h>
@interface AppDelegate : FlutterAppDelegate
@end
#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
{
"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"
}
}
\ No newline at end of file
{
"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"
}
}
# 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.
\ No newline at end of file
<?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>
<?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>
<?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>
#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]));
}
}
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'));
}
}
name: flavors
description: Integration test for build flavors.
dependencies:
flutter:
sdk: flutter
flutter_driver:
sdk: flutter
flutter:
uses-material-design: true
// 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();
});
});
}
......@@ -338,7 +338,8 @@ class AndroidDevice extends Device {
@override
Future<LaunchResult> startApp(
ApplicationPackage package, {
ApplicationPackage package,
BuildMode mode, {
String mainPath,
String route,
DebuggingOptions debuggingOptions,
......@@ -351,7 +352,7 @@ class AndroidDevice extends Device {
if (!await _checkForSupportedAdbVersion() || !await _checkForSupportedAndroidVersion())
return new LaunchResult.failed();
if (await targetPlatform != TargetPlatform.android_arm && !debuggingOptions.buildInfo.isDebug) {
if (await targetPlatform != TargetPlatform.android_arm && mode != BuildMode.debug) {
printError('Profile and release builds are only supported on ARM targets.');
return new LaunchResult.failed();
}
......@@ -360,7 +361,7 @@ class AndroidDevice extends Device {
printTrace('Building APK');
await buildApk(
target: mainPath,
buildInfo: debuggingOptions.buildInfo,
buildMode: debuggingOptions.buildMode,
kernelPath: kernelPath,
);
// Package has been built, so we can get the updated application ID and
......@@ -407,7 +408,7 @@ class AndroidDevice extends Device {
if (debuggingOptions.enableSoftwareRendering)
cmd.addAll(<String>['--ez', 'enable-software-rendering', 'true']);
if (debuggingOptions.debuggingEnabled) {
if (debuggingOptions.buildInfo.isDebug)
if (debuggingOptions.buildMode == BuildMode.debug)
cmd.addAll(<String>['--ez', 'enable-checked-mode', 'true']);
if (debuggingOptions.startPaused)
cmd.addAll(<String>['--ez', 'start-paused', 'true']);
......@@ -434,13 +435,13 @@ class AndroidDevice extends Device {
try {
Uri observatoryUri, diagnosticUri;
if (debuggingOptions.buildInfo.isDebug) {
if (debuggingOptions.buildMode == BuildMode.debug) {
final List<Uri> deviceUris = await Future.wait(
<Future<Uri>>[observatoryDiscovery.uri, diagnosticDiscovery.uri]
);
observatoryUri = deviceUris[0];
diagnosticUri = deviceUris[1];
} else if (debuggingOptions.buildInfo.isProfile) {
} else if (debuggingOptions.buildMode == BuildMode.profile) {
observatoryUri = await observatoryDiscovery.uri;
}
......
......@@ -228,7 +228,7 @@ class BuildableIOSApp extends IOSApp {
bool get isSwift => buildSettings?.containsKey('SWIFT_VERSION');
String _buildAppPath(String type) {
return fs.path.join(getIosBuildDirectory(), type, kBundleName);
return fs.path.join(getIosBuildDirectory(), 'Release-$type', kBundleName);
}
}
......
......@@ -8,42 +8,10 @@ import 'base/platform.dart';
import 'base/utils.dart';
import 'globals.dart';
/// 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);
enum BuildType {
prebuilt,
release,
debug,
}
/// The type of build - `debug`, `profile`, or `release`.
......
......@@ -35,7 +35,6 @@ class BuildApkCommand extends BuildSubCommand {
BuildApkCommand() {
usesTargetOption();
addBuildModeFlags();
usesFlavorOption();
usesPubOption();
}
......@@ -52,14 +51,14 @@ class BuildApkCommand extends BuildSubCommand {
Future<Null> runCommand() async {
await super.runCommand();
final BuildInfo buildInfo = getBuildInfo();
await buildApk(buildInfo: buildInfo, target: targetFile);
final BuildMode buildMode = getBuildMode();
await buildApk(buildMode: buildMode, target: targetFile);
}
}
Future<Null> buildApk({
String target,
BuildInfo buildInfo: BuildInfo.debug,
BuildMode buildMode: BuildMode.debug,
String kernelPath,
}) async {
if (!isProjectUsingGradle()) {
......@@ -81,5 +80,5 @@ Future<Null> buildApk({
throwToolExit('Try re-installing or updating your Android SDK.');
}
return buildGradleProject(buildInfo, target, kernelPath);
return buildGradleProject(buildMode, target, kernelPath);
}
......@@ -15,7 +15,6 @@ import 'build.dart';
class BuildIOSCommand extends BuildSubCommand {
BuildIOSCommand() {
usesTargetOption();
usesFlavorOption();
usesPubOption();
argParser.addFlag('debug',
negatable: false,
......@@ -57,17 +56,17 @@ class BuildIOSCommand extends BuildSubCommand {
printStatus('Warning: Building for device with codesigning disabled. You will '
'have to manually codesign before deploying to device.');
}
final BuildInfo buildInfo = getBuildInfo();
if (forSimulator && !buildInfo.supportsSimulator)
throwToolExit('${toTitleCase(buildInfo.modeName)} mode is not supported for simulators.');
if (forSimulator && !isEmulatorBuildMode(getBuildMode()))
throwToolExit('${toTitleCase(getModeName(getBuildMode()))} mode is not supported for emulators.');
final String logTarget = forSimulator ? 'simulator' : 'device';
final String typeName = artifacts.getEngineType(TargetPlatform.ios, buildInfo.mode);
final String typeName = artifacts.getEngineType(TargetPlatform.ios, getBuildMode());
printStatus('Building $app for $logTarget ($typeName)...');
final XcodeBuildResult result = await buildXcodeProject(
app: app,
buildInfo: buildInfo,
mode: getBuildMode(),
target: targetFile,
buildForDevice: !forSimulator,
codesign: shouldCodesign
......
......@@ -182,7 +182,7 @@ class CreateCommand extends FlutterCommand {
updateXcodeGeneratedProperties(
projectPath: appPath,
buildInfo: BuildInfo.debug,
mode: BuildMode.debug,
target: flx.defaultMainPath,
hasPlugins: generatePlugin,
);
......
......@@ -302,7 +302,6 @@ 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;
......@@ -313,13 +312,13 @@ class AppDomain extends Domain {
if (!fs.isDirectorySync(projectDirectory))
throw "'$projectDirectory' does not exist";
final BuildInfo buildInfo = new BuildInfo(getBuildModeForName(mode) ?? BuildMode.debug, flavor);
final BuildMode buildMode = getBuildModeForName(mode) ?? BuildMode.debug;
DebuggingOptions options;
if (buildInfo.isRelease) {
options = new DebuggingOptions.disabled(buildInfo);
if (buildMode == BuildMode.release) {
options = new DebuggingOptions.disabled(buildMode);
} else {
options = new DebuggingOptions.enabled(
buildInfo,
buildMode,
startPaused: startPaused,
useTestFonts: useTestFonts,
);
......@@ -350,8 +349,8 @@ class AppDomain extends Domain {
String packagesFilePath,
String projectAssets,
}) async {
if (await device.isLocalEmulator && !options.buildInfo.supportsEmulator)
throw '${toTitleCase(options.buildInfo.modeName)} mode is not supported for emulators.';
if (await device.isLocalEmulator && !isEmulatorBuildMode(options.buildMode))
throw '${toTitleCase(getModeName(options.buildMode))} mode is not supported for emulators.';
// We change the current working directory for the duration of the `start` command.
final Directory cwd = fs.currentDirectory;
......
......@@ -8,6 +8,7 @@ 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';
......@@ -27,7 +28,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 generally be a Dart program that uses
/// `_test.dart` file would generall 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.
......@@ -112,7 +113,7 @@ class DriveCommand extends RunCommandBase {
if (argResults['use-existing-app'] == null) {
printStatus('Starting application: $targetFile');
if (getBuildInfo().isRelease) {
if (getBuildMode() == BuildMode.release) {
// 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'
......@@ -266,10 +267,11 @@ 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.getBuildInfo(),
command.getBuildMode(),
startPaused: true,
observatoryPort: command.observatoryPort,
diagnosticPort: command.diagnosticPort,
......
......@@ -122,7 +122,7 @@ class FuchsiaReloadCommand extends FlutterCommand {
flutterDevice.observatoryUris = observatoryUris;
final HotRunner hotRunner = new HotRunner(
<FlutterDevice>[flutterDevice],
debuggingOptions: new DebuggingOptions.enabled(getBuildInfo()),
debuggingOptions: new DebuggingOptions.enabled(getBuildMode()),
target: _target,
projectRootPath: _fuchsiaProjectPath,
packagesFilePath: _dotPackagesPath
......
......@@ -23,7 +23,6 @@ abstract class RunCommandBase extends FlutterCommand {
// Used by run and drive commands.
RunCommandBase() {
addBuildModeFlags(defaultToRelease: false);
usesFlavorOption();
argParser.addFlag('trace-startup',
negatable: true,
defaultsTo: false,
......@@ -209,7 +208,7 @@ class RunCommand extends RunCommandBase {
bool shouldUseHotMode() {
final bool hotArg = argResults['hot'] ?? false;
final bool shouldUseHotMode = hotArg;
return getBuildInfo().isDebug && shouldUseHotMode;
return (getBuildMode() == BuildMode.debug) && shouldUseHotMode;
}
bool get runningWithPrebuiltApplication =>
......@@ -229,12 +228,11 @@ class RunCommand extends RunCommandBase {
}
DebuggingOptions _createDebuggingOptions() {
final BuildInfo buildInfo = getBuildInfo();
if (buildInfo.isRelease) {
return new DebuggingOptions.disabled(buildInfo);
if (getBuildMode() == BuildMode.release) {
return new DebuggingOptions.disabled(getBuildMode());
} else {
return new DebuggingOptions.enabled(
buildInfo,
getBuildMode(),
startPaused: argResults['start-paused'],
useTestFonts: argResults['use-test-fonts'],
enableSoftwareRendering: argResults['enable-software-rendering'],
......
......@@ -235,7 +235,8 @@ 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, {
ApplicationPackage package,
BuildMode mode, {
String mainPath,
String route,
DebuggingOptions debuggingOptions,
......@@ -315,7 +316,7 @@ abstract class Device {
}
class DebuggingOptions {
DebuggingOptions.enabled(this.buildInfo, {
DebuggingOptions.enabled(this.buildMode, {
this.startPaused: false,
this.enableSoftwareRendering: false,
this.useTestFonts: false,
......@@ -323,7 +324,7 @@ class DebuggingOptions {
this.diagnosticPort
}) : debuggingEnabled = true;
DebuggingOptions.disabled(this.buildInfo) :
DebuggingOptions.disabled(this.buildMode) :
debuggingEnabled = false,
useTestFonts = false,
startPaused = false,
......@@ -333,7 +334,7 @@ class DebuggingOptions {
final bool debuggingEnabled;
final BuildInfo buildInfo;
final BuildMode buildMode;
final bool startPaused;
final bool enableSoftwareRendering;
final bool useTestFonts;
......
......@@ -59,7 +59,8 @@ class FuchsiaDevice extends Device {
@override
Future<LaunchResult> startApp(
ApplicationPackage app, {
ApplicationPackage app,
BuildMode mode, {
String mainPath,
String route,
DebuggingOptions debuggingOptions,
......
......@@ -164,7 +164,8 @@ class IOSDevice extends Device {
@override
Future<LaunchResult> startApp(
ApplicationPackage app, {
ApplicationPackage app,
BuildMode mode, {
String mainPath,
String route,
DebuggingOptions debuggingOptions,
......@@ -181,7 +182,7 @@ class IOSDevice extends Device {
// Step 1: Build the precompiled/DBC application if necessary.
final XcodeBuildResult buildResult = await buildXcodeProject(
app: app,
buildInfo: debuggingOptions.buildInfo,
mode: mode,
target: mainPath,
buildForDevice: true,
usesTerminalUi: usesTerminalUi,
......@@ -266,7 +267,7 @@ class IOSDevice extends Device {
final Future<Uri> forwardObservatoryUri = observatoryDiscovery.uri;
Future<Uri> forwardDiagnosticUri;
if (debuggingOptions.buildInfo.isDebug) {
if (debuggingOptions.buildMode == BuildMode.debug) {
forwardDiagnosticUri = diagnosticDiscovery.uri;
} else {
forwardDiagnosticUri = new Future<Uri>.value(null);
......
......@@ -220,7 +220,7 @@ bool _xcodeVersionCheckValid(int major, int minor) {
Future<XcodeBuildResult> buildXcodeProject({
BuildableIOSApp app,
BuildInfo buildInfo,
BuildMode mode,
String target: flx.defaultMainPath,
bool buildForDevice,
bool codesign: true,
......@@ -234,35 +234,6 @@ 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);
......@@ -276,13 +247,13 @@ Future<XcodeBuildResult> buildXcodeProject({
if (hasFlutterPlugins)
await cocoaPods.processPods(
appIosDir: appDirectory,
iosEngineDir: flutterFrameworkDir(buildInfo.mode),
iosEngineDir: flutterFrameworkDir(mode),
isSwift: app.isSwift,
);
updateXcodeGeneratedProperties(
projectPath: fs.currentDirectory.path,
buildInfo: buildInfo,
mode: mode,
target: target,
hasPlugins: hasFlutterPlugins
);
......@@ -293,8 +264,7 @@ Future<XcodeBuildResult> buildXcodeProject({
'xcodebuild',
'clean',
'build',
'-configuration', configuration,
'-scheme', scheme,
'-configuration', 'Release',
'ONLY_ACTIVE_ARCH=YES',
];
......@@ -306,6 +276,7 @@ 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;
......@@ -335,6 +306,7 @@ Future<XcodeBuildResult> buildXcodeProject({
allowReentrantFlutter: true
);
status.stop();
if (result.exitCode != 0) {
printStatus('Failed to build iOS app');
if (result.stderr.isNotEmpty) {
......@@ -356,18 +328,13 @@ Future<XcodeBuildResult> buildXcodeProject({
),
);
} else {
// Look for 'clean build/<configuration>-<sdk>/Runner.app'.
final RegExp regexp = new RegExp(r' clean ([^.]*\.app)$', multiLine: true);
// Look for 'clean build/Release-iphoneos/Runner.app'.
final RegExp regexp = new RegExp(r' clean (\S*\.app)$', multiLine: true);
final Match match = regexp.firstMatch(result.stdout);
String 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);
if (match != null)
outputDir = fs.path.join(app.appDirectory, match.group(1));
return new XcodeBuildResult(success:true, output: outputDir);
}
}
......@@ -389,9 +356,7 @@ Future<Null> diagnoseXcodeBuildFailure(XcodeBuildResult result, BuildableIOSApp
printError(noDevelopmentTeamInstruction, emphasis: true);
return;
}
if (result.xcodeBuildExecution != null &&
result.xcodeBuildExecution.buildForPhysicalDevice &&
app.id?.contains('com.yourcompany') ?? false) {
if (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:");
......
......@@ -306,7 +306,8 @@ class IOSSimulator extends Device {
@override
Future<LaunchResult> startApp(
ApplicationPackage app, {
ApplicationPackage app,
BuildMode mode, {
String mainPath,
String route,
DebuggingOptions debuggingOptions,
......@@ -320,7 +321,7 @@ class IOSSimulator extends Device {
printTrace('Building ${app.name} for $id.');
try {
await _setupUpdatedApplicationBundle(app, debuggingOptions.buildInfo.flavor);
await _setupUpdatedApplicationBundle(app);
} on ToolExit catch (e) {
printError(e.message);
return new LaunchResult.failed();
......@@ -342,7 +343,7 @@ class IOSSimulator extends Device {
}
if (debuggingOptions.debuggingEnabled) {
if (debuggingOptions.buildInfo.isDebug)
if (debuggingOptions.buildMode == BuildMode.debug)
args.add('--enable-checked-mode');
if (debuggingOptions.startPaused)
args.add('--start-paused');
......@@ -394,17 +395,17 @@ class IOSSimulator extends Device {
return criteria.reduce((bool a, bool b) => a && b);
}
Future<Null> _setupUpdatedApplicationBundle(ApplicationPackage app, String flavor) async {
Future<Null> _setupUpdatedApplicationBundle(ApplicationPackage app) async {
await _sideloadUpdatedAssetsForInstalledApplicationBundle(app);
if (!await _applicationIsInstalledAndRunning(app))
return _buildAndInstallApplicationBundle(app, flavor);
return _buildAndInstallApplicationBundle(app);
}
Future<Null> _buildAndInstallApplicationBundle(ApplicationPackage app, String flavor) async {
Future<Null> _buildAndInstallApplicationBundle(ApplicationPackage app) async {
// Step 1: Build the Xcode project.
// The build mode for the simulator is always debug.
final XcodeBuildResult buildResult = await buildXcodeProject(app: app, buildInfo: new BuildInfo(BuildMode.debug, flavor), buildForDevice: false);
final XcodeBuildResult buildResult = await buildXcodeProject(app: app, mode: BuildMode.debug, buildForDevice: false);
if (!buildResult.success)
throwToolExit('Could not build the application for the simulator.');
......
......@@ -7,7 +7,6 @@ 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';
......@@ -21,7 +20,7 @@ String flutterFrameworkDir(BuildMode mode) {
void updateXcodeGeneratedProperties({
@required String projectPath,
@required BuildInfo buildInfo,
@required BuildMode mode,
@required String target,
@required bool hasPlugins,
}) {
......@@ -39,21 +38,21 @@ void updateXcodeGeneratedProperties({
localsBuffer.writeln('FLUTTER_TARGET=$target');
// The runtime mode for the current build.
localsBuffer.writeln('FLUTTER_BUILD_MODE=${buildInfo.modeName}');
localsBuffer.writeln('FLUTTER_BUILD_MODE=${getModeName(mode)}');
// 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(buildInfo.mode)}');
localsBuffer.writeln('FLUTTER_FRAMEWORK_DIR=${flutterFrameworkDir(mode)}');
if (artifacts is LocalEngineArtifacts) {
final LocalEngineArtifacts localEngineArtifacts = artifacts;
localsBuffer.writeln('LOCAL_ENGINE=${localEngineArtifacts.engineOutPath}');
}
// Add dependency to CocoaPods' generated project only if plugins are used.
// Add dependency to CocoaPods' generated project only if plugns are used.
if (hasPlugins)
localsBuffer.writeln('#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"');
......@@ -75,6 +74,7 @@ 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,109 +84,3 @@ 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)';
}
}
......@@ -208,7 +208,7 @@ class FlutterDevice {
bool shouldBuild,
}) async {
final bool prebuiltMode = hotRunner.applicationBinary != null;
final String modeName = hotRunner.debuggingOptions.buildInfo.modeName;
final String modeName = getModeName(hotRunner.debuggingOptions.buildMode);
printStatus('Launching ${getDisplayPath(hotRunner.mainPath)} on ${device.name} in $modeName mode...');
final TargetPlatform targetPlatform = await device.targetPlatform;
......@@ -234,6 +234,7 @@ 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,
......@@ -267,7 +268,7 @@ class FlutterDevice {
applicationBinary: coldRunner.applicationBinary
);
final String modeName = coldRunner.debuggingOptions.buildInfo.modeName;
final String modeName = getModeName(coldRunner.debuggingOptions.buildMode);
final bool prebuiltMode = coldRunner.applicationBinary != null;
if (coldRunner.mainPath == null) {
assert(prebuiltMode);
......@@ -294,6 +295,7 @@ 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,
......@@ -376,9 +378,9 @@ abstract class ResidentRunner {
AssetBundle _assetBundle;
AssetBundle get assetBundle => _assetBundle;
bool get isRunningDebug => debuggingOptions.buildInfo.isDebug;
bool get isRunningProfile => debuggingOptions.buildInfo.isProfile;
bool get isRunningRelease => debuggingOptions.buildInfo.isRelease;
bool get isRunningDebug => debuggingOptions.buildMode == BuildMode.debug;
bool get isRunningProfile => debuggingOptions.buildMode == BuildMode.profile;
bool get isRunningRelease => debuggingOptions.buildMode == BuildMode.release;
bool get supportsServiceProtocol => isRunningDebug || isRunningProfile;
/// Start the app and keep the process running during its lifetime.
......
......@@ -133,19 +133,6 @@ 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();
}
......
......@@ -2,89 +2,33 @@
// 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() {
group('gradle project', () {
GradleProject projectFrom(String properties) => new GradleProject.fromAppProperties(properties);
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);
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,
});
});
}
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);
});
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment