| // Copyright 2024, 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 'useful_directory.dart'; |
| import 'package:file/file.dart'; |
| import 'package:logging/logging.dart'; |
| import 'package:meta/meta.dart'; |
| |
| /// Wraps a function in a dry-run [Zone]. |
| /// |
| /// [Package.isDryRun] returns [isDryRun] if accessed from the zone. |
| /// |
| /// Returns the return value of the function [code]. |
| R dryRun<R>(R Function() code, bool isDryRun) => |
| runZoned(code, zoneValues: {#_dryRun: isDryRun}); |
| |
| /// A resource creation strategy. |
| /// |
| /// A package [fetch]es inputs from a [source], creates a [build], and then |
| /// [upload]s the resulting artifacts to an implementation dependent location. |
| /// |
| /// Packages must implement the [fetch], [build] and [upload] methods to decide |
| /// the strategy for each operation. Implementations should use [log] to log |
| /// additional information. |
| /// |
| /// No artifacts must be uploaded if [isDryRun] is `true`. |
| abstract class Package { |
| final String name; |
| final Uri source; |
| Logger log; |
| bool get isDryRun => Zone.current[#_dryRun] ?? false; |
| |
| Package(this.name, this.source) : log = Logger(name); |
| |
| /// Creates the package by fetching the [source], building the the artifacts, |
| /// and uploading them. |
| /// |
| /// The package is created in subdirectory of [base] with the [name] of this |
| /// package. |
| Future<void> create(Directory base, bool provenance) async { |
| final workingDir = await base.resolve(name).create(recursive: true); |
| log.info("Created working directory: $workingDir"); |
| log.info("Fetching $source..."); |
| final fetched = await fetch(workingDir); |
| log.info("Building..."); |
| final built = await build(fetched, workingDir); |
| log.info("Checking build..."); |
| if (!await check(built)) { |
| log.severe("Build failed to produce the expected outputs."); |
| throw StateError("Build failed!"); |
| } |
| log.info("Uploading $built..."); |
| await upload(built, provenance); |
| log.info("Done!"); |
| } |
| |
| /// Fetches the [source] into [workingDir]. |
| /// |
| /// This method is intended to be called by [create] and must be implemented |
| /// to specify how [source] is fetched. |
| /// |
| /// Returns the fetched source files and directories. |
| @visibleForOverriding |
| Future<List<FileSystemEntity>> fetch(Directory workingDir); |
| |
| /// Builds artifacts from the [fetch]ed [inputs]. |
| /// |
| /// This method is intended to be called by [create] and must be implemented |
| /// to specify how artifacts are built. |
| /// |
| /// Returns a directory containing the build outputs. |
| @visibleForOverriding |
| Future<Directory> build(List<FileSystemEntity> inputs, Directory workingDir); |
| |
| /// Checks the [artifacts] created by [build]. |
| /// |
| /// This method is intended to be called by [create] and must be implemented |
| /// to check that the build has produced the expected outputs. |
| @visibleForOverriding |
| Future<bool> check(Directory artifacts); |
| |
| /// Uploads the [artifacts] created by [build]. |
| /// |
| /// This method is intended to be called by [create] and must be implemented |
| /// to specify how artifacts are uploaded. |
| /// |
| /// Must not upload any artifacts if [dryRun] is `true`. |
| @visibleForOverriding |
| Future<void> upload(Directory artifacts, bool provenance); |
| } |