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
26 changes: 23 additions & 3 deletions .github/workflows/package-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,29 @@ jobs:
name: files-linux-x64
path: dist

- name: Create GitHub release
- name: Checksums and AUR recipe assets
run: |
cd dist
sha256sum files-linux-x64.tar.gz files-packaging.tar.gz Files-x86_64.AppImage Files-x86_64.flatpak > SHA256SUMS
# Release asset names may not start with a dot, so .SRCINFO is attached as SRCINFO.
cp aur/PKGBUILD PKGBUILD
cp aur/.SRCINFO SRCINFO

- name: Create or update GitHub release
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
TAG: ${{ github.ref_name }}
run: |
gh release create "$GITHUB_REF_NAME" -R "$GITHUB_REPOSITORY" --title "$GITHUB_REF_NAME" --generate-notes \
dist/files-linux-x64.tar.gz dist/files-packaging.tar.gz dist/Files-x86_64.AppImage dist/Files-x86_64.flatpak
cd dist
assets=(files-linux-x64.tar.gz files-packaging.tar.gz Files-x86_64.AppImage Files-x86_64.flatpak SHA256SUMS PKGBUILD SRCINFO)
# A direct lookup also finds drafts for a token with write access.
if gh release view "$TAG" >/dev/null 2>&1; then
# Draft or already published: keep its notes and state, replace the assets with the fresh build.
gh release upload "$TAG" "${assets[@]}" --clobber
else
flags=()
# linux-v0.1.0-alpha1 is a prerelease; linux-v0.1.0 is stable.
[[ "${TAG#linux-v}" == *-* ]] && flags+=(--prerelease)
gh release create "$TAG" "${assets[@]}" --title "$TAG" --generate-notes "${flags[@]}"
fi
32 changes: 31 additions & 1 deletion docs/linux-port/packaging.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,17 @@ unpacks the self-contained publish tarball, so there is no .NET SDK, NuGet resto
(Flathub-friendly: for a release, point the `file` source at the release URL with its sha256).
`scripts/linux/build-flatpak.sh [--bundle]` runs `flatpak-builder` (needs the 25.08 Platform and Sdk installed) into
`artifacts/flatpak-repo` and optionally writes `artifacts/Files-x86_64.flatpak`; without `flatpak-builder` it only
validates the manifest structure. Built locally; not yet installed or run inside the sandbox.
validates the manifest structure. Built locally, installed from a throwaway `FLATPAK_USER_DIR` and run inside the sandbox on a private Xvfb.

XDG user folders (Quick access / Pinned): `user-dirs.dirs` is visible in the sandbox (flatpak bind-mounts the host
file at `$XDG_CONFIG_HOME`, which is remapped to `~/.var/app/<id>/config`), `$HOME` is the real home, and
`--filesystem=host` exposes the folders, so no extra finish-arg is needed (`xdg-*` permissions would be redundant).
With a seeded real-path HOME the Desktop/Downloads/Documents/Pictures/Music/Videos pins appear, also when
`user-dirs.dirs` is missing (fallback to `$HOME/<default names>`). What does hide them is a HOME under `/tmp`:
flatpak gives the app a private `/tmp`, so a HOME seeded there looks empty (only the Recycle Bin is pinned). Seed
test homes outside `/tmp`. Defensive changes: `LinuxUserDirectories` honours `XDG_<NAME>_DIR` environment variables
before the file, and the first-run seed of `pinned_folders.json` is persisted only when at least one folder was
found, so a launch that could not see the folders retries instead of freezing an empty list.

`finish-args` rationale (each line is the minimum for a feature that exists in the code):

