flx.dart 8.55 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 14 15
import 'dart:io';
import 'dart:typed_data';

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

16
import 'base/context.dart';
17 18 19 20 21 22 23 24 25 26 27
import 'base/file_system.dart';
import 'toolchain.dart';

const String defaultMainPath = 'lib/main.dart';
const String defaultAssetBase = 'packages/material_design_icons/icons';
const String defaultManifestPath = 'flutter.yaml';
const String defaultFlxOutputPath = 'build/app.flx';
const String defaultSnapshotPath = 'build/snapshot_blob.bin';
const String defaultPrivateKeyPath = 'privatekey.der';

const String _kSnapshotKey = 'snapshot_blob.bin';
28 29 30 31 32 33 34
Map<String, double> _kIconDensities = {
  'mdpi': 1.0,
  'hdpi' : 1.5,
  'xhdpi' : 2.0,
  'xxhdpi' : 3.0,
  'xxxhdpi' : 4.0
};
35 36 37 38
const List<String> _kThemes = const ['white', 'black'];
const List<int> _kSizes = const [18, 24, 36, 48];

class _Asset {
39
  final String source;
40 41 42
  final String base;
  final String key;

43
  _Asset({ this.source, this.base, this.key });
44 45
}

46 47 48 49
Map<_Asset, List<_Asset>> _parseAssets(Map manifestDescriptor, String manifestPath) {
  Map<_Asset, List<_Asset>> result = <_Asset, List<_Asset>>{};
  if (manifestDescriptor == null)
    return result;
50
  String basePath = path.dirname(path.absolute(manifestPath));
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
  if (manifestDescriptor.containsKey('assets')) {
    for (String asset in manifestDescriptor['assets']) {
      _Asset baseAsset = new _Asset(base: basePath, key: asset);
      List<_Asset> variants = <_Asset>[];
      result[baseAsset] = variants;
      // Find asset variants
      String assetPath = path.join(basePath, asset);
      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) {
          String key = path.relative(entity.path, from: basePath);
          variants.add(new _Asset(base: basePath, key: key));
        }
      }
    }
  }
  return result;
72 73
}

74
class _MaterialAsset extends _Asset {
75 76 77 78 79
  final String name;
  final String density;
  final String theme;
  final int size;

80 81 82 83 84 85 86 87 88
  _MaterialAsset(this.name, this.density, this.theme, this.size, String assetBase)
    : super(base: assetBase);

  String get source {
    List<String> parts = name.split('/');
    String category = parts[0];
    String subtype = parts[1];
    return '$category/drawable-$density/ic_${subtype}_${theme}_${size}dp.png';
  }
89 90 91 92 93

  String get key {
    List<String> parts = name.split('/');
    String category = parts[0];
    String subtype = parts[1];
94 95 96 97 98
    double devicePixelRatio = _kIconDensities[density];
    if (devicePixelRatio == 1.0)
      return '$category/ic_${subtype}_${theme}_${size}dp.png';
    else
      return '$category/${devicePixelRatio}x/ic_${subtype}_${theme}_${size}dp.png';
99 100 101 102 103 104 105 106 107
  }
}

List _generateValues(Map assetDescriptor, String key, List defaults) {
  if (assetDescriptor.containsKey(key))
    return [assetDescriptor[key]];
  return defaults;
}

108 109 110 111 112 113 114 115 116 117 118
void _accumulateMaterialAssets(Map<_Asset, List<_Asset>> result, Map assetDescriptor, String assetBase) {
  String name = assetDescriptor['name'];
  for (String theme in _generateValues(assetDescriptor, 'theme', _kThemes)) {
    for (int size in _generateValues(assetDescriptor, 'size', _kSizes)) {
      _MaterialAsset main = new _MaterialAsset(name, 'mdpi', theme, size, assetBase);
      List<_Asset> variants = <_Asset>[];
      result[main] = variants;
      for (String density in _generateValues(assetDescriptor, 'density', _kIconDensities.keys)) {
        if (density == 'mdpi')
          continue;
        variants.add(new _MaterialAsset(name, density, theme, size, assetBase));
119 120 121 122 123
      }
    }
  }
}

