fuchsia_pm.dart 7.28 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
// @dart = 2.8

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

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.
26 27
  Future<bool> init(String buildPath, String appName) {
    return _runPMCommand(<String>[
28 29 30 31 32
      '-o',
      buildPath,
      '-n',
      appName,
      'init',
33
    ]);
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
  }

  /// 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].
51
  Future<bool> build(String buildPath, String manifestPath) {
52
    return _runPMCommand(<String>[
53 54 55 56 57
      '-o',
      buildPath,
      '-m',
      manifestPath,
      'build',
58
    ]);
59 60 61 62 63 64 65
  }

  /// Constructs a .far representation of the Fuchsia package.
  ///
  /// When successful, creates a file `app_name-0.far` under [buildPath], which
  /// is the Fuchsia package.
  ///
66
  /// [buildPath] should be the same path passed to [init], and [manifestPath]
67
  /// should be the same manifest passed to [build].
68
  Future<bool> archive(String buildPath, String manifestPath) {
69
    return _runPMCommand(<String>[
70 71 72 73 74
      '-o',
      buildPath,
      '-m',
      manifestPath,
      'archive',
75 76 77 78 79 80 81 82 83 84 85 86 87
    ]);
  }

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

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

174 175 176
  static const String deviceHost = 'fuchsia.com';
  static const String toolHost = 'flutter_tool';

177 178 179 180 181 182
  final String _repo;
  final String _host;
  final int _port;

  Process _process;

183 184 185
  // The name used to reference the server by fuchsia-pkg:// urls.
  final String name;

186 187
  int get port => _port;

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

  /// 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;
    }
231
    return fuchsiaSdk.fuchsiaPM.publish(_repo, package.path);
232 233 234 235 236 237 238
  }

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