[deps] Roll dart-lang/native

This roll moves `package:record_use` to the dart-lang/native repo.

Change-Id: I31183dc8b72272d7e94ed3031ca0b8bfca583e0d
Cq-Include-Trybots: luci.dart.try:pkg-linux-debug-try,pkg-linux-release-arm64-try,pkg-linux-release-try,pkg-mac-release-arm64-try,pkg-mac-release-try,pkg-win-release-arm64-try,pkg-win-release-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/463662
Commit-Queue: Daco Harkes <dacoharkes@google.com>
Reviewed-by: Michael Goderbauer <goderbauer@google.com>
This commit is contained in:
Daco Harkes
2025-11-24 05:04:03 -08:00
committed by Commit Queue
parent 2c804fa251
commit 0d052e3969
58 changed files with 18 additions and 2355 deletions
+1 -1
View File
@@ -140,7 +140,7 @@ vars = {
"i18n_rev": "dd8a792a8492370a594706c8304d2eb8db844d7a",
"leak_tracker_rev": "f5620600a5ce1c44f65ddaa02001e200b096e14c", # rolled manually
"material_color_utilities_rev": "799b6ba2f3f1c28c67cc7e0b4f18e0c7d7f3c03e",
"native_rev": "3ec573500f743d4a1393f7802143aef50fec0a47", # rolled manually while native assets are experimental
"native_rev": "43a691c2152dd9b699259e29356d10edab17ec14", # rolled manually while record_use is experimental
"protobuf_rev": "3ed04a91ff4de8493226e8fd0d89678c2986edf6",
"pub_rev": "f7f1891e2de3d795532f45ec214f88ac912ffcd6", # rolled manually
"shelf_rev": "b924de80e122d1e17b1132bedb02ddb1445b337c",
+3 -5
View File
@@ -114,7 +114,8 @@ Future<void> copyTestProjects(Uri copyTargetUri, Logger logger,
'code_assets',
'data_assets',
'hooks',
'native_toolchain_c'
'native_toolchain_c',
'record_use',
])
package: {
'path': sdkRoot
@@ -124,9 +125,6 @@ Future<void> copyTestProjects(Uri copyTargetUri, Logger logger,
'meta': {
'path': sdkRoot.resolve('pkg/meta/').toFilePath(),
},
'record_use': {
'path': sdkRoot.resolve('pkg/record_use/').toFilePath(),
},
};
final userDefinesWorkspace = {};
for (final pubspecPath in pubspecPaths) {
@@ -285,7 +283,7 @@ Future<void> recordUseTest(
packageUnderTest,
fun,
const ['drop_dylib_recording', 'drop_data_asset'],
sdkRootUri.resolve('pkg/record_use/'),
sdkRootUri.resolve('third_party/pkg/native/pkgs/record_use/'),
sdkRootUri,
false,
);
-30
View File
@@ -1,30 +0,0 @@
## 0.4.2
- Fix empty instance parsing.
## 0.4.1
- Fix bug in signature parsing.
## 0.4.0
- Update SDK constraint to `^3.5.0`.
- Rewrite API to expose less symbols.
- Remove locations for easier caching.
## 0.3.0
- Make `InstanceConstant` a `Constant`.
- Separate import from location uri.
## 0.2.0
- Use maps instead of lists in serialization.
## 0.1.1
- Fix repository link.
## 0.1.0
- Initial version.
-27
View File
@@ -1,27 +0,0 @@
Copyright 2024, the Dart project authors.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of Google LLC nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-5
View File
@@ -1,5 +0,0 @@
set noparent
file:/tools/OWNERS_ECOSYSTEM
file:/tools/OWNERS_VM
# Global owners
file:/OWNERS
-117
View File
@@ -1,117 +0,0 @@
> [!CAUTION]
> This is an experimental package, and it's API can break at any time. Use at
> your own discretion.
This package provides the data classes for the usage recording feature in the
Dart SDK.
Dart objects with the `@RecordUse` annotation are being recorded at compile
time, providing the user with information. The information depends on the object
being recorded.
- If placed on a static method, the annotation means that arguments passed to
the method will be recorded, as far as they can be inferred at compile time.
- If placed on a class with a constant constructor, the annotation means that
any constant instance of the class will be recorded. This is particularly useful
when using the class as an annotation.
## Example
```dart
import 'package:meta/meta.dart' show RecordUse;
void main() {
print(SomeClass.stringMetadata(42));
print(SomeClass.doubleMetadata(42));
print(SomeClass.intMetadata(42));
print(SomeClass.boolMetadata(42));
}
class SomeClass {
@RecordMetadata('leroyjenkins')
@RecordUse()
static stringMetadata(int i) {
return i + 1;
}
@RecordMetadata(3.14)
@RecordUse()
static doubleMetadata(int i) {
return i + 1;
}
@RecordMetadata(42)
@RecordUse()
static intMetadata(int i) {
return i + 1;
}
@RecordMetadata(true)
@RecordUse()
static boolMetadata(int i) {
return i + 1;
}
}
@RecordUse()
class RecordMetadata {
final Object metadata;
const RecordMetadata(this.metadata);
}
```
This code will generate a data file that contains both the `metadata` values of
the `RecordMetadata` instances, as well as the arguments for the different
methods annotated with `@RecordUse()`.
This information can then be accessed in a link hook as follows:
```dart
import 'dart:convert';
import 'package:hooks/hooks.dart';
import 'package:record_use/record_use_internal.dart';
final methodId = Identifier(
uri: 'myfile.dart',
name: 'myMethod',
);
final classId = Identifier(
uri: 'myfile.dart',
name: 'myClass',
);
void main(List<String> arguments){
link(arguments, (config, output) async {
final usesUri = config.recordedUses;
final usesJson = await File,fromUri(usesUri).readAsString();
final uses = UsageRecord.fromJson(jsonDecode(usesJson));
final args = uses.argumentsTo(methodId));
//[args] is an iterable of arguments, in this case containing "42"
final fields = uses.instancesOf(classId);
//[fields] is an iterable of the fields of the class, in this case
//containing
// {"arguments": "leroyjenkins"}
// {"arguments": 3.14}
// {"arguments": 42}
// {"arguments": true}
... // Do something with the information, such as tree-shaking native assets
});
}
```
## Limitations
As this is designed to work on both web and native platforms, we have to adapt
to the platform pecularities. One of them is that javascript does not support
named arguments, so the dart2js compiler rewrites functions to only accept named
parameters.
While you can use named parameters to record functions, we advise caution as the
retrieval behavior might change once we work around this dart2js limitation and
implement separate positional and named parameters.
## Contributing
Contributions are welcome! Please open an issue or submit a pull request.
-4
View File
@@ -1,4 +0,0 @@
include: package:dart_flutter_team_lints/analysis_options.yaml
analyzer:
exclude: [test_data/**]
@@ -1,17 +0,0 @@
// Copyright (c) 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 'package:record_use/record_use.dart';
void doStuffInLinkHook(
RecordedUsages usage,
Identifier identifier1,
Identifier identifier2,
Identifier identifier3,
) {
print(usage.metadata);
print(usage.constArgumentsFor(identifier1));
print(usage.constantsOf(identifier2));
print(usage.hasNonConstArguments(identifier3));
}
-8
View File
@@ -1,8 +0,0 @@
// Copyright (c) 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.
export 'src/identifier.dart' show Identifier;
export 'src/metadata.dart' show Metadata, MetadataExt;
export 'src/record_use.dart' show ConstantInstance, RecordedUsages;
export 'src/recorded_usage_from_file.dart' show parseFromFile;
@@ -1,25 +0,0 @@
// Copyright (c) 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.
export 'src/constant.dart'
show
BoolConstant,
Constant,
InstanceConstant,
IntConstant,
ListConstant,
MapConstant,
NullConstant,
PrimitiveConstant,
StringConstant;
export 'src/definition.dart' show Definition;
export 'src/identifier.dart' show Identifier;
export 'src/location.dart' show Location;
export 'src/metadata.dart' show Metadata, MetadataExt;
export 'src/record_use.dart' show RecordedUsages;
export 'src/recordings.dart'
show FlattenConstantsExtension, MapifyIterableExtension, Recordings;
export 'src/reference.dart'
show CallReference, CallTearOff, CallWithArguments, InstanceReference;
export 'src/version.dart' show version;
-262
View File
@@ -1,262 +0,0 @@
// Copyright (c) 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 'helper.dart';
const _typeKey = 'type';
const _valueKey = 'value';
/// A constant value that can be recorded and serialized.
///
/// This supports basic constants such as [bool]s or [int]s, as well as
/// [ListConstant], [MapConstant] or [InstanceConstant] for more complex
/// structures.
///
/// This follows the AST constant concept from the Dart SDK.
sealed class Constant {
/// Creates a [Constant] object.
const Constant();
/// Converts this [Constant] object to a JSON representation.
///
/// [constants] needs to be passed, as the [Constant]s are normalized and
/// stored separately in the JSON.
Map<String, Object?> toJson(Map<Constant, int> constants);
/// Converts this [Constant] to the value it represents.
Object? toValue() => switch (this) {
NullConstant() => null,
PrimitiveConstant p => p.value,
ListConstant<Constant> l => l.value.map((c) => c.toValue()).toList(),
MapConstant<Constant> m => m.value.map(
(key, value) => MapEntry(key, value.toValue()),
),
InstanceConstant i => i.fields.map(
(key, value) => MapEntry(key, value.toValue()),
),
};
/// Creates a [Constant] object from its JSON representation.
///
/// [constants] needs to be passed, as the [Constant]s are normalized and
/// stored separately in the JSON.
static Constant fromJson(
Map<String, Object?> value,
List<Constant> constants,
) => switch (value[_typeKey] as String) {
NullConstant._type => const NullConstant(),
BoolConstant._type => BoolConstant(value[_valueKey] as bool),
IntConstant._type => IntConstant(value[_valueKey] as int),
StringConstant._type => StringConstant(value[_valueKey] as String),
ListConstant._type => ListConstant(
(value[_valueKey] as List<dynamic>)
.map((value) => value as int)
.map((value) => constants[value])
.toList(),
),
MapConstant._type => MapConstant(
(value[_valueKey] as Map<String, Object?>).map(
(key, value) => MapEntry(key, constants[value as int]),
),
),
InstanceConstant._type => InstanceConstant(
fields: (value[_valueKey] as Map<String, Object?>? ?? {}).map(
(key, value) => MapEntry(key, constants[value as int]),
),
),
String() =>
throw UnimplementedError('This type is not a supported constant'),
};
}
/// Represents the `null` constant value.
final class NullConstant extends Constant {
/// The type identifier for JSON serialization.
static const _type = 'Null';
/// Creates a [NullConstant] object.
const NullConstant() : super();
@override
Map<String, Object?> toJson(Map<Constant, int> constants) =>
_toJson(_type, null);
@override
bool operator ==(Object other) => other is NullConstant;
@override
int get hashCode => 0;
}
/// Represents a constant value of a primitive type.
sealed class PrimitiveConstant<T extends Object> extends Constant {
/// The underlying value of this constant.
final T value;
/// Creates a [PrimitiveConstant] object with the given [value].
const PrimitiveConstant(this.value);
@override
int get hashCode => value.hashCode;
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is PrimitiveConstant<T> && other.value == value;
}
@override
Map<String, Object?> toJson(Map<Constant, int> constants) => valueToJson();
/// Converts this primitive constant to a JSON representation.
Map<String, Object?> valueToJson();
}
/// Represents a constant boolean value.
final class BoolConstant extends PrimitiveConstant<bool> {
/// The type identifier for JSON serialization.
static const _type = 'bool';
/// Creates a [BoolConstant] object with the given boolean [value].
const BoolConstant(super.value);
@override
Map<String, Object?> valueToJson() => _toJson(_type, value);
}
/// Represents a constant integer value.
final class IntConstant extends PrimitiveConstant<int> {
/// The type identifier for JSON serialization.
static const _type = 'int';
/// Creates an [IntConstant] object with the given integer [value].
const IntConstant(super.value);
@override
Map<String, Object?> valueToJson() => _toJson(_type, value);
}
/// Represents a constant string value.
final class StringConstant extends PrimitiveConstant<String> {
/// The type identifier for JSON serialization.
static const _type = 'String';
/// Creates a [StringConstant] object with the given string [value].
const StringConstant(super.value);
@override
Map<String, Object?> valueToJson() => _toJson(_type, value);
}
/// Represents a constant list of [Constant] values.
final class ListConstant<T extends Constant> extends Constant {
/// The type identifier for JSON serialization.
static const _type = 'list';
/// The underlying list of constant values.
final List<T> value;
/// Creates a [ListConstant] object with the given list of [value]s.
const ListConstant(this.value);
@override
int get hashCode => deepHash(value);
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is ListConstant && deepEquals(other.value, value);
}
@override
Map<String, Object?> toJson(Map<Constant, int> constants) =>
_toJson(_type, value.map((constant) => constants[constant]).toList());
}
/// Represents a constant map from string keys to [Constant] values.
final class MapConstant<T extends Constant> extends Constant {
/// The type identifier for JSON serialization.
static const _type = 'map';
/// The underlying map of constant values.
final Map<String, T> value;
/// Creates a [MapConstant] object with the given map of [value]s.
const MapConstant(this.value);
@override
int get hashCode => deepHash(value);
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is MapConstant && deepEquals(other.value, value);
}
@override
Map<String, Object?> toJson(Map<Constant, int> constants) => _toJson(
_type,
value.map((key, constant) => MapEntry(key, constants[constant]!)),
);
}
/// A constant instance of a class with its fields
///
/// Only as far as they can also be represented by constants. This is more or
/// less the same as a [MapConstant].
final class InstanceConstant extends Constant {
/// The type identifier for JSON serialization.
static const _type = 'Instance';
/// The fields of this instance, mapped from field name to [Constant] value.
final Map<String, Constant> fields;
/// Creates an [InstanceConstant] object with the given [fields].
const InstanceConstant({required this.fields});
/// Creates an [InstanceConstant] object from JSON.
///
/// [json] is a map representing the JSON structure.
/// [constants] is a list of [Constant] objects that are referenced by index
/// in the JSON.
factory InstanceConstant.fromJson(
Map<String, Object?> json,
List<Constant> constants,
) {
return InstanceConstant(
fields: json.map(
(key, constantIndex) => MapEntry(key, constants[constantIndex as int]),
),
);
}
@override
Map<String, Object?> toJson(Map<Constant, int> constants) => _toJson(
_type,
fields.isNotEmpty
? fields.map(
(name, constantIndex) => MapEntry(name, constants[constantIndex]!),
)
: null,
);
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is InstanceConstant && deepEquals(other.fields, fields);
}
@override
int get hashCode => deepHash(fields);
}
/// Helper to create the JSON structure of constants by storing the value with
/// the type.
Map<String, Object?> _toJson(String type, Object? value) {
return {_typeKey: type, if (value != null) _valueKey: value};
}
-42
View File
@@ -1,42 +0,0 @@
// Copyright (c) 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 'identifier.dart' show Identifier;
/// A defintion is an [identifier] with its [loadingUnit].
class Definition {
final Identifier identifier;
final String? loadingUnit;
const Definition({required this.identifier, this.loadingUnit});
static const _identifierKey = 'identifier';
static const _loadingUnitKey = 'loading_unit';
factory Definition.fromJson(Map<String, Object?> json) {
return Definition(
identifier: Identifier.fromJson(
json[_identifierKey] as Map<String, Object?>,
),
loadingUnit: json[_loadingUnitKey] as String?,
);
}
Map<String, Object?> toJson() => {
_identifierKey: identifier.toJson(),
if (loadingUnit != null) _loadingUnitKey: loadingUnit,
};
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is Definition &&
other.identifier == identifier &&
other.loadingUnit == loadingUnit;
}
@override
int get hashCode => Object.hash(identifier, loadingUnit);
}
-9
View File
@@ -1,9 +0,0 @@
// Copyright (c) 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 'package:collection/collection.dart';
final deepEquals = const DeepCollectionEquality().equals;
final deepHash = const DeepCollectionEquality().hash;
-68
View File
@@ -1,68 +0,0 @@
// Copyright (c) 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.
/// Represents a unique identifier for a code element, such as a class, method,
/// or field, within a Dart program.
///
/// An [Identifier] is used to pinpoint a specific element based on its
/// location and name. It consists of:
///
/// - `importUri`: The URI of the library where the element is defined.
/// - `parent`: The name of the parent element (e.g., the class name for a
/// method or field). This is optional, as not all elements have parents (e.g.
/// top-level functions).
/// - `name`: The name of the element itself.
class Identifier {
/// The URI of the library where the element is defined.
///
/// This is given in the form of its package import uri, so that it is OS- and
/// user independent.
final String importUri;
/// The name of the parent element (e.g., the class name for a method or
/// field). This is optional, as not all elements have parents (e.g. top-level
/// functions).
final String? scope;
/// The name of the element itself.
final String name;
/// Creates an [Identifier] object.
///
/// [importUri] is the URI of the library where the element is defined.
/// [scope] is the optional name of the parent element.
/// [name] is the name of the element.
const Identifier({required this.importUri, this.scope, required this.name});
static const String _uriKey = 'uri';
static const String _scopeKey = 'scope';
static const String _nameKey = 'name';
/// Creates an [Identifier] object from its JSON representation.
factory Identifier.fromJson(Map<String, Object?> json) => Identifier(
importUri: json[_uriKey] as String,
scope: json[_scopeKey] as String?,
name: json[_nameKey] as String,
);
/// Converts this [Identifier] object to a JSON representation.
Map<String, Object?> toJson() => {
_uriKey: importUri,
if (scope != null) _scopeKey: scope,
_nameKey: name,
};
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is Identifier &&
other.importUri == importUri &&
other.scope == scope &&
other.name == name;
}
@override
int get hashCode => Object.hash(importUri, scope, name);
}
-44
View File
@@ -1,44 +0,0 @@
// Copyright (c) 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.
class Location {
final String uri;
final int? line;
final int? column;
const Location({required this.uri, this.line, this.column});
static const _uriKey = 'uri';
static const _lineKey = 'line';
static const _columnKey = 'column';
factory Location.fromJson(Map<String, Object?> map) {
return Location(
uri: map[_uriKey] as String,
line: map[_lineKey] as int?,
column: map[_columnKey] as int?,
);
}
Map<String, Object?> toJson() {
return {
_uriKey: uri,
if (line != null) _lineKey: line,
if (line != null) _columnKey: column,
};
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is Location &&
other.uri == uri &&
other.line == line &&
other.column == column;
}
@override
int get hashCode => Object.hash(uri, line, column);
}
-41
View File
@@ -1,41 +0,0 @@
// Copyright (c) 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 'package:pub_semver/pub_semver.dart';
import 'helper.dart';
/// Metadata attached to a recorded usages file.
///
/// Whatever [Metadata] should be added to the usage recording. Care should be
/// applied to not include non-deterministic or dynamic data such as timestamps,
/// as this would mess with the usage recording caching.
class Metadata {
/// The underlying data.
///
/// Together with the metadata extension [MetadataExt], this makes the
/// metadata extensible by the user implementing the recording. For example,
/// dart2js might want to store different metadata than the Dart VM.
final Map<String, Object?> json;
const Metadata._({required this.json});
factory Metadata.fromJson(Map<String, Object?> json) =>
Metadata._(json: json);
@override
bool operator ==(covariant Metadata other) {
if (identical(this, other)) return true;
return deepEquals(other.json, json);
}
@override
int get hashCode => deepHash(json);
}
extension MetadataExt on Metadata {
Version get version => Version.parse(json['version'] as String);
String get comment => json['comment'] as String;
}
-140
View File
@@ -1,140 +0,0 @@
// Copyright (c) 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 '../record_use_internal.dart';
/// Holds all information recorded during compilation.
///
/// This can be queried using the methods provided, which each take an
/// [Identifier] which must be annotated with `@RecordUse` from `package:meta`.
extension type RecordedUsages._(Recordings _recordings) {
RecordedUsages.fromJson(Map<String, Object?> json)
: this._(Recordings.fromJson(json));
/// Show the metadata for this recording of usages.
Metadata get metadata => _recordings.metadata;
/// Finds all const arguments for calls to the [identifier].
///
/// The definition must be annotated with `@RecordUse()`. If there are no
/// calls to the definition, either because it was treeshaken, because it was
/// not annotated, or because it does not exist, returns empty.
///
/// Returns an empty iterable if the arguments were not collected.
///
/// Example:
/// ```dart
/// import 'package:meta/meta.dart' show ResourceIdentifier;
/// void main() {
/// print(SomeClass.someStaticMethod(42));
/// }
///
/// class SomeClass {
/// @ResourceIdentifier('id')
/// static someStaticMethod(int i) {
/// return i + 1;
/// }
/// }
/// ```
///
/// Would mean that
/// ```
/// argumentsTo(Identifier(
/// uri: 'path/to/file.dart',
/// parent: 'SomeClass',
/// name: 'someStaticMethod'),
/// ).first ==
/// [
/// {1: 42}
/// ]
/// ```
Iterable<({Map<String, Object?> named, List<Object?> positional})>
constArgumentsFor(Identifier identifier) {
return _recordings.calls[identifier]?.whereType<CallWithArguments>().map(
(call) => (
named: call.namedArguments.map(
(name, argument) => MapEntry(name, argument?.toValue()),
),
positional:
call.positionalArguments
.map((argument) => argument?.toValue())
.toList(),
),
) ??
[];
}
/// Finds all constant fields of a const instance of the class [identifier].
///
/// The definition must be annotated with `@RecordUse()`. If there are
/// no instances of the definition, either because it was treeshaken, because
/// it was not annotated, or because it does not exist, returns empty.
///
/// The types of fields supported are defined at
///
/// Example:
/// ```dart
/// void main() {
/// print(SomeClass.someStaticMethod(42));
/// }
///
/// class SomeClass {
/// @AnnotationClass('freddie')
/// static someStaticMethod(int i) {
/// return i + 1;
/// }
/// }
///
/// @RecordUse()
/// class AnnotationClass {
/// final String s;
/// const AnnotationClass(this.s);
/// }
/// ```
///
/// Would mean that
/// ```
/// constantsOf(Identifier(
/// uri: 'path/to/file.dart',
/// name: 'AnnotationClass'),
/// ).first['s'] == 'freddie';
/// ```
///
/// What kinds of fields can be recorded depends on the implementation of
/// https://dart-review.googlesource.com/c/sdk/+/369620/13/pkg/vm/lib/transformations/record_use/record_instance.dart
Iterable<ConstantInstance> constantsOf(Identifier identifier) {
return _recordings.instances[identifier]?.map(
(reference) => ConstantInstance(reference.instanceConstant.fields),
) ??
[];
}
/// Checks if any call to [identifier] has non-const arguments, or if any
/// tear-off was recorded.
///
/// The definition must be annotated with `@RecordUse()`. If there are no
/// calls to the definition, either because it was treeshaken, because it was
/// not annotated, or because it does not exist, returns `false`.
bool hasNonConstArguments(Identifier identifier) {
return (_recordings.calls[identifier] ?? []).any(
(element) => switch (element) {
CallTearOff() => true,
CallWithArguments call => call.positionalArguments.any(
(argument) => argument == null,
),
},
);
}
}
extension type ConstantInstance(Map<String, Constant> _fields) {
bool hasField(String key) => _fields.containsKey(key);
Object? operator [](String key) {
if (!hasField(key)) {
throw ArgumentError('No field with name $key found.');
}
return _fields[key]!.toValue();
}
}
@@ -1,13 +0,0 @@
import 'dart:convert' show jsonDecode;
import 'dart:io' show File;
import '../record_use_internal.dart' show RecordedUsages;
RecordedUsages? parseFromFile(Uri? recordedUsagesFile) {
if (recordedUsagesFile == null) {
return null;
}
final usagesContent = File.fromUri(recordedUsagesFile).readAsStringSync();
final usagesJson = jsonDecode(usagesContent) as Map<String, dynamic>;
return RecordedUsages.fromJson(usagesJson);
}
-259
View File
@@ -1,259 +0,0 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
// Copyright (c) 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:convert';
import 'constant.dart';
import 'definition.dart';
import 'helper.dart';
import 'identifier.dart';
import 'location.dart' show Location;
import 'metadata.dart';
import 'reference.dart';
/// [Recordings] combines recordings of calls and instances with metadata.
///
/// This class acts as the top-level container for recorded usage information.
/// The metadata provides context for the recording, such as version and
/// commentary. The [callsForDefinition] and [instancesForDefinition] store the
/// core data, associating each [Definition] with its corresponding [Reference]
/// details.
///
/// The class uses a normalized JSON format, allowing the reuse of locations and
/// constants across multiple recordings to optimize storage.
class Recordings {
/// [Metadata] such as the recording protocol version.
final Metadata metadata;
/// The collected [CallReference]s for each [Definition].
final Map<Definition, List<CallReference>> callsForDefinition;
late final Map<Identifier, List<CallReference>> calls = callsForDefinition
.map((definition, calls) => MapEntry(definition.identifier, calls));
/// The collected [InstanceReference]s for each [Definition].
final Map<Definition, List<InstanceReference>> instancesForDefinition;
late final Map<Identifier, List<InstanceReference>> instances =
instancesForDefinition.map(
(definition, instances) => MapEntry(definition.identifier, instances),
);
static const _metadataKey = 'metadata';
static const _constantsKey = 'constants';
static const _locationsKey = 'locations';
static const _recordingsKey = 'recordings';
static const _callsKey = 'calls';
static const _instancesKey = 'instances';
static const _definitionKey = 'definition';
Recordings({
required this.metadata,
required this.callsForDefinition,
required this.instancesForDefinition,
});
/// Decodes a JSON representation into a [Recordings] object.
///
/// The format is specifically designed to reduce redundancy and improve
/// efficiency. Identifiers and constants are stored in separate tables,
/// allowing them to be referenced by index in the `recordings` map.
factory Recordings.fromJson(Map<String, Object?> json) {
if (json case {
_constantsKey: List<Object?>? constantJsons,
_locationsKey: List<Object?>? locationJsons,
_recordingsKey: List<Object?>? recordingJsons,
}) {
final constants = <Constant>[];
for (final constantJsonObj in constantJsons ?? []) {
final constantJson = constantJsonObj as Map<String, Object?>;
final constant = Constant.fromJson(constantJson, constants);
if (!constants.contains(constant)) {
constants.add(constant);
}
}
final locations = <Location>[];
for (final locationJsonObj in locationJsons ?? []) {
final locationJson = locationJsonObj as Map<String, Object?>;
final location = Location.fromJson(locationJson);
if (!locations.contains(location)) {
locations.add(location);
}
}
final recordings =
recordingJsons?.whereType<Map<String, Object?>>() ?? [];
final recordedCalls = recordings.where(
(recording) => recording[_callsKey] != null,
);
final recordedInstances = recordings.where(
(recording) => recording[_instancesKey] != null,
);
return Recordings(
metadata: Metadata.fromJson(json[_metadataKey] as Map<String, Object?>),
callsForDefinition: {
for (final recording in recordedCalls)
Definition.fromJson(
recording[_definitionKey] as Map<String, Object?>,
):
(recording[_callsKey] as List)
.map(
(json) => CallReference.fromJson(
json as Map<String, Object?>,
constants,
locations,
),
)
.toList(),
},
instancesForDefinition: {
for (final recording in recordedInstances)
Definition.fromJson(
recording[_definitionKey] as Map<String, Object?>,
):
(recording[_instancesKey] as List)
.map(
(json) => InstanceReference.fromJson(
json as Map<String, Object?>,
constants,
locations,
),
)
.toList(),
},
);
} else {
throw ArgumentError('''
Invalid JSON format for Recordings:
${const JsonEncoder.withIndent(' ').convert(json)}
''');
}
}
/// Encodes this object into a JSON representation.
///
/// This method normalizes identifiers and constants for storage efficiency.
Map<String, Object?> toJson() {
final constants =
{
...callsForDefinition.values
.expand((calls) => calls)
.whereType<CallWithArguments>()
.expand(
(call) => [
...call.positionalArguments,
...call.namedArguments.values,
],
)
.nonNulls,
...instancesForDefinition.values
.expand((instances) => instances)
.expand(
(instance) => {
...instance.instanceConstant.fields.values,
instance.instanceConstant,
},
),
}.flatten().asMapToIndices;
final locations =
{
...callsForDefinition.values
.expand((calls) => calls)
.map((call) => call.location)
.nonNulls,
...instancesForDefinition.values
.expand((instances) => instances)
.map((instance) => instance.location)
.nonNulls,
}.asMapToIndices;
return {
_metadataKey: metadata.json,
if (constants.isNotEmpty)
_constantsKey:
constants.keys
.map((constant) => constant.toJson(constants))
.toList(),
if (locations.isNotEmpty)
_locationsKey:
locations.keys.map((location) => location.toJson()).toList(),
if (callsForDefinition.isNotEmpty || instancesForDefinition.isNotEmpty)
_recordingsKey: [
if (callsForDefinition.isNotEmpty)
...callsForDefinition.entries.map(
(entry) => {
_definitionKey: entry.key.toJson(),
_callsKey:
entry.value
.map((call) => call.toJson(constants, locations))
.toList(),
},
),
if (instancesForDefinition.isNotEmpty)
...instancesForDefinition.entries.map(
(entry) => {
_definitionKey: entry.key.toJson(),
_instancesKey:
entry.value
.map(
(instance) => instance.toJson(constants, locations),
)
.toList(),
},
),
],
};
}
@override
bool operator ==(covariant Recordings other) {
if (identical(this, other)) return true;
return other.metadata == metadata &&
deepEquals(other.callsForDefinition, callsForDefinition) &&
deepEquals(other.instancesForDefinition, instancesForDefinition);
}
@override
int get hashCode => Object.hash(
metadata.hashCode,
deepHash(callsForDefinition),
deepHash(instancesForDefinition),
);
}
extension FlattenConstantsExtension on Iterable<Constant> {
Set<Constant> flatten() {
final constants = <Constant>{};
for (final constant in this) {
depthFirstSearch(constant, constants);
}
return constants;
}
void depthFirstSearch(Constant constant, Set<Constant> collected) {
final children = switch (constant) {
ListConstant<Constant>() => constant.value,
MapConstant<Constant>() => constant.value.values,
InstanceConstant() => constant.fields.values,
_ => <Constant>[],
};
for (final child in children) {
if (!collected.contains(child)) {
depthFirstSearch(child, collected);
}
}
collected.add(constant);
}
}
extension MapifyIterableExtension<T> on Iterable<T> {
/// Transform list to map, faster than using list.indexOf
Map<T, int> get asMapToIndices {
var i = 0;
return {for (final element in this) element: i++};
}
}
-198
View File
@@ -1,198 +0,0 @@
// Copyright (c) 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 'constant.dart';
import 'helper.dart';
import 'identifier.dart';
import 'location.dart' show Location;
const _loadingUnitKey = 'loading_unit';
/// A reference to *something*.
///
/// The something might be a call or an instance, matching a [CallReference] or
/// an [InstanceReference].
/// All references have in common that they occur in a [loadingUnit], which we
/// record to be able to piece together which loading units are "related", for
/// example all needing the same asset.
sealed class Reference {
final String? loadingUnit;
final Location? location;
const Reference({required this.loadingUnit, required this.location});
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is Reference &&
other.loadingUnit == loadingUnit &&
other.location == location;
}
@override
int get hashCode => Object.hash(loadingUnit, location);
Map<String, Object?> toJson(
Map<Constant, int> constants,
Map<Location, int> locations,
) => {_loadingUnitKey: loadingUnit, _locationKey: locations[location]};
}
const _locationKey = '@';
const _positionalKey = 'positional';
const _namedKey = 'named';
const _typeKey = 'type';
/// A reference to a call to some [Identifier].
///
/// This might be an actual call, in which case we record the arguments, or a
/// tear-off, in which case we can't record the arguments.
sealed class CallReference extends Reference {
const CallReference({required super.loadingUnit, required super.location});
static CallReference fromJson(
Map<String, Object?> json,
List<Constant> constants,
List<Location> locations,
) {
final loadingUnit = json[_loadingUnitKey] as String?;
final locationIndex = json[_locationKey] as int?;
final location = locationIndex == null ? null : locations[locationIndex];
return json[_typeKey] == 'tearoff'
? CallTearOff(loadingUnit: loadingUnit, location: location)
: CallWithArguments(
positionalArguments:
(json[_positionalKey] as List<dynamic>? ?? [])
.whereType<int?>()
.map(
(constantsIndex) =>
constantsIndex != null
? constants[constantsIndex]
: null,
)
.toList(),
namedArguments: (json[_namedKey] as Map<String, Object?>? ?? {})
.map((key, value) => MapEntry(key, value as int?))
.map(
(name, constantsIndex) => MapEntry(
name,
constantsIndex != null ? constants[constantsIndex] : null,
),
),
loadingUnit: loadingUnit,
location: location,
);
}
}
/// A reference to a call to some [Identifier] with [positionalArguments] and
/// [namedArguments].
final class CallWithArguments extends CallReference {
final List<Constant?> positionalArguments;
final Map<String, Constant?> namedArguments;
const CallWithArguments({
required this.positionalArguments,
required this.namedArguments,
required super.loadingUnit,
required super.location,
});
@override
Map<String, Object?> toJson(
Map<Constant, int> constants,
Map<Location, int> locations,
) {
final positionalJson =
positionalArguments.map((constant) => constants[constant]).toList();
final namedJson = namedArguments.map(
(name, constant) => MapEntry(name, constants[constant]),
);
return {
_typeKey: 'with_arguments',
if (positionalJson.isNotEmpty) _positionalKey: positionalJson,
if (namedJson.isNotEmpty) _namedKey: namedJson,
...super.toJson(constants, locations),
};
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
if (!(super == other)) return false;
return other is CallWithArguments &&
deepEquals(other.positionalArguments, positionalArguments) &&
deepEquals(other.namedArguments, namedArguments);
}
@override
int get hashCode => Object.hash(
deepHash(positionalArguments),
deepHash(namedArguments),
super.hashCode,
);
}
/// A reference to a tear-off use of the [Identifier]. This means that we can't
/// record the arguments possibly passed to the method somewhere else.
final class CallTearOff extends CallReference {
const CallTearOff({required super.loadingUnit, required super.location});
@override
Map<String, Object?> toJson(
Map<Constant, int> constants,
Map<Location, int> locations,
) {
return {_typeKey: 'tearoff', ...super.toJson(constants, locations)};
}
}
final class InstanceReference extends Reference {
final InstanceConstant instanceConstant;
const InstanceReference({
required this.instanceConstant,
required super.loadingUnit,
required super.location,
});
static const _constantKey = 'constant_index';
factory InstanceReference.fromJson(
Map<String, Object?> json,
List<Constant> constants,
List<Location> locations,
) {
final locationIndex = json[_locationKey] as int?;
return InstanceReference(
instanceConstant:
constants[json[_constantKey] as int] as InstanceConstant,
loadingUnit: json[_loadingUnitKey] as String?,
location: locationIndex == null ? null : locations[locationIndex],
);
}
@override
Map<String, Object?> toJson(
Map<Constant, int> constants,
Map<Location, int> locations,
) => {
_constantKey: constants[instanceConstant]!,
...super.toJson(constants, locations),
};
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
if (!(super == other)) return false;
return other is InstanceReference &&
other.instanceConstant == instanceConstant;
}
@override
int get hashCode => Object.hash(instanceConstant, super.hashCode);
}
-7
View File
@@ -1,7 +0,0 @@
// Copyright (c) 2025, 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:pub_semver/pub_semver.dart' show Version;
final version = Version(0, 4, 0);
-19
View File
@@ -1,19 +0,0 @@
name: record_use
description: >
The serialization logic and API for the usage recording SDK feature.
version: 0.4.2
repository: https://github.com/dart-lang/sdk/tree/main/pkg/record_use
environment:
sdk: ^3.7.0
resolution: workspace
dependencies:
collection: ^1.18.0
pub_semver: ^2.1.4
dev_dependencies:
dart_flutter_team_lints: any
lints: any
test: any
-42
View File
@@ -1,42 +0,0 @@
// Copyright (c) 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:convert';
import 'package:record_use/record_use_internal.dart';
import 'package:test/test.dart';
import 'test_data.dart';
void main() {
group('object 1', () {
final json = jsonDecode(recordedUsesJson) as Map<String, Object?>;
test('JSON', () => expect(recordedUses.toJson(), json));
test('Object', () => expect(Recordings.fromJson(json), recordedUses));
test('Json->Object->Json', () {
expect(Recordings.fromJson(json).toJson(), json);
});
test('Object->Json->Object', () {
expect(Recordings.fromJson(recordedUses.toJson()), recordedUses);
});
});
group('object 2', () {
final json2 = jsonDecode(recordedUsesJson2) as Map<String, Object?>;
test('JSON', () => expect(recordedUses2.toJson(), json2));
test('Object', () => expect(Recordings.fromJson(json2), recordedUses2));
test('Json->Object->Json', () {
expect(Recordings.fromJson(json2).toJson(), json2);
});
test('Object->Json->Object', () {
expect(Recordings.fromJson(recordedUses2.toJson()), recordedUses2);
});
});
}
-326
View File
@@ -1,326 +0,0 @@
// Copyright (c) 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 'package:pub_semver/pub_semver.dart';
import 'package:record_use/record_use_internal.dart';
final callId = Identifier(
importUri:
Uri.parse(
'file://lib/_internal/js_runtime/lib/js_helper.dart',
).toString(),
scope: 'MyClass',
name: 'get:loadDeferredLibrary',
);
final instanceId = Identifier(
importUri:
Uri.parse(
'file://lib/_internal/js_runtime/lib/js_helper.dart',
).toString(),
name: 'MyAnnotation',
);
final recordedUses = Recordings(
metadata: Metadata.fromJson({
'version': Version(1, 6, 2, pre: 'wip', build: '5.-.2.z').toString(),
'comment':
'Recorded references at compile time and their argument values, as'
' far as known, to definitions annotated with @RecordUse',
}),
callsForDefinition: {
Definition(identifier: callId, loadingUnit: 'part_15.js'): [
const CallWithArguments(
positionalArguments: [
StringConstant('lib_SHA1'),
BoolConstant(false),
IntConstant(1),
],
namedArguments: {
'freddy': StringConstant('mercury'),
'leroy': StringConstant('jenkins'),
},
loadingUnit: 'o.js',
location: Location(uri: 'lib/test.dart'),
),
const CallWithArguments(
positionalArguments: [
StringConstant('lib_SHA1'),
MapConstant<IntConstant>({'key': IntConstant(99)}),
ListConstant([
StringConstant('camus'),
ListConstant([
StringConstant('einstein'),
StringConstant('insert'),
BoolConstant(false),
]),
StringConstant('einstein'),
]),
],
namedArguments: {
'freddy': IntConstant(0),
'leroy': StringConstant('jenkins'),
},
loadingUnit: 'o.js',
location: Location(uri: 'lib/test2.dart'),
),
],
},
instancesForDefinition: {
Definition(identifier: instanceId): [
const InstanceReference(
instanceConstant: InstanceConstant(
fields: {'a': IntConstant(42), 'b': NullConstant()},
),
loadingUnit: '3',
location: Location(uri: 'lib/test3.dart'),
),
const InstanceReference(
instanceConstant: InstanceConstant(fields: {}),
loadingUnit: '3',
location: Location(uri: 'lib/test3.dart'),
),
],
},
);
final recordedUses2 = Recordings(
metadata: Metadata.fromJson({
'version': Version(1, 6, 2, pre: 'wip', build: '5.-.2.z').toString(),
'comment':
'Recorded references at compile time and their argument values, as'
' far as known, to definitions annotated with @RecordUse',
}),
callsForDefinition: {
Definition(identifier: callId, loadingUnit: 'part_15.js'): [
const CallWithArguments(
positionalArguments: [BoolConstant(false), IntConstant(1)],
namedArguments: {
'freddy': StringConstant('mercury'),
'answer': IntConstant(42),
},
loadingUnit: 'o.js',
location: Location(uri: 'lib/test3.dart'),
),
],
},
instancesForDefinition: {},
);
final recordedUsesJson = '''{
"metadata": {
"version": "1.6.2-wip+5.-.2.z",
"comment": "Recorded references at compile time and their argument values, as far as known, to definitions annotated with @RecordUse"
},
"constants": [
{
"type": "String",
"value": "lib_SHA1"
},
{
"type": "bool",
"value": false
},
{
"type": "int",
"value": 1
},
{
"type": "String",
"value": "mercury"
},
{
"type": "String",
"value": "jenkins"
},
{
"type": "int",
"value": 99
},
{
"type": "map",
"value": {
"key": 5
}
},
{
"type": "String",
"value": "camus"
},
{
"type": "String",
"value": "einstein"
},
{
"type": "String",
"value": "insert"
},
{
"type": "list",
"value": [
8,
9,
1
]
},
{
"type": "list",
"value": [
7,
10,
8
]
},
{
"type": "int",
"value": 0
},
{
"type": "int",
"value": 42
},
{
"type": "Null"
},
{
"type": "Instance",
"value": {
"a": 13,
"b": 14
}
},
{
"type": "Instance"
}
],
"locations": [
{
"uri": "lib/test.dart"
},
{
"uri": "lib/test2.dart"
},
{
"uri": "lib/test3.dart"
}
],
"recordings": [
{
"definition": {
"identifier": {
"uri": "file://lib/_internal/js_runtime/lib/js_helper.dart",
"scope": "MyClass",
"name": "get:loadDeferredLibrary"
},
"loading_unit": "part_15.js"
},
"calls": [
{
"type": "with_arguments",
"positional": [
0,
1,
2
],
"named": {
"freddy": 3,
"leroy": 4
},
"loading_unit": "o.js",
"@": 0
},
{
"type": "with_arguments",
"positional": [
0,
6,
11
],
"named": {
"freddy": 12,
"leroy": 4
},
"loading_unit": "o.js",
"@": 1
}
]
},
{
"definition": {
"identifier": {
"uri": "file://lib/_internal/js_runtime/lib/js_helper.dart",
"name": "MyAnnotation"
}
},
"instances": [
{
"constant_index": 15,
"loading_unit": "3",
"@": 2
},
{
"constant_index": 16,
"loading_unit": "3",
"@": 2
}
]
}
]
}''';
final recordedUsesJson2 = '''{
"metadata": {
"version": "1.6.2-wip+5.-.2.z",
"comment": "Recorded references at compile time and their argument values, as far as known, to definitions annotated with @RecordUse"
},
"constants": [
{
"type": "bool",
"value": false
},
{
"type": "int",
"value": 1
},
{
"type": "String",
"value": "mercury"
},
{
"type": "int",
"value": 42
}
],
"locations": [
{
"uri": "lib/test3.dart"
}
],
"recordings": [
{
"definition": {
"identifier": {
"uri": "file://lib/_internal/js_runtime/lib/js_helper.dart",
"scope": "MyClass",
"name": "get:loadDeferredLibrary"
},
"loading_unit": "part_15.js"
},
"calls": [
{
"type": "with_arguments",
"positional": [
0,
1
],
"named": {
"freddy": 2,
"answer": 3
},
"loading_unit": "o.js",
"@": 0
}
]
}
]
}''';
-127
View File
@@ -1,127 +0,0 @@
// Copyright (c) 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:convert';
import 'package:record_use/record_use_internal.dart';
import 'package:test/test.dart';
import 'test_data.dart';
void main() {
test('All API calls', () {
expect(
RecordedUsages.fromJson(
jsonDecode(recordedUsesJson) as Map<String, Object?>,
)
.constArgumentsFor(
Identifier(
importUri:
Uri.parse(
'file://lib/_internal/js_runtime/lib/js_helper.dart',
).toString(),
scope: 'MyClass',
name: 'get:loadDeferredLibrary',
),
)
.length,
2,
);
});
test('All API instances', () {
final instance =
RecordedUsages.fromJson(
jsonDecode(recordedUsesJson) as Map<String, Object?>,
)
.constantsOf(
Identifier(
importUri:
Uri.parse(
'file://lib/_internal/js_runtime/lib/js_helper.dart',
).toString(),
name: 'MyAnnotation',
),
)
.first;
final instanceMap =
recordedUses.instancesForDefinition.values
.expand((usage) => usage)
.map(
(instance) => instance.instanceConstant.fields.map(
(key, constant) => MapEntry(key, constant.toValue()),
),
)
.first;
for (final entry in instanceMap.entries) {
expect(instance[entry.key], entry.value);
}
});
test('Specific API calls', () {
var arguments =
RecordedUsages.fromJson(
jsonDecode(recordedUsesJson) as Map<String, Object?>,
)
.constArgumentsFor(
Identifier(
importUri:
Uri.parse(
'file://lib/_internal/js_runtime/lib/js_helper.dart',
).toString(),
scope: 'MyClass',
name: 'get:loadDeferredLibrary',
),
)
.toList();
var (named: named0, positional: positional0) = arguments[0];
expect(named0, const {'freddy': 'mercury', 'leroy': 'jenkins'});
expect(positional0, const ['lib_SHA1', false, 1]);
var (named: named1, positional: positional1) = arguments[1];
expect(named1, const {'freddy': 0, 'leroy': 'jenkins'});
expect(positional1, const [
'lib_SHA1',
{'key': 99},
[
'camus',
['einstein', 'insert', false],
'einstein',
],
]);
});
test('Specific API instances', () {
final instance =
RecordedUsages.fromJson(
jsonDecode(recordedUsesJson) as Map<String, Object?>,
)
.constantsOf(
Identifier(
importUri:
Uri.parse(
'file://lib/_internal/js_runtime/lib/js_helper.dart',
).toString(),
name: 'MyAnnotation',
),
)
.first;
expect(instance['a'], 42);
expect(instance['b'], null);
});
test('HasNonConstInstance', () {
expect(
RecordedUsages.fromJson(
jsonDecode(recordedUsesJson2) as Map<String, Object?>,
).hasNonConstArguments(
const Identifier(
importUri:
'package:drop_dylib_recording/src/drop_dylib_recording.dart',
name: 'getMathMethod',
),
),
false,
);
});
}
@@ -1 +0,0 @@
bin/drop_data_asset*/
@@ -1,17 +0,0 @@
# Tree-shaking Data Assets with Record Use
This sample demonstrates how the `record-use` feature can be utilized to
tree-shake (remove) unused data assets from the build output.
## Usage
The `record-use` and `native-assets` experiments need to be enabled.
### JS
Run either `dart compile js --write-resources bin/drop_data_asset_calls.dart` or `dart compile js --write-resources bin/drop_data_asset_instances.dart`.
### Native
Run either `dart --enable-experiment=native-assets,record-use build bin/drop_data_asset_calls.dart` or `dart --enable-experiment=native-assets,record-use build bin/drop_data_asset_instances.dart`.
@@ -1 +0,0 @@
leroy
@@ -1 +0,0 @@
freddy
@@ -1 +0,0 @@
jenkins
@@ -1 +0,0 @@
mercury
@@ -1,9 +0,0 @@
// Copyright (c) 2025, 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:drop_data_asset/drop_data_asset.dart';
void main(List<String> arguments) {
print('Hello world: ${MyMath.add(3, 4)}!');
}
@@ -1,9 +0,0 @@
// Copyright (c) 2025, 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:drop_data_asset/drop_data_asset.dart';
void main(List<String> arguments) {
print('Hello world: ${MyMath.double(3)}!');
}
@@ -1,24 +0,0 @@
// Copyright (c) 2025, 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:data_assets/data_assets.dart';
import 'package:hooks/hooks.dart';
void main(List<String> arguments) async {
await build(arguments, (input, output) async {
for (var element in ['add', 'multiply', 'double', 'square']) {
output.assets.data.add(
DataAsset(
file: input.packageRoot.resolve('assets/$element.txt'),
name: element,
package: input.packageName,
),
routing:
input.config.linkingEnabled
? ToLinkHook(input.packageName)
: const ToAppBundle(),
);
}
});
}
@@ -1,79 +0,0 @@
// Copyright (c) 2025, 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 'dart:io';
import 'package:data_assets/data_assets.dart';
import 'package:hooks/hooks.dart';
import 'package:record_use/record_use.dart';
void main(List<String> arguments) async {
await link(arguments, (input, output) async {
final recordedUsagesFile = input.recordedUsagesFile;
if (recordedUsagesFile == null) {
throw ArgumentError(
'Enable the --enable-experiments=record-use experiment to use this app.',
);
}
final usages = await recordedUsages(recordedUsagesFile);
final dataAssets = input.assets.data;
print('Received assets: ${dataAssets.map((a) => a.id).join(', ')}.');
final symbols = <String>{};
// Tree-shake unused assets using calls
for (final methodName in ['add', 'multiply']) {
final calls = usages.constArgumentsFor(
Identifier(
importUri:
'package:${input.packageName}/src/${input.packageName}.dart',
scope: 'MyMath',
name: methodName,
),
'int add(int a, int b)',
);
print('Checking calls to $methodName...');
for (final call in calls) {
print(
'A call was made to "$methodName" with the arguments ('
'${call.positional[0] as int},${call.positional[1] as int})',
);
symbols.add(methodName);
}
}
// Tree-shake unused assets
final instances = usages.constantsOf(
Identifier(
importUri: 'package:${input.packageName}/src/${input.packageName}.dart',
name: 'RecordCallToC',
),
);
for (final instance in instances) {
final symbol = instance['symbol'] as String;
print('An instance of "$instance" was found with the field "$symbol"');
symbols.add(symbol);
}
final neededCodeAssets = [
for (final asset in dataAssets)
if (symbols.any(asset.id.endsWith)) asset,
];
print('Keeping only ${neededCodeAssets.map((e) => e.id).join(', ')}.');
output.assets.data.addAll(neededCodeAssets);
output.addDependency(recordedUsagesFile);
});
}
Future<RecordedUsages> recordedUsages(Uri recordedUsagesFile) async {
final file = File.fromUri(recordedUsagesFile);
final string = await file.readAsString();
final usages = RecordedUsages.fromJson(
jsonDecode(string) as Map<String, Object?>,
);
return usages;
}
@@ -1,5 +0,0 @@
// Copyright (c) 2025, 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.
export 'src/drop_data_asset.dart';
@@ -1,26 +0,0 @@
// Copyright (c) 2025, 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:meta/meta.dart';
class MyMath {
@RecordUse()
static int add(int a, int b) => a + b;
@RecordUse()
static int multiply(int a, int b) => a * b;
@RecordCallToC('double')
static int double(int a) => a + a;
@RecordCallToC('square')
static int square(int a) => a * a;
}
@RecordUse()
class RecordCallToC {
final String symbol;
const RecordCallToC(this.symbol);
}
@@ -1,27 +0,0 @@
name: drop_data_asset
description: Add four data assets, remove three in linking based on recorded usage.
publish_to: none
environment:
sdk: ^3.7.0
dependencies:
hooks: any
data_assets: any
logging: any
meta: any
record_use:
path: ../../../record_use/
dev_dependencies:
lints: any
test: any
dependency_overrides:
meta:
path: ../../../meta/
hooks:
path: ../../../../third_party/pkg/native/pkgs/hooks/
data_assets:
path: ../../../../third_party/pkg/native/pkgs/data_assets/
@@ -1 +0,0 @@
bin/drop_dylib_recording*/
@@ -1,35 +0,0 @@
This sample builds a native library for adding and multiplying. It then uses
the recorded usages feature to tree-shake unused libraries out.
## Usage:
### Keep all:
```
dart --enable-experiment=record-use build bin/drop_dylib_recording_all.dart
```
The `lib/` folder now contains both libraries
```
./bin/drop_dylib_recording_all/drop_dylib_recording_all.exe add
```
Prints `Hello world: 7!`
### Treeshake using calls:
```
dart --enable-experiment=record-use build bin/drop_dylib_recording_calls.dart
```
The `lib/` folder now contains only the `add` library.
```
./bin/drop_dylib_recording_calls/drop_dylib_recording_calls.exe
```
Prints `Hello world: 7!`
### Treeshake using instances:
```
dart --enable-experiment=record-use build bin/drop_dylib_recording_instances.dart
```
The `lib/` folder now contains only the `add` library.
```
./bin/drop_dylib_recording_calls/drop_dylib_recording_instances.exe
```
Prints `Hello world: 7!`
@@ -1,9 +0,0 @@
// Copyright (c) 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 'package:drop_dylib_recording/drop_dylib_recording.dart';
void main(List<String> arguments) {
print('Hello world: ${MyMath.add(3, 4)}!');
}
@@ -1,9 +0,0 @@
// Copyright (c) 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 'package:drop_dylib_recording/drop_dylib_recording.dart';
void main(List<String> arguments) {
print('Hello world: ${MyMath.double(3)}!');
}
@@ -1,36 +0,0 @@
// Copyright (c) 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 'package:logging/logging.dart';
import 'package:hooks/hooks.dart';
import 'package:code_assets/code_assets.dart';
import 'package:native_toolchain_c/native_toolchain_c.dart';
void main(List<String> arguments) async {
await build(arguments, (input, output) async {
final logger =
Logger('')
..level = Level.ALL
..onRecord.listen((record) {
print('${record.level.name}: ${record.time}: ${record.message}');
});
final List<AssetRouting> routing =
input.config.linkingEnabled
? [ToLinkHook(input.packageName)]
: const [ToAppBundle()];
await CBuilder.library(
name: 'add',
assetName: 'dylib_add',
sources: ['src/native_add.c'],
linkModePreference: LinkModePreference.dynamic,
).run(input: input, output: output, logger: logger, routing: routing);
await CBuilder.library(
name: 'multiply',
assetName: 'dylib_multiply',
sources: ['src/native_multiply.c'],
linkModePreference: LinkModePreference.dynamic,
).run(input: input, output: output, logger: logger, routing: routing);
});
}
@@ -1,82 +0,0 @@
// Copyright (c) 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:convert';
import 'dart:io';
import 'package:hooks/hooks.dart';
import 'package:code_assets/code_assets.dart';
import 'package:record_use/record_use.dart';
void main(List<String> arguments) async {
await link(arguments, (input, output) async {
final recordedUsagesFile = input.recordedUsagesFile;
if (recordedUsagesFile == null) {
throw ArgumentError(
'Enable the --enable-experiments=record-use experiment to use this app.',
);
}
final usages = await recordedUsages(recordedUsagesFile);
final codeAssets = input.assets.code;
print('Received assets: ${codeAssets.map((a) => a.id).join(', ')}.');
final symbols = <String>{};
final argumentsFile = await File.fromUri(
input.outputDirectory.resolve('arguments.txt'),
).create();
final dataLines = <String>[];
// Tree-shake unused assets using calls
for (final methodName in ['add', 'multiply']) {
final calls = usages.constArgumentsFor(
Identifier(
importUri:
'package:drop_dylib_recording/src/drop_dylib_recording.dart',
scope: 'MyMath',
name: methodName,
),
);
for (var call in calls) {
dataLines.add(
'A call was made to "$methodName" with the arguments ('
'${call.positional[0] as int},${call.positional[1] as int})',
);
symbols.add(methodName);
}
}
argumentsFile.writeAsStringSync(dataLines.join('\n'));
// Tree-shake unused assets
final instances = usages.constantsOf(
Identifier(
importUri: 'package:drop_dylib_recording/src/drop_dylib_recording.dart',
name: 'RecordCallToC',
),
);
for (final instance in instances) {
final symbol = instance['symbol'] as String;
symbols.add(symbol);
}
final neededCodeAssets = [
for (final codeAsset in codeAssets)
if (symbols.any(codeAsset.id.endsWith)) codeAsset,
];
print('Keeping only ${neededCodeAssets.map((e) => e.id).join(', ')}.');
output.assets.code.addAll(neededCodeAssets);
output.addDependency(recordedUsagesFile);
});
}
Future<RecordedUsages> recordedUsages(Uri recordedUsagesFile) async {
final file = File.fromUri(recordedUsagesFile);
final string = await file.readAsString();
final usages = RecordedUsages.fromJson(
jsonDecode(string) as Map<String, Object?>,
);
return usages;
}
@@ -1,5 +0,0 @@
// Copyright (c) 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.
export 'src/drop_dylib_recording.dart';
@@ -1,28 +0,0 @@
// Copyright (c) 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 'package:meta/meta.dart';
import 'drop_dylib_recording_bindings.dart' as bindings;
class MyMath {
@RecordUse()
static int add(int a, int b) => bindings.add(a, b);
@RecordUse()
static int multiply(int a, int b) => bindings.multiply(a, b);
@RecordCallToC('add')
static int double(int a) => bindings.add(a, a);
@RecordCallToC('multiply')
static int square(int a) => bindings.multiply(a, a);
}
@RecordUse()
class RecordCallToC {
final String symbol;
const RecordCallToC(this.symbol);
}
@@ -1,15 +0,0 @@
// Copyright (c) 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:ffi' as ffi;
@ffi.Native<ffi.Int32 Function(ffi.Int32, ffi.Int32)>(
assetId: 'package:drop_dylib_recording/dylib_add',
)
external int add(int a, int b);
@ffi.Native<ffi.Int32 Function(ffi.Int32, ffi.Int32)>(
assetId: 'package:drop_dylib_recording/dylib_multiply',
)
external int multiply(int a, int b);
@@ -1,28 +0,0 @@
name: drop_dylib_recording
description: Generate two dylibs, remove one in linking based on recorded usage.
version: 1.0.0
publish_to: none
environment:
sdk: ^3.0.0
dependencies:
logging: ^1.1.1
meta: any
hooks:
path: ../../../../third_party/pkg/native/pkgs/hooks/
code_assets:
path: ../../../../third_party/pkg/native/pkgs/code_assets/
native_toolchain_c:
path: ../../../../third_party/pkg/native/pkgs/native_toolchain_c/
record_use:
path: ../../../record_use/
dev_dependencies:
lints: any
test: any
dependency_overrides:
meta:
path: ../../../meta/
@@ -1,9 +0,0 @@
// Copyright (c) 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.
#include "native_add.h"
MYLIB_EXPORT int32_t add(int32_t a, int32_t b) {
return a + b;
}
@@ -1,13 +0,0 @@
// Copyright (c) 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.
#include <stdint.h>
#if _WIN32
#define MYLIB_EXPORT __declspec(dllexport)
#else
#define MYLIB_EXPORT
#endif
MYLIB_EXPORT int32_t add(int32_t a, int32_t b);
@@ -1,9 +0,0 @@
// Copyright (c) 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.
#include "native_multiply.h"
MYLIB_EXPORT intptr_t multiply(intptr_t a, intptr_t b) {
return a * b;
}
@@ -1,13 +0,0 @@
// Copyright (c) 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.
#include <stdint.h>
#if _WIN32
#define MYLIB_EXPORT __declspec(dllexport)
#else
#define MYLIB_EXPORT
#endif
MYLIB_EXPORT intptr_t multiply(intptr_t a, intptr_t b);
-24
View File
@@ -1,24 +0,0 @@
- drop_dylib_recording/pubspec.yaml
- drop_dylib_recording/lib/src/drop_dylib_recording_bindings.dart
- drop_dylib_recording/lib/src/drop_dylib_recording.dart
- drop_dylib_recording/lib/drop_dylib_recording.dart
- drop_dylib_recording/src/native_add.h
- drop_dylib_recording/src/native_multiply.h
- drop_dylib_recording/src/native_add.c
- drop_dylib_recording/src/native_multiply.c
- drop_dylib_recording/hook/link.dart
- drop_dylib_recording/hook/build.dart
- drop_dylib_recording/bin/drop_dylib_recording_calls.dart
- drop_dylib_recording/bin/drop_dylib_recording_instances.dart
- drop_data_asset/pubspec.yaml
- drop_data_asset/assets/double.txt
- drop_data_asset/assets/square.txt
- drop_data_asset/assets/add.txt
- drop_data_asset/assets/multiply.txt
- drop_data_asset/lib/drop_data_asset.dart
- drop_data_asset/lib/src/drop_data_asset.dart
- drop_data_asset/README.md
- drop_data_asset/hook/link.dart
- drop_data_asset/hook/build.dart
- drop_data_asset/bin/drop_data_asset_instances.dart
- drop_data_asset/bin/drop_data_asset_calls.dart
@@ -46,6 +46,7 @@ final testSuiteDirectories = [
Path('third_party/pkg/native/pkgs/hooks_runner'),
Path('third_party/pkg/native/pkgs/hooks'),
Path('third_party/pkg/native/pkgs/native_toolchain_c'),
Path('third_party/pkg/native/pkgs/record_use'),
Path('third_party/pkg/package_config'),
Path('utils/tests/peg'),
];
+2 -1
View File
@@ -63,7 +63,6 @@ workspace:
- pkg/native_compiler
- pkg/native_stack_traces
- pkg/node_preamble
- pkg/record_use
- pkg/reload_test
- pkg/scrape
- pkg/server_plugin
@@ -227,6 +226,8 @@ dependency_overrides:
path: third_party/pkg/native/pkgs/pub_formats
pub_semver:
path: third_party/pkg/tools/pkgs/pub_semver
record_use:
path: third_party/pkg/native/pkgs/record_use
regression_tests:
path: third_party/pkg/test/integration_tests/regression
shelf:
+1
View File
@@ -28,6 +28,7 @@
!/pkg/hooks_runner.status
!/pkg/hooks.status
!/pkg/native_toolchain_c.status
!/pkg/record_use.status
# These packages are authored in third_party.
!/pkg/dap
+8
View File
@@ -0,0 +1,8 @@
# Copyright (c) 2025, 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.
example/*: SkipByDesign # These test projects don't work without running pub in the project itself.
lib/src/*: SkipByDesign # These are not tests.
test/json_schema/*: SkipByDesign # Dev dependency not in Dart SDK.
+2 -1
View File
@@ -183,7 +183,6 @@
"pkg/kernel/",
"pkg/meta/",
"pkg/native_stack_traces/",
"pkg/hooks_runner/",
"pkg/pkg.status",
"pkg/smith/",
"pkg/status_file/",
@@ -3101,6 +3100,7 @@
"hooks_runner",
"hooks",
"native_toolchain_c",
"record_use",
"package_config",
"pkg/pkg/dartdev/test/native_assets"
]
@@ -3228,6 +3228,7 @@
"hooks_runner",
"hooks",
"native_toolchain_c",
"record_use",
"package_config",
"pkg/pkg/dartdev/test/native_assets"
]