fuchsia_pm.dart 7.65 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
import '../base/common.dart';
import '../base/file_system.dart';
7
import '../base/io.dart';
8
import '../base/net.dart';
9 10
import '../base/process.dart';
import '../convert.dart';
11
import '../globals.dart' as globals;
12 13 14 15 16 17 18 19 20 21 22 23

import 'fuchsia_sdk.dart';

/// 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.
24 25
  Future<bool> init(String buildPath, String appName) {
    return _runPMCommand(<String>[
26 27 28 29 30
      '-o',
      buildPath,
      '-n',
      appName,
      'init',
31
    ]);
32 33 34 35 36
  }

  /// Generates a new private key to be used to sign a Fuchsia package.
  ///
  /// [buildPath] should be the same [buildPath] passed to [init].
37 38
  Future<bool> genkey(String buildPath, String outKeyPath) {
    return _runPMCommand(<String>[
39 40 41 42 43
      '-o',
      buildPath,
      '-k',
      outKeyPath,
      'genkey',
44
    ]);
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
  }

  /// 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].
62
  Future<bool> build(String buildPath, String keyPath, String manifestPath) {
63
    return _runPMCommand(<String>[
64 65 66 67 68 69 70
      '-o',
      buildPath,
      '-k',
      keyPath,
      '-m',
      manifestPath,
      'build',
71
    ]);
72 73 74 75 76 77 78
  }

  /// Constructs a .far representation of the Fuchsia package.
  ///
  /// When successful, creates a file `app_name-0.far` under [buildPath], which
  /// is the Fuchsia package.
  ///
79
  /// [buildPath] should be the same path passed to [init], and [manifestPath]
80
  /// should be the same manifest passed to [build].
81 82
  Future<bool> archive(String buildPath, String keyPath, String manifestPath) {
    return _runPMCommand(<String>[
83 84 85 86 87 88 89
      '-o',
      buildPath,
      '-k',
      keyPath,
      '-m',
      manifestPath,
      'archive',
90 91 92 93 94 95 96 97 98 99 100 101 102
    ]);
  }

  /// 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,
    ]);
  }

103
  /// Spawns an http server in a new process for serving Fuchsia packages.
104
  ///
105
  /// The argument [repoPath] should have previously been an argument to
106 107 108 109
  /// [newrepo]. The [host] should be the host reported by
  /// [FuchsiaDevFinder.resolve], and [port] should be an unused port for the
  /// http server to bind.
  Future<Process> serve(String repoPath, String host, int port) async {
110
    if (globals.fuchsiaArtifacts.pm == null) {
111 112
      throwToolExit('Fuchsia pm tool not found');
    }
113
    if (isIPv6Address(host.split('%').first)) {
114
      host = '[$host]';
115
    }
116
    final List<String> command = <String>[
117
      globals.fuchsiaArtifacts.pm.path,
118 119 120 121 122
      'serve',
      '-repo',
      repoPath,
      '-l',
      '$host:$port',
123
    ];
124
    final Process process = await globals.processUtils.start(command);
125 126 127
    process.stdout
        .transform(utf8.decoder)
        .transform(const LineSplitter())
128
        .listen(globals.printTrace);
129 130 131
    process.stderr
        .transform(utf8.decoder)
        .transform(const LineSplitter())
132
        .listen(globals.printError);
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
    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 {
154
    if (globals.fuchsiaArtifacts.pm == null) {
155 156
      throwToolExit('Fuchsia pm tool not found');
    }
157
    final List<String> command = <String>[globals.fuchsiaArtifacts.pm.path, ...args];
158
    final RunResult result = await globals.processUtils.run(command);
159 160 161 162 163 164 165 166
    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
Pierre-Louis's avatar
Pierre-Louis committed
167
/// server.
168 169 170 171
///
/// Example usage:
/// var server = FuchsiaPackageServer(
///     '/path/to/repo',
172
///     'server_name',
173 174 175 176 177 178 179 180 181 182
///     await FuchsiaDevFinder.resolve(deviceName),
///     await freshPort());
/// try {
///   await server.start();
///   await server.addPackage(farArchivePath);
///   ...
/// } finally {
///   server.stop();
/// }
class FuchsiaPackageServer {
183 184 185 186 187
  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);
188

189 190 191
  static const String deviceHost = 'fuchsia.com';
  static const String toolHost = 'flutter_tool';

192 193 194 195 196 197
  final String _repo;
  final String _host;
  final int _port;

  Process _process;

198 199 200
  // The name used to reference the server by fuchsia-pkg:// urls.
  final String name;

201 202
  int get port => _port;

203
  /// Uses [FuchiaPM.newrepo] and [FuchsiaPM.serve] to spin up a new Fuchsia
204 205
  /// package server.
  ///
Chris Bracken's avatar
Chris Bracken committed
206
  /// Returns false if the repo could not be created or the server could not
207 208 209
  /// be spawned, and true otherwise.
  Future<bool> start() async {
    if (_process != null) {
210
      globals.printError('$this already started!');
211 212 213 214
      return false;
    }
    // initialize a new repo.
    if (!await fuchsiaSdk.fuchsiaPM.newrepo(_repo)) {
215
      globals.printError('Failed to create a new package server repo');
216 217
      return false;
    }
218 219 220 221 222
    _process = await fuchsiaSdk.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) {
223
        globals.printError('Error running Fuchsia pm tool "serve" command');
224 225
      }
    }));
226 227
    return true;
  }
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253

  /// 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 fuchsiaSdk.fuchsiaPM.publish(_repo, package.path);
  }

  @override
  String toString() {
    final String p = (_process == null) ? 'stopped' : 'running ${_process.pid}';
    return 'FuchsiaPackageServer at $_host:$_port ($p)';
  }
254
}