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) {
_throwNativeAssetsBuildFailed();
}
assets.addAll(buildResult.encodedAssets);
dependencies.addAll(buildResult.dependencies);
if (linkingEnabled) {
if (!linkingEnabled) {
assets.addAll(buildResult.encodedAssets);
} else {
final LinkResult? linkResult = await buildRunner.link(
supportedAssetTypes: <String>[CodeAsset.type],
configCreator: () => LinkConfigBuilder()

View File

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

View File

@ -190,23 +190,27 @@ void main() {
() async {
await createPackageConfig(iosEnvironment);
final List<CodeAsset> codeAssets = <CodeAsset>[
CodeAsset(
package: 'foo',
name: 'foo.dart',
linkMode: DynamicLoadingBundled(),
os: OS.iOS,
architecture: Architecture.arm64,
file: Uri.file('foo.framework/foo'),
),
];
final FlutterNativeAssetsBuildRunner buildRunner = FakeFlutterNativeAssetsBuildRunner(
packagesWithNativeAssetsResult: <Package>[Package('foo', iosEnvironment.buildDir.uri)],
buildResult: FakeFlutterNativeAssetsBuilderResult.fromAssets(
codeAssets: <CodeAsset>[
CodeAsset(
package: 'foo',
name: 'foo.dart',
linkMode: DynamicLoadingBundled(),
os: OS.iOS,
architecture: Architecture.arm64,
file: Uri.file('foo.framework/foo'),
),
],
codeAssets: codeAssets,
dependencies: <Uri>[
Uri.file('src/foo.c'),
],
),
linkResult: FakeFlutterNativeAssetsBuilderResult.fromAssets(
codeAssets: codeAssets,
),
);
await NativeAssets(buildRunner: buildRunner).build(iosEnvironment);
@ -252,26 +256,30 @@ void main() {
await createPackageConfig(androidEnvironment);
await fileSystem.file('libfoo.so').create();
final List<CodeAsset> codeAssets = <CodeAsset>[
if (hasAssets)
CodeAsset(
package: 'foo',
name: 'foo.dart',
linkMode: DynamicLoadingBundled(),
os: OS.android,
architecture: Architecture.arm64,
file: Uri.file('libfoo.so'),
),
];
final FakeFlutterNativeAssetsBuildRunner buildRunner = FakeFlutterNativeAssetsBuildRunner(
packagesWithNativeAssetsResult: <Package>[
Package('foo', androidEnvironment.buildDir.uri)
],
buildResult: FakeFlutterNativeAssetsBuilderResult.fromAssets(
codeAssets: <CodeAsset>[
if (hasAssets)
CodeAsset(
package: 'foo',
name: 'foo.dart',
linkMode: DynamicLoadingBundled(),
os: OS.android,
architecture: Architecture.arm64,
file: Uri.file('libfoo.so'),
),
],
codeAssets: codeAssets,
dependencies: <Uri>[
Uri.file('src/foo.c'),
],
),
linkResult: FakeFlutterNativeAssetsBuilderResult.fromAssets(
codeAssets: codeAssets,
),
);
await NativeAssets(buildRunner: buildRunner).build(androidEnvironment);
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: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
/// relies on doing process calls to `pub` and the local file system.
class FakeFlutterNativeAssetsBuildRunner
@ -20,6 +22,7 @@ class FakeFlutterNativeAssetsBuildRunner
this.hasPackageConfigResult = true,
this.packagesWithNativeAssetsResult = const <Package>[],
this.onBuild,
this.onLink,
this.buildDryRunResult = const FakeFlutterNativeAssetsBuilderResult(),
this.buildResult = const FakeFlutterNativeAssetsBuilderResult(),
this.linkResult = const FakeFlutterNativeAssetsBuilderResult(),
@ -30,6 +33,7 @@ class FakeFlutterNativeAssetsBuildRunner
ndkCCompilerConfigResult ?? CCompilerConfig();
final BuildResult? Function(BuildConfig)? onBuild;
final LinkResult? Function(LinkConfig)? onLink;
final BuildResult? buildResult;
final LinkResult? linkResult;
final BuildDryRunResult? buildDryRunResult;
@ -99,11 +103,29 @@ class FakeFlutterNativeAssetsBuildRunner
required Uri workingDirectory,
required BuildResult buildResult,
}) 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;
linkInvocations++;
}
return linkResult;
return result;
}
@override
@ -148,12 +170,22 @@ final class FakeFlutterNativeAssetsBuilderResult
factory FakeFlutterNativeAssetsBuilderResult.fromAssets({
List<CodeAsset> codeAssets = const <CodeAsset>[],
Map<String, List<CodeAsset>> codeAssetsForLinking = const <String, List<CodeAsset>>{},
List<Uri> dependencies = const <Uri>[],
}) {
return FakeFlutterNativeAssetsBuilderResult(
encodedAssets: <EncodedAsset>[
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

View File

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

View File

@ -268,31 +268,34 @@ void main() {
final Uri nonFlutterTesterAssetUri = environment.buildDir.childFile('native_assets.yaml').uri;
await packageConfig.parent.create();
await packageConfig.create();
List<CodeAsset> codeAssets(OS targetOS, CodeConfig codeConfig) => <CodeAsset>[
CodeAsset(
package: 'bar',
name: 'bar.dart',
linkMode: DynamicLoadingBundled(),
os: targetOS,
architecture: codeConfig.targetArchitecture,
file: Uri.file('${codeConfig.targetArchitecture}/libbar.dylib'),
),
CodeAsset(
package: 'buz',
name: 'buz.dart',
linkMode: DynamicLoadingBundled(),
os: targetOS,
architecture: codeConfig.targetArchitecture,
file: Uri.file('${codeConfig.targetArchitecture}/libbuz.dylib'),
),
];
final FakeFlutterNativeAssetsBuildRunner buildRunner = FakeFlutterNativeAssetsBuildRunner(
packagesWithNativeAssetsResult: <Package>[
Package('bar', projectUri),
],
onBuild: (BuildConfig config) =>
FakeFlutterNativeAssetsBuilderResult.fromAssets(
codeAssets: <CodeAsset>[
CodeAsset(
package: 'bar',
name: 'bar.dart',
linkMode: DynamicLoadingBundled(),
os: config.targetOS,
architecture: config.codeConfig.targetArchitecture,
file: Uri.file('${config.codeConfig.targetArchitecture}/libbar.dylib'),
),
CodeAsset(
package: 'buz',
name: 'buz.dart',
linkMode: DynamicLoadingBundled(),
os: config.targetOS,
architecture: config.codeConfig.targetArchitecture,
file: Uri.file('${config.codeConfig.targetArchitecture}/libbuz.dylib'),
),
],
),
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(
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,21 +77,25 @@ void main() {
final File dylibAfterCompiling = fileSystem.file('bar.dll');
// The mock doesn't create the file, so create it here.
await dylibAfterCompiling.create();
final List<CodeAsset> codeAssets = <CodeAsset>[
CodeAsset(
package: 'bar',
name: 'bar.dart',
linkMode: DynamicLoadingBundled(),
os: OS.windows,
architecture: Architecture.x64,
file: dylibAfterCompiling.uri,
),
];
final FakeFlutterNativeAssetsBuildRunner buildRunner = FakeFlutterNativeAssetsBuildRunner(
packagesWithNativeAssetsResult: <Package>[
Package('bar', projectUri),
],
buildResult: FakeFlutterNativeAssetsBuilderResult.fromAssets(
codeAssets: <CodeAsset>[
CodeAsset(
package: 'bar',
name: 'bar.dart',
linkMode: DynamicLoadingBundled(),
os: OS.windows,
architecture: Architecture.x64,
file: dylibAfterCompiling.uri,
),
],
buildResult: FakeFlutterNativeAssetsBuilderResult.fromAssets(codeAssets: codeAssets),
linkResult: buildMode == BuildMode.debug
? null
: FakeFlutterNativeAssetsBuilderResult.fromAssets(codeAssets: codeAssets,
),
);
final (_, Uri nativeAssetsYaml) = await runFlutterSpecificDartBuild(