mirror of
https://github.com/wger-project/flutter.git
synced 2026-02-18 00:17:48 +01:00
306 lines
11 KiB
Dart
306 lines
11 KiB
Dart
// ignore_for_file: avoid_classes_with_only_static_members, avoid_print
|
|
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
void main(List<String> arguments) {
|
|
if (arguments.length != 1) {
|
|
throw Exception('Must have only one argument: the path to the JSON specification.');
|
|
}
|
|
if (!Platform.isLinux) {
|
|
throw Exception('Must be run under x86_64 Linux');
|
|
}
|
|
final jsonFile = File(arguments[0]);
|
|
if (!jsonFile.existsSync()) {
|
|
throw Exception('The provided JSON file does not exist.');
|
|
}
|
|
final specJson = SpecJson.fromJson(jsonFile);
|
|
|
|
final outputDir = Directory.current;
|
|
|
|
final packageGenerator = PackageGenerator(inputDir: jsonFile.parent, specJson: specJson);
|
|
packageGenerator.generatePackage(Directory.current, _Platform.x86_64);
|
|
|
|
if (specJson.linuxArmReleaseBundleDirPath != null) {
|
|
packageGenerator.generatePackage(Directory.current, _Platform.aarch64);
|
|
}
|
|
|
|
final sha256x64 = packageGenerator.sha256x64;
|
|
|
|
if (sha256x64 == null) {
|
|
throw Exception('Could not generate SHA256 for the created package');
|
|
}
|
|
|
|
final sha256aarch64 = packageGenerator.sha256aarch64;
|
|
|
|
final manifestContent =
|
|
FlatpakManifestGenerator(specJson).getFlatpakManifest(sha256x64, sha256aarch64);
|
|
final manifestPath = '${outputDir.path}/${specJson.appId}.json';
|
|
final manifestFile = File(manifestPath);
|
|
manifestFile.writeAsStringSync(manifestContent);
|
|
print('Generated $manifestPath');
|
|
|
|
if (specJson.linuxArmReleaseBundleDirPath == null) {
|
|
final flathubJsonPath = '${outputDir.path}/${FlathubJsonGenerator.filename}';
|
|
final flathubJsonFile = File(flathubJsonPath);
|
|
flathubJsonFile.writeAsStringSync(FlathubJsonGenerator.generate());
|
|
print('Generated $flathubJsonPath');
|
|
}
|
|
}
|
|
|
|
enum _Platform { x86_64, aarch64 }
|
|
|
|
class PackageGenerator {
|
|
final Directory inputDir;
|
|
final SpecJson specJson;
|
|
String? sha256x64;
|
|
String? sha256aarch64;
|
|
|
|
PackageGenerator({required this.inputDir, required this.specJson});
|
|
|
|
void generatePackage(Directory outputDir, _Platform platform) {
|
|
final tempDir = outputDir.createTempSync('flutter_package_generator');
|
|
final appId = specJson.appId;
|
|
|
|
// desktop file
|
|
final desktopFile = File('${inputDir.path}/${specJson.desktopPath}');
|
|
|
|
if (!desktopFile.existsSync()) {
|
|
throw Exception(
|
|
'The desktop file does not exist under the specified path: ${desktopFile.path}');
|
|
}
|
|
|
|
desktopFile.copySync(
|
|
'${tempDir.path}/$appId.desktop'); //todo does "$appName" have to be in the path too?
|
|
|
|
// icons
|
|
final iconTempDir = Directory('${tempDir.path}/icons');
|
|
|
|
for (final icon in specJson.icons) {
|
|
final iconFile = File('${inputDir.path}/${icon.path}');
|
|
if (!iconFile.existsSync()) {
|
|
throw Exception('The icon file ${iconFile.path} does not exist.');
|
|
}
|
|
final iconSubdir = Directory('${iconTempDir.path}/${icon.type}');
|
|
iconSubdir.createSync(recursive: true);
|
|
iconFile.copySync('${iconSubdir.path}/$appId.${icon.fileExtension}');
|
|
}
|
|
|
|
// appdata file
|
|
final origAppDataFile = File('${inputDir.path}/${specJson.appDataPath}');
|
|
if (!origAppDataFile.existsSync()) {
|
|
throw Exception(
|
|
'The app data file does not exist under the specified path: ${origAppDataFile.path}');
|
|
}
|
|
|
|
final editedAppDataContent =
|
|
AppDataModifier.replaceVersions(origAppDataFile.readAsStringSync(), specJson.releases);
|
|
|
|
final editedAppDataFile = File('${tempDir.path}/$appId.appdata.xml');
|
|
editedAppDataFile.writeAsStringSync(editedAppDataContent);
|
|
|
|
// build files
|
|
final bundlePath = platform == _Platform.aarch64
|
|
? specJson.linuxArmReleaseBundleDirPath
|
|
: specJson.linuxReleaseBundleDirPath;
|
|
final buildDir = Directory(bundlePath!);
|
|
if (!buildDir.existsSync()) {
|
|
throw Exception(
|
|
'The linux build directory does not exist under the specified path: ${buildDir.path}');
|
|
}
|
|
final destDir = Directory('${tempDir.path}/bin');
|
|
destDir.createSync();
|
|
|
|
final platformSuffix = platform == _Platform.aarch64 ? 'aarch64' : 'x86_64';
|
|
final packagePath =
|
|
'${outputDir.absolute.path}/${specJson.lowercaseAppName}-linux-$platformSuffix.tar.gz';
|
|
|
|
Process.runSync('cp', [
|
|
'-r',
|
|
'${buildDir.absolute.path}/.',
|
|
destDir.absolute.path
|
|
]); //todo test with spaces in name
|
|
Process.runSync('tar', ['-czvf', packagePath, '.'],
|
|
workingDirectory: tempDir.absolute.path); //todo test with spaces in name
|
|
|
|
print('Generated $packagePath');
|
|
|
|
final preShasum = Process.runSync('shasum', ['-a', '256', packagePath]);
|
|
|
|
if (platform == _Platform.aarch64) {
|
|
sha256aarch64 = preShasum.stdout.toString().split(' ').first;
|
|
} else {
|
|
sha256x64 = preShasum.stdout.toString().split(' ').first;
|
|
}
|
|
|
|
tempDir.deleteSync(recursive: true);
|
|
}
|
|
}
|
|
|
|
// updates releases in ${appName}.appdata.xml
|
|
class AppDataModifier {
|
|
static String replaceVersions(String origAppDataContent, List<Release> versions) {
|
|
final joinedReleases =
|
|
versions.map((v) => '<release version="${v.version}" date="${v.date}">').join('\n');
|
|
final releasesSection = '<releases>\n$joinedReleases\n</releases>';
|
|
if (origAppDataContent.contains('<releases')) {
|
|
return origAppDataContent.replaceFirst(
|
|
RegExp('<releases.*</releases>', multiLine: true), releasesSection);
|
|
} else {
|
|
return origAppDataContent.replaceFirst('</component>', '$releasesSection\n</component>');
|
|
}
|
|
}
|
|
}
|
|
|
|
// ${appId}.json
|
|
class FlatpakManifestGenerator {
|
|
final SpecJson specJson;
|
|
|
|
FlatpakManifestGenerator(this.specJson);
|
|
|
|
String getFlatpakManifest(String sha256x64, String? sha256aarch64) {
|
|
final appName = specJson.lowercaseAppName;
|
|
final appId = specJson.appId;
|
|
const encoder = JsonEncoder.withIndent(' ');
|
|
return encoder.convert({
|
|
'app-id': appId,
|
|
'runtime': 'org.freedesktop.Platform',
|
|
'runtime-version': specJson.runtimeVersion,
|
|
'sdk': 'org.freedesktop.Sdk',
|
|
'command': appName,
|
|
'separate-locales': false,
|
|
'finish-args': specJson.finishArgs,
|
|
'modules': [
|
|
...specJson.extraModules ?? [],
|
|
{
|
|
'name': appName,
|
|
'buildsystem': 'simple',
|
|
'build-commands': [
|
|
'cp -R $appName/bin/ /app/$appName',
|
|
'chmod +x /app/$appName/$appName',
|
|
'mkdir /app/bin/',
|
|
'mkdir /app/lib/',
|
|
'ln -s /app/$appName/$appName /app/bin/$appName',
|
|
...specJson.flatpakCommandsAfterUnpack ?? [],
|
|
...specJson.icons.map((icon) =>
|
|
'install -Dm644 $appName/icons/${icon.type}/$appId.${icon.fileExtension} /app/share/icons/hicolor/${icon.type}/apps/$appId.${icon.fileExtension}'), //TODO THIS DOES NOT ACCOUNT FOR THE symbolic icon name!!!
|
|
'install -Dm644 $appName/$appId.desktop /app/share/applications/$appId.desktop',
|
|
'install -Dm644 $appName/$appId.appdata.xml /app/share/applications/$appId.appdata.xml'
|
|
],
|
|
'sources': [
|
|
{
|
|
'type': 'archive',
|
|
'only-arches': ['x86_64'],
|
|
'url':
|
|
'https://github.com/${specJson.githubReleaseOrganization}/${specJson.githubReleaseProject}/releases/download/${specJson.releases.first.version}/${specJson.lowercaseAppName}-linux-x86_64.tar.gz',
|
|
'sha256': sha256x64,
|
|
'dest': specJson.lowercaseAppName
|
|
},
|
|
if (specJson.linuxArmReleaseBundleDirPath != null)
|
|
{
|
|
'type': 'archive',
|
|
'only-arches': ['aarch64'],
|
|
'url':
|
|
'https://github.com/${specJson.githubReleaseOrganization}/${specJson.githubReleaseProject}/releases/download/${specJson.releases.first.version}/${specJson.lowercaseAppName}-linux-aarch64.tar.gz',
|
|
'sha256': sha256aarch64,
|
|
'dest': specJson.lowercaseAppName
|
|
}
|
|
]
|
|
}
|
|
]
|
|
});
|
|
}
|
|
}
|
|
|
|
// flathub.json
|
|
class FlathubJsonGenerator {
|
|
static const String filename = 'flathub.json';
|
|
|
|
static String generate() {
|
|
const encoder = JsonEncoder.withIndent(' ');
|
|
return encoder.convert({
|
|
'only-arches': ['x86_64']
|
|
});
|
|
}
|
|
}
|
|
|
|
class Release {
|
|
final String version;
|
|
final String date;
|
|
|
|
Release({required this.version, required this.date});
|
|
}
|
|
|
|
class Icon {
|
|
final String type;
|
|
final String path;
|
|
late final String fileExtension;
|
|
|
|
Icon({required this.type, required this.path}) {
|
|
fileExtension = path.split('.').last;
|
|
}
|
|
}
|
|
|
|
class SpecJson {
|
|
//todo allow extra modules
|
|
final String appId;
|
|
final String lowercaseAppName;
|
|
final List<Release> releases;
|
|
final String runtimeVersion;
|
|
final String linuxReleaseBundleDirPath;
|
|
final String? linuxArmReleaseBundleDirPath;
|
|
final String appDataPath;
|
|
final String desktopPath;
|
|
final List<Icon> icons;
|
|
final List<String>? flatpakCommandsAfterUnpack;
|
|
final List<dynamic>? extraModules;
|
|
final List<String> finishArgs;
|
|
final String githubReleaseOrganization;
|
|
final String githubReleaseProject;
|
|
|
|
SpecJson(
|
|
{required this.appId,
|
|
required this.lowercaseAppName,
|
|
required this.releases,
|
|
required this.runtimeVersion,
|
|
required this.linuxReleaseBundleDirPath,
|
|
this.linuxArmReleaseBundleDirPath,
|
|
required this.appDataPath,
|
|
required this.desktopPath,
|
|
required this.icons,
|
|
required this.flatpakCommandsAfterUnpack,
|
|
this.extraModules,
|
|
required this.finishArgs,
|
|
required this.githubReleaseOrganization,
|
|
required this.githubReleaseProject});
|
|
|
|
static SpecJson fromJson(File jsonFile) {
|
|
try {
|
|
final json = jsonDecode(jsonFile.readAsStringSync());
|
|
return SpecJson(
|
|
appId: json['appId'],
|
|
lowercaseAppName: json['lowercaseAppName'],
|
|
releases: (json['releases'] as List).map((r) {
|
|
final rMap = r as Map;
|
|
return Release(version: rMap['version'], date: rMap['date']);
|
|
}).toList(),
|
|
runtimeVersion: json['runtimeVersion'],
|
|
linuxReleaseBundleDirPath: json['linuxReleaseBundleDirPath'],
|
|
appDataPath: json['appDataPath'],
|
|
desktopPath: json['desktopPath'],
|
|
icons: (json['icons'] as Map).entries.map((mapEntry) {
|
|
return Icon(type: mapEntry.key as String, path: mapEntry.value as String);
|
|
}).toList(),
|
|
flatpakCommandsAfterUnpack:
|
|
(json['buildCommandsAfterUnpack'] as List?)?.map((bc) => bc as String)?.toList(),
|
|
linuxArmReleaseBundleDirPath: json['linuxArmReleaseBundleDirPath'] as String?,
|
|
extraModules: json['extraModules'] as List?,
|
|
finishArgs: (json['finishArgs'] as List).map((fa) => fa as String).toList(),
|
|
githubReleaseOrganization: json['githubReleaseOrganization'],
|
|
githubReleaseProject: json['githubReleaseProject']);
|
|
} catch (e) {
|
|
throw Exception('Could not parse JSON file, due to this error:\n$e');
|
|
}
|
|
}
|
|
}
|