Compare commits

...

2 Commits

Author SHA1 Message Date
William Douglas 65cf152900 Add the Provide: pypi in most cases
Usually the pypi ecosystem will do the right thing and respond with a
name but in cases where it does not (bad network, bad json, message
without a name), still figure out a reasonable Provide to add.

Signed-off-by: William Douglas <william.douglas@intel.com>
2025-05-27 13:27:28 -07:00
William Douglas 99a7985f29 Add has_license config
In cases where a package's license subpackage disappears, prefer
failing the build as it should have some manual investigation.

Signed-off-by: William Douglas <william.douglas@intel.com>
2025-05-09 10:33:35 -07:00
5 changed files with 118 additions and 9 deletions
+7
View File
@@ -277,6 +277,9 @@ def package(args, url, name, archives, workingdir):
# specfile template is assumed "correct" and any failures need to be manually addressed
break
filemanager.load_specfile(specfile)
if 'license' in specfile.packages and not conf.config_opts['has_license']:
conf.config_opts['has_license'] = True
conf.rewrite_config_opts()
specfile.write_spec()
filemanager.newfiles_printed = 0
mock_chroot = "/var/lib/mock/clear-{}/root/builddir/build/BUILDROOT/" \
@@ -297,6 +300,10 @@ def package(args, url, name, archives, workingdir):
conf.create_buildreq_cache(content.version, requirements.buildreqs_cache)
print_build_failed()
sys.exit(1)
elif 'license' not in specfile.packages and conf.config_opts['has_license']:
print_fatal("package -license subpackage deleted")
conf.create_buildreq_cache(content.version, requirements.buildreqs_cache)
sys.exit(1)
elif os.path.isfile("README.clear"):
try:
print("\nREADME.clear CONTENTS")
+9 -4
View File
@@ -709,6 +709,7 @@ class Requirements(object):
"""Use pypi for getting package requires and metadata."""
# First look for a local override
pypi_json = ""
pypi_name = pypidata.get_pypi_name(name)
pypi_file = os.path.join(config.download_path, "pypi.json")
if os.path.isfile(pypi_file):
with open(pypi_file, "r") as pfile:
@@ -716,16 +717,20 @@ class Requirements(object):
else:
# Try and grab the pypi details for the package
if config.alias:
name = config.alias
pypi_name = pypidata.get_pypi_name(name)
pypi_name = config.alias
pypi_name = pypidata.get_pypi_name(pypi_name)
pypi_json = pypidata.get_pypi_metadata(pypi_name)
if pypi_json:
if not pypi_json:
self.pypi_provides = pypi_name
else:
try:
package_pypi = json.loads(pypi_json)
except json.JSONDecodeError:
package_pypi = {}
package_pypi = {"name": pypi_name}
if package_pypi.get("name"):
self.pypi_provides = package_pypi["name"]
else:
self.pypi_provides = pypi_name
if package_pypi.get("requires"):
for pkg in package_pypi["requires"]:
self.add_requires(f"pypi({pkg})", config.os_packages, override=True, subpkg="python3")
+1
View File
@@ -193,6 +193,7 @@ class Config(object):
"no_glob": "Do not use the replacement pattern for file matching",
"allow_exe": "Allow Windows executables (*.exe, *.dll) to be packaged",
"use_ninja": "Use ninja build files",
"has_license": "Require license subpackage for successful build",
}
# simple_pattern_pkgconfig patterns
# contains patterns for parsing build.log for missing dependencies
+12 -5
View File
@@ -27,6 +27,15 @@ def pkg_search(name):
return False
def fixup_pypi_prefix(name):
"""Try and chop off the 'pypi-' or 'python-' prefix for names."""
name = name.lower().replace('-', '_')
for prefix in ["pypi_", "python_"]:
if name.startswith(prefix):
name = name[len(prefix):]
return name
def get_pypi_name(name, miss=False):
"""Try and verify the pypi name for a given package name."""
# normalize the name for matching as pypi is case insensitve for search
@@ -35,11 +44,9 @@ def get_pypi_name(name, miss=False):
if pkg_search(name):
return name
# Maybe we have a prefix
for prefix in ["pypi_", "python_"]:
if name.startswith(prefix):
name = name[len(prefix):]
if pkg_search(name):
return name
name = fixup_pypi_prefix(name)
if pkg_search(name):
return name
# Some cases where search fails (Sphinx)
# Just try the name we were given
if miss:
+89
View File
@@ -499,6 +499,95 @@ class TestBuildreq(unittest.TestCase):
self.assertEqual(self.reqs.requires['python3'], pypi_requires)
self.assertEqual(ssummary, summary)
def test_scan_for_configure_pypi_no_name_in_json(self):
"""
Test scan_for_configure when distutils is being used for the build
pattern to test connecting to pypi but not getting a name back.
"""
orig_summary = buildreq.specdescription.default_summary
orig_sscore = buildreq.specdescription.default_summary_score
orig_pypi_name = buildreq.pypidata.get_pypi_name
orig_pypi_meta = buildreq.pypidata.get_pypi_metadata
name = "pypi-name"
content = json.dumps({})
buildreq.pypidata.pkg_search = MagicMock(return_value=False)
buildreq.pypidata.get_pypi_metadata = MagicMock(return_value=content)
with tempfile.TemporaryDirectory() as tmpd:
conf = config.Config(tmpd)
conf.config_opts['use_ninja'] = False
os.mkdir(os.path.join(tmpd, 'subdir'))
open(os.path.join(tmpd, 'subdir', 'pyproject.toml'), 'w').close()
self.reqs.scan_for_configure(os.path.join(tmpd, 'subdir'), name, conf)
post_summary = buildreq.specdescription.default_summary
buildreq.specdescription.default_summary = orig_summary
buildreq.specdescription.default_summary_score = orig_sscore
buildreq.pypidata.get_pypi_name = orig_pypi_name
buildreq.pypidata.get_pypi_metadata = orig_pypi_meta
self.assertEqual(self.reqs.pypi_provides, "name")
self.assertEqual(post_summary, orig_summary)
def test_scan_for_configure_pypi_no_json(self):
"""
Test scan_for_configure when distutils is being used for the build
pattern to test being unable to connect to pypi.
"""
orig_summary = buildreq.specdescription.default_summary
orig_sscore = buildreq.specdescription.default_summary_score
orig_pypi_name = buildreq.pypidata.get_pypi_name
orig_pypi_meta = buildreq.pypidata.get_pypi_metadata
name = "pypi-name"
buildreq.pypidata.pkg_search = MagicMock(return_value=False)
buildreq.pypidata.get_pypi_metadata = MagicMock(return_value="")
with tempfile.TemporaryDirectory() as tmpd:
conf = config.Config(tmpd)
conf.config_opts['use_ninja'] = False
os.mkdir(os.path.join(tmpd, 'subdir'))
open(os.path.join(tmpd, 'subdir', 'pyproject.toml'), 'w').close()
self.reqs.scan_for_configure(os.path.join(tmpd, 'subdir'), name, conf)
post_summary = buildreq.specdescription.default_summary
buildreq.specdescription.default_summary = orig_summary
buildreq.specdescription.default_summary_score = orig_sscore
buildreq.pypidata.get_pypi_name = orig_pypi_name
buildreq.pypidata.get_pypi_metadata = orig_pypi_meta
self.assertEqual(self.reqs.pypi_provides, "name")
self.assertEqual(post_summary, orig_summary)
def test_scan_for_configure_pypi_bad_json(self):
"""
Test scan_for_configure when distutils is being used for the build
pattern to test being given bad json data.
"""
orig_summary = buildreq.specdescription.default_summary
orig_sscore = buildreq.specdescription.default_summary_score
orig_pypi_name = buildreq.pypidata.get_pypi_name
orig_pypi_meta = buildreq.pypidata.get_pypi_metadata
orig_json_loads = buildreq.json.loads
name = "pypi-name"
content = json.dumps({})
buildreq.pypidata.pkg_search = MagicMock(return_value=False)
buildreq.pypidata.get_pypi_metadata = MagicMock(return_value=content)
buildreq.json.loads = MagicMock(side_effect=json.JSONDecodeError("", "", 0))
with tempfile.TemporaryDirectory() as tmpd:
conf = config.Config(tmpd)
conf.config_opts['use_ninja'] = False
os.mkdir(os.path.join(tmpd, 'subdir'))
open(os.path.join(tmpd, 'subdir', 'pyproject.toml'), 'w').close()
self.reqs.scan_for_configure(os.path.join(tmpd, 'subdir'), name, conf)
post_summary = buildreq.specdescription.default_summary
buildreq.specdescription.default_summary = orig_summary
buildreq.specdescription.default_summary_score = orig_sscore
buildreq.pypidata.get_pypi_name = orig_pypi_name
buildreq.pypidata.get_pypi_metadata = orig_pypi_meta
buildreq.json.loads = orig_json_loads
self.assertEqual(self.reqs.pypi_provides, "name")
self.assertEqual(post_summary, orig_summary)
def test_scan_for_configure_pypi_override(self):
"""
Test scan_for_configure when distutils is being used for the build