Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package melange for openSUSE:Factory checked 
in at 2026-01-26 11:06:45
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/melange (Old)
 and      /work/SRC/openSUSE:Factory/.melange.new.1928 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "melange"

Mon Jan 26 11:06:45 2026 rev:133 rq:1329121 version:0.40.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/melange/melange.changes  2026-01-21 
14:18:44.678339525 +0100
+++ /work/SRC/openSUSE:Factory/.melange.new.1928/melange.changes        
2026-01-26 11:07:34.923177800 +0100
@@ -1,0 +2,14 @@
+Mon Jan 26 06:21:57 UTC 2026 - Johannes Kastl 
<[email protected]>
+
+- Update to version 0.40.0:
+  * build(deps): bump actions/checkout in the actions group (#2307)
+  * feat(config): point npm_config_cache to /var/cache/melange/npm
+  * feat(config): point COMPOSER_CACHE_DIR to
+    /var/cache/melange/composer
+  * docs(cache): document cache persistence behavior per runner
+  * feat(maven): enable caching for Maven dependencies
+  * feat(qemu): add virtiofs support for cache directory
+  * feat(config): point UV_CACHE_DIR and PIP_CACHE_DIR to
+    /var/cache/melange/ by default (#2305)
+
+-------------------------------------------------------------------

Old:
----
  melange-0.39.0.obscpio

New:
----
  melange-0.40.0.obscpio

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ melange.spec ++++++
--- /var/tmp/diff_new_pack.HrNQNK/_old  2026-01-26 11:07:35.959221038 +0100
+++ /var/tmp/diff_new_pack.HrNQNK/_new  2026-01-26 11:07:35.959221038 +0100
@@ -17,7 +17,7 @@
 
 
 Name:           melange
-Version:        0.39.0
+Version:        0.40.0
 Release:        0
 Summary:        Build APKs from source code
 License:        Apache-2.0

++++++ _service ++++++
--- /var/tmp/diff_new_pack.HrNQNK/_old  2026-01-26 11:07:35.999222707 +0100
+++ /var/tmp/diff_new_pack.HrNQNK/_new  2026-01-26 11:07:36.003222874 +0100
@@ -3,7 +3,7 @@
     <param name="url">https://github.com/chainguard-dev/melange</param>
     <param name="scm">git</param>
     <param name="exclude">.git</param>
-    <param name="revision">v0.39.0</param>
+    <param name="revision">v0.40.0</param>
     <param name="versionformat">@PARENT_TAG@</param>
     <param name="versionrewrite-pattern">v(.*)</param>
     <param name="changesgenerate">enable</param>

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.HrNQNK/_old  2026-01-26 11:07:36.031224042 +0100
+++ /var/tmp/diff_new_pack.HrNQNK/_new  2026-01-26 11:07:36.035224209 +0100
@@ -1,6 +1,6 @@
 <servicedata>
 <service name="tar_scm">
                 <param 
name="url">https://github.com/chainguard-dev/melange</param>
-              <param 
name="changesrevision">3942cd9a018273b6fbbebc4597fdba081c3d750c</param></service></servicedata>
+              <param 
name="changesrevision">2dc47d0666a4a13854174f673303460e7aaf5139</param></service></servicedata>
 (No newline at EOF)
 

++++++ melange-0.39.0.obscpio -> melange-0.40.0.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/melange-0.39.0/docs/BUILD-CACHE.md 
new/melange-0.40.0/docs/BUILD-CACHE.md
--- old/melange-0.39.0/docs/BUILD-CACHE.md      2026-01-20 15:43:08.000000000 
+0100
+++ new/melange-0.40.0/docs/BUILD-CACHE.md      2026-01-23 16:30:52.000000000 
+0100
@@ -11,9 +11,9 @@
 
 ## How to use it
 
-When you run `melange build`, you can specify a cache directory with the 
`--cache-dir` flag. The value you provide here should be a path to a directory 
on your local filesystem. This local directory will be mounted into the build 
workspace (e.g. a running container) at the path `/var/cache/melange` as a 
read/write volume.
+When you run `melange build`, you can specify a cache directory with the 
`--cache-dir` flag. The value you provide here should be a path to a directory 
on your local filesystem. This local directory will be mounted into the build 
workspace (e.g. a running container) at the path `/var/cache/melange`.
 
-This enables you to speed up builds by preloading data into the cache before 
you run `melange build`. You can additionally use this mounted directory to 
persist cache data generated by the build itself, taking advantage of the cache 
in subsequent builds.
+This enables you to speed up builds by preloading data into the cache before 
you run `melange build`. Depending on the runner you use, you may also be able 
to persist cache data generated by the build itself for use in subsequent 
builds (see [Cache Persistence by Runner](#cache-persistence-by-runner) below).
 
 ### Example: Go Modules
 
@@ -43,6 +43,220 @@
       # ...
 ```
 
-Now you're all set! If you've already downloaded the Go modules you need for 
your Go project to your local filesystem, you'll no longer need to wait for 
Melange to download those Go modules during every build. This can significantly 
speed up builds! 
+Now you're all set! If you've already downloaded the Go modules you need for 
your Go project to your local filesystem, you'll no longer need to wait for 
Melange to download those Go modules during every build. This can significantly 
speed up builds!
 
-Keep in mind that because the build cache is a read/write-able mount, 
modifications to data in this directory during a Melange build **will affect** 
your local filesystem.
\ No newline at end of file
+**Note:** Whether modifications to the cache during a build persist to your 
local filesystem depends on which runner you use. See [Cache Persistence by 
Runner](#cache-persistence-by-runner) for details.
+
+### Example: Python UV
+
+If you're using Melange to build a Python project with 
[uv](https://docs.astral.sh/uv/), you can take advantage of melange's built-in 
UV cache support to speed up your builds.
+
+Melange automatically sets the `UV_CACHE_DIR` environment variable to 
`/var/cache/melange/uv` by default. This means you can use the `--cache-dir` 
flag to mount a local directory that will be used as the UV cache:
+
+```shell
+melange build --cache-dir /path/to/your/cache ...
+```
+
+When using a dedicated UV cache directory on your host, you can mount it 
directly:
+
+```shell
+# Create a cache directory for UV
+mkdir -p ~/.cache/melange/uv
+
+# Run melange with the cache directory
+melange build --cache-dir ~/.cache/melange ...
+```
+
+The UV cache will be stored under `/var/cache/melange/uv` inside the build 
environment. If you want to customize this path, you can override it in your 
Melange config:
+
+```yaml
+environment:
+  environment:
+    UV_CACHE_DIR: '/var/cache/melange/uv'   # This is the default
+```
+
+Or set it within a pipeline step:
+
+```yaml
+pipeline:
+  - runs: |
+      UV_CACHE_DIR="/var/cache/melange/uv"
+      uv pip install -r requirements.txt
+```
+
+This caching support helps significantly speed up Python builds that use UV by 
avoiding repeated downloads of packages across builds.
+
+### Example: Python pip
+
+If you're using Melange to build a Python project with the standard `pip` 
package manager, you can take advantage of melange's built-in pip cache support 
to speed up your builds.
+
+Melange automatically sets the `PIP_CACHE_DIR` environment variable to 
`/var/cache/melange/pip` by default. This means you can use the `--cache-dir` 
flag to mount a local directory that will be used as the pip cache:
+
+```shell
+melange build --cache-dir /path/to/your/cache ...
+```
+
+When using a dedicated pip cache directory on your host, you can mount it 
directly:
+
+```shell
+# Create a cache directory
+mkdir -p ~/.cache/melange
+
+# Run melange with the cache directory
+melange build --cache-dir ~/.cache/melange ...
+```
+
+The pip cache will be stored under `/var/cache/melange/pip` inside the build 
environment. If you want to customize this path, you can override it in your 
Melange config:
+
+```yaml
+environment:
+  environment:
+    PIP_CACHE_DIR: '/var/cache/melange/pip'   # This is the default
+```
+
+Or set it within a pipeline step:
+
+```yaml
+pipeline:
+  - runs: |
+      PIP_CACHE_DIR="/var/cache/melange/pip"
+      pip install -r requirements.txt
+```
+
+This caching support helps significantly speed up Python builds by avoiding 
repeated downloads of packages across builds.
+
+### Example: PHP Composer
+
+If you're using Melange to build a PHP project with 
[Composer](https://getcomposer.org/), you can take advantage of melange's 
built-in Composer cache support to speed up your builds.
+
+Melange automatically sets the `COMPOSER_CACHE_DIR` environment variable to 
`/var/cache/melange/composer` by default. This means you can use the 
`--cache-dir` flag to mount a local directory that will be used as the Composer 
cache:
+
+```shell
+melange build --cache-dir /path/to/your/cache ...
+```
+
+When using a dedicated Composer cache directory on your host, you can mount it 
directly:
+
+```shell
+# Create a cache directory
+mkdir -p ~/.cache/melange
+
+# Run melange with the cache directory
+melange build --cache-dir ~/.cache/melange ...
+```
+
+The Composer cache will be stored under `/var/cache/melange/composer` inside 
the build environment. If you want to customize this path, you can override it 
in your Melange config:
+
+```yaml
+environment:
+  environment:
+    COMPOSER_CACHE_DIR: '/var/cache/melange/composer'   # This is the default
+```
+
+Or set it within a pipeline step:
+
+```yaml
+pipeline:
+  - runs: |
+      COMPOSER_CACHE_DIR="/var/cache/melange/composer"
+      composer install
+```
+
+This caching support helps significantly speed up PHP builds by avoiding 
repeated downloads of packages across builds.
+
+### Example: npm
+
+If you're using Melange to build a JavaScript/Node.js project with 
[npm](https://www.npmjs.com/), you can take advantage of melange's built-in npm 
cache support to speed up your builds.
+
+Melange automatically sets the `npm_config_cache` environment variable to 
`/var/cache/melange/npm` by default. This means you can use the `--cache-dir` 
flag to mount a local directory that will be used as the npm cache:
+
+```shell
+melange build --cache-dir /path/to/your/cache ...
+```
+
+When using a dedicated npm cache directory on your host, you can mount it 
directly:
+
+```shell
+# Create a cache directory
+mkdir -p ~/.cache/melange
+
+# Run melange with the cache directory
+melange build --cache-dir ~/.cache/melange ...
+```
+
+The npm cache will be stored under `/var/cache/melange/npm` inside the build 
environment. If you want to customize this path, you can override it in your 
Melange config:
+
+```yaml
+environment:
+  environment:
+    npm_config_cache: '/var/cache/melange/npm'   # This is the default
+```
+
+Or set it within a pipeline step:
+
+```yaml
+pipeline:
+  - runs: |
+      npm_config_cache="/var/cache/melange/npm"
+      npm install
+```
+
+This caching support helps significantly speed up Node.js builds by avoiding 
repeated downloads of packages across builds.
+
+### Example: Maven Dependencies
+
+Maven caching is automatically enabled when using the `maven/configure-mirror` 
or `maven/pombump` pipelines. When a cache directory is mounted at 
`/var/cache/melange`, the pipelines automatically configure Maven to use 
`/var/cache/melange/m2repository` as the local repository.
+
+To use Maven caching, simply provide a cache directory:
+
+```shell
+melange build --cache-dir /path/to/my/cache ...
+```
+
+No additional configuration is required in your Melange config. The Maven 
pipelines detect the mounted cache directory and configure the local repository 
path automatically. This is useful for Java projects with many dependencies 
(e.g., apicurio-registry requires over 1 GB of dependencies).
+
+On subsequent builds, Maven will reuse the downloaded dependencies from the 
cache, avoiding redundant downloads.
+
+## Cache Persistence by Runner
+
+The cache directory mount behavior varies depending on which runner you use:
+
+| Runner | Mount Type | Writes Persist to Host |
+|--------|------------|------------------------|
+| Docker | Bind mount (read-write) | Yes |
+| Bubblewrap | Bind mount (read-write) | Yes |
+| QEMU (default) | 9p (read-only) + overlay | No |
+| QEMU (virtiofs) | virtiofs (read-write) | Yes |
+
+### Docker and Bubblewrap
+
+Both Docker and Bubblewrap mount the cache directory as a standard read-write 
bind mount. Any modifications made to `/var/cache/melange` during the build 
**will directly affect** your local filesystem. This allows builds to populate 
the cache for use in subsequent builds.
+
+### QEMU (default, without virtiofs)
+
+By default, QEMU mounts the cache directory using the 9p protocol with a 
read-only flag. To allow builds to write to the cache, an overlay filesystem is 
layered on top:
+
+- **Lower layer:** Read-only 9p mount of your host cache directory
+- **Upper layer:** Temporary writable directory inside the guest
+
+This means builds can read from your pre-populated cache, but any writes 
during the build go to the overlay's upper directory and **are discarded** when 
the build completes. To persist cache writes with QEMU, enable virtiofs (see 
below).
+
+## QEMU Runner: virtiofs for Cache Directory
+
+When using the QEMU runner, the default 9p mount does not persist cache writes 
to the host. To enable cache persistence (and improve I/O performance), you can 
use virtiofs instead.
+
+To enable virtiofs for the cache directory, set the `QEMU_USE_VIRTIOFS` 
environment variable:
+
+```shell
+QEMU_USE_VIRTIOFS=1 melange build --runner qemu --cache-dir /path/to/cache ...
+```
+
+**Requirements:**
+- The `virtiofsd` binary must be available on the host system (checked at 
`/usr/libexec/virtiofsd`, `/usr/lib/qemu/virtiofsd`, or in `$PATH`). 
Alternatively, set `QEMU_VIRTIOFS_PATH` to a directory containing the 
`virtiofsd` binary (useful for macOS/brew or non-standard installations).
+- The host system must support virtiofs (Linux with appropriate kernel support)
+
+When virtiofs is enabled, the cache directory is mounted as a read-write 
virtiofs share, providing:
+- **Cache persistence:** Writes during the build are saved to your host 
filesystem
+- **Better I/O performance:** virtiofs offers improved performance compared to 
9p
+
+If `QEMU_USE_VIRTIOFS=1` is set but `virtiofsd` is not found, melange will 
return an error. If the environment variable is not set or set to `0`, the 
default 9p+overlay mount is used and cache writes are not persisted.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/melange-0.39.0/pkg/build/build_test.go 
new/melange-0.40.0/pkg/build/build_test.go
--- old/melange-0.39.0/pkg/build/build_test.go  2026-01-20 15:43:08.000000000 
+0100
+++ new/melange-0.40.0/pkg/build/build_test.go  2026-01-23 16:30:52.000000000 
+0100
@@ -203,9 +203,13 @@
                                },
                                Environment: apko_types.ImageConfiguration{
                                        Environment: map[string]string{
-                                               "GOMODCACHE": 
"/var/cache/melange/gomodcache",
-                                               "HOME":       
"/home/build/special-case",
-                                               "GOPATH":     
"/var/cache/melange/go",
+                                               "GOMODCACHE":         
"/var/cache/melange/gomodcache",
+                                               "HOME":               
"/home/build/special-case",
+                                               "GOPATH":             
"/var/cache/melange/go",
+                                               "UV_CACHE_DIR":       
"/var/cache/melange/uv",
+                                               "PIP_CACHE_DIR":      
"/var/cache/melange/pip",
+                                               "COMPOSER_CACHE_DIR": 
"/var/cache/melange/composer",
+                                               "npm_config_cache":   
"/var/cache/melange/npm",
                                        },
                                        Accounts: apko_types.ImageAccounts{
                                                Users:  
[]apko_types.User{{UserName: buildUser, UID: 1000, GID: 
apko_types.GID(&gid1000)}},
@@ -294,9 +298,13 @@
                Members:   []string{buildUser},
        }}
        expected.Environment.Environment = map[string]string{
-               "HOME":       "/home/build",
-               "GOPATH":     "/home/build/.cache/go",
-               "GOMODCACHE": "/var/cache/melange/gomodcache",
+               "HOME":               "/home/build",
+               "GOPATH":             "/home/build/.cache/go",
+               "GOMODCACHE":         "/var/cache/melange/gomodcache",
+               "UV_CACHE_DIR":       "/var/cache/melange/uv",
+               "PIP_CACHE_DIR":      "/var/cache/melange/pip",
+               "COMPOSER_CACHE_DIR": "/var/cache/melange/composer",
+               "npm_config_cache":   "/var/cache/melange/npm",
        }
 
        f := filepath.Join(t.TempDir(), "config")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/melange-0.39.0/pkg/build/pipelines/maven/configure-mirror.yaml 
new/melange-0.40.0/pkg/build/pipelines/maven/configure-mirror.yaml
--- old/melange-0.39.0/pkg/build/pipelines/maven/configure-mirror.yaml  
2026-01-20 15:43:08.000000000 +0100
+++ new/melange-0.40.0/pkg/build/pipelines/maven/configure-mirror.yaml  
2026-01-23 16:30:52.000000000 +0100
@@ -4,10 +4,19 @@
     - busybox
 pipeline:
   - runs: |
+      # Use melange's build cache for downloaded dependencies if mounted from 
host
+      MAVEN_LOCAL_REPO="/var/cache/melange/m2repository"
+      LOCAL_REPO_CONFIG=""
+      if [ -d "/var/cache/melange" ]; then
+        mkdir -p "$MAVEN_LOCAL_REPO"
+        
LOCAL_REPO_CONFIG="<localRepository>${MAVEN_LOCAL_REPO}</localRepository>"
+      fi
+
       # Maven checks $USER/.m2, we set $HOME to /home/build but it hardcodes 
$USER somehow
       mkdir -p /root/.m2
       cat > /root/.m2/settings.xml <<EOF
         <settings>
+          ${LOCAL_REPO_CONFIG}
           <mirrors>
             <mirror>
               <id>google-maven-central</id>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/melange-0.39.0/pkg/build/pipelines/maven/pombump.yaml 
new/melange-0.40.0/pkg/build/pipelines/maven/pombump.yaml
--- old/melange-0.39.0/pkg/build/pipelines/maven/pombump.yaml   2026-01-20 
15:43:08.000000000 +0100
+++ new/melange-0.40.0/pkg/build/pipelines/maven/pombump.yaml   2026-01-23 
16:30:52.000000000 +0100
@@ -33,6 +33,18 @@
 
 pipeline:
   - runs: |
+      # Use melange's build cache for downloaded dependencies if mounted from 
host
+      MAVEN_LOCAL_REPO="/var/cache/melange/m2repository"
+      if [ -d "/var/cache/melange" ]; then
+        mkdir -p "$MAVEN_LOCAL_REPO"
+        mkdir -p /root/.m2
+        cat > /root/.m2/settings.xml <<EOF
+      <settings>
+        <localRepository>${MAVEN_LOCAL_REPO}</localRepository>
+      </settings>
+      EOF
+      fi
+
       PATCH_FILE_FLAG=""
       PROPERTIES_FILE_FLAG=""
       DEPENDENCIES_FLAG=""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/melange-0.39.0/pkg/config/config.go 
new/melange-0.40.0/pkg/config/config.go
--- old/melange-0.39.0/pkg/config/config.go     2026-01-20 15:43:08.000000000 
+0100
+++ new/melange-0.40.0/pkg/config/config.go     2026-01-23 16:30:52.000000000 
+0100
@@ -1691,9 +1691,13 @@
        }
 
        const (
-               defaultEnvVarHOME       = "/home/build"
-               defaultEnvVarGOPATH     = "/home/build/.cache/go"
-               defaultEnvVarGOMODCACHE = "/var/cache/melange/gomodcache"
+               defaultEnvVarHOME             = "/home/build"
+               defaultEnvVarGOPATH           = "/home/build/.cache/go"
+               defaultEnvVarGOMODCACHE       = "/var/cache/melange/gomodcache"
+               defaultEnvVarUVCACHEDIR       = "/var/cache/melange/uv"
+               defaultEnvVarPIPCACHEDIR      = "/var/cache/melange/pip"
+               defaultEnvVarCOMPOSERCACHEDIR = "/var/cache/melange/composer"
+               defaultEnvVarNPMCACHE         = "/var/cache/melange/npm"
        )
 
        setIfEmpty := func(key, value string) {
@@ -1705,6 +1709,10 @@
        setIfEmpty("HOME", defaultEnvVarHOME)
        setIfEmpty("GOPATH", defaultEnvVarGOPATH)
        setIfEmpty("GOMODCACHE", defaultEnvVarGOMODCACHE)
+       setIfEmpty("UV_CACHE_DIR", defaultEnvVarUVCACHEDIR)
+       setIfEmpty("PIP_CACHE_DIR", defaultEnvVarPIPCACHEDIR)
+       setIfEmpty("COMPOSER_CACHE_DIR", defaultEnvVarCOMPOSERCACHEDIR)
+       setIfEmpty("npm_config_cache", defaultEnvVarNPMCACHE)
 
        if err := cfg.applySubstitutionsForProvides(); err != nil {
                return nil, err
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/melange-0.39.0/pkg/config/config_test.go 
new/melange-0.40.0/pkg/config/config_test.go
--- old/melange-0.39.0/pkg/config/config_test.go        2026-01-20 
15:43:08.000000000 +0100
+++ new/melange-0.40.0/pkg/config/config_test.go        2026-01-23 
16:30:52.000000000 +0100
@@ -1913,6 +1913,133 @@
        }
 }
 
+func TestDefaultEnvironmentVariables(t *testing.T) {
+       ctx := slogtest.Context(t)
+
+       tests := []struct {
+               name          string
+               yaml          string
+               expectedEnv   map[string]string
+               unexpectedEnv []string
+       }{
+               {
+                       name: "defaults are set when no environment specified",
+                       yaml: `
+package:
+  name: test-pkg
+  version: 1.0.0
+  epoch: 0
+`,
+                       expectedEnv: map[string]string{
+                               "HOME":               "/home/build",
+                               "GOPATH":             "/home/build/.cache/go",
+                               "GOMODCACHE":         
"/var/cache/melange/gomodcache",
+                               "UV_CACHE_DIR":       "/var/cache/melange/uv",
+                               "PIP_CACHE_DIR":      "/var/cache/melange/pip",
+                               "COMPOSER_CACHE_DIR": 
"/var/cache/melange/composer",
+                               "npm_config_cache":   "/var/cache/melange/npm",
+                       },
+               },
+               {
+                       name: "UV_CACHE_DIR can be overridden",
+                       yaml: `
+package:
+  name: test-pkg
+  version: 1.0.0
+  epoch: 0
+environment:
+  environment:
+    UV_CACHE_DIR: '/custom/uv/cache'
+`,
+                       expectedEnv: map[string]string{
+                               "HOME":               "/home/build",
+                               "GOPATH":             "/home/build/.cache/go",
+                               "GOMODCACHE":         
"/var/cache/melange/gomodcache",
+                               "UV_CACHE_DIR":       "/custom/uv/cache",
+                               "PIP_CACHE_DIR":      "/var/cache/melange/pip",
+                               "COMPOSER_CACHE_DIR": 
"/var/cache/melange/composer",
+                               "npm_config_cache":   "/var/cache/melange/npm",
+                       },
+               },
+               {
+                       name: "all cache env vars can be overridden",
+                       yaml: `
+package:
+  name: test-pkg
+  version: 1.0.0
+  epoch: 0
+environment:
+  environment:
+    HOME: '/custom/home'
+    GOPATH: '/custom/gopath'
+    GOMODCACHE: '/custom/gomodcache'
+    UV_CACHE_DIR: '/custom/uv'
+    PIP_CACHE_DIR: '/custom/pip'
+    COMPOSER_CACHE_DIR: '/custom/composer'
+    npm_config_cache: '/custom/npm'
+`,
+                       expectedEnv: map[string]string{
+                               "HOME":               "/custom/home",
+                               "GOPATH":             "/custom/gopath",
+                               "GOMODCACHE":         "/custom/gomodcache",
+                               "UV_CACHE_DIR":       "/custom/uv",
+                               "PIP_CACHE_DIR":      "/custom/pip",
+                               "COMPOSER_CACHE_DIR": "/custom/composer",
+                               "npm_config_cache":   "/custom/npm",
+                       },
+               },
+               {
+                       name: "additional env vars do not affect defaults",
+                       yaml: `
+package:
+  name: test-pkg
+  version: 1.0.0
+  epoch: 0
+environment:
+  environment:
+    MY_CUSTOM_VAR: 'custom_value'
+`,
+                       expectedEnv: map[string]string{
+                               "HOME":               "/home/build",
+                               "GOPATH":             "/home/build/.cache/go",
+                               "GOMODCACHE":         
"/var/cache/melange/gomodcache",
+                               "UV_CACHE_DIR":       "/var/cache/melange/uv",
+                               "PIP_CACHE_DIR":      "/var/cache/melange/pip",
+                               "COMPOSER_CACHE_DIR": 
"/var/cache/melange/composer",
+                               "npm_config_cache":   "/var/cache/melange/npm",
+                               "MY_CUSTOM_VAR":      "custom_value",
+                       },
+               },
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       fp := filepath.Join(t.TempDir(), "test-config.yaml")
+                       if err := os.WriteFile(fp, []byte(tt.yaml), 0o644); err 
!= nil {
+                               t.Fatal(err)
+                       }
+
+                       cfg, err := ParseConfiguration(ctx, fp)
+                       require.NoError(t, err)
+
+                       for key, expectedValue := range tt.expectedEnv {
+                               actualValue, exists := 
cfg.Environment.Environment[key]
+                               if !exists {
+                                       t.Errorf("expected environment variable 
%q to be set", key)
+                                       continue
+                               }
+                               require.Equal(t, expectedValue, actualValue, 
"environment variable %q mismatch", key)
+                       }
+
+                       for _, key := range tt.unexpectedEnv {
+                               if _, exists := 
cfg.Environment.Environment[key]; exists {
+                                       t.Errorf("unexpected environment 
variable %q is set", key)
+                               }
+                       }
+               })
+       }
+}
+
 func TestLicensingInfosWithValidation(t *testing.T) {
        // Create a temp directory with a license file
        tmpDir := t.TempDir()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/melange-0.39.0/pkg/container/config.go 
new/melange-0.40.0/pkg/container/config.go
--- old/melange-0.39.0/pkg/container/config.go  2026-01-20 15:43:08.000000000 
+0100
+++ new/melange-0.40.0/pkg/container/config.go  2026-01-23 16:30:52.000000000 
+0100
@@ -75,4 +75,9 @@
        SSHControlClient         *ssh.Client // SSH client for unrestricted 
control environment, has privileges
        QemuPID                  int
        RunAsGID                 string
+
+       // Virtiofs-related fields for cache directory
+       VirtiofsEnabled     bool   // Whether virtiofs is enabled for cache
+       VirtiofsdPID        int    // PID of virtiofsd daemon for cleanup
+       VirtiofsdSocketPath string // Path to Unix socket for virtiofsd
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/melange-0.39.0/pkg/container/qemu_runner.go 
new/melange-0.40.0/pkg/container/qemu_runner.go
--- old/melange-0.39.0/pkg/container/qemu_runner.go     2026-01-20 
15:43:08.000000000 +0100
+++ new/melange-0.40.0/pkg/container/qemu_runner.go     2026-01-23 
16:30:52.000000000 +0100
@@ -390,6 +390,7 @@
        defer os.Remove(cfg.Disk)
        defer os.Remove(cfg.SSHHostKey)
        defer secureDelete(ctx, cfg.InitramfsPath)
+       defer stopVirtiofsd(ctx, cfg)
 
        clog.FromContext(ctx).Info("qemu: sending shutdown signal")
        err := sendSSHCommand(ctx,
@@ -596,6 +597,35 @@
                return err
        }
 
+       // Set up virtiofs for cache directory if enabled and available
+       if cfg.CacheDir != "" {
+               // Ensure the cachedir exists
+               if err := os.MkdirAll(cfg.CacheDir, 0o755); err != nil {
+                       return fmt.Errorf("failed to create shared cachedir: 
%w", err)
+               }
+
+               var err error
+               cfg.VirtiofsEnabled, err = useVirtiofs()
+               if err != nil {
+                       return err
+               }
+
+               if cfg.VirtiofsEnabled {
+                       // Generate a random socket path
+                       var randBytes [8]byte
+                       if _, err := rand.Read(randBytes[:]); err != nil {
+                               return fmt.Errorf("failed to generate random 
bytes for socket path: %w", err)
+                       }
+                       cfg.VirtiofsdSocketPath = filepath.Join(os.TempDir(), 
fmt.Sprintf("melange-virtiofsd-%x.sock", randBytes))
+
+                       virtiofsdCmd, err := startVirtiofsd(ctx, cfg)
+                       if err != nil {
+                               return fmt.Errorf("failed to start virtiofsd: 
%w", err)
+                       }
+                       cfg.VirtiofsdPID = virtiofsdCmd.Process.Pid
+               }
+       }
+
        baseargs := []string{}
        bios := false
        useVM := false
@@ -620,6 +650,12 @@
                "-chardev", "stdio,id=charconsole0",
                "-device", "virtconsole,chardev=charconsole0,id=console0",
        }
+       // Helper to add memory-backend suffix for virtiofs shared memory
+       machineMemorySuffix := ""
+       if cfg.VirtiofsEnabled {
+               machineMemorySuffix = ",memory-backend=mem"
+       }
+
        if useVM {
                // load microvm profile and bios, shave some milliseconds from 
boot
                // using this will make a complete boot->initrd (with working 
network) In ~700ms
@@ -630,7 +666,7 @@
                } {
                        if _, err := os.Stat(p); err == nil && cfg.Arch.ToAPK() 
!= "aarch64" {
                                // only enable pcie for network, enable RTC for 
kernel, disable i8254PIT, i8259PIC and serial port
-                               baseargs = append(baseargs, "-machine", 
"microvm,rtc=on,pcie=on,pit=off,pic=off,isa-serial=on")
+                               baseargs = append(baseargs, "-machine", 
"microvm,rtc=on,pcie=on,pit=off,pic=off,isa-serial=on"+machineMemorySuffix)
                                baseargs = append(baseargs, "-bios", p)
                                // microvm in qemu any version tested will not 
send hvc0/virtconsole to stdout
                                kernelConsole = "console=ttyS0"
@@ -647,9 +683,9 @@
                // if we're on x86 arch, but without microvm machine type, 
let's go to q35
                switch cfg.Arch.ToAPK() {
                case "aarch64":
-                       baseargs = append(baseargs, "-machine", "virt")
+                       baseargs = append(baseargs, "-machine", 
"virt"+machineMemorySuffix)
                case "x86_64":
-                       baseargs = append(baseargs, "-machine", "q35")
+                       baseargs = append(baseargs, "-machine", 
"q35"+machineMemorySuffix)
                default:
                        return fmt.Errorf("unknown architecture: %s", 
cfg.Arch.ToAPK())
                }
@@ -667,7 +703,15 @@
                        mem = memKb
                }
        }
-       baseargs = append(baseargs, "-m", fmt.Sprintf("%dk", mem))
+       // Memory configuration - virtiofs requires shared memory backend
+       if cfg.VirtiofsEnabled {
+               // Round up memory to MiB boundary for memory-backend-memfd 
alignment
+               memMiB := (mem + 1023) / 1024 * 1024
+               baseargs = append(baseargs, "-m", fmt.Sprintf("%dk", memMiB))
+               baseargs = append(baseargs, "-object", 
fmt.Sprintf("memory-backend-memfd,id=mem,size=%dM,share=on", memMiB/1024))
+       } else {
+               baseargs = append(baseargs, "-m", fmt.Sprintf("%dk", mem))
+       }
 
        // default to use all CPUs, if a cpu limit is set, respect it.
        nproc := runtime.NumCPU()
@@ -757,12 +801,19 @@
        baseargs = append(baseargs, "-device", 
"virtio-9p-pci,id=fs100,fsdev=fsdev100,mount_tag=defaultshare")
 
        if cfg.CacheDir != "" {
-               baseargs = append(baseargs, "-fsdev", 
"local,security_model=mapped,id=fsdev101,readonly=on,path="+cfg.CacheDir)
-               baseargs = append(baseargs, "-device", 
"virtio-9p-pci,id=fs101,fsdev=fsdev101,mount_tag=melange_cache")
-
-               // ensure the cachedir exists
-               if err := os.MkdirAll(cfg.CacheDir, 0o755); err != nil {
-                       return fmt.Errorf("failed to create shared cachedir: 
%w", err)
+               if cfg.VirtiofsEnabled {
+                       log.Info("qemu: using virtiofs for cache directory 
(read-write)")
+                       // Chardev for socket communication
+                       baseargs = append(baseargs,
+                               "-chardev",
+                               fmt.Sprintf("socket,id=char_cache,path=%s", 
cfg.VirtiofsdSocketPath))
+                       // vhost-user-fs-pci device
+                       baseargs = append(baseargs, "-device",
+                               
"vhost-user-fs-pci,queue-size=1024,chardev=char_cache,tag=melange_cache")
+               } else {
+                       log.Info("qemu: using 9p for cache directory (read-only 
with overlay)")
+                       baseargs = append(baseargs, "-fsdev", 
"local,security_model=mapped,id=fsdev101,readonly=on,path="+cfg.CacheDir)
+                       baseargs = append(baseargs, "-device", 
"virtio-9p-pci,id=fs101,fsdev=fsdev101,mount_tag=melange_cache")
                }
        }
 
@@ -836,6 +887,7 @@
        if err := qemuCmd.Start(); err != nil {
                defer os.Remove(cfg.ImgRef)
                defer os.Remove(cfg.Disk)
+               defer stopVirtiofsd(ctx, cfg)
                return fmt.Errorf("qemu: failed to start qemu command: %w", err)
        }
 
@@ -908,10 +960,12 @@
        case err := <-qemuExit:
                defer os.Remove(cfg.ImgRef)
                defer os.Remove(cfg.Disk)
+               defer stopVirtiofsd(ctx, cfg)
                return fmt.Errorf("qemu: VM exited unexpectedly: %w", err)
        case <-ctx.Done():
                defer os.Remove(cfg.ImgRef)
                defer os.Remove(cfg.Disk)
+               defer stopVirtiofsd(ctx, cfg)
                return fmt.Errorf("qemu: context canceled while waiting for VM 
to start")
        }
 
@@ -940,33 +994,52 @@
        clog.FromContext(ctx).Infof("qemu: running kernel version: %s", kv)
 
        if cfg.CacheDir != "" {
-               clog.FromContext(ctx).Infof("qemu: setting up melange cachedir: 
%s", cfg.CacheDir)
-               setupMountCommand := fmt.Sprintf(
-                       "mkdir -p %s %s /mount/upper /mount/work && "+
-                               "chmod 1777 /mount/upper && "+
-                               "mount -t 9p -o ro melange_cache %s && "+
-                               "mount -t overlay overlay -o 
lowerdir=%s,upperdir=/mount/upper,workdir=/mount/work %s",
-                       DefaultCacheDir,
-                       filepath.Join("/mount", DefaultCacheDir),
-                       DefaultCacheDir,
-                       DefaultCacheDir,
-                       filepath.Join("/mount", DefaultCacheDir),
-               )
-               if setupMountCommand != ": " {
-                       err = sendSSHCommand(ctx,
-                               cfg.SSHControlClient,
-                               cfg,
-                               nil,
-                               stderr,
-                               stdout,
-                               false,
-                               []string{"sh", "-c", setupMountCommand},
+               var setupMountCommand string
+
+               if cfg.VirtiofsEnabled {
+                       // Virtiofs: read-write mount at the chroot path
+                       // The build runs chrooted at /mount/, so we mount at 
/mount/var/cache/melange
+                       // which appears as /var/cache/melange inside the chroot
+                       chrootCacheDir := filepath.Join("/mount", 
DefaultCacheDir)
+                       clog.FromContext(ctx).Infof("qemu: setting up virtiofs 
cache mount (read-write): %s -> %s", cfg.CacheDir, chrootCacheDir)
+                       setupMountCommand = fmt.Sprintf(
+                               "mkdir -p %s && mount -t virtiofs melange_cache 
%s",
+                               chrootCacheDir,
+                               chrootCacheDir,
+                       )
+               } else {
+                       // 9p: readonly with overlay for write support
+                       clog.FromContext(ctx).Infof("qemu: setting up 9p cache 
mount (read-only with overlay): %s", cfg.CacheDir)
+                       setupMountCommand = fmt.Sprintf(
+                               "mkdir -p %s %s /mount/upper /mount/work && "+
+                                       "chmod 1777 /mount/upper && "+
+                                       "mount -t 9p -o ro melange_cache %s && 
"+
+                                       "mount -t overlay overlay -o 
lowerdir=%s,upperdir=/mount/upper,workdir=/mount/work %s",
+                               DefaultCacheDir,
+                               filepath.Join("/mount", DefaultCacheDir),
+                               DefaultCacheDir,
+                               DefaultCacheDir,
+                               filepath.Join("/mount", DefaultCacheDir),
                        )
+               }
+
+               err = sendSSHCommand(ctx,
+                       cfg.SSHControlClient,
+                       cfg,
+                       nil,
+                       stderr,
+                       stdout,
+                       false,
+                       []string{"sh", "-c", setupMountCommand},
+               )
+               if err != nil {
+                       // Clean up virtiofsd on failure
+                       if cfg.VirtiofsEnabled {
+                               stopVirtiofsd(ctx, cfg)
+                       }
+                       err = qemuCmd.Process.Kill()
                        if err != nil {
-                               err = qemuCmd.Process.Kill()
-                               if err != nil {
-                                       return err
-                               }
+                               return err
                        }
                }
        }
@@ -1675,6 +1748,133 @@
        return l.Addr().(*net.TCPAddr).Port, nil
 }
 
+// virtiofsdSearchPaths defines where to look for the virtiofsd binary.
+var virtiofsdSearchPaths = []string{
+       "/usr/libexec/virtiofsd",  // Fedora, RHEL
+       "/usr/lib/qemu/virtiofsd", // Debian, Ubuntu
+       "virtiofsd",               // In PATH
+}
+
+// isVirtiofsdAvailable checks if virtiofsd is installed and returns its path.
+// If QEMU_VIRTIOFS_PATH is set, it will look for virtiofsd in that directory.
+func isVirtiofsdAvailable() (string, bool) {
+       // Check for user-specified directory first (useful for macOS/brew or 
custom installs)
+       if customDir, ok := os.LookupEnv("QEMU_VIRTIOFS_PATH"); ok && customDir 
!= "" {
+               customPath := filepath.Join(customDir, "virtiofsd")
+               if _, err := os.Stat(customPath); err == nil {
+                       return customPath, true
+               }
+       }
+       for _, path := range virtiofsdSearchPaths {
+               if p, err := exec.LookPath(path); err == nil {
+                       return p, true
+               }
+       }
+       return "", false
+}
+
+// useVirtiofs checks if virtiofs should be used based on environment variable.
+// Returns (true, nil) if virtiofs is enabled and available.
+// Returns (false, nil) if virtiofs is not requested.
+// Returns (false, error) if virtiofs is explicitly requested but not 
available.
+func useVirtiofs() (bool, error) {
+       if envVal, ok := os.LookupEnv("QEMU_USE_VIRTIOFS"); ok {
+               if val, err := strconv.ParseBool(envVal); err == nil && val {
+                       if _, available := isVirtiofsdAvailable(); available {
+                               return true, nil
+                       }
+                       return false, fmt.Errorf("QEMU_USE_VIRTIOFS=1 but 
virtiofsd not found (checked %v, or set QEMU_VIRTIOFS_PATH)", 
virtiofsdSearchPaths)
+               }
+       }
+       return false, nil
+}
+
+// startVirtiofsd starts the virtiofsd daemon and returns its process.
+func startVirtiofsd(ctx context.Context, cfg *Config) (*exec.Cmd, error) {
+       log := clog.FromContext(ctx)
+
+       virtiofsdPath, ok := isVirtiofsdAvailable()
+       if !ok {
+               return nil, fmt.Errorf("virtiofsd not found")
+       }
+
+       // Remove stale socket if exists
+       os.Remove(cfg.VirtiofsdSocketPath)
+
+       args := []string{
+               "--socket-path=" + cfg.VirtiofsdSocketPath,
+               fmt.Sprintf("--thread-pool-size=%d", runtime.NumCPU()*2), // 
Parallel I/O
+               "-o", "source=" + cfg.CacheDir,
+               "-o", "cache=always", // Balance coherency and performance
+               "-o", "sandbox=namespace", // Use namespace sandbox (works 
without root)
+               "-o", "xattr", // Enable xattr support
+               "-o", "writeback", // Enable writeback caching for better write 
performance
+               "-o", "no_posix_lock",
+       }
+
+       log.Debugf("starting virtiofsd: %s %v", virtiofsdPath, args)
+
+       // #nosec G204 - virtiofsdPath is from known paths checked by 
isVirtiofsdAvailable
+       cmd := exec.CommandContext(ctx, virtiofsdPath, args...)
+
+       // Capture stderr for debugging
+       errPipe, err := cmd.StderrPipe()
+       if err != nil {
+               return nil, fmt.Errorf("failed to create stderr pipe: %w", err)
+       }
+
+       if err := cmd.Start(); err != nil {
+               return nil, fmt.Errorf("failed to start virtiofsd: %w", err)
+       }
+
+       // Log virtiofsd output in background
+       go func() {
+               scanner := bufio.NewScanner(errPipe)
+               for scanner.Scan() {
+                       log.Debugf("virtiofsd: %s", scanner.Text())
+               }
+       }()
+
+       // Wait for socket to be created (max 5 seconds)
+       for range 50 {
+               if _, err := os.Stat(cfg.VirtiofsdSocketPath); err == nil {
+                       log.Infof("virtiofsd started successfully, socket: %s", 
cfg.VirtiofsdSocketPath)
+                       return cmd, nil
+               }
+               time.Sleep(100 * time.Millisecond)
+       }
+
+       // Socket not created, kill process and return error
+       _ = cmd.Process.Kill()
+       return nil, fmt.Errorf("virtiofsd socket not created within timeout")
+}
+
+// stopVirtiofsd gracefully stops the virtiofsd daemon.
+func stopVirtiofsd(ctx context.Context, cfg *Config) {
+       log := clog.FromContext(ctx)
+
+       if cfg.VirtiofsdPID > 0 {
+               log.Debugf("stopping virtiofsd (PID %d)", cfg.VirtiofsdPID)
+
+               // Send SIGTERM for graceful shutdown
+               if err := syscall.Kill(cfg.VirtiofsdPID, syscall.SIGTERM); err 
!= nil {
+                       log.Warnf("failed to send SIGTERM to virtiofsd: %v", 
err)
+                       // Force kill if SIGTERM fails
+                       _ = syscall.Kill(cfg.VirtiofsdPID, syscall.SIGKILL)
+               }
+
+               cfg.VirtiofsdPID = 0
+       }
+
+       // Clean up socket file
+       if cfg.VirtiofsdSocketPath != "" {
+               if err := os.Remove(cfg.VirtiofsdSocketPath); err != nil && 
!os.IsNotExist(err) {
+                       log.Warnf("failed to remove virtiofsd socket: %v", err)
+               }
+               cfg.VirtiofsdSocketPath = ""
+       }
+}
+
 // zeroSensitiveFields zeros out sensitive cryptographic material from memory 
after it's no longer needed.
 func zeroSensitiveFields(ctx context.Context, cfg *Config) {
        clog.FromContext(ctx).Debug("qemu: zeroing sensitive cryptographic 
material from memory")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/melange-0.39.0/pkg/container/qemu_runner_test.go 
new/melange-0.40.0/pkg/container/qemu_runner_test.go
--- old/melange-0.39.0/pkg/container/qemu_runner_test.go        2026-01-20 
15:43:08.000000000 +0100
+++ new/melange-0.40.0/pkg/container/qemu_runner_test.go        2026-01-23 
16:30:52.000000000 +0100
@@ -291,3 +291,265 @@
                }
        }
 }
+
+func TestVirtiofsdSearchPaths(t *testing.T) {
+       // Verify search paths are defined and non-empty
+       if len(virtiofsdSearchPaths) == 0 {
+               t.Error("virtiofsdSearchPaths is empty")
+       }
+
+       // Verify expected paths are present
+       expectedPaths := map[string]bool{
+               "/usr/libexec/virtiofsd":  false,
+               "/usr/lib/qemu/virtiofsd": false,
+               "virtiofsd":               false,
+       }
+
+       for _, path := range virtiofsdSearchPaths {
+               if _, ok := expectedPaths[path]; ok {
+                       expectedPaths[path] = true
+               }
+       }
+
+       for path, found := range expectedPaths {
+               if !found {
+                       t.Errorf("expected path %q not found in 
virtiofsdSearchPaths", path)
+               }
+       }
+
+       t.Logf("virtiofsdSearchPaths: %v", virtiofsdSearchPaths)
+}
+
+func TestIsVirtiofsdAvailable(t *testing.T) {
+       path, available := isVirtiofsdAvailable()
+
+       if available {
+               // If available, path should be non-empty
+               if path == "" {
+                       t.Error("isVirtiofsdAvailable() returned available=true 
but empty path")
+               }
+               t.Logf("virtiofsd found at: %s", path)
+       } else {
+               // If not available, path should be empty
+               if path != "" {
+                       t.Errorf("isVirtiofsdAvailable() returned 
available=false but non-empty path: %s", path)
+               }
+               t.Log("virtiofsd not found on this system")
+       }
+}
+
+func TestUseVirtiofs(t *testing.T) {
+       // Save original env and restore after test
+       originalEnv, hadEnv := os.LookupEnv("QEMU_USE_VIRTIOFS")
+       defer func() {
+               if hadEnv {
+                       os.Setenv("QEMU_USE_VIRTIOFS", originalEnv)
+               } else {
+                       os.Unsetenv("QEMU_USE_VIRTIOFS")
+               }
+       }()
+
+       tests := []struct {
+               name        string
+               envValue    string
+               envSet      bool
+               expectUse   bool
+               expectError bool
+       }{
+               {
+                       name:        "env not set",
+                       envSet:      false,
+                       expectUse:   false,
+                       expectError: false,
+               },
+               {
+                       name:        "env set to false",
+                       envValue:    "false",
+                       envSet:      true,
+                       expectUse:   false,
+                       expectError: false,
+               },
+               {
+                       name:        "env set to 0",
+                       envValue:    "0",
+                       envSet:      true,
+                       expectUse:   false,
+                       expectError: false,
+               },
+               {
+                       name:        "env set to invalid value",
+                       envValue:    "invalid",
+                       envSet:      true,
+                       expectUse:   false,
+                       expectError: false,
+               },
+               {
+                       name:        "env set to empty string",
+                       envValue:    "",
+                       envSet:      true,
+                       expectUse:   false,
+                       expectError: false,
+               },
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       if tt.envSet {
+                               os.Setenv("QEMU_USE_VIRTIOFS", tt.envValue)
+                       } else {
+                               os.Unsetenv("QEMU_USE_VIRTIOFS")
+                       }
+
+                       use, err := useVirtiofs()
+
+                       if tt.expectError && err == nil {
+                               t.Error("expected error but got nil")
+                       }
+                       if !tt.expectError && err != nil {
+                               t.Errorf("unexpected error: %v", err)
+                       }
+                       if use != tt.expectUse {
+                               t.Errorf("useVirtiofs() = %v, expected %v", 
use, tt.expectUse)
+                       }
+               })
+       }
+}
+
+func TestUseVirtiofs_EnabledWithAvailability(t *testing.T) {
+       // Save original env and restore after test
+       originalEnv, hadEnv := os.LookupEnv("QEMU_USE_VIRTIOFS")
+       defer func() {
+               if hadEnv {
+                       os.Setenv("QEMU_USE_VIRTIOFS", originalEnv)
+               } else {
+                       os.Unsetenv("QEMU_USE_VIRTIOFS")
+               }
+       }()
+
+       // Test with QEMU_USE_VIRTIOFS=1
+       os.Setenv("QEMU_USE_VIRTIOFS", "1")
+
+       _, available := isVirtiofsdAvailable()
+       use, err := useVirtiofs()
+
+       if available {
+               // virtiofsd is available, should return true with no error
+               if err != nil {
+                       t.Errorf("unexpected error when virtiofsd is available: 
%v", err)
+               }
+               if !use {
+                       t.Error("useVirtiofs() = false when virtiofsd is 
available and QEMU_USE_VIRTIOFS=1")
+               }
+               t.Log("virtiofsd available: useVirtiofs returned true")
+       } else {
+               // virtiofsd is not available, should return error
+               if err == nil {
+                       t.Error("expected error when virtiofsd not available 
but QEMU_USE_VIRTIOFS=1")
+               }
+               if use {
+                       t.Error("useVirtiofs() = true when virtiofsd is not 
available")
+               }
+               t.Logf("virtiofsd not available: useVirtiofs returned error: 
%v", err)
+       }
+}
+
+func TestUseVirtiofs_ErrorMessageContainsPaths(t *testing.T) {
+       // Skip if virtiofsd is available (can't test error path)
+       if _, available := isVirtiofsdAvailable(); available {
+               t.Skip("virtiofsd is available, cannot test error message")
+       }
+
+       // Save original env and restore after test
+       originalEnv, hadEnv := os.LookupEnv("QEMU_USE_VIRTIOFS")
+       defer func() {
+               if hadEnv {
+                       os.Setenv("QEMU_USE_VIRTIOFS", originalEnv)
+               } else {
+                       os.Unsetenv("QEMU_USE_VIRTIOFS")
+               }
+       }()
+
+       os.Setenv("QEMU_USE_VIRTIOFS", "1")
+       _, err := useVirtiofs()
+
+       if err == nil {
+               t.Fatal("expected error but got nil")
+       }
+
+       errMsg := err.Error()
+
+       // Error message should mention the search paths
+       for _, path := range virtiofsdSearchPaths {
+               if !contains(errMsg, path) {
+                       t.Errorf("error message should contain path %q: %s", 
path, errMsg)
+               }
+       }
+
+       t.Logf("error message: %s", errMsg)
+}
+
+func TestStopVirtiofsd_NoOp(t *testing.T) {
+       ctx := clog.WithLogger(context.Background(), slogtest.TestLogger(t))
+
+       // Test that stopVirtiofsd doesn't panic with zero values
+       cfg := &Config{}
+       stopVirtiofsd(ctx, cfg)
+
+       // Test with already-zeroed PID
+       cfg.VirtiofsdPID = 0
+       cfg.VirtiofsdSocketPath = ""
+       stopVirtiofsd(ctx, cfg)
+
+       // Should not panic or error
+       t.Log("stopVirtiofsd handled zero-value config correctly")
+}
+
+func TestStopVirtiofsd_CleansUpSocket(t *testing.T) {
+       ctx := clog.WithLogger(context.Background(), slogtest.TestLogger(t))
+
+       // Create a temporary file to simulate a socket
+       tmpFile, err := os.CreateTemp("", "test-virtiofsd-*.sock")
+       if err != nil {
+               t.Fatalf("failed to create temp file: %v", err)
+       }
+       tmpPath := tmpFile.Name()
+       tmpFile.Close()
+
+       // Verify file exists
+       if _, err := os.Stat(tmpPath); os.IsNotExist(err) {
+               t.Fatal("temp file should exist")
+       }
+
+       cfg := &Config{
+               VirtiofsdPID:        0, // No process to kill
+               VirtiofsdSocketPath: tmpPath,
+       }
+
+       stopVirtiofsd(ctx, cfg)
+
+       // Socket path should be cleared
+       if cfg.VirtiofsdSocketPath != "" {
+               t.Errorf("VirtiofsdSocketPath should be cleared, got %q", 
cfg.VirtiofsdSocketPath)
+       }
+
+       // File should be removed
+       if _, err := os.Stat(tmpPath); !os.IsNotExist(err) {
+               t.Error("socket file should be removed")
+               os.Remove(tmpPath) // Clean up
+       }
+}
+
+// contains checks if substr is in s
+func contains(s, substr string) bool {
+       return len(s) >= len(substr) && (s == substr || len(substr) == 0 ||
+               (len(s) > 0 && len(substr) > 0 && findSubstring(s, substr)))
+}
+
+func findSubstring(s, substr string) bool {
+       for i := 0; i <= len(s)-len(substr); i++ {
+               if s[i:i+len(substr)] == substr {
+                       return true
+               }
+       }
+       return false
+}

++++++ melange.obsinfo ++++++
--- /var/tmp/diff_new_pack.HrNQNK/_old  2026-01-26 11:07:36.859258598 +0100
+++ /var/tmp/diff_new_pack.HrNQNK/_new  2026-01-26 11:07:36.863258765 +0100
@@ -1,5 +1,5 @@
 name: melange
-version: 0.39.0
-mtime: 1768920188
-commit: 3942cd9a018273b6fbbebc4597fdba081c3d750c
+version: 0.40.0
+mtime: 1769182252
+commit: 2dc47d0666a4a13854174f673303460e7aaf5139
 

++++++ vendor.tar.gz ++++++
/work/SRC/openSUSE:Factory/melange/vendor.tar.gz 
/work/SRC/openSUSE:Factory/.melange.new.1928/vendor.tar.gz differ: char 133, 
line 3

Reply via email to