Skip to content

Commit da95aca

Browse files
authored
Data assets (flutter#169273)
Refiling of flutter#164094, which itself is a rebase of flutter#159675 This PR adds bundling support for the experimental dart data asset feature: Dart packages with hooks can now emit data assets which the flutter tool will bundle. It relies on flutter's existing asset bundling mechanism (e.g. entries in AssetManifest.json, DevFS syncing in reload/restart, ...). The support is added under an experimental flag (similar to the existing native assets experimental flag). Also, kNativeAssets is removed to also bundle data assets on flutter build bundle. The chrome sandbox is disabled as per flutter#165664. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
1 parent 0064fba commit da95aca

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1806
-592
lines changed

packages/flutter_tools/lib/executable.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import 'src/base/template.dart';
1111
import 'src/base/terminal.dart';
1212
import 'src/base/user_messages.dart';
1313
import 'src/build_system/build_targets.dart';
14+
import 'src/build_system/targets/hook_runner_native.dart' show FlutterHookRunnerNative;
1415
import 'src/cache.dart';
1516
import 'src/commands/analyze.dart';
1617
import 'src/commands/assemble.dart';
@@ -48,6 +49,7 @@ import 'src/devtools_launcher.dart';
4849
import 'src/features.dart';
4950
import 'src/globals.dart' as globals;
5051
// Files in `isolated` are intentionally excluded from google3 tooling.
52+
import 'src/hook_runner.dart' show FlutterHookRunner;
5153
import 'src/isolated/build_targets.dart';
5254
import 'src/isolated/mustache_template.dart';
5355
import 'src/isolated/native_assets/test/native_assets.dart';
@@ -117,6 +119,7 @@ Future<void> main(List<String> args) async {
117119
muteCommandLogging: muteCommandLogging,
118120
verboseHelp: verboseHelp,
119121
overrides: <Type, Generator>{
122+
FlutterHookRunner: () => FlutterHookRunnerNative(),
120123
// The web runner is not supported in google3 because it depends
121124
// on dwds.
122125
WebRunnerFactory: () => DwdsWebRunnerFactory(),

packages/flutter_tools/lib/src/asset.dart

Lines changed: 127 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,84 @@ import 'flutter_manifest.dart';
2525
import 'license_collector.dart';
2626
import 'project.dart';
2727

28+
class FlutterHookResult {
29+
const FlutterHookResult({
30+
required this.buildStart,
31+
required this.buildEnd,
32+
required this.dataAssets,
33+
required this.dependencies,
34+
});
35+
36+
FlutterHookResult.empty()
37+
: this(
38+
buildStart: DateTime.fromMillisecondsSinceEpoch(0),
39+
buildEnd: DateTime.fromMillisecondsSinceEpoch(0),
40+
dataAssets: <HookAsset>[],
41+
dependencies: <Uri>[],
42+
);
43+
44+
final List<HookAsset> dataAssets;
45+
46+
/// The timestamp at which we start a build - so the timestamp of the inputs.
47+
final DateTime buildStart;
48+
49+
/// The timestamp at which we finish a build - so the timestamp of the
50+
/// outputs.
51+
final DateTime buildEnd;
52+
53+
/// The dependencies of the build are used to check if the build needs to be
54+
/// rerun.
55+
final List<Uri> dependencies;
56+
57+
/// Whether caller may need to re-run the Dart build.
58+
bool hasAnyModifiedFiles(FileSystem fileSystem) =>
59+
_wasAnyFileModifiedSince(fileSystem, buildStart, dependencies);
60+
61+
/// Whether the files produced by the build are up-to-date.
62+
///
63+
/// NOTICE: The build itself may be up-to-date but the output may not be (as
64+
/// the output may be existing on disk and not be produced by the build
65+
/// itself - in which case we may not need to re-build if the file changes,
66+
/// but we may need to make a new asset bundle with the modified file).
67+
bool isOutputDirty(FileSystem fileSystem) => _wasAnyFileModifiedSince(
68+
fileSystem,
69+
buildEnd,
70+
dataAssets.map((HookAsset e) => e.file).toList(),
71+
);
72+
73+
static bool _wasAnyFileModifiedSince(FileSystem fileSystem, DateTime since, List<Uri> uris) {
74+
for (final Uri uri in uris) {
75+
final DateTime modified = fileSystem.statSync(uri.toFilePath()).modified;
76+
if (modified.isAfter(since)) {
77+
return true;
78+
}
79+
}
80+
return false;
81+
}
82+
83+
@override
84+
String toString() {
85+
return dataAssets.toString();
86+
}
87+
}
88+
89+
/// A convenience class to wrap native assets
90+
///
91+
/// When translating from a `DartHooksResult` to a [FlutterHookResult], where we
92+
/// need to have different classes to not import `isolated/` stuff.
93+
class HookAsset {
94+
const HookAsset({required this.file, required this.name, required this.package});
95+
96+
final Uri file;
97+
final String name;
98+
final String package;
99+
100+
@override
101+
String toString() {
102+
return 'HookAsset(file: $file, name: $name, package: $package)';
103+
}
104+
}
105+
28106
const String defaultManifestPath = 'pubspec.yaml';
29107

30108
const String kFontManifestJson = 'FontManifest.json';
@@ -113,6 +191,7 @@ abstract class AssetBundle {
113191

114192
/// Returns 0 for success; non-zero for failure.
115193
Future<int> build({
194+
FlutterHookResult? flutterHookResult,
116195
String manifestPath = defaultManifestPath,
117196
required String packageConfigPath,
118197
bool deferredComponentsEnabled = false,
@@ -163,7 +242,8 @@ class ManifestAssetBundle implements AssetBundle {
163242
_platform = platform,
164243
_flutterRoot = flutterRoot,
165244
_splitDeferredAssets = splitDeferredAssets,
166-
_licenseCollector = LicenseCollector(fileSystem: fileSystem);
245+
_licenseCollector = LicenseCollector(fileSystem: fileSystem),
246+
_lastHookResult = FlutterHookResult.empty();
167247

168248
final Logger _logger;
169249
final FileSystem _fileSystem;
@@ -189,6 +269,8 @@ class ManifestAssetBundle implements AssetBundle {
189269

190270
DateTime? _lastBuildTimestamp;
191271

272+
FlutterHookResult _lastHookResult;
273+
192274
// We assume the main asset is designed for a device pixel ratio of 1.0.
193275
static const String _kAssetManifestJsonFilename = 'AssetManifest.json';
194276
static const String _kAssetManifestBinFilename = 'AssetManifest.bin';
@@ -208,13 +290,19 @@ class ManifestAssetBundle implements AssetBundle {
208290

209291
@override
210292
bool needsBuild({String manifestPath = defaultManifestPath}) {
211-
final DateTime? lastBuildTimestamp = _lastBuildTimestamp;
212-
if (lastBuildTimestamp == null) {
293+
if (!wasBuiltOnce() ||
294+
// We need to re-run the Dart build.
295+
_lastHookResult.hasAnyModifiedFiles(_fileSystem) ||
296+
// We don't have to re-run the Dart build, but some files the Dart build
297+
// wants us to bundle have changed contents.
298+
_lastHookResult.isOutputDirty(_fileSystem)) {
213299
return true;
214300
}
301+
final DateTime lastBuildTimestamp = _lastBuildTimestamp!;
215302

216303
final FileStat manifestStat = _fileSystem.file(manifestPath).statSync();
217-
if (manifestStat.type == FileSystemEntityType.notFound) {
304+
if (manifestStat.type == FileSystemEntityType.notFound ||
305+
manifestStat.modified.isAfter(lastBuildTimestamp)) {
218306
return true;
219307
}
220308

@@ -223,18 +311,19 @@ class ManifestAssetBundle implements AssetBundle {
223311
return true; // directory was deleted.
224312
}
225313
for (final File file in directory.listSync().whereType<File>()) {
226-
final DateTime dateTime = file.statSync().modified;
227-
if (dateTime.isAfter(lastBuildTimestamp)) {
314+
final DateTime lastModified = file.statSync().modified;
315+
if (lastModified.isAfter(lastBuildTimestamp)) {
228316
return true;
229317
}
230318
}
231319
}
232320

233-
return manifestStat.modified.isAfter(lastBuildTimestamp);
321+
return false;
234322
}
235323

236324
@override
237325
Future<int> build({
326+
FlutterHookResult? flutterHookResult,
238327
String manifestPath = defaultManifestPath,
239328
FlutterProject? flutterProject,
240329
required String packageConfigPath,
@@ -258,6 +347,7 @@ class ManifestAssetBundle implements AssetBundle {
258347
// hang on hot reload, as the incremental dill files will never be copied to the
259348
// device.
260349
_lastBuildTimestamp = DateTime.now();
350+
_lastHookResult = flutterHookResult ?? FlutterHookResult.empty();
261351
if (flutterManifest.isEmpty) {
262352
entries[_kAssetManifestJsonFilename] = AssetBundleEntry(
263353
DevFSStringContent('{}'),
@@ -425,9 +515,38 @@ class ManifestAssetBundle implements AssetBundle {
425515
);
426516
}
427517
}
518+
for (final HookAsset dataAsset in flutterHookResult?.dataAssets ?? <HookAsset>[]) {
519+
final Package package = packageConfig[dataAsset.package]!;
520+
final Uri fileUri = dataAsset.file;
521+
522+
final String baseDir;
523+
final Uri relativeUri;
524+
if (fileUri.isAbsolute) {
525+
final String filePath = fileUri.toFilePath();
526+
baseDir = _fileSystem.path.dirname(filePath);
527+
relativeUri = Uri(path: _fileSystem.path.basename(filePath));
528+
} else {
529+
baseDir = package.root.toFilePath();
530+
relativeUri = fileUri;
531+
}
532+
533+
final _Asset asset = _Asset(
534+
baseDir: baseDir,
535+
relativeUri: relativeUri,
536+
entryUri: Uri.parse(_fileSystem.path.join('packages', dataAsset.package, dataAsset.name)),
537+
package: package,
538+
);
539+
if (assetVariants.containsKey(asset)) {
540+
_logger.printError(
541+
'Conflicting assets: The asset "$asset" was declared in the pubspec and the hook.',
542+
);
543+
return 1;
544+
}
545+
assetVariants[asset] = <_Asset>[asset];
546+
}
428547

429548
// Save the contents of each image, image variant, and font
430-
// asset in entries.
549+
// asset in [entries].
431550
for (final _Asset asset in assetVariants.keys) {
432551
final File assetFile = asset.lookupAssetFile(_fileSystem);
433552
final List<_Asset> variants = assetVariants[asset]!;

packages/flutter_tools/lib/src/build_info.dart

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,33 @@ enum TargetPlatform {
590590
}
591591
}
592592

593+
String get osName {
594+
switch (this) {
595+
case TargetPlatform.linux_x64:
596+
case TargetPlatform.linux_arm64:
597+
return 'linux';
598+
case TargetPlatform.darwin:
599+
return 'macos';
600+
case TargetPlatform.windows_x64:
601+
case TargetPlatform.windows_arm64:
602+
return 'windows';
603+
case TargetPlatform.android:
604+
case TargetPlatform.android_arm:
605+
case TargetPlatform.android_arm64:
606+
case TargetPlatform.android_x64:
607+
return 'android';
608+
case TargetPlatform.fuchsia_arm64:
609+
case TargetPlatform.fuchsia_x64:
610+
return 'fuchsia';
611+
case TargetPlatform.ios:
612+
return 'ios';
613+
case TargetPlatform.tester:
614+
return 'flutter-tester';
615+
case TargetPlatform.web_javascript:
616+
return 'web';
617+
}
618+
}
619+
593620
String get simpleName {
594621
switch (this) {
595622
case TargetPlatform.linux_x64:
@@ -712,6 +739,15 @@ DarwinArch getDarwinArchForName(String arch) {
712739
};
713740
}
714741

742+
List<DarwinArch> getDarwinArchsFromEnv(Map<String, String> defines) {
743+
const List<DarwinArch> defaultDarwinArchitectures = <DarwinArch>[
744+
DarwinArch.x86_64,
745+
DarwinArch.arm64,
746+
];
747+
return defines[kDarwinArchs]?.split(' ').map(getDarwinArchForName).toList() ??
748+
defaultDarwinArchitectures;
749+
}
750+
715751
String getNameForTargetPlatform(TargetPlatform platform, {DarwinArch? darwinArch}) {
716752
return switch (platform) {
717753
TargetPlatform.ios when darwinArch != null => 'ios-${darwinArch.name}',
@@ -948,20 +984,6 @@ const String kSdkRoot = 'SdkRoot';
948984
/// Whether to enable Dart obfuscation and where to save the symbol map.
949985
const String kDartObfuscation = 'DartObfuscation';
950986

951-
/// Whether to enable Native Assets.
952-
///
953-
/// If true, native assets are built and the mapping for native assets lookup
954-
/// at runtime is embedded in the kernel file.
955-
///
956-
/// If false, native assets are not built, and an empty mapping is embedded in
957-
/// the kernel file. Used for targets that trigger kernel builds but
958-
/// are not OS/architecture specific.
959-
///
960-
/// Supported values are 'true' and 'false'.
961-
///
962-
/// Defaults to 'true'.
963-
const String kNativeAssets = 'NativeAssets';
964-
965987
/// An output directory where one or more code-size measurements may be written.
966988
const String kCodeSizeDirectory = 'CodeSizeDirectory';
967989

packages/flutter_tools/lib/src/build_system/targets/android.dart

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import '../../base/file_system.dart';
99
import '../../build_info.dart';
1010
import '../../devfs.dart';
1111
import '../../globals.dart' as globals show xcode;
12+
import '../../isolated/native_assets/dart_hook_result.dart';
1213
import '../../project.dart';
1314
import '../build_system.dart';
1415
import '../depfile.dart';
@@ -70,9 +71,11 @@ abstract class AndroidAssetBundle extends Target {
7071
.file(isolateSnapshotData)
7172
.copySync(outputDirectory.childFile('isolate_snapshot_data').path);
7273
}
74+
final DartHookResult dartHookResult = await DartBuild.loadHookResult(environment);
7375
final Depfile assetDepfile = await copyAssets(
7476
environment,
7577
outputDirectory,
78+
dartHookResult: dartHookResult,
7679
targetPlatform: TargetPlatform.android,
7780
buildMode: buildMode,
7881
flavor: environment.defines[kFlavor],
@@ -89,7 +92,11 @@ abstract class AndroidAssetBundle extends Target {
8992
}
9093

9194
@override
92-
List<Target> get dependencies => const <Target>[KernelSnapshot(), InstallCodeAssets()];
95+
List<Target> get dependencies => const <Target>[
96+
DartBuildForNative(),
97+
KernelSnapshot(),
98+
InstallCodeAssets(),
99+
];
93100
}
94101

95102
/// An implementation of [AndroidAssetBundle] that includes dependencies on vm

packages/flutter_tools/lib/src/build_system/targets/assets.dart

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import '../../build_info.dart';
1212
import '../../dart/package_map.dart';
1313
import '../../devfs.dart';
1414
import '../../flutter_manifest.dart';
15+
import '../../isolated/native_assets/dart_hook_result.dart';
1516
import '../build_system.dart';
1617
import '../depfile.dart';
1718
import '../exceptions.dart';
@@ -33,6 +34,7 @@ import 'native_assets.dart';
3334
Future<Depfile> copyAssets(
3435
Environment environment,
3536
Directory outputDirectory, {
37+
required DartHookResult dartHookResult,
3638
Map<String, DevFSContent> additionalContent = const <String, DevFSContent>{},
3739
required TargetPlatform targetPlatform,
3840
required BuildMode buildMode,
@@ -49,6 +51,7 @@ Future<Depfile> copyAssets(
4951
splitDeferredAssets: buildMode != BuildMode.debug && buildMode != BuildMode.jitRelease,
5052
).createBundle();
5153
final int resultCode = await assetBundle.build(
54+
flutterHookResult: dartHookResult.asFlutterResult,
5255
manifestPath: pubspecFile.path,
5356
packageConfigPath: findPackageConfigFileOrDefault(environment.projectDir).path,
5457
deferredComponentsEnabled: environment.defines[kDeferredComponents] == 'true',
@@ -248,7 +251,11 @@ class CopyAssets extends Target {
248251
String get name => 'copy_assets';
249252

250253
@override
251-
List<Target> get dependencies => const <Target>[KernelSnapshot(), InstallCodeAssets()];
254+
List<Target> get dependencies => const <Target>[
255+
DartBuildForNative(),
256+
KernelSnapshot(),
257+
InstallCodeAssets(),
258+
];
252259

253260
@override
254261
List<Source> get inputs => const <Source>[
@@ -274,9 +281,11 @@ class CopyAssets extends Target {
274281
final BuildMode buildMode = BuildMode.fromCliName(buildModeEnvironment);
275282
final Directory output = environment.buildDir.childDirectory('flutter_assets');
276283
output.createSync(recursive: true);
284+
final DartHookResult dartHookResult = await DartBuild.loadHookResult(environment);
277285
final Depfile depfile = await copyAssets(
278286
environment,
279287
output,
288+
dartHookResult: dartHookResult,
280289
targetPlatform: TargetPlatform.android,
281290
buildMode: buildMode,
282291
flavor: environment.defines[kFlavor],

0 commit comments

Comments
 (0)