blob: a2d494840f4f11d42e8502bd89490f4454e43ebb [file] [log] [blame]
// Copyright (c) 2018, 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:build/build.dart';
import 'package:logging/logging.dart';
import '../asset/file_based.dart';
import '../asset/reader.dart';
import '../asset/writer.dart';
import '../generate/build_directory.dart';
import '../generate/build_result.dart';
import '../generate/finalized_assets_view.dart';
import '../package_graph/package_graph.dart';
import 'build_environment.dart';
import 'create_merged_dir.dart';
final _logger = Logger('IOEnvironment');
/// A [BuildEnvironment] writing to disk and stdout.
class IOEnvironment implements BuildEnvironment {
@override
final RunnerAssetReader reader;
@override
final RunnerAssetWriter writer;
final bool _isInteractive;
final bool _outputSymlinksOnly;
final PackageGraph _packageGraph;
IOEnvironment(this._packageGraph, {bool assumeTty, bool outputSymlinksOnly})
: _isInteractive = assumeTty == true || _canPrompt(),
_outputSymlinksOnly = outputSymlinksOnly ?? false,
reader = FileBasedAssetReader(_packageGraph),
writer = FileBasedAssetWriter(_packageGraph) {
if (_outputSymlinksOnly && Platform.isWindows) {
_logger.warning('Symlinks to files are not yet working on Windows, you '
'may experience issues using this mode. Follow '
'https://github.com/dart-lang/sdk/issues/33966 for updates.');
}
}
@override
void onLog(LogRecord record) {
if (record.level >= Level.SEVERE) {
stderr.writeln(record);
} else {
stdout.writeln(record);
}
}
@override
Future<int> prompt(String message, List<String> choices) async {
if (!_isInteractive) throw NonInteractiveBuildException();
while (true) {
stdout.writeln('\n$message');
for (var i = 0, l = choices.length; i < l; i++) {
stdout.writeln('${i + 1} - ${choices[i]}');
}
final input = stdin.readLineSync();
final choice = int.tryParse(input) ?? -1;
if (choice > 0 && choice <= choices.length) return choice - 1;
stdout.writeln('Unrecognized option $input, '
'a number between 1 and ${choices.length} expected');
}
}
@override
Future<BuildResult> finalizeBuild(
BuildResult buildResult,
FinalizedAssetsView finalizedAssetsView,
AssetReader reader,
Set<BuildDirectory> buildDirs) async {
if (buildDirs.any(
(target) => target.outputLocation?.path?.isNotEmpty ?? false) &&
buildResult.status == BuildStatus.success) {
if (!await createMergedOutputDirectories(buildDirs, _packageGraph, this,
reader, finalizedAssetsView, _outputSymlinksOnly)) {
return _convertToFailure(buildResult,
failureType: FailureType.cantCreate);
}
}
return buildResult;
}
}
bool _canPrompt() =>
stdioType(stdin) == StdioType.terminal &&
// Assume running inside a test if the code is running as a `data:` URI
Platform.script.scheme != 'data';
BuildResult _convertToFailure(BuildResult previous,
{FailureType failureType}) =>
BuildResult(
BuildStatus.failure,
previous.outputs,
performance: previous.performance,
failureType: failureType,
);