Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package product-composer for 
openSUSE:Factory checked in at 2026-01-15 16:44:41
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/product-composer (Old)
 and      /work/SRC/openSUSE:Factory/.product-composer.new.1928 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "product-composer"

Thu Jan 15 16:44:41 2026 rev:48 rq:1327191 version:0.8.1

Changes:
--------
--- /work/SRC/openSUSE:Factory/product-composer/product-composer.changes        
2025-08-20 13:28:35.884612850 +0200
+++ 
/work/SRC/openSUSE:Factory/.product-composer.new.1928/product-composer.changes  
    2026-01-15 16:45:36.974680939 +0100
@@ -1,0 +2,76 @@
+Wed Jan 14 10:37:03 UTC 2026 - Adrian Schröter <[email protected]>
+
+- update to version 0.8.1
+  - handle CPE id checks case sensitive, but also write it always
+    lower case.
+
+-------------------------------------------------------------------
+Tue Jan 13 08:04:23 UTC 2026 - Adrian Schröter <[email protected]>
+
+- update to version 0.8.0
+
+  *** INCOMPATIBLE CHANGE ***
+
+  Always enforce /o in cpe-ids, dropping /a support.
+  This was decided in architecture forum for SLFO.
+
+-------------------------------------------------------------------
+Wed Dec 10 13:14:29 UTC 2025 - Adrian Schröter <[email protected]>
+
+- update to version 0.7.3
+  - new no_product_provides build_option
+
+-------------------------------------------------------------------
+Wed Dec 10 10:54:50 UTC 2025 - Adrian Schröter <[email protected]>
+
+- update to version 0.7.2:
+  - relax CPE id check case insensitive
+  - give a warning on empty media
+
+-------------------------------------------------------------------
+Wed Dec 10 09:33:55 UTC 2025 - Adrian Schröter <[email protected]>
+
+- update to version 0.7.1:
+  - relax CPE id check on empty medium. We assume online
+    installation here which needs to be checked when creating that
+    resource.
+
+-------------------------------------------------------------------
+Fri Nov 14 08:57:50 UTC 2025 - Adrian Schröter <[email protected]>
+
+- update to version 0.7.0:
+  - adds validating of CPE ids with release package.
+
+-------------------------------------------------------------------
+Thu Nov 13 15:26:44 UTC 2025 - Adrian Schröter <[email protected]>
+
+- update to version 0.6.18:
+  - Fix filtering of not used rpms in updateinfo
+
+-------------------------------------------------------------------
+Tue Nov 11 09:40:29 UTC 2025 - Adrian Schröter <[email protected]>
+
+- update to version 0.6.17:
+  - fix multiarch media handling of updateinfo id's
+
+-------------------------------------------------------------------
+Thu Oct 30 13:21:10 UTC 2025 - Adrian Schröter <[email protected]>
+
+- update to version 0.6.16:
+  - merge updateinfo's with same id into one
+  - error out on updateinfo with same id, but non-mergable content
+
+-------------------------------------------------------------------
+Wed Oct 29 07:27:35 UTC 2025 - Adrian Schröter <[email protected]>
+
+- update to version 0.6.15:
+  * Support updateinfo handling in arch specific meta data
+
+-------------------------------------------------------------------
+Tue Aug 26 10:56:21 UTC 2025 - Adrian Schröter <[email protected]>
+
+- update to version 0.6.14:
+  * option to disable joliet extensions on media
+  * no joliet extensions on source and debug media anymore
+
+-------------------------------------------------------------------

Old:
----
  product-composer-0.6.13.tar.xz

New:
----
  product-composer-0.8.1.tar.xz

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

Other differences:
------------------
++++++ product-composer.spec ++++++
--- /var/tmp/diff_new_pack.IrU3tT/_old  2026-01-15 16:45:37.878718463 +0100
+++ /var/tmp/diff_new_pack.IrU3tT/_new  2026-01-15 16:45:37.882718629 +0100
@@ -23,7 +23,7 @@
 %endif
 
 Name:           product-composer
-Version:        0.6.13
+Version:        0.8.1
 Release:        0
 Summary:        Product Composer
 License:        GPL-2.0-or-later

++++++ _scmsync.obsinfo ++++++
--- /var/tmp/diff_new_pack.IrU3tT/_old  2026-01-15 16:45:37.950721451 +0100
+++ /var/tmp/diff_new_pack.IrU3tT/_new  2026-01-15 16:45:37.954721618 +0100
@@ -1,5 +1,5 @@
-mtime: 1755678448
-commit: 66ac4085afcffda8e5e575d841b3213016434c5dfb4e184629779339c0614f34
+mtime: 1768387086
+commit: 3ad96175ecf303563ca44f972d77baaafc0e299470ffa8d5e72d7c2ac40fa6ee
 url: https://src.opensuse.org/tools/product-composer
 revision: devel
 

