diff --git a/.github/workflows/hyprcosmic.yml b/.github/workflows/hyprcosmic.yml index 26d8b80..b5b3720 100644 --- a/.github/workflows/hyprcosmic.yml +++ b/.github/workflows/hyprcosmic.yml @@ -42,10 +42,22 @@ jobs: - distro: arch image: archlinux:latest + # Every `run` step works from the crate, which the checkout below deliberately + # places one level down inside the parent repository rather than at the root. + defaults: + run: + working-directory: hyprcosmic/cosmic-session + steps: # Before checkout: actions/checkout needs git and these images are bare. + # + # The three of them override working-directory back to the workspace root, + # because the default above names a directory the checkout has not created + # yet and a run step whose working-directory does not exist fails before it + # runs anything. - name: Install build dependencies (fedora) if: matrix.distro == 'fedora' + working-directory: . run: dnf -y install --setopt=install_weak_deps=False git curl gcc just # bookworm has no `just` package -- it arrived in trixie -- so it is built @@ -54,6 +66,7 @@ jobs: # is better than a comment explaining an absence somewhere else. - name: Install build dependencies (debian) if: matrix.distro == 'debian' + working-directory: . run: | apt-get update DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ @@ -61,9 +74,31 @@ jobs: - name: Install build dependencies (arch) if: matrix.distro == 'arch' + working-directory: . run: pacman -Syu --noconfirm --needed git curl base-devel just - - uses: actions/checkout@v4 + # Two checkouts, laid out the way the working tree is laid out. + # + # Two tests here read `../config/autostart` through include_str!, so the + # crate does not compile its own test profile unless the parent repository + # is its parent directory. That coupling is deliberate -- the point of + # those tests is that the template this session actually ships still + # parses to the commands it documents, and a copy vendored in here to make + # the build self-contained would be a copy that silently goes stale. + # + # The parent lands first and this repository is checked out over the top of + # where its submodule would be, so the crate under test is the commit that + # triggered the run and not whatever the parent has pinned. + - name: Check out the parent repository + uses: actions/checkout@v4 + with: + repository: ${{ github.repository_owner }}/hyprcosmic + path: hyprcosmic + + - name: Check out this repository inside it + uses: actions/checkout@v4 + with: + path: hyprcosmic/cosmic-session - name: Install Rust run: | @@ -71,8 +106,11 @@ jobs: | sh -s -- -y --default-toolchain stable --profile minimal echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" + # Both paths are workspace-relative, and `defaults.run.working-directory` + # does not apply to actions -- only to `run` steps. - uses: Swatinem/rust-cache@v2 with: + workspaces: hyprcosmic/cosmic-session key: ${{ matrix.distro }} - name: Install just (debian only) @@ -114,7 +152,7 @@ jobs: - uses: actions/upload-artifact@v4 with: name: cosmic-session-${{ matrix.distro }} - path: stage/ + path: hyprcosmic/cosmic-session/stage/ retention-days: 14 # Off the matrix on purpose. Shell scripts do not vary by distribution, and diff --git a/src/profile.rs b/src/profile.rs index b4450d1..a52e68b 100644 --- a/src/profile.rs +++ b/src/profile.rs @@ -293,7 +293,9 @@ mod tests { assert_eq!(got.len(), 5, "{got:?}"); // First, so its startup compile lands before the compositor settles. assert_eq!(got[0], vec!["cosmic-conf", "watch"]); - assert_eq!(got[1][0], "waybar"); + // The bar is a shell invocation, because its stylesheet lives under a + // home directory that this file cannot expand on its own. + assert_eq!(got[1][0], "sh"); assert_eq!(got[2], vec!["awww-daemon"]); // Last, so the fallback terminal is the topmost window. assert_eq!(got[4], vec!["cosmic-term"]); @@ -321,8 +323,13 @@ mod tests { assert_eq!(argv.len(), 3, "{argv:?}"); // The symlink, not one of the copies beside it. Naming a copy would // strand this line on a path the next theme import deletes. + // + // `$HOME` rather than a real home directory: sh expands it, and the + // shipped template has to work for whoever installed it, not only for + // whoever wrote it. The double quotes are asserted with it, because + // dropping them is what would split the path at a space in $HOME. assert!( - argv[2].contains(r#""/home/dingo/.local/share/wallpapers/hyprcosmic/current""#), + argv[2].contains(r#""$HOME/.local/share/wallpapers/hyprcosmic/current""#), "{}", argv[2] ); @@ -330,4 +337,24 @@ mod tests { // must not be set until it answers. assert!(argv[2].starts_with("until awww query"), "{}", argv[2]); } + + /// The template is installed verbatim into every user's config directory, + /// so a literal `/home/someone` in it is a file that works for exactly one + /// person -- and it fails quietly for everyone else, because a waybar with + /// an unreadable stylesheet still starts and a wallpaper that was never set + /// looks the same as one that failed to load. + /// + /// Both lines carried an author's home directory until the repository was + /// about to be published. This is here so that neither can carry one again. + #[test] + fn the_shipped_autostart_names_nobodys_home_directory() { + let text = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../config/autostart")); + for (n, line) in text.lines().enumerate() { + assert!( + !line.contains("/home/"), + "config/autostart:{}: absolute home directory: {line}", + n + 1 + ); + } + } }