fuchsia_pm.dart 7.45 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 9
import '../base/process.dart';
import '../convert.dart';
10
import '../globals.dart' as globals;
11 12 13 14 15 16 17 18 19 20 21 22

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

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

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

  /// 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 [manfiestPath]
  /// should be the same manifest passed to [build].
80 81
  Future<bool> archive(String buildPath, String keyPath, String manifestPath) {
    return _runPMCommand(<String>[
82 83 84 85 86 87 88
      '-o',
      buildPath,
      '-k',
      keyPath,
      '-m',
      manifestPath,
      'archive',
89 90 91 92 93 94 95 96 97 98 99 100 101
    ]);
  }

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

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

181 182 183
  static const String deviceHost = 'fuchsia.com';
  static const String toolHost = 'flutter_tool';

184 185 186 187 188 189
  final String _repo;
  final String _host;
  final int _port;

  Process _process;

190
  /// The URL that can be used by the device to access this package server.
191 192
  String get url => 'http://$_host:$_port';

193 194 195
  // The name used to reference the server by fuchsia-pkg:// urls.
  final String name;

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

  /// 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)';
  }
247
}