fuchsia_build.dart 7.78 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
import '../artifacts.dart';
6
import '../asset.dart';
7
import '../base/common.dart';
8
import '../base/file_system.dart';
9
import '../base/logger.dart';
10
import '../base/utils.dart';
11
import '../build_info.dart';
12
import '../bundle_builder.dart';
13
import '../convert.dart';
14
import '../devfs.dart';
15
import '../globals.dart' as globals;
16 17 18 19
import '../project.dart';

import 'fuchsia_pm.dart';

20 21 22
Future<void> _timedBuildStep(String name, Future<void> Function() action) async {
  final Stopwatch sw = Stopwatch()..start();
  await action();
23
  globals.printTrace('$name: ${sw.elapsedMilliseconds} ms.');
24
  globals.flutterUsage.sendTiming('build', name, Duration(milliseconds: sw.elapsedMilliseconds));
25 26
}

27 28
Future<void> _validateCmxFile(FuchsiaProject fuchsiaProject) async {
  final String appName = fuchsiaProject.project.manifest.appName;
29 30
  final String cmxPath = globals.fs.path.join(fuchsiaProject.meta.path, '$appName.cmx');
  final File cmxFile = globals.fs.file(cmxPath);
31 32 33 34 35
  if (!await cmxFile.exists()) {
    throwToolExit('The Fuchsia build requires a .cmx file at $cmxPath for the app: $appName.');
  }
}

36 37 38 39 40 41
// 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.
42
Future<void> buildFuchsia({
43 44 45
  required FuchsiaProject fuchsiaProject,
  required TargetPlatform targetPlatform,
  String? target, // E.g., lib/main.dart
46
  BuildInfo buildInfo = BuildInfo.debug,
47
  String runnerPackageSource = FuchsiaPackageServer.toolHost,
48
}) async {
49
  final String targetPath = target ??= 'lib/main.dart';
50
  await _validateCmxFile(fuchsiaProject);
51
  final Directory outDir = globals.fs.directory(getFuchsiaBuildDirectory());
52 53 54 55
  if (!outDir.existsSync()) {
    outDir.createSync(recursive: true);
  }

56
  await _timedBuildStep('fuchsia-kernel-compile',
57
    () => globals.fuchsiaSdk!.fuchsiaKernelCompiler.build(
58
      fuchsiaProject: fuchsiaProject, target: targetPath, buildInfo: buildInfo));
59 60 61

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

65
  await _timedBuildStep('fuchsia-build-assets',
66
    () => _buildAssets(fuchsiaProject, targetPath, buildInfo));
67
  await _timedBuildStep('fuchsia-build-package',
68
    () => _buildPackage(fuchsiaProject, targetPath, buildInfo, runnerPackageSource));
69 70
}

71 72 73 74 75 76 77 78
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;
79
  final String dilPath = globals.fs.path.join(outDir, '$appName.dil');
80

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

83
  final String genSnapshot = globals.artifacts!.getArtifactPath(
84 85 86 87 88 89 90 91
    Artifact.genSnapshot,
    platform: targetPlatform,
    mode: buildInfo.mode,
  );

  final List<String> command = <String>[
    genSnapshot,
    '--deterministic',
92 93
    '--snapshot_kind=app-aot-elf',
    '--elf=$elf',
94 95 96 97
    if (buildInfo.isDebug) '--enable-asserts',
    dilPath,
  ];
  int result;
98
  final Status status = globals.logger.startProgress(
99 100 101
    'Compiling Fuchsia application to native code...',
  );
  try {
102
    result = await globals.processUtils.stream(command, trace: true);
103 104 105 106 107 108 109 110
  } finally {
    status.cancel();
  }
  if (result != 0) {
    throwToolExit('Build process failed');
  }
}

