fuchsia_build.dart 7.95 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 'dart:async';

7 8
import 'package:meta/meta.dart';

9
import '../artifacts.dart';
10
import '../asset.dart';
11
import '../base/common.dart';
12 13
import '../base/file_system.dart';
import '../base/io.dart';
14 15
import '../base/logger.dart';
import '../base/process.dart';
16
import '../base/utils.dart';
17 18
import '../build_info.dart';
import '../bundle.dart';
19
import '../convert.dart';
20
import '../devfs.dart';
21
import '../globals.dart' as globals;
22 23 24
import '../project.dart';

import 'fuchsia_pm.dart';
25
import 'fuchsia_sdk.dart';
26

27 28 29
Future<void> _timedBuildStep(String name, Future<void> Function() action) async {
  final Stopwatch sw = Stopwatch()..start();
  await action();
30
  globals.printTrace('$name: ${sw.elapsedMilliseconds} ms.');
31
  globals.flutterUsage.sendTiming('build', name, Duration(milliseconds: sw.elapsedMilliseconds));
32 33
}

34 35
Future<void> _validateCmxFile(FuchsiaProject fuchsiaProject) async {
  final String appName = fuchsiaProject.project.manifest.appName;
36 37
  final String cmxPath = globals.fs.path.join(fuchsiaProject.meta.path, '$appName.cmx');
  final File cmxFile = globals.fs.file(cmxPath);
38 39 40 41 42
  if (!await cmxFile.exists()) {
    throwToolExit('The Fuchsia build requires a .cmx file at $cmxPath for the app: $appName.');
  }
}

43 44 45 46 47 48
// Building a Fuchsia package has a few steps:
// 1. Do the custom kernel compile using the kernel compiler from the Fuchsia
//    SDK. This produces .dilp files (among others) and a manifest file.
// 2. Create a manifest file for assets.
// 3. Using these manifests, use the Fuchsia SDK 'pm' tool to create the
//    Fuchsia package.
49 50
Future<void> buildFuchsia({
  @required FuchsiaProject fuchsiaProject,
51
  @required TargetPlatform targetPlatform,
52 53
  @required String target, // E.g., lib/main.dart
  BuildInfo buildInfo = BuildInfo.debug,
54
  String runnerPackageSource = FuchsiaPackageServer.toolHost,
55
}) async {
56
  await _validateCmxFile(fuchsiaProject);
57
  final Directory outDir = globals.fs.directory(getFuchsiaBuildDirectory());
58 59 60 61
  if (!outDir.existsSync()) {
    outDir.createSync(recursive: true);
  }

62 63 64
  await _timedBuildStep('fuchsia-kernel-compile',
    () => fuchsiaSdk.fuchsiaKernelCompiler.build(
      fuchsiaProject: fuchsiaProject, target: target, buildInfo: buildInfo));
65 66 67 68 69 70

  if (buildInfo.usesAot) {
    await _timedBuildStep('fuchsia-gen-snapshot',
      () => _genSnapshot(fuchsiaProject, target, buildInfo, targetPlatform));
  }

71 72 73
  await _timedBuildStep('fuchsia-build-assets',
    () => _buildAssets(fuchsiaProject, target, buildInfo));
  await _timedBuildStep('fuchsia-build-package',
74
    () => _buildPackage(fuchsiaProject, target, buildInfo, runnerPackageSource));
75 76
}

77 78 79 80 81 82 83 84
Future<void> _genSnapshot(
  FuchsiaProject fuchsiaProject,
  String target, // lib/main.dart
  BuildInfo buildInfo,
  TargetPlatform targetPlatform,
) async {
  final String outDir = getFuchsiaBuildDirectory();
  final String appName = fuchsiaProject.project.manifest.appName;
85
  final String dilPath = globals.fs.path.join(outDir, '$appName.dil');
86

87
  final String elf = globals.fs.path.join(outDir, 'elf.aotsnapshot');
88

89
  final String genSnapshot = globals.artifacts.getArtifactPath(
90 91 92 93 94 95 96
    Artifact.genSnapshot,
    platform: targetPlatform,
    mode: buildInfo.mode,
  );

  final List<String> command = <String>[
    genSnapshot,
97 98
    '--no-causal-async-stacks',
    '--lazy-async-stacks',
99
    '--deterministic',
100 101
    '--snapshot_kind=app-aot-elf',
    '--elf=$elf',
102 103 104 105
    if (buildInfo.isDebug) '--enable-asserts',
    dilPath,
  ];
  int result;
106
  final Status status = globals.logger.startProgress(
107 108 109 110 111 112 113 114 115 116 117 118 119
    'Compiling Fuchsia application to native code...',
    timeout: null,
  );
  try {
    result = await processUtils.stream(command, trace: true);
  } finally {
    status.cancel();
  }
  if (result != 0) {
    throwToolExit('Build process failed');
  }
}

120
Future<void> _buildAssets(
121 122 123 124
  FuchsiaProject fuchsiaProject,
  String target, // lib/main.dart
  BuildInfo buildInfo,
) async {
125 126 127 128 129 130 131 132 133 134
  final String assetDir = getAssetBuildDirectory();
  final AssetBundle assets = await buildAssets(
    manifestPath: fuchsiaProject.project.pubspecFile.path,
    packagesPath: fuchsiaProject.project.packagesFile.path,
    assetDirPath: assetDir,
    includeDefaultFonts: false,
  );

  final Map<String, DevFSContent> assetEntries =
      Map<String, DevFSContent>.from(assets.entries);
135
  await writeBundle(globals.fs.directory(assetDir), assetEntries);
136 137 138

  final String appName = fuchsiaProject.project.manifest.appName;
  final String outDir = getFuchsiaBuildDirectory();
139
  final String assetManifest = globals.fs.path.join(outDir, '${appName}_pkgassets');
140

141
  final File destFile = globals.fs.file(assetManifest);
142 143 144
  await destFile.create(recursive: true);
  final IOSink outFile = destFile.openWrite();

145
  for (final String path in assets.entries.keys) {
146 147 148 149 150 151
    outFile.write('data/$appName/$path=$assetDir/$path\n');
  }
  await outFile.flush();
  await outFile.close();
}

152
void _rewriteCmx(BuildMode mode, String runnerPackageSource, File src, File dst) {
153
  final Map<String, dynamic> cmx = castStringKeyedMap(json.decode(src.readAsStringSync()));
154 155 156 157 158 159 160 161 162
  // If the app author has already specified the runner in the cmx file, then
  // do not override it with something else.
  if (cmx.containsKey('runner')) {
    dst.writeAsStringSync(json.encode(cmx));
    return;
  }
  String runner;
  switch (mode) {
    case BuildMode.debug:
163
      runner = 'flutter_jit_runner';
164
      break;
165 166 167 168
    case BuildMode.profile:
      runner = 'flutter_aot_runner';
      break;
    case BuildMode.jitRelease:
169
      runner = 'flutter_jit_product_runner';
170
      break;
171 172 173
    case BuildMode.release:
      runner = 'flutter_aot_product_runner';
      break;
174 175 176 177
    default:
      throwToolExit('Fuchsia does not support build mode "$mode"');
      break;
  }
178
  cmx['runner'] = 'fuchsia-pkg://$runnerPackageSource/$runner#meta/$runner.cmx';
179 180 181
  dst.writeAsStringSync(json.encode(cmx));
}

182 183
// TODO(zra): Allow supplying a signing key.
Future<void> _buildPackage(
184 185 186
  FuchsiaProject fuchsiaProject,
  String target, // lib/main.dart
  BuildInfo buildInfo,
187
  String runnerPackageSource,
188
) async {
189
  final String outDir = getFuchsiaBuildDirectory();
190
  final String pkgDir = globals.fs.path.join(outDir, 'pkg');
191
  final String appName = fuchsiaProject.project.manifest.appName;
192 193 194
  final String pkgassets = globals.fs.path.join(outDir, '${appName}_pkgassets');
  final String packageManifest = globals.fs.path.join(pkgDir, 'package_manifest');
  final String devKeyPath = globals.fs.path.join(pkgDir, 'development.key');
195

196
  final Directory pkg = globals.fs.directory(pkgDir);
197 198 199 200
  if (!pkg.existsSync()) {
    pkg.createSync(recursive: true);
  }

201
  final File srcCmx =
202 203
      globals.fs.file(globals.fs.path.join(fuchsiaProject.meta.path, '$appName.cmx'));
  final File dstCmx = globals.fs.file(globals.fs.path.join(outDir, '$appName.cmx'));
204
  _rewriteCmx(buildInfo.mode, runnerPackageSource, srcCmx, dstCmx);
205

206
  final File manifestFile = globals.fs.file(packageManifest);
207 208

  if (buildInfo.usesAot) {
209
    final String elf = globals.fs.path.join(outDir, 'elf.aotsnapshot');
210
    manifestFile.writeAsStringSync(
211
      'data/$appName/app_aot_snapshot.so=$elf\n');
212
  } else {
213 214
    final String dilpmanifest = globals.fs.path.join(outDir, '$appName.dilpmanifest');
    manifestFile.writeAsStringSync(globals.fs.file(dilpmanifest).readAsStringSync());
215 216
  }

217
  manifestFile.writeAsStringSync(globals.fs.file(pkgassets).readAsStringSync(),
218
      mode: FileMode.append);
219
  manifestFile.writeAsStringSync('meta/$appName.cmx=${dstCmx.path}\n',
220 221 222 223
      mode: FileMode.append);
  manifestFile.writeAsStringSync('meta/package=$pkgDir/meta/package\n',
      mode: FileMode.append);

224
  final FuchsiaPM fuchsiaPM = fuchsiaSdk.fuchsiaPM;
225 226 227 228 229 230 231 232 233 234 235 236 237
  if (!await fuchsiaPM.init(pkgDir, appName)) {
    return;
  }
  if (!await fuchsiaPM.genkey(pkgDir, devKeyPath)) {
    return;
  }
  if (!await fuchsiaPM.build(pkgDir, devKeyPath, packageManifest)) {
    return;
  }
  if (!await fuchsiaPM.archive(pkgDir, devKeyPath, packageManifest)) {
    return;
  }
}