1// Copyright 2019 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5import '../base/common.dart'; 6import '../base/file_system.dart'; 7import '../base/io.dart'; 8import '../base/process.dart'; 9import '../convert.dart'; 10import '../globals.dart'; 11 12import 'fuchsia_sdk.dart'; 13 14/// This is a basic wrapper class for the Fuchsia SDK's `pm` tool. 15class FuchsiaPM { 16 /// Initializes the staging area at [buildPath] for creating the Fuchsia 17 /// package for the app named [appName]. 18 /// 19 /// When successful, this creates a file under [buildPath] at `meta/package`. 20 /// 21 /// NB: The [buildPath] should probably be e.g. `build/fuchsia/pkg`, and the 22 /// [appName] should probably be the name of the app from the pubspec file. 23 Future<bool> init(String buildPath, String appName) { 24 return _runPMCommand(<String>[ 25 '-o', 26 buildPath, 27 '-n', 28 appName, 29 'init', 30 ]); 31 } 32 33 /// Generates a new private key to be used to sign a Fuchsia package. 34 /// 35 /// [buildPath] should be the same [buildPath] passed to [init]. 36 Future<bool> genkey(String buildPath, String outKeyPath) { 37 return _runPMCommand(<String>[ 38 '-o', 39 buildPath, 40 '-k', 41 outKeyPath, 42 'genkey', 43 ]); 44 } 45 46 /// Updates, signs, and seals a Fuchsia package. 47 /// 48 /// [buildPath] should be the same [buildPath] passed to [init]. 49 /// [manifestPath] must be a file containing lines formatted as follows: 50 /// 51 /// data/path/to/file/in/the/package=/path/to/file/on/the/host 52 /// 53 /// which describe the contents of the Fuchsia package. It must also contain 54 /// two other entries: 55 /// 56 /// meta/$APPNAME.cmx=/path/to/cmx/on/the/host/$APPNAME.cmx 57 /// meta/package=/path/to/package/file/from/init/package 58 /// 59 /// where $APPNAME is the same [appName] passed to [init], and meta/package 60 /// is set up to be the file `meta/package` created by [init]. 61 Future<bool> build( 62 String buildPath, String keyPath, String manifestPath) { 63 return _runPMCommand(<String>[ 64 '-o', 65 buildPath, 66 '-k', 67 keyPath, 68 '-m', 69 manifestPath, 70 'build', 71 ]); 72 } 73 74 /// Constructs a .far representation of the Fuchsia package. 75 /// 76 /// When successful, creates a file `app_name-0.far` under [buildPath], which 77 /// is the Fuchsia package. 78 /// 79 /// [buildPath] should be the same path passed to [init], and [manfiestPath] 80 /// should be the same manifest passed to [build]. 81 Future<bool> archive(String buildPath, String keyPath, String manifestPath) { 82 return _runPMCommand(<String>[ 83 '-o', 84 buildPath, 85 '-k', 86 keyPath, 87 '-m', 88 manifestPath, 89 'archive', 90 ]); 91 } 92 93 /// Initializes a new package repository at [repoPath] to be later served by 94 /// the 'serve' command. 95 Future<bool> newrepo(String repoPath) { 96 return _runPMCommand(<String>[ 97 'newrepo', 98 '-repo', 99 repoPath, 100 ]); 101 } 102 103 /// Spawns an http server in a new process for serving Fuchisa packages. 104 /// 105 /// The arguemnt [repoPath] should have previously been an arguemnt to 106 /// [newrepo]. The [host] should be the host reported by 107 /// [FuchsiaDevFinder.resolve], and [port] should be an unused port for the 108 /// http server to bind. 109 Future<Process> serve(String repoPath, String host, int port) async { 110 if (fuchsiaArtifacts.pm == null) { 111 throwToolExit('Fuchsia pm tool not found'); 112 } 113 final List<String> command = <String>[ 114 fuchsiaArtifacts.pm.path, 115 'serve', 116 '-repo', 117 repoPath, 118 '-l', 119 '$host:$port', 120 ]; 121 final Process process = await runCommand(command); 122 process.stdout 123 .transform(utf8.decoder) 124 .transform(const LineSplitter()) 125 .listen(printTrace); 126 process.stderr 127 .transform(utf8.decoder) 128 .transform(const LineSplitter()) 129 .listen(printError); 130 return process; 131 } 132 133 /// Publishes a Fuchsia package to a served package repository. 134 /// 135 /// For a package repo initialized with [newrepo] at [repoPath] and served 136 /// by [serve], this call publishes the `far` package at [packagePath] to 137 /// the repo such that it will be visible to devices connecting to the 138 /// package server. 139 Future<bool> publish(String repoPath, String packagePath) { 140 return _runPMCommand(<String>[ 141 'publish', 142 '-a', 143 '-r', 144 repoPath, 145 '-f', 146 packagePath, 147 ]); 148 } 149 150 Future<bool> _runPMCommand(List<String> args) async { 151 if (fuchsiaArtifacts.pm == null) { 152 throwToolExit('Fuchsia pm tool not found'); 153 } 154 final List<String> command = <String>[fuchsiaArtifacts.pm.path] + args; 155 final RunResult result = await runAsync(command); 156 return result.exitCode == 0; 157 } 158} 159 160/// A class for running and retaining state for a Fuchsia package server. 161/// 162/// [FuchsiaPackageServer] takes care of initializing the package repository, 163/// spinning up the package server, publishing packages, and shutting down the 164/// the server. 165/// 166/// Example usage: 167/// var server = FuchsiaPackageServer( 168/// '/path/to/repo', 169/// 'server_name', 170/// await FuchsiaDevFinder.resolve(deviceName), 171/// await freshPort()); 172/// try { 173/// await server.start(); 174/// await server.addPackage(farArchivePath); 175/// ... 176/// } finally { 177/// server.stop(); 178/// } 179class FuchsiaPackageServer { 180 FuchsiaPackageServer(this._repo, this.name, this._host, this._port); 181 182 final String _repo; 183 final String _host; 184 final int _port; 185 186 Process _process; 187 188 /// The url that can be used by the device to access this package server. 189 String get url => 'http://$_host:$_port'; 190 191 // The name used to reference the server by fuchsia-pkg:// urls. 192 final String name; 193 194 /// Usees [FuchiaPM.newrepo] and [FuchsiaPM.serve] to spin up a new Fuchsia 195 /// package server. 196 /// 197 /// Returns false if the repo could not be created or the server could not 198 /// be spawned, and true otherwise. 199 Future<bool> start() async { 200 if (_process != null) { 201 printError('$this already started!'); 202 return false; 203 } 204 // initialize a new repo. 205 if (!await fuchsiaSdk.fuchsiaPM.newrepo(_repo)) { 206 printError('Failed to create a new package server repo'); 207 return false; 208 } 209 _process = await fuchsiaSdk.fuchsiaPM.serve(_repo, _host, _port); 210 // Put a completer on _process.exitCode to watch for error. 211 unawaited(_process.exitCode.whenComplete(() { 212 // If _process is null, then the server was stopped deliberately. 213 if (_process != null) { 214 printError('Error running Fuchsia pm tool "serve" command'); 215 } 216 })); 217 return true; 218 } 219 220 /// Forcefully stops the package server process by sending it SIGTERM. 221 void stop() { 222 if (_process != null) { 223 _process.kill(); 224 _process = null; 225 } 226 } 227 228 /// Uses [FuchsiaPM.publish] to add the Fuchsia 'far' package at 229 /// [packagePath] to the package server. 230 /// 231 /// Returns true on success and false if the server wasn't started or the 232 /// publish command failed. 233 Future<bool> addPackage(File package) async { 234 if (_process == null) { 235 return false; 236 } 237 return await fuchsiaSdk.fuchsiaPM.publish(_repo, package.path); 238 } 239 240 @override 241 String toString() { 242 final String p = (_process == null) ? 'stopped' : 'running ${_process.pid}'; 243 return 'FuchsiaPackageServer at $_host:$_port ($p)'; 244 } 245} 246