| // 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 'package:build/build.dart'; |
| import 'package:build_runner_core/build_runner_core.dart'; |
| |
| import '../generate/phase.dart'; |
| import '../util/build_dirs.dart'; |
| import 'graph.dart'; |
| import 'node.dart'; |
| |
| /// A cache of the results of checking whether outputs from optional build steps |
| /// were required by in the current build. |
| /// |
| /// An optional output becomes required if: |
| /// - Any of it's transitive outputs is required (based on the criteria below). |
| /// - It was output by the same build step as any required output. |
| /// |
| /// Any outputs from non-optional phases are considered required, unless the |
| /// following are all true. |
| /// - [_buildDirs] is non-empty. |
| /// - The output lives in a non-lib directory. |
| /// - The outputs path is not prefixed by one of [_buildDirs]. |
| /// - If [_buildFilters] is non-empty and the output doesn't match one of the |
| /// filters. |
| /// |
| /// Non-required optional output might still exist in the generated directory |
| /// and the asset graph but we should avoid serving them, outputting them in |
| /// the merged directories, or considering a failed output as an overall. |
| class OptionalOutputTracker { |
| final _checkedOutputs = <AssetId, bool>{}; |
| final AssetGraph _assetGraph; |
| final Set<String> _buildDirs; |
| final Set<BuildFilter> _buildFilters; |
| final List<BuildPhase> _buildPhases; |
| |
| OptionalOutputTracker( |
| this._assetGraph, this._buildDirs, this._buildFilters, this._buildPhases); |
| |
| /// Returns whether [output] is required. |
| /// |
| /// If necessary crawls transitive outputs that read [output] or any other |
| /// assets generated by the same phase until it finds one which is required. |
| /// |
| /// [currentlyChecking] is used to aovid repeatedly checking the same outputs. |
| bool isRequired(AssetId output, [Set<AssetId> currentlyChecking]) { |
| currentlyChecking ??= <AssetId>{}; |
| if (currentlyChecking.contains(output)) return false; |
| currentlyChecking.add(output); |
| |
| final node = _assetGraph.get(output); |
| if (node is! GeneratedAssetNode) return true; |
| final generatedNode = node as GeneratedAssetNode; |
| final phase = _buildPhases[generatedNode.phaseNumber]; |
| if (!phase.isOptional && |
| shouldBuildForDirs(output, _buildDirs, _buildFilters, phase)) { |
| return true; |
| } |
| return _checkedOutputs.putIfAbsent( |
| output, |
| () => |
| generatedNode.outputs |
| .any((o) => isRequired(o, currentlyChecking)) || |
| _assetGraph |
| .outputsForPhase(output.package, generatedNode.phaseNumber) |
| .where((n) => n.primaryInput == generatedNode.primaryInput) |
| .map((n) => n.id) |
| .any((o) => isRequired(o, currentlyChecking))); |
| } |
| |
| /// Clears the cache of which assets were required. |
| /// |
| /// If the tracker is used across multiple builds it must be reset in between |
| /// each one. |
| void reset() { |
| _checkedOutputs.clear(); |
| } |
| } |