Unverified Commit 2337c64d authored by Daco Harkes's avatar Daco Harkes Committed by GitHub

Native assets support for Linux (#134031)

Support for FFI calls with `@Native external` functions through Native assets on Linux. This enables bundling native code without any build-system boilerplate code.

For more info see:

* https://github.com/flutter/flutter/issues/129757

### Implementation details for Linux.

Mainly follows the design of https://github.com/flutter/flutter/pull/130494.

Some differences are:

* Linux does not support cross compiling or compiling for multiple architectures, so this has not been implemented.
* Linux has no add2app.

The assets copying is done in the install-phase of the CMake build of a flutter app.
CMake requires the native assets folder to exist, so we create it also when the feature is disabled or there are no assets.

### Tests

This PR adds new tests to cover the various use cases.

* packages/flutter_tools/test/general.shard/linux/native_assets_test.dart
  * Unit tests the Linux-specific part of building native assets.

It also extends various existing tests:

* packages/flutter_tools/test/integration.shard/native_assets_test.dart
  * Runs (incl hot reload/hot restart), builds, builds frameworks for Linux and flutter-tester.
parent d6d90b0f
......@@ -951,8 +951,10 @@ targets:
{"dependency": "android_sdk", "version": "version:33v6"},
{"dependency": "chrome_and_driver", "version": "version:115.0"},
{"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"},
{"dependency": "open_jdk", "version": "version:11"},
{"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"}
{"dependency": "cmake", "version": "build_id:8787856497187628321"},
{"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"},
{"dependency": "ninja", "version": "version:1.9.0"},
{"dependency": "open_jdk", "version": "version:11"}
]
shard: tool_integration_tests
subshard: "1_4"
......@@ -975,8 +977,10 @@ targets:
{"dependency": "android_sdk", "version": "version:33v6"},
{"dependency": "chrome_and_driver", "version": "version:115.0"},
{"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"},
{"dependency": "open_jdk", "version": "version:11"},
{"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"}
{"dependency": "cmake", "version": "build_id:8787856497187628321"},
{"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"},
{"dependency": "ninja", "version": "version:1.9.0"},
{"dependency": "open_jdk", "version": "version:11"}
]
shard: tool_integration_tests
subshard: "2_4"
......@@ -999,8 +1003,10 @@ targets:
{"dependency": "android_sdk", "version": "version:33v6"},
{"dependency": "chrome_and_driver", "version": "version:115.0"},
{"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"},
{"dependency": "open_jdk", "version": "version:11"},
{"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"}
{"dependency": "cmake", "version": "build_id:8787856497187628321"},
{"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"},
{"dependency": "ninja", "version": "version:1.9.0"},
{"dependency": "open_jdk", "version": "version:11"}
]
shard: tool_integration_tests
subshard: "3_4"
......@@ -1023,8 +1029,10 @@ targets:
{"dependency": "android_sdk", "version": "version:33v6"},
{"dependency": "chrome_and_driver", "version": "version:115.0"},
{"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"},
{"dependency": "open_jdk", "version": "version:11"},
{"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"}
{"dependency": "cmake", "version": "build_id:8787856497187628321"},
{"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"},
{"dependency": "ninja", "version": "version:1.9.0"},
{"dependency": "open_jdk", "version": "version:11"}
]
shard: tool_integration_tests
subshard: "4_4"
......
......@@ -12,6 +12,7 @@ import '../../base/platform.dart';
import '../../build_info.dart';
import '../../dart/package_map.dart';
import '../../ios/native_assets.dart';
import '../../linux/native_assets.dart';
import '../../macos/native_assets.dart';
import '../../macos/xcode.dart';
import '../../native_assets.dart';
......@@ -118,6 +119,21 @@ class NativeAssets extends Target {
fileSystem: fileSystem,
buildRunner: buildRunner,
);
case TargetPlatform.linux_arm64:
case TargetPlatform.linux_x64:
final String? environmentBuildMode = environment.defines[kBuildMode];
if (environmentBuildMode == null) {
throw MissingDefineException(kBuildMode, name);
}
final BuildMode buildMode = BuildMode.fromCliName(environmentBuildMode);
(_, dependencies) = await buildNativeAssetsLinux(
targetPlatform: targetPlatform,
buildMode: buildMode,
projectUri: projectUri,
yamlParentDirectory: environment.buildDir.uri,
fileSystem: fileSystem,
buildRunner: buildRunner,
);
case TargetPlatform.tester:
if (const LocalPlatform().isMacOS) {
(_, dependencies) = await buildNativeAssetsMacOS(
......@@ -129,6 +145,15 @@ class NativeAssets extends Target {
buildRunner: buildRunner,
flutterTester: true,
);
} else if (const LocalPlatform().isLinux) {
(_, dependencies) = await buildNativeAssetsLinux(
buildMode: BuildMode.debug,
projectUri: projectUri,
yamlParentDirectory: environment.buildDir.uri,
fileSystem: fileSystem,
buildRunner: buildRunner,
flutterTester: true,
);
} else {
// TODO(dacoharkes): Implement other OSes. https://github.com/flutter/flutter/issues/129757
// Write the file we claim to have in the [outputs].
......@@ -142,8 +167,6 @@ class NativeAssets extends Target {
case TargetPlatform.android:
case TargetPlatform.fuchsia_arm64:
case TargetPlatform.fuchsia_x64:
case TargetPlatform.linux_arm64:
case TargetPlatform.linux_x64:
case TargetPlatform.web_javascript:
case TargetPlatform.windows_x64:
// TODO(dacoharkes): Implement other OSes. https://github.com/flutter/flutter/issues/129757
......
......@@ -17,6 +17,7 @@ import '../convert.dart';
import '../flutter_plugins.dart';
import '../globals.dart' as globals;
import '../migrations/cmake_custom_command_migration.dart';
import '../migrations/cmake_native_assets_migration.dart';
// Matches the following error and warning patterns:
// - <file path>:<line>:<column>: (fatal) error: <error...>
......@@ -45,6 +46,7 @@ Future<void> buildLinux(
final List<ProjectMigrator> migrators = <ProjectMigrator>[
CmakeCustomCommandMigration(linuxProject, logger),
CmakeNativeAssetsMigration(linuxProject, 'linux', logger),
];
final ProjectMigration migration = ProjectMigration(migrators);
......
// 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:native_assets_builder/native_assets_builder.dart' show BuildResult;
import 'package:native_assets_cli/native_assets_cli.dart' hide BuildMode;
import 'package:native_assets_cli/native_assets_cli.dart' as native_assets_cli;
import '../base/common.dart';
import '../base/file_system.dart';
import '../base/io.dart';
import '../build_info.dart';
import '../globals.dart' as globals;
import '../native_assets.dart';
/// Dry run the native builds.
///
/// This does not build native assets, it only simulates what the final paths
/// of all assets will be so that this can be embedded in the kernel file.
Future<Uri?> dryRunNativeAssetsLinux({
required NativeAssetsBuildRunner buildRunner,
required Uri projectUri,
bool flutterTester = false,
required FileSystem fileSystem,
}) async {
if (await hasNoPackageConfig(buildRunner) || await isDisabledAndNoNativeAssets(buildRunner)) {
return null;
}
final Uri buildUri_ = nativeAssetsBuildUri(projectUri, OS.linux);
final Iterable<Asset> nativeAssetPaths = await dryRunNativeAssetsLinuxInternal(
fileSystem,
projectUri,
flutterTester,
buildRunner,
);
final Uri nativeAssetsUri = await writeNativeAssetsYaml(
nativeAssetPaths,
buildUri_,
fileSystem,
);
return nativeAssetsUri;
}
Future<Iterable<Asset>> dryRunNativeAssetsLinuxInternal(
FileSystem fileSystem,
Uri projectUri,
bool flutterTester,
NativeAssetsBuildRunner buildRunner,
) async {
const OS targetOs = OS.linux;
final Uri buildUri_ = nativeAssetsBuildUri(projectUri, targetOs);
globals.logger.printTrace('Dry running native assets for $targetOs.');
final List<Asset> nativeAssets = (await buildRunner.dryRun(
linkModePreference: LinkModePreference.dynamic,
targetOs: targetOs,
workingDirectory: projectUri,
includeParentEnvironment: true,
))
.assets;
ensureNoLinkModeStatic(nativeAssets);
globals.logger.printTrace('Dry running native assets for $targetOs done.');
final Uri? absolutePath = flutterTester ? buildUri_ : null;
final Map<Asset, Asset> assetTargetLocations = _assetTargetLocations(nativeAssets, absolutePath);
final Iterable<Asset> nativeAssetPaths = assetTargetLocations.values;
return nativeAssetPaths;
}
/// Builds native assets.
///
/// If [targetPlatform] is omitted, the current target architecture is used.
///
/// If [flutterTester] is true, absolute paths are emitted in the native
/// assets mapping. This can be used for JIT mode without sandbox on the host.
/// This is used in `flutter test` and `flutter run -d flutter-tester`.
Future<(Uri? nativeAssetsYaml, List<Uri> dependencies)> buildNativeAssetsLinux({
required NativeAssetsBuildRunner buildRunner,
TargetPlatform? targetPlatform,
required Uri projectUri,
required BuildMode buildMode,
bool flutterTester = false,
Uri? yamlParentDirectory,
required FileSystem fileSystem,
}) async {
const OS targetOs = OS.linux;
final Uri buildUri_ = nativeAssetsBuildUri(projectUri, targetOs);
final Directory buildDir = fileSystem.directory(buildUri_);
if (!await buildDir.exists()) {
// CMake requires the folder to exist to do copying.
await buildDir.create(recursive: true);
}
if (await hasNoPackageConfig(buildRunner) || await isDisabledAndNoNativeAssets(buildRunner)) {
final Uri nativeAssetsYaml = await writeNativeAssetsYaml(<Asset>[], yamlParentDirectory ?? buildUri_, fileSystem);
return (nativeAssetsYaml, <Uri>[]);
}
final Target target = targetPlatform != null ? _getNativeTarget(targetPlatform) : Target.current;
final native_assets_cli.BuildMode buildModeCli = nativeAssetsBuildMode(buildMode);
globals.logger.printTrace('Building native assets for $target $buildModeCli.');
final BuildResult result = await buildRunner.build(
linkModePreference: LinkModePreference.dynamic,
target: target,
buildMode: buildModeCli,
workingDirectory: projectUri,
includeParentEnvironment: true,
cCompilerConfig: await buildRunner.cCompilerConfig,
);
final List<Asset> nativeAssets = result.assets;
final Set<Uri> dependencies = result.dependencies.toSet();
ensureNoLinkModeStatic(nativeAssets);
globals.logger.printTrace('Building native assets for $target done.');
final Uri? absolutePath = flutterTester ? buildUri_ : null;
final Map<Asset, Asset> assetTargetLocations = _assetTargetLocations(nativeAssets, absolutePath);
await _copyNativeAssetsLinux(
buildUri_,
assetTargetLocations,
buildMode,
fileSystem,
);
final Uri nativeAssetsUri = await writeNativeAssetsYaml(
assetTargetLocations.values,
yamlParentDirectory ?? buildUri_,
fileSystem,
);
return (nativeAssetsUri, dependencies.toList());
}
Map<Asset, Asset> _assetTargetLocations(
List<Asset> nativeAssets,
Uri? absolutePath,
) =>
<Asset, Asset>{
for (final Asset asset in nativeAssets) asset: _targetLocationLinux(asset, absolutePath),
};
Asset _targetLocationLinux(Asset asset, Uri? absolutePath) {
final AssetPath path = asset.path;
switch (path) {
case AssetSystemPath _:
case AssetInExecutable _:
case AssetInProcess _:
return asset;
case AssetAbsolutePath _:
final String fileName = path.uri.pathSegments.last;
Uri uri;
if (absolutePath != null) {
// Flutter tester needs full host paths.
uri = absolutePath.resolve(fileName);
} else {
// Flutter Desktop needs "absolute" paths inside the app.
// "relative" in the context of native assets would be relative to the
// kernel or aot snapshot.
uri = Uri(path: fileName);
}
return asset.copyWith(path: AssetAbsolutePath(uri));
}
throw Exception('Unsupported asset path type ${path.runtimeType} in asset $asset');
}
/// Extract the [Target] from a [TargetPlatform].
Target _getNativeTarget(TargetPlatform targetPlatform) {
switch (targetPlatform) {
case TargetPlatform.linux_x64:
return Target.linuxX64;
case TargetPlatform.linux_arm64:
return Target.linuxArm64;
case TargetPlatform.android:
case TargetPlatform.ios:
case TargetPlatform.darwin:
case TargetPlatform.windows_x64:
case TargetPlatform.fuchsia_arm64:
case TargetPlatform.fuchsia_x64:
case TargetPlatform.tester:
case TargetPlatform.web_javascript:
case TargetPlatform.android_arm:
case TargetPlatform.android_arm64:
case TargetPlatform.android_x64:
case TargetPlatform.android_x86:
throw Exception('Unknown targetPlatform: $targetPlatform.');
}
}
Future<void> _copyNativeAssetsLinux(
Uri buildUri,
Map<Asset, Asset> assetTargetLocations,
BuildMode buildMode,
FileSystem fileSystem,
) async {
if (assetTargetLocations.isNotEmpty) {
globals.logger.printTrace('Copying native assets to ${buildUri.toFilePath()}.');
final Directory buildDir = fileSystem.directory(buildUri.toFilePath());
if (!buildDir.existsSync()) {
buildDir.createSync(recursive: true);
}
for (final MapEntry<Asset, Asset> assetMapping in assetTargetLocations.entries) {
final Uri source = (assetMapping.key.path as AssetAbsolutePath).uri;
final Uri target = (assetMapping.value.path as AssetAbsolutePath).uri;
final Uri targetUri = buildUri.resolveUri(target);
final String targetFullPath = targetUri.toFilePath();
await fileSystem.file(source).copy(targetFullPath);
}
globals.logger.printTrace('Copying native assets done.');
}
}
/// Flutter expects `clang++` to be on the path on Linux hosts.
///
/// Search for the accompanying `clang`, `ar`, and `ld`.
Future<CCompilerConfig> cCompilerConfigLinux() async {
const String kClangPlusPlusBinary = 'clang++';
const String kClangBinary = 'clang';
const String kArBinary = 'llvm-ar';
const String kLdBinary = 'ld.lld';
final ProcessResult whichResult = await globals.processManager.run(<String>['which', kClangPlusPlusBinary]);
if (whichResult.exitCode != 0) {
throwToolExit('Failed to find $kClangPlusPlusBinary on PATH.');
}
File clangPpFile = globals.fs.file((whichResult.stdout as String).trim());
clangPpFile = globals.fs.file(await clangPpFile.resolveSymbolicLinks());
final Directory clangDir = clangPpFile.parent;
final Map<String, Uri> binaryPaths = <String, Uri>{};
for (final String binary in <String>[kClangBinary, kArBinary, kLdBinary]) {
final File binaryFile = clangDir.childFile(binary);
if (!await binaryFile.exists()) {
throwToolExit("Failed to find $binary relative to $clangPpFile: $binaryFile doesn't exist.");
}
binaryPaths[binary] = binaryFile.uri;
}
return CCompilerConfig(
ar: binaryPaths[kArBinary],
cc: binaryPaths[kClangBinary],
ld: binaryPaths[kLdBinary],
);
}
// 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 '../base/file_system.dart';
import '../base/project_migrator.dart';
import '../cmake_project.dart';
/// Adds the snippet to the CMake file that copies the native assets.
///
/// ```cmake
/// # Copy the native assets provided by the build.dart from all packages.
/// set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/")
/// install(DIRECTORY "${NATIVE_ASSETS_DIR}"
/// DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
/// COMPONENT Runtime)
/// ```
class CmakeNativeAssetsMigration extends ProjectMigrator {
CmakeNativeAssetsMigration(CmakeBasedProject project, this.os, super.logger)
: _cmakeFile = project.managedCmakeFile;
final File _cmakeFile;
final String os;
@override
void migrate() {
if (!_cmakeFile.existsSync()) {
logger.printTrace('CMake project not found, skipping install() NATIVE_ASSETS_DIR migration.');
return;
}
final String originalProjectContents = _cmakeFile.readAsStringSync();
if (originalProjectContents.contains('set(NATIVE_ASSETS_DIR')) {
// Command is already present.
return;
}
final String copyNativeAssetsCommand = '''
# Copy the native assets provided by the build.dart from all packages.
set(NATIVE_ASSETS_DIR "\${PROJECT_BUILD_DIR}native_assets/$os/")
install(DIRECTORY "\${NATIVE_ASSETS_DIR}"
DESTINATION "\${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
''';
// Insert the new command after the bundled libraries loop.
const String bundleLibrariesCommandEnd = r'''
endforeach(bundled_library)
''';
String newProjectContents = originalProjectContents;
newProjectContents = originalProjectContents.replaceFirst(
bundleLibrariesCommandEnd,
'$bundleLibrariesCommandEnd$copyNativeAssetsCommand',
);
if (originalProjectContents != newProjectContents) {
logger.printStatus('CMake missing install() NATIVE_ASSETS_DIR command, updating.');
_cmakeFile.writeAsStringSync(newProjectContents);
}
}
}
......@@ -19,6 +19,7 @@ import 'cache.dart';
import 'features.dart';
import 'globals.dart' as globals;
import 'ios/native_assets.dart';
import 'linux/native_assets.dart';
import 'macos/native_assets.dart';
import 'macos/native_assets_host.dart';
import 'resident_runner.dart';
......@@ -168,6 +169,9 @@ class NativeAssetsBuildRunnerImpl implements NativeAssetsBuildRunner {
if (globals.platform.isMacOS || globals.platform.isIOS) {
return cCompilerConfigMacOS();
}
if (globals.platform.isLinux) {
return cCompilerConfigLinux();
}
throwToolExit(
'Native assets feature not yet implemented for Linux, Windows and Android.',
);
......@@ -333,6 +337,13 @@ Future<Uri?> dryRunNativeAssets({
fileSystem: fileSystem,
buildRunner: buildRunner,
);
} else if (const LocalPlatform().isLinux) {
nativeAssetsYaml = await dryRunNativeAssetsLinux(
projectUri: projectUri,
flutterTester: true,
fileSystem: fileSystem,
buildRunner: buildRunner,
);
} else {
await ensureNoNativeAssetsOrOsIsSupported(
projectUri,
......@@ -342,6 +353,13 @@ Future<Uri?> dryRunNativeAssets({
);
nativeAssetsYaml = null;
}
case build_info.TargetPlatform.linux_arm64:
case build_info.TargetPlatform.linux_x64:
nativeAssetsYaml = await dryRunNativeAssetsLinux(
projectUri: projectUri,
fileSystem: fileSystem,
buildRunner: buildRunner,
);
case build_info.TargetPlatform.android_arm:
case build_info.TargetPlatform.android_arm64:
case build_info.TargetPlatform.android_x64:
......@@ -349,8 +367,6 @@ Future<Uri?> dryRunNativeAssets({
case build_info.TargetPlatform.android:
case build_info.TargetPlatform.fuchsia_arm64:
case build_info.TargetPlatform.fuchsia_x64:
case build_info.TargetPlatform.linux_arm64:
case build_info.TargetPlatform.linux_x64:
case build_info.TargetPlatform.web_javascript:
case build_info.TargetPlatform.windows_x64:
await ensureNoNativeAssetsOrOsIsSupported(
......@@ -382,7 +398,12 @@ Future<Uri?> dryRunNativeAssetsMultipeOSes({
if (targetPlatforms.contains(build_info.TargetPlatform.darwin) ||
(targetPlatforms.contains(build_info.TargetPlatform.tester) && OS.current == OS.macOS))
...await dryRunNativeAssetsMacOSInternal(fileSystem, projectUri, false, buildRunner),
if (targetPlatforms.contains(build_info.TargetPlatform.ios)) ...await dryRunNativeAssetsIOSInternal(fileSystem, projectUri, buildRunner)
if (targetPlatforms.contains(build_info.TargetPlatform.linux_arm64) ||
targetPlatforms.contains(build_info.TargetPlatform.linux_x64) ||
(targetPlatforms.contains(build_info.TargetPlatform.tester) && OS.current == OS.linux))
...await dryRunNativeAssetsLinuxInternal(fileSystem, projectUri, false, buildRunner),
if (targetPlatforms.contains(build_info.TargetPlatform.ios))
...await dryRunNativeAssetsIOSInternal(fileSystem, projectUri, buildRunner)
];
final Uri nativeAssetsUri = await writeNativeAssetsYaml(nativeAssetPaths, buildUri_, fileSystem);
return nativeAssetsUri;
......
......@@ -16,6 +16,7 @@ import '../bundle.dart';
import '../compile.dart';
import '../flutter_plugins.dart';
import '../globals.dart' as globals;
import '../linux/native_assets.dart';
import '../macos/native_assets.dart';
import '../native_assets.dart';
import '../project.dart';
......@@ -181,7 +182,15 @@ class TestCompiler {
flutterTester: true,
fileSystem: globals.fs,
buildRunner: buildRunner,
);
);
} else if (globals.platform.isLinux) {
(nativeAssetsYaml, _) = await buildNativeAssetsLinux(
buildMode: BuildMode.debug,
projectUri: projectUri,
flutterTester: true,
fileSystem: globals.fs,
buildRunner: buildRunner,
);
} else {
await ensureNoNativeAssetsOrOsIsSupported(
projectUri,
......
......@@ -127,6 +127,12 @@ foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
COMPONENT Runtime)
endforeach(bundled_library)
# Copy the native assets provided by the build.dart from all packages.
set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/")
install(DIRECTORY "${NATIVE_ASSETS_DIR}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
# Fully re-copy the assets directory on each build to avoid having stale files
# from a previous install.
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
......
......@@ -8,6 +8,7 @@ import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/terminal.dart';
import 'package:flutter_tools/src/cmake_project.dart';
import 'package:flutter_tools/src/migrations/cmake_custom_command_migration.dart';
import 'package:flutter_tools/src/migrations/cmake_native_assets_migration.dart';
import 'package:test/fake.dart';
import '../../src/common.dart';
......@@ -155,6 +156,132 @@ add_custom_command(
expect(testLogger.statusText, contains('add_custom_command() missing VERBATIM or FLUTTER_TARGET_PLATFORM, updating.'));
});
});
group('migrate add install() NATIVE_ASSETS_DIR command', () {
late MemoryFileSystem memoryFileSystem;
late BufferLogger testLogger;
late FakeCmakeProject mockCmakeProject;
late File managedCmakeFile;
setUp(() {
memoryFileSystem = MemoryFileSystem.test();
managedCmakeFile = memoryFileSystem.file('CMakeLists.txtx');
testLogger = BufferLogger(
terminal: Terminal.test(),
outputPreferences: OutputPreferences.test(),
);
mockCmakeProject = FakeCmakeProject(managedCmakeFile);
});
testWithoutContext('skipped if files are missing', () {
final CmakeNativeAssetsMigration cmakeProjectMigration = CmakeNativeAssetsMigration(
mockCmakeProject,
'linux',
testLogger,
);
cmakeProjectMigration.migrate();
expect(managedCmakeFile.existsSync(), isFalse);
expect(testLogger.traceText, contains('CMake project not found, skipping install() NATIVE_ASSETS_DIR migration.'));
expect(testLogger.statusText, isEmpty);
});
testWithoutContext('skipped if nothing to migrate', () {
const String contents = 'Nothing to migrate';
managedCmakeFile.writeAsStringSync(contents);
final DateTime projectLastModified = managedCmakeFile.lastModifiedSync();
final CmakeNativeAssetsMigration cmakeProjectMigration = CmakeNativeAssetsMigration(
mockCmakeProject,
'linux',
testLogger,
);
cmakeProjectMigration.migrate();
expect(managedCmakeFile.lastModifiedSync(), projectLastModified);
expect(managedCmakeFile.readAsStringSync(), contents);
expect(testLogger.statusText, isEmpty);
});
testWithoutContext('skipped if already migrated', () {
const String contents = r'''
# Copy the native assets provided by the build.dart from all packages.
set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/")
install(DIRECTORY "${NATIVE_ASSETS_DIR}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
''';
managedCmakeFile.writeAsStringSync(contents);
final DateTime projectLastModified = managedCmakeFile.lastModifiedSync();
final CmakeNativeAssetsMigration cmakeProjectMigration = CmakeNativeAssetsMigration(
mockCmakeProject,
'linux',
testLogger,
);
cmakeProjectMigration.migrate();
expect(managedCmakeFile.lastModifiedSync(), projectLastModified);
expect(managedCmakeFile.readAsStringSync(), contents);
expect(testLogger.statusText, isEmpty);
});
// TODO(dacoharkes): Add test for Windows when adding Windows support. https://github.com/flutter/flutter/issues/129757
testWithoutContext('is migrated to copy native assets', () {
managedCmakeFile.writeAsStringSync(r'''
foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
install(FILES "${bundled_library}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endforeach(bundled_library)
# Fully re-copy the assets directory on each build to avoid having stale files
# from a previous install.
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
install(CODE "
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
" COMPONENT Runtime)
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
''');
final CmakeNativeAssetsMigration cmakeProjectMigration = CmakeNativeAssetsMigration(
mockCmakeProject,
'linux',
testLogger,
);
cmakeProjectMigration.migrate();
expect(managedCmakeFile.readAsStringSync(), r'''
foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
install(FILES "${bundled_library}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endforeach(bundled_library)
# Copy the native assets provided by the build.dart from all packages.
set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/")
install(DIRECTORY "${NATIVE_ASSETS_DIR}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
# Fully re-copy the assets directory on each build to avoid having stale files
# from a previous install.
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
install(CODE "
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
" COMPONENT Runtime)
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
''');
expect(testLogger.statusText, contains('CMake missing install() NATIVE_ASSETS_DIR command, updating.'));
});
});
});
}
......
......@@ -17,6 +17,7 @@ import 'dart:io';
import 'package:file/file.dart';
import 'package:file_testing/file_testing.dart';
import 'package:native_assets_cli/native_assets_cli.dart';
import '../src/common.dart';
import 'test_utils.dart' show fileSystem, platform;
......@@ -56,10 +57,8 @@ const String packageName = 'package_with_native_assets';
const String exampleAppName = '${packageName}_example';
const String dylibName = 'lib$packageName.dylib';
void main() {
if (!platform.isMacOS) {
if (!platform.isMacOS && !platform.isLinux) {
// TODO(dacoharkes): Implement other OSes. https://github.com/flutter/flutter/issues/129757
return;
}
......@@ -148,6 +147,9 @@ void main() {
if (device == 'macos') {
expectDylibIsBundledMacOS(exampleDirectory, buildMode);
}
if (device == 'linux') {
expectDylibIsBundledLinux(exampleDirectory, buildMode);
}
if (device == hostOs) {
expectCCompilerIsConfigured(exampleDirectory);
}
......@@ -201,6 +203,8 @@ void main() {
expectDylibIsBundledMacOS(exampleDirectory, buildMode);
} else if (buildSubcommand == 'ios') {
expectDylibIsBundledIos(exampleDirectory, buildMode);
} else if (buildSubcommand == 'linux') {
expectDylibIsBundledLinux(exampleDirectory, buildMode);
}
expectCCompilerIsConfigured(exampleDirectory);
});
......@@ -278,7 +282,7 @@ void expectDylibIsBundledMacOS(Directory appDirectory, String buildMode) {
expect(appBundle, exists);
final Directory dylibsFolder = appBundle.childDirectory('Contents/Frameworks');
expect(dylibsFolder, exists);
final File dylib = dylibsFolder.childFile(dylibName);
final File dylib = dylibsFolder.childFile(OS.macOS.dylibFileName(packageName));
expect(dylib, exists);
}
......@@ -287,7 +291,21 @@ void expectDylibIsBundledIos(Directory appDirectory, String buildMode) {
expect(appBundle, exists);
final Directory dylibsFolder = appBundle.childDirectory('Frameworks');
expect(dylibsFolder, exists);
final File dylib = dylibsFolder.childFile(dylibName);
final File dylib = dylibsFolder.childFile(OS.iOS.dylibFileName(packageName));
expect(dylib, exists);
}
/// Checks that dylibs are bundled.
///
/// Sample path: build/linux/x64/release/bundle/lib/libmy_package.so
void expectDylibIsBundledLinux(Directory appDirectory, String buildMode) {
// Linux does not support cross compilation, so always only check current architecture.
final String architecture = Architecture.current.dartPlatform;
final Directory appBundle = appDirectory.childDirectory('build/$hostOs/$architecture/$buildMode/bundle/');
expect(appBundle, exists);
final Directory dylibsFolder = appBundle.childDirectory('lib/');
expect(dylibsFolder, exists);
final File dylib = dylibsFolder.childFile(OS.linux.dylibFileName(packageName));
expect(dylib, exists);
}
......@@ -296,7 +314,7 @@ void expectDylibIsBundledIos(Directory appDirectory, String buildMode) {
void expectDylibIsBundledWithFrameworks(Directory appDirectory, String buildMode, String os) {
final Directory frameworksFolder = appDirectory.childDirectory('build/$os/framework/${buildMode.upperCaseFirst()}');
expect(frameworksFolder, exists);
final File dylib = frameworksFolder.childFile(dylibName);
final File dylib = frameworksFolder.childFile(OS.macOS.dylibFileName(packageName));
expect(dylib, exists);
}
......
......@@ -6,6 +6,7 @@ import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:meta/meta.dart';
import 'package:process/process.dart';
......@@ -177,9 +178,15 @@ Future<ProcessTestResult> runFlutter(
minutes:
10), // must be less than test timeout of 15 minutes! See ../../dart_test.yaml.
}) async {
const LocalPlatform platform = LocalPlatform();
final Stopwatch clock = Stopwatch()..start();
final Process process = await processManager.start(
<String>[flutterBin, ...arguments],
<String>[
// In a container with no X display, use the virtual framebuffer.
if (platform.isLinux && (platform.environment['DISPLAY'] ?? '').isEmpty) '/usr/bin/xvfb-run',
flutterBin,
...arguments,
],
workingDirectory: workingDirectory,
);
final List<LogLine> logs = <LogLine>[];
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment