diff --git a/build/CHANGELOG.md b/build/CHANGELOG.md index 91b2857966..9f9441820b 100644 --- a/build/CHANGELOG.md +++ b/build/CHANGELOG.md @@ -1,3 +1,7 @@ +## 4.0.1-wip + +- Improvements to dartdoc. + ## 4.0.0 - Remove methods and classes deprecated in `4.0.0`. diff --git a/build/lib/build.dart b/build/lib/build.dart index 929c41debc..e59be10b7d 100644 --- a/build/lib/build.dart +++ b/build/lib/build.dart @@ -11,7 +11,5 @@ export 'src/file_deleting_builder.dart'; export 'src/logging.dart' show log; export 'src/post_process_build_step.dart'; export 'src/post_process_builder.dart'; -export 'src/reader.dart'; export 'src/resolver.dart'; export 'src/resource.dart'; -export 'src/writer.dart'; diff --git a/build/lib/src/asset_id.dart b/build/lib/src/asset_id.dart index a0105430f4..aa50406de8 100644 --- a/build/lib/src/asset_id.dart +++ b/build/lib/src/asset_id.dart @@ -4,70 +4,59 @@ import 'package:path/path.dart' as p; -/// Identifies an asset within a package. +/// A file in a `build_runner` build. class AssetId implements Comparable { - /// The name of the package containing this asset. + /// The package containing the file. final String package; - /// The path to the asset relative to the root directory of [package]. + /// The relative path of the file under the root of [package]. /// - /// Source (i.e. read from disk) and generated (i.e. the output of a - /// `Builder`) assets all have paths. Even intermediate assets that are - /// generated and then consumed by later transformations will still have a - /// path used to identify it. + /// The path is relative and contains no parent references `..`, guaranteeing + /// that it is under [package]. /// - /// Asset paths always use forward slashes as path separators, regardless of - /// the host platform. Asset paths will always be within their package, that - /// is they will never contain "../". + /// The path segment separator is `/` on all platforms. final String path; - /// Splits [path] into its components. + /// The segments of [path]. List get pathSegments => p.url.split(path); - /// The file extension of the asset, if it has one, including the ".". + /// The file extension of [path]: the portion of its "basename" from the last + /// `.` to the end, including the `.` itself. String get extension => p.extension(path); - /// Creates a new [AssetId] at [path] within [package]. + /// An [AssetId] with the specified [path] under [package]. /// - /// The [path] will be normalized: any backslashes will be replaced with - /// forward slashes (regardless of host OS) and "." and ".." will be removed - /// where possible. + /// The [path] must be relative and under [package], or an [ArgumentError] is + /// thrown. + /// + /// The [path] is normalized: `\` is replaced with `/`, then `.` and `..` are removed. AssetId(this.package, String path) : path = _normalizePath(path); - /// Creates a new [AssetId] from an [uri] String. + /// Creates an [AssetId] from a [uri]. + /// + /// The [uri] can be relative, in which case it will be resolved relative to + /// [from]; if [uri] is relative and [from] is not specified, an + /// [ArgumentError] is thrown. /// - /// This gracefully handles `package:` or `asset:` URIs. + /// Or, [uri] can have the scheme `package` or `asset`. /// - /// Resolve a `package:` URI when creating an [AssetId] from an `import` or - /// `export` directive pointing to a package's _lib_ directory: - /// ```dart - /// AssetId assetOfDirective(UriReferencedElement element) { - /// return new AssetId.resolve(element.uri); - /// } - /// ``` + /// A `package` [uri] has the form `package:$package/$path` and references + /// the specified path within the `lib/` directory of the specified package, just like + /// a `package` URI in Dart source code. /// - /// When resolving a relative URI with no scheme, specifyg the origin asset - /// ([from]) - otherwise an [ArgumentError] will be thrown. - /// ```dart - /// AssetId assetOfDirective(AssetId origin, UriReferencedElement element) { - /// return new AssetId.resolve(element.uri, from: origin); - /// } - /// ``` + /// An `asset` [uri] has the form `asset:$package/$path` and references the + /// specified path within the root of the specified package. /// - /// `asset:` uris have the format '$package/$path', including the top level - /// directory. + /// If [uri] has any other scheme then [UnsupportedError] is thrown. factory AssetId.resolve(Uri uri, {AssetId? from}) { - final resolved = - uri.hasScheme - ? uri - : from != null - ? from.uri.resolveUri(uri) - : (throw ArgumentError.value( - from, - 'from', - 'An AssetId "from" must be specified to resolve a relative ' - 'URI', - )); + if (from == null && !uri.hasScheme) { + throw ArgumentError.value( + from, + 'from', + 'An AssetId `from` must be specified to resolve a relative URI.', + ); + } + final resolved = uri.hasScheme ? uri : from!.uri.resolveUri(uri); if (resolved.scheme == 'package') { return AssetId( resolved.pathSegments.first, @@ -80,46 +69,48 @@ class AssetId implements Comparable { ); } throw UnsupportedError( - 'Cannot resolve $uri; only "package" and "asset" schemes supported', + 'Cannot resolve $uri; only "package" and "asset" schemes supported.', ); } - /// Parses an [AssetId] string of the form "package|path/to/asset.txt". + /// Parses an [AssetId] string of the form `$package|$path`. /// - /// The [path] will be normalized: any backslashes will be replaced with - /// forward slashes (regardless of host OS) and "." and ".." will be removed - /// where possible. - factory AssetId.parse(String description) { - final parts = description.split('|'); + /// If [id] does not match that form, a [FormatException] is thrown. + /// + /// See [AssetId.new] for restrictions on `path` and how it will be + /// normalized. + factory AssetId.parse(String id) { + final parts = id.split('|'); if (parts.length != 2) { - throw FormatException('Could not parse "$description".'); - } - - if (parts[0].isEmpty) { - throw FormatException( - 'Cannot have empty package name in "$description".', - ); - } - - if (parts[1].isEmpty) { - throw FormatException('Cannot have empty path in "$description".'); + throw FormatException('Could not parse "$id".'); } return AssetId(parts[0], parts[1]); } - /// A `package:` URI suitable for use directly with other systems if this - /// asset is under it's package's `lib/` directory, else an `asset:` URI - /// suitable for use within build tools. + /// If [path] starts with `lib/`, the URI starting `package:` that refers to this asset. + /// + /// If not, the URI `asset:$package/$path`. late final Uri uri = _constructUri(this); - /// Deserializes an [AssetId] from [data], which must be the result of - /// calling [serialize] on an existing [AssetId]. - AssetId.deserialize(List data) - : package = data[0] as String, - path = data[1] as String; + /// Returns an [AssetId] in [package] with path `$path$exension`. + AssetId addExtension(String extension) => AssetId(package, '$path$extension'); + + /// Returns an [AssetId] in [package] with [extension] removed and + /// [newExtension] appended. + AssetId changeExtension(String newExtension) => + AssetId(package, p.withoutExtension(path) + newExtension); + + /// Deserializes a `List` from [serialize]. + AssetId.deserialize(List serialized) + : package = serialized[0] as String, + path = serialized[1] as String; + + /// Serializes this [AssetId] to an `Object` that can be sent across isolates. + /// + /// See [AssetId.deserialize]. + Object serialize() => [package, path]; - /// Returns `true` if [other] is an [AssetId] with the same package and path. @override bool operator ==(Object other) => other is AssetId && package == other.package && path == other.path; @@ -134,40 +125,27 @@ class AssetId implements Comparable { return path.compareTo(other.path); } - /// Returns a new [AssetId] with the same [package] as this one and with the - /// [path] extended to include [extension]. - AssetId addExtension(String extension) => AssetId(package, '$path$extension'); - - /// Returns a new [AssetId] with the same [package] and [path] as this one - /// but with file extension [newExtension]. - AssetId changeExtension(String newExtension) => - AssetId(package, p.withoutExtension(path) + newExtension); - + /// Returns `$package|$path`, which can be converted back to an `AssetId` + /// using [AssetId.parse]. @override String toString() => '$package|$path'; - - /// Serializes this [AssetId] to an object that can be sent across isolates - /// and passed to [AssetId.deserialize]. - Object serialize() => [package, path]; } String _normalizePath(String path) { if (p.isAbsolute(path)) { throw ArgumentError.value(path, 'Asset paths must be relative.'); } - - // Normalize path separators so that they are always "/" in the AssetID. path = path.replaceAll(r'\', '/'); // Collapse "." and "..". - final collapsed = p.posix.normalize(path); - if (collapsed.startsWith('../')) { + final result = p.posix.normalize(path); + if (result.startsWith('../')) { throw ArgumentError.value( path, - 'Asset paths may not reach outside the package.', + 'Asset paths must be within the specified the package.', ); } - return collapsed; + return result; } Uri _constructUri(AssetId id) { diff --git a/build/lib/src/build_step.dart b/build/lib/src/build_step.dart index 7406472d63..2087322002 100644 --- a/build/lib/src/build_step.dart +++ b/build/lib/src/build_step.dart @@ -7,72 +7,103 @@ import 'dart:convert'; // ignore: deprecated_member_use until analyzer 7 support is dropped. import 'package:analyzer/dart/element/element2.dart'; +import 'package:crypto/crypto.dart'; +import 'package:glob/glob.dart'; import 'package:package_config/package_config_types.dart'; import 'asset_id.dart'; -import 'reader.dart'; import 'resolver.dart'; import 'resource.dart'; -import 'writer.dart'; -/// A single step in a build process. +/// A single step in `build_runner` build. /// -/// This represents a single [inputId], logic around resolving as a library, -/// and the ability to read and write assets as allowed by the underlying build -/// system. +/// See the `Builder` class API for more information on what causes build steps +/// to run during a builder. The `Builder` class has a `build` method that +/// accepts a [BuildStep] and uses it to read inputs, resolve Dart source and +/// write outputs. abstract class BuildStep implements AssetReader, AssetWriter { - /// The primary for this build step. + /// The primary input that this build step is for. AssetId get inputId; - /// Resolved library defined by [inputId]. + /// Resolves the library in [inputId]. /// /// Throws [NonLibraryAssetException] if [inputId] is not a Dart library file. + /// /// Throws [SyntaxErrorInAssetException] if [inputId] contains syntax errors. - /// If you want to support libraries with syntax errors, resolve the library - /// manually instead of using [inputLibrary]: - /// ```dart - /// Future build(BuildStep step) async { - /// // Resolve the input library, allowing syntax errors - /// final inputLibrary = - /// await step.resolver.libraryFor(step.inputId, allowSyntaxErrors: true); - /// } - /// ``` + /// + /// To resolve allowing syntax errors, instead use + /// `resolver.libraryFor(buildStep.inputId, allowSyntaxErrors: true)`. // ignore: deprecated_member_use until analyzer 7 support is dropped. Future get inputLibrary; - /// Gets an instance provided by [resource] which is guaranteed to be unique - /// within a single build, and may be reused across build steps within a - /// build if the implementation allows. + /// A [Resolver] that can parse or resolve any Dart source code visible to + /// this build step. /// - /// It is also guaranteed that [resource] will be disposed before the next - /// build starts (and the dispose callback will be invoked if provided). - Future fetchResource(Resource resource); + /// That means all source files in all transitive deps of the current package, + /// all source files in the current package and all files output by builders + /// that run _before_ the current builder in the current build. + Resolver get resolver; - /// Writes [bytes] to a binary file located at [id]. + /// Reads the bytes from [id]. /// - /// Returns a [Future] that completes after writing the asset out. + /// - Throws a `PackageNotFoundException` if `id.package` is not found. + /// - Throws an `AssetNotFoundException` if `id.path` is not found. + /// - Throws an `InvalidInputException` if [id] is an invalid input. An input + /// is invalid if it is a non-public member of a dependency package; for + /// example by default `lib` contains public assets but `test` does not. + @override + Future> readAsBytes(AssetId id); + + /// Reads the text contents of [id] using [encoding]. /// - /// * Throws a `PackageNotFoundException` if `id.package` is not found. - /// * Throws an `InvalidOutputException` if the output was not valid (that is, - /// [id] is not in [allowedOutputs]) + /// - Throws a `PackageNotFoundException` if `id.package` is not found. + /// - Throws an `AssetNotFoundException` if `id.path` is not found. + /// - Throws an `InvalidInputException` if [id] is an invalid input. An input + /// is invalid if it is a non-public member of a dependency package; for + /// example by default `lib` contains public assets but `test` does not. + @override + Future readAsString(AssetId id, {Encoding encoding = utf8}); + + /// Indicates whether [id] can be read by this build step. + /// + /// That means the file exists, _and_ it is visible to this build step. /// - /// **NOTE**: Most `Builder` implementations should not need to `await` this - /// Future since the runner will be responsible for waiting until all outputs - /// are written. + /// Files that are generated by builders that run after the current builder + /// are hidden, even if they physically exist on disk due to an earlier build. + /// For such files, `canRead` returns `false` and the `read` methods will + /// throw `AssetNotFoundException`. @override - Future writeAsBytes(AssetId id, FutureOr> bytes); + Future canRead(AssetId id); + + /// Returns all readable assets matching [glob] under the current package. + /// + /// This includes generated assets output by builders that run before the + /// current builder, but _not_ generated assets that will be output by + /// builders that will run after the current builder. + @override + Stream findAssets(Glob glob); - /// Writes [contents] to a text file located at [id] with [encoding]. + /// Assets that are allowed to be written by this build step. /// - /// Returns a [Future] that completes after writing the asset out. + /// These are determined by the `buildExtensions` of the `Builder` running + /// in this step; see that API in `Builder` for more information. + Iterable get allowedOutputs; + + /// Writes [bytes] to [id]. /// - /// * Throws a `PackageNotFoundException` if `id.package` is not found. - /// * Throws an `InvalidOutputException` if the output was not valid (that is, - /// [id] is not in [allowedOutputs]) + /// The [id] must be one of [allowedOutputs] or an `UnexpectedOutputException` + /// is thrown. /// - /// **NOTE**: Most `Builder` implementations should not need to `await` this - /// Future since the runner will be responsible for waiting until all outputs - /// are written. + /// The [id] must not have been written before or an `InvalidOutputException` + /// is thrown. + @override + Future writeAsBytes(AssetId id, FutureOr> bytes); + + /// Writes [contents] to [id] using [encoding]. + /// + /// The [id] must be one of [allowedOutputs] or an `UnexpectedOutputException` + /// is thrown. And, this must be the first write to it, or an + /// `InvalidOutputException` is thrown. @override Future writeAsString( AssetId id, @@ -80,8 +111,11 @@ abstract class BuildStep implements AssetReader, AssetWriter { Encoding encoding = utf8, }); - /// A [Resolver] for [inputId]. - Resolver get resolver; + /// Fetches [resource]. + /// + /// See the [Resource] docs for how resources are created, shared and + /// disposed. + Future fetchResource(Resource resource); /// Tracks performance of [action] separately. /// @@ -90,48 +124,60 @@ abstract class BuildStep implements AssetReader, AssetWriter { /// /// You can specify [action] as [isExternal] (waiting for some external /// resource like network, process or file IO). In that case [action] will - /// be tracked as single time slice from the beginning of the stage till - /// completion of Future returned by [action]. + /// be tracked as a single time slice from the beginning of the stage until + /// completion of the `Future` returned by [action]. /// /// Otherwise all separate time slices of asynchronous execution will be /// tracked, but waiting for external resources will be a gap. /// - /// Returns value returned by [action]. - /// [action] can be async function returning [Future]. + /// Returns the value returned by [action]. + /// + /// Async [action]s that return a `Future` are supported. T trackStage(String label, T Function() action, {bool isExternal = false}); /// Indicates that [ids] were read but their content has no impact on the /// outputs of this step. /// - /// **WARNING**: Using this introduces serious risk of non-hermetic builds. - /// - /// If these files change or become unreadable on the next build this build - /// step may not run. - /// - /// **Note**: This is not guaranteed to have any effect and it should be - /// assumed to be a no-op by default. Implementations must explicitly - /// choose to support this feature. + /// If [ids] change then `build_runner` might optimize by not running this + /// step. void reportUnusedAssets(Iterable ids); - /// Returns assets that may be written in this build step. + /// The [PackageConfig] of the current isolate. /// - /// Allowed outputs are formed by matching the [inputId] against the builder's - /// `buildExtensions`, which declares a list of output extensions for this - /// input. - /// - /// The writing methods [writeAsBytes] and [writeAsString] will throw an - /// `InvalidOutputException` when attempting to write an asset not part of - /// the [allowedOutputs]. - Iterable get allowedOutputs; + /// URIs in the config are `asset:$package/$path` URIs that work with + /// [AssetId.resolve], allowing files to be read with [readAsString] or + /// [readAsBytes]. + Future get packageConfig; +} - /// Returns a [PackageConfig] resolvable from this build step. - /// - /// The package config contains all packages involved in the build. Typically, - /// this is the package config taken from the current isolate. +/// The "write" part of the [BuildStep] API. +/// +/// See [BuildStep] for details. +abstract class AssetWriter { + Future writeAsBytes(AssetId id, List bytes); + + Future writeAsString( + AssetId id, + String contents, { + Encoding encoding = utf8, + }); +} + +/// The "read" part of the [BuildStep] API. +/// +/// See [BuildStep] for details. +abstract class AssetReader { + Future> readAsBytes(AssetId id); + + Future readAsString(AssetId id, {Encoding encoding = utf8}); + + Future canRead(AssetId id); + + Stream findAssets(Glob glob); + + /// Returns a [Digest] representing a hash of the contents of [id]. /// - /// The returned package config does not use `file:/`-based URIs and can't be - /// used to access package contents with `dart:io`. Instead, packages resolve - /// to `asset:/` URIs that can be parsed with [AssetId.resolve] and read with - /// [readAsBytes] or [readAsString]. - Future get packageConfig; + /// This is not intended for use by builders: `build_runner` already checks + /// input digests to only run build steps if inputs changed. + Future digest(AssetId id); } diff --git a/build/lib/src/builder.dart b/build/lib/src/builder.dart index e38c2a6447..57634db9e4 100644 --- a/build/lib/src/builder.dart +++ b/build/lib/src/builder.dart @@ -6,42 +6,61 @@ import 'dart:async'; import 'build_step.dart'; -/// The basic builder class, used to build new files from existing ones. -abstract class Builder { - /// Generates the outputs for a given [BuildStep]. - FutureOr build(BuildStep buildStep); - - /// Mapping from input file extension to output file extensions. - /// - /// All input sources matching any key in this map will be passed as build - /// step to this builder. Only files with the same basename and an extension - /// from the values in this map are expected as outputs. - /// - /// - If an empty key exists, all inputs are considered matching. - /// - An instance of a builder must always return the same configuration. - /// Typically, a builder will return a `const` map. Builders may also choose - /// extensions based on [BuilderOptions]. - /// - Most builders will use a single input extension and one or more output - /// extensions. - /// - For more information on build extensions, see - /// https://github.com/dart-lang/build/blob/master/docs/writing_a_builder.md#configuring-outputs - Map> get buildExtensions; -} +/// A factory for a builder in the `build_runner` build. +/// +/// To write your own builder, create a top-level function that is a +/// `BuilderFactory`: it accepts a `BuilderOptions` and returns an instance of +/// your `Builder`. Then, create a `build.yaml` at the root of the package +/// telling `build_runner` about the factory. +/// +/// For example: +/// +/// ```yaml +/// builders: +/// my_builder: +/// import: "package:my_package/builder.dart" +/// builder_factories: ["myBuilder"] +/// build_extensions: {".dart": [".my_package.dart"]} +/// auto_apply: dependents +/// ``` +/// +/// This `build.yaml` file says there is a `BuilderFactory` in +/// `package:my_package/builder.dart` called `myBuilder`. +/// `auto_apply: dependents` tells `build_runner` to run the builder on all +/// packages that depend on `my_package`. +/// +/// The `build_extensions` should if possible match the value returned by +/// [Builder.buildExtensions], because the `buildExtensions` in the `build.yaml` +/// is what decides the order in which builders will run. However, a builder +/// can change its `buildExtensions` based on options. In this case the builder +/// order is determined based on `build.yaml` but the runtime behavior uses +/// the runtime `buildExtensions`. +typedef BuilderFactory = Builder Function(BuilderOptions options); +/// Options that are passed to a [BuilderFactory] to instantiate a [Builder]. +/// +/// Based on these options the [Builder] can change any aspect of its behavior: +/// its [Builder.buildExtensions], which files it reads, which files it outputs +/// and/or the contents of those files. +/// +/// The options come from three places: a builder's own `build.yaml` file can +/// define defaults; the package the builder is running in can set options; and +/// options can be passed in on the `build_runner` command line using +/// `--define`. class BuilderOptions { /// A configuration with no options set. static const empty = BuilderOptions({}); - /// A configuration with [isRoot] set to `true`, and no options set. + /// A configuration with [isRoot] set to `true` and no options set. static const forRoot = BuilderOptions({}, isRoot: true); /// The configuration to apply to a given usage of a [Builder]. /// - /// A `Map` parsed from json or yaml. The value types will be `String`, `num`, - /// `bool` or `List` or `Map` of these types. + /// Possible values are primitives available in JSON and yaml: `String`, + /// `num`, `bool` or `List` or `Map` of these types. final Map config; - /// Whether or not this builder is running on the root package. + /// Whether this builder is running on the root package. final bool isRoot; const BuilderOptions(this.config, {this.isRoot = false}); @@ -49,11 +68,12 @@ class BuilderOptions { /// Returns a new set of options with keys from [other] overriding options in /// this instance. /// - /// Config values are overridden at a per-key granularity. There is no value - /// level merging. [other] may be null, in which case this instance is - /// returned directly. + /// Config values are overridden at a per-key granularity, there is no + /// value-level merging. + /// + /// If [other] is `null` then this instance is returned directly. /// - /// The `isRoot` value will also be overridden to value from [other]. + /// The `isRoot` value is overridden to the value from [other]. BuilderOptions overrideWith(BuilderOptions? other) { // ignore: avoid_returning_this if (other == null) return this; @@ -66,5 +86,30 @@ class BuilderOptions { } } -/// Creates a [Builder] honoring the configuation in [options]. -typedef BuilderFactory = Builder Function(BuilderOptions options); +/// A builder in the `build_runner` build. +/// +/// Each builder specifies which files it matches as [buildExtensions]; matching +/// files are called "primary inputs". +/// +/// During the build `build_runner` runs the [build] method of each builder once +/// per primary input, with a [BuildStep] created for that input. +abstract class Builder { + /// Generates the outputs for a given [BuildStep]. + FutureOr build(BuildStep buildStep); + + /// Mapping from input file extension to output file extensions. + /// + /// All input sources matching any key in this map will be passed as a build + /// step to this builder. Only files with the same basename and an extension + /// from the values in this map are expected as outputs. + /// + /// - If an empty key exists, all inputs are considered matching. + /// - An instance of a builder must always return the same configuration. + /// Typically, a builder will return a `const` map. Builders may also choose + /// extensions based on [BuilderOptions]. + /// - Most builders will use a single input extension and one or more output + /// extensions. + /// + /// TODO(davidmorgan): add examples. + Map> get buildExtensions; +} diff --git a/build/lib/src/reader.dart b/build/lib/src/reader.dart deleted file mode 100644 index 1b04acc251..0000000000 --- a/build/lib/src/reader.dart +++ /dev/null @@ -1,50 +0,0 @@ -// 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:convert'; - -import 'package:crypto/crypto.dart'; -import 'package:glob/glob.dart'; - -import 'asset_id.dart'; - -/// Standard interface for reading an asset within in a package. -/// -/// An [AssetReader] is required when calling the `runBuilder` method. -abstract class AssetReader { - /// Returns a [Future] that completes with the bytes of a binary asset. - /// - /// * Throws a `PackageNotFoundException` if `id.package` is not found. - /// * Throws a `AssetNotFoundException` if `id.path` is not found. - /// * Throws an `InvalidInputException` if [id] is an invalid input. - Future> readAsBytes(AssetId id); - - /// Returns a [Future] that completes with the contents of a text asset. - /// - /// When decoding as text uses [encoding], or [utf8] is not specified. - /// - /// * Throws a `PackageNotFoundException` if `id.package` is not found. - /// * Throws a `AssetNotFoundException` if `id.path` is not found. - /// * Throws an `InvalidInputException` if [id] is an invalid input. - Future readAsString(AssetId id, {Encoding encoding = utf8}); - - /// Indicates whether asset at [id] is readable. - Future canRead(AssetId id); - - /// Returns all readable assets matching [glob] under the current package. - Stream findAssets(Glob glob); - - /// Returns a [Digest] representing a hash of the contents of [id]. - /// - /// The digests should include the asset ID as well as the content of the - /// file, as some build systems may rely on the digests for two files being - /// different, even if their content is the same. - /// - /// This should be treated as a transparent [Digest] and the implementation - /// may differ based on the current build system being used. - /// - /// Similar to [readAsBytes], `digest` throws an exception if the asset can't - /// be found or if it's an invalid input. - Future digest(AssetId id); -} diff --git a/build/lib/src/writer.dart b/build/lib/src/writer.dart deleted file mode 100644 index fad9f365b7..0000000000 --- a/build/lib/src/writer.dart +++ /dev/null @@ -1,29 +0,0 @@ -// 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:convert'; - -import 'asset_id.dart'; - -/// Standard interface for writing an asset into a package's outputs. -abstract class AssetWriter { - /// Writes [bytes] to a binary file located at [id]. - /// - /// Returns a [Future] that completes after writing the asset out. - /// - /// * Throws a `PackageNotFoundException` if `id.package` is not found. - /// * Throws an `InvalidOutputException` if the output was not valid. - Future writeAsBytes(AssetId id, List bytes); - - /// Writes [contents] to a text file located at [id] with [encoding]. - /// - /// Returns a [Future] that completes after writing the asset out. - /// - /// * Throws a `PackageNotFoundException` if `id.package` is not found. - /// * Throws an `InvalidOutputException` if the output was not valid. - Future writeAsString( - AssetId id, - String contents, { - Encoding encoding = utf8, - }); -} diff --git a/build/pubspec.yaml b/build/pubspec.yaml index 5fecce6897..08ba826544 100644 --- a/build/pubspec.yaml +++ b/build/pubspec.yaml @@ -1,5 +1,5 @@ name: build -version: 4.0.0 +version: 4.0.1-wip description: A package for authoring build_runner compatible code generators. repository: https://github.com/dart-lang/build/tree/master/build resolution: workspace diff --git a/build/test/id_test.dart b/build/test/asset_id_test.dart similarity index 90% rename from build/test/id_test.dart rename to build/test/asset_id_test.dart index be8ea84e5c..2097a534bb 100644 --- a/build/test/id_test.dart +++ b/build/test/asset_id_test.dart @@ -18,6 +18,10 @@ void main() { final id = AssetId('app', r'path\to/asset.txt'); expect(id.path, equals('path/to/asset.txt')); }); + + test('allows empty package and path', () { + AssetId('', ''); + }); }); group('parse', () { @@ -31,14 +35,6 @@ void main() { expect(() => AssetId.parse('app|path|wtf'), throwsFormatException); }); - test("throws if the package name is empty '|'", () { - expect(() => AssetId.parse('|asset.txt'), throwsFormatException); - }); - - test("throws if the path is empty '|'", () { - expect(() => AssetId.parse('app|'), throwsFormatException); - }); - test('normalizes the path', () { final id = AssetId.parse(r'app|path/././/to/drop/..//asset.txt'); expect(id.path, equals('path/to/asset.txt')); @@ -145,6 +141,25 @@ void main() { }); }); + group('changeExtension', () { + test('replaces everything from the last dot', () { + expect( + AssetId('foo', 'lib/bar.dart').changeExtension('.other'), + AssetId('foo', 'lib/bar.other'), + ); + + expect( + AssetId('foo', 'lib/bar.1.dart').changeExtension('.other'), + AssetId('foo', 'lib/bar.1.other'), + ); + + expect( + AssetId('foo', 'lib/bar').changeExtension('.other'), + AssetId('foo', 'lib/bar.other'), + ); + }); + }); + test('equals another ID with the same package and path', () { expect( AssetId.parse('foo|asset.txt'), diff --git a/build_runner/CHANGELOG.md b/build_runner/CHANGELOG.md index d257a319ca..6aa86b8302 100644 --- a/build_runner/CHANGELOG.md +++ b/build_runner/CHANGELOG.md @@ -1,6 +1,7 @@ ## 2.8.1-wip - Bug fix: require `args` 2.5.0. +- Use `build` ^4.0.0. ## 2.8.0 diff --git a/build_runner/pubspec.yaml b/build_runner/pubspec.yaml index 11cd6f7c91..c29f0853fa 100644 --- a/build_runner/pubspec.yaml +++ b/build_runner/pubspec.yaml @@ -16,7 +16,7 @@ dependencies: analyzer: '>=7.4.0 <9.0.0' args: ^2.5.0 async: ^2.5.0 - build: '4.0.0' + build: ^4.0.0 build_config: ">=1.2.0 <1.3.0" build_daemon: ^4.0.0 built_collection: ^5.1.1 diff --git a/build_test/CHANGELOG.md b/build_test/CHANGELOG.md index 30a259e5c2..53508b46fb 100644 --- a/build_test/CHANGELOG.md +++ b/build_test/CHANGELOG.md @@ -1,6 +1,7 @@ ## 3.4.1-wip - Use `build_runner` 2.8.1-wip. +- Use `build` 4.0.1-wip. ## 3.4.0 diff --git a/build_test/pubspec.yaml b/build_test/pubspec.yaml index 21a0e568fb..039550f0c0 100644 --- a/build_test/pubspec.yaml +++ b/build_test/pubspec.yaml @@ -8,7 +8,7 @@ environment: sdk: ^3.7.0 dependencies: - build: '4.0.0' + build: '4.0.1-wip' build_config: ^1.0.0 build_runner: '2.8.1-wip' built_collection: ^5.1.1