Expand Down Expand Up @@ -115,6 +125,26 @@ files, which is how the package was build-tested with `makepkg` here (resulting
installed on the dev box, so the package is not linted. `aur/linuxfiles/PKGBUILD` (from source) is unchanged and
unbuilt.

### Publishing a release (exact procedure)

The draft release `linux-vX.Y.Z[-pre]` already holds hand-uploaded assets; CI replaces them with its own build.

1. Undraft the release (GitHub UI, or `gh release edit linux-vX.Y.Z-pre -R MemerGamer/LinuxFiles --draft=false`).
Publishing creates the tag, which triggers `package-linux.yml`. The `release` job finds the existing release
(drafts included) and runs `gh release upload --clobber` for `files-linux-x64.tar.gz`, `files-packaging.tar.gz`,
`Files-x86_64.AppImage`, `Files-x86_64.flatpak`, a regenerated `SHA256SUMS`, and the AUR `PKGBUILD` and `SRCINFO`
(the `.SRCINFO`; release assets cannot start with a dot). Notes and prerelease state are untouched. If no release
exists for the tag, it creates one (prerelease when the version after `linux-v` contains `-`). `gen-aur.sh` validates the version strictly and keeps the real tag for the download URLs (`_tag`), while `pkgver` drops hyphens. Wait for the run to finish: the CI
tarballs differ from the hand-built ones, so the checksums in the PKGBUILD only match the CI assets.
2. Download the CI's recipe and commit it in the AUR clone:
```
cd ~/Documents/aur/linuxfiles-bin
gh release download linux-vX.Y.Z-pre -R MemerGamer/LinuxFiles -p PKGBUILD -p SRCINFO --clobber
mv -f SRCINFO .SRCINFO
git add PKGBUILD .SRCINFO && git commit -m "Update to X.Y.Z-pre"
```
3. `git push aur` (from the same directory; the remote is the AUR `linuxfiles-bin` repo).

## Nightly builds

`.github/workflows/nightly-linux.yml` builds the tarball and AppImage on every push to `main` and replaces the
Expand Down
5 changes: 3 additions & 2 deletions packaging/linux/aur/linuxfiles-bin/PKGBUILD
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
# Regenerate checksums and .SRCINFO for a release with scripts/linux/gen-aur.sh.
pkgname=linuxfiles-bin
_pkgname=linuxfiles
pkgver=0.1.0alpha1
_tag=0.1.0-alpha1 # release tag without the linux-v prefix; scripts/linux/gen-aur.sh sets it
pkgver=0.1.0alpha1 # the tag with hyphens removed
pkgrel=1
pkgdesc='LinuxFiles, Files for Linux: unofficial port of Files by the Files Community (Uno Platform), prebuilt binaries'
arch=('x86_64')
Expand All @@ -20,7 +21,7 @@ optdepends=('gvfs: network and MTP locations'
provides=("$_pkgname")
conflicts=("$_pkgname")
options=('!strip' '!debug') # stripping breaks the self-contained .NET binaries
_base="$url/releases/download/linux-v${pkgver/alpha/-alpha}" # 0.1.0alpha1 -> tag linux-v0.1.0-alpha1
_base="$url/releases/download/linux-v$_tag"
source=("$pkgname-$pkgver.tar.gz::$_base/files-linux-x64.tar.gz"
"$pkgname-packaging-$pkgver.tar.gz::$_base/files-packaging.tar.gz")
noextract=("$pkgname-$pkgver.tar.gz" "$pkgname-packaging-$pkgver.tar.gz")
Expand Down
43 changes: 36 additions & 7 deletions scripts/linux/gen-aur.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@
set -euo pipefail

root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
ver="${1:?version required, e.g. 0.1.0}"
ver="${ver//-/}" # pkgver may not contain hyphens: 0.1.0-alpha1 -> 0.1.0alpha1
tag="${1:?version required, e.g. 0.1.0 or 0.1.0-alpha1}"
[[ "$tag" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9.]+)?$ ]] || { echo "Invalid version: $tag" >&2; exit 2; }
if [[ -n "${AUR_BASE_URL:-}" && ! "$AUR_BASE_URL" =~ ^[A-Za-z0-9:/._~%@+-]+$ ]]; then
echo "Invalid AUR_BASE_URL" >&2; exit 2
fi
dir="${2:?directory with release tarballs required}"
out="${3:-$root/artifacts/aur}"
tpl="$root/packaging/linux/aur/linuxfiles-bin/PKGBUILD"
Expand All @@ -18,15 +21,41 @@ bin_sum="$(sum files-linux-x64.tar.gz)"
pkg_sum="$(sum files-packaging.tar.gz)"

