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