flx.dart 8.56 KB
Newer Older
1 2 3 4 5
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';
6
import 'dart:convert';
7 8 9 10 11 12 13
import 'dart:io';

import 'package:flx/bundle.dart';
import 'package:flx/signing.dart';
import 'package:path/path.dart' as path;
import 'package:yaml/yaml.dart';

14
import 'artifacts.dart';
yjbanov's avatar
yjbanov committed
15
import 'base/file_system.dart' show ensureDirectoryExists;
16
import 'globals.dart';
17
import 'toolchain.dart';
Devon Carew's avatar
Devon Carew committed
18
import 'zip.dart';
19 20

const String defaultMainPath = 'lib/main.dart';
21
const String defaultAssetBasePath = '.';
22 23 24
const String defaultManifestPath = 'flutter.yaml';
const String defaultFlxOutputPath = 'build/app.flx';
const String defaultSnapshotPath = 'build/snapshot_blob.bin';
25
const String defaultDepfilePath = 'build/snapshot_blob.bin.d';
26
const String defaultPrivateKeyPath = 'privatekey.der';
27
const String defaultWorkingDirPath = 'build/flx';
28 29 30 31

const String _kSnapshotKey = 'snapshot_blob.bin';

class _Asset {
32
  final String source;
33 34 35
  final String base;
  final String key;

36
  _Asset({ this.source, this.base, this.key });
37 38
}

39 40
const String _kMaterialIconsKey = 'fonts/MaterialIcons-Regular.ttf';

Ian Hickson's avatar
Ian Hickson committed
41
List<Map<String, dynamic>> _getMaterialFonts() {
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
  return [{
    'family': 'MaterialIcons',
    'fonts': [{
      'asset': _kMaterialIconsKey
    }]
  }];
}

List<_Asset> _getMaterialAssets() {
  return <_Asset>[
    new _Asset(
      base: '${ArtifactStore.flutterRoot}/bin/cache/artifacts/material_fonts',
      source: 'MaterialIcons-Regular.ttf',
      key: _kMaterialIconsKey
    )
  ];
}

Ian Hickson's avatar
Ian Hickson committed
60
Map<_Asset, List<_Asset>> _parseAssets(Map<String, dynamic> manifestDescriptor, String assetBase) {
61 62 63 64 65
  Map<_Asset, List<_Asset>> result = <_Asset, List<_Asset>>{};
  if (manifestDescriptor == null)
    return result;
  if (manifestDescriptor.containsKey('assets')) {
    for (String asset in manifestDescriptor['assets']) {
66
      _Asset baseAsset = new _Asset(base: assetBase, key: asset);
67 68 69
      List<_Asset> variants = <_Asset>[];
      result[baseAsset] = variants;
      // Find asset variants
70
      String assetPath = path.join(assetBase, asset);
71 72 73 74 75 76 77
      String assetFilename = path.basename(assetPath);
      Directory assetDir = new Directory(path.dirname(assetPath));
      List<FileSystemEntity> files = assetDir.listSync(recursive: true);
      for (FileSystemEntity entity in files) {
        if (path.basename(entity.path) == assetFilename &&
            FileSystemEntity.isFileSync(entity.path) &&
            entity.path != assetPath) {
78 79
          String key = path.relative(entity.path, from: assetBase);
          variants.add(new _Asset(base: assetBase, key: key));
80 81 82 83 84
        }
      }
    }
  }
  return result;
85 86 87 88 89 90 91 92 93
}

dynamic _loadManifest(String manifestPath) {
  if (manifestPath == null || !FileSystemEntity.isFileSync(manifestPath))
    return null;
  String manifestDescriptor = new File(manifestPath).readAsStringSync();
  return loadYaml(manifestDescriptor);
}

Devon Carew's avatar
Devon Carew committed
94
ZipEntry _createAssetEntry(_Asset asset) {
95 96 97 98
  String source = asset.source ?? asset.key;
  File file = new File('${asset.base}/$source');
  if (!file.existsSync()) {
    printError('Cannot find asset "$source" in directory "${path.absolute(asset.base)}".');
Devon Carew's avatar
Devon Carew committed
99
    return null;
100
  }
Devon Carew's avatar
Devon Carew committed
101
  return new ZipEntry.fromFile(asset.key, file);
102 103
}

