Commit 7ae6f7f3 authored by Devon Carew's avatar Devon Carew

make the ios development path less mandatory

parent c30b3cc6
......@@ -18,6 +18,7 @@ import 'src/commands/cache.dart';
import 'src/commands/create.dart';
import 'src/commands/daemon.dart';
import 'src/commands/devices.dart';
import 'src/commands/doctor.dart';
import 'src/commands/install.dart';
import 'src/commands/ios.dart';
import 'src/commands/listen.dart';
......@@ -30,6 +31,8 @@ import 'src/commands/test.dart';
import 'src/commands/trace.dart';
import 'src/commands/upgrade.dart';
import 'src/device.dart';
import 'src/doctor.dart';
import 'src/ios/mac.dart';
import 'src/runner/flutter_command_runner.dart';
/// Main entry point for commands.
......@@ -48,6 +51,7 @@ Future main(List<String> args) async {
..addCommand(new CreateCommand())
..addCommand(new DaemonCommand(hideCommand: !verboseHelp))
..addCommand(new DevicesCommand())
..addCommand(new DoctorCommand())
..addCommand(new InstallCommand())
..addCommand(new IOSCommand())
..addCommand(new ListenCommand())
......@@ -64,6 +68,8 @@ Future main(List<String> args) async {
// Initialize globals.
context[Logger] = new StdoutLogger();
context[DeviceManager] = new DeviceManager();
Doctor.initGlobal();
XCode.initGlobal();
dynamic result = await runner.run(args);
......
// Copyright 2016 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 '../base/globals.dart';
import '../doctor.dart';
import 'android_sdk.dart';
class AndroidWorkflow extends Workflow {
AndroidWorkflow() : super('Android');
bool get appliesToHostPlatform => true;
bool get canListDevices => getAdbPath(androidSdk) != null;
bool get canLaunchDevices => androidSdk != null && androidSdk.validateSdkWellFormed(complain: false);
void diagnose() {
Validator androidValidator = new Validator('Develop for Android devices');
Function _sdkExists = () {
return androidSdk == null ? ValidationType.missing : ValidationType.installed;
};
androidValidator.addValidator(new Validator(
'Android SDK',
description: 'enable development for Android devices',
resolution: 'Download at https://developer.android.com/sdk/',
validatorFunction: _sdkExists
));
androidValidator.validate().print();
}
}
......@@ -4,12 +4,18 @@
import '../android/android_sdk.dart';
import '../device.dart';
import '../doctor.dart';
import '../ios/mac.dart';
import 'context.dart';
import 'logger.dart';
DeviceManager get deviceManager => context[DeviceManager];
Logger get logger => context[Logger];
AndroidSdk get androidSdk => context[AndroidSdk];
Doctor get doctor => context[Doctor];
// Mac specific globals - will be null on other platforms.
XCode get xcode => context[XCode];
/// Display an error level message to the user. Commands should use this if they
/// fail in some way.
......
......@@ -80,6 +80,14 @@ String sdkBinaryName(String name) {
return Platform.isWindows ? '$name.bat' : name;
}
bool exitsHappy(List<String> cli) {
try {
return Process.runSync(cli.first, cli.sublist(1)).exitCode == 0;
} catch (error) {
return false;
}
}
String _runWithLoggingSync(List<String> cmd, {
bool checked: false,
bool noisyErrors: false,
......
......@@ -16,6 +16,12 @@ class DevicesCommand extends FlutterCommand {
bool get requiresProjectRoot => false;
Future<int> runInProject() async {
if (!doctor.canListAnything) {
printError("Unable to locate a development device; please run 'flutter doctor' for "
"information about installing additional components.");
return 1;
}
List<Device> devices = await deviceManager.getAllConnectedDevices();
if (devices.isEmpty) {
......
// Copyright 2016 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 '../artifacts.dart';
import '../base/globals.dart';
import '../runner/flutter_command.dart';
import '../runner/version.dart';
class DoctorCommand extends FlutterCommand {
final String name = 'doctor';
final String description = 'Diagnose the flutter tool.';
bool get requiresProjectRoot => false;
Future<int> runInProject() async {
// general info
String flutterRoot = ArtifactStore.flutterRoot;
printStatus('Flutter root is $flutterRoot.');
printStatus('');
// doctor
doctor.diagnose();
printStatus('');
// version
printStatus(getVersion(flutterRoot));
return 0;
}
}
// Copyright 2016 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 'android/android_workflow.dart';
import 'base/context.dart';
import 'base/globals.dart';
import 'ios/ios_workflow.dart';
class Doctor {
Doctor() {
_iosWorkflow = new IOSWorkflow();
if (_iosWorkflow.appliesToHostPlatform)
_workflows.add(_iosWorkflow);
_androidWorkflow = new AndroidWorkflow();
if (_androidWorkflow.appliesToHostPlatform)
_workflows.add(_androidWorkflow);
}
static void initGlobal() {
context[Doctor] = new Doctor();
}
IOSWorkflow _iosWorkflow;
AndroidWorkflow _androidWorkflow;
/// This can return null for platforms that don't support developing for iOS.
IOSWorkflow get iosWorkflow => _iosWorkflow;
AndroidWorkflow get androidWorkflow => _androidWorkflow;
List<Workflow> _workflows = <Workflow>[];
List<Workflow> get workflows => _workflows;
/// Print verbose information about the state of installed tooling.
void diagnose() {
for (int i = 0; i < workflows.length; i++) {
if (i > 0)
printStatus('');
workflows[i].diagnose();
}
}
bool get canListAnything => workflows.any((Workflow workflow) => workflow.canListDevices);
bool get canLaunchAnything => workflows.any((Workflow workflow) => workflow.canLaunchDevices);
}
/// A series of tools and required install steps for a target platform (iOS or Android).
abstract class Workflow {
Workflow(this.name);
final String name;
/// Whether the workflow applies to this platform (as in, should we ever try and use it).
bool get appliesToHostPlatform;
/// Are we functional enough to list devices?
bool get canListDevices;
/// Could this thing launch *something*? It may still have minor issues.
bool get canLaunchDevices;
/// Print verbose information about the state of the workflow.
void diagnose();
String toString() => name;
}
enum ValidationType {
missing,
partial,
installed
}
typedef ValidationType ValidationFunction();
class Validator {
Validator(this.name, { this.description, this.resolution, this.validatorFunction });
final String name;
final String description;
final String resolution;
final ValidationFunction validatorFunction;
List<Validator> _children = [];
ValidationResult validate() {
if (validatorFunction != null)
return new ValidationResult(validatorFunction(), this);
List<ValidationResult> results = _children.map((Validator child) {
return child.validate();
}).toList();
ValidationType type = _combine(results.map((ValidationResult result) {
return result.type;
}));
return new ValidationResult(type, this, results);
}
ValidationType _combine(Iterable<ValidationType> types) {
if (types.contains(ValidationType.missing) && types.contains(ValidationType.installed))
return ValidationType.partial;
if (types.contains(ValidationType.missing))
return ValidationType.missing;
return ValidationType.installed;
}
void addValidator(Validator validator) => _children.add(validator);
}
class ValidationResult {
ValidationResult(this.type, this.validator, [this.childResults = const <ValidationResult>[]]);
final ValidationType type;
final Validator validator;
final List<ValidationResult> childResults;
void print([String indent = '']) {
printSelf(indent);
for (ValidationResult child in childResults)
child.print(indent + ' ');
}
void printSelf(String indent) {
String result = indent;
if (type == ValidationType.missing)
result += '[ ] ';
else if (type == ValidationType.installed)
result += '[✓] ';
else
result += '[-] ';
result += '${validator.name} ';
if (validator.description != null)
result += '- ${validator.description} ';
if (type == ValidationType.missing)
result += '(missing)';
else if (type == ValidationType.installed)
result += '(installed)';
printStatus(result);
if (type == ValidationType.missing && validator.resolution != null)
printStatus('$indent ${validator.resolution}');
}
}
......@@ -86,6 +86,9 @@ class IOSDevice extends Device {
bool get supportsStartPaused => false;
static List<IOSDevice> getAttachedDevices([IOSDevice mockIOS]) {
if (!doctor.iosWorkflow.hasIdeviceId)
return <IOSDevice>[];
List<IOSDevice> devices = [];
for (String id in _getAttachedDeviceIDs(mockIOS)) {
String name = _getDeviceName(id, mockIOS);
......@@ -176,7 +179,7 @@ class IOSDevice extends Device {
// Step 1: Install the precompiled application if necessary
bool buildResult = await _buildIOSXcodeProject(app, buildForDevice: true);
if (!buildResult) {
printError('Could not build the precompiled application for the device');
printError('Could not build the precompiled application for the device.');
return false;
}
......@@ -184,7 +187,7 @@ class IOSDevice extends Device {
Directory bundle = new Directory(path.join(app.localPath, 'build', 'Release-iphoneos', 'Runner.app'));
bool bundleExists = bundle.existsSync();
if (!bundleExists) {
printError('Could not find the built application bundle at ${bundle.path}');
printError('Could not find the built application bundle at ${bundle.path}.');
return false;
}
......@@ -202,7 +205,7 @@ class IOSDevice extends Device {
]);
if (installationResult != 0) {
printError('Could not install ${bundle.path} on $id');
printError('Could not install ${bundle.path} on $id.');
return false;
}
......@@ -246,6 +249,9 @@ class IOSSimulator extends Device {
IOSSimulator(String id, { this.name }) : super(id);
static List<IOSSimulator> getAttachedDevices() {
if (!xcode.isInstalled)
return <IOSSimulator>[];
return SimControl.getConnectedDevices().map((SimDevice device) {
return new IOSSimulator(device.udid, name: device.name);
}).toList();
......@@ -317,7 +323,7 @@ class IOSSimulator extends Device {
// Step 1: Build the Xcode project
bool buildResult = await _buildIOSXcodeProject(app, buildForDevice: false);
if (!buildResult) {
printError('Could not build the application for the simulator');
printError('Could not build the application for the simulator.');
return false;
}
......@@ -325,7 +331,7 @@ class IOSSimulator extends Device {
Directory bundle = new Directory(path.join(app.localPath, 'build', 'Release-iphonesimulator', 'Runner.app'));
bool bundleExists = await bundle.exists();
if (!bundleExists) {
printError('Could not find the built application bundle at ${bundle.path}');
printError('Could not find the built application bundle at ${bundle.path}.');
return false;
}
......@@ -476,7 +482,8 @@ class _IOSSimulatorLogReader extends DeviceLogReader {
String category = match.group(1);
String content = match.group(2);
if (category == 'Game Center' || category == 'itunesstored' || category == 'nanoregistrylaunchd' ||
category == 'mstreamd' || category == 'syncdefaultsd' || category == 'companionappd' || category == 'searchd')
category == 'mstreamd' || category == 'syncdefaultsd' || category == 'companionappd' ||
category == 'searchd')
return null;
_lastWasFiltered = false;
......
// Copyright 2016 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:io';
import '../base/globals.dart';
import '../base/process.dart';
import '../doctor.dart';
class IOSWorkflow extends Workflow {
IOSWorkflow() : super('iOS');
bool get appliesToHostPlatform => Platform.isMacOS;
// We need xcode (+simctl) to list simulator devices, and idevice_id to list real devices.
bool get canListDevices => xcode.isInstalled;
// We need xcode to launch simulator devices, and ideviceinstaller and ios-deploy
// for real devices.
bool get canLaunchDevices => xcode.isInstalled;
void diagnose() {
Validator iosValidator = new Validator('Develop for iOS devices');
Function _xcodeExists = () {
return xcode.isInstalled ? ValidationType.installed : ValidationType.missing;
};
Function _brewExists = () {
return exitsHappy(<String>['brew', '-v'])
? ValidationType.installed : ValidationType.missing;
};
Function _ideviceinstallerExists = () {
return exitsHappy(<String>['ideviceinstaller', '-h'])
? ValidationType.installed : ValidationType.missing;
};
Function _iosdeployExists = () {
return hasIdeviceId ? ValidationType.installed : ValidationType.missing;
};
iosValidator.addValidator(new Validator(
'XCode',
description: 'enable development for iOS devices',
resolution: 'Download at https://developer.apple.com/xcode/download/',
validatorFunction: _xcodeExists
));
iosValidator.addValidator(new Validator(
'brew',
description: 'install additional development packages',
resolution: 'Download at http://brew.sh/',
validatorFunction: _brewExists
));
iosValidator.addValidator(new Validator(
'ideviceinstaller',
description: 'discover connected iOS devices',
resolution: "Install via 'brew install ideviceinstaller'",
validatorFunction: _ideviceinstallerExists
));
iosValidator.addValidator(new Validator(
'ios-deploy',
description: 'deploy to connected iOS devices',
resolution: "Install via 'brew install ios-deploy'",
validatorFunction: _iosdeployExists
));
iosValidator.validate().print();
}
bool get hasIdeviceId => exitsHappy(<String>['idevice_id', '-h']);
/// Return whether the tooling to list and deploy to real iOS devices (not the
/// simulator) is installed on the user's machine.
bool get canWorkWithIOSDevices {
return exitsHappy(<String>['ideviceinstaller', '-h']) && hasIdeviceId;
}
}
// Copyright 2016 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 '../base/context.dart';
import '../base/process.dart';
class XCode {
static void initGlobal() {
context[XCode] = new XCode();
}
bool get isInstalled => exitsHappy(<String>['xcode-select', '--print-path']);
}
......@@ -3,10 +3,13 @@
// found in the LICENSE file.
import 'dart:async';
import 'dart:io';
import 'package:flutter_tools/src/base/context.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/doctor.dart';
import 'package:flutter_tools/src/ios/mac.dart';
import 'package:test/test.dart';
/// Return the test logger. This assumes that the current Logger is a BufferLogger.
......@@ -29,6 +32,14 @@ void testUsingContext(String description, dynamic testMethod(), {
if (!overrides.containsKey(DeviceManager))
testContext[DeviceManager] = new MockDeviceManager();
if (!overrides.containsKey(Doctor))
testContext[Doctor] = new Doctor();
if (Platform.isMacOS) {
if (!overrides.containsKey(XCode))
testContext[XCode] = new XCode();
}
return testContext.runInZone(testMethod);
}, timeout: timeout);
}
......
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