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-03-09 16:18:46 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/melange (Old) and /work/SRC/openSUSE:Factory/.melange.new.8177 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "melange" Mon Mar 9 16:18:46 2026 rev:143 rq:1337571 version:0.43.7 Changes: -------- --- /work/SRC/openSUSE:Factory/melange/melange.changes 2026-03-05 17:30:18.868359109 +0100 +++ /work/SRC/openSUSE:Factory/.melange.new.8177/melange.changes 2026-03-09 16:23:42.367575368 +0100 @@ -1,0 +2,18 @@ +Mon Mar 09 07:02:45 UTC 2026 - Johannes Kastl <[email protected]> + +- Update to version 0.43.7: + * build(deps): bump step-security/harden-runner from 2.15.0 to + 2.15.1 in the actions group (#2402) + * build(deps): bump github.com/docker/cli from + 29.2.1+incompatible to 29.3.0+incompatible in the gomod group + (#2401) + * Replace goparsify with recursive descent parser (#2399) + * Re-enable "set -u" in git-checkout, AFTER adding a test for it + (#2398) + * Remove erroneous variable quoting (#2397) + * build(deps): bump docker/setup-docker-action from 4.7.0 to + 5.0.0 (#2393) + * build(deps): bump the gomod group across 1 directory with 4 + updates (#2396) + +------------------------------------------------------------------- Old: ---- melange-0.43.6.obscpio New: ---- melange-0.43.7.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ melange.spec ++++++ --- /var/tmp/diff_new_pack.QDU2vs/_old 2026-03-09 16:23:44.727672212 +0100 +++ /var/tmp/diff_new_pack.QDU2vs/_new 2026-03-09 16:23:44.727672212 +0100 @@ -17,7 +17,7 @@ Name: melange -Version: 0.43.6 +Version: 0.43.7 Release: 0 Summary: Build APKs from source code License: Apache-2.0 ++++++ _service ++++++ --- /var/tmp/diff_new_pack.QDU2vs/_old 2026-03-09 16:23:44.771674018 +0100 +++ /var/tmp/diff_new_pack.QDU2vs/_new 2026-03-09 16:23:44.775674182 +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.43.6</param> + <param name="revision">v0.43.7</param> <param name="versionformat">@PARENT_TAG@</param> <param name="versionrewrite-pattern">v(.*)</param> <param name="changesgenerate">enable</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.QDU2vs/_old 2026-03-09 16:23:44.803675331 +0100 +++ /var/tmp/diff_new_pack.QDU2vs/_new 2026-03-09 16:23:44.807675495 +0100 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/chainguard-dev/melange</param> - <param name="changesrevision">44654e54749503cf5425d09c4460170bf6801575</param></service></servicedata> + <param name="changesrevision">d8dfccb7dcd1156156ea3d11d08c541a7c3c5068</param></service></servicedata> (No newline at EOF) ++++++ melange-0.43.6.obscpio -> melange-0.43.7.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.43.6/go.mod new/melange-0.43.7/go.mod --- old/melange-0.43.6/go.mod 2026-03-04 12:01:24.000000000 +0100 +++ new/melange-0.43.7/go.mod 2026-03-08 16:25:37.000000000 +0100 @@ -8,15 +8,14 @@ github.com/chainguard-dev/go-pkgconfig v0.0.0-20240404163941-6351b37b2a10 github.com/chainguard-dev/yam v0.2.51 github.com/charmbracelet/log v0.4.2 - github.com/docker/cli v29.2.1+incompatible + github.com/docker/cli v29.3.0+incompatible github.com/docker/docker v28.5.2+incompatible github.com/dprotaso/go-yit v0.0.0-20250513224043-18a80f8f6df4 github.com/github/go-spdx/v2 v2.4.0 github.com/go-git/go-git/v5 v5.17.0 github.com/google/go-cmp v0.7.0 - github.com/google/go-containerregistry v0.21.1 + github.com/google/go-containerregistry v0.21.2 github.com/google/licenseclassifier/v2 v2.0.0 - github.com/ijt/goparsify v0.0.0-20221203142333-3a5276334b8d github.com/in-toto/attestation v1.1.2 github.com/invopop/jsonschema v0.13.0 github.com/joho/godotenv v1.5.1 @@ -34,9 +33,9 @@ github.com/ulikunitz/xz v0.5.15 github.com/yookoala/realpath v1.0.0 github.com/zealic/xignore v0.3.3 - go.opentelemetry.io/otel v1.40.0 - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0 - go.opentelemetry.io/otel/sdk v1.40.0 + go.opentelemetry.io/otel v1.41.0 + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.41.0 + go.opentelemetry.io/otel/sdk v1.41.0 go.yaml.in/yaml/v2 v2.4.3 golang.org/x/crypto v0.48.0 golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 @@ -61,6 +60,7 @@ github.com/google/martian/v3 v3.3.3 // indirect github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 // indirect + github.com/ijt/goparsify v0.0.0-20221203142333-3a5276334b8d // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/pavlo-v-chernykh/keystore-go/v4 v4.5.0 // indirect go.opencensus.io v0.24.0 // indirect @@ -164,8 +164,8 @@ go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect - go.opentelemetry.io/otel/metric v1.40.0 // indirect - go.opentelemetry.io/otel/trace v1.40.0 // indirect + go.opentelemetry.io/otel/metric v1.41.0 // indirect + go.opentelemetry.io/otel/trace v1.41.0 // indirect go.step.sm/crypto v0.76.2 // indirect golang.org/x/mod v0.33.0 // indirect golang.org/x/net v0.50.0 // indirect diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.43.6/go.sum new/melange-0.43.7/go.sum --- old/melange-0.43.6/go.sum 2026-03-04 12:01:24.000000000 +0100 +++ new/melange-0.43.7/go.sum 2026-03-08 16:25:37.000000000 +0100 @@ -91,8 +91,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/cli v29.2.1+incompatible h1:n3Jt0QVCN65eiVBoUTZQM9mcQICCJt3akW4pKAbKdJg= -github.com/docker/cli v29.2.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v29.3.0+incompatible h1:z3iWveU7h19Pqx7alZES8j+IeFQZ1lhTwb2F+V9SVvk= +github.com/docker/cli v29.3.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM= @@ -171,8 +171,8 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/go-containerregistry v0.21.1 h1:sOt/o9BS2b87FnR7wxXPvRKU1XVJn2QCwOS5g8zQXlc= -github.com/google/go-containerregistry v0.21.1/go.mod h1:ctO5aCaewH4AK1AumSF5DPW+0+R+d2FmylMJdp5G7p0= +github.com/google/go-containerregistry v0.21.2 h1:vYaMU4nU55JJGFC9JR/s8NZcTjbE9DBBbvusTW9NeS0= +github.com/google/go-containerregistry v0.21.2/go.mod h1:ctO5aCaewH4AK1AumSF5DPW+0+R+d2FmylMJdp5G7p0= github.com/google/go-licenses/v2 v2.0.1 h1:ti+9bi5o7DKbeeg5eBb/uZTgsaPNoJaLCh93cRcXsW8= github.com/google/go-licenses/v2 v2.0.1/go.mod h1:efibo0EDNGkau6AIMOViGW+rTNPudhxX9rCxtfw5zKE= github.com/google/go-replayers/httpreplay v1.2.0 h1:VM1wEyyjaoU53BwrOnaf9VhAyQQEEioJvFYxYcLRKzk= @@ -366,22 +366,22 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= -go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms= -go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g= +go.opentelemetry.io/otel v1.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c= +go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0/go.mod h1:u5BF1xyjstDowA1R5QAO9JHzqK+ublenEW/dyqTjBVk= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0 h1:MzfofMZN8ulNqobCmCAVbqVL5syHw+eB2qPRkCMA/fQ= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0/go.mod h1:E73G9UFtKRXrxhBsHtG00TB5WxX57lpsQzogDkqBTz8= -go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g= -go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc= -go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= -go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= -go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw= -go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg= -go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw= -go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.41.0 h1:61oRQmYGMW7pXmFjPg1Muy84ndqMxQ6SH2L8fBG8fSY= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.41.0/go.mod h1:c0z2ubK4RQL+kSDuuFu9WnuXimObon3IiKjJf4NACvU= +go.opentelemetry.io/otel/metric v1.41.0 h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ= +go.opentelemetry.io/otel/metric v1.41.0/go.mod h1:xPvCwd9pU0VN8tPZYzDZV/BMj9CM9vs00GuBjeKhJps= +go.opentelemetry.io/otel/sdk v1.41.0 h1:YPIEXKmiAwkGl3Gu1huk1aYWwtpRLeskpV+wPisxBp8= +go.opentelemetry.io/otel/sdk v1.41.0/go.mod h1:ahFdU0G5y8IxglBf0QBJXgSe7agzjE4GiTJ6HT9ud90= +go.opentelemetry.io/otel/sdk/metric v1.41.0 h1:siZQIYBAUd1rlIWQT2uCxWJxcCO7q3TriaMlf08rXw8= +go.opentelemetry.io/otel/sdk/metric v1.41.0/go.mod h1:HNBuSvT7ROaGtGI50ArdRLUnvRTRGniSUZbxiWxSO8Y= +go.opentelemetry.io/otel/trace v1.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0= +go.opentelemetry.io/otel/trace v1.41.0/go.mod h1:U1NU4ULCoxeDKc09yCWdWe+3QoyweJcISEVa1RBzOis= go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= go.step.sm/crypto v0.76.2 h1:JJ/yMcs/rmcCAwlo+afrHjq74XBFRTJw5B2y4Q4Z4c4= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.43.6/pkg/build/build_integration_test.go new/melange-0.43.7/pkg/build/build_integration_test.go --- old/melange-0.43.6/pkg/build/build_integration_test.go 2026-03-04 12:01:24.000000000 +0100 +++ new/melange-0.43.7/pkg/build/build_integration_test.go 2026-03-08 16:25:37.000000000 +0100 @@ -40,6 +40,10 @@ expectedVersion: "1.0.0_b6", buildErrMatch: regexp.MustCompile("parse version"), }, + { + name: "sed", + expectedVersion: "4.9-r8", + }, } const arch = "x86_64" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.43.6/pkg/build/pipelines/git-checkout.yaml new/melange-0.43.7/pkg/build/pipelines/git-checkout.yaml --- old/melange-0.43.6/pkg/build/pipelines/git-checkout.yaml 2026-03-04 12:01:24.000000000 +0100 +++ new/melange-0.43.7/pkg/build/pipelines/git-checkout.yaml 2026-03-08 16:25:37.000000000 +0100 @@ -94,7 +94,7 @@ - runs: | #!/bin/sh # shellcheck shell=busybox - set -e + set -eu msg() { echo "[git checkout]" "$@"; } fail() { msg FAIL "$@"; exit 1; } @@ -153,7 +153,7 @@ local fetched_branches="" local sdate=${SOURCE_DATE_EPOCH:-0} if [ "$sdate" -lt 315532800 ]; then - msg "Setting commit date to Jan 1, 1980 (SOURCE_DATE_EPOCH found ${SOURCE_DATE_EPOCH})" + msg "Setting commit date to Jan 1, 1980 (SOURCE_DATE_EPOCH found ${SOURCE_DATE_EPOCH:-(not set)})" sdate=315532800 fi if [ -z "$cpicksf" ]; then @@ -177,7 +177,8 @@ # Split the line into branch/hash and comment parts branch=${line%%:*} comment=${line#*:} - comment=$(set -f; echo "$comment") # Strip leading/trailing whitespace + # shellcheck disable=SC2086 + comment=$(set -f; echo $comment) # Strip leading/trailing whitespace if [ -z "$comment" ]; then msg "Empty comment for cherry-pick: $line" @@ -292,7 +293,7 @@ "--config=user.name=Melange Build" \ "[email protected]" \ $flags \ - ${depthflag:+"$depthflag"} "$repo" "$workdir" || + ${depthflag:+"${depthflag}"} "$repo" "$workdir" || fail "git clone failed after $max_retries retries" vr cd "$workdir" @@ -300,7 +301,8 @@ # Configure sparse-checkout if paths were provided if [ -n "$sparse_paths" ]; then msg "Configuring sparse-checkout with paths: $sparse_paths" - vr git sparse-checkout set --cone "$sparse_paths" || + # shellcheck disable=SC2086 + vr git sparse-checkout set --cone $sparse_paths || fail "failed to configure sparse-checkout --filter=blob:none" fi @@ -335,7 +337,7 @@ # git clone --branch=X will pick the branch X if there # exists both a tag and a branch by that name. # since a tag was given, we want the tag. - vr git fetch $quiet $remote "${depthflag:-"$depthflag"}" --no-tags \ + vr git fetch $quiet $remote ${depthflag:+"${depthflag}"} --no-tags \ "+refs/tags/$tag:refs/$remote/tags/$tag" vr git checkout $quiet "$remote/tags/$tag" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.43.6/pkg/build/testdata/build_configs/sed.yaml new/melange-0.43.7/pkg/build/testdata/build_configs/sed.yaml --- old/melange-0.43.6/pkg/build/testdata/build_configs/sed.yaml 1970-01-01 01:00:00.000000000 +0100 +++ new/melange-0.43.7/pkg/build/testdata/build_configs/sed.yaml 2026-03-08 16:25:37.000000000 +0100 @@ -0,0 +1,63 @@ +package: + name: sed + version: "4.9" + epoch: 8 # go/wolfi-rsc/sed + description: "GNU stream editor" + copyright: + - license: GPL-3.0-or-later + resources: + cpu: "7" + memory: 7Gi + +environment: + contents: + packages: + - autoconf + - automake + - build-base + - busybox + - ca-certificates-bundle + - coreutils + - gettext-dev + - rsync # for gnulib translations + - texinfo + - wget # for gnulib translations + +pipeline: + - uses: git-checkout + with: + repository: https://git.savannah.gnu.org/git/sed.git + tag: v${{package.version}} + expected-commit: 7e2e575a36bc88c0f3f3d6d8083c5f5b0ed44009 + recurse-submodules: true + shallow-submodules: true + submodule-jobs: $(nproc) + + # - name: git.savannah.gnu.org is flaky + # runs: git submodule set-url gnulib https://github.com/coreutils/gnulib.git + + - runs: echo ${{package.version}} >.tarball-version + + - runs: ./bootstrap + + - uses: autoconf/configure + + - uses: autoconf/make + + - uses: autoconf/make-install + + - runs: | + rm -f "${{targets.destdir}}"/usr/share/info/dir + + - uses: strip + +update: + enabled: true + release-monitor: + identifier: 4789 + +test: + pipeline: + - runs: | + sed --version + sed --help diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.43.6/pkg/build/testdata/goldenfiles/sboms/sed-4.9-r8.spdx.json new/melange-0.43.7/pkg/build/testdata/goldenfiles/sboms/sed-4.9-r8.spdx.json --- old/melange-0.43.6/pkg/build/testdata/goldenfiles/sboms/sed-4.9-r8.spdx.json 1970-01-01 01:00:00.000000000 +0100 +++ new/melange-0.43.7/pkg/build/testdata/goldenfiles/sboms/sed-4.9-r8.spdx.json 2026-03-08 16:25:37.000000000 +0100 @@ -0,0 +1,102 @@ +{ + "SPDXID": "SPDXRef-DOCUMENT", + "name": "apk-sed-4.9-r8", + "spdxVersion": "SPDX-2.3", + "creationInfo": { + "created": "0001-01-01T00:00:00Z", + "creators": [ + "Tool: melange (devel)", + "Organization: Chainguard, Inc" + ], + "licenseListVersion": "3.22" + }, + "dataLicense": "CC0-1.0", + "documentNamespace": "https://spdx.org/spdxdocs/chainguard/melange/753938d770e34672abb2696f6a4bc0fc", + "documentDescribes": [ + "SPDXRef-Package-apk-sed-4.9-r8" + ], + "packages": [ + { + "SPDXID": "SPDXRef-OperatingSystem", + "name": "wolfi", + "filesAnalyzed": false, + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "description": "Operating System", + "downloadLocation": "NOASSERTION", + "originator": "Organization: Wolfi", + "supplier": "Organization: Wolfi", + "primaryPackagePurpose": "OPERATING-SYSTEM" + }, + { + "SPDXID": "SPDXRef-Package-apk-sed-4.9-r8", + "name": "sed", + "versionInfo": "4.9-r8", + "filesAnalyzed": false, + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "GPL-3.0-or-later", + "downloadLocation": "NOASSERTION", + "originator": "Organization: Wolfi", + "supplier": "Organization: Wolfi", + "copyrightText": "NOASSERTION", + "primaryPackagePurpose": "APPLICATION", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceLocator": "pkg:apk/wolfi/[email protected]?arch=x86_64\u0026distro=wolfi", + "referenceType": "purl" + } + ] + }, + { + "SPDXID": "SPDXRef-Package-Melange-testdata-buildC95configs-sed.yaml-c0ffee", + "name": "testdata/build_configs/sed.yaml", + "versionInfo": "c0ffee", + "filesAnalyzed": false, + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "originator": "Organization: Wolfi", + "supplier": "Organization: Wolfi", + "primaryPackagePurpose": "INSTALL", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceLocator": "pkg:github/wolfi-dev/os@c0ffee#testdata/build_configs/sed.yaml", + "referenceType": "purl" + } + ] + }, + { + "SPDXID": "SPDXRef-Package-Source-git.savannah.gnu.org-git-sed.git-v4.9-7e2e575a36bc88c0f3f3d6d8083c5f5b0ed44009-0", + "name": "git/sed", + "versionInfo": "v4.9", + "filesAnalyzed": false, + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "GPL-3.0-or-later", + "downloadLocation": "https://tarballs.cgr.dev/g/43acfa56015925be19247bfc05ac5dec36f9df5e86ded6e5b74b85038e00b203-7e2e575a36bc88c0f3f3d6d8083c5f5b0ed44009.tar.gz", + "originator": "Organization: Wolfi", + "supplier": "Organization: Wolfi", + "primaryPackagePurpose": "SOURCE", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceLocator": "pkg:generic/git%[email protected]?vcs_url=git%2Bhttps:%2F%2Fgit.savannah.gnu.org%2Fgit%2Fsed.git%407e2e575a36bc88c0f3f3d6d8083c5f5b0ed44009", + "referenceType": "purl" + } + ] + } + ], + "relationships": [ + { + "spdxElementId": "SPDXRef-Package-apk-sed-4.9-r8", + "relationshipType": "DESCRIBED_BY", + "relatedSpdxElement": "SPDXRef-Package-Melange-testdata-buildC95configs-sed.yaml-c0ffee" + }, + { + "spdxElementId": "SPDXRef-Package-apk-sed-4.9-r8", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-Package-Source-git.savannah.gnu.org-git-sed.git-v4.9-7e2e575a36bc88c0f3f3d6d8083c5f5b0ed44009-0" + } + ] +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.43.6/pkg/cond/parser.go new/melange-0.43.7/pkg/cond/parser.go --- old/melange-0.43.6/pkg/cond/parser.go 2026-03-04 12:01:24.000000000 +0100 +++ new/melange-0.43.7/pkg/cond/parser.go 2026-03-08 16:25:37.000000000 +0100 @@ -16,51 +16,198 @@ import ( "fmt" - - "github.com/ijt/goparsify" + "strings" ) -func combineOp(n *goparsify.Result) { - switch n.Child[1].Token { - case "&&": - n.Result = n.Child[0].Result == true && n.Child[2].Result == true - case "||": - n.Result = n.Child[0].Result == true || n.Child[2].Result == true - default: - panic(fmt.Errorf("unrecognized op")) +// A VariableLookupFunction designates how variables should be +// resolved when evaluating expressions. +type VariableLookupFunction func(key string) (string, error) + +// NullLookup returns an empty value for any requested variable and +// does not return an error. This is the default variable lookup +// function used by Evaluate. +func NullLookup(key string) (string, error) { + return "", nil +} + +// parser is a simple recursive descent parser for condition expressions. +type parser struct { + input string + pos int + lookupFn VariableLookupFunction +} + +func isWhitespace(c byte) bool { + return c == ' ' || c == '\t' || c == '\n' || c == '\r' +} + +func (p *parser) skipWhitespace() { + for p.pos < len(p.input) && isWhitespace(p.input[p.pos]) { + p.pos++ + } +} + +func (p *parser) peek(s string) bool { + p.skipWhitespace() + return p.pos+len(s) <= len(p.input) && p.input[p.pos:p.pos+len(s)] == s +} + +func (p *parser) expect(s string) error { + p.skipWhitespace() + if p.pos+len(s) > len(p.input) || p.input[p.pos:p.pos+len(s)] != s { + return fmt.Errorf("expected %q at position %d", s, p.pos) } + p.pos += len(s) + return nil } -func collapseOp(n *goparsify.Result) { - n.Result = true - for _, child := range n.Child { - if child.Result != true { - n.Result = false - return +func isVarChar(c byte) bool { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '.' || c == '-' || c == '_' +} + +// parseValue parses a string literal ('...' or "...") or a variable (${{...}}). +func (p *parser) parseValue() (string, error) { + p.skipWhitespace() + if p.pos >= len(p.input) { + return "", fmt.Errorf("unexpected end of input at position %d", p.pos) + } + + // String literal + if p.input[p.pos] == '\'' || p.input[p.pos] == '"' { + quote := p.input[p.pos] + p.pos++ + var b strings.Builder + for p.pos < len(p.input) && p.input[p.pos] != quote { + if p.input[p.pos] == '\\' && p.pos+1 < len(p.input) { + p.pos++ // skip backslash + switch p.input[p.pos] { + case '\\', '\'', '"': + b.WriteByte(p.input[p.pos]) + case 'n': + b.WriteByte('\n') + case 't': + b.WriteByte('\t') + default: + // Preserve unknown escapes as-is. + b.WriteByte('\\') + b.WriteByte(p.input[p.pos]) + } + } else { + b.WriteByte(p.input[p.pos]) + } + p.pos++ + } + if p.pos >= len(p.input) { + return "", fmt.Errorf("unterminated string literal at position %d", p.pos) } + p.pos++ // consume closing quote + return b.String(), nil } + + // Variable: ${{name}} + if p.peek("${{") { + p.pos += 3 // consume ${{ + p.skipWhitespace() + start := p.pos + for p.pos < len(p.input) && isVarChar(p.input[p.pos]) { + p.pos++ + } + if p.pos == start { + return "", fmt.Errorf("empty variable name at position %d", p.pos) + } + name := p.input[start:p.pos] + p.skipWhitespace() + if err := p.expect("}}"); err != nil { + return "", fmt.Errorf("unterminated variable reference %q at position %d: %w", name, start, err) + } + resolved, err := p.lookupFn(name) + if err != nil { + return "", fmt.Errorf("error resolving variable %q at position %d: %w", name, start, err) + } + return resolved, nil + } + + return "", fmt.Errorf("unexpected character %q at position %d", p.input[p.pos], p.pos) } -func comparisonOp(n *goparsify.Result) { - switch n.Child[1].Token { - case "==": - n.Result = n.Child[0].Token == n.Child[2].Token - case "!=": - n.Result = n.Child[0].Token != n.Child[2].Token - default: - panic(fmt.Errorf("unrecognized op")) +// parseComparison parses: value ('==' | '!=') value +func (p *parser) parseComparison() (bool, error) { + lhs, err := p.parseValue() + if err != nil { + return false, err } + + p.skipWhitespace() + if p.pos+2 > len(p.input) { + return false, fmt.Errorf("expected comparison operator at position %d", p.pos) + } + + op := p.input[p.pos : p.pos+2] + if op != "==" && op != "!=" { + return false, fmt.Errorf("expected '==' or '!=' at position %d, got %q", p.pos, op) + } + p.pos += 2 + + rhs, err := p.parseValue() + if err != nil { + return false, err + } + + if op == "==" { + return lhs == rhs, nil + } + return lhs != rhs, nil } -// A VariableLookupFunction designates how variables should be -// resolved when evaluating expressions. -type VariableLookupFunction func(key string) (string, error) +// parseAtom parses a grouped expression or a comparison. +func (p *parser) parseAtom() (bool, error) { + p.skipWhitespace() + if p.pos < len(p.input) && p.input[p.pos] == '(' { + p.pos++ // consume '(' + result, err := p.parseExpr() + if err != nil { + return false, err + } + if err := p.expect(")"); err != nil { + return false, fmt.Errorf("expected ')' at position %d: %w", p.pos, err) + } + return result, nil + } + return p.parseComparison() +} -// NullLookup returns an empty value for any requested variable and -// does not return an error. This is the default variable lookup -// function used by Evaluate. -func NullLookup(key string) (string, error) { - return "", nil +// parseExpr parses atoms chained with && and ||. +func (p *parser) parseExpr() (bool, error) { + result, err := p.parseAtom() + if err != nil { + return false, err + } + + for { + p.skipWhitespace() + if p.pos+2 > len(p.input) { + break + } + op := p.input[p.pos : p.pos+2] + if op != "&&" && op != "||" { + break + } + p.pos += 2 + + rhs, err := p.parseAtom() + if err != nil { + return false, err + } + + switch op { + case "&&": + result = result && rhs + case "||": + result = result || rhs + } + } + + return result, nil } // Evaluate evaluates an input expression. @@ -76,44 +223,21 @@ lookupFn = lookupFns[0] } - equal := goparsify.Exact("==") - unequal := goparsify.Exact("!=") - comps := goparsify.Any(equal, unequal) - - variableName := goparsify.Chars("a-zA-Z0-9.\\-_") - variable := goparsify.Seq("${{", variableName, "}}").Map(func(n *goparsify.Result) { - if resolved, err := lookupFn(n.Child[1].Token); err == nil { - n.Token = resolved - n.Result = resolved - } - }) - - value := goparsify.Any(goparsify.StringLit("'\""), variable) - expr := goparsify.Seq(value, comps, value).Map(comparisonOp) - - and := goparsify.Exact("&&") - or := goparsify.Exact("||") - chain := goparsify.Any(and, or) - combinedExpr := goparsify.Seq(expr, chain, expr).Map(combineOp) - - exprChain := goparsify.Some(goparsify.Any(combinedExpr, expr), chain).Map(collapseOp) - - group := goparsify.Seq("(", goparsify.Cut(), exprChain, ")").Map(func(n *goparsify.Result) { - n.Result = n.Child[2].Result - }) - groupOrExpr := goparsify.Any(group, exprChain) - combinedGroup := goparsify.Seq(groupOrExpr, chain, groupOrExpr).Map(combineOp) - - groupChain := goparsify.Some(goparsify.Any(combinedGroup, groupOrExpr), chain).Map(collapseOp) + p := &parser{ + input: inputExpr, + pos: 0, + lookupFn: lookupFn, + } - result, _, err := goparsify.Run(groupChain, inputExpr, goparsify.UnicodeWhitespace) + result, err := p.parseExpr() if err != nil { return false, err } - if rbool, ok := result.(bool); ok { - return rbool, nil + p.skipWhitespace() + if p.pos != len(p.input) { + return false, fmt.Errorf("unexpected trailing input at position %d: %q", p.pos, p.input[p.pos:]) } - return false, fmt.Errorf("got non-boolean result from parser") + return result, nil } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.43.6/pkg/cond/parser_test.go new/melange-0.43.7/pkg/cond/parser_test.go --- old/melange-0.43.6/pkg/cond/parser_test.go 2026-03-04 12:01:24.000000000 +0100 +++ new/melange-0.43.7/pkg/cond/parser_test.go 2026-03-08 16:25:37.000000000 +0100 @@ -76,6 +76,80 @@ require.Equal(t, true, result, "${{foo.bar}} definitely equals baz") } +func TestEvaluateUnterminatedVariable(t *testing.T) { + _, err := Evaluate("${{foo.bar} == 'baz'", placeholderLookup) + require.Error(t, err) + require.Contains(t, err.Error(), "unterminated variable reference") + require.Contains(t, err.Error(), "foo.bar") +} + +func TestStringLiteralEscapes(t *testing.T) { + // Escaped double quote inside double-quoted string. + result, err := Evaluate(`"hello \"world\"" == "hello \"world\""`) + require.NoError(t, err) + require.True(t, result) + + // Escaped backslash. + result, err = Evaluate(`"a\\b" == "a\\b"`) + require.NoError(t, err) + require.True(t, result) + + // Escaped single quote inside single-quoted string. + result, err = Evaluate(`'it\'s' == 'it\'s'`) + require.NoError(t, err) + require.True(t, result) + + // Newline and tab escapes. + result, err = Evaluate(`"line1\nline2" == "line1\nline2"`) + require.NoError(t, err) + require.True(t, result) + + // Mismatch: escaped vs literal. + result, err = Evaluate(`"a\\b" == "ab"`) + require.NoError(t, err) + require.False(t, result) +} + +func FuzzEvaluate(f *testing.F) { + // Seed with valid and interesting expressions. + f.Add("'foo' == 'foo'") + f.Add("'foo' != 'bar'") + f.Add("${{foo.bar}} == 'baz'") + f.Add("${{ foo.bar }} == 'baz'") + f.Add(`"hello \"world\"" == "hello \"world\""`) + f.Add(`"a\\b" == "a\\b"`) + f.Add("('a' == 'a' && 'b' == 'b') || 'c' == 'd'") + f.Add("${{") + f.Add("${{ }}") + f.Add("'unterminated") + f.Add("") + + f.Fuzz(func(t *testing.T, input string) { + // Evaluate must never panic regardless of input. + Evaluate(input, func(key string) (string, error) { //nolint:errcheck + return "x", nil + }) + }) +} + +func FuzzSubst(f *testing.F) { + f.Add("Hello ${{foo.bar}}!") + f.Add("${{foo}} ${{bar}}") + f.Add("${{ foo.bar }}") + f.Add("no variables here") + f.Add("${{") + f.Add("${{ }}") + f.Add("${{foo.bar}") + f.Add("") + + f.Fuzz(func(t *testing.T, input string) { + // Subst must never panic regardless of input. + Subst(input, func(key string) (string, error) { //nolint:errcheck + return "x", nil + }) + }) +} + func TestVariableLookupWhitespace(t *testing.T) { result, err := Evaluate("${{ foo.bar }} == 'baz'", placeholderLookup) require.NoErrorf(t, err, "got error: %v", err) @@ -84,4 +158,13 @@ result, err = Evaluate("'baz' == ${{ foo.bar }}", placeholderLookup) require.NoErrorf(t, err, "got error: %v", err) require.Equal(t, true, result, "${{ foo.bar }} definitely equals baz") + + // Tabs and newlines inside braces. + result, err = Evaluate("${{\tfoo.bar\t}} == 'baz'", placeholderLookup) + require.NoErrorf(t, err, "got error: %v", err) + require.Equal(t, true, result, "tabs inside variable braces should be accepted") + + result, err = Evaluate("${{\n foo.bar \n}} == 'baz'", placeholderLookup) + require.NoErrorf(t, err, "got error: %v", err) + require.Equal(t, true, result, "newlines inside variable braces should be accepted") } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.43.6/pkg/cond/subst.go new/melange-0.43.7/pkg/cond/subst.go --- old/melange-0.43.6/pkg/cond/subst.go 2026-03-04 12:01:24.000000000 +0100 +++ new/melange-0.43.7/pkg/cond/subst.go 2026-03-08 16:25:37.000000000 +0100 @@ -18,8 +18,6 @@ "errors" "fmt" "strings" - - "github.com/ijt/goparsify" ) func Subst(inputExpr string, lookupFns ...VariableLookupFunction) (string, error) { @@ -29,43 +27,64 @@ lookupFn = lookupFns[0] } - whiteSpace := goparsify.Many(goparsify.Exact(" ")) - variableName := goparsify.Chars("a-zA-Z0-9.\\-_") - errs := []error{} - variable := goparsify.Seq("${{", whiteSpace, variableName, whiteSpace, "}}").Map(func(n *goparsify.Result) { - if resolved, err := lookupFn(n.Child[2].Token); err == nil { - n.Token = resolved - n.Result = resolved - } else { - errs = append(errs, err) - n.Token = "" - n.Result = "" + var b strings.Builder + b.Grow(len(inputExpr)) + + var errs []error + i := 0 + for i < len(inputExpr) { + // Look for the next ${{ marker. + idx := strings.Index(inputExpr[i:], "${{") + if idx < 0 { + // No more variables, write the rest. + b.WriteString(inputExpr[i:]) + break + } + + // Write text before the variable. + b.WriteString(inputExpr[i : i+idx]) + i += idx + 3 // skip past ${{ + + // Skip whitespace inside braces. + for i < len(inputExpr) && isWhitespace(inputExpr[i]) { + i++ + } + + // Read variable name. + start := i + for i < len(inputExpr) && isVarChar(inputExpr[i]) { + i++ } - }) + name := inputExpr[start:i] - text := goparsify.Until("${{") - node := goparsify.Any(text, variable) + if name == "" { + errs = append(errs, fmt.Errorf("empty variable name at position %d", start)) + continue + } + + // Skip whitespace before }}. + for i < len(inputExpr) && isWhitespace(inputExpr[i]) { + i++ + } - document := goparsify.Many(node).Map(func(n *goparsify.Result) { - tokens := make([]string, 0, len(n.Child)) - for _, tok := range n.Child { - tokens = append(tokens, tok.Token) + // Expect }}. + if i+2 > len(inputExpr) || inputExpr[i:i+2] != "}}" { + errs = append(errs, fmt.Errorf("unterminated variable reference %q at position %d", name, start)) + continue } - n.Result = strings.Join(tokens, "") - }) + i += 2 - result, _, err := goparsify.Run(document, inputExpr, goparsify.NoWhitespace) - if err != nil { - return "", fmt.Errorf("parser error: %w", err) + resolved, err := lookupFn(name) + if err != nil { + errs = append(errs, err) + } else { + b.WriteString(resolved) + } } if err := errors.Join(errs...); err != nil { return "", err } - if rstr, ok := result.(string); ok { - return rstr, nil - } - - return "", fmt.Errorf("got non-string result from parser") + return b.String(), nil } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.43.6/pkg/cond/subst_test.go new/melange-0.43.7/pkg/cond/subst_test.go --- old/melange-0.43.6/pkg/cond/subst_test.go 2026-03-04 12:01:24.000000000 +0100 +++ new/melange-0.43.7/pkg/cond/subst_test.go 2026-03-08 16:25:37.000000000 +0100 @@ -47,6 +47,15 @@ require.Equal(t, expected, result, "result does not match expected result") } +func TestSubstVarWhitespaceTabsNewlines(t *testing.T) { + doc := "Hello ${{\tfoo.bar\t}} ${{\nfoo.bar\n}}!" + expected := "Hello baz baz!" + result, err := Subst(doc, placeholderLookup) + + require.NoErrorf(t, err, "got error: %v", err) + require.Equal(t, expected, result, "result does not match expected result") +} + func TestSubstVarUnderscore(t *testing.T) { doc := "Hello ${{foo.BAR_BAZ}}!" expected := "Hello bar-baz!" @@ -90,6 +99,30 @@ require.Equal(t, expected, result, "result does not match expected result") } +func TestSubstMissingClosingBraces(t *testing.T) { + doc := "Hello ${{foo.bar}!" + _, err := Subst(doc, placeholderLookup) + require.Error(t, err) + require.Contains(t, err.Error(), "unterminated variable reference") +} + +func TestSubstEmptyVarName(t *testing.T) { + doc := "Hello ${{ }}!" + _, err := Subst(doc, placeholderLookup) + require.Error(t, err) + require.Contains(t, err.Error(), "empty variable name") +} + +func TestSubstTrailingMarker(t *testing.T) { + _, err := Subst("hello ${{", placeholderLookup) + require.Error(t, err) + require.Contains(t, err.Error(), "empty variable name") + + _, err = Subst("hello ${{foo", placeholderLookup) + require.Error(t, err) + require.Contains(t, err.Error(), "unterminated variable reference") +} + func TestSubstVarShellFragment(t *testing.T) { doc := `if [ "${{inputs.expected-sha256}}" == "" ] && [ "${{inputs.expected-sha512}}" == "" ]; then printf "One of expected-sha256 or expected-sha512 is required" ++++++ melange.obsinfo ++++++ --- /var/tmp/diff_new_pack.QDU2vs/_old 2026-03-09 16:23:46.571747882 +0100 +++ /var/tmp/diff_new_pack.QDU2vs/_new 2026-03-09 16:23:46.575748047 +0100 @@ -1,5 +1,5 @@ name: melange -version: 0.43.6 -mtime: 1772622084 -commit: 44654e54749503cf5425d09c4460170bf6801575 +version: 0.43.7 +mtime: 1772983537 +commit: d8dfccb7dcd1156156ea3d11d08c541a7c3c5068 ++++++ vendor.tar.gz ++++++ /work/SRC/openSUSE:Factory/melange/vendor.tar.gz /work/SRC/openSUSE:Factory/.melange.new.8177/vendor.tar.gz differ: char 13, line 1
