diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..8c0202a --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +version: 2 +updates: + # Maintain GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 5 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..1483343 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,105 @@ +name: CI + +on: + push: + pull_request: + +permissions: + contents: read + +env: + RELEASEVER: "44" + +jobs: + format: + name: Check Nim formatting + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + submodules: recursive + + - name: Install Nim + uses: jiro4989/setup-nim-action@cb1fc1270b117457dd0d9f610ec981fad3369cec # v2.3.0 + with: + nim-version: "2.2.10" + + - name: Check formatting + run: ./scripts/check-format.sh + + build: + name: Build application + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + submodules: recursive + + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install --no-install-recommends -y \ + libadwaita-1-dev \ + libgtk-4-dev \ + pkg-config + + - name: Install Nim + uses: jiro4989/setup-nim-action@cb1fc1270b117457dd0d9f610ec981fad3369cec # v2.3.0 + with: + nim-version: "2.2.10" + + - name: Build + run: nimble build --define:releasever=${RELEASEVER} --verbose + + - name: Prepare build artifact + run: | + if [[ ! -x ./umswitch ]]; then + candidate=$(find . "$HOME/.nimble/bin" \ + -type f -name umswitch -not -path './umswitch' \ + -print -quit 2>/dev/null || true) + if [[ -z "$candidate" ]]; then + echo "umswitch binary was not produced. Build outputs:" >&2 + find . "$HOME/.nimble/bin" -maxdepth 4 -type f -print 2>/dev/null | sort >&2 + exit 1 + fi + cp "$candidate" ./umswitch + fi + test -x ./umswitch + file ./umswitch + + - name: Upload binary + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: umswitch + path: umswitch + if-no-files-found: error + + test: + name: Compile tests + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + submodules: recursive + + - name: Install Nim + uses: jiro4989/setup-nim-action@cb1fc1270b117457dd0d9f610ec981fad3369cec # v2.3.0 + with: + nim-version: "2.2.10" + + - name: Install test dependencies + run: | + mkdir -p /tmp/hop-test-deps + printf 'version = "0.0.1"\nauthor = "CI"\ndescription = "CI test dependencies"\nlicense = "MIT"\nrequires "fungus == 0.1.19"\n' > /tmp/hop-test-deps/testdeps.nimble + cd /tmp/hop-test-deps + nimble install -y + + - name: Compile test program + run: | + nim c --hints:off \ + --path:$HOME/.nimble/pkgs2/fungus-* \ + --path:$HOME/.nimble/pkgs2/micros-* \ + test.nim diff --git a/scripts/check-format.sh b/scripts/check-format.sh new file mode 100755 index 0000000..c929109 --- /dev/null +++ b/scripts/check-format.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) +tmp_dir=$(mktemp -d) +trap 'rm -rf "$tmp_dir"' EXIT + +status=0 +while IFS= read -r -d '' file; do + formatted="$tmp_dir/$(basename "$file")" + nimpretty --out:"$formatted" "$repo_root/$file" >/dev/null + nimpretty "$formatted" >/dev/null + if ! diff -u "$repo_root/$file" "$formatted"; then + status=1 + fi +done < <(git -C "$repo_root" ls-files -z -- '*.nim') + +if [[ "$status" -ne 0 ]]; then + echo "Formatting check failed. Run nimpretty on the files above." >&2 + exit "$status" +fi + +echo "All Nim files are formatted." diff --git a/src/backend/add.nim b/src/backend/add.nim index ff816be..b9b5fb8 100644 --- a/src/backend/add.nim +++ b/src/backend/add.nim @@ -10,7 +10,8 @@ const de_to_pkgs_to_add: Table[string, seq[string]] = { "XFCE": @["@ultramarine-xfce-product-environment"], }.toTable() -proc add_de_offline*(hub: ref Hub, de: string): Result[void, string] {.thread.} = +proc add_de_offline*(hub: ref Hub, de: string): Result[void, + string] {.thread.} = ?ensure_dnf5() echo "Downloading packages..." hub.toMain.send UpdateState.init "Downloading packages..." @@ -21,7 +22,7 @@ proc add_de_offline*(hub: ref Hub, de: string): Result[void, string] {.thread.} let time = now() var args = @["in", "-y", "--offline"] args &= pkgs - let process = startProcess("/usr/bin/dnf5", args=args, options = {poStdErrToStdOut}) + let process = startProcess("/usr/bin/dnf5", args = args, options = {poStdErrToStdOut}) track_dnf5_download_progress(process, some(hub)) ?end_proc(process, time, "Downloading Packages", "arrange offline DE install") hub.toMain.send MsgToMain DownloadFinish.init diff --git a/src/backend/change.nim b/src/backend/change.nim index 36413ed..c703a4b 100644 --- a/src/backend/change.nim +++ b/src/backend/change.nim @@ -18,7 +18,8 @@ proc swap*(hub: ref Hub, to: string): Result[void, string] {.thread.} = echo "├═ New: "&to stdout.write "┊ " let time = now() - let process = startProcess("/usr/bin/dnf5", args=["swap", "-y", "ultramarine-release-identity", to], options = {poStdErrToStdOut}) + let process = startProcess("/usr/bin/dnf5", args = ["swap", "-y", + "ultramarine-release-identity", to], options = {poStdErrToStdOut}) track_dnf5_download_progress(process, some(hub)) ?end_proc(process, time, "Swap Editions", "swap editions") hub.toMain.send MsgToMain DownloadFinish.init diff --git a/src/backend/delete.nim b/src/backend/delete.nim index afa3b50..b679d0a 100644 --- a/src/backend/delete.nim +++ b/src/backend/delete.nim @@ -11,7 +11,8 @@ const de_to_pkgs_to_rm: Table[string, seq[string]] = { "XFCE": @["@ultramarine-xfce-product-environment"], }.toTable() -proc remove_de_offline*(hub: ref Hub, de: string): Result[void, string] {.thread.} = +proc remove_de_offline*(hub: ref Hub, de: string): Result[void, + string] {.thread.} = ?ensure_dnf5() echo "Removing packages..." hub.toMain.send UpdateState.init("Running dnf5...") @@ -22,7 +23,7 @@ proc remove_de_offline*(hub: ref Hub, de: string): Result[void, string] {.thread let time = now() var args = @["rm", "-y", "--offline"] args &= pkgs - let process = startProcess("/usr/bin/dnf5", args=args, options = {poStdErrToStdOut}) + let process = startProcess("/usr/bin/dnf5", args = args, options = {poStdErrToStdOut}) track_dnf5_download_progress(process, none(ref Hub)) ?end_proc(process, time, "Remove DE Offline", "arrange offline DE remove") ?reboot_apply_offline hub diff --git a/src/backend/pkgs.nim b/src/backend/pkgs.nim index 2b9f0be..c14f83a 100644 --- a/src/backend/pkgs.nim +++ b/src/backend/pkgs.nim @@ -45,7 +45,8 @@ proc ensure_dnf5*(): Result[void, string] = return err fmt"Fail to install dnf5 ({rc=})" ok() -proc track_dnf5_download_progress*(process: Process, hub: Option[ref Hub]) {.thread.} = +proc track_dnf5_download_progress*(process: Process, hub: Option[ + ref Hub]) {.thread.} = let outs = process.outputStream var line = "" var progress = 0.0 @@ -76,7 +77,8 @@ proc track_dnf5_download_progress*(process: Process, hub: Option[ref Hub]) {.thr hub.get.toMain.send Progress.init progress except: discard -proc end_proc*(process: Process, startTime: DateTime, action: string, errAction: string = ""): Result[void, string] {.thread.} = +proc end_proc*(process: Process, startTime: DateTime, action: string, + errAction: string = ""): Result[void, string] {.thread.} = defer: process.close() let errAction = if errAction == "": action else: errAction let rc = process.peekExitCode @@ -90,7 +92,7 @@ proc end_proc*(process: Process, startTime: DateTime, action: string, errAction: echo "end_proc finished!" ok() -proc reboot_apply_offline*(hub: ref Hub): Result[void, string] = +proc reboot_apply_offline*(hub: ref Hub): Result[void, string] = echo "reboot_apply_offline()" hub.toMain.send UpdateState.init("Rebooting...") let rc = execCmd("dnf5 offline reboot -y") diff --git a/src/pages/addDownload.nim b/src/pages/addDownload.nim index 9f84d1c..d5313a9 100644 --- a/src/pages/addDownload.nim +++ b/src/pages/addDownload.nim @@ -13,13 +13,13 @@ viewable AddDownloadPage: hub: ref Hub first: bool = true progress: float = 0.0 - + hooks: afterBuild: proc redrawer(): bool = if state.hub[].toMain.peek > 0: discard redraw state - + const KEEP_LISTENER_ACTIVE = true return KEEP_LISTENER_ACTIVE discard addGlobalTimeout(200, redrawer) diff --git a/src/pages/changeApply.nim b/src/pages/changeApply.nim index 3f6ead1..a4a00c2 100644 --- a/src/pages/changeApply.nim +++ b/src/pages/changeApply.nim @@ -18,7 +18,7 @@ viewable ChangeApplyPage: proc redrawer(): bool = if state.hub[].toMain.peek > 0: discard redraw state - + const KEEP_LISTENER_ACTIVE = true return KEEP_LISTENER_ACTIVE discard addGlobalTimeout(200, redrawer) diff --git a/src/pages/deleteReboot.nim b/src/pages/deleteReboot.nim index 3a35e4f..2a066e4 100644 --- a/src/pages/deleteReboot.nim +++ b/src/pages/deleteReboot.nim @@ -16,7 +16,7 @@ viewable DeleteRebootPage: proc redrawer(): bool = if state.hub[].toMain.peek > 0: discard redraw state - + const KEEP_LISTENER_ACTIVE = true return KEEP_LISTENER_ACTIVE discard addGlobalTimeout(200, redrawer) diff --git a/src/umswitch.nim b/src/umswitch.nim index 8dc0f3a..9f180c1 100644 --- a/src/umswitch.nim +++ b/src/umswitch.nim @@ -1,7 +1,8 @@ import std/[os, osproc, sequtils, envvars, sugar] import owlkettle, owlkettle/adw import app -import pages/[action, add, addDownload, delete, deleteReboot, change, changeApply, zNotFound, zError] +import pages/[action, add, addDownload, delete, deleteReboot, change, + changeApply, zNotFound, zError] import backend/pkgs const @@ -39,21 +40,25 @@ proc main = # let logfile = open(logfilepath, fmWrite) # defer: logfile.close() adw.brew(gui App( - installed_desktops=package_installed(editions.values.toSeq), - installed_identities=package_installed(identities.values.toSeq), - ), stylesheets=stylesheets) + installed_desktops = package_installed(editions.values.toSeq), + installed_identities = package_installed(identities.values.toSeq), + ), stylesheets = stylesheets) when isMainModule: if os.isAdmin(): main() quit(0) when usesudo != 0: - quit findExe("sudo").startProcess(args=getAppFilename()&commandLineParams(), options={poParentStreams}).waitForExit + quit findExe("sudo").startProcess(args = getAppFilename()&commandLineParams( + ), options = {poParentStreams}).waitForExit # run umswitch as root via pkexec - discard findExe("xhost").startProcess(args=["si:localuser:root", "+localhost"], options={poParentStreams}).waitForExit + discard findExe("xhost").startProcess(args = ["si:localuser:root", + "+localhost"], options = {poParentStreams}).waitForExit let envs = collect: for k, v in envPairs(): k&"="&v - let rc = findExe("pkexec").startProcess(args = @["env"]&envs&getAppFilename()&commandLineParams(), options={poParentStreams}).waitForExit - discard findExe("xhost").startProcess(args=["-si:localuser:root", "-localhost"], options={poParentStreams}).waitForExit + let rc = findExe("pkexec").startProcess(args = @["env"]&envs&getAppFilename( + )&commandLineParams(), options = {poParentStreams}).waitForExit + discard findExe("xhost").startProcess(args = ["-si:localuser:root", + "-localhost"], options = {poParentStreams}).waitForExit quit rc diff --git a/umswitch.nimble b/umswitch.nimble index de8521d..6944b07 100644 --- a/umswitch.nimble +++ b/umswitch.nimble @@ -9,12 +9,16 @@ bin = @["umswitch"] import nimsutils/src/nimsutils -import std/[syncio, strutils] +import std/[envvars, syncio, strutils] proc get_releasever(): int = + let requested = getEnv("RELEASEVER") + if requested != "": + return requested.parseInt for l in readFile("/etc/os-release").splitLines: if l.starts_with("VERSION_ID="): - return l[11..12].parseInt + let version = l["VERSION_ID=".len .. ^1].strip(chars = {'"'}) + return version.split('.')[0].parseInt error("cannot find VERSION_ID in /etc/os-release") quit 1 @@ -29,4 +33,4 @@ xtask r, "alias for run": setCommand "run" requires "nim >= 2.0.0" requires "https://github.com/can-lehmann/owlkettle#HEAD" requires "fungus" -requires "results == 0.5.0" \ No newline at end of file +requires "results == 0.5.0"