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
9d9109aa
Unverified
Commit
9d9109aa
authored
Feb 03, 2021
by
chunhtai
Committed by
GitHub
Feb 03, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
add a benchmark test for stack size (#75039)
parent
24d8dbb1
Changes
9
Show whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
355 additions
and
33 deletions
+355
-33
common.dart
dev/benchmarks/macrobenchmarks/lib/common.dart
+3
-0
main.dart
dev/benchmarks/macrobenchmarks/lib/main.dart
+9
-0
stack_size.dart
dev/benchmarks/macrobenchmarks/lib/src/stack_size.dart
+122
-0
stack_size_perf_test.dart
...rks/macrobenchmarks/test_driver/stack_size_perf_test.dart
+39
-0
util.dart
dev/benchmarks/macrobenchmarks/test_driver/util.dart
+40
-27
android_stack_size_test.dart
dev/devicelab/bin/tasks/android_stack_size_test.dart
+12
-0
adb.dart
dev/devicelab/lib/framework/adb.dart
+48
-6
perf_tests.dart
dev/devicelab/lib/tasks/perf_tests.dart
+64
-0
adb_test.dart
dev/devicelab/test/adb_test.dart
+18
-0
No files found.
dev/benchmarks/macrobenchmarks/lib/common.dart
View file @
9d9109aa
...
...
@@ -19,5 +19,8 @@ const String kImageFilteredTransformAnimationRouteName = '/imagefiltered_transfo
const
String
kMultiWidgetConstructionRouteName
=
'/multi_widget_construction'
;
const
String
kHeavyGridViewRouteName
=
'/heavy_gridview'
;
const
String
kSimpleScrollRouteName
=
'/simple_scroll'
;
const
String
kStackSizeRouteName
=
'/stack_size'
;
const
String
kScrollableName
=
'/macrobenchmark_listview'
;
const
String
kStackSizeKey
=
'stack_size'
;
dev/benchmarks/macrobenchmarks/lib/main.dart
View file @
9d9109aa
...
...
@@ -21,6 +21,7 @@ import 'src/picture_cache.dart';
import
'src/post_backdrop_filter.dart'
;
import
'src/simple_animation.dart'
;
import
'src/simple_scroll.dart'
;
import
'src/stack_size.dart'
;
import
'src/text.dart'
;
const
String
kMacrobenchmarks
=
'Macrobenchmarks'
;
...
...
@@ -54,6 +55,7 @@ class MacrobenchmarksApp extends StatelessWidget {
kMultiWidgetConstructionRouteName:
(
BuildContext
context
)
=>
const
MultiWidgetConstructTable
(
10
,
20
),
kHeavyGridViewRouteName:
(
BuildContext
context
)
=>
HeavyGridViewPage
(),
kSimpleScrollRouteName:
(
BuildContext
context
)
=>
SimpleScroll
(),
kStackSizeRouteName:
(
BuildContext
context
)
=>
StackSizePage
(),
},
);
}
...
...
@@ -181,6 +183,13 @@ class HomePage extends StatelessWidget {
Navigator
.
pushNamed
(
context
,
kLargeImageChangerRouteName
);
},
),
ElevatedButton
(
key:
const
Key
(
kStackSizeRouteName
),
child:
const
Text
(
'Stack Size'
),
onPressed:
()
{
Navigator
.
pushNamed
(
context
,
kStackSizeRouteName
);
},
),
],
),
);
...
...
dev/benchmarks/macrobenchmarks/lib/src/stack_size.dart
0 → 100644
View file @
9d9109aa
// Copyright 2014 The Flutter 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:ffi'
as
ffi
;
import
'dart:io'
as
io
;
import
'package:flutter/material.dart'
;
import
'../common.dart'
;
typedef
GetStackPointerCallback
=
int
Function
();
// c interop function:
// void* mmap(void* addr, size_t len, int prot, int flags, int fd, off_t offset);
typedef
CMmap
=
ffi
.
Pointer
<
ffi
.
Void
>
Function
(
ffi
.
Pointer
<
ffi
.
Void
>,
ffi
.
IntPtr
,
ffi
.
Int32
,
ffi
.
Int32
,
ffi
.
Int32
,
ffi
.
IntPtr
);
typedef
DartMmap
=
ffi
.
Pointer
<
ffi
.
Void
>
Function
(
ffi
.
Pointer
<
ffi
.
Void
>,
int
,
int
,
int
,
int
,
int
);
final
DartMmap
mmap
=
ffi
.
DynamicLibrary
.
process
().
lookupFunction
<
CMmap
,
DartMmap
>(
'mmap'
);
// c interop function:
// int mprotect(void* addr, size_t len, int prot);
typedef
CMprotect
=
ffi
.
Int32
Function
(
ffi
.
Pointer
<
ffi
.
Void
>,
ffi
.
IntPtr
,
ffi
.
Int32
);
typedef
DartMprotect
=
int
Function
(
ffi
.
Pointer
<
ffi
.
Void
>,
int
,
int
);
final
DartMprotect
mprotect
=
ffi
.
DynamicLibrary
.
process
()
.
lookupFunction
<
CMprotect
,
DartMprotect
>(
'mprotect'
);
const
int
kProtRead
=
1
;
const
int
kProtWrite
=
2
;
const
int
kProtExec
=
4
;
const
int
kMapPrivate
=
0x02
;
const
int
kMapJit
=
0x0
;
const
int
kMapAnon
=
0x20
;
const
int
kMemorySize
=
16
;
const
int
kInvalidFileDescriptor
=
-
1
;
const
int
kkFileMappingOffset
=
0
;
const
int
kMemoryStartingIndex
=
0
;
const
int
kExitCodeSuccess
=
0
;
final
GetStackPointerCallback
getStackPointer
=
()
{
// Makes sure we are running on an Android arm64 device.
if
(!
io
.
Platform
.
isAndroid
)
throw
'This benchmark test can only be run on Android arm64 devices.'
;
final
io
.
ProcessResult
result
=
io
.
Process
.
runSync
(
'getprop'
,
<
String
>[
'ro.product.cpu.abi'
]);
if
(
result
.
exitCode
!=
0
)
throw
'Failed to retrieve CPU information.'
;
if
(!
result
.
stdout
.
toString
().
contains
(
'arm64'
))
throw
'This benchmark test can only be run on Android arm64 devices.'
;
// Creates a block of memory to store the assembly code.
final
ffi
.
Pointer
<
ffi
.
Void
>
region
=
mmap
(
ffi
.
nullptr
,
kMemorySize
,
kProtRead
|
kProtWrite
,
kMapPrivate
|
kMapAnon
|
kMapJit
,
kInvalidFileDescriptor
,
kkFileMappingOffset
);
if
(
region
==
ffi
.
nullptr
)
{
throw
'Failed to acquire memory for the test.'
;
}
// Writes the assembly code into the memory block. This assembly code returns
// the memory address of the stack pointer.
region
.
cast
<
ffi
.
Uint8
>().
asTypedList
(
kMemorySize
).
setAll
(
kMemoryStartingIndex
,
<
int
>[
// "mov x0, sp" in machine code: E0030091.
0xe0
,
0x03
,
0x00
,
0x91
,
// "ret" in machine code: C0035FD6.
0xc0
,
0x03
,
0x5f
,
0xd6
]
);
// Makes sure the memory block is executable.
if
(
mprotect
(
region
,
kMemorySize
,
kProtRead
|
kProtExec
)
!=
kExitCodeSuccess
)
{
throw
'Failed to write executable code to the memory.'
;
}
return
region
.
cast
<
ffi
.
NativeFunction
<
ffi
.
IntPtr
Function
()>>()
.
asFunction
<
int
Function
()>();
}();
class
StackSizePage
extends
StatelessWidget
{
@override
Widget
build
(
BuildContext
context
)
{
return
Material
(
child:
Column
(
children:
<
Widget
>[
Container
(
width:
200
,
height:
100
,
child:
ParentWidget
(),
),
],
),
);
}
}
class
ParentWidget
extends
StatelessWidget
{
@override
Widget
build
(
BuildContext
context
)
{
final
int
myStackSize
=
getStackPointer
();
return
ChildWidget
(
parentStackSize:
myStackSize
);
}
}
class
ChildWidget
extends
StatelessWidget
{
const
ChildWidget
({
this
.
parentStackSize
,
Key
key
})
:
super
(
key:
key
);
final
int
parentStackSize
;
@override
Widget
build
(
BuildContext
context
)
{
final
int
myStackSize
=
getStackPointer
();
// Captures the stack size difference between parent widget and child widget
// during the rendering pipeline, i.e. one layer of stateless widget.
return
Text
(
'
${parentStackSize - myStackSize}
'
,
key:
const
ValueKey
<
String
>(
kStackSizeKey
),
);
}
}
dev/benchmarks/macrobenchmarks/test_driver/stack_size_perf_test.dart
0 → 100644
View file @
9d9109aa
// Copyright 2014 The Flutter 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:convert'
show
JsonEncoder
;
import
'package:flutter_driver/flutter_driver.dart'
;
import
'package:file/file.dart'
;
import
'package:path/path.dart'
as
path
;
import
'package:test/test.dart'
hide
TypeMatcher
,
isInstanceOf
;
import
'package:macrobenchmarks/common.dart'
;
import
'util.dart'
;
const
JsonEncoder
_prettyEncoder
=
JsonEncoder
.
withIndent
(
' '
);
void
main
(
)
{
test
(
'stack_size'
,
()
async
{
int
stackSizeInBytes
;
await
runDriverTestForRoute
(
kStackSizeRouteName
,
(
FlutterDriver
driver
)
async
{
final
String
stackSize
=
await
driver
.
getText
(
find
.
byValueKey
(
kStackSizeKey
));
expect
(
stackSize
.
isNotEmpty
,
isTrue
);
stackSizeInBytes
=
int
.
parse
(
stackSize
);
});
expect
(
stackSizeInBytes
>
0
,
isTrue
);
await
fs
.
directory
(
testOutputsDirectory
).
create
(
recursive:
true
);
final
File
file
=
fs
.
file
(
path
.
join
(
testOutputsDirectory
,
'stack_size.json'
));
await
file
.
writeAsString
(
_encodeJson
(<
String
,
dynamic
>{
'stack_size'
:
stackSizeInBytes
,
}));
},
timeout:
const
Timeout
(
kTimeout
));
}
String
_encodeJson
(
Map
<
String
,
dynamic
>
jsonObject
)
{
return
_prettyEncoder
.
convert
(
jsonObject
);
}
dev/benchmarks/macrobenchmarks/test_driver/util.dart
View file @
9d9109aa
...
...
@@ -7,16 +7,11 @@ import 'package:test/test.dart' hide TypeMatcher, isInstanceOf;
import
'package:macrobenchmarks/common.dart'
;
void
macroPerfTest
(
String
testName
,
String
routeName
,
{
Duration
pageDelay
,
Duration
duration
=
const
Duration
(
seconds:
3
),
Duration
timeout
=
const
Duration
(
seconds:
30
),
Future
<
void
>
driverOps
(
FlutterDriver
driver
),
Future
<
void
>
setupOps
(
FlutterDriver
driver
),
})
{
test
(
testName
,
()
async
{
const
Duration
kTimeout
=
Duration
(
seconds:
30
);
typedef
DriverTestCallBack
=
Future
<
void
>
Function
(
FlutterDriver
driver
);
Future
<
void
>
runDriverTestForRoute
(
String
routeName
,
DriverTestCallBack
body
)
async
{
final
FlutterDriver
driver
=
await
FlutterDriver
.
connect
();
// The slight initial delay avoids starting the timing during a
...
...
@@ -29,11 +24,28 @@ void macroPerfTest(
final
SerializableFinder
scrollable
=
find
.
byValueKey
(
kScrollableName
);
expect
(
scrollable
,
isNotNull
);
final
SerializableFinder
button
=
find
.
byValueKey
(
r
outeName
);
final
SerializableFinder
button
=
find
.
byValueKey
(
kStackSizeR
outeName
);
expect
(
button
,
isNotNull
);
await
driver
.
scrollUntilVisible
(
scrollable
,
button
,
dyScroll:
-
50.0
);
await
driver
.
tap
(
button
);
await
body
(
driver
);
driver
.
close
();
}
void
macroPerfTest
(
String
testName
,
String
routeName
,
{
Duration
pageDelay
,
Duration
duration
=
const
Duration
(
seconds:
3
),
Duration
timeout
=
kTimeout
,
Future
<
void
>
driverOps
(
FlutterDriver
driver
),
Future
<
void
>
setupOps
(
FlutterDriver
driver
),
})
{
test
(
testName
,
()
async
{
Timeline
timeline
;
await
runDriverTestForRoute
(
routeName
,
(
FlutterDriver
driver
)
async
{
if
(
pageDelay
!=
null
)
{
// Wait for the page to load
await
Future
<
void
>.
delayed
(
pageDelay
);
...
...
@@ -43,15 +55,16 @@ void macroPerfTest(
await
setupOps
(
driver
);
}
final
Timeline
timeline
=
await
driver
.
traceAction
(()
async
{
timeline
=
await
driver
.
traceAction
(()
async
{
final
Future
<
void
>
durationFuture
=
Future
<
void
>.
delayed
(
duration
);
if
(
driverOps
!=
null
)
{
await
driverOps
(
driver
);
}
await
durationFuture
;
});
});
driver
.
close
(
);
expect
(
timeline
,
isNotNull
);
final
TimelineSummary
summary
=
TimelineSummary
.
summarize
(
timeline
);
await
summary
.
writeSummaryToFile
(
testName
,
pretty:
true
);
...
...
dev/devicelab/bin/tasks/android_stack_size_test.dart
0 → 100644
View file @
9d9109aa
// Copyright 2014 The Flutter 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:flutter_devicelab/tasks/perf_tests.dart'
;
import
'package:flutter_devicelab/framework/adb.dart'
;
import
'package:flutter_devicelab/framework/framework.dart'
;
Future
<
void
>
main
()
async
{
deviceOperatingSystem
=
DeviceOperatingSystem
.
androidArm64
;
await
task
(
createStackSizeTest
());
}
dev/devicelab/lib/framework/adb.dart
View file @
9d9109aa
...
...
@@ -52,7 +52,7 @@ String _findMatchId(List<String> idList, String idPattern) {
DeviceDiscovery
get
devices
=>
DeviceDiscovery
();
/// Device operating system the test is configured to test.
enum
DeviceOperatingSystem
{
android
,
ios
,
fuchsia
,
fake
}
enum
DeviceOperatingSystem
{
android
,
androidArm64
,
ios
,
fuchsia
,
fake
}
/// Device OS to test on.
DeviceOperatingSystem
deviceOperatingSystem
=
DeviceOperatingSystem
.
android
;
...
...
@@ -63,6 +63,8 @@ abstract class DeviceDiscovery {
switch
(
deviceOperatingSystem
)
{
case
DeviceOperatingSystem
.
android
:
return
AndroidDeviceDiscovery
();
case
DeviceOperatingSystem
.
androidArm64
:
return
AndroidDeviceDiscovery
(
cpu:
_AndroidCPU
.
arm64
);
case
DeviceOperatingSystem
.
ios
:
return
IosDeviceDiscovery
();
case
DeviceOperatingSystem
.
fuchsia
:
...
...
@@ -155,12 +157,18 @@ abstract class Device {
}
}
enum
_AndroidCPU
{
arm64
,
}
class
AndroidDeviceDiscovery
implements
DeviceDiscovery
{
factory
AndroidDeviceDiscovery
()
{
return
_instance
??=
AndroidDeviceDiscovery
.
_
();
factory
AndroidDeviceDiscovery
(
{
_AndroidCPU
cpu
}
)
{
return
_instance
??=
AndroidDeviceDiscovery
.
_
(
cpu
);
}
AndroidDeviceDiscovery
.
_
();
AndroidDeviceDiscovery
.
_
(
this
.
cpu
);
final
_AndroidCPU
cpu
;
// Parses information about a device. Example:
//
...
...
@@ -185,6 +193,16 @@ class AndroidDeviceDiscovery implements DeviceDiscovery {
return
_workingDevice
;
}
Future
<
bool
>
_matchesCPURequirement
(
AndroidDevice
device
)
async
{
if
(
cpu
==
null
)
return
true
;
switch
(
cpu
)
{
case
_AndroidCPU
.
arm64
:
return
device
.
isArm64
();
}
return
true
;
}
/// Picks a random Android device out of connected devices and sets it as
/// [workingDevice].
@override
...
...
@@ -196,8 +214,22 @@ class AndroidDeviceDiscovery implements DeviceDiscovery {
if
(
allDevices
.
isEmpty
)
throw
const
DeviceException
(
'No Android devices detected'
);
if
(
cpu
!=
null
)
{
for
(
final
AndroidDevice
device
in
allDevices
)
{
if
(
await
_matchesCPURequirement
(
device
))
{
_workingDevice
=
device
;
break
;
}
}
}
else
{
// TODO(yjbanov): filter out and warn about those with low battery level
_workingDevice
=
allDevices
[
math
.
Random
().
nextInt
(
allDevices
.
length
)];
}
if
(
_workingDevice
==
null
)
throw
const
DeviceException
(
'Cannot find a suitable Android device'
);
print
(
'Device chosen:
$_workingDevice
'
);
}
...
...
@@ -206,6 +238,11 @@ class AndroidDeviceDiscovery implements DeviceDiscovery {
final
String
matchedId
=
_findMatchId
(
await
discoverDevices
(),
deviceId
);
if
(
matchedId
!=
null
)
{
_workingDevice
=
AndroidDevice
(
deviceId:
matchedId
);
if
(
cpu
!=
null
)
{
if
(!
await
_matchesCPURequirement
(
_workingDevice
))
{
throw
DeviceException
(
'The selected device
$matchedId
does not match the cpu requirement'
);
}
}
print
(
'Choose device by ID:
$matchedId
'
);
return
;
}
...
...
@@ -444,6 +481,11 @@ class AndroidDevice extends Device {
return
wakefulness
;
}
Future
<
bool
>
isArm64
()
async
{
final
String
cpuInfo
=
await
shellEval
(
'getprop'
,
const
<
String
>[
'ro.product.cpu.abi'
]);
return
cpuInfo
.
contains
(
'arm64'
);
}
Future
<
void
>
_updateDeviceInfo
()
async
{
String
info
;
try
{
...
...
dev/devicelab/lib/tasks/perf_tests.dart
View file @
9d9109aa
...
...
@@ -271,6 +271,43 @@ TaskFunction createTextfieldPerfE2ETest() {
).
run
;
}
TaskFunction
createStackSizeTest
(
)
{
final
String
testDirectory
=
'
${flutterDirectory.path}
/dev/benchmarks/macrobenchmarks'
;
const
String
testTarget
=
'test_driver/run_app.dart'
;
const
String
testDriver
=
'test_driver/stack_size_perf_test.dart'
;
return
()
{
return
inDirectory
<
TaskResult
>(
testDirectory
,
()
async
{
final
Device
device
=
await
devices
.
workingDevice
;
await
device
.
unlock
();
final
String
deviceId
=
device
.
deviceId
;
await
flutter
(
'packages'
,
options:
<
String
>[
'get'
]);
await
flutter
(
'drive'
,
options:
<
String
>[
'--no-android-gradle-daemon'
,
'-v'
,
'--verbose-system-logs'
,
'--profile'
,
'-t'
,
testTarget
,
'--driver'
,
testDriver
,
'-d'
,
deviceId
,
]);
final
Map
<
String
,
dynamic
>
data
=
json
.
decode
(
file
(
'
$testDirectory
/build/stack_size.json'
).
readAsStringSync
(),
)
as
Map
<
String
,
dynamic
>;
final
Map
<
String
,
dynamic
>
result
=
<
String
,
dynamic
>{
'stack_size_per_nesting_level'
:
data
[
'stack_size'
],
};
return
TaskResult
.
success
(
result
,
benchmarkScoreKeys:
result
.
keys
.
toList
(),
);
});
};
}
TaskFunction
createFullscreenTextfieldPerfTest
(
)
{
return
PerfTest
(
'
${flutterDirectory.path}
/dev/benchmarks/macrobenchmarks'
,
...
...
@@ -466,6 +503,15 @@ class StartupTest {
]);
applicationBinaryPath
=
'
$testDirectory
/build/app/outputs/flutter-apk/app-profile.apk'
;
break
;
case
DeviceOperatingSystem
.
androidArm64
:
await
flutter
(
'build'
,
options:
<
String
>[
'apk'
,
'-v'
,
'--profile'
,
'--target-platform=android-arm64'
,
]);
applicationBinaryPath
=
'
$testDirectory
/build/app/outputs/flutter-apk/app-profile.apk'
;
break
;
case
DeviceOperatingSystem
.
ios
:
await
flutter
(
'build'
,
options:
<
String
>[
'ios'
,
...
...
@@ -998,6 +1044,20 @@ class CompileTest {
if
(
reportPackageContentSizes
)
metrics
.
addAll
(
await
getSizesFromApk
(
apkPath
));
break
;
case
DeviceOperatingSystem
.
androidArm64
:
options
.
insert
(
0
,
'apk'
);
options
.
add
(
'--target-platform=android-arm64'
);
options
.
add
(
'--tree-shake-icons'
);
options
.
add
(
'--split-debug-info=infos/'
);
watch
.
start
();
await
flutter
(
'build'
,
options:
options
);
watch
.
stop
();
final
String
apkPath
=
'
$cwd
/build/app/outputs/flutter-apk/app-release.apk'
;
final
File
apk
=
file
(
apkPath
);
releaseSizeInBytes
=
apk
.
lengthSync
();
if
(
reportPackageContentSizes
)
metrics
.
addAll
(
await
getSizesFromApk
(
apkPath
));
break
;
case
DeviceOperatingSystem
.
fuchsia
:
throw
Exception
(
'Unsupported option for Fuchsia devices'
);
case
DeviceOperatingSystem
.
fake
:
...
...
@@ -1024,6 +1084,10 @@ class CompileTest {
options
.
insert
(
0
,
'apk'
);
options
.
add
(
'--target-platform=android-arm'
);
break
;
case
DeviceOperatingSystem
.
androidArm64
:
options
.
insert
(
0
,
'apk'
);
options
.
add
(
'--target-platform=android-arm64'
);
break
;
case
DeviceOperatingSystem
.
fuchsia
:
throw
Exception
(
'Unsupported option for Fuchsia devices'
);
case
DeviceOperatingSystem
.
fake
:
...
...
dev/devicelab/test/adb_test.dart
View file @
9d9109aa
...
...
@@ -22,6 +22,18 @@ void main() {
tearDown
(()
{
});
group
(
'cpu check'
,
()
{
test
(
'arm64'
,
()
async
{
FakeDevice
.
pretendArm64
();
final
AndroidDevice
androidDevice
=
device
as
AndroidDevice
;
expect
(
await
androidDevice
.
isArm64
(),
isTrue
);
expectLog
(<
CommandArgs
>[
cmd
(
command:
'getprop'
,
arguments:
<
String
>[
'ro.bootimage.build.fingerprint'
,
';'
,
'getprop'
,
'ro.build.version.release'
,
';'
,
'getprop'
,
'ro.build.version.sdk'
],
environment:
null
),
cmd
(
command:
'getprop'
,
arguments:
<
String
>[
'ro.product.cpu.abi'
],
environment:
null
),
]);
});
});
group
(
'isAwake/isAsleep'
,
()
{
test
(
'reads Awake'
,
()
async
{
FakeDevice
.
pretendAwake
();
...
...
@@ -187,6 +199,12 @@ class FakeDevice extends AndroidDevice {
'''
;
}
static
void
pretendArm64
()
{
output
=
'''
arm64
'''
;
}
@override
Future
<
String
>
shellEval
(
String
command
,
List
<
String
>
arguments
,
{
Map
<
String
,
String
>
environment
,
bool
silent
=
false
})
async
{
commandLog
.
add
(
CommandArgs
(
...
...
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