124 125
Map<_Asset, List<_Asset>> _parseMaterialAssets(Map manifestDescriptor, String assetBase) {
  Map<_Asset, List<_Asset>> result = <_Asset, List<_Asset>>{};
126
  if (manifestDescriptor == null || !manifestDescriptor.containsKey('material-design-icons'))
127
    return result;
128
  for (Map assetDescriptor in manifestDescriptor['material-design-icons']) {
129
    _accumulateMaterialAssets(result, assetDescriptor, assetBase);
130
  }
131
  return result;
132 133 134 135 136 137 138 139 140
}

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

141 142 143 144 145 146 147
bool _addAssetFile(Archive archive, _Asset asset) {
  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)}".');
    return false;
  }
148
  List<int> content = file.readAsBytesSync();
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
  archive.addFile(
    new ArchiveFile.noCompress(asset.key, content.length, content)
  );
  return true;
}

ArchiveFile _createAssetManifest(Map<_Asset, List<_Asset>> assets) {
  String key = 'AssetManifest.json';
  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;
  }
  List<int> content = UTF8.encode(JSON.encode(json));
165 166 167 168 169 170 171 172 173
  return new ArchiveFile.noCompress(key, content.length, content);
}

ArchiveFile _createSnapshotFile(String snapshotPath) {
  File file = new File(snapshotPath);
  List<int> content = file.readAsBytesSync();
  return new ArchiveFile(_kSnapshotKey, content.length, content);
}

174 175
/// Build the flx in a temp dir and return `localBundlePath` on success.
Future<DirectoryResult> buildInTempDir(
176
  Toolchain toolchain, {
177
  String mainPath: defaultMainPath
178 179 180
}) async {
  int result;
  Directory tempDir = await Directory.systemTemp.createTemp('flutter_tools');
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
  String localBundlePath = path.join(tempDir.path, 'app.flx');
  String localSnapshotPath = path.join(tempDir.path, 'snapshot_blob.bin');
  result = await build(
    toolchain,
    snapshotPath: localSnapshotPath,
    outputPath: localBundlePath,
    mainPath: mainPath
  );
  if (result == 0)
    return new DirectoryResult(tempDir, localBundlePath);
  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);
205 206 207 208 209 210 211 212 213 214 215 216 217
  }
}

Future<int> build(
  Toolchain toolchain, {
  String assetBase: defaultAssetBase,
  String mainPath: defaultMainPath,
  String manifestPath: defaultManifestPath,
  String outputPath: defaultFlxOutputPath,
  String snapshotPath: defaultSnapshotPath,
  String privateKeyPath: defaultPrivateKeyPath,
  bool precompiledSnapshot: false
}) async {
218
  printTrace('Building $outputPath');
219 220 221

  Map manifestDescriptor = _loadManifest(manifestPath);

222 223
  Map<_Asset, List<_Asset>> assets = _parseAssets(manifestDescriptor, manifestPath);
  assets.addAll(_parseMaterialAssets(manifestDescriptor, assetBase));
224 225 226 227 228 229 230 231 232 233

  Archive archive = new Archive();

  if (!precompiledSnapshot) {
    ensureDirectoryExists(snapshotPath);

    // In a precompiled snapshot, the instruction buffer contains script
    // content equivalents
    int result = await toolchain.compiler.compile(mainPath: mainPath, snapshotPath: snapshotPath);
    if (result != 0) {
234
      printError('Failed to run the Flutter compiler. Exit code: $result');
235 236 237 238 239 240
      return result;
    }

    archive.addFile(_createSnapshotFile(snapshotPath));
  }

241 242
  for (_Asset asset in assets.keys) {
    if (!_addAssetFile(archive, asset))
243
      return 1;
244 245 246
    for (_Asset variant in assets[asset]) {
      if (!_addAssetFile(archive, variant))
        return 1;
247 248 249
    }
  }

250
  archive.addFile(_createAssetManifest(assets));
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265

  await CipherParameters.get().seedRandom();

  AsymmetricKeyPair keyPair = keyPairFromPrivateKeyFileSync(privateKeyPath);
  Uint8List zipBytes = new Uint8List.fromList(new ZipEncoder().encode(archive));
  ensureDirectoryExists(outputPath);
  Bundle bundle = new Bundle.fromContent(
    path: outputPath,
    manifest: manifestDescriptor,
    contentBytes: zipBytes,
    keyPair: keyPair
  );
  bundle.writeSync();
  return 0;
}