Skip to content

Commit 11f1736

Browse files
authored
Merge pull request #23 from notimaginative/release_workflow
add GitHub release workflow and AppImage support
2 parents 1410f64 + 938bfeb commit 11f1736

File tree

17 files changed

+485
-35
lines changed

17 files changed

+485
-35
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
name: Create Release
2+
3+
on:
4+
push:
5+
tags:
6+
- 'v*.*.*'
7+
8+
permissions:
9+
contents: write
10+
11+
env:
12+
OUTPUT_DIR: ${{ github.workspace }}/build/packages
13+
PUBLISH_DIR: ${{ github.workspace }}/build/publish
14+
15+
jobs:
16+
build:
17+
name: Build and Release
18+
runs-on: ubuntu-latest
19+
steps:
20+
- name: Checkout code
21+
uses: actions/checkout@v3
22+
with:
23+
fetch-depth: 0
24+
ref: '${{ github.ref }}'
25+
26+
- name: Install utilities
27+
# zip for packaging windows builds
28+
run: sudo apt-get update && sudo apt-get install zip
29+
30+
- name: Create directories
31+
run: |
32+
mkdir -p "$OUTPUT_DIR"
33+
mkdir -p "$PUBLISH_DIR"
34+
35+
- name: Publish all RIDs
36+
run: ${{ github.workspace }}/ci/publish_all.sh
37+
38+
- name: Package all RIDs
39+
run: ${{ github.workspace }}/ci/package_all.sh
40+
41+
- name: Set up QEMU integration for Docker
42+
run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
43+
44+
- name: Create AppImage (x86_64)
45+
env:
46+
ARCH: x86_64
47+
run: ${{ github.workspace }}/ci/create_appimage.sh
48+
49+
- name: Create AppImage (aarch64)
50+
env:
51+
ARCH: aarch64
52+
run: ${{ github.workspace }}/ci/create_appimage.sh
53+
54+
- name: Generate Release
55+
uses: softprops/action-gh-release@v1
56+
with:
57+
# body_path: ${{ github.workspace }}/CHANGES.md
58+
draft: true
59+
files: ${{ env.OUTPUT_DIR }}/*

Knossos.NET/Classes/KnUtils.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ private struct LegacySettings
2929
private static readonly bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
3030
private static readonly bool isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
3131
private static readonly bool isMacOS = RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
32+
private static readonly bool isAppImage = isLinux && !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("APPIMAGE"));
3233
private static readonly bool cpuAVX = Avx.IsSupported;
3334
private static readonly bool cpuAVX2 = Avx2.IsSupported;
3435
private static string fsoPrefPath = string.Empty;
@@ -39,6 +40,13 @@ private struct LegacySettings
3940
public static string CpuArch => cpuArch;
4041
public static bool CpuAVX => cpuAVX;
4142
public static bool CpuAVX2 => cpuAVX2;
43+
public static bool IsAppImage => isAppImage;
44+
public static string AppImagePath => appImagePath;
45+
46+
/// <summary>
47+
/// Full path to AppImage file
48+
/// </summary>
49+
private static readonly string appImagePath = Environment.GetEnvironmentVariable("APPIMAGE")!;
4250

4351
/// <summary>
4452
/// Possible Values:

Knossos.NET/Classes/Knossos.cs

Lines changed: 80 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -222,14 +222,28 @@ private static async Task CheckKnetUpdates()
222222
GitHubReleaseAsset? releaseAsset = null;
223223
foreach (GitHubReleaseAsset a in latest.assets)
224224
{
225-
if(a.name != null && ( a.name.ToLower().Contains(".zip") || a.name.ToLower().Contains(".7z") || a.name.ToLower().Contains(".tar.gz")))
225+
if (KnUtils.IsAppImage)
226226
{
227-
if(a.name.ToLower().Contains(KnUtils.GetOSNameString().ToLower()))
227+
if (a.name != null)
228228
{
229-
if (a.name.ToLower().Contains(KnUtils.CpuArch.ToLower()) && ( KnUtils.CpuArch != "Arm" || KnUtils.CpuArch == "Arm" && !a.name.ToLower().Contains("arm64")))
229+
if ((KnUtils.CpuArch.ToLower() == "x64" && a.name.EndsWith("x86_64.AppImage")) || (KnUtils.CpuArch.ToLower() == "arm64" && a.name.EndsWith("aarch64.AppImage")))
230230
{
231231
releaseAsset = a;
232-
continue;
232+
break;
233+
}
234+
}
235+
}
236+
else
237+
{
238+
if(a.name != null && ( a.name.ToLower().Contains(".zip") || a.name.ToLower().Contains(".7z") || a.name.ToLower().Contains(".tar.gz")))
239+
{
240+
if(a.name.ToLower().Contains(KnUtils.GetOSNameString().ToLower()))
241+
{
242+
if (a.name.ToLower().Contains(KnUtils.CpuArch.ToLower()) && ( KnUtils.CpuArch != "Arm" || KnUtils.CpuArch == "Arm" && !a.name.ToLower().Contains("arm64")))
243+
{
244+
releaseAsset = a;
245+
continue;
246+
}
233247
}
234248
}
235249
}
@@ -256,6 +270,11 @@ private static async Task CheckKnetUpdates()
256270
//Rename files in app folder
257271
var appDirPath = System.AppDomain.CurrentDomain.BaseDirectory;
258272
var execName = System.Diagnostics.Process.GetCurrentProcess().MainModule!.FileName;
273+
if (KnUtils.IsAppImage)
274+
{
275+
// change exec name to be the AppImage itself (is full path!)
276+
execName = KnUtils.AppImagePath;
277+
}
259278
File.Move(execName!, execName + ".old", true);
260279
if (KnUtils.IsWindows)
261280
{
@@ -275,7 +294,7 @@ private static async Task CheckKnetUpdates()
275294
}
276295
catch { }
277296
}
278-
if(KnUtils.IsLinux)
297+
if(KnUtils.IsLinux && !KnUtils.IsAppImage)
279298
{
280299
try
281300
{
@@ -310,51 +329,77 @@ private static async Task CheckKnetUpdates()
310329
//Decompress new files
311330
try
312331
{
313-
using (var archive = ArchiveFactory.Open(KnUtils.GetKnossosDataFolderPath() + Path.DirectorySeparatorChar + "update"+ extension))
332+
// no extraction needed for AppImage, just move exec over and restart
333+
// NOTE: exeName is already the full path to the AppImage
334+
if (KnUtils.IsAppImage)
314335
{
315-
try
316-
{
317-
var reader = archive.ExtractAllEntries();
318-
while (reader.MoveToNextEntry())
319-
{
320-
if (!reader.Entry.IsDirectory)
321-
{
322-
reader.WriteEntryToDirectory(appDirPath!, new ExtractionOptions() { ExtractFullPath = false, Overwrite = true });
323-
}
324-
}
325-
}
326-
catch (Exception ex)
327-
{
328-
Log.Add(Log.LogSeverity.Error, "Knossos.CheckKnetUpdates()", ex);
329-
}
336+
File.Move(KnUtils.GetKnossosDataFolderPath() + Path.DirectorySeparatorChar + "update"+ extension, execName!);
330337

331338
//Start again
332339
try
333340
{
334-
if (KnUtils.IsMacOS || KnUtils.IsLinux)
335-
{
336-
KnUtils.Chmod(Path.Combine(appDirPath, execName!), "+x");
337-
}
341+
KnUtils.Chmod(execName!, "+x");
342+
338343
Process p = new Process();
339-
p.StartInfo.FileName = Path.Combine(appDirPath, execName!);
344+
p.StartInfo.FileName = execName!;
340345
p.Start();
341346
}
342347
catch (Exception ex)
343348
{
344349
Log.Add(Log.LogSeverity.Error, "Knossos.CheckKnetUpdates()", ex);
345350
}
346351

347-
//Cleanup file
348-
try
349-
{
350-
if (File.Exists(KnUtils.GetKnossosDataFolderPath() + Path.DirectorySeparatorChar + "update" + extension))
351-
File.Delete(KnUtils.GetKnossosDataFolderPath() + Path.DirectorySeparatorChar + "update" + extension);
352-
}
353-
catch { }
354-
355352
//Close App
356353
MainWindow.instance!.Close();
357354
}
355+
else
356+
{
357+
using (var archive = ArchiveFactory.Open(KnUtils.GetKnossosDataFolderPath() + Path.DirectorySeparatorChar + "update"+ extension))
358+
{
359+
try
360+
{
361+
var reader = archive.ExtractAllEntries();
362+
while (reader.MoveToNextEntry())
363+
{
364+
if (!reader.Entry.IsDirectory)
365+
{
366+
reader.WriteEntryToDirectory(appDirPath!, new ExtractionOptions() { ExtractFullPath = false, Overwrite = true });
367+
}
368+
}
369+
}
370+
catch (Exception ex)
371+
{
372+
Log.Add(Log.LogSeverity.Error, "Knossos.CheckKnetUpdates()", ex);
373+
}
374+
375+
//Start again
376+
try
377+
{
378+
if (KnUtils.IsMacOS || KnUtils.IsLinux)
379+
{
380+
KnUtils.Chmod(Path.Combine(appDirPath, execName!), "+x");
381+
}
382+
Process p = new Process();
383+
p.StartInfo.FileName = Path.Combine(appDirPath, execName!);
384+
p.Start();
385+
}
386+
catch (Exception ex)
387+
{
388+
Log.Add(Log.LogSeverity.Error, "Knossos.CheckKnetUpdates()", ex);
389+
}
390+
391+
//Cleanup file
392+
try
393+
{
394+
if (File.Exists(KnUtils.GetKnossosDataFolderPath() + Path.DirectorySeparatorChar + "update" + extension))
395+
File.Delete(KnUtils.GetKnossosDataFolderPath() + Path.DirectorySeparatorChar + "update" + extension);
396+
}
397+
catch { }
398+
399+
//Close App
400+
MainWindow.instance!.Close();
401+
}
402+
}
358403
}catch(Exception ex)
359404
{
360405
//Rollback
@@ -381,7 +426,7 @@ private static async Task CheckKnetUpdates()
381426
}
382427
catch { }
383428
}
384-
if (KnUtils.IsLinux)
429+
if (KnUtils.IsLinux && !KnUtils.IsAppImage)
385430
{
386431
try
387432
{

ci/bundle_appimage.sh

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
#!/bin/bash
2+
3+
fail_msg() {
4+
echo "$1"
5+
exit 1
6+
}
7+
8+
#
9+
# This script should be run in, or with the working directory set to, the root project directory
10+
#
11+
12+
pushd "$(readlink -f "$(dirname "${BASH_SOURCE[0]}")"/..)" > /dev/null
13+
14+
# grab default variables
15+
source ./packaging/vars
16+
17+
[ -n "$NAME" ] || fail_msg "ERROR: Name isn't specified!"
18+
[ -n "$ID" ] || fail_msg "ERROR: ID isn't specified!"
19+
[ -n "$RIDS" ] || fail_msg "ERROR: Runtime IDs not specified!"
20+
21+
PUBLISH_DIR="$(readlink -f ${PUBLISH_DIR:="./build/publish"})"
22+
23+
[ -n "$PUBLISH_DIR" ] || fail_msg "ERROR: Publish directory isn't set!"
24+
[ -d "$PUBLISH_DIR" ] || fail_msg "ERROR: Publish location isn't a directory!"
25+
26+
OUTPUT_DIR="$(readlink -f ${OUTPUT_DIR:="./build/packages"})"
27+
28+
[ -n "$OUTPUT_DIR" ] || fail_msg "ERROR: Output directory isn't set!"
29+
[ -d "$OUTPUT_DIR" ] || fail_msg "ERROR: Output location isn't a directory!"
30+
31+
# there should already be a usable binary, so just figure out what we should use
32+
DOTNET_ARCH=
33+
34+
case "$ARCH" in
35+
"x86_64" | "x64")
36+
DOTNET_ARCH="x64"
37+
;;
38+
"aarch64" | "arm64")
39+
DOTNET_ARCH="arm64"
40+
;;
41+
*)
42+
fail_msg "Unknown arch! Aborting!"
43+
;;
44+
esac
45+
46+
[ -n "$DOTNET_ARCH" ] || fail_msg "ERROR: No arch specified!"
47+
48+
# make sure needed archive exists (should have checked for this already!!)
49+
[ -f "$OUTPUT_DIR/Linux_$DOTNET_ARCH.tar.gz" ] || fail_msg "ERROR: Linux $DOTNET_ARCH archive not found!"
50+
51+
52+
VERSION="$(grep string\ AppVersion -rI $NAME | cut -d \" -f 2)" # this will surely come back to bite me
53+
54+
BUILD_DIR="/appimage"
55+
APPDIR="$BUILD_DIR/AppDir"
56+
57+
FILE_ROOT="./packaging/linux"
58+
59+
APPRUN_FILE="AppRun"
60+
METAINFO_FILE="knossos.metainfo.xml.in"
61+
DESKTOP_FILE="knossos.desktop.in"
62+
63+
ICON_SIZES="256 128 64 48 32 16" # must include 256x256 at a minimum
64+
ICON_FILE="knossos"
65+
66+
APPIMAGE_TOOL_URL="https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-$ARCH.AppImage"
67+
68+
UPDATE_SCHEME="gh-releases-zsync|ShivanSpS|$NAME|latest|$NAME-*$ARCH.AppImage.zsync"
69+
70+
71+
# install required packages:
72+
# for this script: wget, envsubst (from gettext)
73+
# for appimagetool: zsync, file, appstream, gpg
74+
# for docker arm64 multiarch workaround: qemu-user-static
75+
export DEBIAN_FRONTEND=noninteractive
76+
apt update && apt install -y wget gettext-base zsync file appstream gpg qemu-user-static
77+
78+
79+
# grab appimagetool
80+
mkdir -p "$BUILD_DIR"
81+
wget -O "$BUILD_DIR/appimagetool" "$APPIMAGE_TOOL_URL" || fail_msg "ERROR: Failed to get appimagetool!"
82+
chmod +x "$BUILD_DIR/appimagetool"
83+
84+
85+
# configure templates
86+
export _NAME="$NAME"
87+
export _ID="$ID"
88+
export _VERSION="$VERSION"
89+
90+
envsubst < "$FILE_ROOT/$METAINFO_FILE" > "$BUILD_DIR/$ID.appdata.xml"
91+
envsubst < "$FILE_ROOT/$DESKTOP_FILE" > "$BUILD_DIR/$ID.desktop"
92+
93+
unset _NAME _ID _VERSION
94+
95+
96+
# create and populate AppDir
97+
if [ -d "$APPDIR" ]
98+
then
99+
rm -Rf "$APPDIR"
100+
fi
101+
102+
install -d "$APPDIR"
103+
install -m 755 "$FILE_ROOT/$APPRUN_FILE" "$APPDIR"
104+
105+
install -d "$APPDIR/usr/bin"
106+
tar -xzvf "$OUTPUT_DIR/Linux_$DOTNET_ARCH.tar.gz" -C "$APPDIR/usr/bin"
107+
chmod +x "$APPDIR/usr/bin/$NAME"
108+
109+
install -d "$APPDIR/usr/share/applications"
110+
install -m 644 "$BUILD_DIR/$ID.desktop" "$APPDIR/usr/share/applications"
111+
112+
install -d "$APPDIR/usr/share/metainfo"
113+
install -m 644 "$BUILD_DIR/$ID.appdata.xml" "$APPDIR/usr/share/metainfo"
114+
115+
for size in $ICON_SIZES; do
116+
install -d "$APPDIR/usr/share/icons/hicolor/${size}x${size}/apps"
117+
install -m 644 "$FILE_ROOT/$ICON_FILE-$size.png" "$APPDIR/usr/share/icons/hicolor/${size}x${size}/apps/$ID.png"
118+
done
119+
120+
# provide symlinks to .desktop and app icon in root as required by AppImage spec
121+
ln -s "usr/share/applications/$ID.desktop" "$APPDIR"
122+
ln -s "usr/share/icons/hicolor/256x256/apps/$ID.png" "$APPDIR"
123+
124+
125+
#
126+
# create AppImage
127+
#
128+
129+
# can't use fuse in container, so work around it
130+
export APPIMAGE_EXTRACT_AND_RUN=1
131+
132+
# arm64 appimagetool doesn't run properly in qemu multiarch so work around it
133+
[ "$DOTNET_ARCH" = "arm64" ] && LAUNCHER="qemu-aarch64-static" || LAUNCHER=""
134+
135+
$LAUNCHER "$BUILD_DIR/appimagetool" -u "$UPDATE_SCHEME" "$APPDIR" "$OUTPUT_DIR/$NAME-$VERSION-$ARCH.AppImage"
136+
137+
# zsync file is saved to current directory so move it to proper place
138+
[ -f "$NAME-$VERSION-$ARCH.AppImage.zsync" ] && mv "$NAME-$VERSION-$ARCH.AppImage.zsync" "$OUTPUT_DIR"

0 commit comments

Comments
 (0)