Fix duplicate work in native assets release builds (#158980)

In release builds linking of native assets is enabled. The build step is
only a temprary step, it's output is given to the link step which then
returns all final assets (effectively a map-reduce system). Assets that
aren't sent to a specific linker could be conceptually viewed as sent to
a linker that will emit it's input as-is.

=> The code currently took output of build & link step and therefore
accumulated assets that aren't explicitly sent to a linker twice.

=> This led to performing work twice for those (e.g. copying them twice)

This PR changes this such that if linking mode is enabled, we only rely
on the output of the link phase.
That in return means many tests that mock the native asset builds need
to be updated to mock the output of the link phase.
This commit is contained in:
Martin Kustermann 2024-11-15 15:05:42 +01:00 committed by GitHub
parent eaa99f28b1
commit 256359194e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 207 additions and 87 deletions

View File

@ -750,9 +750,10 @@ Future<DartBuildResult> _runDartBuild({
if (buildResult == null) { if (buildResult == null) {
_throwNativeAssetsBuildFailed(); _throwNativeAssetsBuildFailed();
} }
assets.addAll(buildResult.encodedAssets);
dependencies.addAll(buildResult.dependencies); dependencies.addAll(buildResult.dependencies);
if (linkingEnabled) { if (!linkingEnabled) {
assets.addAll(buildResult.encodedAssets);
} else {
final LinkResult? linkResult = await buildRunner.link( final LinkResult? linkResult = await buildRunner.link(
supportedAssetTypes: <String>[CodeAsset.type], supportedAssetTypes: <String>[CodeAsset.type],
configCreator: () => LinkConfigBuilder() configCreator: () => LinkConfigBuilder()

View File

@ -67,12 +67,8 @@ void main() {
final File dylibAfterCompiling = fileSystem.file('libbar.so'); final File dylibAfterCompiling = fileSystem.file('libbar.so');
// The mock doesn't create the file, so create it here. // The mock doesn't create the file, so create it here.
await dylibAfterCompiling.create(); await dylibAfterCompiling.create();
final FakeFlutterNativeAssetsBuildRunner buildRunner = FakeFlutterNativeAssetsBuildRunner(
packagesWithNativeAssetsResult: <Package>[ final List<CodeAsset> codeAssets = <CodeAsset>[
Package('bar', projectUri),
],
buildResult: FakeFlutterNativeAssetsBuilderResult.fromAssets(
codeAssets: <CodeAsset>[
CodeAsset( CodeAsset(
package: 'bar', package: 'bar',
name: 'bar.dart', name: 'bar.dart',
@ -81,7 +77,16 @@ void main() {
architecture: Architecture.arm64, architecture: Architecture.arm64,
file: Uri.file('libbar.so'), file: Uri.file('libbar.so'),
), ),
];
final FakeFlutterNativeAssetsBuildRunner buildRunner = FakeFlutterNativeAssetsBuildRunner(
packagesWithNativeAssetsResult: <Package>[
Package('bar', projectUri),
], ],
buildResult: FakeFlutterNativeAssetsBuilderResult.fromAssets(
codeAssets: codeAssets,
),
linkResult: FakeFlutterNativeAssetsBuilderResult.fromAssets(
codeAssets: codeAssets,
), ),
); );
await runFlutterSpecificDartBuild( await runFlutterSpecificDartBuild(

View File

@ -190,10 +190,7 @@ void main() {
() async { () async {
await createPackageConfig(iosEnvironment); await createPackageConfig(iosEnvironment);
final FlutterNativeAssetsBuildRunner buildRunner = FakeFlutterNativeAssetsBuildRunner( final List<CodeAsset> codeAssets = <CodeAsset>[
packagesWithNativeAssetsResult: <Package>[Package('foo', iosEnvironment.buildDir.uri)],
buildResult: FakeFlutterNativeAssetsBuilderResult.fromAssets(
codeAssets: <CodeAsset>[
CodeAsset( CodeAsset(
package: 'foo', package: 'foo',
name: 'foo.dart', name: 'foo.dart',
@ -202,11 +199,18 @@ void main() {
architecture: Architecture.arm64, architecture: Architecture.arm64,
file: Uri.file('foo.framework/foo'), file: Uri.file('foo.framework/foo'),
), ),
], ];
final FlutterNativeAssetsBuildRunner buildRunner = FakeFlutterNativeAssetsBuildRunner(
packagesWithNativeAssetsResult: <Package>[Package('foo', iosEnvironment.buildDir.uri)],
buildResult: FakeFlutterNativeAssetsBuilderResult.fromAssets(
codeAssets: codeAssets,
dependencies: <Uri>[ dependencies: <Uri>[
Uri.file('src/foo.c'), Uri.file('src/foo.c'),
], ],
), ),
linkResult: FakeFlutterNativeAssetsBuilderResult.fromAssets(
codeAssets: codeAssets,
),
); );
await NativeAssets(buildRunner: buildRunner).build(iosEnvironment); await NativeAssets(buildRunner: buildRunner).build(iosEnvironment);
@ -252,12 +256,7 @@ void main() {
await createPackageConfig(androidEnvironment); await createPackageConfig(androidEnvironment);
await fileSystem.file('libfoo.so').create(); await fileSystem.file('libfoo.so').create();
final FakeFlutterNativeAssetsBuildRunner buildRunner = FakeFlutterNativeAssetsBuildRunner( final List<CodeAsset> codeAssets = <CodeAsset>[
packagesWithNativeAssetsResult: <Package>[
Package('foo', androidEnvironment.buildDir.uri)
],
buildResult: FakeFlutterNativeAssetsBuilderResult.fromAssets(
codeAssets: <CodeAsset>[
if (hasAssets) if (hasAssets)
CodeAsset( CodeAsset(
package: 'foo', package: 'foo',
@ -267,11 +266,20 @@ void main() {
architecture: Architecture.arm64, architecture: Architecture.arm64,
file: Uri.file('libfoo.so'), file: Uri.file('libfoo.so'),
), ),
];
final FakeFlutterNativeAssetsBuildRunner buildRunner = FakeFlutterNativeAssetsBuildRunner(
packagesWithNativeAssetsResult: <Package>[
Package('foo', androidEnvironment.buildDir.uri)
], ],
buildResult: FakeFlutterNativeAssetsBuilderResult.fromAssets(
codeAssets: codeAssets,
dependencies: <Uri>[ dependencies: <Uri>[
Uri.file('src/foo.c'), Uri.file('src/foo.c'),
], ],
), ),
linkResult: FakeFlutterNativeAssetsBuilderResult.fromAssets(
codeAssets: codeAssets,
),
); );
await NativeAssets(buildRunner: buildRunner).build(androidEnvironment); await NativeAssets(buildRunner: buildRunner).build(androidEnvironment);
expect( expect(

View File

@ -12,6 +12,8 @@ import 'package:native_assets_builder/native_assets_builder.dart';
import 'package:native_assets_cli/code_assets_builder.dart'; import 'package:native_assets_cli/code_assets_builder.dart';
import 'package:package_config/package_config_types.dart'; import 'package:package_config/package_config_types.dart';
export 'package:native_assets_cli/code_assets_builder.dart' show CodeAsset, DynamicLoadingBundled;
/// Mocks all logic instead of using `package:native_assets_builder`, which /// Mocks all logic instead of using `package:native_assets_builder`, which
/// relies on doing process calls to `pub` and the local file system. /// relies on doing process calls to `pub` and the local file system.
class FakeFlutterNativeAssetsBuildRunner class FakeFlutterNativeAssetsBuildRunner
@ -20,6 +22,7 @@ class FakeFlutterNativeAssetsBuildRunner
this.hasPackageConfigResult = true, this.hasPackageConfigResult = true,
this.packagesWithNativeAssetsResult = const <Package>[], this.packagesWithNativeAssetsResult = const <Package>[],
this.onBuild, this.onBuild,
this.onLink,
this.buildDryRunResult = const FakeFlutterNativeAssetsBuilderResult(), this.buildDryRunResult = const FakeFlutterNativeAssetsBuilderResult(),
this.buildResult = const FakeFlutterNativeAssetsBuilderResult(), this.buildResult = const FakeFlutterNativeAssetsBuilderResult(),
this.linkResult = const FakeFlutterNativeAssetsBuilderResult(), this.linkResult = const FakeFlutterNativeAssetsBuilderResult(),
@ -30,6 +33,7 @@ class FakeFlutterNativeAssetsBuildRunner
ndkCCompilerConfigResult ?? CCompilerConfig(); ndkCCompilerConfigResult ?? CCompilerConfig();
final BuildResult? Function(BuildConfig)? onBuild; final BuildResult? Function(BuildConfig)? onBuild;
final LinkResult? Function(LinkConfig)? onLink;
final BuildResult? buildResult; final BuildResult? buildResult;
final LinkResult? linkResult; final LinkResult? linkResult;
final BuildDryRunResult? buildDryRunResult; final BuildDryRunResult? buildDryRunResult;
@ -99,11 +103,29 @@ class FakeFlutterNativeAssetsBuildRunner
required Uri workingDirectory, required Uri workingDirectory,
required BuildResult buildResult, required BuildResult buildResult,
}) async { }) async {
for (final Package _ in packagesWithNativeAssetsResult) { LinkResult? result = linkResult;
for (final Package package in packagesWithNativeAssetsResult) {
final LinkConfigBuilder configBuilder = configCreator()
..setupHookConfig(
packageRoot: package.root,
packageName: package.name,
targetOS: targetOS,
supportedAssetTypes: supportedAssetTypes,
buildMode: buildMode,
)
..setupLinkRunConfig(
outputDirectory: Uri.parse('build-out-dir'),
outputDirectoryShared: Uri.parse('build-out-dir-shared'),
recordedUsesFile: null,
);
final LinkConfig buildConfig = LinkConfig(configBuilder.json);
if (onLink != null) {
result = onLink!(buildConfig);
}
lastBuildMode = buildMode; lastBuildMode = buildMode;
linkInvocations++; linkInvocations++;
} }
return linkResult; return result;
} }
@override @override
@ -148,12 +170,22 @@ final class FakeFlutterNativeAssetsBuilderResult
factory FakeFlutterNativeAssetsBuilderResult.fromAssets({ factory FakeFlutterNativeAssetsBuilderResult.fromAssets({
List<CodeAsset> codeAssets = const <CodeAsset>[], List<CodeAsset> codeAssets = const <CodeAsset>[],
Map<String, List<CodeAsset>> codeAssetsForLinking = const <String, List<CodeAsset>>{},
List<Uri> dependencies = const <Uri>[], List<Uri> dependencies = const <Uri>[],
}) { }) {
return FakeFlutterNativeAssetsBuilderResult( return FakeFlutterNativeAssetsBuilderResult(
encodedAssets: <EncodedAsset>[ encodedAssets: <EncodedAsset>[
for (final CodeAsset codeAsset in codeAssets) codeAsset.encode(), for (final CodeAsset codeAsset in codeAssets) codeAsset.encode(),
], dependencies: dependencies); ],
encodedAssetsForLinking: <String, List<EncodedAsset>>{
for (final String linkerName in codeAssetsForLinking.keys)
linkerName: <EncodedAsset>[
for (final CodeAsset codeAsset in codeAssetsForLinking[linkerName]!)
codeAsset.encode(),
],
},
dependencies: dependencies,
);
} }
@override @override

View File

@ -162,31 +162,35 @@ void main() {
final Uri nonFlutterTesterAssetUri = environment.buildDir.childFile('native_assets.yaml').uri; final Uri nonFlutterTesterAssetUri = environment.buildDir.childFile('native_assets.yaml').uri;
await packageConfig.parent.create(); await packageConfig.parent.create();
await packageConfig.create(); await packageConfig.create();
final FakeFlutterNativeAssetsBuildRunner buildRunner = FakeFlutterNativeAssetsBuildRunner(
packagesWithNativeAssetsResult: <Package>[ List<CodeAsset> codeAssets(OS targetOS, CodeConfig codeConfig) => <CodeAsset>[
Package('bar', projectUri),
],
onBuild: (BuildConfig config) =>
FakeFlutterNativeAssetsBuilderResult.fromAssets(
codeAssets: <CodeAsset>[
CodeAsset( CodeAsset(
package: 'bar', package: 'bar',
name: 'bar.dart', name: 'bar.dart',
linkMode: DynamicLoadingBundled(), linkMode: DynamicLoadingBundled(),
os: config.targetOS, os: targetOS,
architecture: config.codeConfig.targetArchitecture, architecture: codeConfig.targetArchitecture,
file: Uri.file('${config.codeConfig.targetArchitecture}/libbar.dylib'), file: Uri.file('${codeConfig.targetArchitecture}/libbar.dylib'),
), ),
CodeAsset( CodeAsset(
package: 'buz', package: 'buz',
name: 'buz.dart', name: 'buz.dart',
linkMode: DynamicLoadingBundled(), linkMode: DynamicLoadingBundled(),
os: config.targetOS, os: targetOS,
architecture: config.codeConfig.targetArchitecture, architecture: codeConfig.targetArchitecture,
file: Uri.file('${config.codeConfig.targetArchitecture}/libbuz.dylib'), file: Uri.file('${codeConfig.targetArchitecture}/libbuz.dylib'),
), ),
];
final FakeFlutterNativeAssetsBuildRunner buildRunner = FakeFlutterNativeAssetsBuildRunner(
packagesWithNativeAssetsResult: <Package>[
Package('bar', projectUri),
], ],
), onBuild: (BuildConfig config) =>
FakeFlutterNativeAssetsBuilderResult.fromAssets(codeAssets: codeAssets(config.targetOS, config.codeConfig)),
onLink: (LinkConfig config) =>
buildMode == BuildMode.debug
? null
: FakeFlutterNativeAssetsBuilderResult.fromAssets(codeAssets: codeAssets(config.targetOS, config.codeConfig)),
); );
await runFlutterSpecificDartBuild( await runFlutterSpecificDartBuild(
environmentDefines: <String, String>{ environmentDefines: <String, String>{

View File

@ -268,31 +268,34 @@ void main() {
final Uri nonFlutterTesterAssetUri = environment.buildDir.childFile('native_assets.yaml').uri; final Uri nonFlutterTesterAssetUri = environment.buildDir.childFile('native_assets.yaml').uri;
await packageConfig.parent.create(); await packageConfig.parent.create();
await packageConfig.create(); await packageConfig.create();
final FakeFlutterNativeAssetsBuildRunner buildRunner = FakeFlutterNativeAssetsBuildRunner(
packagesWithNativeAssetsResult: <Package>[ List<CodeAsset> codeAssets(OS targetOS, CodeConfig codeConfig) => <CodeAsset>[
Package('bar', projectUri),
],
onBuild: (BuildConfig config) =>
FakeFlutterNativeAssetsBuilderResult.fromAssets(
codeAssets: <CodeAsset>[
CodeAsset( CodeAsset(
package: 'bar', package: 'bar',
name: 'bar.dart', name: 'bar.dart',
linkMode: DynamicLoadingBundled(), linkMode: DynamicLoadingBundled(),
os: config.targetOS, os: targetOS,
architecture: config.codeConfig.targetArchitecture, architecture: codeConfig.targetArchitecture,
file: Uri.file('${config.codeConfig.targetArchitecture}/libbar.dylib'), file: Uri.file('${codeConfig.targetArchitecture}/libbar.dylib'),
), ),
CodeAsset( CodeAsset(
package: 'buz', package: 'buz',
name: 'buz.dart', name: 'buz.dart',
linkMode: DynamicLoadingBundled(), linkMode: DynamicLoadingBundled(),
os: config.targetOS, os: targetOS,
architecture: config.codeConfig.targetArchitecture, architecture: codeConfig.targetArchitecture,
file: Uri.file('${config.codeConfig.targetArchitecture}/libbuz.dylib'), file: Uri.file('${codeConfig.targetArchitecture}/libbuz.dylib'),
), ),
];
final FakeFlutterNativeAssetsBuildRunner buildRunner = FakeFlutterNativeAssetsBuildRunner(
packagesWithNativeAssetsResult: <Package>[
Package('bar', projectUri),
], ],
), onBuild: (BuildConfig config) =>
FakeFlutterNativeAssetsBuilderResult.fromAssets(codeAssets: codeAssets(config.targetOS, config.codeConfig)),
onLink: (LinkConfig config) => buildMode == BuildMode.debug
? null
: FakeFlutterNativeAssetsBuilderResult.fromAssets(codeAssets: codeAssets(config.targetOS, config.codeConfig)),
); );
final (_, Uri nativeAssetsYaml) = await runFlutterSpecificDartBuild( final (_, Uri nativeAssetsYaml) = await runFlutterSpecificDartBuild(
environmentDefines: <String, String>{ environmentDefines: <String, String>{

View File

@ -300,4 +300,67 @@ void main() {
), ),
); );
}); });
testUsingContext('Native assets: no duplicate assets with linking', overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true),
ProcessManager: () => FakeProcessManager.empty(),
}, () async {
final File packageConfig =
environment.projectDir.childFile('.dart_tool/package_config.json');
final Uri nonFlutterTesterAssetUri = environment.buildDir.childFile('native_assets.yaml').uri;
await packageConfig.parent.create();
await packageConfig.create();
final File directSoFile = environment.projectDir.childFile('direct.so');
directSoFile.writeAsBytesSync(<int>[]);
final File linkableAFile = environment.projectDir.childFile('linkable.a');
linkableAFile.writeAsBytesSync(<int>[]);
final File linkedSoFile = environment.projectDir.childFile('linked.so');
linkedSoFile.writeAsBytesSync(<int>[]);
CodeAsset makeCodeAsset(String name, Uri file, LinkMode linkMode)
=> CodeAsset(
package: 'bar',
name: name,
linkMode: linkMode,
os: OS.linux,
architecture: Architecture.x64,
file: file,
);
final (DartBuildResult result, _) = await runFlutterSpecificDartBuild(
environmentDefines: <String, String>{
// Release mode means the dart build has linking enabled.
kBuildMode: BuildMode.release.cliName,
},
targetPlatform: TargetPlatform.linux_x64,
projectUri: projectUri,
nativeAssetsYamlUri: nonFlutterTesterAssetUri,
fileSystem: fileSystem,
buildRunner: FakeFlutterNativeAssetsBuildRunner(
packagesWithNativeAssetsResult: <Package>[
Package('bar', projectUri),
],
buildResult: FakeFlutterNativeAssetsBuilderResult.fromAssets(
codeAssets: <CodeAsset>[
makeCodeAsset('direct', directSoFile.uri, DynamicLoadingBundled()),
],
codeAssetsForLinking: <String, List<CodeAsset>>{
'package:bar' : <CodeAsset>[
makeCodeAsset('linkable', linkableAFile.uri, StaticLinking()),
],
},
),
linkResult: FakeFlutterNativeAssetsBuilderResult.fromAssets(
codeAssets: <CodeAsset>[
makeCodeAsset('direct', directSoFile.uri, DynamicLoadingBundled()),
makeCodeAsset('linked', linkedSoFile.uri, DynamicLoadingBundled()),
],
),
),
);
expect(
result.codeAssets.map((CodeAsset c) => c.file!.toString()).toList()..sort(),
<String>[directSoFile.uri.toString(), linkedSoFile.uri.toString()]);
});
} }

View File

@ -77,12 +77,8 @@ void main() {
final File dylibAfterCompiling = fileSystem.file('bar.dll'); final File dylibAfterCompiling = fileSystem.file('bar.dll');
// The mock doesn't create the file, so create it here. // The mock doesn't create the file, so create it here.
await dylibAfterCompiling.create(); await dylibAfterCompiling.create();
final FakeFlutterNativeAssetsBuildRunner buildRunner = FakeFlutterNativeAssetsBuildRunner(
packagesWithNativeAssetsResult: <Package>[ final List<CodeAsset> codeAssets = <CodeAsset>[
Package('bar', projectUri),
],
buildResult: FakeFlutterNativeAssetsBuilderResult.fromAssets(
codeAssets: <CodeAsset>[
CodeAsset( CodeAsset(
package: 'bar', package: 'bar',
name: 'bar.dart', name: 'bar.dart',
@ -91,7 +87,15 @@ void main() {
architecture: Architecture.x64, architecture: Architecture.x64,
file: dylibAfterCompiling.uri, file: dylibAfterCompiling.uri,
), ),
];
final FakeFlutterNativeAssetsBuildRunner buildRunner = FakeFlutterNativeAssetsBuildRunner(
packagesWithNativeAssetsResult: <Package>[
Package('bar', projectUri),
], ],
buildResult: FakeFlutterNativeAssetsBuilderResult.fromAssets(codeAssets: codeAssets),
linkResult: buildMode == BuildMode.debug
? null
: FakeFlutterNativeAssetsBuilderResult.fromAssets(codeAssets: codeAssets,
), ),
); );
final (_, Uri nativeAssetsYaml) = await runFlutterSpecificDartBuild( final (_, Uri nativeAssetsYaml) = await runFlutterSpecificDartBuild(