blob: eb2681eb6cdf43e6fca5420c4cd88be231454825 [file] [log] [blame]
// Copyright (c) 2016, 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 'package:build/build.dart';
import 'package:build_config/build_config.dart';
import 'package:build_resolvers/build_resolvers.dart';
import 'package:glob/glob.dart';
import 'package:logging/logging.dart';
import 'package:meta/meta.dart';
import 'package:path/path.dart' as p;
import '../environment/build_environment.dart';
import '../package_graph/package_graph.dart';
import '../package_graph/target_graph.dart';
import '../util/hash.dart';
import 'exceptions.dart';
/// The default list of files to include when an explicit include is not
/// provided.
const List<String> defaultRootPackageWhitelist = [
'assets/**',
'benchmark/**',
'bin/**',
'example/**',
'lib/**',
'test/**',
'tool/**',
'web/**',
'node/**',
'pubspec.yaml',
'pubspec.lock',
r'$package$',
];
final _logger = Logger('BuildOptions');
class LogSubscription {
factory LogSubscription(BuildEnvironment environment,
{bool verbose, Level logLevel}) {
// Set up logging
verbose ??= false;
logLevel ??= verbose ? Level.ALL : Level.INFO;
// Severe logs can fail the build and should always be shown.
if (logLevel == Level.OFF) logLevel = Level.SEVERE;
Logger.root.level = logLevel;
var logListener = Logger.root.onRecord.listen(environment.onLog);
return LogSubscription._(logListener);
}
LogSubscription._(this.logListener);
final StreamSubscription<LogRecord> logListener;
}
/// Describes a set of files that should be built.
class BuildFilter {
/// The package name glob that files must live under in order to match.
final Glob _package;
/// A glob for files under [_package] that must match.
final Glob _path;
BuildFilter(this._package, this._path);
/// Builds a [BuildFilter] from a command line argument.
///
/// Both relative paths and package: uris are supported. Relative
/// paths are treated as relative to the [rootPackage].
///
/// Globs are supported in package names and paths.
factory BuildFilter.fromArg(String arg, String rootPackage) {
var uri = Uri.parse(arg);
if (uri.scheme == 'package') {
var package = uri.pathSegments.first;
var glob = Glob(p.url.joinAll([
'lib',
...uri.pathSegments.skip(1),
]));
return BuildFilter(Glob(package), glob);
} else if (uri.scheme.isEmpty) {
return BuildFilter(Glob(rootPackage), Glob(uri.path));
} else {
throw FormatException('Unsupported scheme ${uri.scheme}', uri);
}
}
/// Returns whether or not [id] mathes this filter.
bool matches(AssetId id) =>
_package.matches(id.package) && _path.matches(id.path);
@override
int get hashCode {
var hash = 0;
hash = hashCombine(hash, _package.context.hashCode);
hash = hashCombine(hash, _package.pattern.hashCode);
hash = hashCombine(hash, _package.recursive.hashCode);
hash = hashCombine(hash, _path.context.hashCode);
hash = hashCombine(hash, _path.pattern.hashCode);
hash = hashCombine(hash, _path.recursive.hashCode);
return hashComplete(hash);
}
@override
bool operator ==(Object other) =>
other is BuildFilter &&
other._path.context == _path.context &&
other._path.pattern == _path.pattern &&
other._path.recursive == _path.recursive &&
other._package.context == _package.context &&
other._package.pattern == _package.pattern &&
other._package.recursive == _package.recursive;
}
/// Manages setting up consistent defaults for all options and build modes.
class BuildOptions {
final bool deleteFilesByDefault;
final bool enableLowResourcesMode;
final StreamSubscription logListener;
/// If present, the path to a directory to write performance logs to.
final String logPerformanceDir;
final PackageGraph packageGraph;
final Resolvers resolvers;
final TargetGraph targetGraph;
final bool trackPerformance;
// Watch mode options.
Duration debounceDelay;
// For testing only, skips the build script updates check.
bool skipBuildScriptCheck;
BuildOptions._({
@required this.debounceDelay,
@required this.deleteFilesByDefault,
@required this.enableLowResourcesMode,
@required this.logListener,
@required this.packageGraph,
@required this.skipBuildScriptCheck,
@required this.trackPerformance,
@required this.targetGraph,
@required this.logPerformanceDir,
@required this.resolvers,
});
static Future<BuildOptions> create(
LogSubscription logSubscription, {
Duration debounceDelay,
bool deleteFilesByDefault,
bool enableLowResourcesMode,
@required PackageGraph packageGraph,
Map<String, BuildConfig> overrideBuildConfig,
bool skipBuildScriptCheck,
bool trackPerformance,
String logPerformanceDir,
Resolvers resolvers,
}) async {
TargetGraph targetGraph;
try {
targetGraph = await TargetGraph.forPackageGraph(packageGraph,
overrideBuildConfig: overrideBuildConfig,
defaultRootPackageWhitelist: defaultRootPackageWhitelist);
} on BuildConfigParseException catch (e, s) {
_logger.severe(
'Failed to parse `build.yaml` for ${e.packageName}.', e.exception, s);
throw CannotBuildException();
}
/// Set up other defaults.
debounceDelay ??= const Duration(milliseconds: 250);
deleteFilesByDefault ??= false;
skipBuildScriptCheck ??= false;
enableLowResourcesMode ??= false;
trackPerformance ??= false;
if (logPerformanceDir != null) {
// Requiring this to be under the root package allows us to use an
// `AssetWriter` to write logs.
if (!p.isWithin(p.current, logPerformanceDir)) {
_logger.severe('Performance logs may only be output under the root '
'package, but got `$logPerformanceDir` which is not.');
throw CannotBuildException();
}
trackPerformance = true;
}
resolvers ??= AnalyzerResolvers();
return BuildOptions._(
debounceDelay: debounceDelay,
deleteFilesByDefault: deleteFilesByDefault,
enableLowResourcesMode: enableLowResourcesMode,
logListener: logSubscription.logListener,
packageGraph: packageGraph,
skipBuildScriptCheck: skipBuildScriptCheck,
trackPerformance: trackPerformance,
targetGraph: targetGraph,
logPerformanceDir: logPerformanceDir,
resolvers: resolvers,
);
}
}