| // 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 '../base/common.dart'; |
| import '../base/file_system.dart'; |
| import '../base/io.dart'; |
| import '../base/net.dart'; |
| import '../base/process.dart'; |
| import '../convert.dart'; |
| import '../globals.dart' as globals; |
| |
| /// This is a basic wrapper class for the Fuchsia SDK's `pm` tool. |
| class FuchsiaPM { |
| /// Initializes the staging area at [buildPath] for creating the Fuchsia |
| /// package for the app named [appName]. |
| /// |
| /// When successful, this creates a file under [buildPath] at `meta/package`. |
| /// |
| /// NB: The [buildPath] should probably be e.g. `build/fuchsia/pkg`, and the |
| /// [appName] should probably be the name of the app from the pubspec file. |
| Future<bool> init(String buildPath, String appName) { |
| return _runPMCommand(<String>[ |
| '-o', |
| buildPath, |
| '-n', |
| appName, |
| 'init', |
| ]); |
| } |
| |
| /// Updates, signs, and seals a Fuchsia package. |
| /// |
| /// [buildPath] should be the same [buildPath] passed to [init]. |
| /// [manifestPath] must be a file containing lines formatted as follows: |
| /// |
| /// data/path/to/file/in/the/package=/path/to/file/on/the/host |
| /// |
| /// which describe the contents of the Fuchsia package. It must also contain |
| /// two other entries: |
| /// |
| /// meta/$APPNAME.cmx=/path/to/cmx/on/the/host/$APPNAME.cmx |
| /// meta/package=/path/to/package/file/from/init/package |
| /// |
| /// where $APPNAME is the same [appName] passed to [init], and meta/package |
| /// is set up to be the file `meta/package` created by [init]. |
| Future<bool> build(String buildPath, String manifestPath) { |
| return _runPMCommand(<String>[ |
| '-o', |
| buildPath, |
| '-m', |
| manifestPath, |
| 'build', |
| ]); |
| } |
| |
| /// Constructs a .far representation of the Fuchsia package. |
| /// |
| /// When successful, creates a file `app_name-0.far` under [buildPath], which |
| /// is the Fuchsia package. |
| /// |
| /// [buildPath] should be the same path passed to [init], and [manifestPath] |
| /// should be the same manifest passed to [build]. |
| Future<bool> archive(String buildPath, String manifestPath) { |
| return _runPMCommand(<String>[ |
| '-o', |
| buildPath, |
| '-m', |
| manifestPath, |
| 'archive', |
| ]); |
| } |
| |
| /// Initializes a new package repository at [repoPath] to be later served by |
| /// the 'serve' command. |
| Future<bool> newrepo(String repoPath) { |
| return _runPMCommand(<String>[ |
| 'newrepo', |
| '-repo', |
| repoPath, |
| ]); |
| } |
| |
| /// Spawns an http server in a new process for serving Fuchsia packages. |
| /// |
| /// The argument [repoPath] should have previously been an argument to |
| /// [newrepo]. The [host] should be the host reported by |
| /// [FuchsiaFfx.resolve], and [port] should be an unused port for the |
| /// http server to bind. |
| Future<Process> serve(String repoPath, String host, int port) async { |
| final File? pm = globals.fuchsiaArtifacts?.pm; |
| if (pm == null) { |
| throwToolExit('Fuchsia pm tool not found'); |
| } |
| if (isIPv6Address(host.split('%').first)) { |
| host = '[$host]'; |
| } |
| final List<String> command = <String>[ |
| pm.path, |
| 'serve', |
| '-repo', |
| repoPath, |
| '-l', |
| '$host:$port', |
| '-c', |
| '2', |
| ]; |
| final Process process = await globals.processUtils.start(command); |
| process.stdout |
| .transform(utf8.decoder) |
| .transform(const LineSplitter()) |
| .listen(globals.printTrace); |
| process.stderr |
| .transform(utf8.decoder) |
| .transform(const LineSplitter()) |
| .listen(globals.printError); |
| return process; |
| } |
| |
| /// Publishes a Fuchsia package to a served package repository. |
| /// |
| /// For a package repo initialized with [newrepo] at [repoPath] and served |
| /// by [serve], this call publishes the `far` package at [packagePath] to |
| /// the repo such that it will be visible to devices connecting to the |
| /// package server. |
| Future<bool> publish(String repoPath, String packagePath) { |
| return _runPMCommand(<String>[ |
| 'publish', |
| '-a', |
| '-r', |
| repoPath, |
| '-f', |
| packagePath, |
| ]); |
| } |
| |
| Future<bool> _runPMCommand(List<String> args) async { |
| final File? pm = globals.fuchsiaArtifacts?.pm; |
| if (pm == null) { |
| throwToolExit('Fuchsia pm tool not found'); |
| } |
| final List<String> command = <String>[pm.path, ...args]; |
| final RunResult result = await globals.processUtils.run(command); |
| return result.exitCode == 0; |
| } |
| } |
| |
| /// A class for running and retaining state for a Fuchsia package server. |
| /// |
| /// [FuchsiaPackageServer] takes care of initializing the package repository, |
| /// spinning up the package server, publishing packages, and shutting down the |
| /// server. |
| /// |
| /// Example usage: |
| /// var server = FuchsiaPackageServer( |
| /// '/path/to/repo', |
| /// 'server_name', |
| /// await FuchsiaFfx.resolve(deviceName), |
| /// await freshPort()); |
| /// try { |
| /// await server.start(); |
| /// await server.addPackage(farArchivePath); |
| /// ... |
| /// } finally { |
| /// server.stop(); |
| /// } |
| class FuchsiaPackageServer { |
| factory FuchsiaPackageServer( |
| String repo, String name, String host, int port) { |
| return FuchsiaPackageServer._(repo, name, host, port); |
| } |
| |
| FuchsiaPackageServer._(this._repo, this.name, this._host, this._port); |
| |
| static const String deviceHost = 'fuchsia.com'; |
| static const String toolHost = 'flutter-tool'; |
| |
| final String _repo; |
| final String _host; |
| final int _port; |
| |
| Process? _process; |
| |
| // The name used to reference the server by fuchsia-pkg:// urls. |
| final String name; |
| |
| int get port => _port; |
| |
| /// Uses [FuchsiaPM.newrepo] and [FuchsiaPM.serve] to spin up a new Fuchsia |
| /// package server. |
| /// |
| /// Returns false if the repo could not be created or the server could not |
| /// be spawned, and true otherwise. |
| Future<bool> start() async { |
| if (_process != null) { |
| globals.printError('$this already started!'); |
| return false; |
| } |
| // initialize a new repo. |
| final FuchsiaPM? fuchsiaPM = globals.fuchsiaSdk?.fuchsiaPM; |
| if (fuchsiaPM == null || !await fuchsiaPM.newrepo(_repo)) { |
| globals.printError('Failed to create a new package server repo'); |
| return false; |
| } |
| _process = await fuchsiaPM.serve(_repo, _host, _port); |
| // Put a completer on _process.exitCode to watch for error. |
| unawaited(_process?.exitCode.whenComplete(() { |
| // If _process is null, then the server was stopped deliberately. |
| if (_process != null) { |
| globals.printError('Error running Fuchsia pm tool "serve" command'); |
| } |
| })); |
| return true; |
| } |
| |
| /// Forcefully stops the package server process by sending it SIGTERM. |
| void stop() { |
| if (_process != null) { |
| _process?.kill(); |
| _process = null; |
| } |
| } |
| |
| /// Uses [FuchsiaPM.publish] to add the Fuchsia 'far' package at |
| /// [packagePath] to the package server. |
| /// |
| /// Returns true on success and false if the server wasn't started or the |
| /// publish command failed. |
| Future<bool> addPackage(File package) async { |
| if (_process == null) { |
| return false; |
| } |
| return (await globals.fuchsiaSdk?.fuchsiaPM.publish(_repo, package.path)) ?? false; |
| } |
| |
| @override |
| String toString() { |
| final String p = (_process == null) ? 'stopped' : 'running ${_process?.pid}'; |
| return 'FuchsiaPackageServer at $_host:$_port ($p)'; |
| } |
| } |