Devon Carew's avatar
Devon Carew committed
104
ZipEntry _createAssetManifest(Map<_Asset, List<_Asset>> assets) {
105 106 107 108 109 110 111
  Map<String, List<String>> json = <String, List<String>>{};
  for (_Asset main in assets.keys) {
    List<String> variants = <String>[];
    for (_Asset variant in assets[main])
      variants.add(variant.key);
    json[main.key] = variants;
  }
Devon Carew's avatar
Devon Carew committed
112
  return new ZipEntry.fromString('AssetManifest.json', JSON.encode(json));
113 114
}

Ian Hickson's avatar
Ian Hickson committed
115 116
ZipEntry _createFontManifest(Map<String, dynamic> manifestDescriptor, List<Map<String, dynamic>> additionalFonts) {
  List<Map<String, dynamic>> fonts = <Map<String, dynamic>>[];
117 118 119 120 121
  if (additionalFonts != null)
    fonts.addAll(additionalFonts);
  if (manifestDescriptor != null && manifestDescriptor.containsKey('fonts'))
    fonts.addAll(manifestDescriptor['fonts']);
  if (fonts.isEmpty)
122
    return null;
123
  return new ZipEntry.fromString('FontManifest.json', JSON.encode(fonts));
124 125
}

Devon Carew's avatar
Devon Carew committed
126 127
/// Build the flx in the build/ directory and return `localBundlePath` on success.
Future<String> buildFlx(
128
  Toolchain toolchain, {
129
  String mainPath: defaultMainPath
130 131
}) async {
  int result;
Devon Carew's avatar
Devon Carew committed
132 133
  String localBundlePath = path.join('build', 'app.flx');
  String localSnapshotPath = path.join('build', 'snapshot_blob.bin');
134 135 136 137 138 139 140
  result = await build(
    toolchain,
    snapshotPath: localSnapshotPath,
    outputPath: localBundlePath,
    mainPath: mainPath
  );
  if (result == 0)
Devon Carew's avatar
Devon Carew committed
141
    return localBundlePath;
142 143 144 145 146 147 148 149 150 151 152 153 154 155
  else
    throw result;
}

/// The result from [buildInTempDir]. Note that this object should be disposed after use.
class DirectoryResult {
  final Directory directory;
  final String localBundlePath;

  DirectoryResult(this.directory, this.localBundlePath);

  /// Call this to delete the temporary directory.
  void dispose() {
    directory.deleteSync(recursive: true);
156 157 158 159 160 161 162 163 164
  }
}

Future<int> build(
  Toolchain toolchain, {
  String mainPath: defaultMainPath,
  String manifestPath: defaultManifestPath,
  String outputPath: defaultFlxOutputPath,
  String snapshotPath: defaultSnapshotPath,
165
  String depfilePath: defaultDepfilePath,
166
  String privateKeyPath: defaultPrivateKeyPath,
167
  String workingDirPath: defaultWorkingDirPath,
168 169
  bool precompiledSnapshot: false
}) async {
Ian Hickson's avatar
Ian Hickson committed
170
  Map<String, dynamic> manifestDescriptor = _loadManifest(manifestPath);
171
  String assetBasePath = path.dirname(path.absolute(manifestPath));
172

Devon Carew's avatar
Devon Carew committed
173 174
  File snapshotFile;

175 176 177 178 179
  if (!precompiledSnapshot) {
    ensureDirectoryExists(snapshotPath);

    // In a precompiled snapshot, the instruction buffer contains script
    // content equivalents
180
    int result = await toolchain.compiler.compile(mainPath: mainPath, snapshotPath: snapshotPath, depfilePath: depfilePath, buildOutputPath: outputPath);
181
    if (result != 0) {
182
      printError('Failed to run the Flutter compiler. Exit code: $result');
183 184 185
      return result;
    }

Devon Carew's avatar
Devon Carew committed
186
    snapshotFile = new File(snapshotPath);
187 188
  }

189 190 191 192 193
  return assemble(
      manifestDescriptor: manifestDescriptor,
      snapshotFile: snapshotFile,
      assetBasePath: assetBasePath,
      outputPath: outputPath,
194 195
      privateKeyPath: privateKeyPath,
      workingDirPath: workingDirPath
196 197 198 199
  );
}

