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
caf5b7ea
Commit
caf5b7ea
authored
Jan 22, 2016
by
Devon Carew
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1334 from devoncarew/refactor_device.dart
Refactor device.dart
parents
0f505fbf
5bce2fbd
Changes
13
Hide 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 @
caf5b7ea
// 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 @
caf5b7ea
// 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 @
caf5b7ea
...
@@ -9,12 +9,12 @@ import 'dart:io';
...
@@ -9,12 +9,12 @@ import 'dart:io';
import
'package:path/path.dart'
as
path
;
import
'package:path/path.dart'
as
path
;
import
'package:yaml/yaml.dart'
;
import
'package:yaml/yaml.dart'
;
import
'../android/device_android.dart'
;
import
'../artifacts.dart'
;
import
'../artifacts.dart'
;
import
'../base/file_system.dart'
;
import
'../base/file_system.dart'
;
import
'../base/logging.dart'
;
import
'../base/logging.dart'
;
import
'../base/process.dart'
;
import
'../base/process.dart'
;
import
'../build_configuration.dart'
;
import
'../build_configuration.dart'
;
import
'../device.dart'
;
import
'../flx.dart'
as
flx
;
import
'../flx.dart'
as
flx
;
import
'../runner/flutter_command.dart'
;
import
'../runner/flutter_command.dart'
;
import
'start.dart'
;
import
'start.dart'
;
...
@@ -392,16 +392,13 @@ class ApkCommand extends FlutterCommand {
...
@@ -392,16 +392,13 @@ class ApkCommand extends FlutterCommand {
String
mainPath
=
findMainDartFile
(
argResults
[
'target'
]);
String
mainPath
=
findMainDartFile
(
argResults
[
'target'
]);
// Build the FLX.
// Build the FLX.
int
result
;
flx
.
DirectoryResult
buildResult
=
await
flx
.
buildInTempDir
(
toolchain
,
mainPath:
mainPath
);
await
flx
.
buildInTempDir
(
toolchain
,
mainPath:
mainPath
,
onBundleAvailable:
(
String
localBundlePath
)
{
result
=
_buildApk
(
components
,
localBundlePath
);
}
);
return
result
;
try
{
return
_buildApk
(
components
,
buildResult
.
localBundlePath
);
}
finally
{
buildResult
.
dispose
();
}
}
}
}
}
}
}
packages/flutter_tools/lib/src/commands/daemon.dart
View file @
caf5b7ea
...
@@ -7,6 +7,7 @@ import 'dart:convert';
...
@@ -7,6 +7,7 @@ import 'dart:convert';
import
'dart:io'
;
import
'dart:io'
;
import
'../android/adb.dart'
;
import
'../android/adb.dart'
;
import
'../android/device_android.dart'
;
import
'../base/logging.dart'
;
import
'../base/logging.dart'
;
import
'../device.dart'
;
import
'../device.dart'
;
import
'../runner/flutter_command.dart'
;
import
'../runner/flutter_command.dart'
;
...
...
packages/flutter_tools/lib/src/commands/init.dart
View file @
caf5b7ea
...
@@ -9,10 +9,10 @@ import 'package:args/command_runner.dart';
...
@@ -9,10 +9,10 @@ import 'package:args/command_runner.dart';
import
'package:mustache4dart/mustache4dart.dart'
as
mustache
;
import
'package:mustache4dart/mustache4dart.dart'
as
mustache
;
import
'package:path/path.dart'
as
path
;
import
'package:path/path.dart'
as
path
;
import
'../android/android.dart'
as
android
;
import
'../artifacts.dart'
;
import
'../artifacts.dart'
;
import
'../base/logging.dart'
;
import
'../base/logging.dart'
;
import
'../base/process.dart'
;
import
'../base/process.dart'
;
import
'../device.dart'
;
class
InitCommand
extends
Command
{
class
InitCommand
extends
Command
{
final
String
name
=
'init'
;
final
String
name
=
'init'
;
...
@@ -247,7 +247,7 @@ final String _apkManifest = '''
...
@@ -247,7 +247,7 @@ final String _apkManifest = '''
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.{{projectName}}">
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"/>
<uses-permission android:name="android.permission.INTERNET"/>
<application android:name="org.domokit.sky.shell.SkyApplication" android:label="{{projectName}}">
<application android:name="org.domokit.sky.shell.SkyApplication" android:label="{{projectName}}">
...
...
packages/flutter_tools/lib/src/commands/list.dart
View file @
caf5b7ea
...
@@ -4,7 +4,8 @@
...
@@ -4,7 +4,8 @@
import
'dart:async'
;
import
'dart:async'
;
import
'../device.dart'
;
import
'../android/device_android.dart'
;
import
'../ios/device_ios.dart'
;
import
'../runner/flutter_command.dart'
;
import
'../runner/flutter_command.dart'
;
class
ListCommand
extends
FlutterCommand
{
class
ListCommand
extends
FlutterCommand
{
...
@@ -29,6 +30,8 @@ class ListCommand extends FlutterCommand {
...
@@ -29,6 +30,8 @@ class ListCommand extends FlutterCommand {
if
(
details
)
if
(
details
)
print
(
'Android Devices:'
);
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
))
{
for
(
AndroidDevice
device
in
AndroidDevice
.
getAttachedDevices
(
devices
.
android
))
{
if
(
details
)
{
if
(
details
)
{
print
(
'
${device.id}
\t
'
print
(
'
${device.id}
\t
'
...
...
packages/flutter_tools/lib/src/commands/start.dart
View file @
caf5b7ea
...
@@ -9,9 +9,7 @@ import 'package:path/path.dart' as path;
...
@@ -9,9 +9,7 @@ import 'package:path/path.dart' as path;
import
'../application_package.dart'
;
import
'../application_package.dart'
;
import
'../base/logging.dart'
;
import
'../base/logging.dart'
;
import
'../build_configuration.dart'
;
import
'../device.dart'
;
import
'../device.dart'
;
import
'../flx.dart'
as
flx
;
import
'../runner/flutter_command.dart'
;
import
'../runner/flutter_command.dart'
;
import
'../toolchain.dart'
;
import
'../toolchain.dart'
;
import
'install.dart'
;
import
'install.dart'
;
...
@@ -138,31 +136,28 @@ Future<int> startApp(
...
@@ -138,31 +136,28 @@ Future<int> startApp(
logging
.
fine
(
'Running build command for
$device
.'
);
logging
.
fine
(
'Running build command for
$device
.'
);
if
(
device
.
platform
==
TargetPlatform
.
android
)
{
Map
<
String
,
dynamic
>
platformArgs
=
<
String
,
dynamic
>{};
await
flx
.
buildInTempDir
(
toolchain
,
if
(
poke
!=
null
)
mainPath:
mainPath
,
platformArgs
[
'poke'
]
=
poke
;
onBundleAvailable:
(
String
localBundlePath
)
{
if
(
traceStartup
!=
null
)
logging
.
fine
(
'Starting bundle for
$device
.'
);
platformArgs
[
'trace-startup'
]
=
traceStartup
;
final
AndroidDevice
androidDevice
=
device
;
// https://github.com/flutter/flutter/issues/1035
if
(
clearLogs
!=
null
)
if
(
androidDevice
.
startBundle
(
package
,
localBundlePath
,
platformArgs
[
'clear-logs'
]
=
clearLogs
;
poke:
poke
,
checked:
checked
,
bool
result
=
await
device
.
startApp
(
traceStartup:
traceStartup
,
package
,
route:
route
,
toolchain
,
clearLogs:
clearLogs
mainPath:
mainPath
,
))
{
route:
route
,
startedSomething
=
true
;
checked:
checked
,
}
platformArgs:
platformArgs
}
);
);
if
(!
result
)
{
logging
.
severe
(
'Could not start
\'
${package.name}
\'
on
\'
${device.id}
\'
'
);
}
else
{
}
else
{
bool
result
=
await
device
.
startApp
(
package
);
startedSomething
=
true
;
if
(!
result
)
{
logging
.
severe
(
'Could not start
\'
${package.name}
\'
on
\'
${device.id}
\'
'
);
}
else
{
startedSomething
=
true
;
}
}
}
}
}
...
...
packages/flutter_tools/lib/src/commands/trace.dart
View file @
caf5b7ea
...
@@ -4,9 +4,9 @@
...
@@ -4,9 +4,9 @@
import
'dart:async'
;
import
'dart:async'
;
import
'../android/device_android.dart'
;
import
'../application_package.dart'
;
import
'../application_package.dart'
;
import
'../base/logging.dart'
;
import
'../base/logging.dart'
;
import
'../device.dart'
;
import
'../runner/flutter_command.dart'
;
import
'../runner/flutter_command.dart'
;
class
TraceCommand
extends
FlutterCommand
{
class
TraceCommand
extends
FlutterCommand
{
...
...
packages/flutter_tools/lib/src/device.dart
View file @
caf5b7ea
...
@@ -3,21 +3,19 @@
...
@@ -3,21 +3,19 @@
// found in the LICENSE file.
// found in the LICENSE file.
import
'dart:async'
;
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
'application_package.dart'
;
import
'base/logging.dart'
;
import
'base/logging.dart'
;
import
'base/process.dart'
;
import
'build_configuration.dart'
;
import
'build_configuration.dart'
;
import
'ios/device_ios.dart'
;
import
'toolchain.dart'
;
abstract
class
Device
{
abstract
class
Device
{
final
String
id
;
final
String
id
;
static
Map
<
String
,
Device
>
_deviceCache
=
{};
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
));
return
_deviceCache
.
putIfAbsent
(
id
,
()
=>
constructor
(
id
));
}
}
...
@@ -25,7 +23,7 @@ abstract class Device {
...
@@ -25,7 +23,7 @@ abstract class Device {
_deviceCache
.
remove
(
id
);
_deviceCache
.
remove
(
id
);
}
}
Device
.
_
(
this
.
id
);
Device
.
fromId
(
this
.
id
);
String
get
name
;
String
get
name
;
...
@@ -42,984 +40,25 @@ abstract class Device {
...
@@ -42,984 +40,25 @@ abstract class Device {
Future
<
int
>
logs
({
bool
clear:
false
});
Future
<
int
>
logs
({
bool
clear:
false
});
/// Start an app package on the current device
/// Start an app package on the current device.
Future
<
bool
>
startApp
(
ApplicationPackage
app
);
///
/// [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
);
Future
<
bool
>
stopApp
(
ApplicationPackage
app
);
String
toString
()
=>
'
$runtimeType
$id
'
;
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
{
class
DeviceStore
{
final
AndroidDevice
android
;
final
AndroidDevice
android
;
final
IOSDevice
iOS
;
final
IOSDevice
iOS
;
...
@@ -1098,17 +137,3 @@ class DeviceStore {
...
@@ -1098,17 +137,3 @@ class DeviceStore {
return
new
DeviceStore
(
android:
android
,
iOS:
iOS
,
iOSSimulator:
iOSSimulator
);
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 @
caf5b7ea
...
@@ -114,28 +114,38 @@ ArchiveFile _createSnapshotFile(String snapshotPath) {
...
@@ -114,28 +114,38 @@ ArchiveFile _createSnapshotFile(String snapshotPath) {
return
new
ArchiveFile
(
_kSnapshotKey
,
content
.
length
,
content
);
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
,
{
Toolchain
toolchain
,
{
String
mainPath:
defaultMainPath
,
String
mainPath:
defaultMainPath
void
onBundleAvailable
(
String
bundlePath
)
})
async
{
})
async
{
int
result
;
int
result
;
Directory
tempDir
=
await
Directory
.
systemTemp
.
createTemp
(
'flutter_tools'
);
Directory
tempDir
=
await
Directory
.
systemTemp
.
createTemp
(
'flutter_tools'
);
try
{
String
localBundlePath
=
path
.
join
(
tempDir
.
path
,
'app.flx'
);
String
localBundlePath
=
path
.
join
(
tempDir
.
path
,
'app.flx'
);
String
localSnapshotPath
=
path
.
join
(
tempDir
.
path
,
'snapshot_blob.bin'
);
String
localSnapshotPath
=
path
.
join
(
tempDir
.
path
,
'snapshot_blob.bin'
);
result
=
await
build
(
result
=
await
build
(
toolchain
,
toolchain
,
snapshotPath:
localSnapshotPath
,
snapshotPath:
localSnapshotPath
,
outputPath:
localBundlePath
,
outputPath:
localBundlePath
,
mainPath:
mainPath
mainPath:
mainPath
);
);
if
(
result
==
0
)
if
(
result
==
0
)
return
new
DirectoryResult
(
tempDir
,
localBundlePath
);
onBundleAvailable
(
localBundlePath
);
else
}
finally
{
throw
result
;
tempDir
.
deleteSync
(
recursive:
true
);
}
/// 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
(
Future
<
int
>
build
(
...
...
packages/flutter_tools/lib/src/ios/device_ios.dart
0 → 100644
View file @
caf5b7ea
// 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 @
caf5b7ea
...
@@ -2,7 +2,7 @@
...
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// found in the LICENSE file.
import
'package:flutter_tools/src/
device
.dart'
;
import
'package:flutter_tools/src/
android/device_android
.dart'
;
import
'package:test/test.dart'
;
import
'package:test/test.dart'
;
main
()
=>
defineTests
();
main
()
=>
defineTests
();
...
...
packages/flutter_tools/test/src/mocks.dart
View file @
caf5b7ea
...
@@ -2,9 +2,11 @@
...
@@ -2,9 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// found in the LICENSE file.
import
'package:flutter_tools/src/android/device_android.dart'
;
import
'package:flutter_tools/src/application_package.dart'
;
import
'package:flutter_tools/src/application_package.dart'
;
import
'package:flutter_tools/src/build_configuration.dart'
;
import
'package:flutter_tools/src/build_configuration.dart'
;
import
'package:flutter_tools/src/device.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/runner/flutter_command.dart'
;
import
'package:flutter_tools/src/toolchain.dart'
;
import
'package:flutter_tools/src/toolchain.dart'
;
import
'package:mockito/mockito.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