blob: ac3f90391f797099b8f80c5700e40128bcf3146e [file] [log] [blame]
// 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));
}