Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package melange for openSUSE:Factory checked in at 2025-07-09 17:28:21 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/melange (Old) and /work/SRC/openSUSE:Factory/.melange.new.7373 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "melange" Wed Jul 9 17:28:21 2025 rev:102 rq:1291375 version:0.29.1 Changes: -------- --- /work/SRC/openSUSE:Factory/melange/melange.changes 2025-07-06 17:18:05.656236399 +0200 +++ /work/SRC/openSUSE:Factory/.melange.new.7373/melange.changes 2025-07-09 17:29:32.330379380 +0200 @@ -1,0 +2,11 @@ +Wed Jul 09 04:44:24 UTC 2025 - Johannes Kastl <opensuse_buildserv...@ojkastl.de> + +- Update to version 0.29.1: + * Revert "feat: allow symlinks in workspaces (#2064)" (#2071) + * fix: avoid running cachedir mount commands via script (#2070) + * Add comment about PATH vs the qemu runner (#2069) + * SCA: Generate provides for shlibs ending with ".so" (#2067) + * licensing: default to concatenating with AND, add simple mode + for license-check --fix (#2057) + +------------------------------------------------------------------- Old: ---- melange-0.29.0.obscpio New: ---- melange-0.29.1.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ melange.spec ++++++ --- /var/tmp/diff_new_pack.iY3Gcv/_old 2025-07-09 17:29:33.326420939 +0200 +++ /var/tmp/diff_new_pack.iY3Gcv/_new 2025-07-09 17:29:33.330421106 +0200 @@ -17,7 +17,7 @@ Name: melange -Version: 0.29.0 +Version: 0.29.1 Release: 0 Summary: Build APKs from source code License: Apache-2.0 ++++++ _service ++++++ --- /var/tmp/diff_new_pack.iY3Gcv/_old 2025-07-09 17:29:33.358422275 +0200 +++ /var/tmp/diff_new_pack.iY3Gcv/_new 2025-07-09 17:29:33.362422441 +0200 @@ -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.29.0</param> + <param name="revision">v0.29.1</param> <param name="versionformat">@PARENT_TAG@</param> <param name="versionrewrite-pattern">v(.*)</param> <param name="changesgenerate">enable</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.iY3Gcv/_old 2025-07-09 17:29:33.382423276 +0200 +++ /var/tmp/diff_new_pack.iY3Gcv/_new 2025-07-09 17:29:33.386423443 +0200 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/chainguard-dev/melange</param> - <param name="changesrevision">75ee8c561e307394b4b565e6e7b23ce7cf059245</param></service></servicedata> + <param name="changesrevision">15d3e93dd6c3501bb04bf1b206c1cb6d97165347</param></service></servicedata> (No newline at EOF) ++++++ melange-0.29.0.obscpio -> melange-0.29.1.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.29.0/docs/md/melange_license-check.md new/melange-0.29.1/docs/md/melange_license-check.md --- old/melange-0.29.0/docs/md/melange_license-check.md 2025-07-03 19:44:54.000000000 +0200 +++ new/melange-0.29.1/docs/md/melange_license-check.md 2025-07-08 17:06:52.000000000 +0200 @@ -29,6 +29,7 @@ ``` --fix fix license issues in the melange yaml file + --format string license fix strategy format: 'simple' or 'flat' (default "flat") -h, --help help for license-check --workdir string path to the working directory, e.g. where the source will be extracted to ``` diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.29.0/e2e-tests/symlinks-in-workspace-build.yaml new/melange-0.29.1/e2e-tests/symlinks-in-workspace-build.yaml --- old/melange-0.29.0/e2e-tests/symlinks-in-workspace-build.yaml 2025-07-03 19:44:54.000000000 +0200 +++ new/melange-0.29.1/e2e-tests/symlinks-in-workspace-build.yaml 1970-01-01 01:00:00.000000000 +0100 @@ -1,17 +0,0 @@ -package: - name: symlinks-in-workspace-build - description: Test that symlinks are copied into workspaces - version: 0.1.0 - epoch: 0 - -environment: - contents: - packages: - - busybox - -pipeline: - - name: Test for symlink presence in workspace - runs: | - testdata_linked=$(cat testdata-symlink.txt) - testdata=$(cat testdata.txt) - [ "$testdata" = "$testdata_linked" ] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.29.0/e2e-tests/test-fixtures/testdata-symlink.txt new/melange-0.29.1/e2e-tests/test-fixtures/testdata-symlink.txt --- old/melange-0.29.0/e2e-tests/test-fixtures/testdata-symlink.txt 2025-07-09 17:29:33.558430620 +0200 +++ new/melange-0.29.1/e2e-tests/test-fixtures/testdata-symlink.txt 1970-01-01 01:00:00.000000000 +0100 @@ -1 +0,0 @@ -symbolic link to testdata.txt diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.29.0/e2e-tests/test-fixtures/testdata.txt new/melange-0.29.1/e2e-tests/test-fixtures/testdata.txt --- old/melange-0.29.0/e2e-tests/test-fixtures/testdata.txt 2025-07-03 19:44:54.000000000 +0200 +++ new/melange-0.29.1/e2e-tests/test-fixtures/testdata.txt 1970-01-01 01:00:00.000000000 +0100 @@ -1 +0,0 @@ -test data is present diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.29.0/pkg/build/build.go new/melange-0.29.1/pkg/build/build.go --- old/melange-0.29.0/pkg/build/build.go 2025-07-03 19:44:54.000000000 +0200 +++ new/melange-0.29.1/pkg/build/build.go 2025-07-08 17:06:52.000000000 +0200 @@ -563,25 +563,7 @@ mode := fi.Mode() if !mode.IsRegular() { - // If this file is a symlink to a regular file, include it. - // It would be easier to include all symlinks but that breaks - // when the top-level workspace directory is a symlink. - if mode&fs.ModeSymlink != 0 { - targetPath, err := filepath.EvalSymlinks(filepath.Join(b.SourceDir, path)) - if err != nil { - log.Debugf("path %s eval gives err %v", path, err) - return err - } - target, err := os.Stat(targetPath) - if err != nil { - return err - } - if !target.Mode().IsRegular() { - return nil - } - } else { - return nil - } + return nil } for _, pat := range ignorePatterns { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.29.0/pkg/build/pipeline.go new/melange-0.29.1/pkg/build/pipeline.go --- old/melange-0.29.0/pkg/build/pipeline.go 2025-07-03 19:44:54.000000000 +0200 +++ new/melange-0.29.1/pkg/build/pipeline.go 2025-07-08 17:06:52.000000000 +0200 @@ -207,6 +207,7 @@ // Pipelines can have their own environment variables, which override the global ones. envOverride := map[string]string{ + // NOTE: This does not currently override PATH in the qemu runner, that's set at openssh build time "PATH": "/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/sbin:/sbin:/bin", } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.29.0/pkg/build/test.go new/melange-0.29.1/pkg/build/test.go --- old/melange-0.29.0/pkg/build/test.go 2025-07-03 19:44:54.000000000 +0200 +++ new/melange-0.29.1/pkg/build/test.go 2025-07-08 17:06:52.000000000 +0200 @@ -334,6 +334,8 @@ } log.Infof("running the main test pipeline") + + pr.config.TestRun = true if err := pr.runPipelines(ctx, t.Configuration.Test.Pipeline); err != nil { return fmt.Errorf("unable to run pipeline: %w", err) } @@ -388,6 +390,7 @@ }() } + pr.config.TestRun = true if err := pr.runPipelines(ctx, sp.Test.Pipeline); err != nil { return fmt.Errorf("unable to run pipeline: %w", err) } @@ -452,7 +455,6 @@ Environment: map[string]string{}, RunAsUID: runAsUID(imgcfg.Accounts), RunAs: runAs(imgcfg.Accounts), - TestRun: true, } if t.Configuration.Capabilities.Add != nil { cfg.Capabilities.Add = t.Configuration.Capabilities.Add diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.29.0/pkg/build/test_test.go new/melange-0.29.1/pkg/build/test_test.go --- old/melange-0.29.0/pkg/build/test_test.go 2025-07-03 19:44:54.000000000 +0200 +++ new/melange-0.29.1/pkg/build/test_test.go 2025-07-08 17:06:52.000000000 +0200 @@ -78,7 +78,6 @@ PackageName: testPkgName, ImgRef: testImgRef, WorkspaceDir: "/workspace", - TestRun: true, Capabilities: container.Capabilities{Networking: true}, Mounts: []container.BindMount{ {Source: testWorkspaceDir, Destination: homeBuild}, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.29.0/pkg/cli/license_check.go new/melange-0.29.1/pkg/cli/license_check.go --- old/melange-0.29.0/pkg/cli/license_check.go 2025-07-03 19:44:54.000000000 +0200 +++ new/melange-0.29.1/pkg/cli/license_check.go 2025-07-08 17:06:52.000000000 +0200 @@ -32,6 +32,7 @@ func licenseCheck() *cobra.Command { var workDir string var fix bool + var format string cmd := &cobra.Command{ Use: "license-check file", Short: "Gather and check licensing data", @@ -81,6 +82,7 @@ ctx, copyright.WithLicenses(detectedLicenses), copyright.WithDiffs(diffs), + copyright.WithFormat(format), ) err = rc.Renovate(cmd.Context(), copyrightRenovator) } @@ -91,6 +93,7 @@ cmd.Flags().StringVar(&workDir, "workdir", "", "path to the working directory, e.g. where the source will be extracted to") cmd.Flags().BoolVar(&fix, "fix", false, "fix license issues in the melange yaml file") + cmd.Flags().StringVar(&format, "format", "flat", "license fix strategy format: 'simple' or 'flat'") return cmd } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.29.0/pkg/config/config.go new/melange-0.29.1/pkg/config/config.go --- old/melange-0.29.0/pkg/config/config.go 2025-07-03 19:44:54.000000000 +0200 +++ new/melange-0.29.1/pkg/config/config.go 2025-07-08 17:06:52.000000000 +0200 @@ -432,7 +432,7 @@ } for _, cp := range p.Copyright { if licenseExpression != "" { - licenseExpression += " OR " + licenseExpression += " AND " } licenseExpression += cp.License } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.29.0/pkg/renovate/copyright/copyright.go new/melange-0.29.1/pkg/renovate/copyright/copyright.go --- old/melange-0.29.0/pkg/renovate/copyright/copyright.go 2025-07-03 19:44:54.000000000 +0200 +++ new/melange-0.29.1/pkg/renovate/copyright/copyright.go 2025-07-08 17:06:52.000000000 +0200 @@ -17,6 +17,9 @@ import ( "context" "fmt" + "maps" + "slices" + "strings" "github.com/chainguard-dev/clog" @@ -30,6 +33,7 @@ type CopyrightConfig struct { Licenses []license.License Diffs []license.LicenseDiff + Format string // "simple" or "flat" } // Option sets a config option on a CopyrightConfig. @@ -53,6 +57,16 @@ } } +// WithFormat sets whether the copyright should be populated with a single +// node containing all detected licenses joined together (simple), or with +// multiple nodes, one per license (flat). +func WithFormat(format string) Option { + return func(cfg *CopyrightConfig) error { + cfg.Format = format + return nil + } +} + // New returns a renovator which performs a copyright update. func New(ctx context.Context, opts ...Option) renovate.Renovator { log := clog.FromContext(ctx) @@ -104,46 +118,98 @@ copyrightNode.Content = nil // Repopulate the copyrightNode with detected licenses - for _, l := range ccfg.Licenses { - // Skip licenses we don't have full confidence in. - if !license.IsLicenseMatchConfident(l) { - log.Infof("skipping unconfident license %s", l.Source) - continue + if ccfg.Format == "simple" { + // Make the copyright field a single node with all licenses joined together + if err = populateSimpleCopyright(ctx, copyrightNode, ccfg.Licenses); err != nil { + return err } - - licenseNode := &yaml.Node{ - Kind: yaml.MappingNode, - Style: yaml.FlowStyle, - Content: []*yaml.Node{}, + } else { + // Use flat license listing (original behavior) + if err = populateFlatCopyright(ctx, copyrightNode, ccfg.Licenses); err != nil { + return err } + } - licenseNode.Content = append(licenseNode.Content, &yaml.Node{ - Kind: yaml.ScalarNode, - Value: "license", - Tag: "!!str", - Style: yaml.FlowStyle, - }, &yaml.Node{ - Kind: yaml.ScalarNode, - Value: l.Name, - Tag: "!!str", - Style: yaml.FlowStyle, - }) - - licenseNode.Content = append(licenseNode.Content, &yaml.Node{ - Kind: yaml.ScalarNode, - Value: "license-path", - Tag: "!!str", - Style: yaml.FlowStyle, - }, &yaml.Node{ - Kind: yaml.ScalarNode, - Value: l.Source, - Tag: "!!str", - Style: yaml.FlowStyle, - }) + return nil + } +} - copyrightNode.Content = append(copyrightNode.Content, licenseNode) +// populateFlatCopyright populates the copyright node with the detected licenses, +// one entry per license. +func populateFlatCopyright(ctx context.Context, copyrightNode *yaml.Node, licenses []license.License) error { + log := clog.FromContext(ctx) + + for _, l := range licenses { + // Skip licenses we don't have full confidence in. + if !license.IsLicenseMatchConfident(l) { + log.Infof("skipping unconfident license %s", l.Source) + continue + } + + licenseNode := &yaml.Node{ + Kind: yaml.MappingNode, + Style: yaml.FlowStyle, + Content: []*yaml.Node{}, + } + + licenseNode.Content = append(licenseNode.Content, &yaml.Node{ + Kind: yaml.ScalarNode, + Value: "license", + Tag: "!!str", + Style: yaml.FlowStyle, + }, &yaml.Node{ + Kind: yaml.ScalarNode, + Value: l.Name, + Tag: "!!str", + Style: yaml.FlowStyle, + }) + + licenseNode.Content = append(licenseNode.Content, &yaml.Node{ + Kind: yaml.ScalarNode, + Value: "license-path", + Tag: "!!str", + Style: yaml.FlowStyle, + }, &yaml.Node{ + Kind: yaml.ScalarNode, + Value: l.Source, + Tag: "!!str", + Style: yaml.FlowStyle, + }) + + copyrightNode.Content = append(copyrightNode.Content, licenseNode) + } + + return nil +} + +// populateSimpleCopyright populates the copyright field with a single node +// with all detected licenses joined together. +func populateSimpleCopyright(ctx context.Context, copyrightNode *yaml.Node, licenses []license.License) error { + log := clog.FromContext(ctx) + + // Gather all the license names and concatenate them with AND statements + licenseMap := make(map[string]struct{}) + for _, l := range licenses { + if !license.IsLicenseMatchConfident(l) { + log.Infof("skipping unconfident license %s", l.Source) + continue } + licenseMap[l.Name] = struct{}{} + } + if len(licenseMap) == 0 { + log.Infof("no confident licenses found to populate copyright") return nil } + + // Join the license names with " AND ", sorting them first for consistency + ls := slices.Collect(maps.Keys(licenseMap)) + slices.Sort(ls) + combined := strings.Join(ls, " AND ") + + copyrightNode.Kind = yaml.ScalarNode + copyrightNode.Value = combined + copyrightNode.Tag = "!!str" + + return nil } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.29.0/pkg/renovate/copyright/copyright_test.go new/melange-0.29.1/pkg/renovate/copyright/copyright_test.go --- old/melange-0.29.0/pkg/renovate/copyright/copyright_test.go 2025-07-03 19:44:54.000000000 +0200 +++ new/melange-0.29.1/pkg/renovate/copyright/copyright_test.go 2025-07-08 17:06:52.000000000 +0200 @@ -126,3 +126,66 @@ assert.Contains(t, string(resultData), "license: Not-Applicable") assert.NotContains(t, string(resultData), "license: Invalid") } + +func TestCopyright_updateSimple(t *testing.T) { + dir := t.TempDir() + ctx := slogtest.Context(t) + + detectedLicenses := []license.License{ + { + Name: "Apache-2.0", + Source: "LICENSE", + Confidence: 1.0, + }, + { + Name: "MIT", + Source: "internal/COPYING", + Confidence: 1.0, + }, + { + Name: "Apache-2.0", + Source: "vendor/foo/LICENSE", + Confidence: 1.0, + }, + { + Name: "GPL-3.0", + Source: "internal/LICENSE", + Confidence: 0.2, + }, + } + + diffs := []license.LicenseDiff{ + { + Path: "LICENSE", + Is: "GPL-2.0", + Should: "Apache-2.0", + }, + } + + // Copy the test data file to the temp directory + src := filepath.Join("testdata", "nolicense.yaml") + testFile := filepath.Join(dir, "nolicense.yaml") + input, err := os.ReadFile(src) + assert.NoError(t, err) + + err = os.WriteFile(testFile, input, 0644) + assert.NoError(t, err) + + rctx, err := renovate.New(renovate.WithConfig(testFile)) + assert.NoError(t, err) + + copyrightRenovator := New(ctx, WithLicenses(detectedLicenses), WithDiffs(diffs), WithFormat("simple")) + + err = rctx.Renovate(slogtest.Context(t), copyrightRenovator) + assert.NoError(t, err) + + resultData, err := os.ReadFile(testFile) + assert.NoError(t, err) + + // The copyright field should be a single string with both licenses joined by " AND " + result := string(resultData) + assert.Contains(t, result, "Apache-2.0 AND MIT") + assert.NotContains(t, result, "GPL-3.0") + assert.NotContains(t, result, "NOASSERTION") + assert.NotContains(t, result, "license:") +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.29.0/pkg/sca/sca.go new/melange-0.29.1/pkg/sca/sca.go --- old/melange-0.29.0/pkg/sca/sca.go 2025-07-03 19:44:54.000000000 +0200 +++ new/melange-0.29.1/pkg/sca/sca.go 2025-07-08 17:06:52.000000000 +0200 @@ -622,7 +622,7 @@ // // As a rough heuristic, we assume that if the filename contains ".so.", // it is meant to be used as a shared object. - if interp == "" || strings.Contains(basename, ".so.") { + if interp == "" || strings.Contains(basename, ".so.") || strings.HasSuffix(basename, ".so") { sonames, err := ef.DynString(elf.DT_SONAME) // most likely SONAME is not set on this object if err != nil { ++++++ melange.obsinfo ++++++ --- /var/tmp/diff_new_pack.iY3Gcv/_old 2025-07-09 17:29:33.650434459 +0200 +++ /var/tmp/diff_new_pack.iY3Gcv/_new 2025-07-09 17:29:33.654434626 +0200 @@ -1,5 +1,5 @@ name: melange -version: 0.29.0 -mtime: 1751564694 -commit: 75ee8c561e307394b4b565e6e7b23ce7cf059245 +version: 0.29.1 +mtime: 1751987212 +commit: 15d3e93dd6c3501bb04bf1b206c1cb6d97165347 ++++++ vendor.tar.gz ++++++ /work/SRC/openSUSE:Factory/melange/vendor.tar.gz /work/SRC/openSUSE:Factory/.melange.new.7373/vendor.tar.gz differ: char 132, line 1