++++++ _service ++++++
--- /var/tmp/diff_new_pack.IrU3tT/_old  2026-01-15 16:45:37.986722946 +0100
+++ /var/tmp/diff_new_pack.IrU3tT/_new  2026-01-15 16:45:37.998723444 +0100
@@ -2,8 +2,8 @@
   <service name="obs_scm" mode="manual">
     <param name="url">https://github.com/openSUSE/product-composer</param>
     <param name="scm">git</param>
-    <param name="revision">0.6.13</param>
-    <param name="version">0.6.13</param>
+    <param name="revision">0.8.1</param>
+    <param name="version">0.8.1</param>
   </service>
   <service name="tar" mode="manual" />
   <service name="recompress" mode="manual">

++++++ build.specials.obscpio ++++++

++++++ build.specials.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/.gitignore new/.gitignore
--- old/.gitignore      1970-01-01 01:00:00.000000000 +0100
+++ new/.gitignore      2026-01-14 11:38:25.000000000 +0100
@@ -0,0 +1,5 @@
+.osc
+*.obscpio
+*.osc
+*.obscpio
+*.osc

++++++ product-composer-0.6.13.tar.xz -> product-composer-0.8.1.tar.xz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/product-composer-0.6.13/.flake8 
new/product-composer-0.8.1/.flake8
--- old/product-composer-0.6.13/.flake8 2025-08-20 10:24:04.000000000 +0200
+++ new/product-composer-0.8.1/.flake8  2026-01-13 12:51:05.000000000 +0100
@@ -1,3 +1,4 @@
 [flake8]
-ignore = A,B,W,E12,E22,E26,E3,E501
+ignore = A,B,W,E302,E126,E128,E265,E266,E303
+max-line-length = 196
 exclude = .git,__pycache__,docs/source/conf.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/product-composer-0.6.13/.github/workflows/tests.yaml 
new/product-composer-0.8.1/.github/workflows/tests.yaml
--- old/product-composer-0.6.13/.github/workflows/tests.yaml    2025-08-20 
10:24:04.000000000 +0200
+++ new/product-composer-0.8.1/.github/workflows/tests.yaml     2026-01-13 
12:51:05.000000000 +0100
@@ -25,6 +25,10 @@
         run: |
           pyproject-build
 
+      - name: Run flake8
+        run: |
+          flake8
+
       - name: Run tests
         run: |
           pytest -v
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/product-composer-0.6.13/docs/build_description.adoc 
new/product-composer-0.8.1/docs/build_description.adoc
--- old/product-composer-0.6.13/docs/build_description.adoc     2025-08-20 
10:24:04.000000000 +0200
+++ new/product-composer-0.8.1/docs/build_description.adoc      2026-01-13 
12:51:05.000000000 +0100
@@ -239,6 +239,19 @@
 Enabling this would result in a simply repacked base image, without
 any package copied there.
 
+===== no_product_provides
+
+Product composer is validating by default that a rpm package is providing
+the same product as well via a cpeid provider. It aborts when no
+matching providers is found.
+
+This option can be used to acknowledge that no product provider for the
+product is available. Also no other product must be provided then.
+
+Use with care as it breaks handling of such repositories with management
+solutions like Multi-Linux-Manager.
+
+
 ==== iso
 
 ===== publisher
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/product-composer-0.6.13/src/productcomposer/cli.py 
new/product-composer-0.8.1/src/productcomposer/cli.py
--- old/product-composer-0.6.13/src/productcomposer/cli.py      2025-08-20 
10:24:04.000000000 +0200
+++ new/product-composer-0.8.1/src/productcomposer/cli.py       2026-01-13 
12:51:05.000000000 +0100
@@ -13,7 +13,7 @@
 def main(argv=None) -> int:
     parser = cliparser.build_parser()
     args = parser.parse_args()
-    
+
     filename = args.filename
     if not filename:
         # No subcommand was specified.
@@ -22,9 +22,10 @@
         die(None)
 
     dispatch(args)
-    
+
     return 0
 
+
 if __name__ == "__main__":
     try:
         status = main()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/product-composer-0.6.13/src/productcomposer/commands/__init__.py 
new/product-composer-0.8.1/src/productcomposer/commands/__init__.py
--- old/product-composer-0.6.13/src/productcomposer/commands/__init__.py        
2025-08-20 10:24:04.000000000 +0200
+++ new/product-composer-0.8.1/src/productcomposer/commands/__init__.py 
2026-01-13 12:51:05.000000000 +0100
@@ -15,4 +15,5 @@
     for _, module_name, _ in pkgutil.iter_modules(package.__path__):
         importlib.import_module(f"{__name__}.{module_name}")
 
+
 _load_all_commands()
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/product-composer-0.6.13/src/productcomposer/core/Package.py 
new/product-composer-0.8.1/src/productcomposer/core/Package.py
--- old/product-composer-0.6.13/src/productcomposer/core/Package.py     
2025-08-20 10:24:04.000000000 +0200
+++ new/product-composer-0.8.1/src/productcomposer/core/Package.py      
2026-01-13 12:51:05.000000000 +0100
@@ -124,7 +124,7 @@
             return None
         dirs = {}
         filedevs = h['filedevices']
