Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions lib/src/authenticating_artifact_updater.dart
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,21 @@ class AuthenticatingArtifactUpdater implements ArtifactUpdater {
@visibleForTesting
final List<File> downloadedFiles = <File>[];

// Compat with Flutter 3.44+ ArtifactUpdater progress-context API.
@override
void setProgressContext({
required int artifactIndex,
required int artifactTotal,
required int downloadTotal,
int downloadIndex = 0,
}) {}

@override
void resetProgressContext() {}

@override
String formatProgressMessage(String artifactName) => artifactName;

static const Set<String> _denylistedBasenames = <String>{
'entitlements.txt',
'without_entitlements.txt',
Expand Down
53 changes: 53 additions & 0 deletions lib/src/build_system/build_app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ class AppBuilder {
);
artifacts ??= globals.flutterpiArtifacts;

_ensureLinuxNativeAssetsCompilerConfig(
outputDir: outDir,
buildInfo: buildInfo,
target: target,
);

// We can still build debug for non-generic platforms of course, the correct
// (generic) target must be chosen in the caller in that case.
if (!target.isGeneric && buildInfo.mode == fl.BuildMode.debug) {
Expand Down Expand Up @@ -161,6 +167,53 @@ class AppBuilder {
return;
}

void _ensureLinuxNativeAssetsCompilerConfig({
required Directory outputDir,
required fl.BuildInfo buildInfo,
required FlutterpiTargetPlatform target,
}) {
final architecture = switch (target) {
FlutterpiTargetPlatform.genericX64 => 'x64',
FlutterpiTargetPlatform.genericRiscv64 => 'riscv64',
FlutterpiTargetPlatform.genericAArch64 ||
FlutterpiTargetPlatform.pi3_64 ||
FlutterpiTargetPlatform.pi4_64 =>
'arm64',
FlutterpiTargetPlatform.genericArmV7 ||
FlutterpiTargetPlatform.pi3 ||
FlutterpiTargetPlatform.pi4 =>
// Flutter's native-assets API has no linux_arm target. Preserve the
// historical behavior until Flutter adds one.
'arm64',
};
final cmakeDirectory = outputDir
.childDirectory('linux')
.childDirectory(architecture)
.childDirectory(buildInfo.mode.cliName);
final cmakeCache = cmakeDirectory.childFile('CMakeCache.txt');
if (cmakeCache.existsSync()) {
return;
}

final clangPp = _operatingSystemUtils.which('clang++');
final archiver = _operatingSystemUtils.which('ar');
final linker = _operatingSystemUtils.which('ld');
if (clangPp == null || archiver == null || linker == null) {
// Unit tests and builds without native-asset hooks don't need this
// compatibility file. If hooks are present, Flutter will report the
// missing toolchain when it tries to configure them.
return;
}

cmakeDirectory.createSync(recursive: true);
cmakeCache.writeAsStringSync('''
// Generated by flutterpi_tool for Flutter native-assets build hooks.
CMAKE_AR:FILEPATH=${archiver.path}
CMAKE_CXX_COMPILER:FILEPATH=${clangPp.path}
CMAKE_LINKER:FILEPATH=${linker.path}
''');
}

Future<FlutterpiAppBundle> buildBundle({
required String id,
required FlutterpiHostPlatform host,
Expand Down
47 changes: 47 additions & 0 deletions lib/src/build_system/native_assets.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import 'dart:convert';

/// Rewrites stock Linux bundled-code locations for a flutter-pi bundle.
///
/// Flutter's Linux runner CMake installs `DynamicLoadingBundled` assets into a
/// directory covered by the runner's `$ORIGIN/lib` RUNPATH. The generated
/// manifest therefore encodes those basenames as `absolute`. flutter-pi has no
/// runner CMake install phase or equivalent RUNPATH; [copiedBasenames] are
/// installed in its bundle working directory instead. A `./` prefix is
/// required because Linux `dlopen` does not search the working directory for a
/// bare library basename.
///
/// Only `absolute` entries whose basename was actually copied are changed.
/// System/relative/process/executable assets and unrelated absolute paths are
/// preserved exactly, apart from JSON whitespace.
String rewriteNativeAssetsManifestForFlutterPi(
String manifestJson,
Set<String> copiedBasenames,
) {
final document = jsonDecode(manifestJson);
if (document is! Map<String, dynamic>) {
throw const FormatException(
'Native Assets manifest root must be an object.',
);
}
final nativeAssets = document['native-assets'];
if (nativeAssets is! Map<String, dynamic>) {
return jsonEncode(document);
}

for (final targetAssets in nativeAssets.values) {
if (targetAssets is! Map<String, dynamic>) continue;
for (final location in targetAssets.values) {
if (location is! List<dynamic> || location.length != 2) continue;
final kind = location[0];
final path = location[1];
if (kind == 'absolute' &&
path is String &&
copiedBasenames.contains(path)) {
location[0] = 'relative';
location[1] = './$path';
}
}
}

return jsonEncode(document);
}
44 changes: 44 additions & 0 deletions lib/src/build_system/targets.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'dart:async';

import 'package:flutterpi_tool/src/artifacts.dart';
import 'package:flutterpi_tool/src/build_system/extended_environment.dart';
import 'package:flutterpi_tool/src/build_system/native_assets.dart';
import 'package:flutterpi_tool/src/cli/flutterpi_command.dart';
import 'package:flutterpi_tool/src/common.dart';
import 'package:flutterpi_tool/src/fltool/common.dart';
Expand Down Expand Up @@ -478,7 +479,9 @@ class CopyFlutterAssets extends Target {

@override
List<Target> get dependencies => <Target>[
const DartBuildForNative(),
const KernelSnapshot(),
const InstallCodeAssets(),
];

@override
Expand Down Expand Up @@ -553,6 +556,47 @@ class CopyFlutterAssets extends Target {
dartHookResult: dartHookResult,
);

// Flutter's stock Linux embedding finishes Native Assets installation in
// the runner's CMake install step: libraries built under
// native_assets/linux/ are copied next to the executable, matching the
// basename paths in NativeAssetsManifest.json. flutter-pi has no runner
// CMake phase, so perform that final placement here. Without it, hooks run
// and the manifest is generated, but @Native resolution fails at runtime.
if (layout == FilesystemLayout.flutterPi) {
final nativeAssetsDirectory = environment.outputDir
.childDirectory('native_assets')
.childDirectory('linux');
final copiedBasenames = <String>{};
if (nativeAssetsDirectory.existsSync()) {
for (final entity in nativeAssetsDirectory.listSync()) {
if (entity is! File) {
continue;
}
final installed = outputDir.childFile(entity.basename);
entity.copySync(installed.path);
copiedBasenames.add(entity.basename);
depfile.inputs.add(entity);
depfile.outputs.add(installed);
}
}

// installCodeAssets emits Linux's stock runner representation:
// ["absolute", "libfoo.so"]. The stock runner has an $ORIGIN/lib
// RUNPATH, but flutter-pi does not. Mark only the basenames installed
// above as explicit paths in the bundle working directory. The `./`
// prefix is significant: Linux dlopen does not search the working
// directory when given only a basename.
final manifest = outputDir.childFile('NativeAssetsManifest.json');
if (manifest.existsSync() && copiedBasenames.isNotEmpty) {
manifest.writeAsStringSync(
rewriteNativeAssetsManifestForFlutterPi(
manifest.readAsStringSync(),
copiedBasenames,
),
);
}
}

environment.depFileService.writeToFile(
depfile,
environment.buildDir.childFile('flutter_assets.d'),
Expand Down
28 changes: 28 additions & 0 deletions lib/src/cli/command_runner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,34 @@ class FlutterpiToolCommandRunner extends CommandRunner<void>
'Print the address of the Dart Tooling Daemon, if one is hosted by the Flutter CLI.',
hide: !verboseHelp,
);

argParser.addOption(
'github-artifacts-repo',
valueHelp: 'owner/repository',
help: 'Download engine artifacts from a custom GitHub repository.',
hide: !verboseHelp,
);

argParser.addOption(
'github-artifacts-runid',
valueHelp: 'run-id',
help: 'Download engine artifacts from a specific GitHub Actions run.',
hide: !verboseHelp,
);

argParser.addOption(
'github-artifacts-engine-version',
valueHelp: 'engine-hash',
help: 'The engine version available in the selected workflow run.',
hide: !verboseHelp,
);

argParser.addOption(
'github-artifacts-auth-token',
valueHelp: 'token',
help: 'GitHub token used to download workflow artifacts.',
hide: !verboseHelp,
);
}

@override
Expand Down
43 changes: 37 additions & 6 deletions lib/src/cli/flutterpi_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'dart:async';

import 'package:args/command_runner.dart';
import 'package:file/file.dart';
import 'package:flutterpi_tool/src/artifacts.dart';
import 'package:flutterpi_tool/src/cache.dart';
import 'package:flutterpi_tool/src/common.dart';
import 'package:flutterpi_tool/src/devices/flutterpi_ssh/device.dart';
Expand Down Expand Up @@ -42,10 +43,8 @@ mixin FlutterpiCommandMixin on fl.FlutterCommand {
if (globals.platform.environment['GITHUB_TOKEN'] case final envToken?) {
globals.logger.printTrace('Using GITHUB_TOKEN from environment.');
token = envToken;
} else if (argParser.options.containsKey('github-artifacts-auth-token')) {
token = stringArg('github-artifacts-auth-token');
} else {
token = null;
token = stringArg('github-artifacts-auth-token', global: true);
}

return MyGithub.caching(
Expand All @@ -64,9 +63,10 @@ mixin FlutterpiCommandMixin on fl.FlutterCommand {
required ProcessManager processManager,
http.Client? httpClient,
}) {
final repo = stringArg('github-artifacts-repo');
final runId = stringArg('github-artifacts-runid');
final githubEngineHash = stringArg('github-artifacts-engine-version');
final repo = stringArg('github-artifacts-repo', global: true);
final runId = stringArg('github-artifacts-runid', global: true);
final githubEngineHash =
stringArg('github-artifacts-engine-version', global: true);

if (runId != null) {
return FlutterpiCache.fromWorkflow(
Expand Down Expand Up @@ -456,6 +456,37 @@ mixin FlutterpiCommandMixin on fl.FlutterCommand {
String? get debugLogsDirectoryPath => null;

Future<T> runWithContext<T>(FutureOr<T> Function() fn) async {
final usesCustomArtifacts =
stringArg('github-artifacts-repo', global: true) != null ||
stringArg('github-artifacts-runid', global: true) != null ||
stringArg('github-artifacts-engine-version', global: true) != null;
if (usesCustomArtifacts) {
_contextOverrides.putIfAbsent(
fl.Cache,
() => () => createCustomCache(
fs: globals.fs,
shutdownHooks: globals.shutdownHooks,
logger: globals.logger,
platform: globals.platform,
os: globals.os as MoreOperatingSystemUtils,
projectFactory: globals.projectFactory,
processManager: globals.processManager,
),
);
_contextOverrides.putIfAbsent(
fl.Artifacts,
() => () => CachedFlutterpiArtifacts(
inner: fl.CachedArtifacts(
fileSystem: globals.fs,
platform: globals.platform,
cache: globals.cache,
operatingSystemUtils: globals.os,
),
cache: globals.flutterpiCache,
),
);
}

return fl.context.run(
body: fn,
overrides: _contextOverrides,
Expand Down
4 changes: 2 additions & 2 deletions lib/src/executable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ Future<void> main(List<String> args) async {
}

await exitWithHooks(
0,
e.exitCode ?? 1,
shutdownHooks: globals.shutdownHooks,
logger: globals.logger,
);
Expand All @@ -79,7 +79,7 @@ Future<void> main(List<String> args) async {
globals.printStatus(e.usage);

await exitWithHooks(
0,
1,
shutdownHooks: globals.shutdownHooks,
logger: globals.logger,
);
Expand Down
1 change: 1 addition & 0 deletions lib/src/more_os_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ class MoreOperatingSystemUtilsWrapper implements MoreOperatingSystemUtils {
HostPlatform.linux_arm64 => FlutterpiHostPlatform.linuxARM64,
HostPlatform.windows_x64 => FlutterpiHostPlatform.windowsX64,
HostPlatform.windows_arm64 => FlutterpiHostPlatform.windowsARM64,
_ => throw UnsupportedError('Unsupported host platform: $hostPlatform'),
};
}

Expand Down
Loading
Loading