Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
258 changes: 165 additions & 93 deletions .github/workflows/portable.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
name: Linux and macOS packages

# Linux packages are built on Ubuntu 24.04 but must run on Ubuntu 22.04 (glibc 2.35). The rhwp CLI release needs a
# newer glibc, so it is built from pinned source on Ubuntu 22.04 first (linux-native, build/build-linux-native.py) and
# replaces the download in the package. SharpHook (>= 8.0.1) ships libuiohook builds for glibc 2.34 and is packaged as
# is. Every ELF file in a Linux package is then checked against glibc 2.35, and the x64 and arm64 packages are tried on
# Ubuntu 22.04, which also catches a SharpHook or engine update that raises the requirement again.

on:
workflow_call:
inputs:
Expand All @@ -18,7 +24,80 @@ permissions:
contents: read

jobs:
linux-native:
name: Linux native ${{ matrix.rid }} (Ubuntu 22.04)
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-22.04
rid: linux-x64
- runner: ubuntu-22.04-arm
rid: linux-arm64
runs-on: ${{ matrix.runner }}
timeout-minutes: 90
defaults:
run:
shell: bash
env:
NATIVE_OUT: ${{ github.workspace }}/build/.cache/linux-native/${{ matrix.rid }}
steps:
- uses: actions/checkout@v4

# Fails when engines.json moved to another rhwp version while build/build-linux-native.py still builds the old
# source (also when the build below comes from the cache).
- name: Check pinned sources
run: python3 build/build-linux-native.py check

- name: Restore native builds
id: native-cache
uses: actions/cache@v4
with:
path: build/.cache/linux-native/${{ matrix.rid }}
key: linux-native-${{ matrix.rid }}-ubuntu-22.04-${{ hashFiles('build/build-linux-native.py') }}

# rhwp's default features are pure Rust: a C toolchain for the linker and build scripts is enough.
- name: Build dependencies
if: steps.native-cache.outputs.cache-hit != 'true'
run: |
set -euo pipefail
sudo apt-get update
sudo apt-get install -y build-essential pkg-config git
if ! command -v rustup >/dev/null; then
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain none
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
fi

- name: Restore cargo downloads
if: steps.native-cache.outputs.cache-hit != 'true'
uses: actions/cache@v4
with:
path: |
~/.cargo/registry/index
~/.cargo/registry/cache
~/.cargo/git/db
key: cargo-registry-${{ matrix.rid }}-${{ hashFiles('build/build-linux-native.py') }}
restore-keys: cargo-registry-${{ matrix.rid }}-

- name: Build rhwp
if: steps.native-cache.outputs.cache-hit != 'true'
run: python3 build/build-linux-native.py rhwp --output "$NATIVE_OUT/rhwp" --work "$RUNNER_TEMP/rhwp"

- name: Check glibc requirements
run: python3 build/check-elf-glibc.py --max 2.35 --needed "$NATIVE_OUT"

- name: Upload native builds
uses: actions/upload-artifact@v4
with:
name: linux-native-${{ matrix.rid }}
path: build/.cache/linux-native/${{ matrix.rid }}/
if-no-files-found: error

portable:
needs: linux-native
# macOS packages don't use the Linux builds: they still run when those failed (the Linux legs then stop at the
# download).
if: ${{ !cancelled() }}
strategy:
fail-fast: false
matrix:
Expand All @@ -39,6 +118,13 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Download Linux native builds
if: runner.os == 'Linux'
uses: actions/download-artifact@v4
with:
name: linux-native-${{ matrix.rid }}
path: ${{ runner.temp }}/linux-native

- uses: actions/setup-dotnet@v4
with:
global-json-file: global.json
Expand All @@ -49,7 +135,7 @@ jobs:
run: |
sudo apt-get update
sudo apt-get install -y ca-certificates libc6 libgcc-s1 libgssapi-krb5-2 libicu74 libssl3t64 libstdc++6 tzdata zlib1g \
libfontconfig1 libfreetype6 libx11-6 libice6 libsm6 libxrandr2 libxkbcommon0 libxi6 libxtst6 xvfb fonts-noto-cjk
libfontconfig1 libfreetype6 libx11-6 libice6 libsm6 libxrandr2 libxkbcommon0 libxi6 libxtst6 libxt6 xvfb fonts-noto-cjk

- name: Fonts
run: ./build/fetch-fonts.ps1
Expand Down Expand Up @@ -77,106 +163,28 @@ jobs:
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
}