mkdir -p "$out"
python3 - "$tpl" "$out/PKGBUILD" "$ver" "$bin_sum" "$pkg_sum" "${AUR_BASE_URL:-}" <<'PY'
python3 - "$tpl" "$out/PKGBUILD" "$tag" "$bin_sum" "$pkg_sum" "${AUR_BASE_URL:-}" <<'PY'
import re, sys
tpl, dst, ver, a, b, base = sys.argv[1:7]
t = open(tpl).read()
t = re.sub(r"^pkgver=.*$", "pkgver=" + ver, t, flags=re.M)
t = re.sub(r"sha256sums=\('SKIP'\n\s+'SKIP'\)", "sha256sums=('%s'\n '%s')" % (a, b), t)
t = re.sub(r"^_tag=.*$", lambda m: "_tag=" + ver, t, flags=re.M)
t = re.sub(r"^pkgver=.*$", lambda m: "pkgver=" + ver.replace("-", ""), t, flags=re.M)
t = re.sub(r"sha256sums=\('SKIP'\n\s+'SKIP'\)", lambda m: "sha256sums=('%s'\n '%s')" % (a, b), t)
if base:
t = re.sub(r'^_base=.*$', '_base="%s"' % base, t, flags=re.M)
t = re.sub(r'^_base=.*$', lambda m: '_base="%s"' % base, t, flags=re.M)
open(dst, "w").write(t)
PY
(cd "$out" && makepkg --printsrcinfo > .SRCINFO)
# makepkg only exists on Arch; elsewhere (CI runs on Ubuntu) emit the same fields by sourcing the PKGBUILD.
srcinfo() {
if command -v makepkg >/dev/null 2>&1 && [[ "${AUR_SRCINFO_FALLBACK:-}" != 1 ]]; then
(cd "$out" && makepkg --printsrcinfo)
return
fi
(
# shellcheck disable=SC1091
source "$out/PKGBUILD"
list() { local k="$1"; shift; local v; for v in "$@"; do printf '\t%s = %s\n' "$k" "$v"; done; }
printf 'pkgbase = %s\n' "$pkgname"
printf '\tpkgdesc = %s\n\tpkgver = %s\n\tpkgrel = %s\n\turl = %s\n' "$pkgdesc" "$pkgver" "$pkgrel" "$url"
list arch "${arch[@]}"
list license "${license[@]}"
list depends "${depends[@]}"
list optdepends "${optdepends[@]}"
list provides "${provides[@]}"
list conflicts "${conflicts[@]}"
list noextract "${noextract[@]}"
list options "${options[@]}"
list source "${source[@]}"
list sha256sums "${sha256sums[@]}"
printf '\npkgname = %s\n' "$pkgname"
)
}
srcinfo > "$out/.SRCINFO"
echo "Wrote $out/PKGBUILD and $out/.SRCINFO"
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,16 @@ private List<string> Read()
defaults.AddRange(ReadGtkBookmarks().Where(p => !defaults.Contains(p)));

// The trash is pinned by default like on other desktops
var foundFolders = defaults.Count > 1 || defaults.Count == 1 && defaults[0] != Constants.UserEnvironmentPaths.RecycleBinPath;
defaults.Add(Constants.UserEnvironmentPaths.RecycleBinPath);

try { Write(defaults); }
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
// Persist only a real seed, so a first run that could not see the user folders retries next launch.
if (foundFolders)
{
try { Write(defaults); }
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
{
}
}

return defaults;
Expand Down
7 changes: 7 additions & 0 deletions src/Files.Platform.Linux/AppData/LinuxUserDirectories.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public LinuxUserDirectories()
public LinuxUserDirectories(Func<string, string?> getEnvironmentVariable, string home)
{
Home = home;
_getEnvironmentVariable = getEnvironmentVariable;

var configHome = getEnvironmentVariable("XDG_CONFIG_HOME");
if (string.IsNullOrEmpty(configHome) || !Path.IsPathRooted(configHome))
Expand Down Expand Up @@ -72,8 +73,14 @@ public LinuxUserDirectories(Func<string, string?> getEnvironmentVariable, string
/// <inheritdoc/>
public string PublicShare { get; }

private readonly Func<string, string?> _getEnvironmentVariable;

private string Resolve(Dictionary<string, string> configured, string key, string defaultName)
{
// XDG_<NAME>_DIR in the environment (set by some sandboxes and sessions) wins over the file.
if (_getEnvironmentVariable($"XDG_{key}_DIR") is { Length: > 0 } fromEnv && Path.IsPathRooted(fromEnv))
return IsHome(fromEnv) ? Path.Combine(Home, defaultName) : fromEnv;

// Like xdg-user-dirs, a value equal to $HOME means the directory is disabled.
return configured.TryGetValue(key, out var path) && !IsHome(path)
? path
Expand Down
20 changes: 20 additions & 0 deletions tests/Files.Platform.Tests/AppData/AppDataTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,26 @@ public void UserDirectories_ReadsFromXdgConfigHome()
Assert.AreEqual(Path.Combine(_root, "D"), d.Desktop);
}

[TestMethod]
public void UserDirectories_EnvironmentVariablesOverrideFile()
{
var cfg = Path.Combine(_root, "cfg");
Directory.CreateDirectory(cfg);
File.WriteAllText(Path.Combine(cfg, "user-dirs.dirs"), "XDG_DESKTOP_DIR=\"$HOME/D\"\nXDG_DOWNLOAD_DIR=\"$HOME/F\"\n");

var d = new LinuxUserDirectories(Env(new()
{
["XDG_CONFIG_HOME"] = cfg,
["XDG_DESKTOP_DIR"] = "/data/desk",
["XDG_DOWNLOAD_DIR"] = "relative",
["XDG_MUSIC_DIR"] = _root,
}), _root);

Assert.AreEqual("/data/desk", d.Desktop);
Assert.AreEqual(Path.Combine(_root, "F"), d.Downloads, "relative env value ignored");
Assert.AreEqual(Path.Combine(_root, "Music"), d.Music, "$HOME means disabled");
}

[TestMethod]
public void Settings_RoundTripsSupportedTypes_AcrossInstances()
{
Expand Down
Loading