• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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