# Linux: rhwp from linux-native instead of the download (see the top).
- name: Build portable package
env:
PACKAGE_VERSION: ${{ inputs.version }}
run: ./build/build-portable.ps1 -Runtime '${{ matrix.rid }}' -Version $env:PACKAGE_VERSION -SkipFonts

- name: Check archive and run native converters
run: |
$archives = @(Get-ChildItem Releases -Filter '*-${{ matrix.rid }}.tar.gz')
if ($archives.Count -ne 1) { throw 'Expected one portable archive.' }
$archive = $archives[0]
$expected = (Get-Content (Join-Path Releases ($archive.Name.Replace('.tar.gz', '-SHA256SUMS.txt')))).Split(' ')[0]
if ((Get-FileHash $archive.FullName -Algorithm SHA256).Hash -ne $expected) { throw 'Archive checksum mismatch.' }
$smoke = Join-Path $env:RUNNER_TEMP 'filee-smoke'
New-Item -ItemType Directory -Path $smoke | Out-Null
tar -xzf $archive.FullName -C $smoke
if ($LASTEXITCODE -ne 0) { throw 'Archive extraction failed.' }
$package = Join-Path $smoke ($archive.Name.Replace('.tar.gz', ''))
if ($IsMacOS) {
codesign --verify --deep --strict (Join-Path $package 'Filee.app')
if ($LASTEXITCODE -ne 0) { throw 'Extracted app signature did not verify.' }
$native = @{}
if ($IsLinux) {
$native = @{ RhwpOverride = Join-Path $env:RUNNER_TEMP 'linux-native/rhwp' }
}
$bin = if ($IsMacOS) { Join-Path $package 'Filee.app/Contents/MacOS' } else { $package }
$cli = Join-Path $bin 'filee-cli'
& $cli version
if ($LASTEXITCODE -ne 0) { throw 'CLI did not start.' }
& (Join-Path $bin 'engines/rhwp/rhwp') --version
if ($LASTEXITCODE -ne 0) { throw 'rhwp did not start.' }
& (Join-Path $bin 'engines/7zip/7zz') i | Out-Null
if ($LASTEXITCODE -ne 0) { throw '7-Zip did not start.' }
$inputFile = Join-Path $smoke 'sample.txt'
'Filee 한글 conversion smoke test' | Set-Content $inputFile -Encoding utf8NoBOM
& $cli convert $inputFile --to hwpx -o (Join-Path $smoke 'converted') --json
if ($LASTEXITCODE -ne 0 -or -not (Test-Path (Join-Path $smoke 'converted/sample.hwpx'))) { throw 'Published CLI conversion failed.' }
"FILEE_SMOKE_APP=$(Join-Path $bin 'Filee')" >> $env:GITHUB_ENV
./build/build-portable.ps1 -Runtime '${{ matrix.rid }}' -Version $env:PACKAGE_VERSION -SkipFonts @native

- name: Check archive and run native converters
run: ./build/smoke-portable.ps1 -Runtime '${{ matrix.rid }}'

- name: Check glibc requirements
if: runner.os == 'Linux'
shell: bash
run: python3 build/check-elf-glibc.py --max 2.35 "$FILEE_SMOKE_PACKAGE"

- name: Launch native desktop app
shell: bash
run: |
set -euo pipefail
python3 - <<'PY'
import os
import pathlib
import signal
import subprocess
import sys
import time

app = os.environ['FILEE_SMOKE_APP']
shots = pathlib.Path(os.environ['RUNNER_TEMP']) / 'filee-screens'
shots.mkdir(exist_ok=True)
command = [app, '--settings', '--install-engines=', '--context-menu=off', '--start-with-system=off']
if os.environ['RUNNER_OS'] == 'Linux':
command = ['xvfb-run', '-a', 'env', '-u', 'WAYLAND_DISPLAY', 'XDG_SESSION_TYPE=x11', *command]
log_path = pathlib.Path(os.environ['RUNNER_TEMP']) / 'filee-startup.log'
with log_path.open('w') as log:
process = subprocess.Popen(command, stdout=log, stderr=subprocess.STDOUT, start_new_session=True)
try:
time.sleep(8)
if process.poll() is not None:
raise RuntimeError('The desktop app exited before the startup check.')
if os.environ['RUNNER_OS'] == 'macOS':
# The real screen with Filee's window, as a person would see it (uploaded with the results).
subprocess.run(['screencapture', '-x', str(shots / 'settings.png')], check=False, timeout=30)
# A second start forwards to the running Filee, which brings its window to the front.
try:
subprocess.run([app], check=False, timeout=30)
except subprocess.TimeoutExpired:
raise RuntimeError('A second start did not hand over to the running Filee.')
time.sleep(4)
subprocess.run(['screencapture', '-x', str(shots / 'second-start.png')], check=False, timeout=30)
if process.poll() is not None:
raise RuntimeError('The desktop app exited after a second start.')
quit_code = 'import os, sys; os.setsid(); os.execv(sys.argv[1], [sys.argv[1], "--quit"])'
# Reap Filee as soon as it ends while --quit waits for it, as launchd or a shell would: a process
# that has ended but was not reaped by its parent (this script) still counts as running.
quitter = subprocess.Popen([sys.executable, '-c', quit_code, app])
quit_deadline = time.monotonic() + 30
while quitter.poll() is None:
process.poll()
if time.monotonic() > quit_deadline:
quitter.kill()
raise RuntimeError('filee --quit did not finish.')
time.sleep(0.05)
if quitter.returncode != 0:
raise RuntimeError(f'filee --quit exited with {quitter.returncode}.')
if process.wait(timeout=15) != 0:
raise RuntimeError('The desktop app did not exit cleanly.')
except BaseException:
log.flush()
print(log_path.read_text(errors='replace'), file=sys.stderr)
raise
finally:
if process.poll() is None:
os.killpg(process.pid, signal.SIGTERM)
try:
process.wait(timeout=10)
except subprocess.TimeoutExpired:
os.killpg(process.pid, signal.SIGKILL)
process.wait()
PY
run: python3 build/smoke-launch-app.py

- name: Collect Filee logs
if: always()
Expand Down Expand Up @@ -207,3 +215,67 @@ jobs:
${{ runner.temp }}/filee-startup.log
${{ runner.temp }}/filee-screens/
if-no-files-found: ignore

# The packages built above, unpacked and started on the oldest supported Ubuntu, with only the runtime libraries a
# user installs from installer/linux/INSTALL.txt.
ubuntu-22-04:
name: Package on Ubuntu 22.04 (${{ matrix.rid }})
needs: portable
# Runs when another platform's package failed; a missing Linux package fails the download.
if: ${{ !cancelled() }}
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-22.04
rid: linux-x64
- runner: ubuntu-22.04-arm
rid: linux-arm64
runs-on: ${{ matrix.runner }}
timeout-minutes: 30
defaults:
run:
shell: pwsh
steps:
- uses: actions/checkout@v4

- name: Linux desktop dependencies (Ubuntu 22.04)
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y ca-certificates libc6 libgcc-s1 libgssapi-krb5-2 libicu70 libssl3 libstdc++6 tzdata zlib1g \
libfontconfig1 libfreetype6 libx11-6 libice6 libsm6 libxrandr2 libxkbcommon0 libxi6 libxtst6 libxt6 xvfb fonts-noto-cjk

- name: Download package
uses: actions/download-artifact@v4
with:
name: portable-${{ matrix.rid }}
path: Releases

- name: Check archive and run native converters
run: ./build/smoke-portable.ps1 -Runtime '${{ matrix.rid }}'

- name: Check glibc requirements
shell: bash
run: python3 build/check-elf-glibc.py --max 2.35 "$FILEE_SMOKE_PACKAGE"

- name: Launch native desktop app
shell: bash
run: python3 build/smoke-launch-app.py

- name: Collect Filee logs
if: always()
shell: bash
run: |
mkdir -p "$RUNNER_TEMP/filee-screens"
if [ -d "$HOME/.config/Filee/logs" ]; then cp -R "$HOME/.config/Filee/logs" "$RUNNER_TEMP/filee-screens/"; fi

- name: Upload diagnostics
if: always()
uses: actions/upload-artifact@v4
with:
name: ubuntu-22-04-results-${{ matrix.rid }}
path: |
${{ runner.temp }}/filee-startup.log
${{ runner.temp }}/filee-screens/
if-no-files-found: ignore
10 changes: 8 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,13 @@ install/uninstall scripts. `Filee-<version>-<rid>-SHA256SUMS.txt` verifies the a
`com.filee.app` and a development ad-hoc signature by default; public distribution still needs Developer ID signing
and notarization. `-SigningIdentity` can supply a signing identity, but does not perform notarization.

Linux release packages must run on Ubuntu 22.04 (glibc 2.35). CI builds rhwp from source on Ubuntu 22.04
(`build/build-linux-native.py`) and passes it with `-RhwpOverride`; a local package without it needs the glibc of the
machine that built it. Check a package with
`python3 build/check-elf-glibc.py --max 2.35 <unpacked package>`. See [docs/ENGINES.md](docs/ENGINES.md#linux-builds-for-older-glibc).

- **Linux:** drag gestures require X11. Install the Avalonia desktop dependencies (fontconfig, FreeType, X11,
Xrandr, Xi, Xtst, XkbCommon, ICE and SM) and CJK fonts for document comparisons. File actions are installed for
Xrandr, Xi, Xtst, Xt, XkbCommon, ICE and SM) and CJK fonts for document comparisons. File actions are installed for
Thunar, Nautilus, Nemo and Dolphin. The first enabled keyboard shortcut uses Thunar's native custom action;
close Thunar before changing or pausing that shortcut and reopen it afterwards. Global selected-file queries
and hold gestures are not supported. Wayland can use file actions, the drop zone, watch folders and CLI.
Expand All @@ -48,7 +53,8 @@ and notarization. `-SigningIdentity` can supply a signing identity, but does not
The macOS LibreOffice package does not include H2Orestart; native rhwp/Unhwp provide the supported HWP routes.
Use Settings > Engines to inspect actual availability before comparing formats.

The reusable `portable.yml` workflow builds and tests all four targets. Native platform tests skip on other OSes.
The reusable `portable.yml` workflow builds and tests all four targets and starts the Linux packages on Ubuntu 22.04.
Native platform tests skip on other OSes.
Before calling a port ready, verify the installed package, CLI conversion, Finder/Thunar selection with Unicode
and spaced paths, drag and keyboard gestures, permissions denied/granted, Retina/scaling, tray, login startup,
engine downloads, watch folders, and uninstall. CI GUI startup alone does not validate these interactions.
Expand Down
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.12" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.12" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.12" />
<PackageVersion Include="SharpHook" Version="8.0.0" />
<PackageVersion Include="SharpHook" Version="8.0.1" />
</ItemGroup>

<ItemGroup Label="Conversion engines">
Expand Down
2 changes: 1 addition & 1 deletion README.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
설치할 때 탐색기 메뉴, 로그인 시 자동 실행, `filee` 명령, 추가 엔진을 고를 수 있어요.
- **macOS 14 이상** (미리보기): `osx-arm64` 또는 `osx-x64` 패키지를 풀고 `./install.sh`를 실행해요.
아직 공증 전이라, 처음 실행할 때 시스템 설정 → 개인정보 보호 및 보안 → **그래도 열기**로 허용해 주세요.
- **Linux x64·ARM64** (미리보기): 패키지를 풀고 `./install.sh`를 실행해요(root 불필요, Python 3 필요).
- **Linux x64·ARM64** (미리보기, Ubuntu 22.04 이상): 패키지를 풀고 `./install.sh`를 실행해요(root 불필요, Python 3 필요).
드래그 제스처는 X11에서 돼요. Wayland에서는 드롭 영역과 파일 관리자 우클릭 메뉴를 써 주세요.

새 버전이 나오면 메뉴에 표시되고, 설정과 엔진은 그대로 둔 채 업데이트돼요.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ Download from [Releases](https://github.com/KnifeLemon/Filee/releases/latest).
Setup can add the Explorer menu, start at sign-in, the `filee` command and optional engines.
- **macOS 14+** (preview): unpack the `osx-arm64` or `osx-x64` package and run `./install.sh`.
The build isn't notarized yet, so allow the first start in System Settings → Privacy & Security → **Open Anyway**.
- **Linux x64/ARM64** (preview): unpack the package and run `./install.sh` (no root, needs Python 3).
- **Linux x64/ARM64** (preview, Ubuntu 22.04+): unpack the package and run `./install.sh` (no root, needs Python 3).
Drag gestures need X11. On Wayland, use the drop zone and the file manager's right-click menu.

Filee shows new releases in its menu and updates itself, keeping your settings and engines.
Expand Down
2 changes: 1 addition & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
安装时可以添加资源管理器菜单、登录时启动、`filee` 命令和可选引擎。
- **macOS 14 及以上**(预览)解压 `osx-arm64` 或 `osx-x64` 包并运行 `./install.sh`。
目前尚未公证,首次启动时请在系统设置 → 隐私与安全性中点 **仍要打开**。
- **Linux x64、ARM64**(预览)解压后运行 `./install.sh`(无需 root,需要 Python 3)。
- **Linux x64、ARM64**(预览,Ubuntu 22.04 及以上)解压后运行 `./install.sh`(无需 root,需要 Python 3)。
拖动手势需要 X11,Wayland 下请使用拖放区域和文件管理器的右键菜单。

有新版本时 Filee 会在菜单中提示,更新时保留设置和引擎。
Expand Down
Loading
Loading