Add Fedora and Arch package recipes (#24)
* Package Ferese for Fedora and Arch releases * Build release packages alongside workspace checks * Include dependency licenses and software GPU test drivers * Limit package checks to release PRs and fix Arch Cargo dependency * Prepare Ferese 0.1.0-beta * Release config locks before closing inherited file descriptions * Install the lock PAM policy for native release checks * Include xauth in distribution package test dependencies * Regenerate Arch Git package metadata * Run package checks with an explicit UTC timezone * Remove unused bundled fonts and accept installed license receipts * Format the system font test helper * Load an installed font for headless text rendering checks * Isolate package test runtimes and reap container child processes
This commit is contained in:
1 parent
587602b493
commit
1b9fabbbe4
25 files changed
+1019
-170
No files matched your search
@@ -3,12 +3,12 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
tags: ["v*"]
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
workflow_call:
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
concurrency:
|
concurrency:
|
||||||
group: release-checks-${{ github.ref }}
|
group: release-checks-${{ github.workflow }}-${{ github.ref }}
|
||||||
cancel-in-progress: false
|
cancel-in-progress: false
|
||||||
jobs:
|
jobs:
|
||||||
workspace:
|
workspace:
|
||||||
@@ -69,7 +69,9 @@ jobs:
|
|||||||
cargo test --locked -p ferese backends::direct::capture::tests::capture_recomposes_cursor_without_touching_the_display_target -- --ignored
|
cargo test --locked -p ferese backends::direct::capture::tests::capture_recomposes_cursor_without_touching_the_display_target -- --ignored
|
||||||
cargo test --locked -p ferese winit::tests::capture_failure_restores_display_target_and_subsequent_frames_render -- --ignored
|
cargo test --locked -p ferese winit::tests::capture_failure_restores_display_target_and_subsequent_frames_render -- --ignored
|
||||||
- name: Test native shell, visibility and resize behavior
|
- name: Test native shell, visibility and resize behavior
|
||||||
run: python3 scripts/ci/run-native-checks.py --suite desktop --log-dir native-logs
|
run: |
|
||||||
|
install -m 0644 packaging/pam.d/ferese-lock.fedora /etc/pam.d/ferese-lock
|
||||||
|
python3 scripts/ci/run-native-checks.py --suite desktop --log-dir native-logs
|
||||||
- name: Upload native test logs
|
- name: Upload native test logs
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
@@ -79,37 +81,3 @@ jobs:
|
|||||||
if-no-files-found: ignore
|
if-no-files-found: ignore
|
||||||
- name: Test installer, session recovery and private portal contracts
|
- name: Test installer, session recovery and private portal contracts
|
||||||
run: python3 -m unittest discover -s scripts/tests -v
|
run: python3 -m unittest discover -s scripts/tests -v
|
||||||
|
|
||||||
publish:
|
|
||||||
name: Publish validated tag
|
|
||||||
needs: workspace
|
|
||||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
steps:
|
|
||||||
- uses: actions/github-script@v8
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const tag = context.ref.slice('refs/tags/'.length);
|
|
||||||
const repo = context.repo;
|
|
||||||
const commit = await github.rest.repos.getCommit({...repo, ref: tag});
|
|
||||||
if (commit.data.sha !== context.sha) {
|
|
||||||
throw new Error('Release tag moved after validation');
|
|
||||||
}
|
|
||||||
let release;
|
|
||||||
try {
|
|
||||||
release = (await github.rest.repos.getReleaseByTag({...repo, tag})).data;
|
|
||||||
} catch (error) {
|
|
||||||
if (error.status !== 404) throw error;
|
|
||||||
}
|
|
||||||
if (release?.draft) {
|
|
||||||
await github.rest.repos.updateRelease({...repo, release_id: release.id, draft: false});
|
|
||||||
} else if (!release) {
|
|
||||||
await github.rest.repos.createRelease({
|
|
||||||
...repo, tag_name: tag, name: tag,
|
|
||||||
generate_release_notes: true, prerelease: tag.includes('-')
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
core.notice(`Release ${tag} is already published`);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
name: Release packages
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, synchronize, reopened, labeled]
|
||||||
|
push:
|
||||||
|
tags: ['v*']
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
env:
|
||||||
|
CARGO_BUILD_JOBS: '2'
|
||||||
|
CARGO_INCREMENTAL: '0'
|
||||||
|
CARGO_PROFILE_RELEASE_DEBUG: '0'
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: release-${{ github.ref }}
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
checks:
|
||||||
|
if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'release')
|
||||||
|
uses: ./.github/workflows/release-checks.yml
|
||||||
|
|
||||||
|
source:
|
||||||
|
if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'release')
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
outputs:
|
||||||
|
version: ${{ steps.version.outputs.version }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
- name: Read release version
|
||||||
|
id: version
|
||||||
|
env:
|
||||||
|
RELEASE_TAG: ${{ github.ref_type == 'tag' && github.ref_name || '' }}
|
||||||
|
run: |
|
||||||
|
python3 - <<'PY'
|
||||||
|
import importlib.util, os
|
||||||
|
spec = importlib.util.spec_from_file_location('package', 'scripts/package.py')
|
||||||
|
package = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(package)
|
||||||
|
version = package.version()
|
||||||
|
package.check_tag(os.environ['RELEASE_TAG'], version)
|
||||||
|
with open(os.environ['GITHUB_OUTPUT'], 'a') as output:
|
||||||
|
print(f'version={version}', file=output)
|
||||||
|
PY
|
||||||
|
- name: Install pinned Rust toolchain
|
||||||
|
run: rustup show
|
||||||
|
- name: Package offline sources and recipes
|
||||||
|
env:
|
||||||
|
RELEASE_TAG: ${{ github.ref_type == 'tag' && github.ref_name || '' }}
|
||||||
|
run: |
|
||||||
|
python3 -m unittest discover -s scripts/tests -p test_package.py
|
||||||
|
python3 scripts/package.py source --tag "$RELEASE_TAG"
|
||||||
|
- uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: release-source
|
||||||
|
path: target/dist/*
|
||||||
|
if-no-files-found: error
|
||||||
|
compression-level: 0
|
||||||
|
|
||||||
|
fedora:
|
||||||
|
needs: source
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
container:
|
||||||
|
image: fedora:44
|
||||||
|
options: --init
|
||||||
|
steps:
|
||||||
|
- name: Install RPM tooling
|
||||||
|
run: dnf install -y rpm-build dnf5-plugins git findutils tar gzip
|
||||||
|
- uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
name: release-source
|
||||||
|
path: dist
|
||||||
|
- name: Build and test Fedora package
|
||||||
|
run: |
|
||||||
|
dnf builddep -y dist/ferese.spec
|
||||||
|
mkdir -p /tmp/rpmbuild/SOURCES
|
||||||
|
cp dist/ferese-*.tar.gz /tmp/rpmbuild/SOURCES/
|
||||||
|
rpmbuild -ba --define '_topdir /tmp/rpmbuild' dist/ferese.spec
|
||||||
|
mkdir -p artifacts
|
||||||
|
find /tmp/rpmbuild/RPMS /tmp/rpmbuild/SRPMS -name '*.rpm' -exec cp '{}' artifacts/ \;
|
||||||
|
- uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: release-fedora
|
||||||
|
path: artifacts/*.rpm
|
||||||
|
if-no-files-found: error
|
||||||
|
compression-level: 0
|
||||||
|
|
||||||
|
arch:
|
||||||
|
needs: source
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
container:
|
||||||
|
image: archlinux:base-devel
|
||||||
|
options: --init
|
||||||
|
steps:
|
||||||
|
- name: Prepare Arch build user and dependencies
|
||||||
|
run: |
|
||||||
|
pacman -Syu --noconfirm --needed rust cargo cmake clang pkgconf python git desktop-file-utils \
|
||||||
|
wayland libxkbcommon libinput systemd seatd mesa vulkan-swrast libdrm fontconfig freetype2 expat dbus \
|
||||||
|
pam polkit foot pipewire wireplumber libpipewire gstreamer xdg-desktop-portal xdg-desktop-portal-gtk xorg-xauth
|
||||||
|
useradd --create-home builder
|
||||||
|
- uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
name: release-source
|
||||||
|
path: dist
|
||||||
|
- name: Build and test Arch package
|
||||||
|
run: |
|
||||||
|
mkdir -p /home/builder/package
|
||||||
|
cp dist/PKGBUILD dist/ferese-*.tar.gz /home/builder/package/
|
||||||
|
chown -R builder:builder /home/builder/package
|
||||||
|
runuser -u builder -- bash -c 'cd /home/builder/package && makepkg --cleanbuild --noconfirm'
|
||||||
|
mkdir -p artifacts
|
||||||
|
cp /home/builder/package/*.pkg.tar.zst artifacts/
|
||||||
|
runuser -u builder -- bash -c 'cd /home/builder/package && makepkg --printsrcinfo' > artifacts/ferese.SRCINFO
|
||||||
|
- uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: release-arch
|
||||||
|
path: artifacts/*
|
||||||
|
if-no-files-found: error
|
||||||
|
compression-level: 0
|
||||||
|
|
||||||
|
publish:
|
||||||
|
if: github.event_name == 'push' && github.ref_type == 'tag'
|
||||||
|
needs: [source, checks, fedora, arch]
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
pattern: release-*
|
||||||
|
merge-multiple: true
|
||||||
|
path: artifacts
|
||||||
|
- name: Prepare draft release
|
||||||
|
uses: actions/github-script@v8
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const tag = context.ref.slice('refs/tags/'.length);
|
||||||
|
const repo = context.repo;
|
||||||
|
const commit = await github.rest.repos.getCommit({...repo, ref: tag});
|
||||||
|
if (commit.data.sha !== context.sha) {
|
||||||
|
throw new Error('Release tag moved after validation');
|
||||||
|
}
|
||||||
|
let release;
|
||||||
|
try {
|
||||||
|
release = (await github.rest.repos.getReleaseByTag({...repo, tag})).data;
|
||||||
|
} catch (error) {
|
||||||
|
if (error.status !== 404) throw error;
|
||||||
|
}
|
||||||
|
if (release && !release.draft) {
|
||||||
|
throw new Error(`Release ${tag} is already published`);
|
||||||
|
}
|
||||||
|
if (!release) {
|
||||||
|
await github.rest.repos.createRelease({
|
||||||
|
...repo, tag_name: tag, name: tag, draft: true,
|
||||||
|
generate_release_notes: true, prerelease: tag.includes('-')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
- name: Upload packages and publish completed release
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
GH_REPO: ${{ github.repository }}
|
||||||
|
RELEASE_TAG: ${{ github.ref_name }}
|
||||||
|
run: |
|
||||||
|
cd artifacts
|
||||||
|
rm SHA256SUMS
|
||||||
|
sha256sum * > SHA256SUMS
|
||||||
|
gh release upload "$RELEASE_TAG" ./* --clobber
|
||||||
|
test "$(gh api "repos/$GH_REPO/commits/$RELEASE_TAG" --jq .sha)" = "$GITHUB_SHA"
|
||||||
|
gh release edit "$RELEASE_TAG" --draft=false
|
||||||
Generated
+16
-16
@@ -1735,7 +1735,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ferese"
|
name = "ferese"
|
||||||
version = "0.1.0"
|
version = "0.1.0-beta"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ab_glyph",
|
"ab_glyph",
|
||||||
"async-channel",
|
"async-channel",
|
||||||
@@ -1766,7 +1766,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ferese-animation"
|
name = "ferese-animation"
|
||||||
version = "0.1.0"
|
version = "0.1.0-beta"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ferese-layout",
|
"ferese-layout",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -1774,7 +1774,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ferese-config"
|
name = "ferese-config"
|
||||||
version = "0.1.0"
|
version = "0.1.0-beta"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ferese-theme-model",
|
"ferese-theme-model",
|
||||||
"jiff",
|
"jiff",
|
||||||
@@ -1786,7 +1786,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ferese-core"
|
name = "ferese-core"
|
||||||
version = "0.1.0"
|
version = "0.1.0-beta"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ferese-layout",
|
"ferese-layout",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -1794,7 +1794,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ferese-ipc"
|
name = "ferese-ipc"
|
||||||
version = "0.1.0"
|
version = "0.1.0-beta"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ferese-theme-model",
|
"ferese-theme-model",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -1804,11 +1804,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ferese-layout"
|
name = "ferese-layout"
|
||||||
version = "0.1.0"
|
version = "0.1.0-beta"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ferese-lock"
|
name = "ferese-lock"
|
||||||
version = "0.1.0"
|
version = "0.1.0-beta"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ferese-config",
|
"ferese-config",
|
||||||
"ferese-ipc",
|
"ferese-ipc",
|
||||||
@@ -1828,7 +1828,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ferese-polkit-agent"
|
name = "ferese-polkit-agent"
|
||||||
version = "0.1.0"
|
version = "0.1.0-beta"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ferese-config",
|
"ferese-config",
|
||||||
"ferese-ipc",
|
"ferese-ipc",
|
||||||
@@ -1847,7 +1847,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ferese-protocols"
|
name = "ferese-protocols"
|
||||||
version = "0.1.0"
|
version = "0.1.0-beta"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.2",
|
"bitflags 2.13.2",
|
||||||
"wayland-backend",
|
"wayland-backend",
|
||||||
@@ -1859,7 +1859,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ferese-settings"
|
name = "ferese-settings"
|
||||||
version = "0.1.0"
|
version = "0.1.0-beta"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ferese-config",
|
"ferese-config",
|
||||||
"ferese-ipc",
|
"ferese-ipc",
|
||||||
@@ -1884,7 +1884,7 @@ source = "git+https://github.com/ferese-wm/ferese-shape?tag=v0.1.0#63d7b6dfd520d
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ferese-shell"
|
name = "ferese-shell"
|
||||||
version = "0.1.0"
|
version = "0.1.0-beta"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"ferese-animation",
|
"ferese-animation",
|
||||||
@@ -1912,7 +1912,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ferese-theme"
|
name = "ferese-theme"
|
||||||
version = "0.1.0"
|
version = "0.1.0-beta"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ferese-config",
|
"ferese-config",
|
||||||
"ferese-theme-model",
|
"ferese-theme-model",
|
||||||
@@ -1924,7 +1924,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ferese-theme-client"
|
name = "ferese-theme-client"
|
||||||
version = "0.1.0"
|
version = "0.1.0-beta"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ferese-config",
|
"ferese-config",
|
||||||
"ferese-ipc",
|
"ferese-ipc",
|
||||||
@@ -1938,14 +1938,14 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ferese-theme-model"
|
name = "ferese-theme-model"
|
||||||
version = "0.1.0"
|
version = "0.1.0-beta"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "feresectl"
|
name = "feresectl"
|
||||||
version = "0.1.0"
|
version = "0.1.0-beta"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"ferese-config",
|
"ferese-config",
|
||||||
@@ -7702,7 +7702,7 @@ checksum = "2fb433233f2df9344722454bc7e96465c9d03bff9d77c248f9e7523fe79585b5"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "xdg-desktop-portal-ferese"
|
name = "xdg-desktop-portal-ferese"
|
||||||
version = "0.1.0"
|
version = "0.1.0-beta"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"calloop 0.14.4",
|
"calloop 0.14.4",
|
||||||
"dirs",
|
"dirs",
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,7 @@ members = [
|
|||||||
resolver = "3"
|
resolver = "3"
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.1.0"
|
version = "0.1.0-beta"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
rust-version = "1.95"
|
rust-version = "1.95"
|
||||||
|
|||||||
@@ -58,6 +58,8 @@ also include desktop controls; their features depend on the shell and tools you
|
|||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
|
Fedora and Arch package builds are covered in [Packages and releases](docs/packaging.md).
|
||||||
|
|
||||||
Start with the [dependencies](docs/installation.md#requirements), then clone Ferese and
|
Start with the [dependencies](docs/installation.md#requirements), then clone Ferese and
|
||||||
build it as your normal user:
|
build it as your normal user:
|
||||||
|
|
||||||
|
|||||||
@@ -1,94 +0,0 @@
|
|||||||
Copyright 2011 The Comfortaa Project Authors
|
|
||||||
(https://github.com/alexeiva/comfortaa), with Reserved Font Name "Comfortaa".
|
|
||||||
|
|
||||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
|
||||||
This license is copied below, and is also available with a FAQ at:
|
|
||||||
http://scripts.sil.org/OFL
|
|
||||||
|
|
||||||
|
|
||||||
-----------------------------------------------------------
|
|
||||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
|
||||||
-----------------------------------------------------------
|
|
||||||
|
|
||||||
PREAMBLE
|
|
||||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
|
||||||
development of collaborative font projects, to support the font creation
|
|
||||||
efforts of academic and linguistic communities, and to provide a free and
|
|
||||||
open framework in which fonts may be shared and improved in partnership
|
|
||||||
with others.
|
|
||||||
|
|
||||||
The OFL allows the licensed fonts to be used, studied, modified and
|
|
||||||
redistributed freely as long as they are not sold by themselves. The
|
|
||||||
fonts, including any derivative works, can be bundled, embedded,
|
|
||||||
redistributed and/or sold with any software provided that any reserved
|
|
||||||
names are not used by derivative works. The fonts and derivatives,
|
|
||||||
however, cannot be released under any other type of license. The
|
|
||||||
requirement for fonts to remain under this license does not apply
|
|
||||||
to any document created using the fonts or their derivatives.
|
|
||||||
|
|
||||||
DEFINITIONS
|
|
||||||
"Font Software" refers to the set of files released by the Copyright
|
|
||||||
Holder(s) under this license and clearly marked as such. This may
|
|
||||||
include source files, build scripts and documentation.
|
|
||||||
|
|
||||||
"Reserved Font Name" refers to any names specified as such after the
|
|
||||||
copyright statement(s).
|
|
||||||
|
|
||||||
"Original Version" refers to the collection of Font Software components as
|
|
||||||
distributed by the Copyright Holder(s).
|
|
||||||
|
|
||||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
|
||||||
or substituting -- in part or in whole -- any of the components of the
|
|
||||||
Original Version, by changing formats or by porting the Font Software to a
|
|
||||||
new environment.
|
|
||||||
|
|
||||||
"Author" refers to any designer, engineer, programmer, technical
|
|
||||||
writer or other person who contributed to the Font Software.
|
|
||||||
|
|
||||||
PERMISSION & CONDITIONS
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
|
||||||
redistribute, and sell modified and unmodified copies of the Font
|
|
||||||
Software, subject to the following conditions:
|
|
||||||
|
|
||||||
1) Neither the Font Software nor any of its individual components,
|
|
||||||
in Original or Modified Versions, may be sold by itself.
|
|
||||||
|
|
||||||
2) Original or Modified Versions of the Font Software may be bundled,
|
|
||||||
redistributed and/or sold with any software, provided that each copy
|
|
||||||
contains the above copyright notice and this license. These can be
|
|
||||||
included either as stand-alone text files, human-readable headers or
|
|
||||||
in the appropriate machine-readable metadata fields within text or
|
|
||||||
binary files as long as those fields can be easily viewed by the user.
|
|
||||||
|
|
||||||
3) No Modified Version of the Font Software may use the Reserved Font
|
|
||||||
Name(s) unless explicit written permission is granted by the corresponding
|
|
||||||
Copyright Holder. This restriction only applies to the primary font name as
|
|
||||||
presented to the users.
|
|
||||||
|
|
||||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
|
||||||
Software shall not be used to promote, endorse or advertise any
|
|
||||||
Modified Version, except to acknowledge the contribution(s) of the
|
|
||||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
|
||||||
permission.
|
|
||||||
|
|
||||||
5) The Font Software, modified or unmodified, in part or in whole,
|
|
||||||
must be distributed entirely under this license, and must not be
|
|
||||||
distributed under any other license. The requirement for fonts to
|
|
||||||
remain under this license does not apply to any document created
|
|
||||||
using the Font Software.
|
|
||||||
|
|
||||||
TERMINATION
|
|
||||||
This license becomes null and void if any of the above conditions are
|
|
||||||
not met.
|
|
||||||
|
|
||||||
DISCLAIMER
|
|
||||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
|
||||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
|
||||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
||||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
|
||||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
|
||||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
|
||||||
Binary file not shown.
@@ -642,8 +642,9 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn removed_desktop_widgets_are_rejected_before_runtime_publication() {
|
fn removed_desktop_widgets_allow_runtime_publication() {
|
||||||
assert!(Config::parse_source("desktop-widgets { clock { enabled #true; }; }").is_err());
|
let config = Config::parse_source("desktop-widgets { clock { enabled #true; }; }").unwrap();
|
||||||
|
config.runtime_config().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse(source: &str) -> Config {
|
fn parse(source: &str) -> Config {
|
||||||
|
|||||||
@@ -1165,7 +1165,16 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn test_font() -> FontArc {
|
fn test_font() -> FontArc {
|
||||||
FontArc::try_from_slice(include_bytes!("../../assets/fonts/Comfortaa-Regular.otf")).unwrap()
|
let mut database = fontdb::Database::new();
|
||||||
|
database.load_system_fonts();
|
||||||
|
let font = database.faces().find_map(|face| {
|
||||||
|
database.with_face_data(face.id, |bytes, index| {
|
||||||
|
FontVec::try_from_vec_and_index(bytes.to_vec(), index)
|
||||||
|
.ok()
|
||||||
|
.map(FontArc::new)
|
||||||
|
})?
|
||||||
|
});
|
||||||
|
font.expect("overview rendering tests require a system font")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -44,13 +44,13 @@ impl ConfigFile {
|
|||||||
fs::TryLockError::Error(error) => error.to_string(),
|
fs::TryLockError::Error(error) => error.to_string(),
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let source = read(&path)?;
|
let mut file = Self {
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
path,
|
path,
|
||||||
source,
|
source: String::new(),
|
||||||
_lock: lock,
|
_lock: lock,
|
||||||
})
|
};
|
||||||
|
file.source = read(&file.path)?;
|
||||||
|
Ok(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn source(&self) -> &str {
|
pub fn source(&self) -> &str {
|
||||||
@@ -102,6 +102,14 @@ impl ConfigFile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Drop for ConfigFile {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// A spawned child can briefly retain the open-file description after our
|
||||||
|
// descriptor closes. Release the lock before closing the owner's handle.
|
||||||
|
let _ = self._lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn read(path: &Path) -> Result<String, String> {
|
fn read(path: &Path) -> Result<String, String> {
|
||||||
let file = match File::open(path) {
|
let file = match File::open(path) {
|
||||||
Ok(file) => file,
|
Ok(file) => file,
|
||||||
@@ -161,6 +169,8 @@ mod tests {
|
|||||||
std::os::unix::fs::symlink(&path, &alias).unwrap();
|
std::os::unix::fs::symlink(&path, &alias).unwrap();
|
||||||
let file = ConfigFile::open(&path).unwrap();
|
let file = ConfigFile::open(&path).unwrap();
|
||||||
assert!(ConfigFile::open(&alias).is_err());
|
assert!(ConfigFile::open(&alias).is_err());
|
||||||
|
// A concurrent process spawn can briefly inherit this open-file description.
|
||||||
|
let inherited = file._lock.try_clone().unwrap();
|
||||||
drop(file);
|
drop(file);
|
||||||
ConfigFile::open(&alias)
|
ConfigFile::open(&alias)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@@ -168,6 +178,7 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(alias.is_symlink());
|
assert!(alias.is_symlink());
|
||||||
assert_eq!(fs::read_to_string(path).unwrap(), "// updated");
|
assert_eq!(fs::read_to_string(path).unwrap(), "// updated");
|
||||||
|
drop(inherited);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
## Develop Ferese
|
## Develop Ferese
|
||||||
|
|
||||||
|
- [Packages and releases](packaging.md): build Fedora and Arch packages and publish a tagged release.
|
||||||
- [Development](development.md): build Ferese, run tests and check portal contracts.
|
- [Development](development.md): build Ferese, run tests and check portal contracts.
|
||||||
- [Nested resize checks](nested-resize-testing.md).
|
- [Nested resize checks](nested-resize-testing.md).
|
||||||
- [Client soak testing](native-soak-testing.md).
|
- [Client soak testing](native-soak-testing.md).
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
Ferese runs on Linux as its own Wayland session alongside your existing desktop.
|
Ferese runs on Linux as its own Wayland session alongside your existing desktop.
|
||||||
Launch it from a login manager, a command-based greeter or a local TTY.
|
Launch it from a login manager, a command-based greeter or a local TTY.
|
||||||
|
|
||||||
|
For Fedora and Arch packages, see [Packages and releases](packaging.md). The
|
||||||
|
steps below build and install from source.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
For a full session, you’ll need a Wayland-capable graphics driver and a local user
|
For a full session, you’ll need a Wayland-capable graphics driver and a local user
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
# Packages and releases
|
||||||
|
|
||||||
|
Ferese ships as one package. It includes the compositor, shell, Settings, lock
|
||||||
|
screen, authentication agent, recording helper and desktop portal.
|
||||||
|
|
||||||
|
The release workflow builds an x86_64 RPM on Fedora 44 and an x86_64 Arch package.
|
||||||
|
COPR and AUR publication are separate steps; neither repository is set up by this
|
||||||
|
workflow.
|
||||||
|
|
||||||
|
## Install a release package
|
||||||
|
|
||||||
|
Download the package for your distribution from
|
||||||
|
[GitHub Releases](https://github.com/ferese-wm/ferese/releases), then install it:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Fedora: select the ferese RPM, not the debuginfo or source RPM.
|
||||||
|
sudo dnf install ./ferese-<version>-1.fc44.x86_64.rpm
|
||||||
|
|
||||||
|
# Arch Linux
|
||||||
|
sudo pacman -U ./ferese-<version>-1-x86_64.pkg.tar.zst
|
||||||
|
```
|
||||||
|
|
||||||
|
Select Ferese in your login manager, or run `ferese-session` from a local TTY.
|
||||||
|
The package does not replace your login manager or modify your personal config.
|
||||||
|
To start with the supplied config:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
mkdir -p ~/.config/ferese
|
||||||
|
cp -n /usr/libexec/ferese/config.example.kdl ~/.config/ferese/config.kdl
|
||||||
|
```
|
||||||
|
|
||||||
|
If you installed with `install.sh`, uninstall that copy before installing a
|
||||||
|
package. Its `/usr/local/bin` launchers take precedence over `/usr/bin`, and its
|
||||||
|
portal service can override the packaged service. From the original checkout:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo ./scripts/install-session.sh uninstall
|
||||||
|
```
|
||||||
|
|
||||||
|
Keep your personal config. Log out before changing the installed session, then
|
||||||
|
log back in once the package is installed.
|
||||||
|
|
||||||
|
## Build packages locally
|
||||||
|
|
||||||
|
With Python 3.11 or newer and the pinned Rust toolchain installed, prepare the
|
||||||
|
source archive and versioned recipes from a checkout:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python3 scripts/package.py source
|
||||||
|
```
|
||||||
|
|
||||||
|
This downloads the locked Rust dependencies and includes an offline Cargo cache.
|
||||||
|
Ferese uses two packages with the same name and version from different sources,
|
||||||
|
which `cargo vendor` cannot represent. The cache preserves those source identities
|
||||||
|
and includes the Git submodules needed by libcosmic. It contains no Cargo
|
||||||
|
credentials or user-level Cargo configuration.
|
||||||
|
|
||||||
|
The resulting files are in `target/dist`: a source archive, Fedora spec, Arch
|
||||||
|
PKGBUILD and source checksum. Package builds run offline. Dependency license
|
||||||
|
notices and a JSON report of their sources and SPDX licenses ship with the package.
|
||||||
|
|
||||||
|
### Fedora
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo dnf install rpm-build dnf5-plugins
|
||||||
|
sudo dnf builddep target/dist/ferese.spec
|
||||||
|
mkdir -p ~/rpmbuild/SOURCES
|
||||||
|
cp target/dist/ferese-*.tar.gz ~/rpmbuild/SOURCES/
|
||||||
|
rpmbuild -ba target/dist/ferese.spec
|
||||||
|
```
|
||||||
|
|
||||||
|
The binary RPMs are in `~/rpmbuild/RPMS`; the source RPM is in `~/rpmbuild/SRPMS`.
|
||||||
|
Build with `--with resize_metrics` to enable resize-barrier measurements.
|
||||||
|
The source RPM can also be submitted to COPR once a project is configured.
|
||||||
|
|
||||||
|
### Arch Linux
|
||||||
|
|
||||||
|
Build as a regular user with the dependencies listed in the generated PKGBUILD:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd target/dist
|
||||||
|
makepkg -s
|
||||||
|
```
|
||||||
|
|
||||||
|
For a development build, copy `packaging/arch/ferese-git/PKGBUILD` into an empty
|
||||||
|
build directory and run `makepkg -s`. It tracks `main` and conflicts with the
|
||||||
|
release package. Submit that recipe to AUR separately if you want AUR distribution.
|
||||||
|
|
||||||
|
## Publish a release
|
||||||
|
|
||||||
|
1. Set `workspace.package.version` in `Cargo.toml` and update `Cargo.lock`.
|
||||||
|
2. Commit the release changes and push them.
|
||||||
|
3. Tag that commit with the same version, prefixed by `v`, then push the tag:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git tag v0.1.0-beta
|
||||||
|
git push origin v0.1.0-beta
|
||||||
|
```
|
||||||
|
|
||||||
|
A tag/version mismatch fails before packaging. Tags with a prerelease suffix,
|
||||||
|
such as `v0.2.0-rc.1`, produce a GitHub prerelease.
|
||||||
|
|
||||||
|
The workflow builds and tests both packages before publishing. Release assets
|
||||||
|
include the offline source archive, binary and source RPMs, Arch package,
|
||||||
|
PKGBUILD, `.SRCINFO`, Fedora spec and SHA-256 checksums. A failed upload leaves a
|
||||||
|
draft; rerunning the workflow can finish that draft. Published releases are not
|
||||||
|
overwritten.
|
||||||
|
|
||||||
|
Label a release preparation PR `release` to run the package builds before
|
||||||
|
merging. You can also use **Run workflow** to test a branch without publishing
|
||||||
|
or creating a tag. Fedora and Arch jobs run the workspace tests under a private
|
||||||
|
D-Bus session and validate the installed desktop files. They do not test real
|
||||||
|
DRM hardware; check login, locking, authentication, screen sharing and recording
|
||||||
|
on each distribution before announcing a release.
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
pkgname=ferese
|
||||||
|
pkgver=@VERSION@
|
||||||
|
pkgrel=1
|
||||||
|
pkgdesc='Scrolling Wayland desktop with its shell, Settings and desktop portal'
|
||||||
|
arch=('x86_64')
|
||||||
|
url='https://github.com/ferese-wm/ferese'
|
||||||
|
license=('MIT' 'Apache-2.0' 'BSD-2-Clause' 'BSD-3-Clause' 'BSL-1.0' 'CC0-1.0' 'CDLA-Permissive-2.0' 'ISC' 'MPL-2.0' 'Unicode-3.0' 'Unlicense' 'Zlib' 'OFL-1.1' 'Apache-2.0 WITH LLVM-exception')
|
||||||
|
depends=('wayland' 'libxkbcommon' 'libinput' 'systemd-libs' 'seatd' 'mesa' 'libdrm'
|
||||||
|
'fontconfig' 'freetype2' 'expat' 'dbus' 'python' 'bash' 'util-linux' 'systemd' 'pam' 'polkit' 'foot' 'pipewire'
|
||||||
|
'wireplumber' 'libpipewire' 'gstreamer' 'xdg-desktop-portal' 'xdg-desktop-portal-gtk')
|
||||||
|
makedepends=('cargo' 'cmake' 'clang' 'pkgconf' 'python' 'desktop-file-utils')
|
||||||
|
checkdepends=('dbus' 'vulkan-swrast' 'xorg-xauth')
|
||||||
|
optdepends=('brightnessctl: brightness controls' 'slurp: screenshot region selection'
|
||||||
|
'satty: screenshot editing' 'wl-clipboard: screenshot clipboard'
|
||||||
|
'gst-plugins-base: recording codecs' 'gst-plugins-good: recording codecs'
|
||||||
|
'gst-plugin-pipewire: PipeWire recording'
|
||||||
|
'xorg-xwayland: X11 applications' 'xwayland-satellite: X11 application integration (requires readiness support)')
|
||||||
|
# GCC LTO objects from the SPA C wrappers cannot be linked by Rust's lld.
|
||||||
|
options=('!lto')
|
||||||
|
backup=('etc/pam.d/ferese-lock')
|
||||||
|
conflicts=('ferese-git')
|
||||||
|
_source_version=@UPSTREAM_VERSION@
|
||||||
|
source=("$url/releases/download/v$_source_version/ferese-$_source_version.tar.gz")
|
||||||
|
sha256sums=('@SHA256@')
|
||||||
|
|
||||||
|
build() {
|
||||||
|
cd "$srcdir/ferese-$_source_version"
|
||||||
|
export CARGO_HOME="$PWD/cargo-home"
|
||||||
|
export CARGO_PROFILE_RELEASE_DEBUG=0
|
||||||
|
cargo build --release --frozen --offline --workspace
|
||||||
|
}
|
||||||
|
|
||||||
|
check() {
|
||||||
|
cd "$srcdir/ferese-$_source_version"
|
||||||
|
export CARGO_HOME="$PWD/cargo-home"
|
||||||
|
export CARGO_PROFILE_RELEASE_DEBUG=0
|
||||||
|
export TZ=UTC
|
||||||
|
export XDG_RUNTIME_DIR="$(mktemp -d)"
|
||||||
|
trap 'rm -r -- "$XDG_RUNTIME_DIR"' EXIT
|
||||||
|
python -m unittest discover -s scripts/tests -p test_package.py
|
||||||
|
dbus-run-session -- cargo test --release --frozen --offline --workspace
|
||||||
|
}
|
||||||
|
|
||||||
|
package() {
|
||||||
|
cd "$srcdir/ferese-$_source_version"
|
||||||
|
python scripts/package.py stage --destdir "$pkgdir" --pam arch
|
||||||
|
desktop-file-validate "$pkgdir/usr/share/wayland-sessions/ferese.desktop"
|
||||||
|
desktop-file-validate "$pkgdir/usr/share/applications/dev.ferese.Settings.desktop"
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
pkgbase = ferese-git
|
||||||
|
pkgdesc = Scrolling Wayland desktop with its shell, Settings and desktop portal (Git)
|
||||||
|
pkgver = 0.1.0beta.r0.g0000000
|
||||||
|
pkgrel = 1
|
||||||
|
url = https://github.com/ferese-wm/ferese
|
||||||
|
arch = x86_64
|
||||||
|
license = MIT
|
||||||
|
license = Apache-2.0
|
||||||
|
license = BSD-2-Clause
|
||||||
|
license = BSD-3-Clause
|
||||||
|
license = BSL-1.0
|
||||||
|
license = CC0-1.0
|
||||||
|
license = CDLA-Permissive-2.0
|
||||||
|
license = ISC
|
||||||
|
license = MPL-2.0
|
||||||
|
license = Unicode-3.0
|
||||||
|
license = Unlicense
|
||||||
|
license = Zlib
|
||||||
|
license = OFL-1.1
|
||||||
|
license = Apache-2.0 WITH LLVM-exception
|
||||||
|
checkdepends = dbus
|
||||||
|
checkdepends = vulkan-swrast
|
||||||
|
checkdepends = xorg-xauth
|
||||||
|
makedepends = cargo
|
||||||
|
makedepends = cmake
|
||||||
|
makedepends = clang
|
||||||
|
makedepends = pkgconf
|
||||||
|
makedepends = python
|
||||||
|
makedepends = git
|
||||||
|
makedepends = desktop-file-utils
|
||||||
|
depends = wayland
|
||||||
|
depends = libxkbcommon
|
||||||
|
depends = libinput
|
||||||
|
depends = systemd-libs
|
||||||
|
depends = seatd
|
||||||
|
depends = mesa
|
||||||
|
depends = libdrm
|
||||||
|
depends = fontconfig
|
||||||
|
depends = freetype2
|
||||||
|
depends = expat
|
||||||
|
depends = dbus
|
||||||
|
depends = python
|
||||||
|
depends = bash
|
||||||
|
depends = util-linux
|
||||||
|
depends = systemd
|
||||||
|
depends = pam
|
||||||
|
depends = polkit
|
||||||
|
depends = foot
|
||||||
|
depends = pipewire
|
||||||
|
depends = wireplumber
|
||||||
|
depends = libpipewire
|
||||||
|
depends = gstreamer
|
||||||
|
depends = xdg-desktop-portal
|
||||||
|
depends = xdg-desktop-portal-gtk
|
||||||
|
optdepends = brightnessctl: brightness controls
|
||||||
|
optdepends = slurp: screenshot region selection
|
||||||
|
optdepends = satty: screenshot editing
|
||||||
|
optdepends = wl-clipboard: screenshot clipboard
|
||||||
|
optdepends = gst-plugins-base: recording codecs
|
||||||
|
optdepends = gst-plugins-good: recording codecs
|
||||||
|
optdepends = gst-plugin-pipewire: PipeWire recording
|
||||||
|
optdepends = xorg-xwayland: X11 applications
|
||||||
|
optdepends = xwayland-satellite: X11 application integration (requires readiness support)
|
||||||
|
provides = ferese
|
||||||
|
conflicts = ferese
|
||||||
|
options = !lto
|
||||||
|
backup = etc/pam.d/ferese-lock
|
||||||
|
source = ferese::git+https://github.com/ferese-wm/ferese.git#branch=main
|
||||||
|
sha256sums = SKIP
|
||||||
|
|
||||||
|
pkgname = ferese-git
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
pkgname=ferese-git
|
||||||
|
pkgver=0.1.0beta.r0.g0000000
|
||||||
|
pkgrel=1
|
||||||
|
pkgdesc='Scrolling Wayland desktop with its shell, Settings and desktop portal (Git)'
|
||||||
|
arch=('x86_64')
|
||||||
|
url='https://github.com/ferese-wm/ferese'
|
||||||
|
license=('MIT' 'Apache-2.0' 'BSD-2-Clause' 'BSD-3-Clause' 'BSL-1.0' 'CC0-1.0' 'CDLA-Permissive-2.0' 'ISC' 'MPL-2.0' 'Unicode-3.0' 'Unlicense' 'Zlib' 'OFL-1.1' 'Apache-2.0 WITH LLVM-exception')
|
||||||
|
depends=('wayland' 'libxkbcommon' 'libinput' 'systemd-libs' 'seatd' 'mesa' 'libdrm'
|
||||||
|
'fontconfig' 'freetype2' 'expat' 'dbus' 'python' 'bash' 'util-linux' 'systemd' 'pam' 'polkit' 'foot' 'pipewire'
|
||||||
|
'wireplumber' 'libpipewire' 'gstreamer' 'xdg-desktop-portal' 'xdg-desktop-portal-gtk')
|
||||||
|
makedepends=('cargo' 'cmake' 'clang' 'pkgconf' 'python' 'git' 'desktop-file-utils')
|
||||||
|
checkdepends=('dbus' 'vulkan-swrast' 'xorg-xauth')
|
||||||
|
optdepends=('brightnessctl: brightness controls' 'slurp: screenshot region selection'
|
||||||
|
'satty: screenshot editing' 'wl-clipboard: screenshot clipboard'
|
||||||
|
'gst-plugins-base: recording codecs' 'gst-plugins-good: recording codecs'
|
||||||
|
'gst-plugin-pipewire: PipeWire recording'
|
||||||
|
'xorg-xwayland: X11 applications' 'xwayland-satellite: X11 application integration (requires readiness support)')
|
||||||
|
provides=('ferese')
|
||||||
|
conflicts=('ferese')
|
||||||
|
# GCC LTO objects from the SPA C wrappers cannot be linked by Rust's lld.
|
||||||
|
options=('!lto')
|
||||||
|
backup=('etc/pam.d/ferese-lock')
|
||||||
|
source=('ferese::git+https://github.com/ferese-wm/ferese.git#branch=main')
|
||||||
|
sha256sums=('SKIP')
|
||||||
|
|
||||||
|
pkgver() {
|
||||||
|
cd "$srcdir/ferese"
|
||||||
|
local version
|
||||||
|
version=$(python -c 'import tomllib; print(tomllib.load(open("Cargo.toml", "rb"))["workspace"]["package"]["version"])')
|
||||||
|
printf '%s.r%s.g%s' "${version//-/}" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)"
|
||||||
|
}
|
||||||
|
|
||||||
|
prepare() {
|
||||||
|
cd "$srcdir/ferese"
|
||||||
|
export CARGO_HOME="$srcdir/cargo-home"
|
||||||
|
cargo fetch --locked
|
||||||
|
}
|
||||||
|
|
||||||
|
build() {
|
||||||
|
cd "$srcdir/ferese"
|
||||||
|
export CARGO_HOME="$srcdir/cargo-home"
|
||||||
|
export CARGO_PROFILE_RELEASE_DEBUG=0
|
||||||
|
cargo build --release --frozen --offline --workspace
|
||||||
|
}
|
||||||
|
|
||||||
|
check() {
|
||||||
|
cd "$srcdir/ferese"
|
||||||
|
export CARGO_HOME="$srcdir/cargo-home"
|
||||||
|
export CARGO_PROFILE_RELEASE_DEBUG=0
|
||||||
|
export TZ=UTC
|
||||||
|
export XDG_RUNTIME_DIR="$(mktemp -d)"
|
||||||
|
trap 'rm -r -- "$XDG_RUNTIME_DIR"' EXIT
|
||||||
|
python -m unittest discover -s scripts/tests -p test_package.py
|
||||||
|
dbus-run-session -- cargo test --release --frozen --offline --workspace
|
||||||
|
}
|
||||||
|
|
||||||
|
package() {
|
||||||
|
cd "$srcdir/ferese"
|
||||||
|
export CARGO_HOME="$srcdir/cargo-home"
|
||||||
|
python scripts/package.py stage --destdir "$pkgdir" --pam arch
|
||||||
|
desktop-file-validate "$pkgdir/usr/share/wayland-sessions/ferese.desktop"
|
||||||
|
desktop-file-validate "$pkgdir/usr/share/applications/dev.ferese.Settings.desktop"
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
# dwz cannot compress the DWARF 5 sections emitted by Rust. Keep debug files
|
||||||
|
# without the optional DWARF compression pass.
|
||||||
|
%global _find_debuginfo_dwz_opts %{nil}
|
||||||
|
%global upstream_version @UPSTREAM_VERSION@
|
||||||
|
%bcond resize_metrics 0
|
||||||
|
|
||||||
|
Name: ferese
|
||||||
|
Version: @VERSION@
|
||||||
|
Release: 1%{?dist}
|
||||||
|
Summary: Scrolling Wayland desktop
|
||||||
|
# Includes vendored Rust libraries and theme palettes. Full notices ship
|
||||||
|
# under /usr/share/licenses/ferese, including per-dependency SPDX metadata.
|
||||||
|
License: MIT AND Apache-2.0 AND BSD-2-Clause AND BSD-3-Clause AND BSL-1.0 AND CC0-1.0 AND CDLA-Permissive-2.0 AND ISC AND MPL-2.0 AND Unicode-3.0 AND Unlicense AND Zlib AND OFL-1.1 AND (Apache-2.0 WITH LLVM-exception)
|
||||||
|
URL: https://github.com/ferese-wm/ferese
|
||||||
|
Source0: %{url}/releases/download/v%{upstream_version}/ferese-%{upstream_version}.tar.gz
|
||||||
|
|
||||||
|
BuildRequires: cargo >= 1.95
|
||||||
|
BuildRequires: rust >= 1.95
|
||||||
|
BuildRequires: gcc-c++ cmake make clang clang-devel python3 pkgconf-pkg-config
|
||||||
|
BuildRequires: wayland-devel libxkbcommon-devel libinput-devel systemd-devel libseat-devel
|
||||||
|
BuildRequires: mesa-libgbm-devel mesa-libEGL-devel libdrm-devel fontconfig-devel freetype-devel expat-devel
|
||||||
|
BuildRequires: mesa-dri-drivers mesa-vulkan-drivers
|
||||||
|
BuildRequires: pipewire-devel gstreamer1-devel dbus-daemon desktop-file-utils polkit-libs pam
|
||||||
|
BuildRequires: xorg-x11-xauth
|
||||||
|
Requires: pam polkit polkit-libs foot pipewire wireplumber python3 bash util-linux dbus-daemon dbus-tools systemd
|
||||||
|
Requires: xdg-desktop-portal xdg-desktop-portal-gtk
|
||||||
|
# Loaded at runtime, so ELF dependency detection does not find these libraries.
|
||||||
|
Requires: libEGL.so.1()(64bit) fontconfig freetype
|
||||||
|
Recommends: brightnessctl slurp wl-clipboard satty
|
||||||
|
Recommends: gstreamer1-plugins-base gstreamer1-plugins-good pipewire-gstreamer
|
||||||
|
|
||||||
|
%description
|
||||||
|
Ferese includes a scrolling Wayland compositor, desktop shell, Settings,
|
||||||
|
lock screen, authentication agent and desktop portal in one package.
|
||||||
|
|
||||||
|
%prep
|
||||||
|
%autosetup -n ferese-%{upstream_version}
|
||||||
|
|
||||||
|
%build
|
||||||
|
export CARGO_HOME="$PWD/cargo-home"
|
||||||
|
export CARGO_PROFILE_RELEASE_DEBUG=0
|
||||||
|
%if %{with resize_metrics}
|
||||||
|
cargo build --release --frozen --offline --workspace --features ferese/resize-metrics
|
||||||
|
%else
|
||||||
|
cargo build --release --frozen --offline --workspace
|
||||||
|
%endif
|
||||||
|
|
||||||
|
%install
|
||||||
|
python3 scripts/package.py stage --destdir "%{buildroot}" --pam fedora
|
||||||
|
desktop-file-validate "%{buildroot}%{_datadir}/wayland-sessions/ferese.desktop"
|
||||||
|
desktop-file-validate "%{buildroot}%{_datadir}/applications/dev.ferese.Settings.desktop"
|
||||||
|
|
||||||
|
%check
|
||||||
|
export CARGO_HOME="$PWD/cargo-home"
|
||||||
|
export CARGO_PROFILE_RELEASE_DEBUG=0
|
||||||
|
export TZ=UTC
|
||||||
|
export XDG_RUNTIME_DIR="$(mktemp -d)"
|
||||||
|
trap 'rm -r -- "$XDG_RUNTIME_DIR"' EXIT
|
||||||
|
python3 -m unittest discover -s scripts/tests -p test_package.py
|
||||||
|
dbus-run-session -- cargo test --release --frozen --offline --workspace
|
||||||
|
|
||||||
|
%files
|
||||||
|
%{_bindir}/ferese*
|
||||||
|
%{_bindir}/xdg-desktop-portal-ferese
|
||||||
|
%{_libexecdir}/ferese/
|
||||||
|
%{_datadir}/ferese/
|
||||||
|
%{_datadir}/wayland-sessions/ferese.desktop
|
||||||
|
%{_datadir}/applications/dev.ferese.Settings.desktop
|
||||||
|
%{_datadir}/icons/hicolor/scalable/apps/ferese.svg
|
||||||
|
%{_datadir}/icons/hicolor/scalable/apps/dev.ferese.Settings.svg
|
||||||
|
%{_datadir}/xdg-desktop-portal/portals/ferese.portal
|
||||||
|
%{_datadir}/xdg-desktop-portal/ferese-portals.conf
|
||||||
|
%{_datadir}/dbus-1/services/org.freedesktop.impl.portal.desktop.ferese.service
|
||||||
|
/usr/lib/systemd/user/ferese-session.target
|
||||||
|
/usr/lib/systemd/user/xdg-desktop-portal-ferese.service
|
||||||
|
%config(noreplace) %{_sysconfdir}/pam.d/ferese-lock
|
||||||
|
%license %{_datadir}/licenses/ferese/
|
||||||
|
%doc %{_datadir}/doc/ferese/
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
#%PAM-1.0
|
||||||
|
# Authenticate the session owner without opening another PAM session.
|
||||||
|
auth include system-auth
|
||||||
|
account include system-auth
|
||||||
@@ -16,6 +16,7 @@ PAGES = [
|
|||||||
('screenshots', 'Screenshots', ROOT / 'docs/screenshots.md'),
|
('screenshots', 'Screenshots', ROOT / 'docs/screenshots.md'),
|
||||||
('why-ferese', 'Why Ferese', ROOT / 'docs/why-ferese.md'),
|
('why-ferese', 'Why Ferese', ROOT / 'docs/why-ferese.md'),
|
||||||
('installation', 'Installation', ROOT / 'docs/installation.md'),
|
('installation', 'Installation', ROOT / 'docs/installation.md'),
|
||||||
|
('packaging', 'Packages and releases', ROOT / 'docs/packaging.md'),
|
||||||
('configuration', 'Configuration', ROOT / 'docs/configuration.md'),
|
('configuration', 'Configuration', ROOT / 'docs/configuration.md'),
|
||||||
('animation-model', 'Animation model', ROOT / 'docs/animation-model.md'),
|
('animation-model', 'Animation model', ROOT / 'docs/animation-model.md'),
|
||||||
('shortcuts', 'Shortcuts and gestures', ROOT / 'docs/shortcuts.md'),
|
('shortcuts', 'Shortcuts and gestures', ROOT / 'docs/shortcuts.md'),
|
||||||
|
|||||||
@@ -147,6 +147,8 @@ def validate_bundle(bundle, *, installed=False):
|
|||||||
# verifiable for upgrades and rollback without adding it to new bundles.
|
# verifiable for upgrades and rollback without adding it to new bundles.
|
||||||
if allowed is None and installed and re.fullmatch(r'wallpapers/[A-Za-z0-9._-]+\.jpe?g', target):
|
if allowed is None and installed and re.fullmatch(r'wallpapers/[A-Za-z0-9._-]+\.jpe?g', target):
|
||||||
allowed = {'target': target, 'mode': '0644'}
|
allowed = {'target': target, 'mode': '0644'}
|
||||||
|
if allowed is None and installed and target in {'licenses/Cantarell-LICENSE.txt', 'licenses/Comfortaa-LICENSE.txt'}:
|
||||||
|
allowed = {'target': target, 'mode': '0644'}
|
||||||
if allowed is None and target == 'wallpapers/ferese.svg':
|
if allowed is None and target == 'wallpapers/ferese.svg':
|
||||||
allowed = {'target': target, 'mode': '0644'}
|
allowed = {'target': target, 'mode': '0644'}
|
||||||
if allowed is None and re.fullmatch(r'licenses/theme-[A-Za-z0-9._-]+\.txt', target):
|
if allowed is None and re.fullmatch(r'licenses/theme-[A-Za-z0-9._-]+\.txt', target):
|
||||||
|
|||||||
@@ -85,11 +85,6 @@
|
|||||||
"target": "config.example.kdl",
|
"target": "config.example.kdl",
|
||||||
"mode": "0644"
|
"mode": "0644"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"source": "assets/fonts/Comfortaa-LICENSE.txt",
|
|
||||||
"target": "licenses/Comfortaa-LICENSE.txt",
|
|
||||||
"mode": "0644"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"source": "assets/themes/licenses/*.txt",
|
"source": "assets/themes/licenses/*.txt",
|
||||||
"target": "licenses/theme-{name}",
|
"target": "licenses/theme-{name}",
|
||||||
|
|||||||
Executable
+282
@@ -0,0 +1,282 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Prepare offline release sources and stage the complete distro package."""
|
||||||
|
import argparse
|
||||||
|
import gzip
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import tarfile
|
||||||
|
import tempfile
|
||||||
|
import tomllib
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
|
||||||
|
|
||||||
|
def run(*args, cwd=ROOT, env=None):
|
||||||
|
return subprocess.check_output(args, cwd=cwd, env=env, text=True)
|
||||||
|
|
||||||
|
|
||||||
|
def version():
|
||||||
|
return tomllib.loads((ROOT / 'Cargo.toml').read_text())['workspace']['package']['version']
|
||||||
|
|
||||||
|
|
||||||
|
def check_tag(tag, release_version):
|
||||||
|
if not re.fullmatch(r'\d+\.\d+\.\d+(?:-[0-9A-Za-z.]+)?', release_version):
|
||||||
|
raise ValueError(f'Unsupported release version: {release_version}')
|
||||||
|
|
||||||
|
if tag and tag != f'v{release_version}':
|
||||||
|
raise ValueError(f'Tag {tag} does not match Cargo version {release_version}')
|
||||||
|
|
||||||
|
|
||||||
|
def licenses(destination, metadata):
|
||||||
|
destination.mkdir(parents=True, exist_ok=True)
|
||||||
|
report = []
|
||||||
|
for package in sorted(metadata['packages'], key=lambda p: (p['name'], p['version'])):
|
||||||
|
if package['source'] is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
directory = Path(package['manifest_path']).parent
|
||||||
|
source_hash = hashlib.sha256(package['source'].encode()).hexdigest()[:8]
|
||||||
|
name = f"{package['name']}-{package['version']}-{source_hash}"
|
||||||
|
files = set()
|
||||||
|
search = [directory]
|
||||||
|
if package['source'].startswith('git+'):
|
||||||
|
for ancestor in directory.parents:
|
||||||
|
search.append(ancestor)
|
||||||
|
if (ancestor / '.git').exists():
|
||||||
|
break
|
||||||
|
|
||||||
|
for parent in search:
|
||||||
|
for pattern in ('LICENSE*', 'LICENCE*', 'COPYING*', 'NOTICE*', 'UNLICENSE*'):
|
||||||
|
files.update(parent.glob(pattern))
|
||||||
|
|
||||||
|
if package.get('license_file'):
|
||||||
|
files.add(directory / package['license_file'])
|
||||||
|
|
||||||
|
for path in sorted(files):
|
||||||
|
if path.is_file():
|
||||||
|
repeated = sum(other.name == path.name for other in files) > 1
|
||||||
|
filename = f'{path.parent.name}-{path.name}' if repeated else path.name
|
||||||
|
target = destination / name / filename
|
||||||
|
target.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
shutil.copyfile(path, target)
|
||||||
|
|
||||||
|
report.append({'name': package['name'], 'version': package['version'],
|
||||||
|
'source': package['source'], 'license': package['license'],
|
||||||
|
'notice_directory': name})
|
||||||
|
|
||||||
|
(destination / 'dependencies.json').write_text(json.dumps(report, indent=2) + '\n')
|
||||||
|
|
||||||
|
|
||||||
|
def stage(destdir, binary_dir, pam):
|
||||||
|
destdir = Path(destdir).absolute()
|
||||||
|
if destdir == Path('/') or destdir.is_symlink():
|
||||||
|
raise ValueError('DESTDIR must be a private package staging directory')
|
||||||
|
|
||||||
|
if destdir.exists() and any(destdir.iterdir()):
|
||||||
|
raise ValueError('DESTDIR must be empty')
|
||||||
|
|
||||||
|
inventory = json.loads((ROOT / 'scripts/installer/inventory.json').read_text())
|
||||||
|
planned = []
|
||||||
|
links = []
|
||||||
|
for entry in inventory['files']:
|
||||||
|
if entry['target'].startswith('pam/'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if entry['source'].startswith('target/release/'):
|
||||||
|
sources = [Path(binary_dir).absolute() / Path(entry['source']).name]
|
||||||
|
else:
|
||||||
|
sources = sorted(ROOT.glob(entry['source']))
|
||||||
|
if not sources:
|
||||||
|
raise ValueError(f"Missing package asset: {entry['source']}")
|
||||||
|
|
||||||
|
for source in sources:
|
||||||
|
|
||||||
|
target = entry['target'].replace('{name}', source.name)
|
||||||
|
if 'destination' in entry:
|
||||||
|
target = entry['destination'].replace('/usr/local/', '/usr/').lstrip('/')
|
||||||
|
elif target.startswith(('wallpapers/', 'licenses/')):
|
||||||
|
prefix = 'usr/share/ferese' if target.startswith('wallpapers/') else 'usr/share'
|
||||||
|
if target.startswith('licenses/'):
|
||||||
|
target = 'licenses/ferese/' + target.removeprefix('licenses/')
|
||||||
|
target = f'{prefix}/{target}'
|
||||||
|
else:
|
||||||
|
target = f'usr/libexec/ferese/{target}'
|
||||||
|
|
||||||
|
planned.append((source, target, int(entry['mode'], 8)))
|
||||||
|
if entry.get('link'):
|
||||||
|
links.append((f'usr/bin/{source.name}', f'/usr/libexec/ferese/{source.name}'))
|
||||||
|
|
||||||
|
planned.extend([(ROOT / f'packaging/pam.d/ferese-lock.{pam}', 'etc/pam.d/ferese-lock', 0o644),
|
||||||
|
(ROOT / 'LICENSE', 'usr/share/licenses/ferese/LICENSE', 0o644),
|
||||||
|
(ROOT / 'README.md', 'usr/share/doc/ferese/README.md', 0o644)])
|
||||||
|
# Validate the complete inventory before writing any part of the package.
|
||||||
|
for source, _, _ in planned:
|
||||||
|
if not source.is_file():
|
||||||
|
raise ValueError(f'Missing package file: {source}')
|
||||||
|
for source, target, mode in planned:
|
||||||
|
destination = destdir / target
|
||||||
|
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
shutil.copyfile(source, destination)
|
||||||
|
destination.chmod(mode)
|
||||||
|
if source.suffix in ('.desktop', '.service', '.target', '.portal', '.conf'):
|
||||||
|
text = destination.read_text().replace('/usr/local/lib/ferese/current/', '/usr/libexec/ferese/')
|
||||||
|
destination.write_text(text.replace('/usr/local/', '/usr/'))
|
||||||
|
|
||||||
|
bundled = ROOT / 'bundled-licenses'
|
||||||
|
if bundled.exists():
|
||||||
|
shutil.copytree(bundled, destdir / 'usr/share/licenses/ferese/dependencies', dirs_exist_ok=True)
|
||||||
|
else:
|
||||||
|
metadata = json.loads(run('cargo', 'metadata', '--locked', '--offline', '--format-version', '1'))
|
||||||
|
licenses(destdir / 'usr/share/licenses/ferese/dependencies', metadata)
|
||||||
|
|
||||||
|
for path in (destdir / 'usr/share/licenses/ferese').rglob('*'):
|
||||||
|
if path.is_file():
|
||||||
|
path.chmod(0o644)
|
||||||
|
|
||||||
|
for path in destdir.rglob('*'):
|
||||||
|
if path.is_dir() and not path.is_symlink():
|
||||||
|
path.chmod(0o755)
|
||||||
|
|
||||||
|
links.append(('usr/libexec/ferese/wallpapers', '/usr/share/ferese/wallpapers'))
|
||||||
|
for target, source in links:
|
||||||
|
destination = destdir / target
|
||||||
|
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
destination.symlink_to(source)
|
||||||
|
|
||||||
|
|
||||||
|
def source(output, tag, offline):
|
||||||
|
release_version = version()
|
||||||
|
check_tag(tag, release_version)
|
||||||
|
output = Path(output).absolute()
|
||||||
|
output.mkdir(parents=True, exist_ok=True)
|
||||||
|
epoch = int(os.environ.get('SOURCE_DATE_EPOCH', run('git', 'show', '-s', '--format=%ct', 'HEAD').strip()))
|
||||||
|
with tempfile.TemporaryDirectory(prefix='ferese-source-') as temporary:
|
||||||
|
tree = Path(temporary) / f'ferese-{release_version}'
|
||||||
|
tree.mkdir()
|
||||||
|
# Include new packaging files in local dry runs; release checkouts are clean.
|
||||||
|
files = run('git', 'ls-files', '-z', '--cached', '--others', '--exclude-standard').split('\0')
|
||||||
|
for filename in sorted(set(files) - {''}):
|
||||||
|
path = ROOT / filename
|
||||||
|
if not path.is_file():
|
||||||
|
continue
|
||||||
|
|
||||||
|
destination = tree / filename
|
||||||
|
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
shutil.copy2(path, destination)
|
||||||
|
|
||||||
|
fetch = ['cargo', 'fetch', '--locked']
|
||||||
|
if offline:
|
||||||
|
fetch.append('--offline')
|
||||||
|
|
||||||
|
run(*fetch, cwd=tree)
|
||||||
|
metadata = json.loads(run('cargo', 'metadata', '--locked', '--offline', '--format-version', '1', cwd=tree))
|
||||||
|
licenses(tree / 'bundled-licenses', metadata)
|
||||||
|
# cargo vendor cannot represent dpi 0.1.2 from both crates.io and Git.
|
||||||
|
# Preserve source identities with an isolated, credential-free Cargo cache.
|
||||||
|
cache = tree / 'cargo-home'
|
||||||
|
for package in metadata['packages']:
|
||||||
|
package_source = package['source']
|
||||||
|
if package_source is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
manifest = Path(package['manifest_path'])
|
||||||
|
if package_source.startswith('registry+'):
|
||||||
|
registry = manifest.parent.parent.name
|
||||||
|
registry_home = manifest.parent.parent.parent.parent
|
||||||
|
name = package['name']
|
||||||
|
crate = f"{name}-{package['version']}.crate"
|
||||||
|
for relative in (f'cache/{registry}/{crate}', f'index/{registry}/config.json'):
|
||||||
|
original = registry_home / relative
|
||||||
|
destination = cache / 'registry' / relative
|
||||||
|
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
shutil.copyfile(original, destination)
|
||||||
|
|
||||||
|
if len(name) == 1:
|
||||||
|
index_path = f'1/{name}'
|
||||||
|
elif len(name) == 2:
|
||||||
|
index_path = f'2/{name}'
|
||||||
|
elif len(name) == 3:
|
||||||
|
index_path = f'3/{name[0]}/{name}'
|
||||||
|
else:
|
||||||
|
index_path = f'{name[:2]}/{name[2:4]}/{name}'
|
||||||
|
|
||||||
|
relative = f'index/{registry}/.cache/{index_path}'
|
||||||
|
destination = cache / 'registry' / relative
|
||||||
|
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
shutil.copyfile(registry_home / relative, destination)
|
||||||
|
elif package_source.startswith('git+'):
|
||||||
|
checkout = next(parent for parent in manifest.parents if parent.parent.name == 'checkouts')
|
||||||
|
original = checkout.parent.parent / 'db' / checkout.name
|
||||||
|
destination = cache / 'git/db' / checkout.name
|
||||||
|
if not destination.exists():
|
||||||
|
shutil.copytree(original, destination)
|
||||||
|
|
||||||
|
# Cargo cannot fetch Git submodules in frozen mode. Carry the
|
||||||
|
# completed checkout, including .cargo-ok and submodule objects.
|
||||||
|
revision = next(parent for parent in manifest.parents if parent.parent == checkout)
|
||||||
|
if (revision / '.gitmodules').exists():
|
||||||
|
ready = cache / 'git/checkouts' / checkout.name / revision.name
|
||||||
|
if not ready.exists():
|
||||||
|
shutil.copytree(revision, ready)
|
||||||
|
else:
|
||||||
|
raise ValueError(f'Unsupported dependency source: {package_source}')
|
||||||
|
|
||||||
|
environment = dict(os.environ, CARGO_HOME=str(cache))
|
||||||
|
run('cargo', 'metadata', '--frozen', '--offline', '--format-version', '1', cwd=tree, env=environment)
|
||||||
|
# Cargo recreates these from the checked source archives and Git objects.
|
||||||
|
for relative in ('registry/src',):
|
||||||
|
shutil.rmtree(cache / relative, ignore_errors=True)
|
||||||
|
|
||||||
|
archive = output / f'ferese-{release_version}.tar.gz'
|
||||||
|
with archive.open('wb') as raw, gzip.GzipFile(filename='', fileobj=raw, mode='wb', mtime=epoch) as zipped:
|
||||||
|
with tarfile.open(fileobj=zipped, mode='w') as tar:
|
||||||
|
for path in [tree, *sorted(tree.rglob('*'))]:
|
||||||
|
info = tar.gettarinfo(str(path), arcname=str(path.relative_to(tree.parent)))
|
||||||
|
info.uid = info.gid = 0
|
||||||
|
info.uname = info.gname = ''
|
||||||
|
info.mtime = epoch
|
||||||
|
# The invoking user's umask must not change release permissions.
|
||||||
|
info.mode = 0o755 if info.isdir() or info.mode & 0o111 else 0o644
|
||||||
|
if info.isfile():
|
||||||
|
with path.open('rb') as contents:
|
||||||
|
tar.addfile(info, contents)
|
||||||
|
else:
|
||||||
|
tar.addfile(info)
|
||||||
|
|
||||||
|
digest = hashlib.sha256(archive.read_bytes()).hexdigest()
|
||||||
|
recipe = (ROOT / 'packaging/arch/PKGBUILD.in').read_text()
|
||||||
|
recipe = recipe.replace('@VERSION@', release_version.replace('-', ''))
|
||||||
|
recipe = recipe.replace('@UPSTREAM_VERSION@', release_version).replace('@SHA256@', digest)
|
||||||
|
(output / 'PKGBUILD').write_text(recipe)
|
||||||
|
spec = (ROOT / 'packaging/fedora/ferese.spec.in').read_text()
|
||||||
|
(output / 'ferese.spec').write_text(spec.replace('@VERSION@', release_version.replace('-', '~'))
|
||||||
|
.replace('@UPSTREAM_VERSION@', release_version))
|
||||||
|
(output / 'SHA256SUMS').write_text(f'{digest} {archive.name}\n')
|
||||||
|
print(archive)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
commands = parser.add_subparsers(dest='command', required=True)
|
||||||
|
stage_parser = commands.add_parser('stage')
|
||||||
|
stage_parser.add_argument('--destdir', required=True)
|
||||||
|
stage_parser.add_argument('--binary-dir', default=str(ROOT / 'target/release'))
|
||||||
|
stage_parser.add_argument('--pam', choices=['fedora', 'arch'], required=True)
|
||||||
|
source_parser = commands.add_parser('source')
|
||||||
|
source_parser.add_argument('--output', default='target/dist')
|
||||||
|
source_parser.add_argument('--tag')
|
||||||
|
source_parser.add_argument('--offline', action='store_true')
|
||||||
|
args = parser.parse_args()
|
||||||
|
if args.command == 'stage':
|
||||||
|
stage(args.destdir, args.binary_dir, args.pam)
|
||||||
|
else:
|
||||||
|
source(args.output, args.tag, args.offline)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
"""Package layout and release-input regressions; no root or network needed."""
|
||||||
|
import importlib.util
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
SPEC = importlib.util.spec_from_file_location('package', Path(__file__).resolve().parents[1] / 'package.py')
|
||||||
|
PACKAGE = importlib.util.module_from_spec(SPEC)
|
||||||
|
SPEC.loader.exec_module(PACKAGE)
|
||||||
|
|
||||||
|
|
||||||
|
class PackageTests(unittest.TestCase):
|
||||||
|
def test_release_tag_must_match_workspace_version(self):
|
||||||
|
PACKAGE.check_tag('v0.1.0', '0.1.0')
|
||||||
|
PACKAGE.check_tag('v0.2.0-rc.1', '0.2.0-rc.1')
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
PACKAGE.check_tag('v0.2.0', '0.1.0')
|
||||||
|
|
||||||
|
def test_refuses_live_root(self):
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
PACKAGE.stage('/', '/missing', 'fedora')
|
||||||
|
|
||||||
|
def test_refuses_existing_content_and_symlink_destinations(self):
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
root = Path(directory)
|
||||||
|
destination = root / 'dest'
|
||||||
|
destination.mkdir()
|
||||||
|
(destination / 'keep').write_text('existing')
|
||||||
|
link = root / 'link'
|
||||||
|
link.symlink_to(destination)
|
||||||
|
for path in (destination, link):
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
PACKAGE.stage(path, root / 'missing', 'fedora')
|
||||||
|
|
||||||
|
self.assertEqual((destination / 'keep').read_text(), 'existing')
|
||||||
|
|
||||||
|
def test_missing_binary_fails_before_staging(self):
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
root = Path(directory)
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
PACKAGE.stage(root / 'dest', root / 'missing', 'fedora')
|
||||||
|
|
||||||
|
self.assertFalse((root / 'dest').exists())
|
||||||
|
|
||||||
|
def test_complete_package_layout_and_both_pam_policies(self):
|
||||||
|
inventory = json.loads((PACKAGE.ROOT / 'scripts/installer/inventory.json').read_text())
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
root = Path(directory)
|
||||||
|
binaries = root / 'bin'
|
||||||
|
binaries.mkdir()
|
||||||
|
for entry in inventory['files']:
|
||||||
|
if entry['source'].startswith('target/release/'):
|
||||||
|
(binaries / entry['target']).write_bytes(b'fixture')
|
||||||
|
|
||||||
|
for pam in ('fedora', 'arch'):
|
||||||
|
with self.subTest(pam=pam), patch.object(PACKAGE, 'licenses'), patch.object(PACKAGE, 'run', return_value='{}'):
|
||||||
|
dest = root / pam
|
||||||
|
PACKAGE.stage(dest, binaries, pam)
|
||||||
|
for entry in inventory['files']:
|
||||||
|
if entry.get('link'):
|
||||||
|
name = Path(entry['source']).name
|
||||||
|
link = dest / 'usr/bin' / name
|
||||||
|
self.assertEqual(link.readlink(), Path('/usr/libexec/ferese') / name)
|
||||||
|
self.assertTrue((dest / str(link.readlink()).lstrip('/')).is_file())
|
||||||
|
|
||||||
|
for file in dest.rglob('*'):
|
||||||
|
if file.is_file() and not file.is_symlink() and file.suffix in ('.desktop', '.service'):
|
||||||
|
self.assertNotIn('/usr/local/', file.read_text())
|
||||||
|
|
||||||
|
self.assertFalse((dest / 'usr/local').exists())
|
||||||
|
self.assertIn('system-auth', (dest / 'etc/pam.d/ferese-lock').read_text())
|
||||||
|
wallpaper = dest / 'usr/libexec/ferese/wallpapers'
|
||||||
|
self.assertEqual(wallpaper.readlink(), Path('/usr/share/ferese/wallpapers'))
|
||||||
|
self.assertTrue((dest / 'usr/share/ferese/wallpapers/ferese.png').is_file())
|
||||||
|
self.assertEqual((dest / 'usr/libexec/ferese/ferese-session').stat().st_mode & 0o777, 0o755)
|
||||||
|
self.assertEqual((dest / 'usr/libexec/ferese').stat().st_mode & 0o777, 0o755)
|
||||||
|
|
||||||
|
def test_same_version_from_different_sources_keeps_both_licenses(self):
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
root = Path(directory)
|
||||||
|
packages = []
|
||||||
|
for source in ('registry+first', 'registry+second'):
|
||||||
|
folder = root / source.removeprefix('registry+')
|
||||||
|
folder.mkdir()
|
||||||
|
(folder / 'LICENSE').write_text(source)
|
||||||
|
packages.append({'name': 'dpi', 'version': '0.1.2', 'source': source,
|
||||||
|
'manifest_path': str(folder / 'Cargo.toml'),
|
||||||
|
'license': 'MIT', 'license_file': None})
|
||||||
|
|
||||||
|
PACKAGE.licenses(root / 'notices', {'packages': packages})
|
||||||
|
texts = {path.read_text() for path in (root / 'notices').glob('dpi-*/LICENSE')}
|
||||||
|
self.assertEqual(texts, {'registry+first', 'registry+second'})
|
||||||
|
|
||||||
|
def test_dependency_license_report_contains_source_and_license(self):
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
root = Path(directory)
|
||||||
|
(root / 'Cargo.toml').write_text('')
|
||||||
|
(root / 'LICENSE-MIT').write_text('test license')
|
||||||
|
metadata = {'packages': [{'name': 'fixture', 'version': '1.0.0',
|
||||||
|
'source': 'registry+test', 'manifest_path': str(root / 'Cargo.toml'),
|
||||||
|
'license': 'MIT', 'license_file': None}]}
|
||||||
|
PACKAGE.licenses(root / 'notices', metadata)
|
||||||
|
self.assertEqual(next((root / 'notices').glob('fixture-1.0.0-*/LICENSE-MIT')).read_text(), 'test license')
|
||||||
|
report = json.loads((root / 'notices/dependencies.json').read_text())
|
||||||
|
self.assertEqual(report[0]['license'], 'MIT')
|
||||||
|
self.assertEqual(report[0]['source'], 'registry+test')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
@@ -317,14 +317,18 @@ fn appearance_scroll_preserves_progress_and_settles_visibility() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn gpu_panel_text_survives_preview_clipping_and_scrolling() {
|
fn gpu_panel_text_survives_preview_clipping_and_scrolling() {
|
||||||
// Native startup preloads text fonts; headless renderers skip that step.
|
let matched = std::process::Command::new("fc-match")
|
||||||
// Supply a bundled proportional font even on minimal CI images.
|
.args(["--format=%{family}\n%{file}", "sans-serif"])
|
||||||
|
.output()
|
||||||
|
.expect("rendering tests require fontconfig");
|
||||||
|
assert!(matched.status.success());
|
||||||
|
let matched = String::from_utf8(matched.stdout).unwrap();
|
||||||
|
let (family, path) = matched.split_once('\n').expect("rendering tests require a system font");
|
||||||
|
let font = ferese_theme::font(Some(family.split(',').next().unwrap()));
|
||||||
cosmic::iced::advanced::graphics::text::font_system()
|
cosmic::iced::advanced::graphics::text::font_system()
|
||||||
.write()
|
.write()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.load_font(std::borrow::Cow::Borrowed(include_bytes!(
|
.load_font(std::borrow::Cow::Owned(std::fs::read(path).unwrap()));
|
||||||
"../../../assets/fonts/Comfortaa-Regular.otf"
|
|
||||||
)));
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct Labels {
|
struct Labels {
|
||||||
@@ -416,7 +420,7 @@ fn gpu_panel_text_survives_preview_clipping_and_scrolling() {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
.0;
|
.0;
|
||||||
app.font = Font::with_name("Comfortaa");
|
app.font = font;
|
||||||
app.panel_page = if selection == "panels" {
|
app.panel_page = if selection == "panels" {
|
||||||
panel_controls::PanelPage::Panels
|
panel_controls::PanelPage::Panels
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -622,10 +622,10 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn removed_desktop_widgets_are_rejected_at_startup_and_reload() {
|
fn removed_desktop_widgets_allow_startup_and_reload() {
|
||||||
let source = "desktop-widgets { clock { enabled #true; }; }";
|
let source = "desktop-widgets { clock { enabled #true; }; }";
|
||||||
assert!(parse_source(source).is_err());
|
parse_source(source).unwrap();
|
||||||
assert!(parse_test_source(source).is_err());
|
parse_test_source(source).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_test_source(source: &str) -> Result<ShellConfig, ferese_config::Error> {
|
fn parse_test_source(source: &str) -> Result<ShellConfig, ferese_config::Error> {
|
||||||
|
|||||||
Reference in new issue
Block a user