mirror of
https://github.com/lucannez64/darling-lol.git
synced 2026-10-26 19:47:42 +00:00
fix(nix): harden fontconfig compatibility across host setups
This commit is contained in:
1 parent
ec85cfb96b
commit
6a96869db8
4 files changed
+131
-6
No files matched your search
@@ -215,6 +215,12 @@ the host's `/usr/share/fonts`, which the FHS environment replaces. Fonts in your
|
||||
user font directories remain available. This also prevents an icon-only user
|
||||
font inventory from becoming the only fallback for ordinary text.
|
||||
|
||||
Inherited `FONTCONFIG_PATH` and `FONTCONFIG_SYSROOT` overrides are cleared inside
|
||||
the FHS environment so foreign include paths or an invalid sysroot cannot hide
|
||||
the packaged fonts. Custom XDG font/configuration directories and legacy
|
||||
`~/.fonts` remain supported, including paths with spaces. The host configuration
|
||||
and user font files are not modified.
|
||||
|
||||
To check font availability without starting Darling or Riot:
|
||||
|
||||
```sh
|
||||
|
||||
@@ -17,7 +17,14 @@
|
||||
darlingFonts = [ pkgs.liberation_ttf pkgs.dejavu_fonts ];
|
||||
darlingFontsConf = pkgs.makeFontsConf {
|
||||
fontDirectories = darlingFonts;
|
||||
includes = [ "${pkgs.fontconfig.out}/etc/fonts/conf.d" ];
|
||||
includes = [
|
||||
"${pkgs.fontconfig.out}/etc/fonts/conf.d"
|
||||
(pkgs.writeText "darling-user-fonts.conf" ''
|
||||
<fontconfig>
|
||||
<dir>~/.fonts</dir>
|
||||
</fontconfig>
|
||||
'')
|
||||
];
|
||||
};
|
||||
|
||||
# Python environment with dependencies required by tooling
|
||||
@@ -163,8 +170,8 @@
|
||||
export DPREFIX="''${DPREFIX:-$HOME/.darling}"
|
||||
export LC_ALL=C.UTF-8
|
||||
export LANG=C.UTF-8
|
||||
unset FONTCONFIG_PATH FONTCONFIG_SYSROOT
|
||||
export FONTCONFIG_FILE=${darlingFontsConf}
|
||||
export FONTCONFIG_PATH=${pkgs.fontconfig.out}/etc/fonts
|
||||
export DARLING_RUNNER_DIR=${darlingRunner}/bin
|
||||
export PATH="$DARLING_RUNNER_DIR:/usr/local/bin:''${PATH:-}"
|
||||
export PKG_CONFIG_PATH="/usr/local/lib/pkgconfig:/usr/local/share/pkgconfig:/usr/lib/pkgconfig:/usr/lib64/pkgconfig:/usr/share/pkgconfig:''${PKG_CONFIG_PATH:-}"
|
||||
@@ -267,10 +274,10 @@ DESKTOP_EOF
|
||||
fonts = pkgs.runCommand "darling-lol-font-tests" {
|
||||
nativeBuildInputs = [ pkgs.fontconfig ];
|
||||
} ''
|
||||
export HOME="$TMPDIR/home" XDG_CONFIG_HOME="$TMPDIR/config"
|
||||
export XDG_DATA_HOME="$TMPDIR/data" XDG_CACHE_HOME="$TMPDIR/cache"
|
||||
export HOME="$TMPDIR/home with spaces" XDG_CONFIG_HOME="$TMPDIR/config with spaces"
|
||||
export XDG_DATA_HOME="$TMPDIR/data with spaces" XDG_CACHE_HOME="$TMPDIR/cache with spaces"
|
||||
unset FONTCONFIG_PATH FONTCONFIG_SYSROOT
|
||||
export FONTCONFIG_FILE=${darlingFontsConf}
|
||||
export FONTCONFIG_PATH=${pkgs.fontconfig.out}/etc/fonts
|
||||
mkdir -p "$HOME" "$XDG_CONFIG_HOME" "$XDG_DATA_HOME" "$XDG_CACHE_HOME"
|
||||
# Check real font selection with no host or user text fonts available.
|
||||
check_font() {
|
||||
@@ -294,6 +301,23 @@ DESKTOP_EOF
|
||||
cp -R ${pkgs.font-awesome}/share/fonts "$XDG_DATA_HOME/fonts"
|
||||
fc-list --format '%{file}\n' | grep -F "$XDG_DATA_HOME/fonts/"
|
||||
check_aliases
|
||||
|
||||
# Retain custom XDG and legacy fonts as well as user alias rules.
|
||||
chmod u+w "$XDG_DATA_HOME/fonts"
|
||||
mkdir -p "$HOME/.fonts" "$XDG_CONFIG_HOME/fontconfig"
|
||||
cp ${pkgs.liberation_ttf}/share/fonts/truetype/LiberationSans-Regular.ttf "$XDG_DATA_HOME/fonts/user-text.ttf"
|
||||
cp ${pkgs.liberation_ttf}/share/fonts/truetype/LiberationSerif-Regular.ttf "$HOME/.fonts/legacy-text.ttf"
|
||||
fc-list --format '%{file}\n' | grep -Fx "$XDG_DATA_HOME/fonts/user-text.ttf"
|
||||
fc-list --format '%{file}\n' | grep -Fx "$HOME/.fonts/legacy-text.ttf"
|
||||
cat > "$XDG_CONFIG_HOME/fontconfig/fonts.conf" <<'USERCONF_EOF'
|
||||
<fontconfig>
|
||||
<alias>
|
||||
<family>DarlingCompatibilityAlias</family>
|
||||
<prefer><family>Liberation Serif</family></prefer>
|
||||
</alias>
|
||||
</fontconfig>
|
||||
USERCONF_EOF
|
||||
check_font DarlingCompatibilityAlias 'Liberation Serif'
|
||||
touch "$out"
|
||||
'';
|
||||
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
|
||||
def extract_fhs_profile(flake_text: str) -> str:
|
||||
match = re.search(r"profile\s*=\s*''(.*?)'';", flake_text, re.DOTALL)
|
||||
if not match:
|
||||
raise ValueError("Could not find profile in flake.nix")
|
||||
raw_profile = match.group(1)
|
||||
# Nix multi-line strings: (?<!'')\$\{...\} is interpolation, ''$ is escaped $
|
||||
profile_script = re.sub(r"(?<!'')\$\{([^}]+)\}", r"/nix/store/mock-\1", raw_profile)
|
||||
profile_script = profile_script.replace("''$", "$")
|
||||
return profile_script
|
||||
|
||||
class NixFontsConfigTest(unittest.TestCase):
|
||||
def test_flake_font_configuration(self):
|
||||
flake_text = (REPO_ROOT / "flake.nix").read_text(encoding="utf-8")
|
||||
|
||||
# Flake must bundle both font families in darlingFontsConf
|
||||
self.assertIn("pkgs.liberation_ttf", flake_text)
|
||||
self.assertIn("pkgs.dejavu_fonts", flake_text)
|
||||
|
||||
# makeFontsConf includes store fontconfig config directory
|
||||
self.assertIn('"${pkgs.fontconfig.out}/etc/fonts/conf.d"', flake_text)
|
||||
|
||||
# Legacy ~/.fonts XML fragment included in darlingFontsConf
|
||||
self.assertIn("<dir>~/.fonts</dir>", flake_text)
|
||||
|
||||
# FHS profile unsets foreign/sysroot fontconfig vars before exporting package FONTCONFIG_FILE
|
||||
self.assertIn("unset FONTCONFIG_PATH FONTCONFIG_SYSROOT", flake_text)
|
||||
self.assertIn("export FONTCONFIG_FILE=${darlingFontsConf}", flake_text)
|
||||
|
||||
unset_pos = flake_text.index("unset FONTCONFIG_PATH FONTCONFIG_SYSROOT")
|
||||
export_pos = flake_text.index("export FONTCONFIG_FILE=${darlingFontsConf}")
|
||||
self.assertLess(unset_pos, export_pos, "unset must happen before export FONTCONFIG_FILE")
|
||||
|
||||
def test_fhs_profile_hostile_environment_sanitization(self):
|
||||
flake_text = (REPO_ROOT / "flake.nix").read_text(encoding="utf-8")
|
||||
profile_script = extract_fhs_profile(flake_text)
|
||||
|
||||
hostile_envs = [
|
||||
{
|
||||
"FONTCONFIG_SYSROOT": "/not/a/real/host/sysroot",
|
||||
"FONTCONFIG_PATH": "/foreign/host/etc/fonts",
|
||||
},
|
||||
{
|
||||
"FONTCONFIG_SYSROOT": "",
|
||||
"FONTCONFIG_PATH": "/foreign/host/etc/fonts",
|
||||
},
|
||||
]
|
||||
|
||||
for hostile_font_env in hostile_envs:
|
||||
with self.subTest(hostile_font_env=hostile_font_env):
|
||||
test_env = dict(hostile_font_env)
|
||||
test_env.update({
|
||||
"HOME": "/tmp/mock-home",
|
||||
"XDG_DATA_HOME": "/tmp/mock-xdg data with spaces",
|
||||
"XDG_CONFIG_HOME": "/tmp/mock-xdg config with spaces",
|
||||
"UNRELATED_USER_VAR": "preserve_this_value",
|
||||
"PATH": os.environ.get("PATH", "/bin:/usr/bin"),
|
||||
})
|
||||
|
||||
dump_cmd = (
|
||||
f"{profile_script}\n"
|
||||
f'"{sys.executable}" -c \'import os, json; print(json.dumps(dict(os.environ)))\''
|
||||
)
|
||||
|
||||
proc = subprocess.run(
|
||||
["bash", "-c", dump_cmd],
|
||||
env=test_env,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
)
|
||||
|
||||
resulting_env = json.loads(proc.stdout)
|
||||
|
||||
self.assertNotIn("FONTCONFIG_SYSROOT", resulting_env)
|
||||
self.assertNotIn("FONTCONFIG_PATH", resulting_env)
|
||||
self.assertTrue(
|
||||
resulting_env.get("FONTCONFIG_FILE", "").startswith("/nix/store/"),
|
||||
f"Unexpected FONTCONFIG_FILE: {resulting_env.get('FONTCONFIG_FILE')}",
|
||||
)
|
||||
self.assertEqual(resulting_env.get("XDG_DATA_HOME"), "/tmp/mock-xdg data with spaces")
|
||||
self.assertEqual(resulting_env.get("XDG_CONFIG_HOME"), "/tmp/mock-xdg config with spaces")
|
||||
self.assertEqual(resulting_env.get("UNRELATED_USER_VAR"), "preserve_this_value")
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -453,7 +453,7 @@ class SessionTests(unittest.TestCase):
|
||||
self.assertIn('terminal', result.stderr.lower())
|
||||
self.assertFalse((root / 'build').exists())
|
||||
self.assertIn('Terminal=true', (ROOT / 'flake.nix').read_text())
|
||||
self.assertIn('checks.${system}.packaging', (ROOT / 'flake.nix').read_text())
|
||||
self.assertIn('checks.${system}', (ROOT / 'flake.nix').read_text())
|
||||
|
||||
def test_rootless_update_refuses_live_marker_without_build_or_stop(self):
|
||||
with tempfile.TemporaryDirectory(prefix='nix-update-idle-') as directory:
|
||||
|
||||
Reference in new issue
Block a user