Future<int> assemble({
Ian Hickson's avatar
Ian Hickson committed
200
  Map<String, dynamic> manifestDescriptor: const <String, dynamic>{},
Devon Carew's avatar
Devon Carew committed
201
  File snapshotFile,
202 203
  String assetBasePath: defaultAssetBasePath,
  String outputPath: defaultFlxOutputPath,
204 205
  String privateKeyPath: defaultPrivateKeyPath,
  String workingDirPath: defaultWorkingDirPath
206 207 208 209
}) async {
  printTrace('Building $outputPath');

  Map<_Asset, List<_Asset>> assets = _parseAssets(manifestDescriptor, assetBasePath);
210 211

  final bool usesMaterialDesign = manifestDescriptor != null && manifestDescriptor['uses-material-design'] == true;
212

Devon Carew's avatar
Devon Carew committed
213
  ZipBuilder zipBuilder = new ZipBuilder();
214 215

  if (snapshotFile != null)
Devon Carew's avatar
Devon Carew committed
216
    zipBuilder.addEntry(new ZipEntry.fromFile(_kSnapshotKey, snapshotFile));
217

218
  for (_Asset asset in assets.keys) {
Devon Carew's avatar
Devon Carew committed
219 220
    ZipEntry assetEntry = _createAssetEntry(asset);
    if (assetEntry == null)
221
      return 1;
222
    zipBuilder.addEntry(assetEntry);
Devon Carew's avatar
Devon Carew committed
223

224
    for (_Asset variant in assets[asset]) {
Devon Carew's avatar
Devon Carew committed
225 226
      ZipEntry variantEntry = _createAssetEntry(variant);
      if (variantEntry == null)
227
        return 1;
228 229 230 231 232 233 234 235 236 237
      zipBuilder.addEntry(variantEntry);
    }
  }

  if (usesMaterialDesign) {
    for (_Asset asset in _getMaterialAssets()) {
      ZipEntry assetEntry = _createAssetEntry(asset);
      if (assetEntry == null)
        return 1;
      zipBuilder.addEntry(assetEntry);
238 239 240
    }
  }

Devon Carew's avatar
Devon Carew committed
241
  zipBuilder.addEntry(_createAssetManifest(assets));
242

243
  ZipEntry fontManifest = _createFontManifest(manifestDescriptor, usesMaterialDesign ? _getMaterialFonts() : null);
244
  if (fontManifest != null)
Devon Carew's avatar
Devon Carew committed
245
    zipBuilder.addEntry(fontManifest);
246

Ian Hickson's avatar
Ian Hickson committed
247
  AsymmetricKeyPair<PublicKey, PrivateKey> keyPair = keyPairFromPrivateKeyFileSync(privateKeyPath);
248
  printTrace('KeyPair from $privateKeyPath: $keyPair.');
249 250 251 252 253 254

  if (keyPair != null) {
    printTrace('Calling CipherParameters.seedRandom().');
    CipherParameters.get().seedRandom();
  }

Devon Carew's avatar
Devon Carew committed
255 256
  File zipFile = new File(outputPath.substring(0, outputPath.length - 4) + '.zip');
  printTrace('Encoding zip file to ${zipFile.path}');
257
  zipBuilder.createZip(zipFile, new Directory(workingDirPath));
Devon Carew's avatar
Devon Carew committed
258
  List<int> zipBytes = zipFile.readAsBytesSync();
259

260
  ensureDirectoryExists(outputPath);
261

Devon Carew's avatar
Devon Carew committed
262
  printTrace('Creating flx at $outputPath.');
263 264 265 266 267 268 269
  Bundle bundle = new Bundle.fromContent(
    path: outputPath,
    manifest: manifestDescriptor,
    contentBytes: zipBytes,
    keyPair: keyPair
  );
  bundle.writeSync();
270

Devon Carew's avatar
Devon Carew committed
271
  printTrace('Built $outputPath.');
272

273 274
  return 0;
}