-        fileinos= h['fileinodes']
+        fileinos = h['fileinodes']
         filesizes = h['filesizes']
         filemodes = h['filemodes']
         dirnames = h['dirnames']
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/product-composer-0.6.13/src/productcomposer/createartifacts/createappstream.py
 
new/product-composer-0.8.1/src/productcomposer/createartifacts/createappstream.py
--- 
old/product-composer-0.6.13/src/productcomposer/createartifacts/createappstream.py
  2025-08-20 10:24:04.000000000 +0200
+++ 
new/product-composer-0.8.1/src/productcomposer/createartifacts/createappstream.py
   2026-01-13 12:51:05.000000000 +0100
@@ -21,7 +21,7 @@
     # Did the process create files?
     main_file = f'{rpmdir}/appdata.xml.gz'
     if not os.path.exists(main_file):
-       return
+        return
 
     # compression type is part of the AppStream spec
     mr = ModifyrepoWrapper(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/product-composer-0.6.13/src/productcomposer/createartifacts/createchecksumfile.py
 
new/product-composer-0.8.1/src/productcomposer/createartifacts/createchecksumfile.py
--- 
old/product-composer-0.6.13/src/productcomposer/createartifacts/createchecksumfile.py
       2025-08-20 10:24:04.000000000 +0200
+++ 
new/product-composer-0.8.1/src/productcomposer/createartifacts/createchecksumfile.py
        2026-01-13 12:51:05.000000000 +0100
@@ -1,6 +1,5 @@
 import os
 from ..utils.runhelper import run_helper
-from ..config import chksums_tool
 
 def create_checksums_file(maindir):
     # Legacy linuxrc expect SHA 256 checksums, so don't follow global default 
here
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/product-composer-0.6.13/src/productcomposer/createartifacts/createiso.py 
new/product-composer-0.8.1/src/productcomposer/createartifacts/createiso.py
--- 
old/product-composer-0.6.13/src/productcomposer/createartifacts/createiso.py    
    2025-08-20 10:24:04.000000000 +0200
+++ new/product-composer-0.8.1/src/productcomposer/createartifacts/createiso.py 
2026-01-13 12:51:05.000000000 +0100
@@ -6,7 +6,9 @@
 def create_iso(outdir, isoconf, workdir, application_id):
     verbose = True if verbose_level > 0 else False
     args = ['/usr/bin/mkisofs', '-quiet', '-p', ISO_PREPARER]
-    args += ['-r', '-pad', '-f', '-J', '-joliet-long']
+    args += ['-r', '-pad', '-f']
+    if isoconf['joliet']:
+        args += ['-J', '-joliet-long']
     if isoconf['publisher']:
         args += ['-publisher', isoconf['publisher']]
     if isoconf['volume_id']:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/product-composer-0.6.13/src/productcomposer/createartifacts/createtree.py 
new/product-composer-0.8.1/src/productcomposer/createartifacts/createtree.py
--- 
old/product-composer-0.6.13/src/productcomposer/createartifacts/createtree.py   
    2025-08-20 10:24:04.000000000 +0200
+++ 
new/product-composer-0.8.1/src/productcomposer/createartifacts/createtree.py    
    2026-01-13 12:51:05.000000000 +0100
@@ -5,6 +5,7 @@
 from ..utils.loggerutils import (die, warn, note)
 from ..utils.rpmutils import (link_rpms_to_tree, unpack_meta_rpms)
 from ..utils.runcreaterepo import run_createrepo
+from ..utils.cpeid import get_cpeid
 from ..createartifacts.createmediadir import create_media_dir
 from ..createartifacts.createchecksumfile import create_checksums_file
 from ..createartifacts.createsusedataxml import create_susedata_xml
@@ -55,7 +56,7 @@
 
     for arch in yml['architectures']:
         note(f"Linking rpms for {arch}")
-        link_rpms_to_tree(maindir, yml, pool, arch, flavor, tree_report, 
supporstatus, supportstatus_override, debugdir, sourcedir)
+        link_rpms_to_tree(maindir, yml, pool, arch, flavor, tree_report, 
supporstatus, supportstatus_override, debugdir, sourcedir, get_cpeid(yml))
 
     for arch in yml['architectures']:
         note(f"Unpack rpms for {arch}")
@@ -157,7 +158,13 @@
             run_helper(args, fatal=('ignore_errors' not in 
yml['installcheck']), failmsg="run installcheck validation")
 
     if 'skip_updateinfos' not in yml['build_options']:
-        create_updateinfo_xml(maindir, yml, pool, flavor, debugdir, sourcedir)
+        if yml['repodata']:
+            if yml['repodata'] == 'all':
+                create_updateinfo_xml(maindir, yml, pool, flavor, debugdir, 
sourcedir)
+            for arch in yml['architectures']:
+                create_updateinfo_xml(maindir, yml, pool, flavor, debugdir, 
sourcedir, arch)
+        else:
+            create_updateinfo_xml(maindir, yml, pool, flavor, debugdir, 
sourcedir)
 
     # Add License File and create extra .license directory
     licensefilename = '/license.tar'
@@ -214,7 +221,12 @@
                 note(f"Export main tree into agama iso file for {agama_arch}")
                 create_agama_iso(outdir, yml['iso'], yml['build_options'], 
pool, workdir, application_id, agama_arch)
             else:
-                create_iso(outdir, yml['iso'], workdir, application_id)
+                iso_config = yml['iso'].copy()
+                if workdir != maindir:
+                    # Ensure that joliet stays disabled on non-primary
+                    # media
+                    iso_config['joliet'] = False
+                create_iso(outdir, iso_config, workdir, application_id)
 
             # cleanup
             if yml['iso']['tree'] == 'drop':
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/product-composer-0.6.13/src/productcomposer/createartifacts/createupdateinfoxml.py
 
new/product-composer-0.8.1/src/productcomposer/createartifacts/createupdateinfoxml.py
--- 
old/product-composer-0.6.13/src/productcomposer/createartifacts/createupdateinfoxml.py
      2025-08-20 10:24:04.000000000 +0200
+++ 
new/product-composer-0.8.1/src/productcomposer/createartifacts/createupdateinfoxml.py
       2026-01-13 12:51:05.000000000 +0100
@@ -17,7 +17,7 @@
     return entry
 
 # Add updateinfo.xml to metadata
-def create_updateinfo_xml(rpmdir, yml, pool, flavor, debugdir, sourcedir):
+def create_updateinfo_xml(rpmdir, yml, pool, flavor, debugdir, sourcedir, 
archsubdir=None):
     if not pool.updateinfos:
         return
 
@@ -37,6 +37,15 @@
 
     uitemp = None
 
+    archlist = yml['architectures']
+    subarchpath = ""
+    if archsubdir:
+        archlist = [archsubdir, "noarch"]
+        subarchpath = archsubdir + "/"
+
+    updateinfo_file = os.path.join(rpmdir, subarchpath, "updateinfo.xml")
+
+    export_updates = {}
     for u in sorted(pool.lookup_all_updateinfos()):
         note("Add updateinfo " + u.location)
         for update in u.root.findall('update'):
@@ -53,11 +62,15 @@
             id_node = update.find('id')
             if len(yml['set_updateinfo_id_prefix']) > 0:
                 # avoid double application of same prefix
-                id_text = re.sub(r'^'+yml['set_updateinfo_id_prefix'], '', 
id_node.text)
+                id_text = re.sub(r'^' + yml['set_updateinfo_id_prefix'], '', 
id_node.text)
                 id_node.text = yml['set_updateinfo_id_prefix'] + id_text
 
             for pkgentry in parent.findall('package'):
                 src = pkgentry.get('src')
+                if archsubdir:
+                    # former run might have prefixed already
+                    src = "../" + pkgentry.get('src').removeprefix("../")
+                    pkgentry.set('src', src)
 
                 # check for embargo date
                 embargo = pkgentry.get('embargo_date')
@@ -81,7 +94,7 @@
                     pkgentry.attrib.pop(internal_attributes, None)
 
                 # check if we have files for the entry
-                if os.path.exists(rpmdir + '/' + src):
+                if os.path.exists(rpmdir + '/' + subarchpath + src):
                     needed = True
                     continue
                 if debugdir and os.path.exists(debugdir + '/' + src):
@@ -101,12 +114,12 @@
                     parent.remove(pkgentry)
                     continue
                 # ignore unwanted architectures
-                if pkgarch != 'noarch' and pkgarch not in yml['architectures']:
+                if pkgarch != 'noarch' and pkgarch not in archlist:
                     parent.remove(pkgentry)
                     continue
 
                 # check if we should have this package
-                if name in main_pkgset_names:
+                if name in main_pkgset_names and not archsubdir:
                     updatepkg = create_updateinfo_package(pkgentry)
                     if main_pkgset.matchespkg(None, updatepkg):
                         warn(f"package {updatepkg} not found")
@@ -119,22 +132,60 @@
                     die(f'Stumbled over an updateinfo.xml where no rpm is 
used: {id_node.text}')
                 continue
 
-            if not uitemp:
-                uitemp = open(rpmdir + '/updateinfo.xml', 'x')
-                uitemp.write("<updates>\n  ")
-            uitemp.write(ET.tostring(update, encoding=ET_ENCODING))
-
-    if uitemp:
+            update_id = update.find('id').text
+            if update_id in export_updates:
+                # same entry id, compare allmost all elements
+                for element in update:
+                    if element.tag == 'pkglist':
+                        # we merged it before
+                        continue
+                    if element.tag == 'issued':
+                        # we accept a difference here
+                        continue
+                    # compare element effective result only
+                    if ET.tostring(element) != 
ET.tostring(export_updates[update_id].find(element.tag)):
+                        die(f"Error: updateinfos {update_id} differ in element 
{element.tag}")
+
+                if len(update) != len(export_updates[update_id]):
+                    die(f"Error: updateinfos {update_id} have different amount 
of elements")
+
+                # entry already exists, we need to merge it
+                export_collection = 
export_updates[update_id].findall('pkglist')[0].findall('collection')[0]
+                collection = 
update.findall('pkglist')[0].findall('collection')[0]
+                for pkgentry in collection.findall('package'):
+                    for existing_entry in export_collection.findall('package'):
+                        if existing_entry.get('name') != pkgentry.get('name'):
+                            continue
+                        if existing_entry.get('epoch') != 
pkgentry.get('epoch'):
+                            continue
+                        if existing_entry.get('version') != 
pkgentry.get('version'):
+                            continue
+                        if existing_entry.get('release') != 
pkgentry.get('release'):
+                            continue
+                        if existing_entry.get('arch') != pkgentry.get('arch'):
+                            continue
+                        break    # same entry exists, so break for skipping 
the else part
+                    else:
+                        # add the pkgentry to existing element
+                        export_collection.append(pkgentry)
+            else:
+                # new entry
+                export_updates[update_id] = update
+
+    if export_updates:
+        uitemp = open(updateinfo_file, 'x')
+        uitemp.write("<updates>\n  ")
+        for update in sorted(export_updates):
+            uitemp.write(ET.tostring(export_updates[update], 
encoding=ET_ENCODING))
         uitemp.write("</updates>\n")
         uitemp.close()
 
         mr = ModifyrepoWrapper(
-                file=os.path.join(rpmdir, "updateinfo.xml"),
-                directory=os.path.join(rpmdir, "repodata"),
-                )
+                file=updateinfo_file,
+                directory=os.path.join(rpmdir, subarchpath, "repodata"))
         mr.run_cmd()
 
-        os.unlink(rpmdir + '/updateinfo.xml')
+        os.unlink(updateinfo_file)
 
     if missing_package and 'ignore_missing_packages' not in 
yml['build_options']:
         die('Abort due to missing packages for updateinfo')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/product-composer-0.6.13/src/productcomposer/parsers/yamlparser.py 
new/product-composer-0.8.1/src/productcomposer/parsers/yamlparser.py
--- old/product-composer-0.6.13/src/productcomposer/parsers/yamlparser.py       
2025-08-20 10:24:04.000000000 +0200
+++ new/product-composer-0.8.1/src/productcomposer/parsers/yamlparser.py        
2026-01-13 12:51:05.000000000 +0100
@@ -8,7 +8,7 @@
 
 
 
-def parse_yaml(filename: str, flavor: str | None) -> Dict[str, any]:
+def parse_yaml(filename: str, flavor: str | None) -> Dict[str, Any]:
     with open(filename, 'r') as file:
         _yml = yaml.safe_load(file)
 
@@ -39,6 +39,7 @@
             'summary',
             'version',
             'version_from_package',
+            'installcheck',
             'update',
             'edition',
             'product_type',
@@ -50,6 +51,7 @@
             'unpack',
             'set_updateinfo_from',
             'set_updateinfo_id_prefix',
+            'block_updates_under_embargo',
         ):
             if f.get(tag, None):
                 yml[tag] = f[tag]
@@ -62,7 +64,7 @@
         if f['iso']:
             if not yml['iso']:
                 yml['iso'] = compose_schema_iso().dict()
-            for tag in ('volume_id', 'publisher', 'tree', 'base'):
+            for tag in ('volume_id', 'publisher', 'tree', 'base', 'joliet'):
                 if f['iso'].get(tag):
                     yml['iso'][tag] = f['iso'][tag]
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/product-composer-0.6.13/src/productcomposer/utils/cpeid.py 
new/product-composer-0.8.1/src/productcomposer/utils/cpeid.py
--- old/product-composer-0.6.13/src/productcomposer/utils/cpeid.py      
1970-01-01 01:00:00.000000000 +0100
+++ new/product-composer-0.8.1/src/productcomposer/utils/cpeid.py       
2026-01-13 12:51:05.000000000 +0100
@@ -0,0 +1,14 @@
+from ..utils.loggerutils import die
+
+def get_cpeid(yml):
+    if yml['product_type'] not in ('base', 'module', 'extension', None):
+        die('Undefined product-type')
+    cpeid = f"cpe:/o:{yml['vendor']}:{yml['name']}:{yml['version']}"
+    if yml['update']:
+        cpeid = cpeid + f":{yml['update']}"
+        if yml['edition']:
+            cpeid = cpeid + f":{yml['edition']}"
+    elif yml['edition']:
+        cpeid = cpeid + f"::{yml['edition']}"
+
+    return cpeid.lower()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/product-composer-0.6.13/src/productcomposer/utils/loggerutils.py 
new/product-composer-0.8.1/src/productcomposer/utils/loggerutils.py
--- old/product-composer-0.6.13/src/productcomposer/utils/loggerutils.py        
2025-08-20 10:24:04.000000000 +0200
+++ new/product-composer-0.8.1/src/productcomposer/utils/loggerutils.py 
2026-01-13 12:51:05.000000000 +0100
@@ -1,4 +1,7 @@
-def die(msg, details=None):
+from typing import NoReturn, Optional
+
+
+def die(msg: Optional[str], details: Optional[str] = None) -> NoReturn:
     if msg:
         print("ERROR: " + msg)
     if details:
@@ -6,7 +9,7 @@
     raise SystemExit(1)
 
 
-def warn(msg, details=None):
+def warn(msg: str, details: Optional[str] = None) -> None:
     print("WARNING: " + msg)
     if details:
         print(details)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/product-composer-0.6.13/src/productcomposer/utils/rpmutils.py 
new/product-composer-0.8.1/src/productcomposer/utils/rpmutils.py
--- old/product-composer-0.6.13/src/productcomposer/utils/rpmutils.py   
2025-08-20 10:24:04.000000000 +0200
+++ new/product-composer-0.8.1/src/productcomposer/utils/rpmutils.py    
2026-01-13 12:51:05.000000000 +0100
@@ -1,6 +1,7 @@
 import os
 import re
 import shutil
+import urllib.parse
 
 from ..core.PkgSet import PkgSet
 from ..utils.loggerutils import die, note, warn
@@ -150,7 +151,7 @@
     if missing_package and 'ignore_missing_packages' not in 
yml['build_options']:
         die('Abort due to missing meta packages')
 
-def link_rpms_to_tree(rpmdir, yml, pool, arch, flavor, tree_report, 
supportstatus, supportstatus_override, debugdir=None, sourcedir=None):
+def link_rpms_to_tree(rpmdir, yml, pool, arch, flavor, tree_report, 
supportstatus, supportstatus_override, debugdir=None, sourcedir=None, 
cpeid=None):
     singlemode = True
     if 'take_all_available_versions' in yml['build_options']:
         singlemode = False
@@ -180,6 +181,8 @@
     ###
 
     missing_package = None
+    found_matching_cpeid = None
+    empty_medium = True
     for sel in main_pkgset:
         if singlemode:
             rpm = pool.lookup_rpm(arch, sel.name, sel.op, sel.epoch, 
sel.version, sel.release)
@@ -194,6 +197,7 @@
             missing_package = True
             continue
 
+        empty_medium = False
         for rpm in rpms:
             if referenced_update_rpms is not None:
                 if (rpm.arch + '/' + rpm.canonfilename) not in 
referenced_update_rpms:
@@ -211,6 +215,17 @@
                 warn(f"package {rpm} does not have a source rpm")
                 continue
 
+            if cpeid:
+                for provide in rpm.provides:
+                    if provide.startswith('product-cpeid() = '):
+                        cpeid_provided = 
urllib.parse.unquote_plus(provide.removeprefix('product-cpeid() = '))
+                        if 'no_product_provides' in yml['build_options']:
+                            die(f"no_product_provides option is set, but 
product {cpeid_provided} is provided by {rpm.canonfilename}")
+                        if cpeid != cpeid_provided:
+                            warn(f"rpm package {rpm} provides an additional 
cpeid: {cpeid_provided}")
+                        else:
+                            found_matching_cpeid = True
+
             if sourcedir:
                 # so we need to add also the src rpm
                 srpm = pool.lookup_rpm(srcrpm.arch, srcrpm.name, '=', None, 
srcrpm.version, srcrpm.release)
@@ -239,3 +254,9 @@
 
     if missing_package and 'ignore_missing_packages' not in 
yml['build_options']:
         die('Abort due to missing packages')
+
+    if empty_medium:
+        warn("This medium is not providing any rpm. Only online installation 
is possible.")
+    elif cpeid and not found_matching_cpeid and 'no_product_provides' not in 
yml['build_options']:
+        die(f"Product release file with matching cpeid {cpeid} not found!")
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/product-composer-0.6.13/src/productcomposer/utils/runcreaterepo.py 
new/product-composer-0.8.1/src/productcomposer/utils/runcreaterepo.py
--- old/product-composer-0.6.13/src/productcomposer/utils/runcreaterepo.py      
2025-08-20 10:24:04.000000000 +0200
+++ new/product-composer-0.8.1/src/productcomposer/utils/runcreaterepo.py       
2026-01-13 12:51:05.000000000 +0100
@@ -1,25 +1,12 @@
 import os
 import subprocess
 from ..wrappers import CreaterepoWrapper
-from ..utils.loggerutils import die
+from ..utils.cpeid import get_cpeid
 
 def run_createrepo(rpmdir, yml, content=[], repos=[]):
-    match yml['product_type']:
-        case 'base' | None:
-            product_type = '/o'
-        case 'module' | 'extension':
-            product_type = '/a'
-        case _:
-            die('Undefined product-type')
     cr = CreaterepoWrapper(directory=".")
     cr.distro = f"{yml.get('summary', yml['name'])} {yml['version']}"
-    cr.cpeid = 
f"cpe:{product_type}:{yml['vendor']}:{yml['name']}:{yml['version']}"
-    if yml['update']:
-        cr.cpeid = cr.cpeid + f":{yml['update']}"
-        if yml['edition']:
-            cr.cpeid = cr.cpeid + f":{yml['edition']}"
-    elif yml['edition']:
-        cr.cpeid = cr.cpeid + f"::{yml['edition']}"
+    cr.cpeid = get_cpeid(yml)
     cr.repos = repos
     # cr.split = True
     # cr.baseurl = "media://"
@@ -34,4 +21,4 @@
         for arch in yml['architectures']:
             if os.path.isdir(f"{rpmdir}/{arch}"):
                 cr.arch_specific_repodata = arch
-                cr.run_cmd(cwd=rpmdir, stdout=subprocess.PIPE)
\ No newline at end of file
+                cr.run_cmd(cwd=rpmdir, stdout=subprocess.PIPE)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/product-composer-0.6.13/src/productcomposer/utils/runhelper.py 
new/product-composer-0.8.1/src/productcomposer/utils/runhelper.py
--- old/product-composer-0.6.13/src/productcomposer/utils/runhelper.py  
2025-08-20 10:24:04.000000000 +0200
+++ new/product-composer-0.8.1/src/productcomposer/utils/runhelper.py   
2026-01-13 12:51:05.000000000 +0100
@@ -16,9 +16,9 @@
 
     if popen.returncode:
         if failmsg:
-            msg="Failed to " + failmsg
+            msg = "Failed to " + failmsg
         else:
-            msg="Failed to run " + args[0]
+            msg = "Failed to run " + args[0]
         if fatal:
             die(msg, details=output)
         else:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/product-composer-0.6.13/src/productcomposer/verifiers/composeschema.py 
new/product-composer-0.8.1/src/productcomposer/verifiers/composeschema.py
--- old/product-composer-0.6.13/src/productcomposer/verifiers/composeschema.py  
2025-08-20 10:24:04.000000000 +0200
+++ new/product-composer-0.8.1/src/productcomposer/verifiers/composeschema.py   
2026-01-13 12:51:05.000000000 +0100
@@ -11,6 +11,7 @@
     volume_id: Optional[str] = None
     tree: Optional[str] = None
     base: Optional[str] = None
+    joliet: Optional[bool] = True
 
 
 _compose_schema_supportstatus = Literal[
@@ -40,7 +41,7 @@
     product_class: Optional[str] = Field(default=None, alias='product-class')
     free: Optional[bool] = False
     predecessors: Optional[list[compose_schema_scc_cpe]] = None
-    product_name_prefix: Optional[str] = Field(default=None, 
alias='product-name-prefix') # default is SLE-Product- in scc converter
+    product_name_prefix: Optional[str] = Field(default=None, 
alias='product-name-prefix')  # default is SLE-Product- in scc converter
     shortname: Optional[str] = None
     base_products: Optional[list[compose_schema_scc_cpe]] = None
     root_products: Optional[list[compose_schema_scc_cpe]] = None
@@ -49,14 +50,17 @@
 
 
 compose_schema_build_option = Literal[
+    'abort_on_empty_updateinfo',
     'add_slsa_provenance',
     'base_skip_packages',
     'block_updates_under_embargo',
     'hide_flavor_in_product_directory_name',
     'ignore_missing_packages',
+    'no_product_provides',
     'skip_updateinfos',
     'take_all_available_versions',
     'updateinfo_packages_only',
+    'OBS_unordered_product_repos',
 ]
 
 compose_schema_source_and_debug = Literal['drop', 'include', 'split']
@@ -80,17 +84,17 @@
     build_options: Optional[list[compose_schema_build_option]] = []
     scc: Optional[compose_schema_scc] = None
     iso: Optional[compose_schema_iso] = None
+    installcheck: Optional[list[Literal['ignore_errors']]] | None = None
+
+    set_updateinfo_from: Optional[str] = None
+    set_updateinfo_id_prefix: Optional[str] = ""
+    block_updates_under_embargo: Optional[str] = None
 
 class ComposeSchema(compose_schema, BaseModel):
     product_compose_schema: Literal['0.1', '0.2']
     vendor: str
     bcntsynctag: Optional[str] = None
     milestone: Optional[str] = None
-    installcheck: Optional[list[Literal['ignore_errors']]] | None = None
-
-    set_updateinfo_from: Optional[str] = None
-    set_updateinfo_id_prefix: Optional[str] = ""
-    block_updates_under_embargo: Optional[str] = None
 
     flavors: Optional[dict[str, compose_schema]] = {}
     packagesets: Optional[list[compose_schema_packageset]] = None
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/product-composer-0.6.13/tests/unit/parsers/test_eulasparser.py 
new/product-composer-0.8.1/tests/unit/parsers/test_eulasparser.py
--- old/product-composer-0.6.13/tests/unit/parsers/test_eulasparser.py  
2025-08-20 10:24:04.000000000 +0200
+++ new/product-composer-0.8.1/tests/unit/parsers/test_eulasparser.py   
2026-01-13 12:51:05.000000000 +0100
@@ -1,4 +1,3 @@
-import pytest
 from productcomposer.parsers.eulasparser import parse_eulas
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/product-composer-0.6.13/tests/unit/parsers/test_supportstatusparser.py 
new/product-composer-0.8.1/tests/unit/parsers/test_supportstatusparser.py
--- old/product-composer-0.6.13/tests/unit/parsers/test_supportstatusparser.py  
2025-08-20 10:24:04.000000000 +0200
+++ new/product-composer-0.8.1/tests/unit/parsers/test_supportstatusparser.py   
2026-01-13 12:51:05.000000000 +0100
@@ -1,4 +1,3 @@
-import pytest
 from productcomposer.parsers.supportstatusparser import parse_supportstatus
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/product-composer-0.6.13/tests/unit/parsers/test_yamlparser.py 
new/product-composer-0.8.1/tests/unit/parsers/test_yamlparser.py
--- old/product-composer-0.6.13/tests/unit/parsers/test_yamlparser.py   
2025-08-20 10:24:04.000000000 +0200
+++ new/product-composer-0.8.1/tests/unit/parsers/test_yamlparser.py    
2026-01-13 12:51:05.000000000 +0100
@@ -1,4 +1,3 @@
-import pydantic
 import pytest
 
 from productcomposer.parsers.yamlparser import parse_yaml
@@ -10,8 +9,8 @@
 
 
 def test_yamlparser_wrong_schema(capsys):
-    with pytest.raises(SystemExit) as exc:
-        yml = parse_yaml(
+    with pytest.raises(SystemExit):
+        parse_yaml(
             'tests/assets/yamls/UnsupportedSchemaVer.productcompose', 
'backports_x86_64'
         )
 
@@ -19,32 +18,31 @@
 
 
 def test_yamlparser_missing_schema(capsys):
-    with pytest.raises(SystemExit) as exc:
-        yml = parse_yaml(
+    with pytest.raises(SystemExit):
+        parse_yaml(
             'tests/assets/yamls/MissingSchemaVer.productcompose', 
'backports_x86_64'
         )
     assert 'field required' in capsys.readouterr().out.lower()
 
 
 def test_yamlparser_invalid_schema(capsys):
-    with pytest.raises(SystemExit) as exc:
-        yml = parse_yaml('tests/assets/yamls/InvalidSchema.productcompose', 
'backports')
+    with pytest.raises(SystemExit):
+        parse_yaml('tests/assets/yamls/InvalidSchema.productcompose', 
'backports')
     assert 'Flavor not found' in capsys.readouterr().out
 
 
 def test_yamlparser_flavor_notfound(capsys):
     with pytest.raises(SystemExit) as excinfo:
-        yml = parse_yaml('tests/assets/yamls/Backports.productcompose', 
'ports_x86_64')
+        parse_yaml('tests/assets/yamls/Backports.productcompose', 
'ports_x86_64')
 
-    assert excinfo.type == SystemExit
-    assert excinfo.value.code == 1
+        assert excinfo.value.code == 1
 
     assert 'ERROR: Flavor not found' in capsys.readouterr().out
 
 
 def test_yamlparser_invalid_buildoption(capsys):
-    with pytest.raises(SystemExit) as exc:
-        yml = parse_yaml(
+    with pytest.raises(SystemExit):
+        parse_yaml(
             'tests/assets/yamls/InvalidBuildOption.productcompose', 
'backports_x86_64'
         )
 

++++++ product-composer.obsinfo ++++++
--- /var/tmp/diff_new_pack.IrU3tT/_old  2026-01-15 16:45:38.426741209 +0100
+++ /var/tmp/diff_new_pack.IrU3tT/_new  2026-01-15 16:45:38.430741376 +0100
@@ -1,5 +1,5 @@
 name: product-composer
-version: 0.6.13
-mtime: 1755678244
-commit: 9c31dad3c56ef7f8fec3671208447e8a67673597
+version: 0.8.1
+mtime: 1768305065
+commit: 893d9bd96dc22df8fda76c81e99ab0f5b9d79100
 

Reply via email to