289 lines
8.7 KiB
Dart
289 lines
8.7 KiB
Dart
// Copyright 2017 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' as convert;
|
|
|
|
import 'package:json_schema/json_schema.dart';
|
|
import 'package:meta/meta.dart';
|
|
import 'package:yaml/yaml.dart';
|
|
|
|
import 'base/file_system.dart';
|
|
import 'base/utils.dart';
|
|
import 'cache.dart';
|
|
import 'globals.dart';
|
|
|
|
final RegExp _versionPattern = new RegExp(r'^(\d+)(\.(\d+)(\.(\d+))?)?(\+(\d+))?$');
|
|
|
|
/// A wrapper around the `flutter` section in the `pubspec.yaml` file.
|
|
class FlutterManifest {
|
|
FlutterManifest._();
|
|
|
|
/// Returns an empty manifest.
|
|
static FlutterManifest empty() {
|
|
final FlutterManifest manifest = new FlutterManifest._();
|
|
manifest._descriptor = const <String, dynamic>{};
|
|
manifest._flutterDescriptor = const <String, dynamic>{};
|
|
return manifest;
|
|
}
|
|
|
|
/// Returns null on invalid manifest. Returns empty manifest on missing file.
|
|
static Future<FlutterManifest> createFromPath(String path) async {
|
|
if (path == null || !fs.isFileSync(path))
|
|
return _createFromYaml(null);
|
|
final String manifest = await fs.file(path).readAsString();
|
|
return createFromString(manifest);
|
|
}
|
|
|
|
/// Returns null on missing or invalid manifest
|
|
@visibleForTesting
|
|
static Future<FlutterManifest> createFromString(String manifest) async {
|
|
return _createFromYaml(loadYaml(manifest));
|
|
}
|
|
|
|
static Future<FlutterManifest> _createFromYaml(dynamic yamlDocument) async {
|
|
final FlutterManifest pubspec = new FlutterManifest._();
|
|
if (yamlDocument != null && !await _validate(yamlDocument))
|
|
return null;
|
|
|
|
final Map<dynamic, dynamic> yamlMap = yamlDocument;
|
|
if (yamlMap != null) {
|
|
pubspec._descriptor = yamlMap.cast<String, dynamic>();
|
|
} else {
|
|
pubspec._descriptor = <String, dynamic>{};
|
|
}
|
|
|
|
final Map<dynamic, dynamic> flutterMap = pubspec._descriptor['flutter'];
|
|
if (flutterMap != null) {
|
|
pubspec._flutterDescriptor = flutterMap.cast<String, dynamic>();
|
|
} else {
|
|
pubspec._flutterDescriptor = <String, dynamic>{};
|
|
}
|
|
|
|
return pubspec;
|
|
}
|
|
|
|
/// A map representation of the entire `pubspec.yaml` file.
|
|
Map<String, dynamic> _descriptor;
|
|
|
|
/// A map representation of the `flutter` section in the `pubspec.yaml` file.
|
|
Map<String, dynamic> _flutterDescriptor;
|
|
|
|
/// True if the `pubspec.yaml` file does not exist.
|
|
bool get isEmpty => _descriptor.isEmpty;
|
|
|
|
/// The string value of the top-level `name` property in the `pubspec.yaml` file.
|
|
String get appName => _descriptor['name'] ?? '';
|
|
|
|
/// The version String from the `pubspec.yaml` file.
|
|
/// Can be null if it isn't set or has a wrong format.
|
|
String get appVersion {
|
|
final String version = _descriptor['version']?.toString();
|
|
if (version != null && _versionPattern.hasMatch(version))
|
|
return version;
|
|
else
|
|
return null;
|
|
}
|
|
|
|
/// The build version name from the `pubspec.yaml` file.
|
|
/// Can be null if version isn't set or has a wrong format.
|
|
String get buildName {
|
|
if (appVersion != null && appVersion.contains('+'))
|
|
return appVersion.split('+')?.elementAt(0);
|
|
else
|
|
return appVersion;
|
|
}
|
|
|
|
/// The build version number from the `pubspec.yaml` file.
|
|
/// Can be null if version isn't set or has a wrong format.
|
|
int get buildNumber {
|
|
if (appVersion != null && appVersion.contains('+')) {
|
|
final String value = appVersion.split('+')?.elementAt(1);
|
|
return value == null ? null : int.tryParse(value);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
bool get usesMaterialDesign {
|
|
return _flutterDescriptor['uses-material-design'] ?? false;
|
|
}
|
|
|
|
/// True if this manifest declares a Flutter module project.
|
|
///
|
|
/// A Flutter project is considered a module when it has a `module:`
|
|
/// descriptor. A Flutter module project supports integration into an
|
|
/// existing host app.
|
|
///
|
|
/// Such a project can be created using `flutter create -t module`.
|
|
bool get isModule => _flutterDescriptor.containsKey('module');
|
|
|
|
/// True if this manifest declares a Flutter plugin project.
|
|
///
|
|
/// A Flutter project is considered a plugin when it has a `plugin:`
|
|
/// descriptor. A Flutter plugin project wraps custom Android and/or
|
|
/// iOS code in a Dart interface for consumption by other Flutter app
|
|
/// projects.
|
|
///
|
|
/// Such a project can be created using `flutter create -t plugin`.
|
|
bool get isPlugin => _flutterDescriptor.containsKey('plugin');
|
|
|
|
/// Returns the Android package declared by this manifest in its
|
|
/// module or plugin descriptor. Returns null, if there is no
|
|
/// such declaration.
|
|
String get androidPackage {
|
|
if (isModule)
|
|
return _flutterDescriptor['module']['androidPackage'];
|
|
if (isPlugin)
|
|
return _flutterDescriptor['plugin']['androidPackage'];
|
|
return null;
|
|
}
|
|
|
|
/// Returns the iOS bundle identifier declared by this manifest in its
|
|
/// module descriptor. Returns null, if there is no such declaration.
|
|
String get iosBundleIdentifier {
|
|
if (isModule)
|
|
return _flutterDescriptor['module']['iosBundleIdentifier'];
|
|
return null;
|
|
}
|
|
|
|
List<Map<String, dynamic>> get fontsDescriptor {
|
|
final List<dynamic> fontList = _flutterDescriptor['fonts'];
|
|
return fontList == null
|
|
? const <Map<String, dynamic>>[]
|
|
: fontList.map<Map<String, dynamic>>(castStringKeyedMap).toList();
|
|
}
|
|
|
|
List<Uri> get assets {
|
|
final List<dynamic> assets = _flutterDescriptor['assets'];
|
|
if (assets == null) {
|
|
return const <Uri>[];
|
|
}
|
|
return assets
|
|
.cast<String>()
|
|
.map<String>(Uri.encodeFull)
|
|
?.map<Uri>(Uri.parse)
|
|
?.toList();
|
|
}
|
|
|
|
List<Font> _fonts;
|
|
|
|
List<Font> get fonts {
|
|
_fonts ??= _extractFonts();
|
|
return _fonts;
|
|
}
|
|
|
|
List<Font> _extractFonts() {
|
|
if (!_flutterDescriptor.containsKey('fonts'))
|
|
return <Font>[];
|
|
|
|
final List<Font> fonts = <Font>[];
|
|
for (Map<String, dynamic> fontFamily in fontsDescriptor) {
|
|
final List<dynamic> fontFiles = fontFamily['fonts'];
|
|
final String familyName = fontFamily['family'];
|
|
if (familyName == null) {
|
|
printError('Warning: Missing family name for font.', emphasis: true);
|
|
continue;
|
|
}
|
|
if (fontFiles == null) {
|
|
printError('Warning: No fonts specified for font $familyName', emphasis: true);
|
|
continue;
|
|
}
|
|
|
|
final List<FontAsset> fontAssets = <FontAsset>[];
|
|
for (Map<dynamic, dynamic> fontFile in fontFiles) {
|
|
final String asset = fontFile['asset'];
|
|
if (asset == null) {
|
|
printError('Warning: Missing asset in fonts for $familyName', emphasis: true);
|
|
continue;
|
|
}
|
|
|
|
fontAssets.add(new FontAsset(
|
|
Uri.parse(asset),
|
|
weight: fontFile['weight'],
|
|
style: fontFile['style'],
|
|
));
|
|
}
|
|
if (fontAssets.isNotEmpty)
|
|
fonts.add(new Font(fontFamily['family'], fontAssets));
|
|
}
|
|
return fonts;
|
|
}
|
|
}
|
|
|
|
class Font {
|
|
Font(this.familyName, this.fontAssets)
|
|
: assert(familyName != null),
|
|
assert(fontAssets != null),
|
|
assert(fontAssets.isNotEmpty);
|
|
|
|
final String familyName;
|
|
final List<FontAsset> fontAssets;
|
|
|
|
Map<String, dynamic> get descriptor {
|
|
return <String, dynamic>{
|
|
'family': familyName,
|
|
'fonts': fontAssets.map((FontAsset a) => a.descriptor).toList(),
|
|
};
|
|
}
|
|
|
|
@override
|
|
String toString() => '$runtimeType(family: $familyName, assets: $fontAssets)';
|
|
}
|
|
|
|
class FontAsset {
|
|
FontAsset(this.assetUri, {this.weight, this.style})
|
|
: assert(assetUri != null);
|
|
|
|
final Uri assetUri;
|
|
final int weight;
|
|
final String style;
|
|
|
|
Map<String, dynamic> get descriptor {
|
|
final Map<String, dynamic> descriptor = <String, dynamic>{};
|
|
if (weight != null)
|
|
descriptor['weight'] = weight;
|
|
|
|
if (style != null)
|
|
descriptor['style'] = style;
|
|
|
|
descriptor['asset'] = assetUri.path;
|
|
return descriptor;
|
|
}
|
|
|
|
@override
|
|
String toString() => '$runtimeType(asset: ${assetUri.path}, weight; $weight, style: $style)';
|
|
}
|
|
|
|
@visibleForTesting
|
|
String buildSchemaDir(FileSystem fs) {
|
|
return fs.path.join(
|
|
fs.path.absolute(Cache.flutterRoot), 'packages', 'flutter_tools', 'schema',
|
|
);
|
|
}
|
|
|
|
@visibleForTesting
|
|
String buildSchemaPath(FileSystem fs) {
|
|
return fs.path.join(
|
|
buildSchemaDir(fs),
|
|
'pubspec_yaml.json',
|
|
);
|
|
}
|
|
|
|
Future<bool> _validate(dynamic manifest) async {
|
|
final String schemaPath = buildSchemaPath(fs);
|
|
|
|
final String schemaData = fs.file(schemaPath).readAsStringSync();
|
|
final Schema schema = await Schema.createSchema(
|
|
convert.json.decode(schemaData));
|
|
final Validator validator = new Validator(schema);
|
|
if (validator.validate(manifest)) {
|
|
return true;
|
|
} else {
|
|
printStatus('Error detected in pubspec.yaml:', emphasis: true);
|
|
printError(validator.errors.join('\n'));
|
|
return false;
|
|
}
|
|
}
|