Skip to content

Commit

Permalink
preserve executable bit when copying files to output directory
Browse files Browse the repository at this point in the history
Sometimes copying the flutter-pi binary, flutter engine (+dbgsyms) and app.so to the output directory will loose the file executable permission, which leads to errors when then trying to run the app on the target.
  • Loading branch information
ardera committed Aug 8, 2024
1 parent 60bc840 commit 975bb8c
Show file tree
Hide file tree
Showing 5 changed files with 243 additions and 24 deletions.
8 changes: 7 additions & 1 deletion lib/src/build_system/build_app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

import 'dart:async';
import 'package:file/file.dart';
import 'package:flutterpi_tool/src/build_system/extended_environment.dart';
import 'package:flutterpi_tool/src/build_system/targets.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';
import 'package:flutterpi_tool/src/fltool/common.dart';
import 'package:flutterpi_tool/src/fltool/globals.dart' as globals;
import 'package:flutterpi_tool/src/more_os_utils.dart';
import 'package:unified_analytics/unified_analytics.dart';

Future<FlutterpiAppBundle> buildFlutterpiApp({
Expand All @@ -16,6 +18,7 @@ Future<FlutterpiAppBundle> buildFlutterpiApp({
required FlutterpiTargetPlatform target,
required BuildInfo buildInfo,
required FlutterpiArtifactPaths artifactPaths,
required MoreOperatingSystemUtils operatingSystemUtils,
FlutterProject? project,
String? mainPath,
String manifestPath = defaultManifestPath,
Expand All @@ -38,6 +41,7 @@ Future<FlutterpiAppBundle> buildFlutterpiApp({
buildInfo: buildInfo,
artifactPaths: artifactPaths,
outDir: outDir,
operatingSystemUtils: operatingSystemUtils,
);

return PrebuiltFlutterpiAppBundle(
Expand All @@ -53,6 +57,7 @@ Future<void> buildFlutterpiBundle({
required FlutterpiTargetPlatform target,
required BuildInfo buildInfo,
required FlutterpiArtifactPaths artifactPaths,
required MoreOperatingSystemUtils operatingSystemUtils,
FlutterProject? project,
String? mainPath,
String manifestPath = defaultManifestPath,
Expand Down Expand Up @@ -89,7 +94,7 @@ Future<void> buildFlutterpiBundle({
}

// If the precompiled flag was not passed, force us into debug mode.
final environment = Environment(
final environment = ExtendedEnvironment(
projectDir: project.directory,
outputDir: outDir,
buildDir: project.dartTool.childDirectory('flutter_build'),
Expand Down Expand Up @@ -127,6 +132,7 @@ Future<void> buildFlutterpiBundle({
usage: globals.flutterUsage,
platform: globals.platform,
generateDartPluginRegistry: true,
operatingSystemUtils: operatingSystemUtils,
);

final buildTarget = switch (buildInfo.mode) {
Expand Down
112 changes: 112 additions & 0 deletions lib/src/build_system/extended_environment.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import 'package:file/file.dart';
import 'package:flutterpi_tool/src/fltool/common.dart';
import 'package:flutterpi_tool/src/more_os_utils.dart';
import 'package:process/process.dart';
import 'package:unified_analytics/unified_analytics.dart';

class ExtendedEnvironment implements Environment {
factory ExtendedEnvironment({
required Directory projectDir,
required Directory outputDir,
required Directory cacheDir,
required Directory flutterRootDir,
required FileSystem fileSystem,
required Logger logger,
required Artifacts artifacts,
required ProcessManager processManager,
required Platform platform,
required Usage usage,
required Analytics analytics,
String? engineVersion,
required bool generateDartPluginRegistry,
Directory? buildDir,
required MoreOperatingSystemUtils operatingSystemUtils,
Map<String, String> defines = const <String, String>{},
Map<String, String> inputs = const <String, String>{},
}) {
return ExtendedEnvironment.wrap(
operatingSystemUtils: operatingSystemUtils,
delegate: Environment(
projectDir: projectDir,
outputDir: outputDir,
cacheDir: cacheDir,
flutterRootDir: flutterRootDir,
fileSystem: fileSystem,
logger: logger,
artifacts: artifacts,
processManager: processManager,
platform: platform,
usage: usage,
analytics: analytics,
engineVersion: engineVersion,
generateDartPluginRegistry: generateDartPluginRegistry,
buildDir: buildDir,
defines: defines,
inputs: inputs,
),
);
}

ExtendedEnvironment.wrap({
required this.operatingSystemUtils,
required Environment delegate,
}) : _delegate = delegate;

final Environment _delegate;

@override
Analytics get analytics => _delegate.analytics;

@override
Artifacts get artifacts => _delegate.artifacts;

@override
Directory get buildDir => _delegate.buildDir;

@override
Directory get cacheDir => _delegate.cacheDir;

@override
Map<String, String> get defines => _delegate.defines;

@override
DepfileService get depFileService => _delegate.depFileService;

@override
String? get engineVersion => _delegate.engineVersion;

@override
FileSystem get fileSystem => _delegate.fileSystem;

@override
Directory get flutterRootDir => _delegate.flutterRootDir;

@override
bool get generateDartPluginRegistry => _delegate.generateDartPluginRegistry;

@override
Map<String, String> get inputs => _delegate.inputs;

@override
Logger get logger => _delegate.logger;

@override
Directory get outputDir => _delegate.outputDir;

@override
Platform get platform => _delegate.platform;

@override
ProcessManager get processManager => _delegate.processManager;

@override
Directory get projectDir => _delegate.projectDir;

@override
Directory get rootBuildDir => _delegate.rootBuildDir;

@override
Usage get usage => _delegate.usage;

final MoreOperatingSystemUtils operatingSystemUtils;
}
145 changes: 122 additions & 23 deletions lib/src/build_system/targets.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
import 'dart:async';

import 'package:file/file.dart';
import 'package:flutterpi_tool/src/build_system/extended_environment.dart';
import 'package:flutterpi_tool/src/cache.dart';
import 'package:flutterpi_tool/src/common.dart';
import 'package:flutterpi_tool/src/fltool/common.dart';
import 'package:flutterpi_tool/src/fltool/globals.dart';
import 'package:flutterpi_tool/src/more_os_utils.dart';

class ReleaseBundleFlutterpiAssets extends CompositeTarget {
ReleaseBundleFlutterpiAssets({
Expand Down Expand Up @@ -127,6 +130,51 @@ class CopyIcudtl extends Target {
}
}

extension _FileExecutableBits on File {
(bool owner, bool group, bool other) getExecutableBits() {
// ignore: constant_identifier_names
const S_IXUSR = 00100, S_IXGRP = 00010, S_IXOTH = 00001;

final stat = statSync();
final mode = stat.mode;

return (
(mode & S_IXUSR) != 0,
(mode & S_IXGRP) != 0,
(mode & S_IXOTH) != 0
);
}
}

void fixupExePermissions(
File input,
File output, {
required Platform platform,
required Logger logger,
required MoreOperatingSystemUtils os,
}) {
if (platform.isLinux || platform.isMacOS) {
final inputExeBits = input.getExecutableBits();
final outputExeBits = output.getExecutableBits();

if (outputExeBits != (true, true, true)) {
if (inputExeBits == outputExeBits) {
logger.printTrace(
'${input.basename} in cache was not universally executable. '
'Changing permissions...',
);
} else {
logger.printTrace(
'Copying ${input.basename} from cache to output directory did not preserve executable bit. '
'Changing permissions...',
);
}

os.chmod(output, 'ugo+x');
}
}
}

class CopyFlutterpiBinary extends Target {
CopyFlutterpiBinary({
required this.target,
Expand All @@ -147,7 +195,31 @@ class CopyFlutterpiBinary extends Target {

final outputFile = environment.outputDir.childFile('flutter-pi');

if (!outputFile.parent.existsSync()) {
outputFile.parent.createSync(recursive: true);
}
file.copySync(outputFile.path);

if (environment.platform.isLinux || environment.platform.isMacOS) {
final inputExeBits = file.getExecutableBits();
final outputExeBits = outputFile.getExecutableBits();

if (outputExeBits != (true, true, true)) {
if (inputExeBits == outputExeBits) {
environment.logger.printTrace(
'flutter-pi binary in cache was not universally executable. '
'Changing permissions...',
);
} else {
environment.logger.printTrace(
'Copying flutter-pi binary from cache to output directory did not preserve executable bit. '
'Changing permissions...',
);
}

os.chmod(outputFile, 'ugo+x');
}
}
}

@override
Expand Down Expand Up @@ -222,22 +294,30 @@ class CopyFlutterpiEngine extends Target {
];

@override
Future<void> build(Environment environment) async {
Future<void> build(covariant ExtendedEnvironment environment) async {
final outputFile = environment.outputDir.childFile('libflutter_engine.so');
if (!outputFile.parent.existsSync()) {
outputFile.parent.createSync(recursive: true);
}

_artifactPaths
.getEngine(
engineCacheDir: environment.cacheDir
.childDirectory('artifacts')
.childDirectory('engine'),
hostPlatform: _hostPlatform,
target: flutterpiTargetPlatform,
flavor: _engineFlavor,
)
.copySync(outputFile.path);
final engine = _artifactPaths.getEngine(
engineCacheDir: environment.cacheDir
.childDirectory('artifacts')
.childDirectory('engine'),
hostPlatform: _hostPlatform,
target: flutterpiTargetPlatform,
flavor: _engineFlavor,
);

engine.copySync(outputFile.path);

fixupExePermissions(
engine,
outputFile,
platform: environment.platform,
logger: environment.logger,
os: environment.operatingSystemUtils,
);

if (includeDebugSymbols) {
final dbgsymsOutputFile =
Expand All @@ -246,15 +326,24 @@ class CopyFlutterpiEngine extends Target {
dbgsymsOutputFile.parent.createSync(recursive: true);
}

(_artifactPaths as FlutterpiArtifactPathsV2)
.getEngineDbgsyms(
engineCacheDir: environment.cacheDir
.childDirectory('artifacts')
.childDirectory('engine'),
target: flutterpiTargetPlatform,
flavor: _engineFlavor,
)
.copySync(dbgsymsOutputFile.path);
final dbgsyms =
(_artifactPaths as FlutterpiArtifactPathsV2).getEngineDbgsyms(
engineCacheDir: environment.cacheDir
.childDirectory('artifacts')
.childDirectory('engine'),
target: flutterpiTargetPlatform,
flavor: _engineFlavor,
);

dbgsyms.copySync(dbgsymsOutputFile.path);

fixupExePermissions(
dbgsyms,
dbgsymsOutputFile,
platform: environment.platform,
logger: environment.logger,
os: environment.operatingSystemUtils,
);
}
}
}
Expand Down Expand Up @@ -286,9 +375,19 @@ class FlutterpiAppElf extends Target {
];

@override
Future<void> build(Environment environment) async {
final File outputFile = environment.buildDir.childFile('app.so');
outputFile.copySync(environment.outputDir.childFile('app.so').path);
Future<void> build(covariant ExtendedEnvironment environment) async {
final appElf = environment.buildDir.childFile('app.so');
final outputFile = environment.outputDir.childFile('app.so');

appElf.copySync(outputFile.path);

fixupExePermissions(
appElf,
outputFile,
platform: environment.platform,
logger: logger,
os: environment.operatingSystemUtils,
);
}
}

Expand Down
1 change: 1 addition & 0 deletions lib/src/cli/commands/build.dart
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ class BuildCommand extends FlutterpiCommand {
buildInfo: buildInfo,
mainPath: targetFile,
artifactPaths: flutterpiCache.artifactPaths,
operatingSystemUtils: os,

// for `--debug-unoptimized` build mode
unoptimized: flavor.unoptimized,
Expand Down
1 change: 1 addition & 0 deletions lib/src/devices/flutterpi_ssh/device.dart
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ class FlutterpiSshDevice extends Device {
buildInfo: debuggingOptions.buildInfo,
artifactPaths: cache.artifactPaths,
mainPath: mainPath,
operatingSystemUtils: os,
);
}

Expand Down

0 comments on commit 975bb8c

Please sign in to comment.