| // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file |
| // for details. 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:io'; |
| |
| import 'package:checked_yaml/checked_yaml.dart'; |
| import 'package:json_annotation/json_annotation.dart'; |
| import 'package:meta/meta.dart'; |
| import 'package:path/path.dart' as p; |
| import 'package:pubspec_parse/pubspec_parse.dart'; |
| |
| import 'build_target.dart'; |
| import 'builder_definition.dart'; |
| import 'common.dart'; |
| import 'expandos.dart'; |
| import 'input_set.dart'; |
| import 'key_normalization.dart'; |
| |
| part 'build_config.g.dart'; |
| |
| /// The parsed values from a `build.yaml` file. |
| @JsonSerializable(createToJson: false, disallowUnrecognizedKeys: true) |
| class BuildConfig { |
| /// Returns a parsed [BuildConfig] file in [path], if one exist, otherwise a |
| /// default config. |
| /// |
| /// [path] must be a directory which contains a `pubspec.yaml` file and |
| /// optionally a `build.yaml`. |
| static Future<BuildConfig> fromPackageDir(String path) async { |
| final pubspec = await _fromPackageDir(path); |
| return fromBuildConfigDir(pubspec.name, pubspec.dependencies.keys, path); |
| } |
| |
| /// Returns a parsed [BuildConfig] file in [path], if one exists, otherwise a |
| /// default config. |
| /// |
| /// [path] should the path to a directory which may contain a `build.yaml`. |
| static Future<BuildConfig> fromBuildConfigDir( |
| String packageName, Iterable<String> dependencies, String path) async { |
| final configPath = p.join(path, 'build.yaml'); |
| final file = File(configPath); |
| if (await file.exists()) { |
| return BuildConfig.parse( |
| packageName, |
| dependencies, |
| await file.readAsString(), |
| configYamlPath: file.path, |
| ); |
| } else { |
| return BuildConfig.useDefault(packageName, dependencies); |
| } |
| } |
| |
| @JsonKey(ignore: true) |
| final String packageName; |
| |
| /// All the `builders` defined in a `build.yaml` file. |
| @JsonKey(name: 'builders') |
| final Map<String, BuilderDefinition> builderDefinitions; |
| |
| /// All the `post_process_builders` defined in a `build.yaml` file. |
| @JsonKey(name: 'post_process_builders') |
| final Map<String, PostProcessBuilderDefinition> postProcessBuilderDefinitions; |
| |
| /// All the `targets` defined in a `build.yaml` file. |
| @JsonKey(name: 'targets', fromJson: _buildTargetsFromJson) |
| final Map<String, BuildTarget> buildTargets; |
| |
| @JsonKey(name: 'global_options') |
| final Map<String, GlobalBuilderConfig> globalOptions; |
| |
| /// The default config if you have no `build.yaml` file. |
| factory BuildConfig.useDefault( |
| String packageName, Iterable<String> dependencies) { |
| return runInBuildConfigZone(() { |
| final key = '$packageName:$packageName'; |
| final target = BuildTarget( |
| dependencies: dependencies |
| .map((dep) => normalizeTargetKeyUsage(dep, packageName)) |
| .toList(), |
| sources: InputSet.anything, |
| ); |
| return BuildConfig( |
| packageName: packageName, |
| buildTargets: {key: target}, |
| globalOptions: {}, |
| ); |
| }, packageName, dependencies.toList()); |
| } |
| |
| /// Create a [BuildConfig] by parsing [configYaml]. |
| /// |
| /// If [configYamlPath] is passed, it's used as the URL from which |
| /// [configYaml] for error reporting. |
| factory BuildConfig.parse( |
| String packageName, |
| Iterable<String> dependencies, |
| String configYaml, { |
| String configYamlPath, |
| }) { |
| try { |
| return checkedYamlDecode( |
| configYaml, |
| (map) => |
| BuildConfig.fromMap(packageName, dependencies, map ?? const {}), |
| allowNull: true, |
| sourceUrl: configYamlPath, |
| ); |
| } on ParsedYamlException catch (e) { |
| throw ArgumentError(e.formattedMessage); |
| } |
| } |
| |
| /// Create a [BuildConfig] read a map which was already parsed. |
| factory BuildConfig.fromMap( |
| String packageName, Iterable<String> dependencies, Map config) { |
| return runInBuildConfigZone(() => BuildConfig._fromJson(config), |
| packageName, dependencies.toList()); |
| } |
| |
| BuildConfig({ |
| String packageName, |
| @required Map<String, BuildTarget> buildTargets, |
| Map<String, GlobalBuilderConfig> globalOptions, |
| Map<String, BuilderDefinition> builderDefinitions, |
| Map<String, PostProcessBuilderDefinition> postProcessBuilderDefinitions = |
| const {}, |
| }) : buildTargets = buildTargets ?? |
| { |
| _defaultTarget(packageName ?? currentPackage): BuildTarget( |
| dependencies: currentPackageDefaultDependencies, |
| ) |
| }, |
| globalOptions = (globalOptions ?? const {}).map((key, config) => |
| MapEntry(normalizeBuilderKeyUsage(key, currentPackage), config)), |
| builderDefinitions = _normalizeBuilderDefinitions( |
| builderDefinitions ?? const {}, packageName ?? currentPackage), |
| postProcessBuilderDefinitions = _normalizeBuilderDefinitions( |
| postProcessBuilderDefinitions ?? const {}, |
| packageName ?? currentPackage), |
| packageName = packageName ?? currentPackage { |
| // Set up the expandos for all our build targets and definitions so they |
| // can know which package and builder key they refer to. |
| this.buildTargets.forEach((key, target) { |
| packageExpando[target] = this.packageName; |
| builderKeyExpando[target] = key; |
| }); |
| this.builderDefinitions.forEach((key, definition) { |
| packageExpando[definition] = this.packageName; |
| builderKeyExpando[definition] = key; |
| }); |
| this.postProcessBuilderDefinitions.forEach((key, definition) { |
| packageExpando[definition] = this.packageName; |
| builderKeyExpando[definition] = key; |
| }); |
| } |
| |
| factory BuildConfig._fromJson(Map json) => _$BuildConfigFromJson(json); |
| } |
| |
| String _defaultTarget(String package) => '$package:$package'; |
| |
| Map<String, T> _normalizeBuilderDefinitions<T>( |
| Map<String, T> builderDefinitions, String packageName) => |
| builderDefinitions.map((key, definition) => |
| MapEntry(normalizeBuilderKeyDefinition(key, packageName), definition)); |
| |
| Map<String, BuildTarget> _buildTargetsFromJson(Map json) { |
| if (json == null) { |
| return null; |
| } |
| var targets = json.map((key, target) => MapEntry( |
| normalizeTargetKeyDefinition(key as String, currentPackage), |
| BuildTarget.fromJson(target as Map))); |
| |
| if (!targets.containsKey(_defaultTarget(currentPackage))) { |
| throw ArgumentError('Must specify a target with the name ' |
| '`$currentPackage` or `\$default`.'); |
| } |
| |
| return targets; |
| } |
| |
| Future<Pubspec> _fromPackageDir(String path) async { |
| final pubspec = p.join(path, 'pubspec.yaml'); |
| final file = File(pubspec); |
| if (await file.exists()) { |
| return Pubspec.parse(await file.readAsString()); |
| } |
| throw FileSystemException('No file found', p.absolute(pubspec)); |
| } |