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
d80999d6
Unverified
Commit
d80999d6
authored
Nov 02, 2018
by
Jonah Williams
Committed by
GitHub
Nov 02, 2018
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Support attach on fuchsia devices (#23436)
parent
47f62109
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
308 additions
and
39 deletions
+308
-39
utils.dart
packages/flutter_tools/lib/src/base/utils.dart
+1
-1
attach.dart
packages/flutter_tools/lib/src/commands/attach.dart
+74
-14
fuchsia_device.dart
packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart
+166
-14
fuchsia_sdk.dart
packages/flutter_tools/lib/src/fuchsia/fuchsia_sdk.dart
+54
-6
fuchsa_device_test.dart
packages/flutter_tools/test/fuchsia/fuchsa_device_test.dart
+13
-4
No files found.
packages/flutter_tools/lib/src/base/utils.dart
View file @
d80999d6
...
...
@@ -530,7 +530,7 @@ List<String> _wrapTextAsLines(String text, {int start = 0, int columnWidth, bool
result
.
add
(
joinRun
(
splitLine
,
currentLineStart
,
index
));
// Skip any intervening whitespace.
while
(
i
sWhitespace
(
splitLine
[
index
])
&&
index
<
splitLine
.
length
)
{
while
(
i
ndex
<
splitLine
.
length
&&
isWhitespace
(
splitLine
[
index
])
)
{
index
++;
}
...
...
packages/flutter_tools/lib/src/commands/attach.dart
View file @
d80999d6
...
...
@@ -7,10 +7,12 @@ import 'dart:async';
import
'../base/common.dart'
;
import
'../base/file_system.dart'
;
import
'../base/io.dart'
;
import
'../base/logger.dart'
;
import
'../base/utils.dart'
;
import
'../cache.dart'
;
import
'../commands/daemon.dart'
;
import
'../device.dart'
;
import
'../fuchsia/fuchsia_device.dart'
;
import
'../globals.dart'
;
import
'../protocol_discovery.dart'
;
import
'../resident_runner.dart'
;
...
...
@@ -19,6 +21,8 @@ import '../runner/flutter_command.dart';
final
String
ipv4Loopback
=
InternetAddress
.
loopbackIPv4
.
address
;
final
String
ipv6Loopback
=
InternetAddress
.
loopbackIPv6
.
address
;
/// A Flutter-command that attaches to applications that have been launched
/// without `flutter run`.
///
...
...
@@ -35,6 +39,9 @@ final String ipv4Loopback = InternetAddress.loopbackIPv4.address;
/// ```
/// As soon as a new observatory is detected the command attaches to it and
/// enables hot reloading.
///
/// To attach to a flutter mod running on a fuchsia device, `--module` must
/// also be provided.
class
AttachCommand
extends
FlutterCommand
{
AttachCommand
({
bool
verboseHelp
=
false
,
this
.
hotRunnerFactory
})
{
addBuildModeFlags
(
defaultToRelease:
false
);
...
...
@@ -53,11 +60,17 @@ class AttachCommand extends FlutterCommand {
'project-root'
,
hide:
!
verboseHelp
,
help:
'Normally used only in run target'
,
)..
addOption
(
'module'
,
abbr:
'm'
,
hide:
!
verboseHelp
,
help:
'The name of the module (required if attaching to a fuchsia device)'
,
valueHelp:
'module-name'
,
)..
addFlag
(
'machine'
,
hide:
!
verboseHelp
,
negatable:
false
,
help:
'Handle machine structured JSON command input and provide output '
'and progress in machine friendly format.'
,
hide:
!
verboseHelp
,
negatable:
false
,
help:
'Handle machine structured JSON command input and provide output '
'and progress in machine friendly format.'
,
);
hotRunnerFactory
??=
HotRunnerFactory
();
}
...
...
@@ -106,18 +119,50 @@ class AttachCommand extends FlutterCommand {
:
null
;
Uri
observatoryUri
;
bool
ipv6
=
false
;
bool
attachLogger
=
false
;
if
(
devicePort
==
null
)
{
ProtocolDiscovery
observatoryDiscovery
;
try
{
observatoryDiscovery
=
ProtocolDiscovery
.
observatory
(
device
.
getLogReader
(),
portForwarder:
device
.
portForwarder
,
if
(
device
is
FuchsiaDevice
)
{
attachLogger
=
true
;
final
String
module
=
argResults
[
'module'
];
if
(
module
==
null
)
{
throwToolExit
(
'
\'
--module
\'
is requried for attaching to a Fuchsia device'
);
}
ipv6
=
_isIpv6
(
device
.
id
);
final
List
<
int
>
ports
=
await
device
.
servicePorts
();
if
(
ports
.
isEmpty
)
{
throwToolExit
(
'No active service ports on
${device.name}
'
);
}
final
List
<
int
>
localPorts
=
<
int
>[];
for
(
int
port
in
ports
)
{
localPorts
.
add
(
await
device
.
portForwarder
.
forward
(
port
));
}
final
Status
status
=
logger
.
startProgress
(
'Waiting for a connection from Flutter on
${device.name}
...'
,
expectSlowOperation:
true
,
);
printStatus
(
'Waiting for a connection from Flutter on
${device.name}
...'
);
observatoryUri
=
await
observatoryDiscovery
.
uri
;
printStatus
(
'Done.'
);
}
finally
{
await
observatoryDiscovery
?.
cancel
();
final
int
localPort
=
await
device
.
findIsolatePort
(
module
,
localPorts
);
if
(
localPort
==
null
)
{
status
.
cancel
();
throwToolExit
(
'No active Observatory running module
\'
$module
\'
on
${device.name}
'
);
}
status
.
stop
();
observatoryUri
=
ipv6
?
Uri
.
parse
(
'http://[
$ipv6Loopback
]:
$localPort
/'
)
:
Uri
.
parse
(
'http://
$ipv4Loopback
:
$localPort
/'
);
}
else
{
ProtocolDiscovery
observatoryDiscovery
;
try
{
observatoryDiscovery
=
ProtocolDiscovery
.
observatory
(
device
.
getLogReader
(),
portForwarder:
device
.
portForwarder
,
);
printStatus
(
'Waiting for a connection from Flutter on
${device.name}
...'
);
observatoryUri
=
await
observatoryDiscovery
.
uri
;
printStatus
(
'Done.'
);
}
finally
{
await
observatoryDiscovery
?.
cancel
();
}
}
}
else
{
final
int
localPort
=
await
device
.
portForwarder
.
forward
(
devicePort
);
...
...
@@ -141,7 +186,11 @@ class AttachCommand extends FlutterCommand {
usesTerminalUI:
daemon
==
null
,
projectRootPath:
argResults
[
'project-root'
],
dillOutputPath:
argResults
[
'output-dill'
],
ipv6:
ipv6
,
);
if
(
attachLogger
)
{
flutterDevice
.
startEchoingDeviceLog
();
}
if
(
daemon
!=
null
)
{
AppInstance
app
;
...
...
@@ -165,6 +214,17 @@ class AttachCommand extends FlutterCommand {
}
Future
<
void
>
_validateArguments
()
async
{}
bool
_isIpv6
(
String
address
)
{
// Workaround for https://github.com/dart-lang/sdk/issues/29456
final
String
fragment
=
address
.
split
(
'%'
).
first
;
try
{
Uri
.
parseIPv6Address
(
fragment
);
return
true
;
}
on
FormatException
{
return
false
;
}
}
}
class
HotRunnerFactory
{
...
...
packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart
View file @
d80999d6
...
...
@@ -7,17 +7,29 @@ import 'dart:async';
import
'package:meta/meta.dart'
;
import
'../application_package.dart'
;
import
'../base/common.dart'
;
import
'../base/io.dart'
;
import
'../base/platform.dart'
;
import
'../base/process.dart'
;
import
'../base/process_manager.dart'
;
import
'../build_info.dart'
;
import
'../device.dart'
;
import
'../globals.dart'
;
import
'../vmservice.dart'
;
import
'fuchsia_sdk.dart'
;
import
'fuchsia_workflow.dart'
;
final
String
_ipv4Loopback
=
InternetAddress
.
loopbackIPv4
.
address
;
final
String
_ipv6Loopback
=
InternetAddress
.
loopbackIPv6
.
address
;
/// Read the log for a particular device.
class
_FuchsiaLogReader
extends
DeviceLogReader
{
_FuchsiaLogReader
(
this
.
_device
);
// TODO(jonahwilliams): handle filtering log output from different modules.
static
final
Pattern
flutterLogOutput
=
RegExp
(
r'\[\d+\.\d+\]\[\d+\]\[\d+\]\[klog\] INFO: \w+\(flutter\): '
);
FuchsiaDevice
_device
;
@override
String
get
name
=>
_device
.
name
;
...
...
@@ -25,7 +37,8 @@ class _FuchsiaLogReader extends DeviceLogReader {
Stream
<
String
>
_logLines
;
@override
Stream
<
String
>
get
logLines
{
_logLines
??=
const
Stream
<
String
>.
empty
();
_logLines
??=
fuchsiaSdk
.
syslogs
()
.
where
((
String
line
)
=>
flutterLogOutput
.
matchAsPrefix
(
line
)
!=
null
);
return
_logLines
;
}
...
...
@@ -48,21 +61,26 @@ class FuchsiaDevices extends PollingDeviceDiscovery {
return
<
Device
>[];
}
final
String
text
=
await
fuchsiaSdk
.
netls
();
return
parseFuchsiaDeviceOutput
(
text
);
final
List
<
FuchsiaDevice
>
devices
=
<
FuchsiaDevice
>[];
for
(
String
name
in
parseFuchsiaDeviceOutput
(
text
))
{
final
String
id
=
await
fuchsiaSdk
.
netaddr
();
devices
.
add
(
FuchsiaDevice
(
id
,
name:
name
));
}
return
devices
;
}
@override
Future
<
List
<
String
>>
getDiagnostics
()
async
=>
const
<
String
>[];
}
/// Parses output from the netls tool into fuchsia devices.
/// Parses output from the netls tool into fuchsia devices
names
.
///
/// Example output:
/// $ ./netls
/// > device liliac-shore-only-last (fe80::82e4:da4d:fe81:227d/3)
@visibleForTesting
List
<
FuchsiaDevice
>
parseFuchsiaDeviceOutput
(
String
text
)
{
final
List
<
FuchsiaDevice
>
devices
=
<
FuchsiaDevice
>[];
List
<
String
>
parseFuchsiaDeviceOutput
(
String
text
)
{
final
List
<
String
>
names
=
<
String
>[];
for
(
String
rawLine
in
text
.
trim
().
split
(
'
\n
'
))
{
final
String
line
=
rawLine
.
trim
();
if
(!
line
.
startsWith
(
'device'
))
...
...
@@ -70,10 +88,9 @@ List<FuchsiaDevice> parseFuchsiaDeviceOutput(String text) {
// ['device', 'device name', '(id)']
final
List
<
String
>
words
=
line
.
split
(
' '
);
final
String
name
=
words
[
1
];
final
String
id
=
words
[
2
].
substring
(
1
,
words
[
2
].
length
-
1
);
devices
.
add
(
FuchsiaDevice
(
id
,
name:
name
));
names
.
add
(
name
);
}
return
devic
es
;
return
nam
es
;
}
class
FuchsiaDevice
extends
Device
{
...
...
@@ -131,15 +148,13 @@ class FuchsiaDevice extends Device {
@override
Future
<
String
>
get
sdkNameAndVersion
async
=>
'Fuchsia'
;
_FuchsiaLogReader
_logReader
;
@override
DeviceLogReader
getLogReader
({
ApplicationPackage
app
})
{
_logReader
??=
_FuchsiaLogReader
(
this
);
return
_logReader
;
}
DeviceLogReader
getLogReader
({
ApplicationPackage
app
})
=>
_logReader
??=
_FuchsiaLogReader
(
this
);
_FuchsiaLogReader
_logReader
;
@override
DevicePortForwarder
get
portForwarder
=>
null
;
DevicePortForwarder
get
portForwarder
=>
_portForwarder
??=
_FuchsiaPortForwarder
(
this
);
_FuchsiaPortForwarder
_portForwarder
;
@override
void
clearLogs
()
{
...
...
@@ -147,4 +162,141 @@ class FuchsiaDevice extends Device {
@override
bool
get
supportsScreenshot
=>
false
;
/// List the ports currently running a dart observatory.
Future
<
List
<
int
>>
servicePorts
()
async
{
final
String
lsOutput
=
await
shell
(
'ls /tmp/dart.services'
);
return
parseFuchsiaDartPortOutput
(
lsOutput
);
}
/// Run `command` on the Fuchsia device shell.
Future
<
String
>
shell
(
String
command
)
async
{
final
RunResult
result
=
await
runAsync
(<
String
>[
'ssh'
,
'-F'
,
fuchsiaSdk
.
sshConfig
.
absolute
.
path
,
id
,
command
]);
if
(
result
.
exitCode
!=
0
)
{
throwToolExit
(
'Command failed:
$command
\n
stdout:
${result.stdout}
\n
stderr:
${result.stderr}
'
);
return
null
;
}
return
result
.
stdout
;
}
/// Finds the first port running a VM matching `isolateName` from the
/// provided set of `ports`.
///
/// Returns null if no isolate port can be found.
///
// TODO(jonahwilliams): replacing this with the hub will require an update
// to the flutter_runner.
Future
<
int
>
findIsolatePort
(
String
isolateName
,
List
<
int
>
ports
)
async
{
for
(
int
port
in
ports
)
{
try
{
// Note: The square-bracket enclosure for using the IPv6 loopback
// didn't appear to work, but when assigning to the IPv4 loopback device,
// netstat shows that the local port is actually being used on the IPv6
// loopback (::1).
final
Uri
uri
=
Uri
.
parse
(
'http://[
$_ipv6Loopback
]:
$port
'
);
final
VMService
vmService
=
await
VMService
.
connect
(
uri
);
await
vmService
.
getVM
();
await
vmService
.
refreshViews
();
for
(
FlutterView
flutterView
in
vmService
.
vm
.
views
)
{
if
(
flutterView
.
uiIsolate
==
null
)
{
continue
;
}
final
Uri
address
=
flutterView
.
owner
.
vmService
.
httpAddress
;
if
(
flutterView
.
uiIsolate
.
name
.
contains
(
isolateName
))
{
return
address
.
port
;
}
}
}
on
SocketException
catch
(
err
)
{
printTrace
(
'Failed to connect to
$port
:
$err
'
);
}
}
throwToolExit
(
'No ports found running
$isolateName
'
);
return
null
;
}
}
class
_FuchsiaPortForwarder
extends
DevicePortForwarder
{
_FuchsiaPortForwarder
(
this
.
device
);
final
FuchsiaDevice
device
;
final
Map
<
int
,
Process
>
_processes
=
<
int
,
Process
>{};
@override
Future
<
int
>
forward
(
int
devicePort
,
{
int
hostPort
})
async
{
hostPort
??=
await
_findPort
();
// Note: the provided command works around a bug in -N, see US-515
// for more explanation.
final
List
<
String
>
command
=
<
String
>[
'ssh'
,
'-6'
,
'-F'
,
fuchsiaSdk
.
sshConfig
.
absolute
.
path
,
'-nNT'
,
'-vvv'
,
'-f'
,
'-L'
,
'
$hostPort
:
$_ipv4Loopback
:
$devicePort
'
,
device
.
id
,
'true'
];
final
Process
process
=
await
processManager
.
start
(
command
);
process
.
exitCode
.
then
((
int
exitCode
)
{
// ignore: unawaited_futures
if
(
exitCode
!=
0
)
{
throwToolExit
(
'Failed to forward port:
$devicePort
'
);
}
});
_processes
[
hostPort
]
=
process
;
_forwardedPorts
.
add
(
ForwardedPort
(
hostPort
,
devicePort
));
return
hostPort
;
}
@override
List
<
ForwardedPort
>
get
forwardedPorts
=>
_forwardedPorts
;
final
List
<
ForwardedPort
>
_forwardedPorts
=
<
ForwardedPort
>[];
@override
Future
<
void
>
unforward
(
ForwardedPort
forwardedPort
)
async
{
_forwardedPorts
.
remove
(
forwardedPort
);
final
Process
process
=
_processes
.
remove
(
forwardedPort
.
hostPort
);
process
?.
kill
();
final
List
<
String
>
command
=
<
String
>[
'ssh'
,
'-F'
,
fuchsiaSdk
.
sshConfig
.
absolute
.
path
,
'-O'
,
'cancel'
,
'-vvv'
,
'-L'
,
'
${forwardedPort.hostPort}
:
$_ipv4Loopback
:
${forwardedPort.devicePort}
'
,
device
.
id
];
final
ProcessResult
result
=
await
processManager
.
run
(
command
);
if
(
result
.
exitCode
!=
0
)
{
throwToolExit
(
result
.
stderr
);
}
}
static
Future
<
int
>
_findPort
()
async
{
int
port
=
0
;
ServerSocket
serverSocket
;
try
{
serverSocket
=
await
ServerSocket
.
bind
(
_ipv4Loopback
,
0
);
port
=
serverSocket
.
port
;
}
catch
(
e
)
{
// Failures are signaled by a return value of 0 from this function.
printTrace
(
'_findPort failed:
$e
'
);
}
if
(
serverSocket
!=
null
)
await
serverSocket
.
close
();
return
port
;
}
}
/// Parses output from `dart.services` output on a fuchsia device.
///
/// Example output:
/// $ ls /tmp/dart.services
/// > d 2 0 .
/// > - 1 0 36780
@visibleForTesting
List
<
int
>
parseFuchsiaDartPortOutput
(
String
text
)
{
final
List
<
int
>
ports
=
<
int
>[];
if
(
text
==
null
)
return
ports
;
for
(
String
line
in
text
.
split
(
'
\n
'
))
{
final
String
trimmed
=
line
.
trim
();
final
int
lastSpace
=
trimmed
.
lastIndexOf
(
' '
);
final
String
lastWord
=
trimmed
.
substring
(
lastSpace
+
1
);
if
((
lastWord
!=
'.'
)
&&
(
lastWord
!=
'..'
))
{
final
int
value
=
int
.
tryParse
(
lastWord
);
if
(
value
!=
null
)
{
ports
.
add
(
value
);
}
}
}
return
ports
;
}
packages/flutter_tools/lib/src/fuchsia/fuchsia_sdk.dart
View file @
d80999d6
...
...
@@ -2,9 +2,17 @@
// 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:convert'
;
import
'../base/common.dart'
;
import
'../base/context.dart'
;
import
'../base/file_system.dart'
;
import
'../base/io.dart'
;
import
'../base/platform.dart'
;
import
'../base/process.dart'
;
import
'../base/process_manager.dart'
;
import
'../globals.dart'
;
/// The [FuchsiaSdk] instance.
FuchsiaSdk
get
fuchsiaSdk
=>
context
[
FuchsiaSdk
];
...
...
@@ -14,6 +22,22 @@ FuchsiaSdk get fuchsiaSdk => context[FuchsiaSdk];
/// This workflow assumes development within the fuchsia source tree,
/// including a working fx command-line tool in the user's PATH.
class
FuchsiaSdk
{
static
const
List
<
String
>
_netaddrCommand
=
<
String
>[
'fx'
,
'netaddr'
,
'--fuchsia'
,
'--nowait'
];
static
const
List
<
String
>
_netlsCommand
=
<
String
>[
'fx'
,
'netls'
,
'--nowait'
];
static
const
List
<
String
>
_syslogCommand
=
<
String
>[
'fx'
,
'syslog'
];
/// The location of the SSH configuration file used to interact with a
/// fuchsia device.
///
/// Requires the env variable `BUILD_DIR` to be set.
File
get
sshConfig
{
if
(
_sshConfig
==
null
)
{
final
String
buildDirectory
=
platform
.
environment
[
'BUILD_DIR'
];
_sshConfig
=
fs
.
file
(
'
$buildDirectory
/ssh-keys/ssh_config'
);
}
return
_sshConfig
;
}
File
_sshConfig
;
/// Invokes the `netaddr` command.
///
...
...
@@ -21,12 +45,37 @@ class FuchsiaSdk {
/// not currently support multiple attached devices.
///
/// Example output:
/// $ fx netaddr --fuchsia
/// $ fx netaddr --fuchsia
--nowait
/// > fe80::9aaa:fcff:fe60:d3af%eth1
Future
<
String
>
netaddr
()
async
{
try
{
final
RunResult
process
=
await
runAsync
(<
String
>[
'fx'
,
'netaddr'
,
'--fuchsia'
,
'--nowait'
]);
return
process
.
stdout
;
final
RunResult
process
=
await
runAsync
(
_netaddrCommand
);
return
process
.
stdout
.
trim
();
}
on
ArgumentError
catch
(
exception
)
{
throwToolExit
(
'
$exception
'
);
}
return
null
;
}
/// Returns the fuchsia system logs for an attached device.
///
/// Does not currently support multiple attached devices.
Stream
<
String
>
syslogs
()
{
Process
process
;
try
{
final
StreamController
<
String
>
controller
=
StreamController
<
String
>(
onCancel:
()
{
process
.
kill
();
});
processManager
.
start
(
_syslogCommand
).
then
((
Process
newProcess
)
{
printTrace
(
'Running logs'
);
if
(
controller
.
isClosed
)
{
return
;
}
process
=
newProcess
;
process
.
exitCode
.
then
((
_
)
=>
controller
.
close
);
controller
.
addStream
(
process
.
stdout
.
transform
(
utf8
.
decoder
).
transform
(
const
LineSplitter
()));
});
return
controller
.
stream
;
}
on
ArgumentError
catch
(
exception
)
{
throwToolExit
(
'
$exception
'
);
}
...
...
@@ -39,12 +88,11 @@ class FuchsiaSdk {
/// not currently support multiple attached devices.
///
/// Example output:
/// $ fx netls
/// $ fx netls
--nowait
/// > device liliac-shore-only-last (fe80::82e4:da4d:fe81:227d/3)
Future
<
String
>
netls
()
async
{
try
{
final
RunResult
process
=
await
runAsync
(
<
String
>[
'fx'
,
'netls'
,
'--nowait'
]);
final
RunResult
process
=
await
runAsync
(
_netlsCommand
);
return
process
.
stdout
;
}
on
ArgumentError
catch
(
exception
)
{
throwToolExit
(
'
$exception
'
);
...
...
packages/flutter_tools/test/fuchsia/fuchsa_device_test.dart
View file @
d80999d6
...
...
@@ -19,11 +19,20 @@ void main() {
test
(
'parse netls log output'
,
()
{
const
String
example
=
'device lilia-shore-only-last (fe80::0000:a00a:f00f:2002/3)'
;
final
List
<
FuchsiaDevice
>
devic
es
=
parseFuchsiaDeviceOutput
(
example
);
final
List
<
String
>
nam
es
=
parseFuchsiaDeviceOutput
(
example
);
expect
(
devices
.
length
,
1
);
expect
(
devices
.
first
.
id
,
'fe80::0000:a00a:f00f:2002/3'
);
expect
(
devices
.
first
.
name
,
'lilia-shore-only-last'
);
expect
(
names
.
length
,
1
);
expect
(
names
.
first
,
'lilia-shore-only-last'
);
});
test
(
'parse ls tmp/dart.servies output'
,
()
{
const
String
example
=
'''
d 2 0 .
'
-
1
0
36780
''';
final List<int> ports = parseFuchsiaDartPortOutput(example);
expect(ports.length, 1);
expect(ports.single, 36780);
});
});
}
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