mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
297 lines
9.6 KiB
Dart
297 lines
9.6 KiB
Dart
// 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';
|
|
import 'dart:convert';
|
|
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';
|
|
|
|
import 'base/file_system.dart';
|
|
import 'globals.dart';
|
|
import 'toolchain.dart';
|
|
|
|
const String defaultMainPath = 'lib/main.dart';
|
|
const String defaultAssetBasePath = '.';
|
|
const String defaultMaterialAssetBasePath = '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';
|
|
Map<String, double> _kIconDensities = {
|
|
'mdpi': 1.0,
|
|
'hdpi' : 1.5,
|
|
'xhdpi' : 2.0,
|
|
'xxhdpi' : 3.0,
|
|
'xxxhdpi' : 4.0
|
|
};
|
|
const List<String> _kThemes = const <String>['white', 'black'];
|
|
const List<int> _kSizes = const <int>[18, 24, 36, 48];
|
|
|
|
class _Asset {
|
|
final String source;
|
|
final String base;
|
|
final String key;
|
|
|
|
_Asset({ this.source, this.base, this.key });
|
|
}
|
|
|
|
Map<_Asset, List<_Asset>> _parseAssets(Map manifestDescriptor, String assetBase) {
|
|
Map<_Asset, List<_Asset>> result = <_Asset, List<_Asset>>{};
|
|
if (manifestDescriptor == null)
|
|
return result;
|
|
if (manifestDescriptor.containsKey('assets')) {
|
|
for (String asset in manifestDescriptor['assets']) {
|
|
_Asset baseAsset = new _Asset(base: assetBase, key: asset);
|
|
List<_Asset> variants = <_Asset>[];
|
|
result[baseAsset] = variants;
|
|
// Find asset variants
|
|
String assetPath = path.join(assetBase, 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: assetBase);
|
|
variants.add(new _Asset(base: assetBase, key: key));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
class _MaterialAsset extends _Asset {
|
|
final String name;
|
|
final String density;
|
|
final String theme;
|
|
final int size;
|
|
|
|
_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';
|
|
}
|
|
|
|
String get key {
|
|
List<String> parts = name.split('/');
|
|
String category = parts[0];
|
|
String subtype = parts[1];
|
|
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';
|
|
}
|
|
}
|
|
|
|
Iterable/*<T>*/ _generateValues/*<T>*/(
|
|
Map/*<String, T>*/ assetDescriptor,
|
|
String key,
|
|
Iterable/*<T>*/ defaults
|
|
) {
|
|
return assetDescriptor.containsKey(key) ? /*<T>*/[assetDescriptor[key]] : defaults;
|
|
}
|
|
|
|
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));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Map<_Asset, List<_Asset>> _parseMaterialAssets(Map manifestDescriptor, String assetBase) {
|
|
Map<_Asset, List<_Asset>> result = <_Asset, List<_Asset>>{};
|
|
if (manifestDescriptor == null || !manifestDescriptor.containsKey('material-design-icons'))
|
|
return result;
|
|
for (Map assetDescriptor in manifestDescriptor['material-design-icons']) {
|
|
_accumulateMaterialAssets(result, assetDescriptor, assetBase);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
dynamic _loadManifest(String manifestPath) {
|
|
if (manifestPath == null || !FileSystemEntity.isFileSync(manifestPath))
|
|
return null;
|
|
String manifestDescriptor = new File(manifestPath).readAsStringSync();
|
|
return loadYaml(manifestDescriptor);
|
|
}
|
|
|
|
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;
|
|
}
|
|
List<int> content = file.readAsBytesSync();
|
|
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));
|
|
return new ArchiveFile.noCompress(key, content.length, content);
|
|
}
|
|
|
|
ArchiveFile _createFontManifest(Map manifestDescriptor) {
|
|
List<int> content = UTF8.encode(JSON.encode(manifestDescriptor['fonts']));
|
|
return new ArchiveFile.noCompress('FontManifest.json', content.length, content);
|
|
}
|
|
|
|
ArchiveFile _createSnapshotFile(String snapshotPath) {
|
|
File file = new File(snapshotPath);
|
|
List<int> content = file.readAsBytesSync();
|
|
return new ArchiveFile(_kSnapshotKey, content.length, content);
|
|
}
|
|
|
|
/// Build the flx in a temp dir and return `localBundlePath` on success.
|
|
Future<DirectoryResult> buildInTempDir(
|
|
Toolchain toolchain, {
|
|
String mainPath: defaultMainPath
|
|
}) async {
|
|
int result;
|
|
Directory tempDir = await Directory.systemTemp.createTemp('flutter_tools');
|
|
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);
|
|
}
|
|
}
|
|
|
|
Future<int> build(
|
|
Toolchain toolchain, {
|
|
String materialAssetBasePath: defaultMaterialAssetBasePath,
|
|
String mainPath: defaultMainPath,
|
|
String manifestPath: defaultManifestPath,
|
|
String outputPath: defaultFlxOutputPath,
|
|
String snapshotPath: defaultSnapshotPath,
|
|
String privateKeyPath: defaultPrivateKeyPath,
|
|
bool precompiledSnapshot: false
|
|
}) async {
|
|
Map manifestDescriptor = _loadManifest(manifestPath);
|
|
String assetBasePath = path.dirname(path.absolute(manifestPath));
|
|
|
|
ArchiveFile snapshotFile = null;
|
|
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) {
|
|
printError('Failed to run the Flutter compiler. Exit code: $result');
|
|
return result;
|
|
}
|
|
|
|
snapshotFile = _createSnapshotFile(snapshotPath);
|
|
}
|
|
|
|
return assemble(
|
|
manifestDescriptor: manifestDescriptor,
|
|
snapshotFile: snapshotFile,
|
|
assetBasePath: assetBasePath,
|
|
materialAssetBasePath: materialAssetBasePath,
|
|
outputPath: outputPath,
|
|
privateKeyPath: privateKeyPath
|
|
);
|
|
}
|
|
|
|
Future<int> assemble({
|
|
Map manifestDescriptor: const {},
|
|
ArchiveFile snapshotFile: null,
|
|
String assetBasePath: defaultAssetBasePath,
|
|
String materialAssetBasePath: defaultMaterialAssetBasePath,
|
|
String outputPath: defaultFlxOutputPath,
|
|
String privateKeyPath: defaultPrivateKeyPath
|
|
}) async {
|
|
printTrace('Building $outputPath');
|
|
|
|
Map<_Asset, List<_Asset>> assets = _parseAssets(manifestDescriptor, assetBasePath);
|
|
assets.addAll(_parseMaterialAssets(manifestDescriptor, materialAssetBasePath));
|
|
|
|
Archive archive = new Archive();
|
|
|
|
if (snapshotFile != null)
|
|
archive.addFile(snapshotFile);
|
|
|
|
for (_Asset asset in assets.keys) {
|
|
if (!_addAssetFile(archive, asset))
|
|
return 1;
|
|
for (_Asset variant in assets[asset]) {
|
|
if (!_addAssetFile(archive, variant))
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
archive.addFile(_createAssetManifest(assets));
|
|
archive.addFile(_createFontManifest(manifestDescriptor));
|
|
|
|
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;
|
|
}
|