Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Flatpak updates #484

Merged
merged 2 commits into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions .github/workflows/build-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,9 @@ jobs:
uses: actions/checkout@v4

- name: Setup Java
uses: actions/setup-java@v4
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'oracle'
java-version: 17.x

- name: Setup Ruby
uses: ruby/setup-ruby@v1
Expand Down Expand Up @@ -64,7 +63,7 @@ jobs:
flutter build linux --release
cd flatpak/scripts
dart pub get
dart flatpak_packager.dart --meta ../flatpak_meta.json --github
dart flatpak_packager.dart --meta ../flatpak_meta.json --github --addTodaysVersion ${{ env.VERSION }}

- name: Build AAB
run: flutter build appbundle --release
Expand Down
8 changes: 1 addition & 7 deletions flatpak/flatpak_meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,8 @@
"lowercaseAppName": "wger",
"githubReleaseOrganization": "wger-project",
"githubReleaseProject": "flutter",
"localReleases": [

],
"localReleaseAssets": [

],
"localLinuxBuildDir": "../build/linux",
"appDataPath": "de.wger.flutter.appdata.xml",
"appStreamPath": "de.wger.flutter.metainfo.xml",
"desktopPath": "de.wger.flutter.desktop",
"icons": {
"64x64": "logo64.png",
Expand Down
123 changes: 73 additions & 50 deletions flatpak/scripts/flatpak_packager.dart
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
// ignore_for_file: avoid_print

import 'dart:io';

import 'flatpak_shared.dart';

/// Creates an archive containing all the sources for the Flatpak package for a
/// specific architecture.
///
/// 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.isWindows) {
throw Exception('Must be run under a UNIX-like operating system.');
if (!Platform.isLinux) {
throw Exception('Must be run under Linux');
}

// PARSE ARGUMENTS
final metaIndex = arguments.indexOf('--meta');
if (metaIndex == -1) {
throw Exception(
Expand All @@ -22,123 +32,136 @@ void main(List<String> arguments) async {
}

final metaFile = File(arguments[metaIndex + 1]);
if (!metaFile.existsSync()) {
if (!(await metaFile.exists())) {
throw Exception('The provided metadata file does not exist.');
}

final meta = FlatpakMeta.fromJson(metaFile);

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');
outputDir.createSync();
await outputDir.create();

final packageGenerator = PackageGenerator(inputDir: metaFile.parent, meta: meta);
final packageGenerator = PackageGenerator(
inputDir: metaFile.parent, meta: meta, addedTodaysVersion: addedTodaysVersion);

packageGenerator.generatePackage(
outputDir,
PackageGenerator.runningOnARM() ? CPUArchitecture.aarch64 : CPUArchitecture.x86_64,
fetchFromGithub,
);
await packageGenerator.generatePackage(
outputDir,
await PackageGenerator.runningOnARM() ? CPUArchitecture.aarch64 : CPUArchitecture.x86_64,
fetchFromGithub);
}

class PackageGenerator {
final Directory inputDir;
final FlatpakMeta meta;
final Map<CPUArchitecture, String> shaByArch = {};
final String? addedTodaysVersion;

PackageGenerator({required this.inputDir, required this.meta});
PackageGenerator({required this.inputDir, required this.meta, required this.addedTodaysVersion});

Future<void> generatePackage(
Directory outputDir, CPUArchitecture arch, bool fetchReleasesFromGithub) async {
final tempDir = outputDir.createTempSync('flutter_generator_temp');
final tempDir = await outputDir.createTemp('flutter_generator_temp');
final appId = meta.appId;

// desktop file
final desktopFile = File('${inputDir.path}/${meta.desktopPath}');

if (!desktopFile.existsSync()) {
if (!(await desktopFile.exists())) {
throw Exception(
'The desktop file does not exist under the specified path: ${desktopFile.path}');
}

desktopFile.copySync('${tempDir.path}/$appId.desktop');
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 (!iconFile.existsSync()) {
if (!(await iconFile.exists())) {
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}/${icon.getFilename(appId)}');
await iconSubdir.create(recursive: true);
await iconFile.copy('${iconSubdir.path}/${icon.getFilename(appId)}');
}

// appdata file
final origAppDataFile = File('${inputDir.path}/${meta.appDataPath}');
if (!origAppDataFile.existsSync()) {
// 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: ${origAppDataFile.path}');
'The app data file does not exist under the specified path: ${origAppStreamFile.path}');
}

final editedAppDataContent = AppDataModifier.replaceVersions(
origAppDataFile.readAsStringSync(), await meta.getReleases(fetchReleasesFromGithub));
final editedAppStreamContent = AppStreamModifier.replaceVersions(
await origAppStreamFile.readAsString(),
await meta.getReleases(fetchReleasesFromGithub, addedTodaysVersion));

final editedAppDataFile = File('${tempDir.path}/$appId.appdata.xml');
editedAppDataFile.writeAsStringSync(editedAppDataContent);
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 (!buildDir.existsSync()) {
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');
destDir.createSync();
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';

final baseFileName = '${meta.lowercaseAppName}-linux-${arch.flatpakArchCode}';
await Process.run('cp', ['-r', '${buildDir.absolute.path}/.', destDir.absolute.path]);
await Process.run('tar', ['-czvf', packagePath, '.'], workingDirectory: tempDir.absolute.path);

final packagePath = '${outputDir.absolute.path}/$baseFileName.tar.gz';
Process.runSync('cp', ['-r', '${buildDir.absolute.path}/.', destDir.absolute.path]);
Process.runSync('tar', ['-czvf', packagePath, '.'], workingDirectory: tempDir.absolute.path);
print('Generated $packagePath');

final preShasum = Process.runSync('shasum', ['-a', '256', packagePath]);
final sha256 = preShasum.stdout.toString().split(' ').first;
final preShasum = await Process.run('shasum', ['-a', '256', packagePath]);
final shasum = preShasum.stdout.toString().split(' ').first;

final shaFile = await File('${outputDir.path}/$baseFileName.sha256').writeAsString(sha256);
print('Generated ${shaFile.path}');
await File(shaPath).writeAsString(shasum);

shaByArch.putIfAbsent(arch, () => sha256);
print('Generated $shaPath');

tempDir.deleteSync(recursive: true);
await tempDir.delete(recursive: true);
}

static bool runningOnARM() {
final unameRes = Process.runSync('uname', ['-m']);
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}.appdata.xml
class AppDataModifier {
static String replaceVersions(String origAppDataContent, List<Release> versions) {
// 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 (origAppDataContent.contains('<releases')) {
return origAppDataContent
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 origAppDataContent.replaceFirst('</component>', '\n\t$releasesSection\n</component>');
return origAppStreamContent.replaceFirst(
'</component>', '\n\t$releasesSection\n</component>');
}
}
}
Loading