Unverified Commit 0759043e authored by xster's avatar xster Committed by GitHub

Revert "Enable developers to run pod from terminal directly and skip pod...

Revert "Enable developers to run pod from terminal directly and skip pod install if possible. (#13374)" (#13770)

This reverts commit c6a17525.
parent c6a17525
...@@ -24,7 +24,7 @@ class InjectPluginsCommand extends FlutterCommand { ...@@ -24,7 +24,7 @@ class InjectPluginsCommand extends FlutterCommand {
@override @override
Future<Null> runCommand() async { Future<Null> runCommand() async {
final bool result = injectPlugins().hasPlugin; final bool result = injectPlugins();
if (result) { if (result) {
printStatus('GeneratedPluginRegistrants successfully written.'); printStatus('GeneratedPluginRegistrants successfully written.');
} else { } else {
......
...@@ -57,17 +57,15 @@ class CocoaPods { ...@@ -57,17 +57,15 @@ class CocoaPods {
Future<Null> processPods({ Future<Null> processPods({
@required Directory appIosDir, @required Directory appIosDir,
@required String iosEngineDir,
bool isSwift: false, bool isSwift: false,
bool pluginOrFlutterPodChanged: true,
}) async { }) async {
if (await _checkPodCondition()) { if (await _checkPodCondition()) {
if (!fs.file(fs.path.join(appIosDir.path, 'Podfile')).existsSync()) { if (!fs.file(fs.path.join(appIosDir.path, 'Podfile')).existsSync()) {
await _createPodfile(appIosDir, isSwift); await _createPodfile(appIosDir, isSwift);
} // TODO(xster): Add more logic for handling merge conflicts. } // TODO(xster): Add more logic for handling merge conflicts.
if (_checkIfRunPodInstall(appIosDir.path, pluginOrFlutterPodChanged))
await _runPodInstall(appIosDir); await _runPodInstall(appIosDir, iosEngineDir);
} else {
throwToolExit('CocoaPods not available for project using Flutter plugins');
} }
} }
...@@ -110,12 +108,13 @@ class CocoaPods { ...@@ -110,12 +108,13 @@ class CocoaPods {
podfileTemplate.copySync(fs.path.join(bundle.path, 'Podfile')); podfileTemplate.copySync(fs.path.join(bundle.path, 'Podfile'));
} }
Future<Null> _runPodInstall(Directory bundle) async { Future<Null> _runPodInstall(Directory bundle, String engineDirectory) async {
final Status status = logger.startProgress('Running pod install...', expectSlowOperation: true); final Status status = logger.startProgress('Running pod install...', expectSlowOperation: true);
final ProcessResult result = await processManager.run( final ProcessResult result = await processManager.run(
<String>['pod', 'install', '--verbose'], <String>['pod', 'install', '--verbose'],
workingDirectory: bundle.path, workingDirectory: bundle.path,
environment: <String, String>{ environment: <String, String>{
'FLUTTER_FRAMEWORK_DIR': engineDirectory,
// See https://github.com/flutter/flutter/issues/10873. // See https://github.com/flutter/flutter/issues/10873.
// CocoaPods analytics adds a lot of latency. // CocoaPods analytics adds a lot of latency.
'COCOAPODS_DISABLE_STATS': 'true', 'COCOAPODS_DISABLE_STATS': 'true',
...@@ -138,28 +137,6 @@ class CocoaPods { ...@@ -138,28 +137,6 @@ class CocoaPods {
} }
} }
// Check if you need to run pod install.
// The pod install will run if any of below is true.
// 1.Any plugins changed (add/update/delete)
// 2.The flutter.framework has changed (debug/release/profile)
// 3.The podfile.lock doesn't exists
// 4.The Pods/manifest.lock doesn't exists
// 5.The podfile.lock doesn't match Pods/manifest.lock.
bool _checkIfRunPodInstall(String appDir, bool pluginOrFlutterPodChanged) {
if (pluginOrFlutterPodChanged)
return true;
// Check if podfile.lock and Pods/Manifest.lock exists and matches.
final File podfileLockFile = fs.file(fs.path.join(appDir, 'Podfile.lock'));
final File manifestLockFile =
fs.file(fs.path.join(appDir, 'Pods', 'Manifest.lock'));
if (!podfileLockFile.existsSync() ||
!manifestLockFile.existsSync() ||
podfileLockFile.readAsStringSync() !=
manifestLockFile.readAsStringSync())
return true;
return false;
}
void _diagnosePodInstallFailure(ProcessResult result) { void _diagnosePodInstallFailure(ProcessResult result) {
if (result.stdout is String && result.stdout.contains('out-of-date source repos')) { if (result.stdout is String && result.stdout.contains('out-of-date source repos')) {
printError( printError(
......
...@@ -254,9 +254,14 @@ Future<XcodeBuildResult> buildXcodeProject({ ...@@ -254,9 +254,14 @@ Future<XcodeBuildResult> buildXcodeProject({
// copied over to a location that is suitable for Xcodebuild to find them. // copied over to a location that is suitable for Xcodebuild to find them.
final Directory appDirectory = fs.directory(app.appDirectory); final Directory appDirectory = fs.directory(app.appDirectory);
await _addServicesToBundle(appDirectory); await _addServicesToBundle(appDirectory);
final InjectPluginsResult injectionResult = injectPlugins(); final bool hasFlutterPlugins = injectPlugins();
final bool hasFlutterPlugins = injectionResult.hasPlugin;
final String priorGeneratedXCConfig = readGeneratedXCConfig(app.appDirectory); if (hasFlutterPlugins)
await cocoaPods.processPods(
appIosDir: appDirectory,
iosEngineDir: flutterFrameworkDir(buildInfo.mode),
isSwift: app.isSwift,
);
updateXcodeGeneratedProperties( updateXcodeGeneratedProperties(
projectPath: fs.currentDirectory.path, projectPath: fs.currentDirectory.path,
...@@ -266,15 +271,6 @@ Future<XcodeBuildResult> buildXcodeProject({ ...@@ -266,15 +271,6 @@ Future<XcodeBuildResult> buildXcodeProject({
previewDart2: buildInfo.previewDart2, previewDart2: buildInfo.previewDart2,
); );
if (hasFlutterPlugins) {
final String currentGeneratedXCConfig = readGeneratedXCConfig(app.appDirectory);
await cocoaPods.processPods(
appIosDir: appDirectory,
isSwift: app.isSwift,
pluginOrFlutterPodChanged: (injectionResult.hasChanged ||
priorGeneratedXCConfig != currentGeneratedXCConfig));
}
final List<String> commands = <String>[ final List<String> commands = <String>[
'/usr/bin/env', '/usr/bin/env',
'xcrun', 'xcrun',
...@@ -359,17 +355,7 @@ Future<XcodeBuildResult> buildXcodeProject({ ...@@ -359,17 +355,7 @@ Future<XcodeBuildResult> buildXcodeProject({
} }
} }
String readGeneratedXCConfig(String appPath) { Future<Null> diagnoseXcodeBuildFailure(XcodeBuildResult result, BuildableIOSApp app) async {
final String generateXCConfigPath =
fs.path.join(fs.currentDirectory.path, appPath, 'Flutter','Generated.xcconfig');
final File generateXCConfigFile = fs.file(generateXCConfigPath);
if (!generateXCConfigFile.existsSync())
return null;
return generateXCConfigFile.readAsStringSync();
}
Future<Null> diagnoseXcodeBuildFailure(
XcodeBuildResult result, BuildableIOSApp app) async {
if (result.xcodeBuildExecution != null && if (result.xcodeBuildExecution != null &&
result.xcodeBuildExecution.buildForPhysicalDevice && result.xcodeBuildExecution.buildForPhysicalDevice &&
result.stdout?.contains('BCEROR') == true && result.stdout?.contains('BCEROR') == true &&
......
...@@ -77,10 +77,8 @@ List<Plugin> _findPlugins(String directory) { ...@@ -77,10 +77,8 @@ List<Plugin> _findPlugins(String directory) {
return plugins; return plugins;
} }
// Return true if .flutter-plugins has changed, otherwise return false. void _writeFlutterPluginsList(String directory, List<Plugin> plugins) {
bool _writeFlutterPluginsList(String directory, List<Plugin> plugins) {
final File pluginsProperties = fs.file(fs.path.join(directory, '.flutter-plugins')); final File pluginsProperties = fs.file(fs.path.join(directory, '.flutter-plugins'));
final String priorFlutterPlugins = (pluginsProperties.existsSync() ? pluginsProperties.readAsStringSync() : null);
final String pluginManifest = final String pluginManifest =
plugins.map((Plugin p) => '${p.name}=${escapePath(p.path)}').join('\n'); plugins.map((Plugin p) => '${p.name}=${escapePath(p.path)}').join('\n');
if (pluginManifest.isNotEmpty) { if (pluginManifest.isNotEmpty) {
...@@ -90,8 +88,6 @@ bool _writeFlutterPluginsList(String directory, List<Plugin> plugins) { ...@@ -90,8 +88,6 @@ bool _writeFlutterPluginsList(String directory, List<Plugin> plugins) {
pluginsProperties.deleteSync(); pluginsProperties.deleteSync();
} }
} }
final String currentFlutterPlugins = (pluginsProperties.existsSync() ? pluginsProperties.readAsStringSync() : null);
return currentFlutterPlugins != priorFlutterPlugins;
} }
const String _androidPluginRegistryTemplate = '''package io.flutter.plugins; const String _androidPluginRegistryTemplate = '''package io.flutter.plugins;
...@@ -210,25 +206,17 @@ void _writeIOSPluginRegistry(String directory, List<Plugin> plugins) { ...@@ -210,25 +206,17 @@ void _writeIOSPluginRegistry(String directory, List<Plugin> plugins) {
} }
class InjectPluginsResult{
InjectPluginsResult({this.hasPlugin : false, this.hasChanged : false});
/// True if any flutter plugin exists, otherwise false.
final bool hasPlugin;
/// True if plugins have changed since last build.
final bool hasChanged;
}
/// Finds Flutter plugins in the pubspec.yaml, creates platform injection /// Finds Flutter plugins in the pubspec.yaml, creates platform injection
/// registries classes and add them to the build dependencies. /// registries classes and add them to the build dependencies.
/// ///
/// Returns whether any Flutter plugins are added. /// Returns whether any Flutter plugins are added.
InjectPluginsResult injectPlugins({String directory}) { bool injectPlugins({String directory}) {
directory ??= fs.currentDirectory.path; directory ??= fs.currentDirectory.path;
final List<Plugin> plugins = _findPlugins(directory); final List<Plugin> plugins = _findPlugins(directory);
final bool hasPluginsChanged = _writeFlutterPluginsList(directory, plugins); _writeFlutterPluginsList(directory, plugins);
if (fs.isDirectorySync(fs.path.join(directory, 'android'))) if (fs.isDirectorySync(fs.path.join(directory, 'android')))
_writeAndroidPluginRegistry(directory, plugins); _writeAndroidPluginRegistry(directory, plugins);
if (fs.isDirectorySync(fs.path.join(directory, 'ios'))) if (fs.isDirectorySync(fs.path.join(directory, 'ios')))
_writeIOSPluginRegistry(directory, plugins); _writeIOSPluginRegistry(directory, plugins);
return new InjectPluginsResult(hasPlugin: plugins.isNotEmpty, hasChanged: hasPluginsChanged); return plugins.isNotEmpty;
} }
\ No newline at end of file
# Uncomment this line to define a global platform for your project # Uncomment this line to define a global platform for your project
# platform :ios, '9.0' # platform :ios, '9.0'
def parse_KV_file(file,seperator='=') if ENV['FLUTTER_FRAMEWORK_DIR'] == nil
file_abs_path = File.expand_path(file) abort('Please set FLUTTER_FRAMEWORK_DIR to the directory containing Flutter.framework')
if !File.exists? file_abs_path
puts "Generated.xcconfig or .flutter-plugins expected but not present. If you're running pod install manually, make sure flutter build or flutter run is executed once first."
return [];
end
pods_ary = []
File.foreach(file_abs_path) { |line|
plugin = line.split(pattern=seperator)
if plugin.length == 2
podname = plugin[0].strip()
path = plugin[1].strip()
podpath = File.expand_path("#{path}", file_abs_path)
pods_ary.push({:name => podname,:path=>podpath});
else
puts "Invalid plugin specification: #{line}"
end
}
return pods_ary
end end
target 'Runner' do target 'Runner' do
use_frameworks! # Pods for Runner
# Flutter Pods # Flutter Pods
generated_xcode_build_settings = parse_KV_file("./Flutter/Generated.xcconfig") pod 'Flutter', :path => ENV['FLUTTER_FRAMEWORK_DIR']
generated_xcode_build_settings.map{ |p|
if p[:name]=='FLUTTER_FRAMEWORK_DIR'
pod 'Flutter', :path => p[:path]
end
}
# Plugin Pods if File.exists? '../.flutter-plugins'
plugin_pods = parse_KV_file("../.flutter-plugins") flutter_root = File.expand_path('..')
plugin_pods.map{ |p| File.foreach('../.flutter-plugins') { |line|
pod p[:name], :path => File.expand_path("ios",p[:path]) plugin = line.split(pattern='=')
} if plugin.length == 2
name = plugin[0].strip()
path = plugin[1].strip()
resolved_path = File.expand_path("#{path}/ios", flutter_root)
pod name, :path => resolved_path
else
puts "Invalid plugin specification: #{line}"
end
}
end
end end
post_install do |installer| post_install do |installer|
...@@ -45,4 +33,4 @@ post_install do |installer| ...@@ -45,4 +33,4 @@ post_install do |installer|
config.build_settings['ENABLE_BITCODE'] = 'NO' config.build_settings['ENABLE_BITCODE'] = 'NO'
end end
end end
end end
\ No newline at end of file
# Uncomment this line to define a global platform for your project # Uncomment this line to define a global platform for your project
# platform :ios, '9.0' # platform :ios, '9.0'
def parse_KV_file(file,seperator='=') if ENV['FLUTTER_FRAMEWORK_DIR'] == nil
file_abs_path = File.expand_path(file) abort('Please set FLUTTER_FRAMEWORK_DIR to the directory containing Flutter.framework')
if !File.exists? file_abs_path
puts "Generated.xcconfig or .flutter-plugins expected but not present. If you're running pod install manually, make sure flutter build or flutter run is executed once first."
return [];
end
pods_ary = []
File.foreach(file_abs_path) { |line|
plugin = line.split(pattern=seperator)
if plugin.length == 2
podname = plugin[0].strip()
path = plugin[1].strip()
podpath = File.expand_path("#{path}", file_abs_path)
pods_ary.push({:name => podname,:path=>podpath});
else
puts "Invalid plugin specification: #{line}"
end
}
return pods_ary
end end
target 'Runner' do target 'Runner' do
use_frameworks! use_frameworks!
# Pods for Runner
# Flutter Pods # Flutter Pods
generated_xcode_build_settings = parse_KV_file("./Flutter/Generated.xcconfig") pod 'Flutter', :path => ENV['FLUTTER_FRAMEWORK_DIR']
generated_xcode_build_settings.map{ |p|
if p[:name]=='FLUTTER_FRAMEWORK_DIR'
pod 'Flutter', :path => p[:path]
end
}
# Plugin Pods if File.exists? '../.flutter-plugins'
plugin_pods = parse_KV_file("../.flutter-plugins") flutter_root = File.expand_path('..')
plugin_pods.map{ |p| File.foreach('../.flutter-plugins') { |line|
pod p[:name], :path => File.expand_path("ios",p[:path]) plugin = line.split(pattern='=')
} if plugin.length == 2
name = plugin[0].strip()
path = plugin[1].strip()
resolved_path = File.expand_path("#{path}/ios", flutter_root)
pod name, :path => resolved_path
else
puts "Invalid plugin specification: #{line}"
end
}
end
end end
post_install do |installer| post_install do |installer|
...@@ -45,4 +35,4 @@ post_install do |installer| ...@@ -45,4 +35,4 @@ post_install do |installer|
config.build_settings['ENABLE_BITCODE'] = 'NO' config.build_settings['ENABLE_BITCODE'] = 'NO'
end end
end end
end end
\ No newline at end of file
...@@ -41,7 +41,7 @@ void main() { ...@@ -41,7 +41,7 @@ void main() {
when(mockProcessManager.run( when(mockProcessManager.run(
<String>['pod', 'install', '--verbose'], <String>['pod', 'install', '--verbose'],
workingDirectory: 'project/ios', workingDirectory: 'project/ios',
environment: <String, String>{'COCOAPODS_DISABLE_STATS': 'true'}, environment: <String, String>{'FLUTTER_FRAMEWORK_DIR': 'engine/path', 'COCOAPODS_DISABLE_STATS': 'true'},
)).thenReturn(exitsHappy); )).thenReturn(exitsHappy);
}); });
...@@ -50,12 +50,13 @@ void main() { ...@@ -50,12 +50,13 @@ void main() {
() async { () async {
await cocoaPodsUnderTest.processPods( await cocoaPodsUnderTest.processPods(
appIosDir: projectUnderTest, appIosDir: projectUnderTest,
iosEngineDir: 'engine/path',
); );
expect(fs.file(fs.path.join('project', 'ios', 'Podfile')).readAsStringSync() , 'Objective-C podfile template'); expect(fs.file(fs.path.join('project', 'ios', 'Podfile')).readAsStringSync() , 'Objective-C podfile template');
verify(mockProcessManager.run( verify(mockProcessManager.run(
<String>['pod', 'install', '--verbose'], <String>['pod', 'install', '--verbose'],
workingDirectory: 'project/ios', workingDirectory: 'project/ios',
environment: <String, String>{'COCOAPODS_DISABLE_STATS': 'true'}, environment: <String, String>{'FLUTTER_FRAMEWORK_DIR': 'engine/path', 'COCOAPODS_DISABLE_STATS': 'true'},
)); ));
}, },
overrides: <Type, Generator>{ overrides: <Type, Generator>{
...@@ -69,13 +70,14 @@ void main() { ...@@ -69,13 +70,14 @@ void main() {
() async { () async {
await cocoaPodsUnderTest.processPods( await cocoaPodsUnderTest.processPods(
appIosDir: projectUnderTest, appIosDir: projectUnderTest,
iosEngineDir: 'engine/path',
isSwift: true, isSwift: true,
); );
expect(fs.file(fs.path.join('project', 'ios', 'Podfile')).readAsStringSync() , 'Swift podfile template'); expect(fs.file(fs.path.join('project', 'ios', 'Podfile')).readAsStringSync() , 'Swift podfile template');
verify(mockProcessManager.run( verify(mockProcessManager.run(
<String>['pod', 'install', '--verbose'], <String>['pod', 'install', '--verbose'],
workingDirectory: 'project/ios', workingDirectory: 'project/ios',
environment: <String, String>{'COCOAPODS_DISABLE_STATS': 'true'}, environment: <String, String>{'FLUTTER_FRAMEWORK_DIR': 'engine/path', 'COCOAPODS_DISABLE_STATS': 'true'},
)); ));
}, },
overrides: <Type, Generator>{ overrides: <Type, Generator>{
...@@ -92,11 +94,12 @@ void main() { ...@@ -92,11 +94,12 @@ void main() {
..writeAsString('Existing Podfile'); ..writeAsString('Existing Podfile');
await cocoaPodsUnderTest.processPods( await cocoaPodsUnderTest.processPods(
appIosDir: projectUnderTest, appIosDir: projectUnderTest,
iosEngineDir: 'engine/path',
); expect(fs.file(fs.path.join('project', 'ios', 'Podfile')).readAsStringSync() , 'Existing Podfile'); ); expect(fs.file(fs.path.join('project', 'ios', 'Podfile')).readAsStringSync() , 'Existing Podfile');
verify(mockProcessManager.run( verify(mockProcessManager.run(
<String>['pod', 'install', '--verbose'], <String>['pod', 'install', '--verbose'],
workingDirectory: 'project/ios', workingDirectory: 'project/ios',
environment: <String, String>{'COCOAPODS_DISABLE_STATS': 'true'}, environment: <String, String>{'FLUTTER_FRAMEWORK_DIR': 'engine/path', 'COCOAPODS_DISABLE_STATS': 'true'},
)); ));
}, },
overrides: <Type, Generator>{ overrides: <Type, Generator>{
...@@ -112,13 +115,14 @@ void main() { ...@@ -112,13 +115,14 @@ void main() {
try { try {
await cocoaPodsUnderTest.processPods( await cocoaPodsUnderTest.processPods(
appIosDir: projectUnderTest, appIosDir: projectUnderTest,
iosEngineDir: 'engine/path',
); );
fail('Expected tool error'); fail('Expected tool error');
} catch (ToolExit) { } catch (ToolExit) {
verifyNever(mockProcessManager.run( verifyNever(mockProcessManager.run(
<String>['pod', 'install', '--verbose'], <String>['pod', 'install', '--verbose'],
workingDirectory: 'project/ios', workingDirectory: 'project/ios',
environment: <String, String>{'COCOAPODS_DISABLE_STATS': 'true'}, environment: <String, String>{'FLUTTER_FRAMEWORK_DIR': 'engine/path', 'COCOAPODS_DISABLE_STATS': 'true'},
)); ));
} }
}, },
...@@ -138,7 +142,7 @@ void main() { ...@@ -138,7 +142,7 @@ void main() {
when(mockProcessManager.run( when(mockProcessManager.run(
<String>['pod', 'install', '--verbose'], <String>['pod', 'install', '--verbose'],
workingDirectory: 'project/ios', workingDirectory: 'project/ios',
environment: <String, String>{'COCOAPODS_DISABLE_STATS': 'true'}, environment: <String, String>{'FLUTTER_FRAMEWORK_DIR': 'engine/path', 'COCOAPODS_DISABLE_STATS': 'true'},
)).thenReturn(new ProcessResult( )).thenReturn(new ProcessResult(
1, 1,
1, 1,
...@@ -161,6 +165,7 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by ...@@ -161,6 +165,7 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by
try { try {
await cocoaPodsUnderTest.processPods( await cocoaPodsUnderTest.processPods(
appIosDir: projectUnderTest, appIosDir: projectUnderTest,
iosEngineDir: 'engine/path',
); expect(fs.file(fs.path.join('project', 'ios', 'Podfile')).readAsStringSync() , 'Existing Podfile'); ); expect(fs.file(fs.path.join('project', 'ios', 'Podfile')).readAsStringSync() , 'Existing Podfile');
fail('Exception expected'); fail('Exception expected');
} catch (ToolExit) { } catch (ToolExit) {
...@@ -172,62 +177,6 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by ...@@ -172,62 +177,6 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by
ProcessManager: () => mockProcessManager, ProcessManager: () => mockProcessManager,
}, },
); );
testUsingContext(
'Run pod install if plugins or flutter framework have changes.',
() async {
fs.file(fs.path.join('project', 'ios', 'Podfile'))
..createSync()
..writeAsString('Existing Podfile');
fs.file(fs.path.join('project', 'ios', 'Podfile.lock'))
..createSync()
..writeAsString('Existing lock files.');
fs.file(fs.path.join('project', 'ios', 'Pods','Manifest.lock'))
..createSync(recursive: true)
..writeAsString('Existing lock files.');
await cocoaPodsUnderTest.processPods(
appIosDir: projectUnderTest,
pluginOrFlutterPodChanged: true
);
verify(mockProcessManager.run(
<String>['pod', 'install', '--verbose'],
workingDirectory: 'project/ios',
environment: <String, String>{'COCOAPODS_DISABLE_STATS': 'true'},
));
},
overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => mockProcessManager,
},
);
testUsingContext(
'Skip pod install if plugins and flutter framework remain unchanged.',
() async {
fs.file(fs.path.join('project', 'ios', 'Podfile'))
..createSync()
..writeAsString('Existing Podfile');
fs.file(fs.path.join('project', 'ios', 'Podfile.lock'))
..createSync()
..writeAsString('Existing lock files.');
fs.file(fs.path.join('project', 'ios', 'Pods','Manifest.lock'))
..createSync(recursive: true)
..writeAsString('Existing lock files.');
await cocoaPodsUnderTest.processPods(
appIosDir: projectUnderTest,
pluginOrFlutterPodChanged: false
);
verifyNever(mockProcessManager.run(
<String>['pod', 'install', '--verbose'],
workingDirectory: 'project/ios',
environment: <String, String>{'COCOAPODS_DISABLE_STATS': 'true'},
));
},
overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => mockProcessManager,
},
);
} }
class MockProcessManager extends Mock implements ProcessManager {} class MockProcessManager extends Mock implements ProcessManager {}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment