fontconfig: Branch 'main' - 3 commits
GitLab Mirror
gitlab-mirror at kemper.freedesktop.org
Mon Jul 28 10:32:17 UTC 2025
configure.ac | 10 ++++-
meson.build | 6 ++-
src/fcarch.c | 2 -
src/fccache.c | 39 +++++++++++++++++++++
src/fcint.h | 3 +
src/meson.build | 6 +++
test/fctest/__init__.py | 4 ++
test/meson.build | 15 +++++---
test/test-gen-testcache.c | 45 ++++++++++++++++++++++++
test/test_sandbox.py | 83 ++++++++++++++++++++++++++++++++++++++++++++++
10 files changed, 204 insertions(+), 9 deletions(-)
New commits:
commit ce174010e814be20e5c74141b2a86f986d89cb75
Merge: 8f169b6 59da606
Author: Akira TAGOH <akira at tagoh.org>
Date: Mon Jul 28 10:32:14 2025 +0000
Merge branch 'fc-version-in-cache' into 'main'
Add fontconfig version in FcCache
See merge request fontconfig/fontconfig!452
commit 59da606145558a0041eb90d9c80a26a6f0c1d348
Author: Akira TAGOH <akira at tagoh.org>
Date: Fri Jul 18 19:19:50 2025 +0900
Add fontconfig version in FcCache
Even if cache files needs to be updated, do not re-generate them
if it was generated by newer version of fontconfig.
Fontconfig cache files are basically compatible with older versions
of fontconfig unless any drastic changes happens there and bump a
cache version. older versions of fontconfig simply ignore unknown
objects in caches.
However, a troublesome situation is coming by sharing caches between
host and containers. A situation of updating caches is likely to
happens on even containers when mtime of font directories are updated.
And it may drops such unknown objects. So this aims to avoid such
situation and keep not updating and warn to encourage users to update
caches at host.
Also, added FONTCONFIG_NO_CHECK_CACHE_VERSION environment variable to
revert this behavior for contigency plan. This will be removed in
the future.
Changelog: changed
diff --git a/configure.ac b/configure.ac
index 2d48096..578a894 100644
--- a/configure.ac
+++ b/configure.ac
@@ -68,8 +68,16 @@ dnl Initialize libtool
LT_PREREQ([2.2])
LT_INIT([disable-static win32-dll])
+dnl fc version
+eval `echo $VERSION |
+ awk -F. '{ printf ("major=%d\nminor=%d\nrevision=%d\n",
+ $1, $2, $3); }'`
+AC_DEFINE_UNQUOTED([FC_VERSION_MAJOR], [$major], [major version])
+AC_DEFINE_UNQUOTED([FC_VERSION_MINOR], [$minor], [minor version])
+AC_DEFINE_UNQUOTED([FC_VERSION_MICRO], [$revision], [revision])
+
dnl cache version
-CACHE_VERSION=9
+CACHE_VERSION=10
AC_SUBST(CACHE_VERSION)
dnl libtool versioning
diff --git a/meson.build b/meson.build
index e742ea6..63a2ca8 100644
--- a/meson.build
+++ b/meson.build
@@ -25,7 +25,7 @@ curversion = fc_version_minor - 1
libversion = '@0 at .@1 at .0'.format(soversion, curversion)
defversion = '@0 at .@1@'.format(curversion, fc_version_micro)
osxversion = curversion + 1
-cacheversion = '9'
+cacheversion = '10'
freetype_req = '>= 21.0.15'
freetype_req_cmake = '>= 2.8.1'
@@ -35,6 +35,10 @@ math_dep = cc.find_library('m', required: false)
conf = configuration_data()
+conf.set('FC_VERSION_MAJOR', fc_version_major)
+conf.set('FC_VERSION_MINOR', fc_version_minor)
+conf.set('FC_VERSION_MICRO', fc_version_micro)
+
freetype_dep = dependency('freetype2', method: 'pkg-config', version: freetype_req, required: false)
# Give another shot using CMake
diff --git a/src/fcarch.c b/src/fcarch.c
index 66a0240..b1882d0 100644
--- a/src/fcarch.c
+++ b/src/fcarch.c
@@ -50,7 +50,7 @@ FC_ASSERT_STATIC (0x08 + 1 * FC_MAX (SIZEOF_VOID_P, ALIGNOF_DOUBLE) == sizeof (F
FC_ASSERT_STATIC (0x00 + 2 * SIZEOF_VOID_P == sizeof (FcPatternElt));
FC_ASSERT_STATIC (0x08 + 2 * SIZEOF_VOID_P == sizeof (FcPattern));
FC_ASSERT_STATIC (0x08 + 2 * SIZEOF_VOID_P == sizeof (FcCharSet));
-FC_ASSERT_STATIC (0x10 + 6 * SIZEOF_VOID_P == sizeof (FcCache));
+FC_ASSERT_STATIC (0x28 + 4 * SIZEOF_VOID_P == sizeof (FcCache));
int
main (int argc FC_UNUSED, char **argv FC_UNUSED)
diff --git a/src/fccache.c b/src/fccache.c
index 15791e3..5847c8e 100644
--- a/src/fccache.c
+++ b/src/fccache.c
@@ -770,6 +770,33 @@ FcCacheFini (void)
free_lock();
}
+static FcBool
+FcCacheIsNewVersion (FcConfig *config, FcCache *cache)
+{
+ int64_t version = (FC_VERSION_MAJOR << 24) +
+ (FC_VERSION_MINOR << 12) +
+ FC_VERSION_MICRO;
+ static FcBool warn = FcFalse;
+ static FcBool flag = FcFalse, is_initialized = FcFalse;
+
+ /* To revert the behavior for some emergency. This will be removed in the future */
+ if (!is_initialized) {
+ const char *env = getenv ("FONTCONFIG_NO_CHECK_CACHE_VERSION");
+
+ is_initialized = FcTrue;
+ if (env)
+ FcNameBool ((const FcChar8 *)env, &flag);
+ }
+ if (flag)
+ return !flag;
+ if (cache->fc_version > version && !warn) {
+ warn = FcTrue;
+ fprintf (stderr, "Fontconfig warning: Some cache files was generated by the newer version (0x%lx) of Fontconfig but caches is outdated. We won't regenearate it to avoid an unexpected behavior. Please regenerate caches with the latest version of Fontconfig. (current: 0x%lx)\n", cache->fc_version, version);
+ }
+
+ return cache->fc_version > version;
+}
+
static FcBool
FcCacheTimeValid (FcConfig *config, FcCache *cache, struct stat *dir_stat)
{
@@ -968,6 +995,12 @@ FcDirCacheMapFd (FcConfig *config, int fd, struct stat *fd_stat, struct stat *di
if (cache) {
if (FcCacheTimeValid (config, cache, dir_stat))
return cache;
+ else if (FcCacheIsNewVersion (config, cache)) {
+ /* Re-use if cache was generated by newer version of fontconfig
+ * Do not trigger regenerating
+ */
+ return cache;
+ }
FcDirCacheUnload (cache);
cache = NULL;
}
@@ -1014,7 +1047,8 @@ FcDirCacheMapFd (FcConfig *config, int fd, struct stat *fd_stat, struct stat *di
cache->version < FC_CACHE_VERSION_NUMBER ||
cache->size != (intptr_t)fd_stat->st_size ||
!FcCacheOffsetsValid (cache) ||
- !FcCacheTimeValid (config, cache, dir_stat) ||
+ (!FcCacheTimeValid (config, cache, dir_stat) &&
+ !FcCacheIsNewVersion (config, cache)) ||
!FcCacheInsert (cache, fd_stat)) {
if (allocated)
free (cache);
@@ -1284,6 +1318,9 @@ FcDirCacheBuild (FcFontSet *set, const FcChar8 *dir, struct stat *dir_stat, FcSt
cache->size = serialize->size;
cache->checksum = FcDirChecksum (dir_stat);
cache->checksum_nano = FcDirChecksumNano (dir_stat);
+ cache->fc_version = (FC_VERSION_MAJOR << 24) +
+ (FC_VERSION_MINOR << 12) +
+ FC_VERSION_MICRO;
/*
* Serialize directory name
diff --git a/src/fcint.h b/src/fcint.h
index 8046cc8..9f03329 100644
--- a/src/fcint.h
+++ b/src/fcint.h
@@ -455,9 +455,12 @@ struct _FcCache {
intptr_t dir; /* offset to dir name */
intptr_t dirs; /* offset to subdirs */
int dirs_count; /* number of subdir strings */
+ int pad1;
intptr_t set; /* offset to font set */
int checksum; /* checksum of directory state */
+ int pad2;
int64_t checksum_nano; /* checksum of directory state */
+ int64_t fc_version; /* fontconfig version */
};
#undef FcCacheDir
diff --git a/src/meson.build b/src/meson.build
index 5a93687..c4d65d6 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -73,5 +73,6 @@ lib_fontconfig_kwargs = {
fcarch = executable('fcarch', ['fcarch.c', 'fcarch.h', fcstdint_h, fclang_h],
include_directories: [incbase, incsrc],
c_args: c_args,
+ dependencies: [libintl_dep],
install: false,
install_tag: 'runtime')
diff --git a/test/fctest/__init__.py b/test/fctest/__init__.py
index cbc3782..4abab2e 100644
--- a/test/fctest/__init__.py
+++ b/test/fctest/__init__.py
@@ -116,6 +116,10 @@ class FcTest:
self._extra = [x for x in self._extra if not re.search(r'\b<remap-dir\b', x)]
self._extra += [f'<remap-dir as-path="{self.fontdir.name}">{v}</remap-dir>']
+ @property
+ def env(self):
+ return self._env
+
def config(self) -> str:
return self.__conf_templ.format(fontdir=self.convert_path(self.fontdir.name),
cachedir=self.convert_path(self.cachedir.name),
diff --git a/test/meson.build b/test/meson.build
index 188c8ce..f26b252 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -23,6 +23,9 @@ tests = [
['test-family-matching.c'],
['test-ptrlist.c', {'include_directories': include_directories('../src'), 'dependencies': libintl_dep}],
]
+tests_build_only = [
+ ['test-gen-testcache.c', {'include_directories': include_directories('../src'), 'dependencies': libintl_dep}],
+]
tests_not_parallel = []
if host_machine.system() != 'windows'
@@ -50,7 +53,7 @@ if get_option('fontations').enabled()
link_with_libs += [fc_fontations]
endif
-foreach test_data : tests + tests_not_parallel
+foreach test_data : tests + tests_not_parallel + tests_build_only
fname = test_data[0]
opts = test_data.length() > 1 ? test_data[1] : {}
extra_c_args = opts.get('c_args', [])
@@ -66,10 +69,12 @@ foreach test_data : tests + tests_not_parallel
dependencies: extra_deps,
)
- if test_data in tests
- test(test_name, exe, timeout: 600, is_parallel: true)
- else
- test(test_name, exe, timeout: 600, is_parallel: false)
+ if test_data not in tests_build_only
+ if test_data in tests
+ test(test_name, exe, timeout: 600, is_parallel: true)
+ else
+ test(test_name, exe, timeout: 600, is_parallel: false)
+ endif
endif
endforeach
diff --git a/test/test-gen-testcache.c b/test/test-gen-testcache.c
new file mode 100644
index 0000000..06d8333
--- /dev/null
+++ b/test/test-gen-testcache.c
@@ -0,0 +1,45 @@
+/* Copyright (C) 2025 fontconfig Authors */
+/* SPDX-License-Identifier: HPND */
+#include "fcint.h"
+#include "src/fcint.h"
+
+int
+main (int argc, char **argv)
+{
+ FcConfig *config;
+ FcFontSet *fs;
+ FcStrSet *dirs;
+ FcCache *cache;
+ struct stat st;
+ int ret = -1;
+
+ if (argc <= 1) {
+ fprintf (stderr, "Usage: %s <font path>\n", argv[0]);
+ return 1;
+ }
+ FcInit();
+ config = FcConfigGetCurrent();
+ fs = FcFontSetCreate();
+ dirs = FcStrSetCreateEx (FCSS_GROW_BY_64);
+ if (FcStatChecksum ((const FcChar8 *)argv[1], &st) < 0)
+ goto bail;
+ if (!FcDirScanConfig (fs, dirs, (const FcChar8 *)argv[1], FcTrue, config))
+ goto bail2;
+ cache = FcDirCacheBuild (fs, (const FcChar8 *)argv[1], &st, dirs);
+ if (!cache)
+ goto bail2;
+ cache->fc_version = ((FC_VERSION_MAJOR + 1) << 24) +
+ (FC_VERSION_MINOR << 12) +
+ FC_VERSION_MICRO;
+ FcDirCacheWrite (cache, config);
+ ret = 0;
+ bail2:
+ FcStrSetDestroy (dirs);
+ bail:
+ FcFontSetDestroy (fs);
+ FcConfigDestroy (config);
+
+ FcFini();
+
+ return ret;
+}
diff --git a/test/test_sandbox.py b/test/test_sandbox.py
index 4732fb6..2283dde 100644
--- a/test/test_sandbox.py
+++ b/test/test_sandbox.py
@@ -164,3 +164,86 @@ def test_md5_consistency(fctest, fcfont):
# Make sure they are totally different but same filename
assert cache_files_before == cache_files_after
assert cmp_cache_before != cmp_cache_after
+
+
+ at pytest.mark.skipif(not not os.getenv('EXEEXT'), reason='not working on Win32')
+ at pytest.mark.skipif(not shutil.which('bwrap'), reason='No bwrap installed')
+def test_gen_testcache(fctest, fcfont):
+ testexe = Path(fctest.builddir) / 'test' / ('test-gen-testcache' + fctest._exeext)
+ if not testexe.exists():
+ testexe = Path(fctest.builddir) / 'test' / ('test_gen_testcache' + fctest._exeext)
+ if not testexe.exists():
+ raise RuntimeError('No test case for gen-testcache')
+
+ fctest.setup()
+ fctest.install_font(fcfont.fonts, '.')
+ for ret, stdout, stderr in fctest.run(testexe, [fctest.fontdir.name]):
+ assert ret == 0, stderr
+ cache_files1 = [f for f in fctest.cache_files()]
+ assert len(cache_files1) == 1, cache_files1
+ time.sleep(1)
+ # Update mtime
+ Path(fctest.fontdir.name).touch()
+ cache_stat1 = [f.stat() for f in fctest.cache_files()]
+ fctest.logger.info(cache_files1)
+
+ time.sleep(1)
+ basedir = tempfile.TemporaryDirectory(prefix='fontconfig.',
+ suffix='.base')
+ with fctest.sandboxed(basedir.name) as f:
+ for ret, stdout, stderr in f.run_match([]):
+ assert ret == 0, stderr
+ assert "Fontconfig warning" in stderr, stderr
+ f.logger.info(stdout)
+ f.logger.info(stderr)
+
+ cache_stat2 = [f.stat() for f in fctest.cache_files()]
+ cache_files2 = [f for f in fctest.cache_files()]
+ assert len(cache_stat2) == 1, cache_stat2
+ assert cache_files1 == cache_files2, cache_files2
+ # ignore st_atime
+ cmp_cache_before = [attrgetter('st_mode', 'st_ino', 'st_dev', 'st_nlink', 'st_uid', 'st_gid', 'st_size', 'st_mtime', 'st_ctime')(st) for st in cache_stat1]
+ cmp_cache_after = [attrgetter('st_mode', 'st_ino', 'st_dev', 'st_nlink', 'st_uid', 'st_gid', 'st_size', 'st_mtime', 'st_ctime')(st) for st in cache_stat2]
+ assert cmp_cache_before == cmp_cache_after
+
+
+ at pytest.mark.skipif(not not os.getenv('EXEEXT'), reason='not working on Win32')
+ at pytest.mark.skipif(not shutil.which('bwrap'), reason='No bwrap installed')
+def test_gen_testcache_no_check(fctest, fcfont):
+ testexe = Path(fctest.builddir) / 'test' / ('test-gen-testcache' + fctest._exeext)
+ if not testexe.exists():
+ testexe = Path(fctest.builddir) / 'test' / ('test_gen_testcache' + fctest._exeext)
+ if not testexe.exists():
+ raise RuntimeError('No test case for gen-testcache')
+
+ fctest.env['FONTCONFIG_NO_CHECK_CACHE_VERSION'] = '1'
+ fctest.setup()
+ fctest.install_font(fcfont.fonts, '.')
+ for ret, stdout, stderr in fctest.run(testexe, [fctest.fontdir.name]):
+ assert ret == 0, stderr
+ cache_files1 = [f for f in fctest.cache_files()]
+ assert len(cache_files1) == 1, cache_files1
+ time.sleep(1)
+ # Update mtime
+ Path(fctest.fontdir.name).touch()
+ cache_stat1 = [f.stat() for f in fctest.cache_files()]
+ fctest.logger.info(cache_files1)
+
+ time.sleep(1)
+ basedir = tempfile.TemporaryDirectory(prefix='fontconfig.',
+ suffix='.base')
+ with fctest.sandboxed(basedir.name) as f:
+ for ret, stdout, stderr in f.run_match([]):
+ assert ret == 0, stderr
+ assert "Fontconfig warning" not in stderr, stderr
+ f.logger.info(stdout)
+ f.logger.info(stderr)
+
+ cache_stat2 = [f.stat() for f in fctest.cache_files()]
+ cache_files2 = [f for f in fctest.cache_files()]
+ assert len(cache_stat2) == 1, cache_stat2
+ assert cache_files1 == cache_files2, cache_files2
+ # ignore st_atime
+ cmp_cache_before = [attrgetter('st_mode', 'st_ino', 'st_dev', 'st_nlink', 'st_uid', 'st_gid', 'st_size', 'st_mtime', 'st_ctime')(st) for st in cache_stat1]
+ cmp_cache_after = [attrgetter('st_mode', 'st_ino', 'st_dev', 'st_nlink', 'st_uid', 'st_gid', 'st_size', 'st_mtime', 'st_ctime')(st) for st in cache_stat2]
+ assert cmp_cache_before != cmp_cache_after
commit 5b040c8224798586beca65d39ea3384309790a5b
Author: Akira TAGOH <akira at tagoh.org>
Date: Fri Jul 18 18:27:36 2025 +0900
meson: Add a missing fontconfig architecture test case
diff --git a/src/meson.build b/src/meson.build
index bf5a781..5a93687 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -70,3 +70,8 @@ lib_fontconfig_kwargs = {
'link_with': [pattern_lib],
}
+fcarch = executable('fcarch', ['fcarch.c', 'fcarch.h', fcstdint_h, fclang_h],
+ include_directories: [incbase, incsrc],
+ c_args: c_args,
+ install: false,
+ install_tag: 'runtime')
More information about the Fontconfig
mailing list