Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
5bce2fbd
Commit
5bce2fbd
authored
Jan 21, 2016
by
Devon Carew
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
refactor platform specific code out of device.dart
remove device type specific checks
parent
0f505fbf
Changes
13
Show whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
1150 additions
and
1051 deletions
+1150
-1051
android.dart
packages/flutter_tools/lib/src/android/android.dart
+7
-0
device_android.dart
packages/flutter_tools/lib/src/android/device_android.dart
+531
-0
apk.dart
packages/flutter_tools/lib/src/commands/apk.dart
+7
-10
daemon.dart
packages/flutter_tools/lib/src/commands/daemon.dart
+1
-0
init.dart
packages/flutter_tools/lib/src/commands/init.dart
+2
-2
list.dart
packages/flutter_tools/lib/src/commands/list.dart
+4
-1
start.dart
packages/flutter_tools/lib/src/commands/start.dart
+21
-26
trace.dart
packages/flutter_tools/lib/src/commands/trace.dart
+1
-1
device.dart
packages/flutter_tools/lib/src/device.dart
+18
-993
flx.dart
packages/flutter_tools/lib/src/flx.dart
+27
-17
device_ios.dart
packages/flutter_tools/lib/src/ios/device_ios.dart
+528
-0
android_device_test.dart
packages/flutter_tools/test/android_device_test.dart
+1
-1
mocks.dart
packages/flutter_tools/test/src/mocks.dart
+2
-0
No files found.
packages/flutter_tools/lib/src/android/android.dart
0 → 100644
View file @
5bce2fbd
// 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.
const
int
minApiLevel
=
16
;
const
String
minVersionName
=
'Jelly Bean'
;
const
String
minVersionText
=
'4.1.x'
;
packages/flutter_tools/lib/src/android/device_android.dart
0 → 100644
View file @
5bce2fbd
// 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
'dart:io'
;
import
'package:crypto/crypto.dart'
;
import
'package:path/path.dart'
as
path
;
import
'../application_package.dart'
;
import
'../base/logging.dart'
;
import
'../base/process.dart'
;
import
'../build_configuration.dart'
;
import
'../device.dart'
;
import
'../flx.dart'
as
flx
;
import
'../toolchain.dart'
;
import
'android.dart'
;
class
AndroidDevice
extends
Device
{
static
const
String
_defaultAdbPath
=
'adb'
;
static
const
int
_observatoryPort
=
8181
;
static
final
String
defaultDeviceID
=
'default_android_device'
;
String
productID
;
String
modelID
;
String
deviceCodeName
;
bool
_connected
;
String
_adbPath
;
String
get
adbPath
=>
_adbPath
;
bool
_hasAdb
=
false
;
bool
_hasValidAndroid
=
false
;
factory
AndroidDevice
({
String
id:
null
,
String
productID:
null
,
String
modelID:
null
,
String
deviceCodeName:
null
,
bool
connected
})
{
AndroidDevice
device
=
Device
.
unique
(
id
??
defaultDeviceID
,
(
String
id
)
=>
new
AndroidDevice
.
fromId
(
id
));
device
.
productID
=
productID
;
device
.
modelID
=
modelID
;
device
.
deviceCodeName
=
deviceCodeName
;
if
(
connected
!=
null
)
device
.
_connected
=
connected
;
return
device
;
}
/// This constructor is intended as protected access; prefer [AndroidDevice].
AndroidDevice
.
fromId
(
id
)
:
super
.
fromId
(
id
)
{
_adbPath
=
getAdbPath
();
_hasAdb
=
_checkForAdb
();
// Checking for [minApiName] only needs to be done if we are starting an
// app, but it has an important side effect, which is to discard any
// progress messages if the adb server is restarted.
_hasValidAndroid
=
_checkForSupportedAndroidVersion
();
if
(!
_hasAdb
||
!
_hasValidAndroid
)
{
logging
.
warning
(
'Unable to run on Android.'
);
}
}
/// mockAndroid argument is only to facilitate testing with mocks, so that
/// we don't have to rely on the test setup having adb available to it.
static
List
<
AndroidDevice
>
getAttachedDevices
([
AndroidDevice
mockAndroid
])
{
List
<
AndroidDevice
>
devices
=
[];
String
adbPath
=
(
mockAndroid
!=
null
)
?
mockAndroid
.
adbPath
:
getAdbPath
();
try
{
runCheckedSync
([
adbPath
,
'version'
]);
}
catch
(
e
)
{
logging
.
severe
(
'Unable to find adb. Is "adb" in your path?'
);
return
devices
;
}
List
<
String
>
output
=
runSync
([
adbPath
,
'devices'
,
'-l'
]).
trim
().
split
(
'
\n
'
);
// 015d172c98400a03 device usb:340787200X product:nakasi model:Nexus_7 device:grouper
RegExp
deviceRegex1
=
new
RegExp
(
r'^(\S+)\s+device\s+.*product:(\S+)\s+model:(\S+)\s+device:(\S+)$'
);
// 0149947A0D01500C device usb:340787200X
RegExp
deviceRegex2
=
new
RegExp
(
r'^(\S+)\s+device\s+\S+$'
);
RegExp
unauthorizedRegex
=
new
RegExp
(
r'^(\S+)\s+unauthorized\s+\S+$'
);
RegExp
offlineRegex
=
new
RegExp
(
r'^(\S+)\s+offline\s+\S+$'
);
// Skip first line, which is always 'List of devices attached'.
for
(
String
line
in
output
.
skip
(
1
))
{
// Skip lines like:
// * daemon not running. starting it now on port 5037 *
// * daemon started successfully *
if
(
line
.
startsWith
(
'* daemon '
))
continue
;
if
(
line
.
startsWith
(
'List of devices'
))
continue
;
if
(
deviceRegex1
.
hasMatch
(
line
))
{
Match
match
=
deviceRegex1
.
firstMatch
(
line
);
String
deviceID
=
match
[
1
];
String
productID
=
match
[
2
];
String
modelID
=
match
[
3
];
String
deviceCodeName
=
match
[
4
];
devices
.
add
(
new
AndroidDevice
(
id:
deviceID
,
productID:
productID
,
modelID:
modelID
,
deviceCodeName:
deviceCodeName
));
}
else
if
(
deviceRegex2
.
hasMatch
(
line
))
{
Match
match
=
deviceRegex2
.
firstMatch
(
line
);
String
deviceID
=
match
[
1
];
devices
.
add
(
new
AndroidDevice
(
id:
deviceID
));
}
else
if
(
unauthorizedRegex
.
hasMatch
(
line
))
{
Match
match
=
unauthorizedRegex
.
firstMatch
(
line
);
String
deviceID
=
match
[
1
];
logging
.
warning
(
'Device
$deviceID
is not authorized.
\n
'
'You might need to check your device for an authorization dialog.'
);
}
else
if
(
offlineRegex
.
hasMatch
(
line
))
{
Match
match
=
offlineRegex
.
firstMatch
(
line
);
String
deviceID
=
match
[
1
];
logging
.
warning
(
'Device
$deviceID
is offline.'
);
}
else
{
logging
.
warning
(
'Unexpected failure parsing device information from adb output:
\n
'
'
$line
\n
'
'Please report a bug at https://github.com/flutter/flutter/issues/new'
);
}
}
return
devices
;
}
static
String
getAndroidSdkPath
()
{
if
(
Platform
.
environment
.
containsKey
(
'ANDROID_HOME'
))
{
String
androidHomeDir
=
Platform
.
environment
[
'ANDROID_HOME'
];
if
(
FileSystemEntity
.
isDirectorySync
(
path
.
join
(
androidHomeDir
,
'platform-tools'
)))
{
return
androidHomeDir
;
}
else
if
(
FileSystemEntity
.
isDirectorySync
(
path
.
join
(
androidHomeDir
,
'sdk'
,
'platform-tools'
)))
{
return
path
.
join
(
androidHomeDir
,
'sdk'
);
}
else
{
logging
.
warning
(
'Android SDK not found at
$androidHomeDir
'
);
return
null
;
}
}
else
{
logging
.
warning
(
'Android SDK not found. The ANDROID_HOME variable must be set.'
);
return
null
;
}
}
static
String
getAdbPath
()
{
if
(
Platform
.
environment
.
containsKey
(
'ANDROID_HOME'
))
{
String
androidHomeDir
=
Platform
.
environment
[
'ANDROID_HOME'
];
String
adbPath1
=
path
.
join
(
androidHomeDir
,
'sdk'
,
'platform-tools'
,
'adb'
);
String
adbPath2
=
path
.
join
(
androidHomeDir
,
'platform-tools'
,
'adb'
);
if
(
FileSystemEntity
.
isFileSync
(
adbPath1
))
{
return
adbPath1
;
}
else
if
(
FileSystemEntity
.
isFileSync
(
adbPath2
))
{
return
adbPath2
;
}
else
{
logging
.
info
(
'"adb" not found at
\n
"
$adbPath1
" or
\n
"
$adbPath2
"
\n
'
+
'using default path "
$_defaultAdbPath
"'
);
return
_defaultAdbPath
;
}
}
else
{
return
_defaultAdbPath
;
}
}
List
<
String
>
adbCommandForDevice
(
List
<
String
>
args
)
{
List
<
String
>
result
=
<
String
>[
adbPath
];
if
(
id
!=
defaultDeviceID
)
{
result
.
addAll
([
'-s'
,
id
]);
}
result
.
addAll
(
args
);
return
result
;
}
bool
_isValidAdbVersion
(
String
adbVersion
)
{
// Sample output: 'Android Debug Bridge version 1.0.31'
Match
versionFields
=
new
RegExp
(
r'(\d+)\.(\d+)\.(\d+)'
).
firstMatch
(
adbVersion
);
if
(
versionFields
!=
null
)
{
int
majorVersion
=
int
.
parse
(
versionFields
[
1
]);
int
minorVersion
=
int
.
parse
(
versionFields
[
2
]);
int
patchVersion
=
int
.
parse
(
versionFields
[
3
]);
if
(
majorVersion
>
1
)
{
return
true
;
}
if
(
majorVersion
==
1
&&
minorVersion
>
0
)
{
return
true
;
}
if
(
majorVersion
==
1
&&
minorVersion
==
0
&&
patchVersion
>=
32
)
{
return
true
;
}
return
false
;
}
logging
.
warning
(
'Unrecognized adb version string
$adbVersion
. Skipping version check.'
);
return
true
;
}
bool
_checkForAdb
()
{
try
{
String
adbVersion
=
runCheckedSync
([
adbPath
,
'version'
]);
if
(
_isValidAdbVersion
(
adbVersion
))
{
return
true
;
}
String
locatedAdbPath
=
runCheckedSync
([
'which'
,
'adb'
]);
logging
.
severe
(
'"
$locatedAdbPath
" is too old. '
'Please install version 1.0.32 or later.
\n
'
'Try setting ANDROID_HOME to the path to your Android SDK install. '
'Android builds are unavailable.'
);
}
catch
(
e
,
stack
)
{
logging
.
severe
(
'"adb" not found in
\
$PATH
. '
'Please install the Android SDK or set ANDROID_HOME '
'to the path of your Android SDK install.'
);
logging
.
info
(
e
);
logging
.
info
(
stack
);
}
return
false
;
}
bool
_checkForSupportedAndroidVersion
()
{
try
{
// If the server is automatically restarted, then we get irrelevant
// output lines like this, which we want to ignore:
// adb server is out of date. killing..
// * daemon started successfully *
runCheckedSync
(
adbCommandForDevice
([
'start-server'
]));
String
ready
=
runSync
(
adbCommandForDevice
([
'shell'
,
'echo'
,
'ready'
]));
if
(
ready
.
trim
()
!=
'ready'
)
{
logging
.
info
(
'Android device not found.'
);
return
false
;
}
// Sample output: '22'
String
sdkVersion
=
runCheckedSync
(
adbCommandForDevice
([
'shell'
,
'getprop'
,
'ro.build.version.sdk'
]))
.
trimRight
();
int
sdkVersionParsed
=
int
.
parse
(
sdkVersion
,
onError:
(
String
source
)
=>
null
);
if
(
sdkVersionParsed
==
null
)
{
logging
.
severe
(
'Unexpected response from getprop: "
$sdkVersion
"'
);
return
false
;
}
if
(
sdkVersionParsed
<
minApiLevel
)
{
logging
.
severe
(
'The Android version (
$sdkVersion
) on the target device is too old. Please '
'use a
$minVersionName
(version
$minApiLevel
/
$minVersionText
) device or later.'
);
return
false
;
}
return
true
;
}
catch
(
e
)
{
logging
.
severe
(
'Unexpected failure from adb: '
,
e
);
}
return
false
;
}
String
_getDeviceSha1Path
(
ApplicationPackage
app
)
{
return
'/data/local/tmp/sky.
${app.id}
.sha1'
;
}
String
_getDeviceApkSha1
(
ApplicationPackage
app
)
{
return
runCheckedSync
(
adbCommandForDevice
([
'shell'
,
'cat'
,
_getDeviceSha1Path
(
app
)]));
}
String
_getSourceSha1
(
ApplicationPackage
app
)
{
var
sha1
=
new
SHA1
();
var
file
=
new
File
(
app
.
localPath
);
sha1
.
add
(
file
.
readAsBytesSync
());
return
CryptoUtils
.
bytesToHex
(
sha1
.
close
());
}
String
get
name
=>
modelID
;
@override
bool
isAppInstalled
(
ApplicationPackage
app
)
{
if
(!
isConnected
())
{
return
false
;
}
if
(
runCheckedSync
(
adbCommandForDevice
([
'shell'
,
'pm'
,
'path'
,
app
.
id
]))
==
''
)
{
logging
.
info
(
'TODO(iansf): move this log to the caller.
${app.name}
is not on the device. Installing now...'
);
return
false
;
}
if
(
_getDeviceApkSha1
(
app
)
!=
_getSourceSha1
(
app
))
{
logging
.
info
(
'TODO(iansf): move this log to the caller.
${app.name}
is out of date. Installing now...'
);
return
false
;
}
return
true
;
}
@override
bool
installApp
(
ApplicationPackage
app
)
{
if
(!
isConnected
())
{
logging
.
info
(
'Android device not connected. Not installing.'
);
return
false
;
}
if
(!
FileSystemEntity
.
isFileSync
(
app
.
localPath
))
{
logging
.
severe
(
'"
${app.localPath}
" does not exist.'
);
return
false
;
}
print
(
'Installing
${app.name}
on device.'
);
runCheckedSync
(
adbCommandForDevice
([
'install'
,
'-r'
,
app
.
localPath
]));
runCheckedSync
(
adbCommandForDevice
([
'shell'
,
'echo'
,
'-n'
,
_getSourceSha1
(
app
),
'>'
,
_getDeviceSha1Path
(
app
)]));
return
true
;
}
void
_forwardObservatoryPort
()
{
// Set up port forwarding for observatory.
String
portString
=
'tcp:
$_observatoryPort
'
;
try
{
runCheckedSync
(
adbCommandForDevice
([
'forward'
,
portString
,
portString
]));
}
catch
(
e
)
{
logging
.
warning
(
'Unable to forward observatory port (
$_observatoryPort
):
\n
$e
'
);
}
}
bool
startBundle
(
AndroidApk
apk
,
String
bundlePath
,
{
bool
poke:
false
,
bool
checked:
true
,
bool
traceStartup:
false
,
String
route
,
bool
clearLogs:
false
})
{
logging
.
fine
(
'
$this
startBundle'
);
if
(!
FileSystemEntity
.
isFileSync
(
bundlePath
))
{
logging
.
severe
(
'Cannot find
$bundlePath
'
);
return
false
;
}
if
(!
poke
)
_forwardObservatoryPort
();
if
(
clearLogs
)
this
.
clearLogs
();
String
deviceTmpPath
=
'/data/local/tmp/dev.flx'
;
runCheckedSync
(
adbCommandForDevice
([
'push'
,
bundlePath
,
deviceTmpPath
]));
List
<
String
>
cmd
=
adbCommandForDevice
([
'shell'
,
'am'
,
'start'
,
'-a'
,
'android.intent.action.RUN'
,
'-d'
,
deviceTmpPath
,
]);
if
(
checked
)
cmd
.
addAll
([
'--ez'
,
'enable-checked-mode'
,
'true'
]);
if
(
traceStartup
)
cmd
.
addAll
([
'--ez'
,
'trace-startup'
,
'true'
]);
if
(
route
!=
null
)
cmd
.
addAll
([
'--es'
,
'route'
,
route
]);
cmd
.
add
(
apk
.
launchActivity
);
runCheckedSync
(
cmd
);
return
true
;
}
@override
Future
<
bool
>
startApp
(
ApplicationPackage
package
,
Toolchain
toolchain
,
{
String
mainPath
,
String
route
,
bool
checked:
true
,
Map
<
String
,
dynamic
>
platformArgs
})
{
return
flx
.
buildInTempDir
(
toolchain
,
mainPath:
mainPath
).
then
((
flx
.
DirectoryResult
buildResult
)
{
logging
.
fine
(
'Starting bundle for
$this
.'
);
try
{
if
(
startBundle
(
package
,
buildResult
.
localBundlePath
,
poke:
platformArgs
[
'poke'
],
checked:
checked
,
traceStartup:
platformArgs
[
'trace-startup'
],
route:
route
,
clearLogs:
platformArgs
[
'clear-logs'
]
))
{
return
true
;
}
else
{
return
false
;
}
}
finally
{
buildResult
.
dispose
();
}
});
}
Future
<
bool
>
stopApp
(
ApplicationPackage
app
)
async
{
final
AndroidApk
apk
=
app
;
runSync
(
adbCommandForDevice
([
'shell'
,
'am'
,
'force-stop'
,
apk
.
id
]));
return
true
;
}
@override
TargetPlatform
get
platform
=>
TargetPlatform
.
android
;
void
clearLogs
()
{
runSync
(
adbCommandForDevice
([
'logcat'
,
'-c'
]));
}
Future
<
int
>
logs
({
bool
clear:
false
})
async
{
if
(!
isConnected
())
{
return
2
;
}
if
(
clear
)
{
clearLogs
();
}
return
await
runCommandAndStreamOutput
(
adbCommandForDevice
([
'logcat'
,
'-v'
,
'tag'
,
// Only log the tag and the message
'-s'
,
'flutter:V'
,
'ActivityManager:W'
,
'System.err:W'
,
'*:F'
,
]),
prefix:
'android: '
);
}
void
startTracing
(
AndroidApk
apk
)
{
runCheckedSync
(
adbCommandForDevice
([
'shell'
,
'am'
,
'broadcast'
,
'-a'
,
'
${apk.id}
.TRACING_START'
]));
}
static
String
_threeDigits
(
int
n
)
{
if
(
n
>=
100
)
return
"
$n
"
;
if
(
n
>=
10
)
return
"0
$n
"
;
return
"00
$n
"
;
}
static
String
_twoDigits
(
int
n
)
{
if
(
n
>=
10
)
return
"
$n
"
;
return
"0
$n
"
;
}
static
String
_logcatDateFormat
(
DateTime
dt
)
{
// Doing this manually, instead of using package:intl for simplicity.
// adb logcat -T wants "%m-%d %H:%M:%S.%3q"
String
m
=
_twoDigits
(
dt
.
month
);
String
d
=
_twoDigits
(
dt
.
day
);
String
H
=
_twoDigits
(
dt
.
hour
);
String
M
=
_twoDigits
(
dt
.
minute
);
String
S
=
_twoDigits
(
dt
.
second
);
String
q
=
_threeDigits
(
dt
.
millisecond
);
return
"
$m
-
$d
$H
:
$M
:
$S
.
$q
"
;
}
// TODO(eseidel): This is fragile, there must be a better way!
DateTime
timeOnDevice
()
{
// Careful: Android's date command is super-lame, any arguments are taken as
// attempts to set the timezone and will screw your device.
String
output
=
runCheckedSync
(
adbCommandForDevice
([
'shell'
,
'date'
])).
trim
();
// format: Fri Dec 18 13:22:07 PST 2015
// intl doesn't handle timezones: https://github.com/dart-lang/intl/issues/93
// So we use the local date command to parse dates for us.
String
seconds
=
runSync
([
'date'
,
'--date'
,
output
,
'+%s'
]);
// Although '%s' is supposed to be UTC, date appears to be ignoring the
// timezone in the passed string, so using isUTC: false here.
return
new
DateTime
.
fromMillisecondsSinceEpoch
(
int
.
parse
(
seconds
)
*
1000
,
isUtc:
false
);
}
String
stopTracing
(
AndroidApk
apk
,
{
String
outPath:
null
})
{
// Workaround for logcat -c not always working:
// http://stackoverflow.com/questions/25645012/logcat-on-android-l-not-clearing-after-unplugging-and-reconnecting
String
beforeStop
=
_logcatDateFormat
(
timeOnDevice
());
runCheckedSync
(
adbCommandForDevice
([
'shell'
,
'am'
,
'broadcast'
,
'-a'
,
'
${apk.id}
.TRACING_STOP'
]));
RegExp
traceRegExp
=
new
RegExp
(
r'Saving trace to (\S+)'
,
multiLine:
true
);
RegExp
completeRegExp
=
new
RegExp
(
r'Trace complete'
,
multiLine:
true
);
String
tracePath
=
null
;
bool
isComplete
=
false
;
while
(!
isComplete
)
{
String
logs
=
runCheckedSync
(
adbCommandForDevice
([
'logcat'
,
'-d'
,
'-T'
,
beforeStop
]));
Match
fileMatch
=
traceRegExp
.
firstMatch
(
logs
);
if
(
fileMatch
!=
null
&&
fileMatch
[
1
]
!=
null
)
{
tracePath
=
fileMatch
[
1
];
}
isComplete
=
completeRegExp
.
hasMatch
(
logs
);
}
if
(
tracePath
!=
null
)
{
String
localPath
=
(
outPath
!=
null
)
?
outPath
:
path
.
basename
(
tracePath
);
runCheckedSync
(
adbCommandForDevice
([
'root'
]));
runSync
(
adbCommandForDevice
([
'shell'
,
'run-as'
,
apk
.
id
,
'chmod'
,
'777'
,
tracePath
]));
runCheckedSync
(
adbCommandForDevice
([
'pull'
,
tracePath
,
localPath
]));
runSync
(
adbCommandForDevice
([
'shell'
,
'rm'
,
tracePath
]));
return
localPath
;
}
logging
.
warning
(
'No trace file detected. '
'Did you remember to start the trace before stopping it?'
);
return
null
;
}
bool
isConnected
()
=>
_connected
!=
null
?
_connected
:
_hasValidAndroid
;
void
setConnected
(
bool
value
)
{
_connected
=
value
;
}
}
packages/flutter_tools/lib/src/commands/apk.dart
View file @
5bce2fbd
...
...
@@ -9,12 +9,12 @@ import 'dart:io';
import
'package:path/path.dart'
as
path
;
import
'package:yaml/yaml.dart'
;
import
'../android/device_android.dart'
;
import
'../artifacts.dart'
;
import
'../base/file_system.dart'
;
import
'../base/logging.dart'
;
import
'../base/process.dart'
;
import
'../build_configuration.dart'
;
import
'../device.dart'
;
import
'../flx.dart'
as
flx
;
import
'../runner/flutter_command.dart'
;
import
'start.dart'
;
...
...
@@ -392,16 +392,13 @@ class ApkCommand extends FlutterCommand {
String
mainPath
=
findMainDartFile
(
argResults
[
'target'
]);
// Build the FLX.
int
result
;
await
flx
.
buildInTempDir
(
toolchain
,
mainPath:
mainPath
,
onBundleAvailable:
(
String
localBundlePath
)
{
result
=
_buildApk
(
components
,
localBundlePath
);
}
);
flx
.
DirectoryResult
buildResult
=
await
flx
.
buildInTempDir
(
toolchain
,
mainPath:
mainPath
);
return
result
;
try
{
return
_buildApk
(
components
,
buildResult
.
localBundlePath
);
}
finally
{
buildResult
.
dispose
();
}
}
}
}
packages/flutter_tools/lib/src/commands/daemon.dart
View file @
5bce2fbd
...
...
@@ -7,6 +7,7 @@ import 'dart:convert';
import
'dart:io'
;
import
'../android/adb.dart'
;
import
'../android/device_android.dart'
;
import
'../base/logging.dart'
;
import
'../device.dart'
;
import
'../runner/flutter_command.dart'
;
...
...
packages/flutter_tools/lib/src/commands/init.dart
View file @
5bce2fbd
...
...
@@ -9,10 +9,10 @@ import 'package:args/command_runner.dart';
import
'package:mustache4dart/mustache4dart.dart'
as
mustache
;
import
'package:path/path.dart'
as
path
;
import
'../android/android.dart'
as
android
;
import
'../artifacts.dart'
;
import
'../base/logging.dart'
;
import
'../base/process.dart'
;
import
'../device.dart'
;
class
InitCommand
extends
Command
{
final
String
name
=
'init'
;
...
...
@@ -247,7 +247,7 @@ final String _apkManifest = '''
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.{{projectName}}">
<uses-sdk android:minSdkVersion="
${
AndroidDevice
.minApiLevel}
" android:targetSdkVersion="21" />
<uses-sdk android:minSdkVersion="
${
android
.minApiLevel}
" android:targetSdkVersion="21" />
<uses-permission android:name="android.permission.INTERNET"/>
<application android:name="org.domokit.sky.shell.SkyApplication" android:label="{{projectName}}">
...
...
packages/flutter_tools/lib/src/commands/list.dart
View file @
5bce2fbd
...
...
@@ -4,7 +4,8 @@
import
'dart:async'
;
import
'../device.dart'
;
import
'../android/device_android.dart'
;
import
'../ios/device_ios.dart'
;
import
'../runner/flutter_command.dart'
;
class
ListCommand
extends
FlutterCommand
{
...
...
@@ -29,6 +30,8 @@ class ListCommand extends FlutterCommand {
if
(
details
)
print
(
'Android Devices:'
);
// TODO(devoncarew): We should have a more generic mechanism for device discovery.
// DeviceDiscoveryService? DeviceDiscoveryParticipant?
for
(
AndroidDevice
device
in
AndroidDevice
.
getAttachedDevices
(
devices
.
android
))
{
if
(
details
)
{
print
(
'
${device.id}
\t
'
...
...
packages/flutter_tools/lib/src/commands/start.dart
View file @
5bce2fbd
...
...
@@ -9,9 +9,7 @@ import 'package:path/path.dart' as path;
import
'../application_package.dart'
;
import
'../base/logging.dart'
;
import
'../build_configuration.dart'
;
import
'../device.dart'
;
import
'../flx.dart'
as
flx
;
import
'../runner/flutter_command.dart'
;
import
'../toolchain.dart'
;
import
'install.dart'
;
...
...
@@ -138,33 +136,30 @@ Future<int> startApp(
logging
.
fine
(
'Running build command for
$device
.'
);
if
(
device
.
platform
==
TargetPlatform
.
android
)
{
await
flx
.
buildInTempDir
(
Map
<
String
,
dynamic
>
platformArgs
=
<
String
,
dynamic
>{};
if
(
poke
!=
null
)
platformArgs
[
'poke'
]
=
poke
;
if
(
traceStartup
!=
null
)
platformArgs
[
'trace-startup'
]
=
traceStartup
;
if
(
clearLogs
!=
null
)
platformArgs
[
'clear-logs'
]
=
clearLogs
;
bool
result
=
await
device
.
startApp
(
package
,
toolchain
,
mainPath:
mainPath
,
onBundleAvailable:
(
String
localBundlePath
)
{
logging
.
fine
(
'Starting bundle for
$device
.'
);
final
AndroidDevice
androidDevice
=
device
;
// https://github.com/flutter/flutter/issues/1035
if
(
androidDevice
.
startBundle
(
package
,
localBundlePath
,
poke:
poke
,
checked:
checked
,
traceStartup:
traceStartup
,
route:
route
,
clearLogs:
clearLogs
))
{
startedSomething
=
true
;
}
}
checked:
checked
,
platformArgs:
platformArgs
);
}
else
{
bool
result
=
await
device
.
startApp
(
package
);
if
(!
result
)
{
logging
.
severe
(
'Could not start
\'
${package.name}
\'
on
\'
${device.id}
\'
'
);
}
else
{
startedSomething
=
true
;
}
}
}
if
(!
startedSomething
)
{
if
(!
devices
.
all
.
any
((
device
)
=>
device
.
isConnected
()))
{
...
...
packages/flutter_tools/lib/src/commands/trace.dart
View file @
5bce2fbd
...
...
@@ -4,9 +4,9 @@
import
'dart:async'
;
import
'../android/device_android.dart'
;
import
'../application_package.dart'
;
import
'../base/logging.dart'
;
import
'../device.dart'
;
import
'../runner/flutter_command.dart'
;
class
TraceCommand
extends
FlutterCommand
{
...
...
packages/flutter_tools/lib/src/device.dart
View file @
5bce2fbd
...
...
@@ -3,21 +3,19 @@
// found in the LICENSE file.
import
'dart:async'
;
import
'dart:io'
;
import
'package:crypto/crypto.dart'
;
import
'package:path/path.dart'
as
path
;
import
'android/device_android.dart'
;
import
'application_package.dart'
;
import
'base/logging.dart'
;
import
'base/process.dart'
;
import
'build_configuration.dart'
;
import
'ios/device_ios.dart'
;
import
'toolchain.dart'
;
abstract
class
Device
{
final
String
id
;
static
Map
<
String
,
Device
>
_deviceCache
=
{};
static
Device
_
unique
(
String
id
,
Device
constructor
(
String
id
))
{
static
Device
unique
(
String
id
,
Device
constructor
(
String
id
))
{
return
_deviceCache
.
putIfAbsent
(
id
,
()
=>
constructor
(
id
));
}
...
...
@@ -25,7 +23,7 @@ abstract class Device {
_deviceCache
.
remove
(
id
);
}
Device
.
_
(
this
.
id
);
Device
.
fromId
(
this
.
id
);
String
get
name
;
...
...
@@ -42,984 +40,25 @@ abstract class Device {
Future
<
int
>
logs
({
bool
clear:
false
});
/// Start an app package on the current device
Future
<
bool
>
startApp
(
ApplicationPackage
app
);
/// Start an app package on the current device.
///
/// [platformArgs] allows callers to pass platform-specific arguments to the
/// start call.
Future
<
bool
>
startApp
(
ApplicationPackage
package
,
Toolchain
toolchain
,
{
String
mainPath
,
String
route
,
bool
checked:
true
,
Map
<
String
,
dynamic
>
platformArgs
});
/// Stop an app package on the current device
/// Stop an app package on the current device
.
Future
<
bool
>
stopApp
(
ApplicationPackage
app
);
String
toString
()
=>
'
$runtimeType
$id
'
;
}
class
IOSDevice
extends
Device
{
static
final
String
defaultDeviceID
=
'default_ios_id'
;
static
const
String
_macInstructions
=
'To work with iOS devices, please install ideviceinstaller. '
'If you use homebrew, you can install it with '
'"
\$
brew install ideviceinstaller".'
;
static
const
String
_linuxInstructions
=
'To work with iOS devices, please install ideviceinstaller. '
'On Ubuntu or Debian, you can install it with '
'"
\$
apt-get install ideviceinstaller".'
;
String
_installerPath
;
String
get
installerPath
=>
_installerPath
;
String
_listerPath
;
String
get
listerPath
=>
_listerPath
;
String
_informerPath
;
String
get
informerPath
=>
_informerPath
;
String
_debuggerPath
;
String
get
debuggerPath
=>
_debuggerPath
;
String
_loggerPath
;
String
get
loggerPath
=>
_loggerPath
;
String
_pusherPath
;
String
get
pusherPath
=>
_pusherPath
;
String
_name
;
String
get
name
=>
_name
;
factory
IOSDevice
({
String
id
,
String
name
})
{
IOSDevice
device
=
Device
.
_unique
(
id
??
defaultDeviceID
,
(
String
id
)
=>
new
IOSDevice
.
_
(
id
));
device
.
_name
=
name
;
return
device
;
}
IOSDevice
.
_
(
String
id
)
:
super
.
_
(
id
)
{
_installerPath
=
_checkForCommand
(
'ideviceinstaller'
);
_listerPath
=
_checkForCommand
(
'idevice_id'
);
_informerPath
=
_checkForCommand
(
'ideviceinfo'
);
_debuggerPath
=
_checkForCommand
(
'idevicedebug'
);
_loggerPath
=
_checkForCommand
(
'idevicesyslog'
);
_pusherPath
=
_checkForCommand
(
'ios-deploy'
,
'To copy files to iOS devices, please install ios-deploy. '
'You can do this using homebrew as follows:
\n
'
'
\$
brew tap flutter/flutter
\n
'
'
\$
brew install ios-deploy'
);
}
static
List
<
IOSDevice
>
getAttachedDevices
([
IOSDevice
mockIOS
])
{
List
<
IOSDevice
>
devices
=
[];
for
(
String
id
in
_getAttachedDeviceIDs
(
mockIOS
))
{
String
name
=
_getDeviceName
(
id
,
mockIOS
);
devices
.
add
(
new
IOSDevice
(
id:
id
,
name:
name
));
}
return
devices
;
}
static
Iterable
<
String
>
_getAttachedDeviceIDs
([
IOSDevice
mockIOS
])
{
String
listerPath
=
(
mockIOS
!=
null
)
?
mockIOS
.
listerPath
:
_checkForCommand
(
'idevice_id'
);
String
output
;
try
{
output
=
runSync
([
listerPath
,
'-l'
]);
}
catch
(
e
)
{
return
[];
}
return
output
.
trim
()
.
split
(
'
\n
'
)
.
where
((
String
s
)
=>
s
!=
null
&&
s
.
length
>
0
);
}
static
String
_getDeviceName
(
String
deviceID
,
[
IOSDevice
mockIOS
])
{
String
informerPath
=
(
mockIOS
!=
null
)
?
mockIOS
.
informerPath
:
_checkForCommand
(
'ideviceinfo'
);
return
runSync
([
informerPath
,
'-k'
,
'DeviceName'
,
'-u'
,
deviceID
]);
}
static
final
Map
<
String
,
String
>
_commandMap
=
{};
static
String
_checkForCommand
(
String
command
,
[
String
macInstructions
=
_macInstructions
,
String
linuxInstructions
=
_linuxInstructions
])
{
return
_commandMap
.
putIfAbsent
(
command
,
()
{
try
{
command
=
runCheckedSync
([
'which'
,
command
]).
trim
();
}
catch
(
e
)
{
if
(
Platform
.
isMacOS
)
{
logging
.
severe
(
macInstructions
);
}
else
if
(
Platform
.
isLinux
)
{
logging
.
severe
(
linuxInstructions
);
}
else
{
logging
.
severe
(
'
$command
is not available on your platform.'
);
}
}
return
command
;
});
}
@override
bool
installApp
(
ApplicationPackage
app
)
{
try
{
if
(
id
==
defaultDeviceID
)
{
runCheckedSync
([
installerPath
,
'-i'
,
app
.
localPath
]);
}
else
{
runCheckedSync
([
installerPath
,
'-u'
,
id
,
'-i'
,
app
.
localPath
]);
}
return
true
;
}
catch
(
e
)
{
return
false
;
}
return
false
;
}
@override
bool
isConnected
()
{
Iterable
<
String
>
ids
=
_getAttachedDeviceIDs
();
for
(
String
id
in
ids
)
{
if
(
id
==
this
.
id
||
this
.
id
==
defaultDeviceID
)
{
return
true
;
}
}
return
false
;
}
@override
bool
isAppInstalled
(
ApplicationPackage
app
)
{
try
{
String
apps
=
runCheckedSync
([
installerPath
,
'--list-apps'
]);
if
(
new
RegExp
(
app
.
id
,
multiLine:
true
).
hasMatch
(
apps
))
{
return
true
;
}
}
catch
(
e
)
{
return
false
;
}
return
false
;
}
@override
Future
<
bool
>
startApp
(
ApplicationPackage
app
)
async
{
logging
.
fine
(
"Attempting to build and install
${app.name}
on
$id
"
);
// Step 1: Install the precompiled application if necessary
bool
buildResult
=
await
_buildIOSXcodeProject
(
app
,
true
);
if
(!
buildResult
)
{
logging
.
severe
(
'Could not build the precompiled application for the device'
);
return
false
;
}
// Step 2: Check that the application exists at the specified path
Directory
bundle
=
new
Directory
(
path
.
join
(
app
.
localPath
,
'build'
,
'Release-iphoneos'
,
'Runner.app'
));
bool
bundleExists
=
await
bundle
.
exists
();
if
(!
bundleExists
)
{
logging
.
severe
(
'Could not find the built application bundle at
${bundle.path}
'
);
return
false
;
}
// Step 3: Attempt to install the application on the device
int
installationResult
=
await
runCommandAndStreamOutput
([
'/usr/bin/env'
,
'ios-deploy'
,
'--id'
,
id
,
'--bundle'
,
bundle
.
path
,
]);
if
(
installationResult
!=
0
)
{
logging
.
severe
(
'Could not install
${bundle.path}
on
$id
'
);
return
false
;
}
logging
.
fine
(
'Installation successful'
);
return
true
;
}
@override
Future
<
bool
>
stopApp
(
ApplicationPackage
app
)
async
{
// Currently we don't have a way to stop an app running on iOS.
return
false
;
}
Future
<
bool
>
pushFile
(
ApplicationPackage
app
,
String
localFile
,
String
targetFile
)
async
{
if
(
Platform
.
isMacOS
)
{
runSync
([
pusherPath
,
'-t'
,
'1'
,
'--bundle_id'
,
app
.
id
,
'--upload'
,
localFile
,
'--to'
,
targetFile
]);
return
true
;
}
else
{
return
false
;
}
return
false
;
}
@override
TargetPlatform
get
platform
=>
TargetPlatform
.
iOS
;
/// Note that clear is not supported on iOS at this time.
Future
<
int
>
logs
({
bool
clear:
false
})
async
{
if
(!
isConnected
())
{
return
2
;
}
return
await
runCommandAndStreamOutput
([
loggerPath
],
prefix:
'iOS dev: '
,
filter:
new
RegExp
(
r'.*SkyShell.*'
));
}
}
class
IOSSimulator
extends
Device
{
static
final
String
defaultDeviceID
=
'default_ios_sim_id'
;
static
const
String
_macInstructions
=
'To work with iOS devices, please install ideviceinstaller. '
'If you use homebrew, you can install it with '
'"
\$
brew install ideviceinstaller".'
;
static
String
_xcrunPath
=
path
.
join
(
'/usr'
,
'bin'
,
'xcrun'
);
String
_iOSSimPath
;
String
get
iOSSimPath
=>
_iOSSimPath
;
String
get
xcrunPath
=>
_xcrunPath
;
String
_name
;
String
get
name
=>
_name
;
factory
IOSSimulator
({
String
id
,
String
name
,
String
iOSSimulatorPath
})
{
IOSSimulator
device
=
Device
.
_unique
(
id
??
defaultDeviceID
,
(
String
id
)
=>
new
IOSSimulator
.
_
(
id
));
device
.
_name
=
name
;
if
(
iOSSimulatorPath
==
null
)
{
iOSSimulatorPath
=
path
.
join
(
'/Applications'
,
'iOS Simulator.app'
,
'Contents'
,
'MacOS'
,
'iOS Simulator'
);
}
device
.
_iOSSimPath
=
iOSSimulatorPath
;
return
device
;
}
IOSSimulator
.
_
(
String
id
)
:
super
.
_
(
id
);
static
_IOSSimulatorInfo
_getRunningSimulatorInfo
([
IOSSimulator
mockIOS
])
{
String
xcrunPath
=
mockIOS
!=
null
?
mockIOS
.
xcrunPath
:
_xcrunPath
;
String
output
=
runCheckedSync
([
xcrunPath
,
'simctl'
,
'list'
,
'devices'
]);
Match
match
;
// iPhone 6s Plus (8AC808E1-6BAE-4153-BBC5-77F83814D414) (Booted)
Iterable
<
Match
>
matches
=
new
RegExp
(
r'[\W]*(.*) \(([^\)]+)\) \(Booted\)'
,
multiLine:
true
).
allMatches
(
output
);
if
(
matches
.
length
>
1
)
{
// More than one simulator is listed as booted, which is not allowed but
// sometimes happens erroneously. Kill them all because we don't know
// which one is actually running.
logging
.
warning
(
'Multiple running simulators were detected, '
'which is not supposed to happen.'
);
for
(
Match
match
in
matches
)
{
if
(
match
.
groupCount
>
0
)
{
// TODO: We're killing simulator devices inside an accessor method;
// we probably shouldn't be changing state here.
logging
.
warning
(
'Killing simulator
${match.group(1)}
'
);
runSync
([
xcrunPath
,
'simctl'
,
'shutdown'
,
match
.
group
(
2
)]);
}
}
}
else
if
(
matches
.
length
==
1
)
{
match
=
matches
.
first
;
}
if
(
match
!=
null
&&
match
.
groupCount
>
0
)
{
return
new
_IOSSimulatorInfo
(
match
.
group
(
2
),
match
.
group
(
1
));
}
else
{
logging
.
info
(
'No running simulators found'
);
return
null
;
}
}
String
_getSimulatorPath
()
{
String
deviceID
=
id
==
defaultDeviceID
?
_getRunningSimulatorInfo
()?.
id
:
id
;
String
homeDirectory
=
path
.
absolute
(
Platform
.
environment
[
'HOME'
]);
if
(
deviceID
==
null
)
return
null
;
return
path
.
join
(
homeDirectory
,
'Library'
,
'Developer'
,
'CoreSimulator'
,
'Devices'
,
deviceID
);
}
String
_getSimulatorAppHomeDirectory
(
ApplicationPackage
app
)
{
String
simulatorPath
=
_getSimulatorPath
();
if
(
simulatorPath
==
null
)
return
null
;
return
path
.
join
(
simulatorPath
,
'data'
);
}
static
List
<
IOSSimulator
>
getAttachedDevices
([
IOSSimulator
mockIOS
])
{
List
<
IOSSimulator
>
devices
=
[];
_IOSSimulatorInfo
deviceInfo
=
_getRunningSimulatorInfo
(
mockIOS
);
if
(
deviceInfo
!=
null
)
devices
.
add
(
new
IOSSimulator
(
id:
deviceInfo
.
id
,
name:
deviceInfo
.
name
));
return
devices
;
}
Future
<
bool
>
boot
()
async
{
if
(!
Platform
.
isMacOS
)
return
false
;
if
(
isConnected
())
return
true
;
if
(
id
==
defaultDeviceID
)
{
runDetached
([
iOSSimPath
]);
Future
<
bool
>
checkConnection
([
int
attempts
=
20
])
async
{
if
(
attempts
==
0
)
{
logging
.
info
(
'Timed out waiting for iOS Simulator
$id
to boot.'
);
return
false
;
}
if
(!
isConnected
())
{
logging
.
info
(
'Waiting for iOS Simulator
$id
to boot...'
);
return
await
new
Future
.
delayed
(
new
Duration
(
milliseconds:
500
),
()
=>
checkConnection
(
attempts
-
1
));
}
return
true
;
}
return
await
checkConnection
();
}
else
{
try
{
runCheckedSync
([
xcrunPath
,
'simctl'
,
'boot'
,
id
]);
}
catch
(
e
)
{
logging
.
warning
(
'Unable to boot iOS Simulator
$id
: '
,
e
);
return
false
;
}
}
return
false
;
}
@override
bool
installApp
(
ApplicationPackage
app
)
{
if
(!
isConnected
())
return
false
;
try
{
if
(
id
==
defaultDeviceID
)
{
runCheckedSync
([
xcrunPath
,
'simctl'
,
'install'
,
'booted'
,
app
.
localPath
]);
}
else
{
runCheckedSync
([
xcrunPath
,
'simctl'
,
'install'
,
id
,
app
.
localPath
]);
}
return
true
;
}
catch
(
e
)
{
return
false
;
}
}
@override
bool
isConnected
()
{
if
(!
Platform
.
isMacOS
)
return
false
;
_IOSSimulatorInfo
deviceInfo
=
_getRunningSimulatorInfo
();
if
(
deviceInfo
==
null
)
{
return
false
;
}
else
if
(
deviceInfo
.
id
==
defaultDeviceID
)
{
return
true
;
}
else
{
return
_getRunningSimulatorInfo
()?.
id
==
id
;
}
}
@override
bool
isAppInstalled
(
ApplicationPackage
app
)
{
try
{
String
simulatorHomeDirectory
=
_getSimulatorAppHomeDirectory
(
app
);
return
FileSystemEntity
.
isDirectorySync
(
simulatorHomeDirectory
);
}
catch
(
e
)
{
return
false
;
}
}
@override
Future
<
bool
>
startApp
(
ApplicationPackage
app
)
async
{
logging
.
fine
(
'Building
${app.name}
for
$id
'
);
// Step 1: Build the Xcode project
bool
buildResult
=
await
_buildIOSXcodeProject
(
app
,
false
);
if
(!
buildResult
)
{
logging
.
severe
(
'Could not build the application for the simulator'
);
return
false
;
}
// Step 2: Assert that the Xcode project was successfully built
Directory
bundle
=
new
Directory
(
path
.
join
(
app
.
localPath
,
'build'
,
'Release-iphonesimulator'
,
'Runner.app'
));
bool
bundleExists
=
await
bundle
.
exists
();
if
(!
bundleExists
)
{
logging
.
severe
(
'Could not find the built application bundle at
${bundle.path}
'
);
return
false
;
}
// Step 3: Install the updated bundle to the simulator
int
installResult
=
await
runCommandAndStreamOutput
([
xcrunPath
,
'simctl'
,
'install'
,
id
==
defaultDeviceID
?
'booted'
:
id
,
path
.
absolute
(
bundle
.
path
)
]);
if
(
installResult
!=
0
)
{
logging
.
severe
(
'Could not install the application bundle on the simulator'
);
return
false
;
}
// Step 4: Launch the updated application in the simulator
int
launchResult
=
await
runCommandAndStreamOutput
([
xcrunPath
,
'simctl'
,
'launch'
,
id
==
defaultDeviceID
?
'booted'
:
id
,
app
.
id
]);
if
(
launchResult
!=
0
)
{
logging
.
severe
(
'Could not launch the freshly installed application on the simulator'
);
return
false
;
}
logging
.
fine
(
'Successfully started
${app.name}
on
$id
'
);
return
true
;
}
@override
Future
<
bool
>
stopApp
(
ApplicationPackage
app
)
async
{
// Currently we don't have a way to stop an app running on iOS.
return
false
;
}
Future
<
bool
>
pushFile
(
ApplicationPackage
app
,
String
localFile
,
String
targetFile
)
async
{
if
(
Platform
.
isMacOS
)
{
String
simulatorHomeDirectory
=
_getSimulatorAppHomeDirectory
(
app
);
runCheckedSync
([
'cp'
,
localFile
,
path
.
join
(
simulatorHomeDirectory
,
targetFile
)]);
return
true
;
}
return
false
;
}
@override
TargetPlatform
get
platform
=>
TargetPlatform
.
iOSSimulator
;
Future
<
int
>
logs
({
bool
clear:
false
})
async
{
if
(!
isConnected
())
return
2
;
String
homeDirectory
=
path
.
absolute
(
Platform
.
environment
[
'HOME'
]);
String
simulatorDeviceID
=
_getRunningSimulatorInfo
().
id
;
String
logFilePath
=
path
.
join
(
homeDirectory
,
'Library'
,
'Logs'
,
'CoreSimulator'
,
simulatorDeviceID
,
'system.log'
);
if
(
clear
)
runSync
([
'rm'
,
logFilePath
]);
return
await
runCommandAndStreamOutput
(
[
'tail'
,
'-f'
,
logFilePath
],
prefix:
'iOS sim: '
,
filter:
new
RegExp
(
r'.*SkyShell.*'
)
);
}
}
class
_IOSSimulatorInfo
{
final
String
id
;
final
String
name
;
_IOSSimulatorInfo
(
this
.
id
,
this
.
name
);
}
class
AndroidDevice
extends
Device
{
static
const
int
minApiLevel
=
16
;
static
const
String
minVersionName
=
'Jelly Bean'
;
static
const
String
minVersionText
=
'4.1.x'
;
static
const
String
_defaultAdbPath
=
'adb'
;
static
const
int
_observatoryPort
=
8181
;
static
final
String
defaultDeviceID
=
'default_android_device'
;
String
productID
;
String
modelID
;
String
deviceCodeName
;
bool
_connected
;
String
_adbPath
;
String
get
adbPath
=>
_adbPath
;
bool
_hasAdb
=
false
;
bool
_hasValidAndroid
=
false
;
factory
AndroidDevice
({
String
id:
null
,
String
productID:
null
,
String
modelID:
null
,
String
deviceCodeName:
null
,
bool
connected
})
{
AndroidDevice
device
=
Device
.
_unique
(
id
??
defaultDeviceID
,
(
String
id
)
=>
new
AndroidDevice
.
_
(
id
));
device
.
productID
=
productID
;
device
.
modelID
=
modelID
;
device
.
deviceCodeName
=
deviceCodeName
;
if
(
connected
!=
null
)
device
.
_connected
=
connected
;
return
device
;
}
/// mockAndroid argument is only to facilitate testing with mocks, so that
/// we don't have to rely on the test setup having adb available to it.
static
List
<
AndroidDevice
>
getAttachedDevices
([
AndroidDevice
mockAndroid
])
{
List
<
AndroidDevice
>
devices
=
[];
String
adbPath
=
(
mockAndroid
!=
null
)
?
mockAndroid
.
adbPath
:
getAdbPath
();
try
{
runCheckedSync
([
adbPath
,
'version'
]);
}
catch
(
e
)
{
logging
.
severe
(
'Unable to find adb. Is "adb" in your path?'
);
return
devices
;
}
List
<
String
>
output
=
runSync
([
adbPath
,
'devices'
,
'-l'
]).
trim
().
split
(
'
\n
'
);
// 015d172c98400a03 device usb:340787200X product:nakasi model:Nexus_7 device:grouper
RegExp
deviceRegex1
=
new
RegExp
(
r'^(\S+)\s+device\s+.*product:(\S+)\s+model:(\S+)\s+device:(\S+)$'
);
// 0149947A0D01500C device usb:340787200X
RegExp
deviceRegex2
=
new
RegExp
(
r'^(\S+)\s+device\s+\S+$'
);
RegExp
unauthorizedRegex
=
new
RegExp
(
r'^(\S+)\s+unauthorized\s+\S+$'
);
RegExp
offlineRegex
=
new
RegExp
(
r'^(\S+)\s+offline\s+\S+$'
);
// Skip first line, which is always 'List of devices attached'.
for
(
String
line
in
output
.
skip
(
1
))
{
// Skip lines like:
// * daemon not running. starting it now on port 5037 *
// * daemon started successfully *
if
(
line
.
startsWith
(
'* daemon '
))
continue
;
if
(
line
.
startsWith
(
'List of devices'
))
continue
;
if
(
deviceRegex1
.
hasMatch
(
line
))
{
Match
match
=
deviceRegex1
.
firstMatch
(
line
);
String
deviceID
=
match
[
1
];
String
productID
=
match
[
2
];
String
modelID
=
match
[
3
];
String
deviceCodeName
=
match
[
4
];
devices
.
add
(
new
AndroidDevice
(
id:
deviceID
,
productID:
productID
,
modelID:
modelID
,
deviceCodeName:
deviceCodeName
));
}
else
if
(
deviceRegex2
.
hasMatch
(
line
))
{
Match
match
=
deviceRegex2
.
firstMatch
(
line
);
String
deviceID
=
match
[
1
];
devices
.
add
(
new
AndroidDevice
(
id:
deviceID
));
}
else
if
(
unauthorizedRegex
.
hasMatch
(
line
))
{
Match
match
=
unauthorizedRegex
.
firstMatch
(
line
);
String
deviceID
=
match
[
1
];
logging
.
warning
(
'Device
$deviceID
is not authorized.
\n
'
'You might need to check your device for an authorization dialog.'
);
}
else
if
(
offlineRegex
.
hasMatch
(
line
))
{
Match
match
=
offlineRegex
.
firstMatch
(
line
);
String
deviceID
=
match
[
1
];
logging
.
warning
(
'Device
$deviceID
is offline.'
);
}
else
{
logging
.
warning
(
'Unexpected failure parsing device information from adb output:
\n
'
'
$line
\n
'
'Please report a bug at https://github.com/flutter/flutter/issues/new'
);
}
}
return
devices
;
}
AndroidDevice
.
_
(
id
)
:
super
.
_
(
id
)
{
_adbPath
=
getAdbPath
();
_hasAdb
=
_checkForAdb
();
// Checking for [minApiName] only needs to be done if we are starting an
// app, but it has an important side effect, which is to discard any
// progress messages if the adb server is restarted.
_hasValidAndroid
=
_checkForSupportedAndroidVersion
();
if
(!
_hasAdb
||
!
_hasValidAndroid
)
{
logging
.
warning
(
'Unable to run on Android.'
);
}
}
static
String
getAndroidSdkPath
()
{
if
(
Platform
.
environment
.
containsKey
(
'ANDROID_HOME'
))
{
String
androidHomeDir
=
Platform
.
environment
[
'ANDROID_HOME'
];
if
(
FileSystemEntity
.
isDirectorySync
(
path
.
join
(
androidHomeDir
,
'platform-tools'
)))
{
return
androidHomeDir
;
}
else
if
(
FileSystemEntity
.
isDirectorySync
(
path
.
join
(
androidHomeDir
,
'sdk'
,
'platform-tools'
)))
{
return
path
.
join
(
androidHomeDir
,
'sdk'
);
}
else
{
logging
.
warning
(
'Android SDK not found at
$androidHomeDir
'
);
return
null
;
}
}
else
{
logging
.
warning
(
'Android SDK not found. The ANDROID_HOME variable must be set.'
);
return
null
;
}
}
static
String
getAdbPath
()
{
if
(
Platform
.
environment
.
containsKey
(
'ANDROID_HOME'
))
{
String
androidHomeDir
=
Platform
.
environment
[
'ANDROID_HOME'
];
String
adbPath1
=
path
.
join
(
androidHomeDir
,
'sdk'
,
'platform-tools'
,
'adb'
);
String
adbPath2
=
path
.
join
(
androidHomeDir
,
'platform-tools'
,
'adb'
);
if
(
FileSystemEntity
.
isFileSync
(
adbPath1
))
{
return
adbPath1
;
}
else
if
(
FileSystemEntity
.
isFileSync
(
adbPath2
))
{
return
adbPath2
;
}
else
{
logging
.
info
(
'"adb" not found at
\n
"
$adbPath1
" or
\n
"
$adbPath2
"
\n
'
+
'using default path "
$_defaultAdbPath
"'
);
return
_defaultAdbPath
;
}
}
else
{
return
_defaultAdbPath
;
}
}
List
<
String
>
adbCommandForDevice
(
List
<
String
>
args
)
{
List
<
String
>
result
=
<
String
>[
adbPath
];
if
(
id
!=
defaultDeviceID
)
{
result
.
addAll
([
'-s'
,
id
]);
}
result
.
addAll
(
args
);
return
result
;
}
bool
_isValidAdbVersion
(
String
adbVersion
)
{
// Sample output: 'Android Debug Bridge version 1.0.31'
Match
versionFields
=
new
RegExp
(
r'(\d+)\.(\d+)\.(\d+)'
).
firstMatch
(
adbVersion
);
if
(
versionFields
!=
null
)
{
int
majorVersion
=
int
.
parse
(
versionFields
[
1
]);
int
minorVersion
=
int
.
parse
(
versionFields
[
2
]);
int
patchVersion
=
int
.
parse
(
versionFields
[
3
]);
if
(
majorVersion
>
1
)
{
return
true
;
}
if
(
majorVersion
==
1
&&
minorVersion
>
0
)
{
return
true
;
}
if
(
majorVersion
==
1
&&
minorVersion
==
0
&&
patchVersion
>=
32
)
{
return
true
;
}
return
false
;
}
logging
.
warning
(
'Unrecognized adb version string
$adbVersion
. Skipping version check.'
);
return
true
;
}
bool
_checkForAdb
()
{
try
{
String
adbVersion
=
runCheckedSync
([
adbPath
,
'version'
]);
if
(
_isValidAdbVersion
(
adbVersion
))
{
return
true
;
}
String
locatedAdbPath
=
runCheckedSync
([
'which'
,
'adb'
]);
logging
.
severe
(
'"
$locatedAdbPath
" is too old. '
'Please install version 1.0.32 or later.
\n
'
'Try setting ANDROID_HOME to the path to your Android SDK install. '
'Android builds are unavailable.'
);
}
catch
(
e
,
stack
)
{
logging
.
severe
(
'"adb" not found in
\
$PATH
. '
'Please install the Android SDK or set ANDROID_HOME '
'to the path of your Android SDK install.'
);
logging
.
info
(
e
);
logging
.
info
(
stack
);
}
return
false
;
}
bool
_checkForSupportedAndroidVersion
()
{
try
{
// If the server is automatically restarted, then we get irrelevant
// output lines like this, which we want to ignore:
// adb server is out of date. killing..
// * daemon started successfully *
runCheckedSync
(
adbCommandForDevice
([
'start-server'
]));
String
ready
=
runSync
(
adbCommandForDevice
([
'shell'
,
'echo'
,
'ready'
]));
if
(
ready
.
trim
()
!=
'ready'
)
{
logging
.
info
(
'Android device not found.'
);
return
false
;
}
// Sample output: '22'
String
sdkVersion
=
runCheckedSync
(
adbCommandForDevice
([
'shell'
,
'getprop'
,
'ro.build.version.sdk'
]))
.
trimRight
();
int
sdkVersionParsed
=
int
.
parse
(
sdkVersion
,
onError:
(
String
source
)
=>
null
);
if
(
sdkVersionParsed
==
null
)
{
logging
.
severe
(
'Unexpected response from getprop: "
$sdkVersion
"'
);
return
false
;
}
if
(
sdkVersionParsed
<
minApiLevel
)
{
logging
.
severe
(
'The Android version (
$sdkVersion
) on the target device is too old. Please '
'use a
$minVersionName
(version
$minApiLevel
/
$minVersionText
) device or later.'
);
return
false
;
}
return
true
;
}
catch
(
e
)
{
logging
.
severe
(
'Unexpected failure from adb: '
,
e
);
}
return
false
;
}
String
_getDeviceSha1Path
(
ApplicationPackage
app
)
{
return
'/data/local/tmp/sky.
${app.id}
.sha1'
;
}
String
_getDeviceApkSha1
(
ApplicationPackage
app
)
{
return
runCheckedSync
(
adbCommandForDevice
([
'shell'
,
'cat'
,
_getDeviceSha1Path
(
app
)]));
}
String
_getSourceSha1
(
ApplicationPackage
app
)
{
var
sha1
=
new
SHA1
();
var
file
=
new
File
(
app
.
localPath
);
sha1
.
add
(
file
.
readAsBytesSync
());
return
CryptoUtils
.
bytesToHex
(
sha1
.
close
());
}
String
get
name
=>
modelID
;
@override
bool
isAppInstalled
(
ApplicationPackage
app
)
{
if
(!
isConnected
())
{
return
false
;
}
if
(
runCheckedSync
(
adbCommandForDevice
([
'shell'
,
'pm'
,
'path'
,
app
.
id
]))
==
''
)
{
logging
.
info
(
'TODO(iansf): move this log to the caller.
${app.name}
is not on the device. Installing now...'
);
return
false
;
}
if
(
_getDeviceApkSha1
(
app
)
!=
_getSourceSha1
(
app
))
{
logging
.
info
(
'TODO(iansf): move this log to the caller.
${app.name}
is out of date. Installing now...'
);
return
false
;
}
return
true
;
}
@override
bool
installApp
(
ApplicationPackage
app
)
{
if
(!
isConnected
())
{
logging
.
info
(
'Android device not connected. Not installing.'
);
return
false
;
}
if
(!
FileSystemEntity
.
isFileSync
(
app
.
localPath
))
{
logging
.
severe
(
'"
${app.localPath}
" does not exist.'
);
return
false
;
}
print
(
'Installing
${app.name}
on device.'
);
runCheckedSync
(
adbCommandForDevice
([
'install'
,
'-r'
,
app
.
localPath
]));
runCheckedSync
(
adbCommandForDevice
([
'shell'
,
'echo'
,
'-n'
,
_getSourceSha1
(
app
),
'>'
,
_getDeviceSha1Path
(
app
)]));
return
true
;
}
void
_forwardObservatoryPort
()
{
// Set up port forwarding for observatory.
String
portString
=
'tcp:
$_observatoryPort
'
;
try
{
runCheckedSync
(
adbCommandForDevice
([
'forward'
,
portString
,
portString
]));
}
catch
(
e
)
{
logging
.
warning
(
'Unable to forward observatory port (
$_observatoryPort
):
\n
$e
'
);
}
}
bool
startBundle
(
AndroidApk
apk
,
String
bundlePath
,
{
bool
poke:
false
,
bool
checked:
true
,
bool
traceStartup:
false
,
String
route
,
bool
clearLogs:
false
})
{
logging
.
fine
(
'
$this
startBundle'
);
if
(!
FileSystemEntity
.
isFileSync
(
bundlePath
))
{
logging
.
severe
(
'Cannot find
$bundlePath
'
);
return
false
;
}
if
(!
poke
)
_forwardObservatoryPort
();
if
(
clearLogs
)
this
.
clearLogs
();
String
deviceTmpPath
=
'/data/local/tmp/dev.flx'
;
runCheckedSync
(
adbCommandForDevice
([
'push'
,
bundlePath
,
deviceTmpPath
]));
List
<
String
>
cmd
=
adbCommandForDevice
([
'shell'
,
'am'
,
'start'
,
'-a'
,
'android.intent.action.RUN'
,
'-d'
,
deviceTmpPath
,
]);
if
(
checked
)
cmd
.
addAll
([
'--ez'
,
'enable-checked-mode'
,
'true'
]);
if
(
traceStartup
)
cmd
.
addAll
([
'--ez'
,
'trace-startup'
,
'true'
]);
if
(
route
!=
null
)
cmd
.
addAll
([
'--es'
,
'route'
,
route
]);
cmd
.
add
(
apk
.
launchActivity
);
runCheckedSync
(
cmd
);
return
true
;
}
@override
Future
<
bool
>
startApp
(
ApplicationPackage
app
)
async
{
// Android currently has to be started with startBundle(...).
assert
(
false
);
return
false
;
}
Future
<
bool
>
stopApp
(
ApplicationPackage
app
)
async
{
final
AndroidApk
apk
=
app
;
runSync
(
adbCommandForDevice
([
'shell'
,
'am'
,
'force-stop'
,
apk
.
id
]));
return
true
;
}
@override
TargetPlatform
get
platform
=>
TargetPlatform
.
android
;
void
clearLogs
()
{
runSync
(
adbCommandForDevice
([
'logcat'
,
'-c'
]));
}
Future
<
int
>
logs
({
bool
clear:
false
})
async
{
if
(!
isConnected
())
{
return
2
;
}
if
(
clear
)
{
clearLogs
();
}
return
await
runCommandAndStreamOutput
(
adbCommandForDevice
([
'logcat'
,
'-v'
,
'tag'
,
// Only log the tag and the message
'-s'
,
'flutter:V'
,
'ActivityManager:W'
,
'System.err:W'
,
'*:F'
,
]),
prefix:
'android: '
);
}
void
startTracing
(
AndroidApk
apk
)
{
runCheckedSync
(
adbCommandForDevice
([
'shell'
,
'am'
,
'broadcast'
,
'-a'
,
'
${apk.id}
.TRACING_START'
]));
}
static
String
_threeDigits
(
int
n
)
{
if
(
n
>=
100
)
return
"
$n
"
;
if
(
n
>=
10
)
return
"0
$n
"
;
return
"00
$n
"
;
}
static
String
_twoDigits
(
int
n
)
{
if
(
n
>=
10
)
return
"
$n
"
;
return
"0
$n
"
;
}
static
String
_logcatDateFormat
(
DateTime
dt
)
{
// Doing this manually, instead of using package:intl for simplicity.
// adb logcat -T wants "%m-%d %H:%M:%S.%3q"
String
m
=
_twoDigits
(
dt
.
month
);
String
d
=
_twoDigits
(
dt
.
day
);
String
H
=
_twoDigits
(
dt
.
hour
);
String
M
=
_twoDigits
(
dt
.
minute
);
String
S
=
_twoDigits
(
dt
.
second
);
String
q
=
_threeDigits
(
dt
.
millisecond
);
return
"
$m
-
$d
$H
:
$M
:
$S
.
$q
"
;
}
// TODO(eseidel): This is fragile, there must be a better way!
DateTime
timeOnDevice
()
{
// Careful: Android's date command is super-lame, any arguments are taken as
// attempts to set the timezone and will screw your device.
String
output
=
runCheckedSync
(
adbCommandForDevice
([
'shell'
,
'date'
])).
trim
();
// format: Fri Dec 18 13:22:07 PST 2015
// intl doesn't handle timezones: https://github.com/dart-lang/intl/issues/93
// So we use the local date command to parse dates for us.
String
seconds
=
runSync
([
'date'
,
'--date'
,
output
,
'+%s'
]);
// Although '%s' is supposed to be UTC, date appears to be ignoring the
// timezone in the passed string, so using isUTC: false here.
return
new
DateTime
.
fromMillisecondsSinceEpoch
(
int
.
parse
(
seconds
)
*
1000
,
isUtc:
false
);
}
String
stopTracing
(
AndroidApk
apk
,
{
String
outPath:
null
})
{
// Workaround for logcat -c not always working:
// http://stackoverflow.com/questions/25645012/logcat-on-android-l-not-clearing-after-unplugging-and-reconnecting
String
beforeStop
=
_logcatDateFormat
(
timeOnDevice
());
runCheckedSync
(
adbCommandForDevice
([
'shell'
,
'am'
,
'broadcast'
,
'-a'
,
'
${apk.id}
.TRACING_STOP'
]));
RegExp
traceRegExp
=
new
RegExp
(
r'Saving trace to (\S+)'
,
multiLine:
true
);
RegExp
completeRegExp
=
new
RegExp
(
r'Trace complete'
,
multiLine:
true
);
String
tracePath
=
null
;
bool
isComplete
=
false
;
while
(!
isComplete
)
{
String
logs
=
runCheckedSync
(
adbCommandForDevice
([
'logcat'
,
'-d'
,
'-T'
,
beforeStop
]));
Match
fileMatch
=
traceRegExp
.
firstMatch
(
logs
);
if
(
fileMatch
!=
null
&&
fileMatch
[
1
]
!=
null
)
{
tracePath
=
fileMatch
[
1
];
}
isComplete
=
completeRegExp
.
hasMatch
(
logs
);
}
if
(
tracePath
!=
null
)
{
String
localPath
=
(
outPath
!=
null
)
?
outPath
:
path
.
basename
(
tracePath
);
runCheckedSync
(
adbCommandForDevice
([
'root'
]));
runSync
(
adbCommandForDevice
([
'shell'
,
'run-as'
,
apk
.
id
,
'chmod'
,
'777'
,
tracePath
]));
runCheckedSync
(
adbCommandForDevice
([
'pull'
,
tracePath
,
localPath
]));
runSync
(
adbCommandForDevice
([
'shell'
,
'rm'
,
tracePath
]));
return
localPath
;
}
logging
.
warning
(
'No trace file detected. '
'Did you remember to start the trace before stopping it?'
);
return
null
;
}
bool
isConnected
()
=>
_connected
!=
null
?
_connected
:
_hasValidAndroid
;
void
setConnected
(
bool
value
)
{
_connected
=
value
;
}
}
class
DeviceStore
{
final
AndroidDevice
android
;
final
IOSDevice
iOS
;
...
...
@@ -1098,17 +137,3 @@ class DeviceStore {
return
new
DeviceStore
(
android:
android
,
iOS:
iOS
,
iOSSimulator:
iOSSimulator
);
}
}
Future
<
bool
>
_buildIOSXcodeProject
(
ApplicationPackage
app
,
bool
isDevice
)
async
{
List
<
String
>
command
=
[
'/usr/bin/env'
,
'xcrun'
,
'xcodebuild'
,
'-target'
,
'Runner'
,
'-configuration'
,
'Release'
];
if
(!
isDevice
)
{
command
.
addAll
([
'-sdk'
,
'iphonesimulator'
]);
}
int
result
=
await
runCommandAndStreamOutput
(
command
,
workingDirectory:
app
.
localPath
);
return
result
==
0
;
}
packages/flutter_tools/lib/src/flx.dart
View file @
5bce2fbd
...
...
@@ -114,14 +114,13 @@ ArchiveFile _createSnapshotFile(String snapshotPath) {
return
new
ArchiveFile
(
_kSnapshotKey
,
content
.
length
,
content
);
}
Future
<
int
>
buildInTempDir
(
/// Build the flx in a temp dir and return `localBundlePath` on success.
Future
<
DirectoryResult
>
buildInTempDir
(
Toolchain
toolchain
,
{
String
mainPath:
defaultMainPath
,
void
onBundleAvailable
(
String
bundlePath
)
String
mainPath:
defaultMainPath
})
async
{
int
result
;
Directory
tempDir
=
await
Directory
.
systemTemp
.
createTemp
(
'flutter_tools'
);
try
{
String
localBundlePath
=
path
.
join
(
tempDir
.
path
,
'app.flx'
);
String
localSnapshotPath
=
path
.
join
(
tempDir
.
path
,
'snapshot_blob.bin'
);
result
=
await
build
(
...
...
@@ -131,11 +130,22 @@ Future<int> buildInTempDir(
mainPath:
mainPath
);
if
(
result
==
0
)
onBundleAvailable
(
localBundlePath
);
}
finally
{
tempDir
.
deleteSync
(
recursive:
true
);
return
new
DirectoryResult
(
tempDir
,
localBundlePath
);
else
throw
result
;
}
/// The result from [buildInTempDir]. Note that this object should be disposed after use.
class
DirectoryResult
{
final
Directory
directory
;
final
String
localBundlePath
;
DirectoryResult
(
this
.
directory
,
this
.
localBundlePath
);
/// Call this to delete the temporary directory.
void
dispose
()
{
directory
.
deleteSync
(
recursive:
true
);
}
return
result
;
}
Future
<
int
>
build
(
...
...
packages/flutter_tools/lib/src/ios/device_ios.dart
0 → 100644
View file @
5bce2fbd
// 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
'dart:io'
;
import
'package:path/path.dart'
as
path
;
import
'../application_package.dart'
;
import
'../base/logging.dart'
;
import
'../base/process.dart'
;
import
'../build_configuration.dart'
;
import
'../device.dart'
;
import
'../toolchain.dart'
;
class
IOSDevice
extends
Device
{
static
final
String
defaultDeviceID
=
'default_ios_id'
;
static
const
String
_macInstructions
=
'To work with iOS devices, please install ideviceinstaller. '
'If you use homebrew, you can install it with '
'"
\$
brew install ideviceinstaller".'
;
static
const
String
_linuxInstructions
=
'To work with iOS devices, please install ideviceinstaller. '
'On Ubuntu or Debian, you can install it with '
'"
\$
apt-get install ideviceinstaller".'
;
String
_installerPath
;
String
get
installerPath
=>
_installerPath
;
String
_listerPath
;
String
get
listerPath
=>
_listerPath
;
String
_informerPath
;
String
get
informerPath
=>
_informerPath
;
String
_debuggerPath
;
String
get
debuggerPath
=>
_debuggerPath
;
String
_loggerPath
;
String
get
loggerPath
=>
_loggerPath
;
String
_pusherPath
;
String
get
pusherPath
=>
_pusherPath
;
String
_name
;
String
get
name
=>
_name
;
factory
IOSDevice
({
String
id
,
String
name
})
{
IOSDevice
device
=
Device
.
unique
(
id
??
defaultDeviceID
,
(
String
id
)
=>
new
IOSDevice
.
fromId
(
id
));
device
.
_name
=
name
;
return
device
;
}
IOSDevice
.
fromId
(
String
id
)
:
super
.
fromId
(
id
)
{
_installerPath
=
_checkForCommand
(
'ideviceinstaller'
);
_listerPath
=
_checkForCommand
(
'idevice_id'
);
_informerPath
=
_checkForCommand
(
'ideviceinfo'
);
_debuggerPath
=
_checkForCommand
(
'idevicedebug'
);
_loggerPath
=
_checkForCommand
(
'idevicesyslog'
);
_pusherPath
=
_checkForCommand
(
'ios-deploy'
,
'To copy files to iOS devices, please install ios-deploy. '
'You can do this using homebrew as follows:
\n
'
'
\$
brew tap flutter/flutter
\n
'
'
\$
brew install ios-deploy'
);
}
static
List
<
IOSDevice
>
getAttachedDevices
([
IOSDevice
mockIOS
])
{
List
<
IOSDevice
>
devices
=
[];
for
(
String
id
in
_getAttachedDeviceIDs
(
mockIOS
))
{
String
name
=
_getDeviceName
(
id
,
mockIOS
);
devices
.
add
(
new
IOSDevice
(
id:
id
,
name:
name
));
}
return
devices
;
}
static
Iterable
<
String
>
_getAttachedDeviceIDs
([
IOSDevice
mockIOS
])
{
String
listerPath
=
(
mockIOS
!=
null
)
?
mockIOS
.
listerPath
:
_checkForCommand
(
'idevice_id'
);
String
output
;
try
{
output
=
runSync
([
listerPath
,
'-l'
]);
}
catch
(
e
)
{
return
[];
}
return
output
.
trim
()
.
split
(
'
\n
'
)
.
where
((
String
s
)
=>
s
!=
null
&&
s
.
length
>
0
);
}
static
String
_getDeviceName
(
String
deviceID
,
[
IOSDevice
mockIOS
])
{
String
informerPath
=
(
mockIOS
!=
null
)
?
mockIOS
.
informerPath
:
_checkForCommand
(
'ideviceinfo'
);
return
runSync
([
informerPath
,
'-k'
,
'DeviceName'
,
'-u'
,
deviceID
]);
}
static
final
Map
<
String
,
String
>
_commandMap
=
{};
static
String
_checkForCommand
(
String
command
,
[
String
macInstructions
=
_macInstructions
,
String
linuxInstructions
=
_linuxInstructions
])
{
return
_commandMap
.
putIfAbsent
(
command
,
()
{
try
{
command
=
runCheckedSync
([
'which'
,
command
]).
trim
();
}
catch
(
e
)
{
if
(
Platform
.
isMacOS
)
{
logging
.
severe
(
macInstructions
);
}
else
if
(
Platform
.
isLinux
)
{
logging
.
severe
(
linuxInstructions
);
}
else
{
logging
.
severe
(
'
$command
is not available on your platform.'
);
}
}
return
command
;
});
}
@override
bool
installApp
(
ApplicationPackage
app
)
{
try
{
if
(
id
==
defaultDeviceID
)
{
runCheckedSync
([
installerPath
,
'-i'
,
app
.
localPath
]);
}
else
{
runCheckedSync
([
installerPath
,
'-u'
,
id
,
'-i'
,
app
.
localPath
]);
}
return
true
;
}
catch
(
e
)
{
return
false
;
}
return
false
;
}
@override
bool
isConnected
()
{
Iterable
<
String
>
ids
=
_getAttachedDeviceIDs
();
for
(
String
id
in
ids
)
{
if
(
id
==
this
.
id
||
this
.
id
==
defaultDeviceID
)
{
return
true
;
}
}
return
false
;
}
@override
bool
isAppInstalled
(
ApplicationPackage
app
)
{
try
{
String
apps
=
runCheckedSync
([
installerPath
,
'--list-apps'
]);
if
(
new
RegExp
(
app
.
id
,
multiLine:
true
).
hasMatch
(
apps
))
{
return
true
;
}
}
catch
(
e
)
{
return
false
;
}
return
false
;
}
@override
Future
<
bool
>
startApp
(
ApplicationPackage
app
,
Toolchain
toolchain
,
{
String
mainPath
,
String
route
,
bool
checked:
true
,
Map
<
String
,
dynamic
>
platformArgs
})
async
{
// TODO: Use checked, mainPath, route
logging
.
fine
(
'Building
${app.name}
for
$id
'
);
// Step 1: Install the precompiled application if necessary
bool
buildResult
=
await
_buildIOSXcodeProject
(
app
,
true
);
if
(!
buildResult
)
{
logging
.
severe
(
'Could not build the precompiled application for the device'
);
return
false
;
}
// Step 2: Check that the application exists at the specified path
Directory
bundle
=
new
Directory
(
path
.
join
(
app
.
localPath
,
'build'
,
'Release-iphoneos'
,
'Runner.app'
));
bool
bundleExists
=
await
bundle
.
exists
();
if
(!
bundleExists
)
{
logging
.
severe
(
'Could not find the built application bundle at
${bundle.path}
'
);
return
false
;
}
// Step 3: Attempt to install the application on the device
int
installationResult
=
await
runCommandAndStreamOutput
([
'/usr/bin/env'
,
'ios-deploy'
,
'--id'
,
id
,
'--bundle'
,
bundle
.
path
,
]);
if
(
installationResult
!=
0
)
{
logging
.
severe
(
'Could not install
${bundle.path}
on
$id
'
);
return
false
;
}
logging
.
fine
(
'Installation successful'
);
return
true
;
}
@override
Future
<
bool
>
stopApp
(
ApplicationPackage
app
)
async
{
// Currently we don't have a way to stop an app running on iOS.
return
false
;
}
Future
<
bool
>
pushFile
(
ApplicationPackage
app
,
String
localFile
,
String
targetFile
)
async
{
if
(
Platform
.
isMacOS
)
{
runSync
([
pusherPath
,
'-t'
,
'1'
,
'--bundle_id'
,
app
.
id
,
'--upload'
,
localFile
,
'--to'
,
targetFile
]);
return
true
;
}
else
{
return
false
;
}
return
false
;
}
@override
TargetPlatform
get
platform
=>
TargetPlatform
.
iOS
;
/// Note that clear is not supported on iOS at this time.
Future
<
int
>
logs
({
bool
clear:
false
})
async
{
if
(!
isConnected
())
{
return
2
;
}
return
await
runCommandAndStreamOutput
([
loggerPath
],
prefix:
'iOS dev: '
,
filter:
new
RegExp
(
r'.*SkyShell.*'
));
}
}
class
IOSSimulator
extends
Device
{
static
final
String
defaultDeviceID
=
'default_ios_sim_id'
;
static
const
String
_macInstructions
=
'To work with iOS devices, please install ideviceinstaller. '
'If you use homebrew, you can install it with '
'"
\$
brew install ideviceinstaller".'
;
static
String
_xcrunPath
=
path
.
join
(
'/usr'
,
'bin'
,
'xcrun'
);
String
_iOSSimPath
;
String
get
iOSSimPath
=>
_iOSSimPath
;
String
get
xcrunPath
=>
_xcrunPath
;
String
_name
;
String
get
name
=>
_name
;
factory
IOSSimulator
({
String
id
,
String
name
,
String
iOSSimulatorPath
})
{
IOSSimulator
device
=
Device
.
unique
(
id
??
defaultDeviceID
,
(
String
id
)
=>
new
IOSSimulator
.
fromId
(
id
));
device
.
_name
=
name
;
if
(
iOSSimulatorPath
==
null
)
{
iOSSimulatorPath
=
path
.
join
(
'/Applications'
,
'iOS Simulator.app'
,
'Contents'
,
'MacOS'
,
'iOS Simulator'
);
}
device
.
_iOSSimPath
=
iOSSimulatorPath
;
return
device
;
}
IOSSimulator
.
fromId
(
String
id
)
:
super
.
fromId
(
id
);
static
_IOSSimulatorInfo
_getRunningSimulatorInfo
([
IOSSimulator
mockIOS
])
{
String
xcrunPath
=
mockIOS
!=
null
?
mockIOS
.
xcrunPath
:
_xcrunPath
;
String
output
=
runCheckedSync
([
xcrunPath
,
'simctl'
,
'list'
,
'devices'
]);
Match
match
;
// iPhone 6s Plus (8AC808E1-6BAE-4153-BBC5-77F83814D414) (Booted)
Iterable
<
Match
>
matches
=
new
RegExp
(
r'[\W]*(.*) \(([^\)]+)\) \(Booted\)'
,
multiLine:
true
).
allMatches
(
output
);
if
(
matches
.
length
>
1
)
{
// More than one simulator is listed as booted, which is not allowed but
// sometimes happens erroneously. Kill them all because we don't know
// which one is actually running.
logging
.
warning
(
'Multiple running simulators were detected, '
'which is not supposed to happen.'
);
for
(
Match
match
in
matches
)
{
if
(
match
.
groupCount
>
0
)
{
// TODO: We're killing simulator devices inside an accessor method;
// we probably shouldn't be changing state here.
logging
.
warning
(
'Killing simulator
${match.group(1)}
'
);
runSync
([
xcrunPath
,
'simctl'
,
'shutdown'
,
match
.
group
(
2
)]);
}
}
}
else
if
(
matches
.
length
==
1
)
{
match
=
matches
.
first
;
}
if
(
match
!=
null
&&
match
.
groupCount
>
0
)
{
return
new
_IOSSimulatorInfo
(
match
.
group
(
2
),
match
.
group
(
1
));
}
else
{
logging
.
info
(
'No running simulators found'
);
return
null
;
}
}
String
_getSimulatorPath
()
{
String
deviceID
=
id
==
defaultDeviceID
?
_getRunningSimulatorInfo
()?.
id
:
id
;
String
homeDirectory
=
path
.
absolute
(
Platform
.
environment
[
'HOME'
]);
if
(
deviceID
==
null
)
return
null
;
return
path
.
join
(
homeDirectory
,
'Library'
,
'Developer'
,
'CoreSimulator'
,
'Devices'
,
deviceID
);
}
String
_getSimulatorAppHomeDirectory
(
ApplicationPackage
app
)
{
String
simulatorPath
=
_getSimulatorPath
();
if
(
simulatorPath
==
null
)
return
null
;
return
path
.
join
(
simulatorPath
,
'data'
);
}
static
List
<
IOSSimulator
>
getAttachedDevices
([
IOSSimulator
mockIOS
])
{
List
<
IOSSimulator
>
devices
=
[];
_IOSSimulatorInfo
deviceInfo
=
_getRunningSimulatorInfo
(
mockIOS
);
if
(
deviceInfo
!=
null
)
devices
.
add
(
new
IOSSimulator
(
id:
deviceInfo
.
id
,
name:
deviceInfo
.
name
));
return
devices
;
}
Future
<
bool
>
boot
()
async
{
if
(!
Platform
.
isMacOS
)
return
false
;
if
(
isConnected
())
return
true
;
if
(
id
==
defaultDeviceID
)
{
runDetached
([
iOSSimPath
]);
Future
<
bool
>
checkConnection
([
int
attempts
=
20
])
async
{
if
(
attempts
==
0
)
{
logging
.
info
(
'Timed out waiting for iOS Simulator
$id
to boot.'
);
return
false
;
}
if
(!
isConnected
())
{
logging
.
info
(
'Waiting for iOS Simulator
$id
to boot...'
);
return
await
new
Future
.
delayed
(
new
Duration
(
milliseconds:
500
),
()
=>
checkConnection
(
attempts
-
1
));
}
return
true
;
}
return
await
checkConnection
();
}
else
{
try
{
runCheckedSync
([
xcrunPath
,
'simctl'
,
'boot'
,
id
]);
}
catch
(
e
)
{
logging
.
warning
(
'Unable to boot iOS Simulator
$id
: '
,
e
);
return
false
;
}
}
return
false
;
}
@override
bool
installApp
(
ApplicationPackage
app
)
{
if
(!
isConnected
())
return
false
;
try
{
if
(
id
==
defaultDeviceID
)
{
runCheckedSync
([
xcrunPath
,
'simctl'
,
'install'
,
'booted'
,
app
.
localPath
]);
}
else
{
runCheckedSync
([
xcrunPath
,
'simctl'
,
'install'
,
id
,
app
.
localPath
]);
}
return
true
;
}
catch
(
e
)
{
return
false
;
}
}
@override
bool
isConnected
()
{
if
(!
Platform
.
isMacOS
)
return
false
;
_IOSSimulatorInfo
deviceInfo
=
_getRunningSimulatorInfo
();
if
(
deviceInfo
==
null
)
{
return
false
;
}
else
if
(
deviceInfo
.
id
==
defaultDeviceID
)
{
return
true
;
}
else
{
return
_getRunningSimulatorInfo
()?.
id
==
id
;
}
}
@override
bool
isAppInstalled
(
ApplicationPackage
app
)
{
try
{
String
simulatorHomeDirectory
=
_getSimulatorAppHomeDirectory
(
app
);
return
FileSystemEntity
.
isDirectorySync
(
simulatorHomeDirectory
);
}
catch
(
e
)
{
return
false
;
}
}
@override
Future
<
bool
>
startApp
(
ApplicationPackage
app
,
Toolchain
toolchain
,
{
String
mainPath
,
String
route
,
bool
checked:
true
,
Map
<
String
,
dynamic
>
platformArgs
})
async
{
// TODO: Use checked, mainPath, route
logging
.
fine
(
'Building
${app.name}
for
$id
'
);
// Step 1: Build the Xcode project
bool
buildResult
=
await
_buildIOSXcodeProject
(
app
,
false
);
if
(!
buildResult
)
{
logging
.
severe
(
'Could not build the application for the simulator'
);
return
false
;
}
// Step 2: Assert that the Xcode project was successfully built
Directory
bundle
=
new
Directory
(
path
.
join
(
app
.
localPath
,
'build'
,
'Release-iphonesimulator'
,
'Runner.app'
));
bool
bundleExists
=
await
bundle
.
exists
();
if
(!
bundleExists
)
{
logging
.
severe
(
'Could not find the built application bundle at
${bundle.path}
'
);
return
false
;
}
// Step 3: Install the updated bundle to the simulator
int
installResult
=
await
runCommandAndStreamOutput
([
xcrunPath
,
'simctl'
,
'install'
,
id
==
defaultDeviceID
?
'booted'
:
id
,
path
.
absolute
(
bundle
.
path
)
]);
if
(
installResult
!=
0
)
{
logging
.
severe
(
'Could not install the application bundle on the simulator'
);
return
false
;
}
// Step 4: Launch the updated application in the simulator
int
launchResult
=
await
runCommandAndStreamOutput
([
xcrunPath
,
'simctl'
,
'launch'
,
id
==
defaultDeviceID
?
'booted'
:
id
,
app
.
id
]);
if
(
launchResult
!=
0
)
{
logging
.
severe
(
'Could not launch the freshly installed application on the simulator'
);
return
false
;
}
logging
.
fine
(
'Successfully started
${app.name}
on
$id
'
);
return
true
;
}
@override
Future
<
bool
>
stopApp
(
ApplicationPackage
app
)
async
{
// Currently we don't have a way to stop an app running on iOS.
return
false
;
}
Future
<
bool
>
pushFile
(
ApplicationPackage
app
,
String
localFile
,
String
targetFile
)
async
{
if
(
Platform
.
isMacOS
)
{
String
simulatorHomeDirectory
=
_getSimulatorAppHomeDirectory
(
app
);
runCheckedSync
([
'cp'
,
localFile
,
path
.
join
(
simulatorHomeDirectory
,
targetFile
)]);
return
true
;
}
return
false
;
}
@override
TargetPlatform
get
platform
=>
TargetPlatform
.
iOSSimulator
;
Future
<
int
>
logs
({
bool
clear:
false
})
async
{
if
(!
isConnected
())
return
2
;
String
homeDirectory
=
path
.
absolute
(
Platform
.
environment
[
'HOME'
]);
String
simulatorDeviceID
=
_getRunningSimulatorInfo
().
id
;
String
logFilePath
=
path
.
join
(
homeDirectory
,
'Library'
,
'Logs'
,
'CoreSimulator'
,
simulatorDeviceID
,
'system.log'
);
if
(
clear
)
runSync
([
'rm'
,
logFilePath
]);
return
await
runCommandAndStreamOutput
(
[
'tail'
,
'-f'
,
logFilePath
],
prefix:
'iOS sim: '
,
filter:
new
RegExp
(
r'.*SkyShell.*'
)
);
}
}
class
_IOSSimulatorInfo
{
final
String
id
;
final
String
name
;
_IOSSimulatorInfo
(
this
.
id
,
this
.
name
);
}
Future
<
bool
>
_buildIOSXcodeProject
(
ApplicationPackage
app
,
bool
isDevice
)
async
{
List
<
String
>
command
=
[
'/usr/bin/env'
,
'xcrun'
,
'xcodebuild'
,
'-target'
,
'Runner'
,
'-configuration'
,
'Release'
];
if
(!
isDevice
)
{
command
.
addAll
([
'-sdk'
,
'iphonesimulator'
]);
}
int
result
=
await
runCommandAndStreamOutput
(
command
,
workingDirectory:
app
.
localPath
);
return
result
==
0
;
}
packages/flutter_tools/test/android_device_test.dart
View file @
5bce2fbd
...
...
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'package:flutter_tools/src/
device
.dart'
;
import
'package:flutter_tools/src/
android/device_android
.dart'
;
import
'package:test/test.dart'
;
main
()
=>
defineTests
();
...
...
packages/flutter_tools/test/src/mocks.dart
View file @
5bce2fbd
...
...
@@ -2,9 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'package:flutter_tools/src/android/device_android.dart'
;
import
'package:flutter_tools/src/application_package.dart'
;
import
'package:flutter_tools/src/build_configuration.dart'
;
import
'package:flutter_tools/src/device.dart'
;
import
'package:flutter_tools/src/ios/device_ios.dart'
;
import
'package:flutter_tools/src/runner/flutter_command.dart'
;
import
'package:flutter_tools/src/toolchain.dart'
;
import
'package:mockito/mockito.dart'
;
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment