-
Notifications
You must be signed in to change notification settings - Fork 3.8k
[fuchsia_ctl] initial emulator support #147
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
cc6bb8d
546ea4a
47e2ade
e13302d
822798b
eb51c98
c539669
6d3f76f
9e3eab6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,19 +7,18 @@ import 'dart:io'; | |
| import 'package:args/args.dart'; | ||
| import 'package:file/file.dart'; | ||
| import 'package:file/local.dart'; | ||
| import 'package:fuchsia_ctl/fuchsia_ctl.dart'; | ||
| import 'package:fuchsia_ctl/src/amber_ctl.dart'; | ||
| import 'package:fuchsia_ctl/src/operation_result.dart'; | ||
| import 'package:fuchsia_ctl/src/tar.dart'; | ||
| import 'package:meta/meta.dart'; | ||
| import 'package:path/path.dart' as path; | ||
| import 'package:retry/retry.dart'; | ||
| import 'package:uuid/uuid.dart'; | ||
|
|
||
| import 'package:fuchsia_ctl/fuchsia_ctl.dart'; | ||
|
|
||
| typedef AsyncResult = Future<OperationResult> Function( | ||
| String, DevFinder, ArgResults); | ||
|
|
||
| const Map<String, AsyncResult> commands = <String, AsyncResult>{ | ||
| 'emu': emulator, | ||
| 'pave': pave, | ||
| 'pm': pm, | ||
| 'ssh': ssh, | ||
|
|
@@ -42,6 +41,18 @@ Future<void> main(List<String> args) async { | |
| defaultsTo: './dev_finder', | ||
| help: 'The path to the dev_finder executable.') | ||
| ..addFlag('help', defaultsTo: false, help: 'Prints help.'); | ||
|
|
||
| parser.addCommand('emu') | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this a blocking command? e.g it will remain running until calling ctrl+c?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes it's blocking. Should that be documented?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, please.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added in the code. I couldn't find a way to configure it to show with ArgParser |
||
| ..addOption('image', help: 'Fuchsia image to run') | ||
| ..addOption('zbi', help: 'Bootloader image to sign and run') | ||
| ..addOption('qemu-kernel', help: 'QEMU kernel to run') | ||
| ..addOption('window-size', help: 'Emulator window size formatted "WxH"') | ||
| ..addOption('aemu', help: 'AEMU executable path') | ||
| ..addOption('sdk', | ||
| help: 'Location to Fuchsia SDK containing tools and images') | ||
| ..addOption('ssh-path', defaultsTo: '.fuchsia', help: 'Path to ssh keys') | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is different from the defaults in the other commands ./.ssh
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Additionally all the other commands are only passing the private key. I'd recommend to implement the same behavior here.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ahh I see. Switched over to that. Is it okay to leave the internal reference of it in
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it will be a good idea to try to use the same names across the utility.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added. Switched to using the |
||
| ..addFlag('headless', help: 'Run FEMU without graphical window'); | ||
|
|
||
| parser.addCommand('ssh') | ||
| ..addFlag('interactive', | ||
| abbr: 'i', | ||
|
|
@@ -129,6 +140,28 @@ Future<void> main(List<String> args) async { | |
| } | ||
| } | ||
|
|
||
| @visibleForTesting | ||
| Future<OperationResult> emulator( | ||
| String deviceName, | ||
| DevFinder devFinder, | ||
| ArgResults args, | ||
| ) async { | ||
| final Emulator emulator = Emulator( | ||
| aemuPath: args['aemu'], | ||
| fuchsiaImagePath: args['image'], | ||
| fuchsiaSdkPath: args['sdk'], | ||
| qemuKernelPath: args['qemu-kernel'], | ||
| sshPath: args['ssh-path'], | ||
| zbiPath: args['zbi'], | ||
| ); | ||
| await emulator.prepareEnvironment(); | ||
|
|
||
| return emulator.start( | ||
| headless: args['headless'], | ||
| windowSize: args['window-size'], | ||
| ); | ||
| } | ||
|
|
||
| @visibleForTesting | ||
| Future<OperationResult> ssh( | ||
| String deviceName, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,11 +5,9 @@ | |
| import 'dart:io'; | ||
|
|
||
| import 'package:meta/meta.dart'; | ||
| import 'package:fuchsia_ctl/fuchsia_ctl.dart'; | ||
| import 'package:fuchsia_ctl/src/ssh_client.dart'; | ||
| import 'package:uuid/uuid.dart'; | ||
|
|
||
| import 'operation_result.dart'; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't needed anymore?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was clean up as it was already imported from the high level fuchsia_ctl since this imports the ssh class. |
||
| import 'package:fuchsia_ctl/fuchsia_ctl.dart'; | ||
|
|
||
| const SshClient _kSsh = SshClient(); | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,232 @@ | ||
| // Copyright 2019 The Flutter Authors. All rights reserved. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 2020?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This work was derivative of the package server file, so it's left as 2019. |
||
| // Use of this source code is governed by a BSD-style license that can be | ||
| // found in the LICENSE file. | ||
|
|
||
| import 'dart:io'; | ||
|
|
||
| import 'package:file/file.dart'; | ||
| import 'package:file/local.dart'; | ||
| import 'package:meta/meta.dart'; | ||
| import 'package:path/path.dart' as path; | ||
| import 'package:process/process.dart'; | ||
|
|
||
| import 'operation_result.dart'; | ||
|
|
||
| /// A wrapper for running Fuchsia images on the Android Emulator (AEMU). | ||
| class Emulator { | ||
| /// Creates a new wrapper for the `emu` tool. | ||
| Emulator({ | ||
| @required this.aemuPath, | ||
| @required this.fuchsiaImagePath, | ||
| @required this.fuchsiaSdkPath, | ||
| this.fs = const LocalFileSystem(), | ||
| this.processManager = const LocalProcessManager(), | ||
| @required this.qemuKernelPath, | ||
| this.sshPath = '.fuchsia', | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use private key instead. |
||
| @required this.zbiPath, | ||
| }) : assert(processManager != null); | ||
|
|
||
| /// The path to the AEMU executable on disk. | ||
| final String aemuPath; | ||
|
|
||
| /// Fuchsia image to load into the emulator. | ||
| final String fuchsiaImagePath; | ||
|
|
||
| /// The path to the Fuchsia SDK that contains the tools `fvm` and `zbi`. | ||
| final String fuchsiaSdkPath; | ||
|
|
||
| /// The QEMU kernel image to use. This is only bundled in Fuchsia QEMU images. | ||
| final String qemuKernelPath; | ||
|
|
||
| /// The path to the directory containing authorized_keys. | ||
| final String sshPath; | ||
|
|
||
| /// The Fuchsia bootloader image. | ||
| final String zbiPath; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any reason to pass this path if it is already in the sdk? or the comment for fuchsiaSdkPath needs to get updated?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I flipped through several iterations of naming this. This is the |
||
|
|
||
| /// Location of `fvm` in [fuchsiaSdkPath]. | ||
| @visibleForTesting | ||
| final String fvmToolPath = 'sdk/tools/fvm'; | ||
|
|
||
| /// Location of `zbi` in [fuchsiaSdkPath]. | ||
| @visibleForTesting | ||
| final String zbiToolPath = 'sdk/tools/zbi'; | ||
|
|
||
| /// Default AEMU window size to be launched. | ||
| @visibleForTesting | ||
| final String defaultWindowSize = '1280x800'; | ||
|
|
||
| /// Flag to pass to AEMU to run in headless mode. | ||
| @visibleForTesting | ||
| final String aemuHeadlessFlag = '-no-window'; | ||
|
|
||
| /// The [FileSystem] to use when running the `emu` tool. | ||
| final FileSystem fs; | ||
|
|
||
| /// The [ProcessManager] to use for running the `emu` tool. | ||
| final ProcessManager processManager; | ||
|
|
||
| /// FVM extended image of [fuchsiaImagePath] for running on FEMU. | ||
| @visibleForTesting | ||
| String fvmImagePath; | ||
|
|
||
| /// [zbiPath] that is accessible with SSH using [sshPath] keys. | ||
| @visibleForTesting | ||
| String signedZbiPath; | ||
|
|
||
| /// Update given Fuchsia assets to make them compatible with FEMU. | ||
| /// | ||
| /// 1. Ensure required assets exist. | ||
| /// 2. Create FVM image for running with FEMU. | ||
| /// 3. Sign boot image for host access to the guest FEMU instance. | ||
| Future<void> prepareEnvironment() async { | ||
| assert(fs.isFileSync(fuchsiaImagePath)); | ||
| assert(fs.isFileSync(zbiPath)); | ||
| assert(fs.isFileSync(qemuKernelPath)); | ||
|
|
||
| final String tmpPath = fs.systemTempDirectory.createTempSync().path; | ||
| fvmImagePath = '$tmpPath/fvm.blk'; | ||
| signedZbiPath = '$tmpPath/fuchsia-ssh.zbi'; | ||
|
|
||
| await _prepareFvmImage(fuchsiaImagePath, fvmImagePath); | ||
| await _signBootImage(zbiPath, signedZbiPath); | ||
| } | ||
|
|
||
| /// Double the size of [fuchsiaImagePath] to make space for the emulator | ||
| /// to write back to it. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this needed?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is for when applications or tests are run on the emulator, the emulator has space to write back to to add those files. This could be extended to take a precise value to mock devices better, but this is the current logic |
||
| Future<void> _prepareFvmImage(String fuchsiaImagePath, String fvmPath, | ||
| {String fvmExecutable}) async { | ||
| fvmExecutable ??= path.join(fuchsiaSdkPath, fvmToolPath); | ||
|
|
||
| await _run(<String>['cp', fuchsiaImagePath, fvmPath]); | ||
|
|
||
| /// [fvmTool] and FEMU need write access to [fvmPath]. | ||
| await _run(<String>['chmod', 'u+w', fvmPath]); | ||
|
|
||
| // Calculate new size by doubling the current size | ||
| final File fvmFile = fs.file(fvmPath)..createSync(); | ||
| final int newSize = fvmFile.lengthSync() * 2; | ||
|
|
||
| await _run( | ||
| <String>[fvmExecutable, fvmPath, 'extend', '--length', '$newSize']); | ||
| } | ||
|
|
||
| /// Signed [zbiPath] using [zbiExecutable] with [authorizedKeysPath] to | ||
| /// create a bootloader image that is accessible from the host. | ||
| Future<void> _signBootImage(String zbiPath, String signedZbiPath, | ||
| {String zbiExecutable, String authorizedKeysPath}) async { | ||
| zbiExecutable ??= path.join(fuchsiaSdkPath, zbiToolPath); | ||
| authorizedKeysPath ??= path.join(sshPath, 'authorized_keys'); | ||
|
|
||
| /// Ensure `zbi` is able to find the ssh keys by giving the full path. | ||
| final File authorizedKeysAbsolute = fs.file(authorizedKeysPath).absolute; | ||
|
|
||
| final List<String> zbiCommand = <String>[ | ||
| zbiExecutable, | ||
| '--compressed=zstd', | ||
| '-o', | ||
| signedZbiPath, | ||
| zbiPath, | ||
| '-e', | ||
| 'data/ssh/authorized_keys=${authorizedKeysAbsolute.path}' | ||
| ]; | ||
| await _run(zbiCommand); | ||
| } | ||
|
|
||
| /// Launch AEMU with [fvmImagePath], [signedZbiPath], and [qemuKernelPath]. | ||
| /// | ||
| /// [prepareEnvironment] must have been called before starting the emulator. | ||
| /// | ||
| /// If [headless] is true, AEMU will run without a graphical window. Infra | ||
| /// will run AEMU in headless mode. | ||
| /// | ||
| /// [windowSize] is what AEMU will set its window size to. Defaults to | ||
| /// [defaultWindowSize]. Expected to be in the format of "WIDTHxHEIGHT". | ||
| Future<OperationResult> start( | ||
| {bool headless = false, String windowSize}) async { | ||
| assert(fvmImagePath != null && fs.isFileSync(fvmImagePath)); | ||
| assert(signedZbiPath != null && fs.isFileSync(signedZbiPath)); | ||
|
|
||
| final List<String> aemuCommand = <String>[ | ||
| aemuPath, | ||
| '-feature', | ||
| 'VirtioInput,RefCountPipe,KVM,GLDirectMem,Vulkan', | ||
| '-window-size', | ||
| windowSize ?? defaultWindowSize, | ||
| '-gpu', | ||
| 'swiftshader_indirect', | ||
| if (headless) aemuHeadlessFlag, | ||
| ]; | ||
|
|
||
| /// Anything after -fuchsia flag will be passed to QEMU | ||
| aemuCommand.addAll(<String>[ | ||
| '-fuchsia', | ||
| '-kernel', qemuKernelPath, | ||
| '-initrd', signedZbiPath, | ||
| '-m', '2048', | ||
| '-serial', 'stdio', | ||
| '-vga', 'none', | ||
| '-device', 'virtio-keyboard-pci', | ||
| '-device', 'virtio_input_multi_touch_pci_1', | ||
| '-smp', '4,threads=2', | ||
| '-machine', 'q35', | ||
| '-device', 'isa-debug-exit,iobase=0xf4,iosize=0x04', | ||
| // TODO(chillers): Add hardware acceleration option to configure this. | ||
| '-enable-kvm', | ||
| '-cpu', 'host,migratable=no,+invtsc', | ||
| '-netdev', 'type=tap,ifname=qemu,script=no,downscript=no,id=net0', | ||
| '-device', 'e1000,netdev=net0,mac=52:54:00:63:5e:7a', | ||
| '-drive', 'file=$fvmImagePath,format=raw,if=none,id=vdisk', | ||
| '-device', 'virtio-blk-pci,drive=vdisk', | ||
| '-append', | ||
| // TODO(chillers): Generate entropy mixin. | ||
| '\'TERM=xterm-256color kernel.serial=legacy kernel.entropy-mixin=660486b6b20b4ace3fb5c81b0002abf5271289185c6a5620707606c55b377562 kernel.halt-on-panic=true\'', | ||
| ]); | ||
|
|
||
| await _start(aemuCommand); | ||
|
|
||
| return OperationResult.success(); | ||
| } | ||
|
|
||
| /// Helper function for running [command] and manging its stdio and errors. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can this be abstracted to a utilities module?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added. I moved this to |
||
| Future<void> _run(List<String> command) async { | ||
| stdout.writeln(command.join(' ')); | ||
| final ProcessResult process = await processManager.run( | ||
| command, | ||
| ); | ||
| stdout.writeln(process.stdout); | ||
| stderr.writeln(process.stderr); | ||
|
|
||
| if (process.exitCode != 0) { | ||
| throw EmulatorException('${command.first} did not return exit code 0'); | ||
| } | ||
| } | ||
|
|
||
| /// Helper function for starting [command] and piping its stdio and errors. | ||
| Future<Process> _start(List<String> command) async { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here, looks like a generic function that can be reused in some other places.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moved |
||
| stdout.writeln(command.join(' ')); | ||
| final Process process = await processManager.start( | ||
| command, | ||
| ); | ||
| stdout.addStream(process.stdout); | ||
| stderr.addStream(process.stderr); | ||
|
|
||
| if (await process.exitCode != 0) { | ||
| throw EmulatorException('${command.first} did not return exit code 0'); | ||
| } | ||
|
|
||
| return process; | ||
| } | ||
| } | ||
|
|
||
| /// Wraps exceptions thrown by [Emulator]. | ||
| class EmulatorException implements Exception { | ||
| /// Creates a new [EmulatorException]. | ||
| const EmulatorException(this.message); | ||
|
|
||
| /// The user-facing message to display. | ||
| final String message; | ||
|
|
||
| @override | ||
| String toString() => message; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,8 +3,8 @@ description: > | |
| A Dart package for paving, serving packages to, and running commands on a | ||
| Fuchsia Device. This package is primarily intended for use in Flutter's | ||
| continuous integration/testing infrastructure. | ||
| homepage: https://github.com/flutter/packags/tree/master/packages/fuchsia_ctl | ||
| version: 0.0.21 | ||
| homepage: https://github.com/flutter/packages/tree/master/packages/fuchsia_ctl | ||
| version: 0.0.22 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 23?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed |
||
|
|
||
| environment: | ||
| sdk: ">=2.4.0 <3.0.0" | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is hard to control because when releasing we usually found errors and we need to create new versions skipping some numbers. The current prod version is 0.22
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh, okay. Fixed.