Skip to content

Commit

Permalink
wip: any_app_packager package add package-any-app executable
Browse files Browse the repository at this point in the history
  • Loading branch information
lijy91 committed Sep 2, 2024
1 parent 04f2a1d commit 4b6693a
Show file tree
Hide file tree
Showing 6 changed files with 325 additions and 21 deletions.
211 changes: 209 additions & 2 deletions packages/any_app_packager/bin/main.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,210 @@
void main() {
print('any_app_packager is still under development');
import 'dart:io';

import 'package:any_app_packager/src/any_app_packager.dart';
import 'package:args/args.dart';
import 'package:args/command_runner.dart';

/// Package an application bundle for a specific platform and target
///
/// This command wrapper defines, parses and transforms all passed arguments,
/// so that they may be passed to `flutter_distributor`. The distributor will
/// then build an application bundle using `flutter_app_packager`.
class PackageCommand extends Command {
PackageCommand(this.appPackager) {
argParser.addOption(
'platform',
valueHelp: [
'android',
'ios',
'linux',
'macos',
'windows',
'web',
].join(','),
help: 'The platform to package the application for',
);

argParser.addOption(
'targets',
aliases: ['target'],
valueHelp: [
'apk',
'aab',
'appimage',
'deb',
'dmg',
'exe',
'ipa',
'msix',
'pkg',
'rpm',
'zip',
].join(','),
help: 'Comma separated list of bundle types to build.',
);

argParser.addOption('channel', valueHelp: '');
argParser.addOption('artifact-name', valueHelp: '');

argParser.addFlag(
'skip-clean',
help: 'Whether or not to skip \'flutter clean\' before packaging.',
);

argParser.addOption(
'flutter-build-args',
valueHelp: 'verbose,obfuscate',
help: 'Arguments to pass directly to flutter build',
);

argParser.addOption(
'build-target',
valueHelp: 'path',
help: 'The --target argument passed to \'flutter build\'',
);

argParser.addOption(
'build-flavor',
valueHelp: '',
help: 'The --flavor argument passed to \'flutter build\'',
);

argParser.addOption(
'build-target-platform',
valueHelp: '',
help: 'The --target-platform argument passed to \'flutter build\'',
);

argParser.addOption(
'build-export-options-plist',
valueHelp: '',
help: 'The --export-options-plist argument passed \'flutter build\'',
);

argParser.addMultiOption(
'build-dart-define',
valueHelp: 'foo=bar',
help: [
'The --dart-define argument(s) passed to \'flutter build\'',
'You may add multiple \'--build-dart-define key=value\' pairs',
].join('\n'),
);
}

final AnyAppPackager appPackager;

@override
String get name => 'package';

@override
String get description => [
'Package the current Flutter application',
'',
'Options named --build-* are passed to \'flutter build\' as is',
'Please consult the \'flutter build\' CLI help for more informations.',
].join('\n');

@override
Future run() async {
final String? platform = argResults?['platform'];
final List<String> targets = '${argResults?['targets'] ?? ''}'
.split(',')
.where((e) => e.isNotEmpty)
.toList();
final String? channel = argResults?['channel'];
final String? artifactName = argResults?['artifact-name'];
final String? flutterBuildArgs = argResults?['flutter-build-args'];
final bool isSkipClean = argResults?.wasParsed('skip-clean') ?? false;
final Map<String, dynamic> buildArguments =
_generateBuildArgs(flutterBuildArgs);

// At least `platform` and one `targets` is required for flutter build
if (platform == null) {
print('\nThe \'platform\' options is mandatory!');
exit(1);
}

if (targets.isEmpty) {
print('\nAt least one \'target\' must be specified!');
exit(1);
}

return appPackager.package(
platform,
targets,
channel: channel,
artifactName: artifactName,
cleanBeforeBuild: !isSkipClean,
buildArguments: buildArguments,
);
}

Map<String, dynamic> _generateBuildArgs(String? flutterBuildArgs) {
Map<String, dynamic> buildArguments = {};

if (argResults?.options == null) return buildArguments;

for (var option in argResults!.options) {
if (!option.startsWith('build-')) continue;
dynamic value = argResults?[option];

if (value is List) {
// ignore: prefer_for_elements_to_map_fromiterable
value = Map.fromIterable(
value,
key: (e) => e.split('=')[0],
value: (e) => e.split('=')[1],
);
}

buildArguments.putIfAbsent(
option.replaceAll('build-', ''),
() => value,
);
}

for (var arg in flutterBuildArgs?.split(',') ?? <String>[]) {
if (arg.split('=').length == 2) {
buildArguments.putIfAbsent(
arg.split('=').first,
() => arg.split('=').last,
);
} else if (arg.split('=').length == 1) {
buildArguments.putIfAbsent(
arg.split('=')[0],
() => true,
);
} else {
buildArguments.putIfAbsent(arg, () => true);
}
}

return buildArguments;
}
}

Future<void> main(List<String> args) async {
final AnyAppPackager appPackager = AnyAppPackager();

final runner = CommandRunner(
'any_app_packager',
'Package your any app into OS-specific bundles (.dmg, .exe, etc.) via Dart or the command line.',
);
runner.argParser
..addFlag(
'version',
help: 'Reports the version of this tool.',
negatable: false,
)
..addFlag(
'version-check',
help: 'Check for updates when this command runs.',
defaultsTo: true,
negatable: true,
);

runner.addCommand(PackageCommand(appPackager));

ArgResults argResults = runner.parse(['package', ...args]);
return runner.runCommand(argResults);
}
119 changes: 103 additions & 16 deletions packages/any_app_packager/lib/src/any_app_packager.dart
Original file line number Diff line number Diff line change
@@ -1,26 +1,113 @@
import 'dart:convert';
import 'dart:io';

import 'package:any_app_packager/src/utils/logger.dart';
import 'package:flutter_app_builder/flutter_app_builder.dart';
import 'package:flutter_app_packager/flutter_app_packager.dart';
import 'package:pubspec_parse/pubspec_parse.dart';

class AnyAppPackager {
final FlutterAppPackager _flutterAppPackager = FlutterAppPackager();
final FlutterAppBuilder _appBuilder = FlutterAppBuilder();
final FlutterAppPackager _appPackager = FlutterAppPackager();

final Map<String, String> _globalVariables = {};
Map<String, String> get globalVariables {
if (_globalVariables.keys.isEmpty) {
for (String key in Platform.environment.keys) {
String? value = Platform.environment[key];
if ((value ?? '').isNotEmpty) {
_globalVariables[key] = value!;
}
}
}
return _globalVariables;
}

/// Packages the app for the given platform and target.
Future<MakeResult> package(
Future<List<MakeResult>> package(
String platform,
String target,
Map<String, dynamic>? arguments,
Directory outputDirectory, {
required Directory buildOutputDirectory,
required List<File> buildOutputFiles,
}) {
return _flutterAppPackager.package(
platform,
target,
arguments,
outputDirectory,
buildOutputDirectory: buildOutputDirectory,
buildOutputFiles: buildOutputFiles,
);
List<String> targets, {
String? channel,
String? artifactName,
required bool cleanBeforeBuild,
required Map<String, dynamic> buildArguments,
Map<String, String>? variables,
}) async {
List<MakeResult> makeResultList = [];

try {
Directory outputDirectory = Directory('dist/');
if (!outputDirectory.existsSync()) {
outputDirectory.createSync(recursive: true);
}

if (cleanBeforeBuild) {
await _appBuilder.clean();
}

bool isBuildOnlyOnce = platform != 'android';
BuildResult? buildResult;

final yamlString = File('pubspec.yaml').readAsStringSync();
Pubspec pubspec = Pubspec.parse(yamlString);

for (String target in targets) {
logger.info('Packaging ${pubspec.name} ${pubspec.version} as $target:');
if (!isBuildOnlyOnce || (isBuildOnlyOnce && buildResult == null)) {
try {
buildResult = await _appBuilder.build(
platform,
target: target,
arguments: buildArguments,
environment: variables ?? globalVariables,
);
print(
const JsonEncoder.withIndent(' ').convert(buildResult.toJson()),
);

logger.fine(
'Successfully built ${buildResult.outputDirectory} in ${buildResult.duration!.inSeconds}s',
);
} on UnsupportedError catch (error) {
logger.warning('Warning: ${error.message}');
continue;
} catch (error) {
rethrow;
}
}

if (buildResult != null) {
String buildMode =
buildArguments.containsKey('profile') ? 'profile' : 'release';
Map<String, dynamic>? arguments = {
'build_mode': buildMode,
'flavor': buildArguments['flavor'],
'channel': channel,
'artifact_name': artifactName,
};
MakeResult makeResult = await _appPackager.package(
platform,
target,
arguments,
outputDirectory,
buildOutputDirectory: buildResult.outputDirectory,
buildOutputFiles: buildResult.outputFiles,
);
print(
const JsonEncoder.withIndent(' ').convert(makeResult.toJson()),
);
FileSystemEntity artifact = makeResult.artifacts.first;
logger.fine('Successfully packaged ${artifact.path}');
makeResultList.add(makeResult);
}
}
} catch (error) {
logger.severe(error.toString());
if (error is Error) {
logger.severe(error.stackTrace.toString());
}
rethrow;
}
return makeResultList;
}
}
6 changes: 6 additions & 0 deletions packages/any_app_packager/lib/src/utils/logger.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import 'package:logging/logging.dart';

Logger logger = Logger('any_app_packager')
..onRecord.listen((record) {
print(record.message);
});
6 changes: 5 additions & 1 deletion packages/any_app_packager/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@ environment:
sdk: ">=2.16.0 <4.0.0"

dependencies:
args: ^2.2.0
flutter_app_builder: ^0.4.5
flutter_app_packager: ^0.4.5
logging: ^1.0.2
pubspec_parse: ^1.1.0

dev_dependencies:
dependency_validator: ^3.0.0
mostly_reasonable_lints: ^0.1.2

executables:
packageanyapp: main
package-any-app: main
2 changes: 1 addition & 1 deletion packages/flutter_app_packager/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ environment:
sdk: ">=2.16.0 <4.0.0"

dependencies:
archive: ^3.4.10
archive: ^3.6.1
io: ^1.0.3
liquid_engine: ^0.2.2
msix: ^3.16.6
Expand Down
2 changes: 1 addition & 1 deletion packages/parse_app_package/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ environment:
sdk: ">=2.16.0 <4.0.0"

dependencies:
archive: ^3.4.10
archive: ^3.6.1
args: ^2.2.0
plist_parser: ^0.0.11
shell_executor: ^0.1.6
Expand Down

0 comments on commit 4b6693a

Please sign in to comment.