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
f9e6242d
Unverified
Commit
f9e6242d
authored
Jan 25, 2019
by
Dan Field
Committed by
GitHub
Jan 25, 2019
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Use mDNS to discover the device port (#26944)
* Discover port over mDNS * opt in, only for iOS for now
parent
06b979c4
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
246 additions
and
7 deletions
+246
-7
attach.dart
packages/flutter_tools/lib/src/commands/attach.dart
+130
-6
pubspec.yaml
packages/flutter_tools/pubspec.yaml
+2
-1
attach_test.dart
packages/flutter_tools/test/commands/attach_test.dart
+114
-0
No files found.
packages/flutter_tools/lib/src/commands/attach.dart
View file @
f9e6242d
...
...
@@ -4,6 +4,8 @@
import
'dart:async'
;
import
'package:multicast_dns/multicast_dns.dart'
;
import
'../base/common.dart'
;
import
'../base/file_system.dart'
;
import
'../base/io.dart'
;
...
...
@@ -14,16 +16,14 @@ import '../compile.dart';
import
'../device.dart'
;
import
'../fuchsia/fuchsia_device.dart'
;
import
'../globals.dart'
;
import
'../ios/devices.dart'
;
import
'../ios/simulators.dart'
;
import
'../protocol_discovery.dart'
;
import
'../resident_runner.dart'
;
import
'../run_cold.dart'
;
import
'../run_hot.dart'
;
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`.
///
...
...
@@ -56,7 +56,16 @@ class AttachCommand extends FlutterCommand {
..
addOption
(
'debug-port'
,
help:
'Device port where the observatory is listening.'
,
)..
addOption
(
'pid-file'
,
)..
addOption
(
'app-id'
,
help:
'The package name (Android) or bundle identifier (iOS) for the application. '
'This can be specified to avoid being prompted if multiple observatory ports '
'are advertised.
\n
'
'If you have multiple devices or emulators running, you should include the '
'device hostname as well, e.g. "com.example.myApp@my-iphone".
\n
'
'This parameter is case-insensitive.'
,
)..
addOption
(
'pid-file'
,
help:
'Specify a file to write the process id to. '
'You can send SIGUSR1 to trigger a hot reload '
'and SIGUSR2 to trigger a hot restart.'
,
...
...
@@ -92,6 +101,10 @@ class AttachCommand extends FlutterCommand {
return
null
;
}
String
get
appId
{
return
argResults
[
'app-id'
];
}
@override
Future
<
void
>
validateCommand
()
async
{
await
super
.
validateCommand
();
...
...
@@ -114,6 +127,9 @@ class AttachCommand extends FlutterCommand {
@override
Future
<
FlutterCommandResult
>
runCommand
()
async
{
final
String
ipv4Loopback
=
InternetAddress
.
loopbackIPv4
.
address
;
final
String
ipv6Loopback
=
InternetAddress
.
loopbackIPv6
.
address
;
Cache
.
releaseLockEarly
();
await
_validateArguments
();
...
...
@@ -121,7 +137,19 @@ class AttachCommand extends FlutterCommand {
writePidFile
(
argResults
[
'pid-file'
]);
final
Device
device
=
await
findTargetDevice
();
final
int
devicePort
=
debugPort
;
Future
<
int
>
getDevicePort
()
async
{
if
(
debugPort
!=
null
)
{
return
debugPort
;
}
// This call takes a non-trivial amount of time, and only iOS devices and
// simulators support it.
// If/when we do this on Android or other platforms, we can update it here.
if
(
device
is
IOSDevice
||
device
is
IOSSimulator
)
{
return
MDnsObservatoryPortDiscovery
().
queryForPort
(
applicationId:
appId
);
}
return
null
;
}
final
int
devicePort
=
await
getDevicePort
();
final
Daemon
daemon
=
argResults
[
'machine'
]
?
Daemon
(
stdinCommandStream
,
stdoutCommandResponse
,
...
...
@@ -273,3 +301,99 @@ class HotRunnerFactory {
ipv6:
ipv6
,
);
}
/// A wrapper around [MDnsClient] to find a Dart observatory port.
class
MDnsObservatoryPortDiscovery
{
/// Creates a new [MDnsObservatoryPortDiscovery] object.
///
/// The [client] parameter will be defaulted to a new [MDnsClient] if null.
/// The [applicationId] parameter may be null, and can be used to
/// automatically select which application to use if multiple are advertising
/// Dart observatory ports.
MDnsObservatoryPortDiscovery
({
MDnsClient
mdnsClient
})
:
client
=
mdnsClient
??
MDnsClient
();
/// The [MDnsClient] used to do a lookup.
final
MDnsClient
client
;
static
const
String
dartObservatoryName
=
'_dartobservatory._tcp.local'
;
/// Executes an mDNS query for a Dart Observatory port.
///
/// The [applicationId] parameter may be used to specify which application
/// to find. For Android, it refers to the package name; on iOS, it refers to
/// the bundle ID.
///
/// If it is not null, this method will find the port of the
/// Dart Observatory for that application. If it cannot find a Dart
/// Observatory matching that application identifier, it will call
/// [throwToolExit].
///
/// If it is null and there are multiple ports available, the user will be
/// prompted with a list of available observatory ports and asked to select
/// one.
///
/// If it is null and there is only one available port, it will return that
/// port regardless of what application the port is for.
Future
<
int
>
queryForPort
({
String
applicationId
})
async
{
printStatus
(
'Checking for advertised Dart observatories...'
);
try
{
await
client
.
start
();
final
List
<
PtrResourceRecord
>
pointerRecords
=
await
client
.
lookup
<
PtrResourceRecord
>(
ResourceRecordQuery
.
serverPointer
(
dartObservatoryName
),
)
.
toList
();
if
(
pointerRecords
.
isEmpty
)
{
return
null
;
}
// We have no guarantee that we won't get multiple hits from the same
// service on this.
final
List
<
String
>
uniqueDomainNames
=
pointerRecords
.
map
<
String
>((
PtrResourceRecord
record
)
=>
record
.
domainName
)
.
toSet
()
.
toList
();
String
domainName
;
if
(
applicationId
!=
null
)
{
for
(
String
name
in
uniqueDomainNames
)
{
if
(
name
.
toLowerCase
().
startsWith
(
applicationId
.
toLowerCase
()))
{
domainName
=
name
;
break
;
}
}
if
(
domainName
==
null
)
{
throwToolExit
(
'Did not find a observatory port advertised for
$applicationId
.'
);
}
}
else
if
(
uniqueDomainNames
.
length
>
1
)
{
final
StringBuffer
buffer
=
StringBuffer
();
buffer
.
writeln
(
'There are multiple observatory ports available.'
);
buffer
.
writeln
(
'Rerun this command with one of the following passed in as the appId:'
);
buffer
.
writeln
(
''
);
for
(
final
String
uniqueDomainName
in
uniqueDomainNames
)
{
buffer
.
writeln
(
' flutter attach --app-id
${uniqueDomainName.replaceAll('.$dartObservatoryName', '')}
'
);
}
throwToolExit
(
buffer
.
toString
());
}
else
{
domainName
=
pointerRecords
[
0
].
domainName
;
}
printStatus
(
'Checking for available port on
$domainName
'
);
// Here, if we get more than one, it should just be a duplicate.
final
List
<
SrvResourceRecord
>
srv
=
await
client
.
lookup
<
SrvResourceRecord
>(
ResourceRecordQuery
.
service
(
domainName
),
)
.
toList
();
if
(
srv
.
isEmpty
)
{
return
null
;
}
if
(
srv
.
length
>
1
)
{
printError
(
'Unexpectedly found more than one observatory report for
$domainName
'
'- using first one (
${srv.first.port}
).'
);
}
return
srv
.
first
.
port
;
}
finally
{
client
.
stop
();
}
}
}
packages/flutter_tools/pubspec.yaml
View file @
f9e6242d
...
...
@@ -23,6 +23,7 @@ dependencies:
json_schema
:
1.0.10
linter
:
0.1.74
meta
:
1.1.6
multicast_dns
:
0.1.0+1
mustache
:
1.1.0
package_config
:
1.0.5
platform
:
2.2.0
...
...
@@ -95,4 +96,4 @@ dartdoc:
# Exclude this package from the hosted API docs.
nodoc
:
true
# PUBSPEC CHECKSUM:
3fcc
# PUBSPEC CHECKSUM:
78f0
packages/flutter_tools/test/commands/attach_test.dart
View file @
f9e6242d
...
...
@@ -17,6 +17,7 @@ import 'package:flutter_tools/src/resident_runner.dart';
import
'package:flutter_tools/src/run_hot.dart'
;
import
'package:meta/meta.dart'
;
import
'package:mockito/mockito.dart'
;
import
'package:multicast_dns/multicast_dns.dart'
;
import
'../src/common.dart'
;
import
'../src/context.dart'
;
...
...
@@ -422,8 +423,121 @@ void main() {
FileSystem:
()
=>
testFileSystem
,
});
});
group
(
'mDNS Discovery'
,
()
{
final
int
year3000
=
DateTime
(
3000
).
millisecondsSinceEpoch
;
MDnsClient
getMockClient
(
List
<
PtrResourceRecord
>
ptrRecords
,
Map
<
String
,
List
<
SrvResourceRecord
>>
srvResponse
,
)
{
final
MDnsClient
client
=
MockMDnsClient
();
when
(
client
.
lookup
<
PtrResourceRecord
>(
ResourceRecordQuery
.
serverPointer
(
MDnsObservatoryPortDiscovery
.
dartObservatoryName
),
)).
thenAnswer
((
_
)
=>
Stream
<
PtrResourceRecord
>.
fromIterable
(
ptrRecords
));
for
(
final
MapEntry
<
String
,
List
<
SrvResourceRecord
>>
entry
in
srvResponse
.
entries
)
{
when
(
client
.
lookup
<
SrvResourceRecord
>(
ResourceRecordQuery
.
service
(
entry
.
key
),
)).
thenAnswer
((
_
)
=>
Stream
<
SrvResourceRecord
>.
fromIterable
(
entry
.
value
));
}
return
client
;
}
testUsingContext
(
'No ports available'
,
()
async
{
final
MDnsClient
client
=
getMockClient
(<
PtrResourceRecord
>[],
<
String
,
List
<
SrvResourceRecord
>>{});
final
MDnsObservatoryPortDiscovery
portDiscovery
=
MDnsObservatoryPortDiscovery
(
mdnsClient:
client
);
final
int
port
=
await
portDiscovery
.
queryForPort
();
expect
(
port
,
isNull
);
});
testUsingContext
(
'One port available, no appId'
,
()
async
{
final
MDnsClient
client
=
getMockClient
(
<
PtrResourceRecord
>[
PtrResourceRecord
(
'foo'
,
year3000
,
domainName:
'bar'
),
],
<
String
,
List
<
SrvResourceRecord
>>{
'bar'
:
<
SrvResourceRecord
>[
SrvResourceRecord
(
'bar'
,
year3000
,
port:
123
,
weight:
1
,
priority:
1
,
target:
'appId'
),
],
},
);
final
MDnsObservatoryPortDiscovery
portDiscovery
=
MDnsObservatoryPortDiscovery
(
mdnsClient:
client
);
final
int
port
=
await
portDiscovery
.
queryForPort
();
expect
(
port
,
123
);
});
testUsingContext
(
'Multiple ports available, without appId'
,
()
async
{
final
MDnsClient
client
=
getMockClient
(
<
PtrResourceRecord
>[
PtrResourceRecord
(
'foo'
,
year3000
,
domainName:
'bar'
),
PtrResourceRecord
(
'baz'
,
year3000
,
domainName:
'fiz'
),
],
<
String
,
List
<
SrvResourceRecord
>>{
'bar'
:
<
SrvResourceRecord
>[
SrvResourceRecord
(
'bar'
,
year3000
,
port:
123
,
weight:
1
,
priority:
1
,
target:
'appId'
),
],
'fiz'
:
<
SrvResourceRecord
>[
SrvResourceRecord
(
'fiz'
,
year3000
,
port:
321
,
weight:
1
,
priority:
1
,
target:
'local'
),
],
},
);
final
MDnsObservatoryPortDiscovery
portDiscovery
=
MDnsObservatoryPortDiscovery
(
mdnsClient:
client
);
expect
(()
=>
portDiscovery
.
queryForPort
(),
throwsToolExit
());
});
testUsingContext
(
'Multiple ports available, with appId'
,
()
async
{
final
MDnsClient
client
=
getMockClient
(
<
PtrResourceRecord
>[
PtrResourceRecord
(
'foo'
,
year3000
,
domainName:
'bar'
),
PtrResourceRecord
(
'baz'
,
year3000
,
domainName:
'fiz'
),
],
<
String
,
List
<
SrvResourceRecord
>>{
'bar'
:
<
SrvResourceRecord
>[
SrvResourceRecord
(
'bar'
,
year3000
,
port:
123
,
weight:
1
,
priority:
1
,
target:
'appId'
),
],
'fiz'
:
<
SrvResourceRecord
>[
SrvResourceRecord
(
'fiz'
,
year3000
,
port:
321
,
weight:
1
,
priority:
1
,
target:
'local'
),
],
},
);
final
MDnsObservatoryPortDiscovery
portDiscovery
=
MDnsObservatoryPortDiscovery
(
mdnsClient:
client
);
final
int
port
=
await
portDiscovery
.
queryForPort
(
applicationId:
'fiz'
);
expect
(
port
,
321
);
});
testUsingContext
(
'Multiple ports available per process, with appId'
,
()
async
{
final
MDnsClient
client
=
getMockClient
(
<
PtrResourceRecord
>[
PtrResourceRecord
(
'foo'
,
year3000
,
domainName:
'bar'
),
PtrResourceRecord
(
'baz'
,
year3000
,
domainName:
'fiz'
),
],
<
String
,
List
<
SrvResourceRecord
>>{
'bar'
:
<
SrvResourceRecord
>[
SrvResourceRecord
(
'bar'
,
year3000
,
port:
1234
,
weight:
1
,
priority:
1
,
target:
'appId'
),
SrvResourceRecord
(
'bar'
,
year3000
,
port:
123
,
weight:
1
,
priority:
1
,
target:
'appId'
),
],
'fiz'
:
<
SrvResourceRecord
>[
SrvResourceRecord
(
'fiz'
,
year3000
,
port:
4321
,
weight:
1
,
priority:
1
,
target:
'local'
),
SrvResourceRecord
(
'fiz'
,
year3000
,
port:
321
,
weight:
1
,
priority:
1
,
target:
'local'
),
],
},
);
final
MDnsObservatoryPortDiscovery
portDiscovery
=
MDnsObservatoryPortDiscovery
(
mdnsClient:
client
);
final
int
port
=
await
portDiscovery
.
queryForPort
(
applicationId:
'bar'
);
expect
(
port
,
1234
);
});
});
}
class
MockMDnsClient
extends
Mock
implements
MDnsClient
{}
class
MockPortForwarder
extends
Mock
implements
DevicePortForwarder
{}
class
MockHotRunner
extends
Mock
implements
HotRunner
{}
...
...
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