blob: 3d0a03251d042c84e9e06f7566b407ef6d91f278 [file] [log] [blame]
// Copyright 2014 The Flutter Authors. 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:file/file.dart';
import 'package:file/memory.dart';
import 'package:file_testing/file_testing.dart';
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/build_system/build_system.dart';
import 'package:flutter_tools/src/features.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:flutter_tools/src/isolated/native_assets/android/native_assets.dart';
import 'package:native_assets_cli/native_assets_cli_internal.dart';
import 'package:package_config/package_config_types.dart';
import '../../../src/common.dart';
import '../../../src/context.dart';
import '../../../src/fakes.dart';
import '../fake_native_assets_build_runner.dart';
void main() {
late FakeProcessManager processManager;
late Environment environment;
late Artifacts artifacts;
late FileSystem fileSystem;
late BufferLogger logger;
late Uri projectUri;
setUp(() {
processManager = FakeProcessManager.empty();
logger = BufferLogger.test();
artifacts = Artifacts.test();
fileSystem = MemoryFileSystem.test();
environment = Environment.test(
fileSystem.currentDirectory,
inputs: <String, String>{},
artifacts: artifacts,
processManager: processManager,
fileSystem: fileSystem,
logger: logger,
);
environment.buildDir.createSync(recursive: true);
projectUri = environment.projectDir.uri;
});
testUsingContext('dry run with no package config', overrides: <Type, Generator>{
ProcessManager: () => FakeProcessManager.empty(),
}, () async {
expect(
await dryRunNativeAssetsAndroid(
projectUri: projectUri,
fileSystem: fileSystem,
buildRunner: FakeNativeAssetsBuildRunner(
hasPackageConfigResult: false,
),
),
null,
);
expect(
(globals.logger as BufferLogger).traceText,
contains('No package config found. Skipping native assets compilation.'),
);
});
testUsingContext('build with no package config', overrides: <Type, Generator>{
ProcessManager: () => FakeProcessManager.empty(),
}, () async {
await buildNativeAssetsAndroid(
androidArchs: <AndroidArch>[AndroidArch.arm64_v8a],
targetAndroidNdkApi: 21,
projectUri: projectUri,
buildMode: BuildMode.debug,
fileSystem: fileSystem,
yamlParentDirectory: environment.buildDir.uri,
buildRunner: FakeNativeAssetsBuildRunner(
hasPackageConfigResult: false,
),
);
expect(
(globals.logger as BufferLogger).traceText,
contains('No package config found. Skipping native assets compilation.'),
);
});
testUsingContext('dry run with assets but not enabled', overrides: <Type, Generator>{
ProcessManager: () => FakeProcessManager.empty(),
}, () async {
final File packageConfig = environment.projectDir.childFile('.dart_tool/package_config.json');
await packageConfig.parent.create();
await packageConfig.create();
expect(
() => dryRunNativeAssetsAndroid(
projectUri: projectUri,
fileSystem: fileSystem,
buildRunner: FakeNativeAssetsBuildRunner(
packagesWithNativeAssetsResult: <Package>[
Package('bar', projectUri),
],
),
),
throwsToolExit(
message: 'Package(s) bar require the native assets feature to be enabled. '
'Enable using `flutter config --enable-native-assets`.',
),
);
});
testUsingContext('dry run with assets', overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true),
ProcessManager: () => FakeProcessManager.empty(),
}, () async {
final File packageConfig = environment.projectDir.childFile('.dart_tool/package_config.json');
await packageConfig.parent.create();
await packageConfig.create();
final FakeNativeAssetsBuildRunner buildRunner = FakeNativeAssetsBuildRunner(
packagesWithNativeAssetsResult: <Package>[
Package('bar', projectUri),
],
buildDryRunResult: FakeNativeAssetsBuilderResult(
assets: <AssetImpl>[
NativeCodeAssetImpl(
id: 'package:bar/bar.dart',
linkMode: DynamicLoadingBundledImpl(),
os: OSImpl.macOS,
architecture: ArchitectureImpl.arm64,
file: Uri.file('libbar.so'),
),
NativeCodeAssetImpl(
id: 'package:bar/bar.dart',
linkMode: DynamicLoadingBundledImpl(),
os: OSImpl.macOS,
architecture: ArchitectureImpl.x64,
file: Uri.file('libbar.so'),
),
],
),
);
final Uri? nativeAssetsYaml = await dryRunNativeAssetsAndroid(
projectUri: projectUri,
fileSystem: fileSystem,
buildRunner: buildRunner,
);
expect(
(globals.logger as BufferLogger).traceText,
stringContainsInOrder(<String>[
'Dry running native assets for android.',
'Dry running native assets for android done.',
]),
);
expect(
nativeAssetsYaml,
projectUri.resolve('build/native_assets/android/native_assets.yaml'),
);
expect(
await fileSystem.file(nativeAssetsYaml).readAsString(),
contains('package:bar/bar.dart'),
);
expect(buildRunner.buildDryRunInvocations, 1);
expect(buildRunner.linkDryRunInvocations, 0);
});
testUsingContext('build with assets but not enabled', () async {
final File packageConfig = environment.projectDir.childFile('.dart_tool/package_config.json');
await packageConfig.parent.create();
await packageConfig.create();
expect(
() => buildNativeAssetsAndroid(
androidArchs: <AndroidArch>[AndroidArch.arm64_v8a],
targetAndroidNdkApi: 21,
projectUri: projectUri,
buildMode: BuildMode.debug,
fileSystem: fileSystem,
yamlParentDirectory: environment.buildDir.uri,
buildRunner: FakeNativeAssetsBuildRunner(
packagesWithNativeAssetsResult: <Package>[
Package('bar', projectUri),
],
),
),
throwsToolExit(
message: 'Package(s) bar require the native assets feature to be enabled. '
'Enable using `flutter config --enable-native-assets`.',
),
);
});
testUsingContext('build no assets', overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true),
ProcessManager: () => FakeProcessManager.empty(),
}, () async {
final File packageConfig = environment.projectDir.childFile('.dart_tool/package_config.json');
await packageConfig.parent.create();
await packageConfig.create();
await buildNativeAssetsAndroid(
androidArchs: <AndroidArch>[AndroidArch.arm64_v8a],
targetAndroidNdkApi: 21,
projectUri: projectUri,
buildMode: BuildMode.debug,
fileSystem: fileSystem,
yamlParentDirectory: environment.buildDir.uri,
buildRunner: FakeNativeAssetsBuildRunner(
packagesWithNativeAssetsResult: <Package>[
Package('bar', projectUri),
],
),
);
expect(
environment.buildDir.childFile('native_assets.yaml'),
exists,
);
});
for (final BuildMode buildMode in <BuildMode>[
BuildMode.debug,
BuildMode.release,
]) {
testUsingContext('build with assets $buildMode',
skip: const LocalPlatform().isWindows, // [intended] Backslashes in commands, but we will never run these commands on Windows.
overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true),
ProcessManager: () => FakeProcessManager.empty(),
}, () async {
final File packageConfig =
environment.projectDir.childFile('.dart_tool/package_config.json');
await packageConfig.parent.create();
await packageConfig.create();
final File dylibAfterCompiling = fileSystem.file('libbar.so');
// The mock doesn't create the file, so create it here.
await dylibAfterCompiling.create();
final FakeNativeAssetsBuildRunner buildRunner =
FakeNativeAssetsBuildRunner(
packagesWithNativeAssetsResult: <Package>[
Package('bar', projectUri),
],
buildResult: FakeNativeAssetsBuilderResult(
assets: <AssetImpl>[
NativeCodeAssetImpl(
id: 'package:bar/bar.dart',
linkMode: DynamicLoadingBundledImpl(),
os: OSImpl.android,
architecture: ArchitectureImpl.arm64,
file: Uri.file('libbar.so'),
),
],
),
);
await buildNativeAssetsAndroid(
androidArchs: <AndroidArch>[AndroidArch.arm64_v8a],
targetAndroidNdkApi: 21,
projectUri: projectUri,
buildMode: buildMode,
fileSystem: fileSystem,
yamlParentDirectory: environment.buildDir.uri,
buildRunner: buildRunner,
);
expect(
(globals.logger as BufferLogger).traceText,
stringContainsInOrder(<String>[
'Building native assets for [android_arm64] $buildMode.',
'Building native assets for [android_arm64] done.',
]),
);
expect(
environment.buildDir.childFile('native_assets.yaml'),
exists,
);
expect(buildRunner.buildInvocations, 1);
expect(
buildRunner.linkInvocations,
buildMode == BuildMode.release ? 1 : 0,
);
});
}
// Ensure no exceptions for a non installed NDK are thrown if no native
// assets have to be build.
testUsingContext(
'does not throw if NDK not present but no native assets present',
overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true),
ProcessManager: () => FakeProcessManager.empty(),
}, () async {
final File packageConfig =
environment.projectDir.childFile('.dart_tool/package_config.json');
await packageConfig.create(recursive: true);
await buildNativeAssetsAndroid(
androidArchs: <AndroidArch>[AndroidArch.x86_64],
targetAndroidNdkApi: 21,
projectUri: projectUri,
buildMode: BuildMode.debug,
fileSystem: fileSystem,
buildRunner: _BuildRunnerWithoutNdk(),
);
expect(
(globals.logger as BufferLogger).traceText,
isNot(contains('Building native assets for ')),
);
});
testUsingContext('throw if NDK not present and there are native assets',
overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true),
}, () async {
final File packageConfig =
environment.projectDir.childFile('.dart_tool/package_config.json');
await packageConfig.parent.create();
await packageConfig.create();
expect(
() => buildNativeAssetsAndroid(
androidArchs: <AndroidArch>[AndroidArch.arm64_v8a],
targetAndroidNdkApi: 21,
projectUri: projectUri,
buildMode: BuildMode.debug,
fileSystem: fileSystem,
yamlParentDirectory: environment.buildDir.uri,
buildRunner: _BuildRunnerWithoutNdk(
packagesWithNativeAssetsResult: <Package>[
Package('bar', projectUri),
],
),
),
throwsToolExit(
message: 'Android NDK Clang could not be found.',
),
);
});
testUsingContext('Native assets dry run error', overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true),
ProcessManager: () => FakeProcessManager.empty(),
}, () async {
final File packageConfig =
environment.projectDir.childFile('.dart_tool/package_config.json');
await packageConfig.parent.create();
await packageConfig.create();
expect(
() => dryRunNativeAssetsAndroid(
projectUri: projectUri,
fileSystem: fileSystem,
buildRunner: FakeNativeAssetsBuildRunner(
packagesWithNativeAssetsResult: <Package>[
Package('bar', projectUri),
],
buildDryRunResult: const FakeNativeAssetsBuilderResult(
success: false,
),
),
),
throwsToolExit(
message:
'Building (dry run) native assets failed. See the logs for more details.',
),
);
});
testUsingContext('Native assets build error', overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true),
ProcessManager: () => FakeProcessManager.empty(),
}, () async {
final File packageConfig =
environment.projectDir.childFile('.dart_tool/package_config.json');
await packageConfig.parent.create();
await packageConfig.create();
for (final String hook in <String>['Building', 'Linking']) {
expect(
() => buildNativeAssetsAndroid(
androidArchs: <AndroidArch>[AndroidArch.arm64_v8a],
targetAndroidNdkApi: 21,
projectUri: projectUri,
buildMode: BuildMode.release,
fileSystem: fileSystem,
yamlParentDirectory: environment.buildDir.uri,
buildRunner: FakeNativeAssetsBuildRunner(
packagesWithNativeAssetsResult: <Package>[
Package('bar', projectUri),
],
buildResult: FakeNativeAssetsBuilderResult(
success: hook != 'Building',
),
linkResult: FakeNativeAssetsBuilderResult(
success: hook != 'Linking',
),
),
),
throwsToolExit(
message:
'$hook native assets failed. See the logs for more details.',
),
);
}
});
}
class _BuildRunnerWithoutNdk extends FakeNativeAssetsBuildRunner {
_BuildRunnerWithoutNdk({
super.packagesWithNativeAssetsResult = const <Package>[],
});
@override
Future<CCompilerConfigImpl> get ndkCCompilerConfigImpl async =>
throwToolExit('Android NDK Clang could not be found.');
}