111
Future<void> _buildAssets(
112 113 114 115
  FuchsiaProject fuchsiaProject,
  String target, // lib/main.dart
  BuildInfo buildInfo,
) async {
116
  final String assetDir = getAssetBuildDirectory();
117
  final AssetBundle? assets = await buildAssets(
118 119 120 121 122
    manifestPath: fuchsiaProject.project.pubspecFile.path,
    packagesPath: fuchsiaProject.project.packagesFile.path,
    assetDirPath: assetDir,
  );

123 124 125 126
  if (assets == null) {
    throwToolExit('Unable to find assets.', exitCode: 1);
  }

127
  final Map<String, DevFSContent> assetEntries =
128
      Map<String, DevFSContent>.of(assets.entries);
129
  await writeBundle(globals.fs.directory(assetDir), assetEntries);
130 131 132

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

135
  final File destFile = globals.fs.file(assetManifest);
136 137 138
  await destFile.create(recursive: true);
  final IOSink outFile = destFile.openWrite();

139
  for (final String path in assets.entries.keys) {
140 141 142 143 144 145
    outFile.write('data/$appName/$path=$assetDir/$path\n');
  }
  await outFile.flush();
  await outFile.close();
}

146
void _rewriteCmx(BuildMode mode, String runnerPackageSource, File src, File dst) {
147
  final Map<String, Object?> cmx = castStringKeyedMap(json.decode(src.readAsStringSync())) ?? <String, Object?>{};
148 149 150 151 152 153 154 155 156
  // 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:
157
      runner = 'flutter_jit_runner';
158
      break;
159 160 161 162
    case BuildMode.profile:
      runner = 'flutter_aot_runner';
      break;
    case BuildMode.jitRelease:
163
      runner = 'flutter_jit_product_runner';
164
      break;
165 166 167
    case BuildMode.release:
      runner = 'flutter_aot_product_runner';
      break;
168 169 170
    default:
      throwToolExit('Fuchsia does not support build mode "$mode"');
  }
171
  cmx['runner'] = 'fuchsia-pkg://$runnerPackageSource/$runner#meta/$runner.cmx';
172 173 174
  dst.writeAsStringSync(json.encode(cmx));
}

175
// TODO(zanderso): Allow supplying a signing key.
176
Future<void> _buildPackage(
177 178 179
  FuchsiaProject fuchsiaProject,
  String target, // lib/main.dart
  BuildInfo buildInfo,
180
  String runnerPackageSource,
181
) async {
182
  final String outDir = getFuchsiaBuildDirectory();
183
  final String pkgDir = globals.fs.path.join(outDir, 'pkg');
184
  final String appName = fuchsiaProject.project.manifest.appName;
185 186
  final String pkgassets = globals.fs.path.join(outDir, '${appName}_pkgassets');
  final String packageManifest = globals.fs.path.join(pkgDir, 'package_manifest');
187

188
  final Directory pkg = globals.fs.directory(pkgDir);
189 190 191 192
  if (!pkg.existsSync()) {
    pkg.createSync(recursive: true);
  }

193
  final File srcCmx =
194 195
      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'));
196
  _rewriteCmx(buildInfo.mode, runnerPackageSource, srcCmx, dstCmx);
197

198
  final File manifestFile = globals.fs.file(packageManifest);
199 200

  if (buildInfo.usesAot) {
201
    final String elf = globals.fs.path.join(outDir, 'elf.aotsnapshot');
202
    manifestFile.writeAsStringSync(
203
      'data/$appName/app_aot_snapshot.so=$elf\n');
204
  } else {
205 206
    final String dilpmanifest = globals.fs.path.join(outDir, '$appName.dilpmanifest');
    manifestFile.writeAsStringSync(globals.fs.file(dilpmanifest).readAsStringSync());
207 208
  }

209
  manifestFile.writeAsStringSync(globals.fs.file(pkgassets).readAsStringSync(),
210
      mode: FileMode.append);
211
  manifestFile.writeAsStringSync('meta/$appName.cmx=${dstCmx.path}\n',
212 213 214 215
      mode: FileMode.append);
  manifestFile.writeAsStringSync('meta/package=$pkgDir/meta/package\n',
      mode: FileMode.append);

216
  final FuchsiaPM? fuchsiaPM = globals.fuchsiaSdk?.fuchsiaPM;
217 218 219
  if (fuchsiaPM == null) {
    return;
  }
220 221 222
  if (!await fuchsiaPM.init(pkgDir, appName)) {
    return;
  }
223
  if (!await fuchsiaPM.build(pkgDir, packageManifest)) {
224 225
    return;
  }
226
  if (!await fuchsiaPM.archive(pkgDir, packageManifest)) {
227 228 229
    return;
  }
}