[4/4] rootfs: manually purge locales instead of using localepurge

Message ID 20260915093258.1818604-5-felix.moessbauer@siemens.com
State Under Review
Headers show
Series Rework locale generation | expand

Commit Message

Felix Moessbauer Sept. 15, 2026, 9:32 a.m. UTC
Using localepurge is problematic as this package first needs to be
installed and later removed from the rootfs again. This is time
consuming and leaves traces in the rootfs.

Instead, we use a dpkg config to not extract the files belonging to
locales we don't want to install. This has the additional benefit of
extracting less, making the overall rootfs install faster. As we no
longer install the localepurge package, less needs to be downloaded and
no traces are left in the rootfs.

Signed-off-by: Felix Moessbauer <felix.moessbauer@siemens.com>
---
 .../image-locales-extension.bbclass           | 143 ++++++++++--------
 1 file changed, 79 insertions(+), 64 deletions(-)

Patch

diff --git a/meta/classes-recipe/image-locales-extension.bbclass b/meta/classes-recipe/image-locales-extension.bbclass
index 66f3ac8c..02e6fb19 100644
--- a/meta/classes-recipe/image-locales-extension.bbclass
+++ b/meta/classes-recipe/image-locales-extension.bbclass
@@ -21,84 +21,108 @@  def get_locale_gen(d, sep='\n'):
                                for i in locale_gen.split('\\n')
                                if i.strip())))
 
-def get_nopurge(d):
+def get_locale_dirs(d):
+    # Directory names below /usr/share/locale that shall be kept, derived from
+    # the language and language_territory parts of LOCALE_GEN.
     locale_gen = d.getVar("LOCALE_GEN") or ""
-    return '\n'.join(sorted(set(i.strip()
-                                for j in locale_gen.split('\\n')
-                                if j.strip()
-                                for i in (j.split()[0].split("_")[0],
-                                          j.split()[0].split(".")[0],
-                                          j.split()[0]))))
-
-ROOTFS_INSTALL_COMMAND_BEFORE_EXPORT += "image_install_localepurge_download"
-image_install_localepurge_download[weight] = "40"
-image_install_localepurge_download[network] = "${TASK_USE_NETWORK_AND_SUDO}"
-image_install_localepurge_download() {
-    run_privileged_heredoc <<'EOF'
-    set -e
-    ${@insert_isar_mounts(d, d.getVar('ROOTFSDIR'), d.getVar('ROOTFS_MOUNTS') if d.getVar('ISAR_CHROOT_MODE') == 'unshare' else '')}
-    chroot ${ROOTFSDIR} \
-        /usr/bin/apt-get ${ROOTFS_APT_ARGS} -o Debug::NoLocking=1 --download-only localepurge
-EOF
-}
+    return sorted(set(i
+                      for j in locale_gen.split('\\n')
+                      if j.strip()
+                      for i in (j.split()[0].split("_")[0],
+                                j.split()[0].split(".")[0])))
+
+def get_locale_path_include(d):
+    lines = []
+    for name in get_locale_dirs(d):
+        lines.append("path-include=/usr/share/locale/%s" % name)
+        lines.append("path-include=/usr/share/locale/%s/*" % name)
+    return "\n".join(lines)
+
+# Configure dpkg to only unpack the requested locales, so unneeded locale files
+# are never installed. This runs before any package is installed into the image.
+# Locale files that the bootstrap already installed are cleaned up here as well.
+ROOTFS_CONFIGURE_COMMAND += "image_configure_locale_filter"
+image_configure_locale_filter[weight] = "5"
+image_configure_locale_filter() {
+    cat<<__EOF__ > ${WORKDIR}/locale.dpkg-filter
+path-exclude=/usr/share/locale/*
+path-include=/usr/share/locale/locale.alias
+${@get_locale_path_include(d)}
+__EOF__
 
-ROOTFS_INSTALL_COMMAND += "image_install_localepurge_install"
-image_install_localepurge_install[weight] = "700"
-image_install_localepurge_install[network] = "${TASK_USE_NETWORK_AND_SUDO}"
-image_install_localepurge_install() {
+    run_privileged_heredoc <<'EOSUDO'
+        set -e
 
-    # Generate locale and localepurge configuration:
-    cat<<__EOF__ > ${WORKDIR}/locale.gen
-${@get_locale_gen(d)}
-__EOF__
+        mkdir -p '${ROOTFSDIR}/etc/dpkg/dpkg.cfg.d'
+        cat '${WORKDIR}/locale.dpkg-filter' \
+            > '${ROOTFSDIR}/etc/dpkg/dpkg.cfg.d/50isar-locales'
+
+        # Drop locale files the bootstrap installed that are not requested.
+        if [ -d '${ROOTFSDIR}/usr/share/locale' ]; then
+            keep=' locale.alias ${@' '.join(get_locale_dirs(d))} '
+            for entry in '${ROOTFSDIR}'/usr/share/locale/*; do
+                [ -e "$entry" ] || continue
+                name=$(basename "$entry")
+                case "$keep" in
+                    *" $name "*) ;;
+                    *) rm -rf "$entry" ;;
+                esac
+            done
+        fi
+EOSUDO
+}
+
+# Preseed the debconf selection and generate the requested locales up front,
+# before any package is installed, so package maintainer scripts find working
+# locales.
+ROOTFS_CONFIGURE_COMMAND += "image_configure_locale_debconf"
+image_configure_locale_debconf[weight] = "5"
+image_configure_locale_debconf() {
     cat<<__EOF__ > ${WORKDIR}/locale.debconf
 locales     locales/locales_to_be_generated    multiselect ${@get_locale_gen(d, ', ')}
 locales     locales/default_environment_locale select      ${LOCALE_DEFAULT}
+__EOF__
+    cat<<__EOF__ > ${WORKDIR}/locale.gen
+${@get_locale_gen(d)}
 __EOF__
     cat<<__EOF__ > ${WORKDIR}/locale.default
 LANG=${LOCALE_DEFAULT}
-__EOF__
-    cat<<__EOF__ > ${WORKDIR}/locale.nopurge
-#USE_DPKG
-MANDELETE
-DONTBOTHERNEWLOCALE
-#SHOWFREEDSPACE
-#QUICKNDIRTYCALC
-#VERBOSE
-${@get_nopurge(d)}
 __EOF__
 
-    # Install configuration into image:
     run_privileged_heredoc <<'EOSUDO'
         set -e
 
         ${@insert_isar_mounts(d, d.getVar('ROOTFSDIR'), '')}
 
-        localepurge_state='i'
-        if chroot '${ROOTFSDIR}' dpkg -s localepurge 2>/dev/null >&2
-        then
-            echo 'localepurge was installed (leaving it installed later)'
-        else
-            localepurge_state='p'
-            echo 'localepurge was not installed (removing it later)'
-            # track additional packages that will be installed, as these packages might be
-            # in the suggested set of other packages and by that need to be explicitly removed
-            localepurge_pkgs=$(chroot '${ROOTFSDIR}' apt-get ${ROOTFS_APT_ARGS} -s localepurge 2>&1 | sed -n 's/^Inst \([^ ]*\) .*/\1/p')
-            chroot '${ROOTFSDIR}' apt-get ${ROOTFS_APT_ARGS} localepurge
-        fi
-
-        cat '${WORKDIR}/locale.gen' >> '${ROOTFSDIR}/etc/locale.gen'
         cat '${WORKDIR}/locale.default' > '${ROOTFSDIR}/etc/default/locale'
-        cat '${WORKDIR}/locale.nopurge' > '${ROOTFSDIR}/etc/locale.nopurge'
         cat '${WORKDIR}/locale.debconf' > '${ROOTFSDIR}/tmp/locale.debconf'
 
-        # Enter image and trigger locales config and localepurge:
+        # Enable the requested locales by uncommenting them in /etc/locale.gen
+        while read -r locale; do
+            [ -n "$locale" ] || continue
+            sed -i "/$locale/s/^# *//" '${ROOTFSDIR}/etc/locale.gen'
+        done < '${WORKDIR}/locale.gen'
+
         chroot '${ROOTFSDIR}' /bin/sh <<'EOSH'
             set -e
 
-            echo 'running locale debconf-set-selections'
             debconf-set-selections /tmp/locale.debconf
-            rm -f '/tmp/locale.debconf'
+            rm -f /tmp/locale.debconf
+EOSH
+EOSUDO
+}
+
+# The systemd locale.conf symlink can only be created once systemd is installed.
+ROOTFS_INSTALL_COMMAND += "image_configure_locales"
+image_configure_locales[weight] = "100"
+image_configure_locales() {
+    run_privileged_heredoc <<'EOSUDO'
+        set -e
+
+        ${@insert_isar_mounts(d, d.getVar('ROOTFSDIR'), '')}
+
+        chroot '${ROOTFSDIR}' /bin/sh <<'EOSH'
+            set -e
 
             SYSTEMD_VERSION=$(dpkg-query \
                 --showformat='${source:Upstream-Version}' \
@@ -112,15 +136,6 @@  __EOF__
 
             echo 'reconfigure locales'
             dpkg-reconfigure -f noninteractive locales
-
-            echo 'running localepurge'
-            localepurge
 EOSH
-
-        if [ "$localepurge_state" = 'p' ]
-        then
-            echo removing localepurge...
-            chroot '${ROOTFSDIR}' apt-get purge --yes $localepurge_pkgs
-        fi
 EOSUDO
 }