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
1b21d69d
Unverified
Commit
1b21d69d
authored
Jan 24, 2019
by
Jonah Williams
Committed by
GitHub
Jan 24, 2019
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Support running macOS prebuilt application (#26593)
parent
65a70bc7
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
280 additions
and
32 deletions
+280
-32
application_package.dart
packages/flutter_tools/lib/src/application_package.dart
+4
-0
plist_utils.dart
packages/flutter_tools/lib/src/ios/plist_utils.dart
+1
-0
application_package.dart
...ages/flutter_tools/lib/src/macos/application_package.dart
+91
-0
macos_device.dart
packages/flutter_tools/lib/src/macos/macos_device.dart
+89
-21
macos_workflow.dart
packages/flutter_tools/lib/src/macos/macos_workflow.dart
+1
-1
macos_device_test.dart
packages/flutter_tools/test/macos/macos_device_test.dart
+86
-10
macos_workflow_test.dart
packages/flutter_tools/test/macos/macos_workflow_test.dart
+8
-0
No files found.
packages/flutter_tools/lib/src/application_package.dart
View file @
1b21d69d
...
...
@@ -18,6 +18,7 @@ import 'build_info.dart';
import
'globals.dart'
;
import
'ios/ios_workflow.dart'
;
import
'ios/plist_utils.dart'
as
plist
;
import
'macos/application_package.dart'
;
import
'project.dart'
;
import
'tester/flutter_tester.dart'
;
...
...
@@ -42,6 +43,9 @@ class ApplicationPackageFactory {
case
TargetPlatform
.
tester
:
return
FlutterTesterApp
.
fromCurrentDirectory
();
case
TargetPlatform
.
darwin_x64
:
return
applicationBinary
!=
null
?
MacOSApp
.
fromPrebuiltApp
(
applicationBinary
)
:
null
;
case
TargetPlatform
.
linux_x64
:
case
TargetPlatform
.
windows_x64
:
case
TargetPlatform
.
fuchsia
:
...
...
packages/flutter_tools/lib/src/ios/plist_utils.dart
View file @
1b21d69d
...
...
@@ -7,6 +7,7 @@ import '../base/process.dart';
const
String
kCFBundleIdentifierKey
=
'CFBundleIdentifier'
;
const
String
kCFBundleShortVersionStringKey
=
'CFBundleShortVersionString'
;
const
String
kCFBundleExecutable
=
'CFBundleExecutable'
;
// Prefer using [iosWorkflow.getPlistValueFromFile] to enable mocking.
String
getValueFromFile
(
String
plistFilePath
,
String
key
)
{
...
...
packages/flutter_tools/lib/src/macos/application_package.dart
0 → 100644
View file @
1b21d69d
// Copyright 2019 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
'package:meta/meta.dart'
;
import
'../application_package.dart'
;
import
'../base/file_system.dart'
;
import
'../globals.dart'
;
import
'../ios/plist_utils.dart'
as
plist
;
/// Tests whether a [FileSystemEntity] is an macOS bundle directory
bool
_isBundleDirectory
(
FileSystemEntity
entity
)
=>
entity
is
Directory
&&
entity
.
path
.
endsWith
(
'.app'
);
abstract
class
MacOSApp
extends
ApplicationPackage
{
MacOSApp
({
@required
String
projectBundleId
})
:
super
(
id:
projectBundleId
);
/// Creates a new [MacOSApp] from an existing app bundle.
///
/// `applicationBinary` is the path to the framework directory created by an
/// Xcode build. By default, this is located under
/// "~/Library/Developer/Xcode/DerivedData/" and contains an executable
/// which is expected to start the application and send the observatory
/// port over stdout.
factory
MacOSApp
.
fromPrebuiltApp
(
FileSystemEntity
applicationBinary
)
{
final
FileSystemEntityType
entityType
=
fs
.
typeSync
(
applicationBinary
.
path
);
if
(
entityType
==
FileSystemEntityType
.
notFound
)
{
printError
(
'File "
${applicationBinary.path}
" does not exist.'
);
return
null
;
}
Directory
bundleDir
;
if
(
entityType
==
FileSystemEntityType
.
directory
)
{
final
Directory
directory
=
fs
.
directory
(
applicationBinary
);
if
(!
_isBundleDirectory
(
directory
))
{
printError
(
'Folder "
${applicationBinary.path}
" is not an app bundle.'
);
return
null
;
}
bundleDir
=
fs
.
directory
(
applicationBinary
);
}
else
{
printError
(
'Folder "
${applicationBinary.path}
" is not an app bundle.'
);
return
null
;
}
final
String
plistPath
=
fs
.
path
.
join
(
bundleDir
.
path
,
'Contents'
,
'Info.plist'
);
if
(!
fs
.
file
(
plistPath
).
existsSync
())
{
printError
(
'Invalid prebuilt macOS app. Does not contain Info.plist.'
);
return
null
;
}
final
String
id
=
plist
.
getValueFromFile
(
plistPath
,
plist
.
kCFBundleIdentifierKey
);
final
String
executableName
=
plist
.
getValueFromFile
(
plistPath
,
plist
.
kCFBundleExecutable
);
if
(
id
==
null
)
{
printError
(
'Invalid prebuilt macOS app. Info.plist does not contain bundle identifier'
);
return
null
;
}
final
String
executable
=
fs
.
path
.
join
(
bundleDir
.
path
,
'Contents'
,
'MacOS'
,
executableName
);
if
(!
fs
.
file
(
executable
).
existsSync
())
{
printError
(
'Could not find macOS binary at
$executable
'
);
return
null
;
}
return
PrebuiltMacOSApp
(
bundleDir:
bundleDir
,
bundleName:
fs
.
path
.
basename
(
bundleDir
.
path
),
projectBundleId:
id
,
executable:
executable
,
);
}
@override
String
get
displayName
=>
id
;
String
get
executable
;
}
class
PrebuiltMacOSApp
extends
MacOSApp
{
PrebuiltMacOSApp
({
@required
this
.
bundleDir
,
@required
this
.
bundleName
,
@required
this
.
projectBundleId
,
@required
this
.
executable
,
})
:
super
(
projectBundleId:
projectBundleId
);
final
Directory
bundleDir
;
final
String
bundleName
;
final
String
projectBundleId
;
@override
final
String
executable
;
@override
String
get
name
=>
bundleName
;
}
packages/flutter_tools/lib/src/macos/macos_device.dart
View file @
1b21d69d
...
...
@@ -2,16 +2,23 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'dart:convert'
;
import
'../application_package.dart'
;
import
'../base/io.dart'
;
import
'../base/os.dart'
;
import
'../base/platform.dart'
;
import
'../base/process_manager.dart'
;
import
'../build_info.dart'
;
import
'../device.dart'
;
import
'../globals.dart'
;
import
'../macos/application_package.dart'
;
import
'../protocol_discovery.dart'
;
import
'macos_workflow.dart'
;
/// A device that represents a desktop MacOS target.
class
MacOSDevice
extends
Device
{
MacOSDevice
()
:
super
(
'
M
acOS'
);
MacOSDevice
()
:
super
(
'
m
acOS'
);
@override
void
clearLogs
()
{}
...
...
@@ -19,20 +26,20 @@ class MacOSDevice extends Device {
@override
DeviceLogReader
getLogReader
({
ApplicationPackage
app
})
=>
NoOpDeviceLogReader
(
'macos'
);
// Since the host and target devices are the same, no work needs to be done
// to install the application.
@override
Future
<
bool
>
installApp
(
ApplicationPackage
app
)
{
throw
UnimplementedError
();
}
Future
<
bool
>
installApp
(
ApplicationPackage
app
)
async
=>
true
;
// Since the host and target devices are the same, no work needs to be done
// to install the application.
@override
Future
<
bool
>
isAppInstalled
(
ApplicationPackage
app
)
{
throw
UnimplementedError
();
}
Future
<
bool
>
isAppInstalled
(
ApplicationPackage
app
)
async
=>
true
;
// Since the host and target devices are the same, no work needs to be done
// to install the application.
@override
Future
<
bool
>
isLatestBuildInstalled
(
ApplicationPackage
app
)
{
throw
UnimplementedError
();
}
Future
<
bool
>
isLatestBuildInstalled
(
ApplicationPackage
app
)
async
=>
true
;
@override
Future
<
bool
>
get
isLocalEmulator
async
=>
false
;
...
...
@@ -41,7 +48,7 @@ class MacOSDevice extends Device {
bool
isSupported
()
=>
true
;
@override
String
get
name
=>
'
M
acOS'
;
String
get
name
=>
'
m
acOS'
;
@override
DevicePortForwarder
get
portForwarder
=>
const
NoOpDevicePortForwarder
();
...
...
@@ -50,7 +57,7 @@ class MacOSDevice extends Device {
Future
<
String
>
get
sdkNameAndVersion
async
=>
os
.
name
;
@override
Future
<
LaunchResult
>
startApp
(
ApplicationPackage
package
,
{
Future
<
LaunchResult
>
startApp
(
covariant
MacOSApp
package
,
{
String
mainPath
,
String
route
,
DebuggingOptions
debuggingOptions
,
...
...
@@ -59,26 +66,72 @@ class MacOSDevice extends Device {
bool
applicationNeedsRebuild
=
false
,
bool
usesTerminalUi
=
true
,
bool
ipv6
=
false
,
})
{
throw
UnimplementedError
();
})
async
{
if
(!
prebuiltApplication
)
{
return
LaunchResult
.
failed
();
}
// Stop any running applications with the same executable.
await
stopApp
(
package
);
final
Process
process
=
await
processManager
.
start
(<
String
>[
package
.
executable
]);
final
MacOSLogReader
logReader
=
MacOSLogReader
(
package
,
process
);
final
ProtocolDiscovery
observatoryDiscovery
=
ProtocolDiscovery
.
observatory
(
logReader
);
try
{
final
Uri
observatoryUri
=
await
observatoryDiscovery
.
uri
;
return
LaunchResult
.
succeeded
(
observatoryUri:
observatoryUri
);
}
catch
(
error
)
{
printError
(
'Error waiting for a debug connection:
$error
'
);
return
LaunchResult
.
failed
();
}
finally
{
await
observatoryDiscovery
.
cancel
();
}
}
@override
Future
<
bool
>
stopApp
(
ApplicationPackage
app
)
{
throw
UnimplementedError
();
// TODO(jonahwilliams): implement using process manager.
// currently we rely on killing the isolate taking down the application.
@override
Future
<
bool
>
stopApp
(
covariant
MacOSApp
app
)
async
{
final
RegExp
whitespace
=
RegExp
(
r'\s+'
);
bool
succeeded
=
true
;
try
{
final
ProcessResult
result
=
await
processManager
.
run
(<
String
>[
'ps'
,
'aux'
,
]);
if
(
result
.
exitCode
!=
0
)
{
return
false
;
}
final
List
<
String
>
lines
=
result
.
stdout
.
split
(
'
\n
'
);
for
(
String
line
in
lines
)
{
if
(!
line
.
contains
(
app
.
executable
))
{
continue
;
}
final
List
<
String
>
values
=
line
.
split
(
whitespace
);
if
(
values
.
length
<
2
)
{
continue
;
}
final
String
pid
=
values
[
1
];
final
ProcessResult
killResult
=
await
processManager
.
run
(<
String
>[
'kill'
,
pid
]);
succeeded
&=
killResult
.
exitCode
==
0
;
}
return
true
;
}
on
ArgumentError
{
succeeded
=
false
;
}
return
succeeded
;
}
@override
Future
<
TargetPlatform
>
get
targetPlatform
async
=>
TargetPlatform
.
darwin_x64
;
// Since the host and target devices are the same, no work needs to be done
// to uninstall the application.
@override
Future
<
bool
>
uninstallApp
(
ApplicationPackage
app
)
{
throw
UnimplementedError
();
}
Future
<
bool
>
uninstallApp
(
ApplicationPackage
app
)
async
=>
true
;
}
class
MacOSDevices
extends
PollingDeviceDiscovery
{
MacOSDevices
()
:
super
(
'mac
os
devices'
);
MacOSDevices
()
:
super
(
'mac
OS
devices'
);
@override
bool
get
supportsPlatform
=>
platform
.
isMacOS
;
...
...
@@ -99,3 +152,18 @@ class MacOSDevices extends PollingDeviceDiscovery {
@override
Future
<
List
<
String
>>
getDiagnostics
()
async
=>
const
<
String
>[];
}
class
MacOSLogReader
extends
DeviceLogReader
{
MacOSLogReader
(
this
.
macOSApp
,
this
.
process
);
final
MacOSApp
macOSApp
;
final
Process
process
;
@override
Stream
<
String
>
get
logLines
{
return
process
.
stdout
.
transform
(
utf8
.
decoder
);
}
@override
String
get
name
=>
macOSApp
.
displayName
;
}
packages/flutter_tools/lib/src/macos/macos_workflow.dart
View file @
1b21d69d
...
...
@@ -10,7 +10,7 @@ import '../doctor.dart';
/// The [MacOSWorkflow] instance.
MacOSWorkflow
get
macOSWorkflow
=>
context
[
MacOSWorkflow
];
/// The mac
os
-specific implementation of a [Workflow].
/// The mac
OS
-specific implementation of a [Workflow].
///
/// This workflow requires the flutter-desktop-embedding as a sibling
/// repository to the flutter repo.
...
...
packages/flutter_tools/test/macos/macos_device_test.dart
View file @
1b21d69d
...
...
@@ -2,11 +2,19 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'dart:convert'
;
import
'package:flutter_tools/src/base/context.dart'
;
import
'package:mockito/mockito.dart'
;
import
'package:process/process.dart'
;
import
'package:flutter_tools/src/base/file_system.dart'
;
import
'package:flutter_tools/src/base/io.dart'
;
import
'package:flutter_tools/src/base/platform.dart'
;
import
'package:flutter_tools/src/build_info.dart'
;
import
'package:flutter_tools/src/device.dart'
;
import
'package:flutter_tools/src/macos/application_package.dart'
;
import
'package:flutter_tools/src/macos/macos_device.dart'
;
import
'package:mockito/mockito.dart'
;
import
'../src/common.dart'
;
import
'../src/context.dart'
;
...
...
@@ -15,21 +23,79 @@ void main() {
group
(
MacOSDevice
,
()
{
final
MockPlatform
notMac
=
MockPlatform
();
final
MacOSDevice
device
=
MacOSDevice
();
final
MockProcessManager
mockProcessManager
=
MockProcessManager
();
when
(
notMac
.
isMacOS
).
thenReturn
(
false
);
when
(
notMac
.
environment
).
thenReturn
(
const
<
String
,
String
>{});
when
(
mockProcessManager
.
run
(
any
)).
thenAnswer
((
Invocation
invocation
)
async
{
return
ProcessResult
(
0
,
1
,
''
,
''
);
});
test
(
'defaults'
,
()
async
{
testUsingContext
(
'defaults'
,
()
async
{
final
MockMacOSApp
mockMacOSApp
=
MockMacOSApp
();
when
(
mockMacOSApp
.
executable
).
thenReturn
(
'foo'
);
expect
(
await
device
.
targetPlatform
,
TargetPlatform
.
darwin_x64
);
expect
(
device
.
name
,
'MacOS'
);
expect
(
device
.
name
,
'macOS'
);
expect
(
await
device
.
installApp
(
mockMacOSApp
),
true
);
expect
(
await
device
.
uninstallApp
(
mockMacOSApp
),
true
);
expect
(
await
device
.
isLatestBuildInstalled
(
mockMacOSApp
),
true
);
expect
(
await
device
.
isAppInstalled
(
mockMacOSApp
),
true
);
expect
(
await
device
.
stopApp
(
mockMacOSApp
),
false
);
},
overrides:
<
Type
,
Generator
>{
ProcessManager:
()
=>
mockProcessManager
,
});
testUsingContext
(
'stopApp'
,
()
async
{
const
String
psOut
=
r''
'
tester 17193 0.0 0.2 4791128 37820 ?? S 2:27PM 0:00.09 /Applications/foo
'''
;
final
MockMacOSApp
mockMacOSApp
=
MockMacOSApp
();
when
(
mockMacOSApp
.
executable
).
thenReturn
(
'/Applications/foo'
);
when
(
mockProcessManager
.
run
(<
String
>[
'ps'
,
'aux'
])).
thenAnswer
((
Invocation
invocation
)
async
{
return
ProcessResult
(
1
,
0
,
psOut
,
''
);
});
when
(
mockProcessManager
.
run
(<
String
>[
'kill'
,
'17193'
])).
thenAnswer
((
Invocation
invocation
)
async
{
return
ProcessResult
(
2
,
0
,
''
,
''
);
});
expect
(
await
device
.
stopApp
(
mockMacOSApp
),
true
);
verify
(
mockProcessManager
.
run
(<
String
>[
'kill'
,
'17193'
]));
},
overrides:
<
Type
,
Generator
>{
ProcessManager:
()
=>
mockProcessManager
,
});
test
(
'unimplemented methods'
,
()
{
expect
(()
=>
device
.
installApp
(
null
),
throwsA
(
isInstanceOf
<
UnimplementedError
>()));
expect
(()
=>
device
.
uninstallApp
(
null
),
throwsA
(
isInstanceOf
<
UnimplementedError
>()));
expect
(()
=>
device
.
isLatestBuildInstalled
(
null
),
throwsA
(
isInstanceOf
<
UnimplementedError
>()));
expect
(()
=>
device
.
startApp
(
null
),
throwsA
(
isInstanceOf
<
UnimplementedError
>()));
expect
(()
=>
device
.
stopApp
(
null
),
throwsA
(
isInstanceOf
<
UnimplementedError
>()));
expect
(()
=>
device
.
isAppInstalled
(
null
),
throwsA
(
isInstanceOf
<
UnimplementedError
>()));
group
(
'startApp'
,
()
{
final
MockMacOSApp
macOSApp
=
MockMacOSApp
();
final
MockFileSystem
mockFileSystem
=
MockFileSystem
();
final
MockProcessManager
mockProcessManager
=
MockProcessManager
();
final
MockFile
mockFile
=
MockFile
();
final
MockProcess
mockProcess
=
MockProcess
();
when
(
macOSApp
.
executable
).
thenReturn
(
'test'
);
when
(
mockFileSystem
.
file
(
'test'
)).
thenReturn
(
mockFile
);
when
(
mockFile
.
existsSync
()).
thenReturn
(
true
);
when
(
mockProcessManager
.
start
(<
String
>[
'test'
])).
thenAnswer
((
Invocation
invocation
)
async
{
return
mockProcess
;
});
when
(
mockProcessManager
.
run
(
any
)).
thenAnswer
((
Invocation
invocation
)
async
{
return
ProcessResult
(
0
,
1
,
''
,
''
);
});
when
(
mockProcess
.
stdout
).
thenAnswer
((
Invocation
invocation
)
{
return
Stream
<
List
<
int
>>.
fromIterable
(<
List
<
int
>>[
utf8
.
encode
(
'Observatory listening on http://127.0.0.1/0'
),
]);
});
test
(
'fails without a prebuilt application'
,
()
async
{
final
LaunchResult
result
=
await
device
.
startApp
(
macOSApp
,
prebuiltApplication:
false
);
expect
(
result
.
started
,
false
);
});
testUsingContext
(
'Can run from prebuilt application'
,
()
async
{
final
LaunchResult
result
=
await
device
.
startApp
(
macOSApp
,
prebuiltApplication:
true
);
expect
(
result
.
started
,
true
);
expect
(
result
.
observatoryUri
,
Uri
.
parse
(
'http://127.0.0.1/0'
));
},
overrides:
<
Type
,
Generator
>{
FileSystem:
()
=>
mockFileSystem
,
ProcessManager:
()
=>
mockProcessManager
,
});
});
test
(
'noop port forwarding'
,
()
async
{
...
...
@@ -49,3 +115,13 @@ void main() {
}
class
MockPlatform
extends
Mock
implements
Platform
{}
class
MockMacOSApp
extends
Mock
implements
MacOSApp
{}
class
MockFileSystem
extends
Mock
implements
FileSystem
{}
class
MockFile
extends
Mock
implements
File
{}
class
MockProcessManager
extends
Mock
implements
ProcessManager
{}
class
MockProcess
extends
Mock
implements
Process
{}
packages/flutter_tools/test/macos/macos_workflow_test.dart
View file @
1b21d69d
...
...
@@ -2,10 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'package:flutter_tools/src/base/io.dart'
;
import
'package:mockito/mockito.dart'
;
import
'package:flutter_tools/src/base/platform.dart'
;
import
'package:flutter_tools/src/macos/macos_workflow.dart'
;
import
'package:process/process.dart'
;
import
'../src/common.dart'
;
import
'../src/context.dart'
;
...
...
@@ -20,6 +22,10 @@ void main() {
when
(
macWithFde
.
isMacOS
).
thenReturn
(
true
);
when
(
notMac
.
isMacOS
).
thenReturn
(
false
);
final
MockProcessManager
mockProcessManager
=
MockProcessManager
();
when
(
mockProcessManager
.
run
(
any
)).
thenAnswer
((
Invocation
invocation
)
async
{
return
ProcessResult
(
0
,
1
,
''
,
''
);
});
testUsingContext
(
'Applies to mac platform'
,
()
{
expect
(
macOSWorkflow
.
appliesToHostPlatform
,
true
);
},
overrides:
<
Type
,
Generator
>{
...
...
@@ -45,3 +51,5 @@ class MockPlatform extends Mock implements Platform {
@override
Map
<
String
,
String
>
environment
=
<
String
,
String
>{};
}
class
MockProcessManager
extends
Mock
implements
ProcessManager
{}
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