fuchsia_sdk.dart 5.26 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 7 8
import 'dart:async';

import '../base/file_system.dart';
import '../base/io.dart';
9
import '../base/platform.dart';
10
import '../convert.dart';
11
import '../features.dart' show featureFlags;
12
import '../globals.dart' as globals;
13

14
import 'fuchsia_ffx.dart';
15 16 17
import 'fuchsia_kernel_compiler.dart';
import 'fuchsia_pm.dart';

18
/// Returns [true] if the current platform supports Fuchsia targets.
19
bool isFuchsiaSupportedPlatform(Platform platform) {
20
  return featureFlags.isFuchsiaEnabled && (platform.isLinux || platform.isMacOS);
21 22
}

23 24 25 26 27
/// The Fuchsia SDK shell commands.
///
/// This workflow assumes development within the fuchsia source tree,
/// including a working fx command-line tool in the user's PATH.
class FuchsiaSdk {
28
  /// Interface to the 'pm' tool.
29
  late final FuchsiaPM fuchsiaPM = FuchsiaPM();
30 31

  /// Interface to the 'kernel_compiler' tool.
32
  late final FuchsiaKernelCompiler fuchsiaKernelCompiler = FuchsiaKernelCompiler();
33

34
  /// Interface to the 'ffx' tool.
35
  late final FuchsiaFfx fuchsiaFfx = FuchsiaFfx();
36

37 38
  /// Returns any attached devices is a newline-denominated String.
  ///
39
  /// Example output: abcd::abcd:abc:abcd:abcd%qemu scare-cable-skip-joy
40 41 42 43
  Future<String?> listDevices({Duration? timeout}) async {
    final File? ffx = globals.fuchsiaArtifacts?.ffx;
    if (ffx == null || !ffx.existsSync()) {
      return null;
44
    }
45
    final List<String>? devices = await fuchsiaFfx.list(timeout: timeout);
46 47 48
    if (devices == null) {
      return null;
    }
49
    return devices.isNotEmpty ? devices.join('\n') : null;
50 51
  }

52 53
  /// Returns the fuchsia system logs for an attached device where
  /// [id] is the IP address of the device.
54 55
  Stream<String>? syslogs(String id) {
    Process? process;
56
    try {
57
      final StreamController<String> controller = StreamController<String>(onCancel: () {
58
        process?.kill();
59
      });
60 61
      final File? sshConfig = globals.fuchsiaArtifacts?.sshConfig;
      if (sshConfig == null || !sshConfig.existsSync()) {
62 63
        globals.printError('Cannot read device logs: No ssh config.');
        globals.printError('Have you set FUCHSIA_SSH_CONFIG or FUCHSIA_BUILD_DIR?');
64 65 66 67 68 69
        return null;
      }
      const String remoteCommand = 'log_listener --clock Local';
      final List<String> cmd = <String>[
        'ssh',
        '-F',
70
        sshConfig.absolute.path,
71
        id, // The device's IP.
72
        remoteCommand,
73
      ];
74
      globals.processManager.start(cmd).then((Process newProcess) {
75 76 77 78
        if (controller.isClosed) {
          return;
        }
        process = newProcess;
79 80
        process?.exitCode.whenComplete(controller.close);
        controller.addStream(process!.stdout
81 82
            .transform(utf8.decoder)
            .transform(const LineSplitter()));
83 84
      });
      return controller.stream;
85
    } on Exception catch (exception) {
86
      globals.printTrace('$exception');
87
    }
88
    return const Stream<String>.empty();
89 90
  }
}
91 92 93 94

/// Fuchsia-specific artifacts used to interact with a device.
class FuchsiaArtifacts {
  /// Creates a new [FuchsiaArtifacts].
95 96
  FuchsiaArtifacts({
    this.sshConfig,
97
    this.ffx,
98
    this.pm,
99
  });
100

101 102 103 104 105 106 107
  /// Creates a new [FuchsiaArtifacts] using the cached Fuchsia SDK.
  ///
  /// Finds tools under bin/cache/artifacts/fuchsia/tools.
  /// Queries environment variables (first FUCHSIA_BUILD_DIR, then
  /// FUCHSIA_SSH_CONFIG) to find the ssh configuration needed to talk to
  /// a device.
  factory FuchsiaArtifacts.find() {
108
    if (!isFuchsiaSupportedPlatform(globals.platform)) {
109 110 111
      // Don't try to find the artifacts on platforms that are not supported.
      return FuchsiaArtifacts();
    }
112 113
    // If FUCHSIA_BUILD_DIR is defined, then look for the ssh_config dir
    // relative to it. Next, if FUCHSIA_SSH_CONFIG is defined, then use it.
114
    // TODO(zanderso): Consider passing the ssh config path in with a flag.
115
    File? sshConfig;
116 117
    if (globals.platform.environment.containsKey(_kFuchsiaBuildDir)) {
      sshConfig = globals.fs.file(globals.fs.path.join(
118
          globals.platform.environment[_kFuchsiaBuildDir]!, 'ssh-keys', 'ssh_config'));
119 120
    } else if (globals.platform.environment.containsKey(_kFuchsiaSshConfig)) {
      sshConfig = globals.fs.file(globals.platform.environment[_kFuchsiaSshConfig]);
121
    }
122

123 124
    final String fuchsia = globals.cache.getArtifactDirectory('fuchsia').path;
    final String tools = globals.fs.path.join(fuchsia, 'tools');
125
    final File ffx = globals.fs.file(globals.fs.path.join(tools, 'x64/ffx'));
126
    final File pm = globals.fs.file(globals.fs.path.join(tools, 'pm'));
127

128 129
    return FuchsiaArtifacts(
      sshConfig: sshConfig,
130
      ffx: ffx.existsSync() ? ffx : null,
131
      pm: pm.existsSync() ? pm : null,
132 133 134 135 136 137
    );
  }

  static const String _kFuchsiaSshConfig = 'FUCHSIA_SSH_CONFIG';
  static const String _kFuchsiaBuildDir = 'FUCHSIA_BUILD_DIR';

138
  /// The location of the SSH configuration file used to interact with a
139
  /// Fuchsia device.
140
  final File? sshConfig;
141

142 143
  /// The location of the ffx tool used to locate connected
  /// Fuchsia devices.
144
  final File? ffx;
145

146
  /// The pm tool.
147
  final File? pm;
148 149

  /// Returns true if the [sshConfig] file is not null and exists.
150
  bool get hasSshConfig => sshConfig != null && sshConfig!.existsSync();
151
}