diff --git a/lib/src/build_system/build_app.dart b/lib/src/build_system/build_app.dart index 1c9e5a8..c22801a 100644 --- a/lib/src/build_system/build_app.dart +++ b/lib/src/build_system/build_app.dart @@ -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 buildFlutterpiApp({ @@ -16,6 +18,7 @@ Future buildFlutterpiApp({ required FlutterpiTargetPlatform target, required BuildInfo buildInfo, required FlutterpiArtifactPaths artifactPaths, + required MoreOperatingSystemUtils operatingSystemUtils, FlutterProject? project, String? mainPath, String manifestPath = defaultManifestPath, @@ -38,6 +41,7 @@ Future buildFlutterpiApp({ buildInfo: buildInfo, artifactPaths: artifactPaths, outDir: outDir, + operatingSystemUtils: operatingSystemUtils, ); return PrebuiltFlutterpiAppBundle( @@ -53,6 +57,7 @@ Future buildFlutterpiBundle({ required FlutterpiTargetPlatform target, required BuildInfo buildInfo, required FlutterpiArtifactPaths artifactPaths, + required MoreOperatingSystemUtils operatingSystemUtils, FlutterProject? project, String? mainPath, String manifestPath = defaultManifestPath, @@ -89,7 +94,7 @@ Future 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'), @@ -127,6 +132,7 @@ Future buildFlutterpiBundle({ usage: globals.flutterUsage, platform: globals.platform, generateDartPluginRegistry: true, + operatingSystemUtils: operatingSystemUtils, ); final buildTarget = switch (buildInfo.mode) { diff --git a/lib/src/build_system/extended_environment.dart b/lib/src/build_system/extended_environment.dart new file mode 100644 index 0000000..6fb0c80 --- /dev/null +++ b/lib/src/build_system/extended_environment.dart @@ -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 defines = const {}, + Map inputs = const {}, + }) { + 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 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 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; +} diff --git a/lib/src/build_system/targets.dart b/lib/src/build_system/targets.dart index 1fc562d..a4f8ede 100644 --- a/lib/src/build_system/targets.dart +++ b/lib/src/build_system/targets.dart @@ -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({ @@ -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, @@ -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 @@ -222,22 +294,30 @@ class CopyFlutterpiEngine extends Target { ]; @override - Future build(Environment environment) async { + Future 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 = @@ -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, + ); } } } @@ -286,9 +375,19 @@ class FlutterpiAppElf extends Target { ]; @override - Future build(Environment environment) async { - final File outputFile = environment.buildDir.childFile('app.so'); - outputFile.copySync(environment.outputDir.childFile('app.so').path); + Future 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, + ); } } diff --git a/lib/src/cli/commands/build.dart b/lib/src/cli/commands/build.dart index a670fbe..dea4983 100644 --- a/lib/src/cli/commands/build.dart +++ b/lib/src/cli/commands/build.dart @@ -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, diff --git a/lib/src/devices/flutterpi_ssh/device.dart b/lib/src/devices/flutterpi_ssh/device.dart index 0ad34af..6ff388c 100644 --- a/lib/src/devices/flutterpi_ssh/device.dart +++ b/lib/src/devices/flutterpi_ssh/device.dart @@ -270,6 +270,7 @@ class FlutterpiSshDevice extends Device { buildInfo: debuggingOptions.buildInfo, artifactPaths: cache.artifactPaths, mainPath: mainPath, + operatingSystemUtils: os, ); }