mirror of
https://github.com/hamaluik/timecop.git
synced 2025-05-17 16:56:05 +08:00
Flatpak build scripts
This commit is contained in:
38
flatpak/flatpak_meta.json
Normal file
38
flatpak/flatpak_meta.json
Normal file
@ -0,0 +1,38 @@
|
||||
{
|
||||
"appId": "ca.hamaluik.Timecop",
|
||||
"lowercaseAppName": "timecop",
|
||||
"githubReleaseOrganization": "hamaluik",
|
||||
"githubReleaseProject": "timecop",
|
||||
"localReleases": [
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"date": "2020-03-19"
|
||||
}
|
||||
],
|
||||
"localReleaseAssets": [
|
||||
{
|
||||
"arch": "aarch64",
|
||||
"tarballPath": "scripts/flatpak_generator_exports/timecop-linux-aarch64.tar.gz"
|
||||
}
|
||||
],
|
||||
"localLinuxBuildDir": "../build/linux",
|
||||
"appStreamPath": "ca.hamaluik.Timecop.appdata.xml",
|
||||
"desktopPath": "ca.hamaluik.Timecop.desktop",
|
||||
"icons": {
|
||||
"symbolic": "ca.hamaluik.Timecop-symbolic.svg",
|
||||
"scalable": "ca.hamaluik.Timecop.svg"
|
||||
},
|
||||
"freedesktopRuntime": "22.08",
|
||||
"buildCommandsAfterUnpack": [
|
||||
"ln -s /usr/lib/x86_64-linux-gnu/libsqlite3.so.0 /app/lib/libsqlite3.so",
|
||||
"if [ ! -e '/app/lib/libsqlite3.so' ]; then ln -s -f /usr/lib/aarch64-linux-gnu/libsqlite3.so.0 /app/lib/libsqlite3.so; fi",
|
||||
"if [ $(arch)=\"aarch64\" ]; then sed -si \"/Exec=/cExec=env LIBGL_ALWAYS_SOFTWARE=1 timecop\" timecop/ca.hamaluik.Timecop.desktop; fi"
|
||||
],
|
||||
"finishArgs": [
|
||||
"--share=ipc",
|
||||
"--socket=fallback-x11",
|
||||
"--socket=wayland",
|
||||
"--device=dri",
|
||||
"--talk-name=org.freedesktop.Notifications"
|
||||
]
|
||||
}
|
182
flatpak/scripts/flatpak_packager.dart
Normal file
182
flatpak/scripts/flatpak_packager.dart
Normal file
@ -0,0 +1,182 @@
|
||||
// ignore_for_file: avoid_print
|
||||
|
||||
import 'dart:io';
|
||||
import 'flatpak_shared.dart';
|
||||
|
||||
/// arguments:
|
||||
/// --meta [file]
|
||||
/// Required argument for providing the metadata file for this script.
|
||||
|
||||
/// --github
|
||||
/// Use this option to pull release info from Github rather than the metadata file.
|
||||
|
||||
/// --addTodaysVersion [version]
|
||||
/// If pulling data from Github, this provides a way to specify the release to be released today.
|
||||
|
||||
void main(List<String> arguments) async {
|
||||
if (!Platform.isLinux) {
|
||||
throw Exception('Must be run under Linux');
|
||||
}
|
||||
|
||||
// PARSE ARGUMENTS
|
||||
final metaIndex = arguments.indexOf('--meta');
|
||||
if (metaIndex == -1) {
|
||||
throw Exception(
|
||||
'You must run this script with a metadata file argument, using the --meta flag.');
|
||||
}
|
||||
if (arguments.length == metaIndex + 1) {
|
||||
throw Exception(
|
||||
'The --meta flag must be followed by the path to the metadata file.');
|
||||
}
|
||||
|
||||
final metaFile = File(arguments[metaIndex + 1]);
|
||||
if (!(await metaFile.exists())) {
|
||||
throw Exception('The provided metadata file does not exist.');
|
||||
}
|
||||
|
||||
final fetchFromGithub = arguments.contains('--github');
|
||||
|
||||
final addTodaysVersionIndex = arguments.indexOf('--addTodaysVersion');
|
||||
if (addTodaysVersionIndex != -1 &&
|
||||
arguments.length == addTodaysVersionIndex + 1) {
|
||||
throw Exception(
|
||||
'The --addTodaysVersion flag must be followed by the version name.');
|
||||
}
|
||||
|
||||
final addedTodaysVersion =
|
||||
addTodaysVersionIndex != -1 ? arguments[addTodaysVersionIndex + 1] : null;
|
||||
|
||||
// GENERATE PACKAGE
|
||||
|
||||
final meta =
|
||||
FlatpakMeta.fromJson(metaFile, skipLocalReleases: fetchFromGithub);
|
||||
|
||||
final outputDir =
|
||||
Directory('${Directory.current.path}/flatpak_generator_exports');
|
||||
await outputDir.create();
|
||||
|
||||
final packageGenerator = PackageGenerator(
|
||||
inputDir: metaFile.parent,
|
||||
meta: meta,
|
||||
addedTodaysVersion: addedTodaysVersion);
|
||||
|
||||
await packageGenerator.generatePackage(
|
||||
outputDir,
|
||||
await PackageGenerator.runningOnARM()
|
||||
? CPUArchitecture.aarch64
|
||||
: CPUArchitecture.x86_64,
|
||||
fetchFromGithub);
|
||||
}
|
||||
|
||||
class PackageGenerator {
|
||||
final Directory inputDir;
|
||||
final FlatpakMeta meta;
|
||||
final String? addedTodaysVersion;
|
||||
|
||||
PackageGenerator(
|
||||
{required this.inputDir,
|
||||
required this.meta,
|
||||
required this.addedTodaysVersion});
|
||||
|
||||
Future<void> generatePackage(Directory outputDir, CPUArchitecture arch,
|
||||
bool fetchReleasesFromGithub) async {
|
||||
final tempDir = await outputDir.createTemp('flutter_generator_temp');
|
||||
final appId = meta.appId;
|
||||
|
||||
// desktop file
|
||||
final desktopFile = File('${inputDir.path}/${meta.desktopPath}');
|
||||
|
||||
if (!(await desktopFile.exists())) {
|
||||
throw Exception(
|
||||
'The desktop file does not exist under the specified path: ${desktopFile.path}');
|
||||
}
|
||||
|
||||
await desktopFile.copy('${tempDir.path}/$appId.desktop');
|
||||
|
||||
// icons
|
||||
final iconTempDir = Directory('${tempDir.path}/icons');
|
||||
|
||||
for (final icon in meta.icons) {
|
||||
final iconFile = File('${inputDir.path}/${icon.path}');
|
||||
if (!(await iconFile.exists())) {
|
||||
throw Exception('The icon file ${iconFile.path} does not exist.');
|
||||
}
|
||||
final iconSubdir = Directory('${iconTempDir.path}/${icon.type}');
|
||||
await iconSubdir.create(recursive: true);
|
||||
await iconFile.copy('${iconSubdir.path}/${icon.getFilename(appId)}');
|
||||
}
|
||||
|
||||
// AppStream metainfo file
|
||||
final origAppStreamFile = File('${inputDir.path}/${meta.appStreamPath}');
|
||||
if (!(await origAppStreamFile.exists())) {
|
||||
throw Exception(
|
||||
'The app data file does not exist under the specified path: ${origAppStreamFile.path}');
|
||||
}
|
||||
|
||||
final editedAppStreamContent = AppStreamModifier.replaceVersions(
|
||||
await origAppStreamFile.readAsString(),
|
||||
await meta.getReleases(fetchReleasesFromGithub, addedTodaysVersion));
|
||||
|
||||
final editedAppStreamFile = File('${tempDir.path}/$appId.metainfo.xml');
|
||||
await editedAppStreamFile.writeAsString(editedAppStreamContent);
|
||||
|
||||
// build files
|
||||
final bundlePath =
|
||||
'${inputDir.path}/${meta.localLinuxBuildDir}/${arch.flutterDirName}/release/bundle';
|
||||
final buildDir = Directory(bundlePath);
|
||||
if (!(await buildDir.exists())) {
|
||||
throw Exception(
|
||||
'The linux build directory does not exist under the specified path: ${buildDir.path}');
|
||||
}
|
||||
final destDir = Directory('${tempDir.path}/bin');
|
||||
await destDir.create();
|
||||
|
||||
final baseFilename =
|
||||
'${meta.lowercaseAppName}-linux-${arch.flatpakArchCode}';
|
||||
final packagePath = '${outputDir.absolute.path}/$baseFilename.tar.gz';
|
||||
final shaPath = '${outputDir.absolute.path}/$baseFilename.sha256';
|
||||
|
||||
await Process.run(
|
||||
'cp', ['-r', '${buildDir.absolute.path}/.', destDir.absolute.path]);
|
||||
await Process.run('tar', ['-czvf', packagePath, '.'],
|
||||
workingDirectory: tempDir.absolute.path);
|
||||
|
||||
print('Generated $packagePath');
|
||||
|
||||
final preShasum = await Process.run('shasum', ['-a', '256', packagePath]);
|
||||
final shasum = preShasum.stdout.toString().split(' ').first;
|
||||
|
||||
await File(shaPath).writeAsString(shasum);
|
||||
|
||||
print('Generated $shaPath');
|
||||
|
||||
await tempDir.delete(recursive: true);
|
||||
}
|
||||
|
||||
static Future<bool> runningOnARM() async {
|
||||
final unameRes = await Process.run('uname', ['-m']);
|
||||
final unameString = unameRes.stdout.toString().trimLeft();
|
||||
return unameString.startsWith('arm') || unameString.startsWith('aarch');
|
||||
}
|
||||
}
|
||||
|
||||
// Updates releases in ${appName}.metainfo.xml
|
||||
class AppStreamModifier {
|
||||
static String replaceVersions(
|
||||
String origAppStreamContent, List<Release> versions) {
|
||||
final joinedReleases = versions
|
||||
.map((v) => '\t\t<release version="${v.version}" date="${v.date}" />')
|
||||
.join('\n');
|
||||
final releasesSection =
|
||||
'<releases>\n$joinedReleases\n\t</releases>'; //TODO check this
|
||||
if (origAppStreamContent.contains('<releases')) {
|
||||
return origAppStreamContent
|
||||
.replaceAll('\n', '<~>')
|
||||
.replaceFirst(RegExp('<releases.*</releases>'), releasesSection)
|
||||
.replaceAll('<~>', '\n');
|
||||
} else {
|
||||
return origAppStreamContent.replaceFirst(
|
||||
'</component>', '\n\t$releasesSection\n</component>');
|
||||
}
|
||||
}
|
||||
}
|
337
flatpak/scripts/flatpak_shared.dart
Normal file
337
flatpak/scripts/flatpak_shared.dart
Normal file
@ -0,0 +1,337 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
class Release {
|
||||
final String version;
|
||||
final String date;
|
||||
|
||||
Release({required this.version, required this.date});
|
||||
}
|
||||
|
||||
enum CPUArchitecture {
|
||||
x86_64('x86_64', 'x64'),
|
||||
aarch64('aarch64', 'arm64');
|
||||
|
||||
final String flatpakArchCode;
|
||||
final String flutterDirName;
|
||||
const CPUArchitecture(this.flatpakArchCode, this.flutterDirName);
|
||||
}
|
||||
|
||||
class ReleaseAsset {
|
||||
final CPUArchitecture arch;
|
||||
final String tarballUrlOrPath;
|
||||
final bool isRelativeLocalPath;
|
||||
final String tarballSha256;
|
||||
|
||||
ReleaseAsset(
|
||||
{required this.arch,
|
||||
required this.tarballUrlOrPath,
|
||||
required this.isRelativeLocalPath,
|
||||
required this.tarballSha256});
|
||||
}
|
||||
|
||||
class Icon {
|
||||
static const _symbolicType = 'symbolic';
|
||||
final String type;
|
||||
final String path;
|
||||
late final String _fileExtension;
|
||||
|
||||
Icon({required this.type, required this.path}) {
|
||||
_fileExtension = path.split('.').last;
|
||||
}
|
||||
|
||||
String getFilename(String appId) => (type == _symbolicType)
|
||||
? '$appId-symbolic.$_fileExtension'
|
||||
: '$appId.$_fileExtension';
|
||||
}
|
||||
|
||||
class GithubReleases {
|
||||
final String githubReleaseOrganization;
|
||||
final String githubReleaseProject;
|
||||
List<Release>? _releases;
|
||||
List<ReleaseAsset>? _latestReleaseAssets;
|
||||
|
||||
GithubReleases(this.githubReleaseOrganization, this.githubReleaseProject);
|
||||
|
||||
Future<List<Release>> getReleases(bool canBeEmpty) async {
|
||||
if (_releases == null) {
|
||||
await _fetchReleasesAndAssets(canBeEmpty);
|
||||
}
|
||||
return _releases!;
|
||||
}
|
||||
|
||||
Future<List<ReleaseAsset>?> getLatestReleaseAssets() async {
|
||||
if (_releases == null) {
|
||||
await _fetchReleasesAndAssets(false);
|
||||
}
|
||||
return _latestReleaseAssets;
|
||||
}
|
||||
|
||||
Future<void> _fetchReleasesAndAssets(bool canBeEmpty) async {
|
||||
final releaseJsonContent = (await http.get(Uri(
|
||||
scheme: 'https',
|
||||
host: 'api.github.com',
|
||||
path:
|
||||
'/repos/$githubReleaseOrganization/$githubReleaseProject/releases')))
|
||||
.body;
|
||||
final decodedJson = jsonDecode(releaseJsonContent) as List;
|
||||
|
||||
DateTime? latestReleaseAssetDate;
|
||||
|
||||
final releases = List<Release>.empty(growable: true);
|
||||
|
||||
await Future.forEach<dynamic>(decodedJson, (dynamic releaseDynamic) async {
|
||||
final releaseMap = releaseDynamic as Map;
|
||||
|
||||
final releaseDateAndTime =
|
||||
DateTime.parse(releaseMap['published_at'] as String);
|
||||
final releaseDateString =
|
||||
releaseDateAndTime.toIso8601String().split('T').first;
|
||||
|
||||
if (latestReleaseAssetDate == null ||
|
||||
(latestReleaseAssetDate?.compareTo(releaseDateAndTime) == -1)) {
|
||||
final assets =
|
||||
await _parseGithubReleaseAssets(releaseMap['assets'] as List);
|
||||
if (assets != null) {
|
||||
_latestReleaseAssets = assets;
|
||||
latestReleaseAssetDate = releaseDateAndTime;
|
||||
}
|
||||
}
|
||||
|
||||
releases.add(Release(
|
||||
version: releaseMap['name'] as String, date: releaseDateString));
|
||||
});
|
||||
|
||||
if (releases.isNotEmpty || canBeEmpty) {
|
||||
_releases = releases;
|
||||
} else {
|
||||
throw Exception("Github must contain at least 1 release.");
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<ReleaseAsset>?> _parseGithubReleaseAssets(List assetMaps) async {
|
||||
String? x64TarballUrl;
|
||||
String? x64Sha;
|
||||
String? aarch64TarballUrl;
|
||||
String? aarch64Sha;
|
||||
for (final am in assetMaps) {
|
||||
final amMap = am as Map;
|
||||
|
||||
final downloadUrl = amMap['browser_download_url'] as String;
|
||||
final filename = amMap['name'] as String;
|
||||
final fileExtension = filename.substring(filename.indexOf('.') + 1);
|
||||
final filenameWithoutExtension =
|
||||
filename.substring(0, filename.indexOf('.'));
|
||||
|
||||
final arch = filenameWithoutExtension.endsWith('aarch64')
|
||||
? CPUArchitecture.aarch64
|
||||
: CPUArchitecture.x86_64;
|
||||
|
||||
switch (fileExtension) {
|
||||
case 'sha256':
|
||||
if (arch == CPUArchitecture.aarch64) {
|
||||
aarch64Sha = await _readSha(downloadUrl);
|
||||
} else {
|
||||
x64Sha = await _readSha(downloadUrl);
|
||||
}
|
||||
break;
|
||||
case 'tar':
|
||||
case 'tar.gz':
|
||||
case 'tgz':
|
||||
case 'tar.xz':
|
||||
case 'txz':
|
||||
case 'tar.bz2':
|
||||
case 'tbz2':
|
||||
case 'zip':
|
||||
case '7z':
|
||||
if (arch == CPUArchitecture.aarch64) {
|
||||
aarch64TarballUrl = downloadUrl;
|
||||
} else {
|
||||
x64TarballUrl = downloadUrl;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
final res = List<ReleaseAsset>.empty(growable: true);
|
||||
if (x64TarballUrl != null && x64Sha != null) {
|
||||
res.add(ReleaseAsset(
|
||||
arch: CPUArchitecture.x86_64,
|
||||
tarballUrlOrPath: x64TarballUrl,
|
||||
isRelativeLocalPath: false,
|
||||
tarballSha256: x64Sha));
|
||||
}
|
||||
if (aarch64TarballUrl != null && aarch64Sha != null) {
|
||||
res.add(ReleaseAsset(
|
||||
arch: CPUArchitecture.aarch64,
|
||||
tarballUrlOrPath: aarch64TarballUrl,
|
||||
isRelativeLocalPath: false,
|
||||
tarballSha256: aarch64Sha));
|
||||
}
|
||||
return res.isEmpty ? null : res;
|
||||
}
|
||||
|
||||
Future<String> _readSha(String shaUrl) async =>
|
||||
(await http.get(Uri.parse(shaUrl))).body.split(' ').first;
|
||||
}
|
||||
|
||||
class FlatpakMeta {
|
||||
final String appId;
|
||||
final String lowercaseAppName;
|
||||
final String appStreamPath;
|
||||
final String desktopPath;
|
||||
final List<Icon> icons;
|
||||
|
||||
// Flatpak manifest releated properties
|
||||
final String freedesktopRuntime;
|
||||
final List<String>? buildCommandsAfterUnpack;
|
||||
final List<dynamic>? extraModules;
|
||||
final List<String> finishArgs;
|
||||
|
||||
// Properties relevant only for local releases
|
||||
final List<Release>? _localReleases;
|
||||
final List<ReleaseAsset>? _localReleaseAssets;
|
||||
final String localLinuxBuildDir;
|
||||
|
||||
// Properties relevant only for releases fetched from Github
|
||||
final String? githubReleaseOrganization;
|
||||
final String? githubReleaseProject;
|
||||
late final GithubReleases? _githubReleases;
|
||||
|
||||
FlatpakMeta(
|
||||
{required this.appId,
|
||||
required this.lowercaseAppName,
|
||||
required this.githubReleaseOrganization,
|
||||
required this.githubReleaseProject,
|
||||
required List<Release>? localReleases,
|
||||
required List<ReleaseAsset>? localReleaseAssets,
|
||||
required this.localLinuxBuildDir,
|
||||
required this.appStreamPath,
|
||||
required this.desktopPath,
|
||||
required this.icons,
|
||||
required this.freedesktopRuntime,
|
||||
required this.buildCommandsAfterUnpack,
|
||||
required this.extraModules,
|
||||
required this.finishArgs})
|
||||
: _localReleases = localReleases,
|
||||
_localReleaseAssets = localReleaseAssets {
|
||||
if (githubReleaseOrganization != null && githubReleaseProject != null) {
|
||||
_githubReleases =
|
||||
GithubReleases(githubReleaseOrganization!, githubReleaseProject!);
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<Release>> getReleases(
|
||||
bool fetchReleasesFromGithub, String? addedTodaysVersion) async {
|
||||
final releases = List<Release>.empty(growable: true);
|
||||
if (addedTodaysVersion != null) {
|
||||
releases.add(Release(
|
||||
version: addedTodaysVersion,
|
||||
date: DateTime.now().toIso8601String().split("T").first));
|
||||
}
|
||||
if (fetchReleasesFromGithub) {
|
||||
if (_githubReleases == null) {
|
||||
throw Exception(
|
||||
'Metadata must include Github repository info if fetching releases from Github.');
|
||||
}
|
||||
releases.addAll(
|
||||
await _githubReleases!.getReleases(addedTodaysVersion != null));
|
||||
} else {
|
||||
if (_localReleases == null && addedTodaysVersion == null) {
|
||||
throw Exception(
|
||||
'Metadata must include releases if not fetching releases from Github.');
|
||||
}
|
||||
if (_localReleases?.isNotEmpty ?? false) {
|
||||
releases.addAll(_localReleases!);
|
||||
}
|
||||
}
|
||||
return releases;
|
||||
}
|
||||
|
||||
Future<List<ReleaseAsset>?> getLatestReleaseAssets(
|
||||
bool fetchReleasesFromGithub) async {
|
||||
if (fetchReleasesFromGithub) {
|
||||
if (_githubReleases == null) {
|
||||
throw Exception(
|
||||
'Metadata must include Github repository info if fetching releases from Github.');
|
||||
}
|
||||
return await _githubReleases!.getLatestReleaseAssets();
|
||||
} else {
|
||||
if (_localReleases == null) {
|
||||
throw Exception(
|
||||
'Metadata must include releases if not fetching releases from Github.');
|
||||
}
|
||||
return _localReleaseAssets;
|
||||
}
|
||||
}
|
||||
|
||||
static FlatpakMeta fromJson(File jsonFile, {bool skipLocalReleases = false}) {
|
||||
try {
|
||||
final dynamic json = jsonDecode(jsonFile.readAsStringSync());
|
||||
return FlatpakMeta(
|
||||
appId: json['appId'] as String,
|
||||
lowercaseAppName: json['lowercaseAppName'] as String,
|
||||
githubReleaseOrganization:
|
||||
json['githubReleaseOrganization'] as String?,
|
||||
githubReleaseProject: json['githubReleaseProject'] as String?,
|
||||
localReleases: skipLocalReleases
|
||||
? null
|
||||
: (json['localReleases'] as List?)?.map((dynamic r) {
|
||||
final rMap = r as Map;
|
||||
return Release(
|
||||
version: rMap['version'] as String,
|
||||
date: rMap['date'] as String);
|
||||
}).toList(),
|
||||
localReleaseAssets: skipLocalReleases
|
||||
? null
|
||||
: (json['localReleaseAssets'] as List?)?.map((dynamic ra) {
|
||||
final raMap = ra as Map;
|
||||
final archString = raMap['arch'] as String;
|
||||
final arch = (archString ==
|
||||
CPUArchitecture.x86_64.flatpakArchCode)
|
||||
? CPUArchitecture.x86_64
|
||||
: (archString == CPUArchitecture.aarch64.flatpakArchCode)
|
||||
? CPUArchitecture.aarch64
|
||||
: null;
|
||||
if (arch == null) {
|
||||
throw Exception(
|
||||
'Architecture must be either "${CPUArchitecture.x86_64.flatpakArchCode}" or "${CPUArchitecture.aarch64.flatpakArchCode}"');
|
||||
}
|
||||
final tarballFile = File(
|
||||
'${jsonFile.parent.path}/${raMap['tarballPath'] as String}');
|
||||
final tarballPath = tarballFile.absolute.path;
|
||||
final preShasum =
|
||||
Process.runSync('shasum', ['-a', '256', tarballPath]);
|
||||
final shasum = preShasum.stdout.toString().split(' ').first;
|
||||
if (preShasum.exitCode != 0) {
|
||||
throw Exception(preShasum.stderr);
|
||||
}
|
||||
return ReleaseAsset(
|
||||
arch: arch,
|
||||
tarballUrlOrPath: tarballPath,
|
||||
isRelativeLocalPath: true,
|
||||
tarballSha256: shasum);
|
||||
}).toList(),
|
||||
localLinuxBuildDir: json['localLinuxBuildDir'] as String,
|
||||
appStreamPath: json['appStreamPath'] as String,
|
||||
desktopPath: json['desktopPath'] as String,
|
||||
icons: (json['icons'] as Map).entries.map((mapEntry) {
|
||||
return Icon(
|
||||
type: mapEntry.key as String, path: mapEntry.value as String);
|
||||
}).toList(),
|
||||
freedesktopRuntime: json['freedesktopRuntime'] as String,
|
||||
buildCommandsAfterUnpack: (json['buildCommandsAfterUnpack'] as List?)
|
||||
?.map((dynamic bc) => bc as String)
|
||||
.toList(),
|
||||
extraModules: json['extraModules'] as List?,
|
||||
finishArgs: (json['finishArgs'] as List)
|
||||
.map((dynamic fa) => fa as String)
|
||||
.toList());
|
||||
} catch (e) {
|
||||
throw Exception('Could not parse JSON file, due to this error:\n$e');
|
||||
}
|
||||
}
|
||||
}
|
170
flatpak/scripts/manifest_generator.dart
Normal file
170
flatpak/scripts/manifest_generator.dart
Normal file
@ -0,0 +1,170 @@
|
||||
// ignore_for_file: avoid_print
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'flatpak_shared.dart';
|
||||
|
||||
/// arguments:
|
||||
/// --meta [file]
|
||||
/// Required argument for providing the metadata file for this script.
|
||||
|
||||
/// --github
|
||||
/// Use this option to pull release info from Github rather than the metadata file.
|
||||
|
||||
void main(List<String> arguments) async {
|
||||
if (Platform.isWindows) {
|
||||
throw Exception('Must be run under a UNIX-like operating system.');
|
||||
}
|
||||
|
||||
final metaIndex = arguments.indexOf('--meta');
|
||||
if (metaIndex == -1) {
|
||||
throw Exception(
|
||||
'You must run this script with a metadata file argument, using the --meta flag.');
|
||||
}
|
||||
if (arguments.length == metaIndex + 1) {
|
||||
throw Exception(
|
||||
'The --meta flag must be followed by the path to the metadata file.');
|
||||
}
|
||||
|
||||
final metaFile = File(arguments[metaIndex + 1]);
|
||||
if (!metaFile.existsSync()) {
|
||||
throw Exception('The provided metadata file does not exist.');
|
||||
}
|
||||
|
||||
final fetchFromGithub = arguments.contains('--github');
|
||||
|
||||
final meta =
|
||||
FlatpakMeta.fromJson(metaFile, skipLocalReleases: fetchFromGithub);
|
||||
|
||||
final outputDir =
|
||||
Directory('${Directory.current.path}/flatpak_generator_exports');
|
||||
outputDir.createSync();
|
||||
|
||||
final manifestGenerator = FlatpakManifestGenerator(meta);
|
||||
final manifestContent =
|
||||
await manifestGenerator.generateFlatpakManifest(fetchFromGithub);
|
||||
final manifestPath = '${outputDir.path}/${meta.appId}.json';
|
||||
final manifestFile = File(manifestPath);
|
||||
manifestFile.writeAsStringSync(manifestContent);
|
||||
print('Generated $manifestPath');
|
||||
|
||||
final flathubJsonContent =
|
||||
await manifestGenerator.generateFlathubJson(fetchFromGithub);
|
||||
if (flathubJsonContent != null) {
|
||||
final flathubJsonPath = '${outputDir.path}/flathub.json';
|
||||
final flathubJsonFile = File(flathubJsonPath);
|
||||
flathubJsonFile.writeAsStringSync(flathubJsonContent);
|
||||
print('Generated $flathubJsonPath');
|
||||
}
|
||||
}
|
||||
|
||||
// ${appId}.json
|
||||
class FlatpakManifestGenerator {
|
||||
final FlatpakMeta meta;
|
||||
Map<CPUArchitecture, bool>? _githubArchSupport;
|
||||
Map<CPUArchitecture, bool>? _localArchSupport;
|
||||
|
||||
FlatpakManifestGenerator(this.meta);
|
||||
|
||||
Future<String> generateFlatpakManifest(bool fetchFromGithub) async {
|
||||
final appName = meta.lowercaseAppName;
|
||||
final appId = meta.appId;
|
||||
final assets = await meta.getLatestReleaseAssets(fetchFromGithub);
|
||||
|
||||
if (assets == null) {
|
||||
throw Exception('There are no associated assets.');
|
||||
}
|
||||
|
||||
_lazyGenerateArchSupportMap(fetchFromGithub, assets);
|
||||
|
||||
const encoder = JsonEncoder.withIndent(' ');
|
||||
return encoder.convert({
|
||||
'app-id': appId,
|
||||
'runtime': 'org.freedesktop.Platform',
|
||||
'runtime-version': meta.freedesktopRuntime,
|
||||
'sdk': 'org.freedesktop.Sdk',
|
||||
'command': appName,
|
||||
'separate-locales': false,
|
||||
'finish-args': meta.finishArgs,
|
||||
'modules': <dynamic>[
|
||||
...meta.extraModules ?? <dynamic>[],
|
||||
{
|
||||
'name': appName,
|
||||
'buildsystem': 'simple',
|
||||
'build-commands': <dynamic>[
|
||||
'cp -R $appName/bin/ /app/$appName',
|
||||
'chmod +x /app/$appName/$appName',
|
||||
'mkdir -p /app/bin/',
|
||||
'mkdir -p /app/lib/',
|
||||
'ln -s /app/$appName/$appName /app/bin/$appName',
|
||||
...meta.buildCommandsAfterUnpack ?? [],
|
||||
...meta.icons.map((icon) =>
|
||||
'install -Dm644 $appName/icons/${icon.type}/${icon.getFilename(appId)} /app/share/icons/hicolor/${icon.type}/apps/${icon.getFilename(appId)}'),
|
||||
'install -Dm644 $appName/$appId.desktop /app/share/applications/$appId.desktop',
|
||||
'install -Dm644 $appName/$appId.metainfo.xml /app/share/metainfo/$appId.metainfo.xml'
|
||||
],
|
||||
'sources': assets
|
||||
.map((a) => {
|
||||
'type': 'archive',
|
||||
'only-arches': [a.arch.flatpakArchCode],
|
||||
(fetchFromGithub ? 'url' : 'path'): a.tarballUrlOrPath,
|
||||
'sha256': a.tarballSha256,
|
||||
'dest': meta.lowercaseAppName
|
||||
})
|
||||
.toList()
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
Future<String?> generateFlathubJson(bool fetchFromGithub) async {
|
||||
final assets = await meta.getLatestReleaseAssets(fetchFromGithub);
|
||||
|
||||
if (assets == null) {
|
||||
throw Exception('There are no associated assets.');
|
||||
}
|
||||
|
||||
_lazyGenerateArchSupportMap(fetchFromGithub, assets);
|
||||
|
||||
const encoder = JsonEncoder.withIndent(' ');
|
||||
|
||||
final onlyArchListInput =
|
||||
fetchFromGithub ? _githubArchSupport! : _localArchSupport!;
|
||||
|
||||
final onlyArchList = List<String>.empty(growable: true);
|
||||
for (final e in onlyArchListInput.entries) {
|
||||
if (e.value == true) {
|
||||
onlyArchList.add(e.key.flatpakArchCode);
|
||||
}
|
||||
}
|
||||
|
||||
if (onlyArchList.length == CPUArchitecture.values.length) {
|
||||
return null;
|
||||
} else {
|
||||
return encoder.convert({'only-arches': onlyArchList});
|
||||
}
|
||||
}
|
||||
|
||||
void _lazyGenerateArchSupportMap(
|
||||
bool fetchFromGithub, List<ReleaseAsset> assets) {
|
||||
if (fetchFromGithub) {
|
||||
if (_githubArchSupport == null) {
|
||||
_githubArchSupport = <CPUArchitecture, bool>{
|
||||
for (final arch in CPUArchitecture.values) arch: false
|
||||
};
|
||||
for (final a in assets) {
|
||||
_githubArchSupport![a.arch] = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (_localArchSupport == null) {
|
||||
_localArchSupport = <CPUArchitecture, bool>{
|
||||
for (final arch in CPUArchitecture.values) arch: false
|
||||
};
|
||||
for (final a in assets) {
|
||||
_localArchSupport![a.arch] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
85
flatpak/scripts/pubspec.lock
Normal file
85
flatpak/scripts/pubspec.lock
Normal file
@ -0,0 +1,85 @@
|
||||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.11.0"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.17.1"
|
||||
http:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: http
|
||||
sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.13.6"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_parser
|
||||
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.2"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.8.3"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.0"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.2"
|
||||
sdks:
|
||||
dart: ">=2.19.0 <4.0.0"
|
8
flatpak/scripts/pubspec.yaml
Normal file
8
flatpak/scripts/pubspec.yaml
Normal file
@ -0,0 +1,8 @@
|
||||
name: flatpak_generator
|
||||
version: 1.0.0
|
||||
|
||||
environment:
|
||||
sdk: '>=2.18.5'
|
||||
|
||||
dependencies:
|
||||
http: ^0.13.5
|
Reference in New Issue
Block a user