diff --git a/bconds.py b/bconds.py index 586904da..b2bc0cb5 100644 --- a/bconds.py +++ b/bconds.py @@ -1,12 +1,22 @@ +import argparse +import datetime import functools +import json +import os import pathlib -import re -import subprocess import sys -from utils import CONFIG, log +from gitrepo import patch_spec, refresh_or_clone +from utils import CONFIG, log, run KOJI_ID_FILENAME = 'koji.id' +SRPM_EXTENSION = '.src.rpm' +SPEC_EXTENSION = '.spec' + +KOJI_STATE_CLOSED = 'closed' +KOJI_FAILED_STATES = ('canceled', 'failed') + +KOJI_ARTIFACT_RETENTION_DAYS = 7 reverse_id_lookup = {} @@ -35,42 +45,7 @@ def bcond_cache_identifier(component_name, bcond_config, *, branch='', target='' branch = '' identifier = f'{component_name}:{withouts_id}:{withs_id}:{replacements_id}:{branch}:{target}' reverse_id_lookup[identifier] = bcond_config - return identifier - - -def run(*cmd, **kwargs): - kwargs.setdefault('check', True) - kwargs.setdefault('capture_output', True) - kwargs.setdefault('text', True) - return subprocess.run(cmd, **kwargs) - - -def clone_into(component_name, target, branch=''): - branch = branch or CONFIG['distgit']['branch'] - log(f' • Cloning {component_name} into "{target}"...', end=' ') - # I would like to use --depth=1 but that breaks rpmautospec - # https://pagure.io/fedora-infra/rpmautospec/issue/227 - run('fedpkg', 'clone', component_name, target, f'--branch={branch}') - log('done.') - - -def refresh_gitrepo(repopath, prune_exisitng=False): - log(f' • Refreshing "{repopath}" git repo...', end=' ') - git = 'git', '-C', repopath - head_before = run(*git, 'rev-parse', 'HEAD').stdout.rstrip() - run(*git, 'stash') - run(*git, 'reset', '--hard') - run(*git, 'pull') - head_after = run(*git, 'rev-parse', 'HEAD').stdout.rstrip() - if head_before == head_after: - if not prune_exisitng: - # we try to preserve the changes for local inspection, but if it fails, meh - run(*git, 'stash', 'pop', check=False) - log('already up to date.') - return False - else: - log(f'updated {head_before[:10]}..{head_after[:10]}.') - return True + return identifier def srpm_path(directory): @@ -79,36 +54,14 @@ def srpm_path(directory): Returns None if not there. Raises RuntimeError when multiple SRPMs are found. """ - candidates = list(directory.glob('*.src.rpm')) + candidates = list(directory.glob(f'*{SRPM_EXTENSION}')) if not candidates: return None - if count := len(candidates) > 1: + if (count := len(candidates)) > 1: raise RuntimeError(f'Found {count} SRPMs in {directory}.') return candidates[0] -def patch_spec(specpath, bcond_config): - log(f' • Patching {specpath.name}') - - run('git', '-C', specpath.parent, 'reset', '--hard') - - spec_text = specpath.read_text() - - lines = [] - for without in sorted(bcond_config.get('withouts', ())): - if without in bcond_config.get('withs', ()): - raise ValueError(f'Cannot have the same with and without: {without}') - lines.append(f'%global _without_{without} 1') - for with_ in sorted(bcond_config.get('withs', ())): - lines.append(f'%global _with_{with_} 1') - for macro, value in bcond_config.get('replacements', {}).items(): - spec_text = re.sub(fr'^(\s*)%(define|global)(\s+){macro}(\s+)\S.*$', - fr'\1%\2\g<3>{macro}\g<4>{value}', - spec_text, flags=re.MULTILINE) - lines.append(spec_text) - specpath.write_text('\n'.join(lines)) - - def submit_scratchbuild(repopath, target=''): command = ('fedpkg', 'build', '--scratch', '--srpm', f'--arches={CONFIG["architectures"]["koji"]}', '--nowait', '--background') @@ -139,10 +92,21 @@ def koji_status(koji_id): for line in output: if line.startswith('State: '): return line.split(' ')[-1] - raise RuntimeError('Carnot parse koji taskinfo output') + raise RuntimeError('Cannot parse koji taskinfo output') -def handle_exisitng_srpm(repopath, *, was_updated): +def koji_id_is_older_than_week(koji_id_path): + """ + Returns True if koji_id file was created earlier than the retention period. + We assume this is a threshold time after which Koji artifacts are deleted, + and we need to remove koji_id to retrigger the scratchbuild. + """ + koji_id_mtime = datetime.datetime.fromtimestamp(os.path.getmtime(koji_id_path)) + retention_cutoff = datetime.datetime.now() - datetime.timedelta(days=KOJI_ARTIFACT_RETENTION_DAYS) + return koji_id_mtime < retention_cutoff + + +def handle_existing_srpm(repopath, *, was_updated): srpm = srpm_path(repopath) if srpm and not was_updated: log(f' • Found {srpm.name}, will not rebuild; remove it to force me.') @@ -152,7 +116,7 @@ def handle_exisitng_srpm(repopath, *, was_updated): return None -def handle_exisitng_koji_id(repopath, *, was_updated): +def handle_existing_koji_id(repopath, *, was_updated): koji_id_path = repopath / KOJI_ID_FILENAME if koji_id_path.exists(): if was_updated: @@ -161,59 +125,102 @@ def handle_exisitng_koji_id(repopath, *, was_updated): else: koji_task_id = koji_id_path.read_text() status = koji_status(koji_task_id) - if status in ('canceled', 'failed'): + if status in KOJI_FAILED_STATES: log(f' • Koji task {koji_task_id} is {status}; ' f'removing {KOJI_ID_FILENAME}.') koji_id_path.unlink() return None + elif status == KOJI_STATE_CLOSED and koji_id_is_older_than_week(koji_id_path): + log(f' • Koji task {koji_task_id} is older than {KOJI_ARTIFACT_RETENTION_DAYS} days, ' + f'there may be nothing to download; removing {KOJI_ID_FILENAME}.') + koji_id_path.unlink() else: log(f' • Koji task {koji_task_id} is {status}; ' - f'not rebulding (rm {KOJI_ID_FILENAME} to force).') + f'not rebuilding (rm {KOJI_ID_FILENAME} to force).') return koji_task_id -def scratchbuild_patched_if_needed(component_name, bcond_config, *, branch='', target=''): +def check_existing_artifacts(repopath, was_updated, bcond_config): """ - This will: - 1. clone/fetch the given component_name package from Fedora to fedpkg_cache_dir - in case the repo existed and HEAD was not updated, this ends early if: - - a SRPM exists - - a previously stored Koji task ID is present and not canceled or failed - (both information is added to the provided bcond_config) - 2. change the specfile to apply the given bcond/macro config - 3. scratchbuild the package in Koji (in a given target if specified) - 4. cleanup the generated SRPM - 5. write the Koji ID to KOJI_ID_FILENAME in the repo directory and to bcond_config - 6. return True if something was submitted to Koji + Check if we can reuse existing SRPM or Koji task. + + Args: + repopath: Path to the repository + was_updated: Whether the repository was just updated + bcond_config: Configuration dict to update with found artifacts + + Returns: + bool: True if existing artifact found and can be reused (no rebuild needed) """ - repopath = pathlib.Path(CONFIG['cache_dir']['fedpkg']) / bcond_config['id'] - if repopath.exists(): - news = refresh_gitrepo(repopath) - else: - pathlib.Path(CONFIG['cache_dir']['fedpkg']).mkdir(exist_ok=True) - clone_into(component_name, repopath, branch=branch) - news = True - - if srpm := handle_exisitng_srpm(repopath, was_updated=news): + if srpm := handle_existing_srpm(repopath, was_updated=was_updated): bcond_config['srpm'] = srpm - return False - - if koji_id := handle_exisitng_koji_id(repopath, was_updated=news): + return True + + if koji_id := handle_existing_koji_id(repopath, was_updated=was_updated): bcond_config['koji_task_id'] = koji_id - return False + return True + + return False - specpath = repopath / f'{component_name}.spec' + +def prepare_spec_for_build(component_name, repopath, bcond_config): + """ + Prepare the spec file by applying bcond patches and optionally bumping release. + + Args: + component_name: Name of the component + repopath: Path to the repository + bcond_config: Configuration with bcond settings + + Returns: + Path to the prepared spec file + """ + specpath = repopath / f'{component_name}{SPEC_EXTENSION}' patch_spec(specpath, bcond_config) + if 'bootstrap' in bcond_config.get('withs', ()): # bump the release not to create an older EVR with ~bootstrap # this is useful if we build the testing SRPMs in copr run('rpmdev-bumpspec', '--rightmost', specpath) + + return specpath + +def scratchbuild_patched_if_needed(component_name, bcond_config, *, branch='', target='', no_git_refresh=False): + """ + Clone/refresh a component repository and submit a Koji scratchbuild if needed. + + This function: + 1. Clones/fetches the component package from Fedora to fedpkg_cache_dir + 2. Checks for existing SRPM or Koji task (returns early if found) + 3. Prepares the specfile with bcond/macro patches + 4. Submits a scratchbuild to Koji + 5. Updates bcond_config with the Koji task ID + + Args: + component_name: Name of the component to build + bcond_config: Configuration dict with bcond settings and build cache identifier + branch: Git branch to use (defaults to config value) + target: Koji target to build for (optional) + no_git_refresh: Skip git refresh if True + + Returns: + bool: True if a scratchbuild was submitted, False if reusing existing artifact + """ + repopath = pathlib.Path(CONFIG['cache_dir']['fedpkg']) / bcond_config['id'] + + was_updated = refresh_or_clone(repopath, component_name, no_git_refresh=no_git_refresh, branch=branch) + + if check_existing_artifacts(repopath, was_updated, bcond_config): + return False + + prepare_spec_for_build(component_name, repopath, bcond_config) + bcond_config['koji_task_id'] = submit_scratchbuild(repopath, target=target) return True -def download_srpm_if_possible(component_name, bcond_config): +def download_srpm_if_possible(bcond_config): """ This will: 1. inspect the bcond_config for srpm path and a koji build id @@ -223,20 +230,20 @@ def download_srpm_if_possible(component_name, bcond_config): """ if ('srpm' in bcond_config or 'koji_task_id' not in bcond_config or - koji_status(bcond_config['koji_task_id']) != 'closed'): + koji_status(bcond_config['koji_task_id']) != KOJI_STATE_CLOSED): return False log(' • Downloading SRPM from Koji...', end=' ') repopath = pathlib.Path(CONFIG['cache_dir']['fedpkg']) / bcond_config['id'] command = ('koji', 'download-task', bcond_config['koji_task_id'], '--arch=src', '--noprogress') koji_output = run(*command, cwd=repopath).stdout.splitlines() if (l := len(koji_output)) != 1: - raise RuntimeError(f'Cannot parse koji download-task ouptut, expected 1 line, got {l}') + raise RuntimeError(f'Cannot parse koji download-task output, expected 1 line, got: {l}') srpm_filename = koji_output[0].split(' ')[-1] - if not srpm_filename.endswith('.src.rpm'): - raise RuntimeError('Cannot parse koji download-task ouptut, expected a *.src.rpm filename, got {srpm_filename}') + if not srpm_filename.endswith(SRPM_EXTENSION): + raise RuntimeError(f'Cannot parse koji download-task output, expected a *{SRPM_EXTENSION} filename, got: {srpm_filename}') srpm = repopath / srpm_filename if not srpm.exists(): - raise RuntimeError('Downloaded SRPM does not exist: {srpm}') + raise RuntimeError(f'Downloaded SRPM does not exist: {srpm}') bcond_config['srpm'] = srpm log(srpm_filename) return True @@ -255,7 +262,7 @@ def rpm_requires(rpm): return tuple(sorted({r for r in raw_requires if not r.startswith('rpmlib(')})) -def extract_buildrequires_if_possible(component_name, bcond_config): +def extract_buildrequires_if_possible(bcond_config): """ This will: 1. inspect the bcond_config for srpm path @@ -285,11 +292,164 @@ def build_reverse_id_lookup(): pass +def serialize_bcond_config(bcond_config): + """ + Convert bcond_config to JSON-serializable dict. + Handles Path → str and tuple → list conversions. + """ + serialized = {} + + for key in ('withs', 'withouts', 'replacements'): + if key in bcond_config: + serialized[key] = bcond_config[key] + + serialized['id'] = bcond_config['id'] + + # Store only SRPM filename (not full path, as paths differ between users) + if 'srpm' in bcond_config: + serialized['srpm_filename'] = bcond_config['srpm'].name + + # Convert tuple to list for JSON + if 'buildrequires' in bcond_config: + serialized['buildrequires'] = list(bcond_config['buildrequires']) + + return serialized + + +def deserialize_bcond_config(config_dict): + """ + Convert JSON dict back to bcond_config format. + Reconstructs Path and converts list → tuple. + """ + bcond_config = {} + + for key in ('withs', 'withouts', 'replacements', 'id'): + if key in config_dict: + bcond_config[key] = config_dict[key] + + if 'srpm_filename' in config_dict: + repopath = pathlib.Path(CONFIG['cache_dir']['fedpkg']) / config_dict['id'] + srpm_path = repopath / config_dict['srpm_filename'] + # Only set if file exists (user may not have downloaded it) + if srpm_path.exists(): + bcond_config['srpm'] = srpm_path + + # Convert list to tuple (rpm_requires returns tuple for caching) + if 'buildrequires' in config_dict: + bcond_config['buildrequires'] = tuple(config_dict['buildrequires']) + + return bcond_config + + +def save_bconds_cache(filename='bconds_cache.json'): + """ + Save bcond configurations with BuildRequires to JSON file. + Only saves configs that have 'buildrequires' populated. + """ + + all_bconds = {} + for component_name, bcond_configs in CONFIG['bconds'].items(): + configs_with_br = [ + serialize_bcond_config(cfg) + for cfg in bcond_configs + if 'buildrequires' in cfg + ] + if configs_with_br: + all_bconds[component_name] = configs_with_br + + cache_data = { + 'bconds': all_bconds + } + + try: + with open(filename, 'w') as f: + json.dump(cache_data, f, indent=2) + except Exception as e: + log(f'Warning: Failed to save bconds cache: {e}') + + +def load_bconds_cache(filename='bconds_cache.json'): + """ + Load bcond configurations from JSON file into CONFIG['bconds']. + Returns number of configs loaded. + """ + try: + with open(filename) as f: + cache_data = json.load(f) + except json.JSONDecodeError as e: + raise ValueError(f'Invalid JSON in cache file: {e}') + + loaded_count = 0 + for component_name, config_dicts in cache_data.get('bconds', {}).items(): + if component_name not in CONFIG['bconds']: + log(f'Warning: Component {component_name} in cache but not in config.toml, skipping') + continue + + # Match cached configs to existing configs by ID + for config_dict in config_dicts: + deserialized = deserialize_bcond_config(config_dict) + + for existing_config in CONFIG['bconds'][component_name]: + if existing_config.get('id') == deserialized['id']: + existing_config.update(deserialized) + loaded_count += 1 + break + + return loaded_count + + +def read_bconds_cache_if_exists(): + try: + build_reverse_id_lookup() + loaded_count = load_bconds_cache() + log(f'Loaded {loaded_count} bcond configs from bconds_cache.json') + except FileNotFoundError: + log('Cache file bconds_cache.json not found, continuing without...') + pass + except ValueError as e: + log(f'Error loading cache: {e}') + sys.exit(1) + + +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument( + '-R', '--no-git-refresh', + action='store_true', + default=False, + help="Don't refresh the gitrepo of each existing component, just send new components scratchbuilds and downloads srpms." + ) + parser.add_argument( + '--trust-cache', + action='store_true', + default=False, + help="Trust cached BuildRequires data and skip builds for packages already in cache." + ) + parser.add_argument( + 'packages', + nargs='*', + help='Only fetch bconds for given package name(s).' + ) + return parser.parse_args() + + if __name__ == '__main__': + args = parse_args() + + read_bconds_cache_if_exists() + # build everything something_was_submitted = False for component_name, bcond_config in each_bcond_name_config(): - something_was_submitted |= scratchbuild_patched_if_needed(component_name, bcond_config) + if args.packages and component_name not in args.packages: + continue + + # If trusting cache and buildrequires already exists, skip build + if args.trust_cache and 'buildrequires' in bcond_config: + log(f'• {component_name} ({bcond_config["id"]}): Using cached BuildRequires, skipping build') + continue + + something_was_submitted |= scratchbuild_patched_if_needed(component_name, bcond_config, no_git_refresh=args.no_git_refresh) # download everything until there's nothing downloaded # the idea is that while downloading, other tasks could finish @@ -299,13 +459,17 @@ def build_reverse_id_lookup(): something_was_downloaded = False # while we were downloading, we could have finished Koji builds for pkg, bcond_configs in CONFIG['bconds'].items(): + if args.packages and pkg not in args.packages: + continue for bcond_config in bcond_configs: - if 'buildrequires' not in bcond_config: - something_was_downloaded |= download_srpm_if_possible(component_name, bcond_config) - if extract_buildrequires_if_possible(component_name, bcond_config): + if 'srpm' not in bcond_config: + something_was_downloaded |= download_srpm_if_possible(bcond_config) + if extract_buildrequires_if_possible(bcond_config): extracted_count += 1 + save_bconds_cache() koji_status.cache_clear() - log(f'Extracted BuildRequires from {extracted_count} SRPMs.') + log(f'Extracted BuildRequires from {extracted_count} SRPMs and saved to bconds_cache.json.') + if not_extracted_count := sum(len(bcond_configs) for bcond_configs in CONFIG['bconds'].values()) - extracted_count: sys.exit(f'{not_extracted_count} SRPMs remain to be built/downloaded/extracted, run this again in a while.') diff --git a/bconds_cache.json b/bconds_cache.json new file mode 100644 index 00000000..3e4e4f84 --- /dev/null +++ b/bconds_cache.json @@ -0,0 +1,3986 @@ +{ + "bconds": { + "python-setuptools": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-setuptools::bootstrap:::", + "srpm_filename": "python-setuptools-80.10.2-9.fc45~bootstrap.src.rpm", + "buildrequires": [ + "pyproject-rpm-macros", + "python3-devel", + "python3-rpm-generators >= 12-8", + "unzip" + ] + } + ], + "python-packaging": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-packaging::bootstrap:::", + "srpm_filename": "python-packaging-26.0-2.fc45~bootstrap.src.rpm", + "buildrequires": [ + "pyproject-rpm-macros", + "python3-devel", + "python3-flit-core", + "unzip" + ] + } + ], + "python-pip": [ + { + "withouts": [ + "tests", + "man" + ], + "id": "python-pip:man-tests::::", + "srpm_filename": "python-pip-26.0.1-3.fc45.src.rpm", + "buildrequires": [ + "bash-completion", + "ca-certificates", + "pyproject-rpm-macros", + "python3-devel", + "python3-flit-core", + "python3-rpm-generators >= 11-8" + ] + } + ], + "python-six": [ + { + "withouts": [ + "tests" + ], + "id": "python-six:tests::::", + "srpm_filename": "python-six-1.17.0-9.fc45.src.rpm", + "buildrequires": [ + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools) >= 40.8" + ] + } + ], + "python-tomli-w": [ + { + "withouts": [ + "check" + ], + "id": "python-tomli-w:check::::", + "srpm_filename": "python-tomli-w-1.2.0-8.fc45.src.rpm", + "buildrequires": [ + "(python3dist(flit-core) < 4~~ with python3dist(flit-core) >= 3.2)", + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pip) >= 19" + ] + } + ], + "python-setuptools_scm": [ + { + "withouts": [ + "tests" + ], + "id": "python-setuptools_scm:tests::::", + "srpm_filename": "python-setuptools_scm-9.2.2-6.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(packaging) >= 20", + "python3dist(pip) >= 19", + "python3dist(setuptools)", + "python3dist(setuptools) >= 61" + ] + } + ], + "python-py": [ + { + "withouts": [ + "docs", + "tests" + ], + "id": "python-py:docs-tests::::", + "srpm_filename": "python-py-1.11.0-20.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools)", + "python3dist(setuptools-scm)", + "python3dist(setuptools-scm[toml])" + ] + } + ], + "python-chardet": [ + { + "withouts": [ + "doc" + ], + "id": "python-chardet:doc::::", + "srpm_filename": "python-chardet-6.0.0.post1-2.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(hatch-vcs)", + "python3dist(hatchling)", + "python3dist(packaging)", + "python3dist(pip) >= 19" + ] + } + ], + "python-pbr": [ + { + "withs": [ + "bootstrap" + ], + "withouts": [ + "tests" + ], + "id": "python-pbr:tests:bootstrap:::", + "srpm_filename": "python-pbr-7.0.3-3.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "git-core", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools)", + "python3dist(setuptools) >= 64" + ] + } + ], + "python-extras": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-extras::bootstrap:::", + "srpm_filename": "python-extras-1.0.0-43.fc45~bootstrap.1.src.rpm", + "buildrequires": [ + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools) >= 40.8" + ] + } + ], + "python-testtools": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-testtools::bootstrap:::", + "srpm_filename": "python-testtools-2.8.7-2.fc45~bootstrap.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(hatch-vcs)", + "python3dist(hatchling)", + "python3dist(packaging)", + "python3dist(pip) >= 19" + ] + } + ], + "python-attrs": [ + { + "withouts": [ + "tests" + ], + "id": "python-attrs:tests::::", + "srpm_filename": "python-attrs-26.1.0-2.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(hatch-fancy-pypi-readme) >= 23.2", + "python3dist(hatch-vcs)", + "python3dist(hatchling)", + "python3dist(packaging)", + "python3dist(pip) >= 19" + ] + } + ], + "python-pluggy": [ + { + "withouts": [ + "tests" + ], + "id": "python-pluggy:tests::::", + "srpm_filename": "python-pluggy-1.6.0-5.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools) >= 65", + "python3dist(setuptools-scm) >= 8", + "python3dist(setuptools-scm[toml]) >= 8" + ] + } + ], + "python-sortedcontainers": [ + { + "withouts": [ + "docs", + "tests" + ], + "id": "python-sortedcontainers:docs-tests::::", + "srpm_filename": "python-sortedcontainers-2.4.0-26.fc45.src.rpm", + "buildrequires": [ + "make", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools) >= 40.8" + ] + } + ], + "python-hypothesis": [ + { + "withouts": [ + "doc", + "tests" + ], + "id": "python-hypothesis:doc-tests::::", + "srpm_filename": "python-hypothesis-6.151.9-3.fc45.src.rpm", + "buildrequires": [ + "(python3dist(sortedcontainers) < 3~~ with python3dist(sortedcontainers) >= 2.1)", + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "pyproject-rpm-macros >= 0-43", + "python3-devel", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools) >= 78.1", + "python3dist(wheel)" + ] + } + ], + "python-hypothesmith": [ + { + "withouts": [ + "tests" + ], + "id": "python-hypothesmith:tests::::", + "srpm_filename": "python-hypothesmith-0.3.3-14.fc45.src.rpm", + "buildrequires": [ + "pyproject-rpm-macros", + "python3-devel", + "python3dist(hypothesis) >= 6.93", + "python3dist(hypothesis[lark]) >= 6.93", + "python3dist(libcst) >= 1.0.1", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools) >= 40.8" + ] + } + ], + "python-pygments": [ + { + "withouts": [ + "docs", + "tests" + ], + "id": "python-pygments:docs-tests::::", + "srpm_filename": "python-pygments-2.19.2-2.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(hatchling)", + "python3dist(packaging)", + "python3dist(pip) >= 19" + ] + } + ], + "python-filelock": [ + { + "withouts": [ + "docs", + "tests" + ], + "id": "python-filelock:docs-tests::::", + "srpm_filename": "python-filelock-3.20.3-2.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(hatch-vcs) >= 0.5", + "python3dist(hatchling) >= 1.27", + "python3dist(packaging)", + "python3dist(pip) >= 19" + ] + } + ], + "python-elementpath": [ + { + "withouts": [ + "tests" + ], + "id": "python-elementpath:tests::::", + "srpm_filename": "python-elementpath-4.8.0-8.fc45.src.rpm", + "buildrequires": [ + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools) >= 40.8" + ] + } + ], + "python-iniconfig": [ + { + "withouts": [ + "tests" + ], + "id": "python-iniconfig:tests::::", + "srpm_filename": "python-iniconfig-2.3.0-3.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools) >= 77", + "python3dist(setuptools-scm) >= 8" + ] + } + ], + "Cython": [ + { + "withouts": [ + "tests" + ], + "id": "Cython:tests::::", + "srpm_filename": "Cython-3.2.4-4.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "gcc", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools)", + "python3dist(setuptools) >= 40.8" + ] + } + ], + "python-more-itertools": [ + { + "withouts": [ + "tests" + ], + "id": "python-more-itertools:tests::::", + "srpm_filename": "python-more-itertools-10.5.0-9.fc45.src.rpm", + "buildrequires": [ + "(python3dist(flit-core) < 4~~ with python3dist(flit-core) >= 3.2)", + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pip) >= 19" + ] + } + ], + "python-fixtures": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-fixtures::bootstrap:::", + "srpm_filename": "python-fixtures-4.2.2-8.fc45~bootstrap.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pbr) > 5.7.0", + "python3dist(pbr) >= 5.7", + "python3dist(pip) >= 19", + "python3dist(setuptools) >= 36.6" + ] + } + ], + "python-wcwidth": [ + { + "withouts": [ + "tests" + ], + "id": "python-wcwidth:tests::::", + "srpm_filename": "python-wcwidth-0.6.0-2.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(hatchling)", + "python3dist(packaging)", + "python3dist(pip) >= 19" + ] + } + ], + "pytest": [ + { + "withouts": [ + "timeout", + "tests", + "docs" + ], + "id": "pytest:docs-tests-timeout::::", + "srpm_filename": "pytest-8.4.2-4.fc45.src.rpm", + "buildrequires": [ + "(python3dist(pluggy) < 2~~ with python3dist(pluggy) >= 1.5)", + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "pyproject-rpm-macros >= 0-51", + "python3-devel", + "python3dist(iniconfig) >= 1", + "python3dist(packaging)", + "python3dist(packaging) >= 20", + "python3dist(pip) >= 19", + "python3dist(pygments) >= 2.7.2", + "python3dist(setuptools) >= 61", + "python3dist(setuptools-scm) >= 6.2.3", + "python3dist(setuptools-scm[toml]) >= 6.2.3" + ] + } + ], + "python-virtualenv": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-virtualenv::bootstrap:::", + "srpm_filename": "python-virtualenv-21.2.0-2.fc45~bootstrap.src.rpm", + "buildrequires": [ + "(python3dist(distlib) < 1~~ with python3dist(distlib) >= 0.3.7)", + "(python3dist(filelock) < 4~~ with python3dist(filelock) >= 3.12.2)", + "(python3dist(platformdirs) < 5~~ with python3dist(platformdirs) >= 3.9.1)", + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python-pip-wheel >= 25.1", + "python-setuptools-wheel >= 70.1", + "python3-devel", + "python3dist(hatch-vcs) >= 0.4", + "python3dist(hatchling) >= 1.27", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(python-discovery) >= 1" + ] + } + ], + "babel": [ + { + "withs": [ + "bootstrap" + ], + "id": "babel::bootstrap:::", + "srpm_filename": "babel-2.18.0-3.fc45~bootstrap.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools) >= 40.8" + ] + } + ], + "python-jinja2": [ + { + "withouts": [ + "docs", + "asyncio_tests" + ], + "id": "python-jinja2:asyncio_tests-docs::::", + "srpm_filename": "python-jinja2-3.1.6-7.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3-pytest", + "python3dist(babel) >= 2.7", + "python3dist(flit-core) < 4~~", + "python3dist(markupsafe) >= 2", + "python3dist(packaging)", + "python3dist(pip) >= 19" + ] + } + ], + "python-sphinx_rtd_theme": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-sphinx_rtd_theme::bootstrap:::", + "srpm_filename": "python-sphinx_rtd_theme-3.1.0-3.fc45~bootstrap.src.rpm", + "buildrequires": [ + "(python3dist(docutils) < 0.23~~ with python3dist(docutils) > 0.18.0)", + "(python3dist(sphinx) < 10~~ with python3dist(sphinx) >= 6)", + "(python3dist(sphinxcontrib-jquery) < 5~~ with python3dist(sphinxcontrib-jquery) >= 4)", + "font(fontawesome)", + "font(lato)", + "font(robotoslab)", + "make", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools) >= 40.8" + ] + } + ], + "python-sphinx-basic-ng": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-sphinx-basic-ng::bootstrap:::", + "srpm_filename": "python-sphinx-basic-ng-1.0.0-0.19.beta2.fc45~bootstrap.1.src.rpm", + "buildrequires": [ + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools) >= 40.8", + "python3dist(sphinx) >= 4" + ] + } + ], + "python-urllib3": [ + { + "withouts": [ + "tests", + "extradeps" + ], + "id": "python-urllib3:extradeps-tests::::", + "srpm_filename": "python-urllib3-2.6.3-3.fc45.src.rpm", + "buildrequires": [ + "(python3dist(hatch-vcs) < 0.6~~ with python3dist(hatch-vcs) >= 0.4)", + "(python3dist(hatchling) < 2~~ with python3dist(hatchling) >= 1.27)", + "(python3dist(setuptools-scm) < 10~~ with python3dist(setuptools-scm) >= 8)", + "(python3dist(tomli) if python3-devel < 3.11)", + "ca-certificates", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(idna)", + "python3dist(packaging)", + "python3dist(pip) >= 19" + ] + } + ], + "python-requests": [ + { + "withouts": [ + "tests" + ], + "id": "python-requests:tests::::", + "srpm_filename": "python-requests-2.32.5-6.fc45.src.rpm", + "buildrequires": [ + "((python3dist(pysocks) < 1.5.7 or python3dist(pysocks) > 1.5.7) with python3dist(pysocks) >= 1.5.6)", + "(python3dist(chardet) < 7~~ with python3dist(chardet) >= 3.0.2)", + "(python3dist(charset-normalizer) < 4~~ with python3dist(charset-normalizer) >= 2)", + "(python3dist(idna) < 4~~ with python3dist(idna) >= 2.5)", + "(python3dist(tomli) if python3-devel < 3.11)", + "(python3dist(urllib3) < 3~~ with python3dist(urllib3) >= 1.21.1)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools) >= 40.8" + ] + } + ], + "python-sphinx": [ + { + "withouts": [ + "tests", + "sphinxcontrib" + ], + "id": "python-sphinx:sphinxcontrib-tests::::", + "srpm_filename": "python-sphinx-9.1.0-2.fc45.src.rpm", + "buildrequires": [ + "(python3dist(docutils) < 0.23~~ with python3dist(docutils) >= 0.21)", + "(python3dist(tomli) if python3-devel < 3.11)", + "make", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(alabaster) >= 0.7.14", + "python3dist(babel) >= 2.13", + "python3dist(flit-core) >= 3.12", + "python3dist(imagesize) >= 1.3", + "python3dist(jinja2) >= 3.1", + "python3dist(packaging)", + "python3dist(packaging) >= 23", + "python3dist(pip) >= 19", + "python3dist(pygments) >= 2.17", + "python3dist(requests) >= 2.30", + "python3dist(roman-numerals) >= 1", + "python3dist(snowballstemmer) >= 2.2" + ] + } + ], + "python-jedi": [ + { + "withouts": [ + "tests" + ], + "id": "python-jedi:tests::::", + "srpm_filename": "python-jedi-0.19.2-12.fc45.src.rpm", + "buildrequires": [ + "(python3dist(parso) < 0.9~~ with python3dist(parso) >= 0.8.4)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools) >= 40.8" + ] + } + ], + "python-dateutil": [ + { + "withouts": [ + "tests" + ], + "id": "python-dateutil:tests::::", + "srpm_filename": "python-dateutil-2.9.0.post0-9.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "make", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools)", + "python3dist(setuptools-scm)", + "python3dist(six) >= 1.5", + "python3dist(sphinx)", + "python3dist(sphinx-rtd-theme)", + "python3dist(wheel)" + ] + } + ], + "python-jsonschema": [ + { + "withouts": [ + "tests" + ], + "id": "python-jsonschema:tests::::", + "srpm_filename": "python-jsonschema-4.26.0-6.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(attrs) >= 22.2", + "python3dist(hatch-fancy-pypi-readme)", + "python3dist(hatch-vcs)", + "python3dist(hatchling)", + "python3dist(jsonschema-specifications) >= 2023.3.6", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(referencing) >= 0.28.4", + "python3dist(rpds-py) >= 0.25" + ] + } + ], + "python-sphinxcontrib-websupport": [ + { + "withouts": [ + "optional_tests" + ], + "id": "python-sphinxcontrib-websupport:optional_tests::::", + "srpm_filename": "python-sphinxcontrib-websupport-1.2.7-18.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(flit-core) >= 3.7", + "python3dist(jinja2)", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(pytest)", + "python3dist(sphinx) >= 5", + "python3dist(sphinxcontrib-serializinghtml)", + "python3dist(sqlalchemy)", + "python3dist(tox) >= 2.4", + "python3dist(tox-current-env) >= 0.0.16", + "python3dist(whoosh)" + ] + } + ], + "python-soupsieve": [ + { + "withouts": [ + "tests" + ], + "id": "python-soupsieve:tests::::", + "srpm_filename": "python-soupsieve-2.8.3-2.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3-pytest", + "python3dist(hatchling) >= 0.21.1", + "python3dist(packaging)", + "python3dist(pip) >= 19" + ] + } + ], + "python-pytest-asyncio": [ + { + "withouts": [ + "tests" + ], + "id": "python-pytest-asyncio:tests::::", + "srpm_filename": "python-pytest-asyncio-1.1.0-4.fc45.src.rpm", + "buildrequires": [ + "(python3dist(pytest) < 9~~ with python3dist(pytest) >= 8.2)", + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools) >= 77", + "python3dist(setuptools-scm) >= 6.2", + "python3dist(setuptools-scm[toml]) >= 6.2" + ] + } + ], + "python-pytest-cov": [ + { + "withouts": [ + "tests" + ], + "id": "python-pytest-cov:tests::::", + "srpm_filename": "python-pytest-cov-7.1.0-2.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(coverage) >= 7.10.6", + "python3dist(coverage[toml]) >= 7.10.6", + "python3dist(hatch-fancy-pypi-readme)", + "python3dist(hatchling)", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(pluggy) >= 1.2", + "python3dist(pytest) >= 7" + ] + } + ], + "python-flit-core": [ + { + "withouts": [ + "tests" + ], + "id": "python-flit-core:tests::::", + "srpm_filename": "python-flit-core-3.12.0-11.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pip) >= 19" + ] + } + ], + "python-async-timeout": [ + { + "withouts": [ + "tests" + ], + "id": "python-async-timeout:tests::::", + "srpm_filename": "python-async-timeout-5.0.1-8.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools) >= 45", + "python3dist(wheel)" + ] + } + ], + "python-jupyter-client": [ + { + "withouts": [ + "tests" + ], + "id": "python-jupyter-client:tests::::", + "srpm_filename": "python-jupyter-client-8.8.0-3.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(hatchling) >= 1.5", + "python3dist(jupyter-core) >= 5.1", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(python-dateutil) >= 2.8.2", + "python3dist(pyzmq) >= 25", + "python3dist(tornado) >= 6.4.1", + "python3dist(traitlets) >= 5.3" + ] + } + ], + "python-jupyter-server": [ + { + "withouts": [ + "tests" + ], + "id": "python-jupyter-server:tests::::", + "srpm_filename": "python-jupyter-server-2.17.0-5.fc45.src.rpm", + "buildrequires": [ + "((python3dist(jupyter-core) < 5~~ or python3dist(jupyter-core) >= 5.1) with python3dist(jupyter-core) >= 4.12)", + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(anyio) >= 3.1", + "python3dist(argon2-cffi) >= 21.1", + "python3dist(hatch-jupyter-builder) >= 0.8.1", + "python3dist(hatchling) >= 1.11", + "python3dist(jinja2) >= 3.0.3", + "python3dist(jupyter-client) >= 7.4.4", + "python3dist(jupyter-events) >= 0.11", + "python3dist(jupyter-server-terminals) >= 0.4.4", + "python3dist(nbconvert) >= 6.4.4", + "python3dist(nbformat) >= 5.3", + "python3dist(packaging)", + "python3dist(packaging) >= 22", + "python3dist(pip) >= 19", + "python3dist(prometheus-client) >= 0.9", + "python3dist(pyzmq) >= 24", + "python3dist(send2trash) >= 1.8.2", + "python3dist(terminado) >= 0.8.3", + "python3dist(tornado) >= 6.2", + "python3dist(traitlets) >= 5.6", + "python3dist(websocket-client) >= 1.7" + ] + } + ], + "ipython": [ + { + "withouts": [ + "check", + "doc" + ], + "id": "ipython:check-doc::::", + "srpm_filename": "ipython-9.11.0-4.fc45.src.rpm", + "buildrequires": [ + "(python3dist(prompt-toolkit) < 3.1~~ with python3dist(prompt-toolkit) >= 3.0.41)", + "(python3dist(tomli) if python3-devel < 3.11)", + "make", + "pyproject-rpm-macros", + "python3-devel", + "python3-sphinx", + "python3dist(decorator) >= 5.1", + "python3dist(ipython-pygments-lexers) >= 1", + "python3dist(jedi) >= 0.18.2", + "python3dist(matplotlib-inline) >= 0.1.6", + "python3dist(packaging)", + "python3dist(pexpect) > 4.6.0", + "python3dist(pip) >= 19", + "python3dist(pygments) >= 2.14", + "python3dist(setuptools) >= 80", + "python3dist(stack-data) >= 0.6", + "python3dist(traitlets) >= 5.13" + ] + } + ], + "python-ipykernel": [ + { + "withouts": [ + "tests" + ], + "id": "python-ipykernel:tests::::", + "srpm_filename": "python-ipykernel-6.29.3-18.fc45.src.rpm", + "buildrequires": [ + "((python3dist(jupyter-core) < 5~~ or python3dist(jupyter-core) >= 5.1) with python3dist(jupyter-core) >= 4.12)", + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(comm) >= 0.1.1", + "python3dist(hatchling) >= 1.4", + "python3dist(ipython) >= 7.23.1", + "python3dist(jupyter-client) >= 6", + "python3dist(jupyter-client) >= 6.1.12", + "python3dist(matplotlib-inline) >= 0.1", + "python3dist(nest-asyncio)", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(psutil)", + "python3dist(pyzmq) >= 24", + "python3dist(tornado) >= 6.1", + "python3dist(traitlets) >= 5.4" + ] + } + ], + "pybind11": [ + { + "withouts": [ + "optional_tests" + ], + "id": "pybind11:optional_tests::::", + "srpm_filename": "pybind11-3.0-5.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "cmake", + "cmake(Catch2)", + "cmake(Eigen3)", + "gcc-c++", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(build)", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(pytest)", + "python3dist(scikit-build-core) >= 0.11.2" + ] + } + ], + "python-nbconvert": [ + { + "withouts": [ + "check", + "doc" + ], + "id": "python-nbconvert:check-doc::::", + "srpm_filename": "python-nbconvert-7.16.4-14.fc45.src.rpm", + "buildrequires": [ + "(python3dist(bleach) < 5 or python3dist(bleach) > 5)", + "(python3dist(mistune) < 4~~ with python3dist(mistune) >= 2.0.3)", + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(beautifulsoup4)", + "python3dist(defusedxml)", + "python3dist(hatchling) >= 1.5", + "python3dist(jinja2) >= 3", + "python3dist(jupyter-core) >= 4.7", + "python3dist(jupyterlab-pygments)", + "python3dist(markupsafe) >= 2", + "python3dist(nbclient) >= 0.5", + "python3dist(nbformat) >= 5.7", + "python3dist(packaging)", + "python3dist(pandocfilters) >= 1.4.1", + "python3dist(pip) >= 19", + "python3dist(pygments) >= 2.4.1", + "python3dist(tinycss2)", + "python3dist(traitlets) >= 5.1" + ] + } + ], + "python-nbclient": [ + { + "withouts": [ + "check" + ], + "id": "python-nbclient:check::::", + "srpm_filename": "python-nbclient-0.10.2-14.fc45.src.rpm", + "buildrequires": [ + "((python3dist(jupyter-core) < 5~~ or python3dist(jupyter-core) >= 5.1) with python3dist(jupyter-core) >= 4.12)", + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(hatchling) >= 1.10", + "python3dist(jupyter-client) >= 6.1.12", + "python3dist(nbformat) >= 5.1", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(traitlets) >= 5.4" + ] + } + ], + "python-pyquery": [ + { + "withouts": [ + "tests" + ], + "id": "python-pyquery:tests::::", + "srpm_filename": "python-pyquery-2.0.1-8.fc45.src.rpm", + "buildrequires": [ + "pyproject-rpm-macros", + "python3-cssselect", + "python3-devel", + "python3-lxml >= 2.1", + "python3-requests", + "python3dist(cssselect) >= 1.2", + "python3dist(lxml) >= 2.1", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools) >= 40.8" + ] + } + ], + "python-cherrypy": [ + { + "withouts": [ + "tests" + ], + "id": "python-cherrypy:tests::::", + "srpm_filename": "python-cherrypy-18.10.0-14.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(cheroot) >= 8.2.1", + "python3dist(jaraco-collections)", + "python3dist(more-itertools)", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(portend) >= 2.1.1", + "python3dist(setuptools) >= 45", + "python3dist(setuptools-scm)", + "python3dist(setuptools-scm) >= 7", + "python3dist(setuptools-scm[toml]) >= 7", + "python3dist(zc-lockfile)" + ] + } + ], + "freeipa-healthcheck": [ + { + "withouts": [ + "tests" + ], + "id": "freeipa-healthcheck:tests::::", + "srpm_filename": "freeipa-healthcheck-0.19-5.fc45.src.rpm", + "buildrequires": [ + "pyproject-rpm-macros", + "python3-devel", + "python3-lib389", + "python3-libsss_nss_idmap", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools) >= 40.8", + "systemd-devel" + ] + } + ], + "python-Traits": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-Traits::bootstrap:::", + "srpm_filename": "python-Traits-7.0.2-5.fc45~bootstrap.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "gcc", + "pyproject-rpm-macros", + "python3-Cython", + "python3-devel", + "python3-numpy", + "python3-setuptools", + "python3-sphinx", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools)", + "python3dist(wheel)", + "xorg-x11-server-Xvfb" + ] + } + ], + "python-pcodedmp": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-pcodedmp::bootstrap:::", + "srpm_filename": "python-pcodedmp-1.2.6-30.fc45.1.src.rpm", + "buildrequires": [ + "pyproject-rpm-macros", + "python3-devel", + "python3-pypandoc", + "python3dist(oletools) >= 0.54", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools) >= 40.8" + ] + } + ], + "python-libcst": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-libcst::bootstrap:::", + "srpm_filename": "python-libcst-1.8.0-8.fc45~bootstrap.src.rpm", + "buildrequires": [ + "(crate(annotate-snippets/default) >= 0.11.5 with crate(annotate-snippets/default) < 0.12.0~)", + "(crate(difference/default) >= 2.0.0 with crate(difference/default) < 3.0.0~)", + "(crate(itertools/default) >= 0.13.0 with crate(itertools/default) < 0.14.0~)", + "(crate(memchr/default) >= 2.7.4 with crate(memchr/default) < 3.0.0~)", + "(crate(paste/default) >= 1.0.15 with crate(paste/default) < 2.0.0~)", + "(crate(peg/default) >= 0.8.5 with crate(peg/default) < 0.9.0~)", + "(crate(pyo3/default) >= 0.25.0 with crate(pyo3/default) < 0.26.0~)", + "(crate(pyo3/extension-module) >= 0.25.0 with crate(pyo3/extension-module) < 0.26.0~)", + "(crate(quote/default) >= 1.0.0 with crate(quote/default) < 2.0.0~)", + "(crate(rayon/default) >= 1.10.0 with crate(rayon/default) < 2.0.0~)", + "(crate(regex/default) >= 1.11.1 with crate(regex/default) < 2.0.0~)", + "(crate(syn/default) >= 2.0.0 with crate(syn/default) < 3.0.0~)", + "(crate(thiserror/default) >= 2.0.12 with crate(thiserror/default) < 3.0.0~)", + "(crate(trybuild/default) >= 1.0.0 with crate(trybuild/default) < 2.0.0~)", + "(python3dist(tomli) if python3-devel < 3.11)", + "cargo-rpm-macros >= 24", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(pyyaml) >= 5.2", + "python3dist(setuptools)", + "python3dist(setuptools) >= 40.8", + "python3dist(setuptools-rust)", + "python3dist(setuptools-scm)", + "python3dist(wheel)", + "rust >= 1.31", + "rust >= 1.70" + ] + } + ], + "scipy": [ + { + "withouts": [ + "pythran" + ], + "id": "scipy:pythran::::", + "srpm_filename": "scipy-1.16.2-3.fc45.src.rpm", + "buildrequires": [ + "flexiblas-devel", + "gcc-c++", + "gcc-gfortran", + "pybind11-devel", + "pyproject-rpm-macros >= 1.15", + "python3-devel", + "python3-numpy-f2py" + ] + } + ], + "python-fasteners": [ + { + "withouts": [ + "tests" + ], + "id": "python-fasteners:tests::::", + "srpm_filename": "python-fasteners-0.20-5.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools)", + "python3dist(wheel)", + "tomcli" + ] + } + ], + "python-zope-event": [ + { + "withs": [ + "bootstrap" + ], + "withouts": [ + "docs" + ], + "id": "python-zope-event:docs:bootstrap:::", + "srpm_filename": "python-zope-event-6.0-2.fc45~bootstrap.1.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "make", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools) >= 75.8.2", + "python3dist(setuptools) >= 78.1.1", + "python3dist(wheel)" + ] + } + ], + "python-zope-interface": [ + { + "withouts": [ + "docs" + ], + "id": "python-zope-interface:docs::::", + "srpm_filename": "python-zope-interface-8.2-3.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "gcc", + "make", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(coverage)", + "python3dist(coverage[toml])", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools)", + "python3dist(wheel)", + "python3dist(zope-event)", + "python3dist(zope-testing)" + ] + } + ], + "python-tqdm": [ + { + "withouts": [ + "tests" + ], + "id": "python-tqdm:tests::::", + "srpm_filename": "python-tqdm-4.67.3-2.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3-setuptools_scm+toml", + "python3-wheel", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools) >= 42", + "python3dist(setuptools-scm) >= 3.4", + "python3dist(setuptools-scm[toml]) >= 3.4" + ] + } + ], + "python-cryptography": [ + { + "withouts": [ + "tests" + ], + "id": "python-cryptography:tests::::", + "srpm_filename": "python-cryptography-46.0.5-2.fc45.src.rpm", + "buildrequires": [ + "((python3dist(setuptools) < 74 or python3dist(setuptools) > 74) with (python3dist(setuptools) < 74.1 or python3dist(setuptools) > 74.1) with (python3dist(setuptools) < 74.1.1 or python3dist(setuptools) > 74.1.1) with (python3dist(setuptools) < 74.1.2 or python3dist(setuptools) > 74.1.2))", + "(crate(asn1) >= 0.22.0 with crate(asn1) < 0.23.0~)", + "(crate(base64/default) >= 0.22.0 with crate(base64/default) < 0.23.0~)", + "(crate(cc/default) >= 1.2.37 with crate(cc/default) < 2.0.0~)", + "(crate(cfg-if/default) >= 1.0.0 with crate(cfg-if/default) < 2.0.0~)", + "(crate(foreign-types-shared/default) >= 0.1.0 with crate(foreign-types-shared/default) < 0.2.0~)", + "(crate(foreign-types/default) >= 0.3.0 with crate(foreign-types/default) < 0.4.0~)", + "(crate(once_cell/default) >= 1.0.0 with crate(once_cell/default) < 2.0.0~)", + "(crate(openssl-sys/default) >= 0.9.110 with crate(openssl-sys/default) < 0.10.0~)", + "(crate(openssl/default) >= 0.10.74 with crate(openssl/default) < 0.11.0~)", + "(crate(pem) >= 3.0.0 with crate(pem) < 4.0.0~)", + "(crate(pyo3-build-config/default) >= 0.26.0 with crate(pyo3-build-config/default) < 0.27.0~)", + "(crate(pyo3/abi3) >= 0.26.0 with crate(pyo3/abi3) < 0.27.0~)", + "(crate(pyo3/default) >= 0.26.0 with crate(pyo3/default) < 0.27.0~)", + "(crate(self_cell/default) >= 1.0.0 with crate(self_cell/default) < 2.0.0~)", + "(python3dist(maturin) < 2~~ with python3dist(maturin) >= 1.9.4)", + "(python3dist(tomli) if python3-devel < 3.11)", + "gcc", + "gnupg2", + "openssl-devel", + "pyproject-rpm-macros", + "python3-cffi >= 1.12", + "python3-devel", + "python3-setuptools", + "python3-setuptools-rust >= 0.11.4", + "python3dist(cffi) >= 2", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "rust >= 1.74.0", + "rust-packaging" + ] + } + ], + "python-decopatch": [ + { + "withouts": [ + "tests" + ], + "id": "python-decopatch:tests::::", + "srpm_filename": "python-decopatch-1.4.10-30.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(makefun) >= 1.5", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools-scm)" + ] + } + ], + "python-geopandas": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-geopandas::bootstrap:::", + "srpm_filename": "python-geopandas-1.1.3-2.fc45~bootstrap.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(numpy) >= 1.24", + "python3dist(packaging)", + "python3dist(pandas) >= 2", + "python3dist(pip) >= 19", + "python3dist(pyogrio) >= 0.7.2", + "python3dist(pyproj) >= 3.5", + "python3dist(setuptools) >= 61", + "python3dist(shapely) >= 2" + ] + } + ], + "python-astropy": [ + { + "withouts": [ + "check" + ], + "id": "python-astropy:check::::", + "srpm_filename": "python-astropy-7.2.0-3.fc45.src.rpm", + "buildrequires": [ + "(python3dist(cython) < 4~~ with python3dist(cython) >= 3)", + "(python3dist(extension-helpers) < 2~~ with python3dist(extension-helpers) >= 1.4)", + "(python3dist(numpy) < 3~~ with python3dist(numpy) >= 2)", + "(python3dist(tomli) if python3-devel < 3.11)", + "expat-devel", + "gcc", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(astropy-iers-data) >= 0.2025.10.27.0.39.10", + "python3dist(configobj)", + "python3dist(h5py)", + "python3dist(hypothesis)", + "python3dist(matplotlib)", + "python3dist(numpy) >= 1.24", + "python3dist(packaging)", + "python3dist(packaging) >= 22", + "python3dist(pip) >= 19", + "python3dist(ply)", + "python3dist(pyerfa) >= 2.0.1.1", + "python3dist(pytest)", + "python3dist(pyyaml) >= 6", + "python3dist(scikit-image)", + "python3dist(scipy)", + "python3dist(setuptools) >= 77", + "python3dist(setuptools-scm) >= 8", + "python3dist(tox)", + "wcslib-devel >= 8.4" + ] + } + ], + "python-pyerfa": [ + { + "withouts": [ + "tests" + ], + "id": "python-pyerfa:tests::::", + "srpm_filename": "python-pyerfa-2.0.1.5-8.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "erfa-devel", + "gcc", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(jinja2) >= 2.10.3", + "python3dist(numpy) >= 1.19.3", + "python3dist(numpy) >= 2~rc1", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(pip) >= 19.3.1", + "python3dist(pytest)", + "python3dist(pytest-doctestplus) >= 0.7", + "python3dist(setuptools) >= 30.3", + "python3dist(setuptools) >= 61.2", + "python3dist(setuptools-scm) >= 6.2", + "python3dist(tox)", + "python3dist(tox-current-env) >= 0.0.16", + "python3dist(wheel)" + ] + } + ], + "python-aiohttp": [ + { + "withouts": [ + "tests" + ], + "id": "python-aiohttp:tests::::", + "srpm_filename": "python-aiohttp-3.13.3-5.fc45.src.rpm", + "buildrequires": [ + "(python3dist(multidict) < 7~~ with python3dist(multidict) >= 4.5)", + "(python3dist(tomli) if python3-devel < 3.11)", + "(python3dist(yarl) < 2~~ with python3dist(yarl) >= 1.17)", + "gcc", + "llhttp-devel >= 9.3.0", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(aiohappyeyeballs) >= 2.5", + "python3dist(aiosignal) >= 1.4", + "python3dist(attrs) >= 17.3", + "python3dist(cython) >= 3.1.1", + "python3dist(frozenlist) >= 1.1.1", + "python3dist(multidict)", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(pkgconfig)", + "python3dist(propcache) >= 0.2", + "python3dist(setuptools) >= 67" + ] + } + ], + "python-azure-core": [ + { + "withouts": [ + "tests" + ], + "id": "python-azure-core:tests::::", + "srpm_filename": "python-azure-core-1.38.0-2.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(requests) >= 2.21", + "python3dist(setuptools) >= 40.8", + "python3dist(typing-extensions) >= 4.6" + ] + } + ], + "python-subprocess-tee": [ + { + "withouts": [ + "tests" + ], + "id": "python-subprocess-tee:tests::::", + "srpm_filename": "python-subprocess-tee-0.4.1-28.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools) >= 61", + "python3dist(setuptools-scm) >= 7", + "python3dist(setuptools-scm[toml]) >= 7" + ] + } + ], + "python-typeguard": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-typeguard::bootstrap:::", + "srpm_filename": "python-typeguard-4.4.4-3.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(mypy) >= 1.2", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(pytest) >= 7", + "python3dist(setuptools) >= 77", + "python3dist(setuptools-scm) >= 6.4", + "python3dist(setuptools-scm[toml]) >= 6.4", + "python3dist(typing-extensions) >= 4.14", + "tomcli" + ] + } + ], + "python-tox": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-tox::bootstrap:::", + "srpm_filename": "python-tox-4.35.0-3.fc45~bootstrap.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "pyproject-rpm-macros >= 1.16", + "python3-devel", + "python3dist(cachetools)", + "python3dist(chardet) >= 5.2", + "python3dist(colorama) >= 0.4.6", + "python3dist(filelock)", + "python3dist(hatch-vcs)", + "python3dist(hatchling) >= 1.13", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(platformdirs)", + "python3dist(pluggy) >= 1.5", + "python3dist(pyproject-api)", + "python3dist(virtualenv) >= 20.29" + ] + } + ], + "python-fsspec": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-fsspec::bootstrap:::", + "srpm_filename": "python-fsspec-2026.2.0-2.fc45~bootstrap.src.rpm", + "buildrequires": [ + "((python3dist(aiohttp) < 4~a0 or python3dist(aiohttp) > 4~a0) with (python3dist(aiohttp) < 4~a1 or python3dist(aiohttp) > 4~a1))", + "(python3dist(tomli) if python3-devel < 3.11)", + "fuse", + "git-core", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(cloudpickle)", + "python3dist(fusepy)", + "python3dist(hatch-vcs)", + "python3dist(hatchling) >= 1.27", + "python3dist(jinja2)", + "python3dist(libarchive-c)", + "python3dist(lz4)", + "python3dist(numpy)", + "python3dist(packaging)", + "python3dist(paramiko)", + "python3dist(pip) >= 19", + "python3dist(pyarrow) >= 1", + "python3dist(pygit2)", + "python3dist(pytest)", + "python3dist(pytest-asyncio)", + "python3dist(pytest-mock)", + "python3dist(pytest-rerunfailures)", + "python3dist(python-snappy)", + "python3dist(requests)", + "python3dist(smbprotocol)", + "python3dist(tqdm)", + "python3dist(zstandard)" + ] + } + ], + "python-pyface": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-pyface::bootstrap:::", + "srpm_filename": "python-pyface-8.0.0-19.fc45~bootstrap.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "/usr/bin/xvfb-run", + "glibc-langpack-en", + "pyproject-rpm-macros", + "python3-devel", + "python3-devel >= 3.9", + "python3-pillow-qt", + "python3-pyqt6", + "python3dist(numpy)", + "python3dist(packaging)", + "python3dist(pillow)", + "python3dist(pip) >= 19", + "python3dist(pygments)", + "python3dist(pyqt5)", + "python3dist(pyqt6)", + "python3dist(setuptools) >= 61", + "python3dist(traits) >= 6.2", + "python3dist(wheel)", + "python3dist(wxpython) >= 4" + ] + } + ], + "python-contourpy": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-contourpy::bootstrap:::", + "srpm_filename": "python-contourpy-1.3.3-5.fc45~bootstrap.src.rpm", + "buildrequires": [ + "((python3dist(pybind11) < 2.13.3 or python3dist(pybind11) > 2.13.3) with python3dist(pybind11) >= 2.13.2)", + "(python3dist(tomli) if python3-devel < 3.11)", + "gcc-c++", + "pyproject-rpm-macros", + "pyproject-rpm-macros >= 1.15.1", + "python3-devel", + "python3dist(meson) >= 1.2", + "python3dist(meson-python) >= 0.13.1", + "python3dist(numpy) >= 1.25", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(pytest)", + "python3dist(pytest-cov)", + "python3dist(pytest-rerunfailures)", + "python3dist(pytest-xdist)", + "python3dist(wurlitzer)" + ] + } + ], + "python-msrest": [ + { + "withouts": [ + "tests" + ], + "id": "python-msrest:tests::::", + "srpm_filename": "python-msrest-0.7.1-20.20221014git2d8fd04.fc45.src.rpm", + "buildrequires": [ + "(python3dist(requests) >= 2.16 with python3dist(requests) < 3)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(aiodns)", + "python3dist(aiohttp) >= 3", + "python3dist(azure-core) >= 1.24", + "python3dist(certifi) >= 2017.4.17", + "python3dist(isodate) >= 0.6", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(requests-oauthlib) >= 0.5", + "python3dist(setuptools) >= 40.8" + ] + } + ], + "python-oletools": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-oletools::bootstrap:::", + "srpm_filename": "python-oletools-0.56.2-26.fc45~bootstrap.1.src.rpm", + "buildrequires": [ + "python3-colorclass", + "python3-cryptography", + "python3-devel", + "python3-easygui", + "python3-msoffcrypto", + "python3-olefile", + "python3-prettytable", + "python3-pymilter", + "python3-pyparsing", + "python3-setuptools" + ] + } + ], + "python-google-api-core": [ + { + "withouts": [ + "tests" + ], + "id": "python-google-api-core:tests::::", + "srpm_filename": "python-google-api-core-2.27.0-3.fc45.src.rpm", + "buildrequires": [ + "(python3dist(google-auth) < 3~~ with python3dist(google-auth) >= 2.14.1)", + "(python3dist(google-auth) < 3~~ with python3dist(google-auth) >= 2.35)", + "(python3dist(google-auth[aiohttp]) < 3~~ with python3dist(google-auth[aiohttp]) >= 2.35)", + "(python3dist(googleapis-common-protos) < 2~~ with python3dist(googleapis-common-protos) >= 1.56.2)", + "(python3dist(grpcio-gcp) < 1~~ with python3dist(grpcio-gcp) >= 0.2.2)", + "(python3dist(requests) < 3~~ with python3dist(requests) >= 2.18)", + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(grpcio)", + "python3dist(grpcio-status)", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(proto-plus)", + "python3dist(protobuf)", + "python3dist(setuptools)", + "tomcli" + ] + } + ], + "fonttools": [ + { + "withouts": [ + "tests", + "plot_extra", + "symfont_extra", + "ufo_extra", + "woff_extra", + "graphite_extra", + "interpolatable_extra" + ], + "id": "fonttools:graphite_extra-interpolatable_extra-plot_extra-symfont_extra-tests-ufo_extra-woff_extra::::", + "srpm_filename": "fonttools-4.62.1-1.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "gcc", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(lxml) >= 4", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools) >= 40.8", + "python3dist(uharfbuzz) >= 0.45", + "python3dist(unicodedata2) >= 17" + ] + } + ], + "conda": [ + { + "withs": [ + "bootstrap" + ], + "id": "conda::bootstrap:::", + "srpm_filename": "conda-26.1.1-3.fc45~bootstrap.src.rpm", + "buildrequires": [ + "(python3dist(requests) < 3~~ with python3dist(requests) >= 2.28)", + "(python3dist(tomli) if python3-devel < 3.11)", + "pkgconfig(bash-completion)", + "pyproject-rpm-macros", + "python-conda-sphinx-theme", + "python-unversioned-command", + "python3-boltons", + "python3-boto3", + "python3-devel", + "python3-flask", + "python3-jsonpatch", + "python3-libmambapy", + "python3-pexpect", + "python3-pytest-mock", + "python3-pytest-rerunfailures", + "python3-pytest-split", + "python3-pytest-timeout", + "python3-pytest-xprocess", + "python3-responses", + "python3dist(archspec) >= 0.2.3", + "python3dist(boltons) >= 23", + "python3dist(charset-normalizer)", + "python3dist(conda-package-handling) >= 2.2", + "python3dist(distro) >= 1.5", + "python3dist(frozendict) >= 2.4.2", + "python3dist(hatch-vcs) >= 0.2", + "python3dist(hatchling) >= 1.12.2", + "python3dist(jsonpatch) >= 1.32", + "python3dist(menuinst) >= 2", + "python3dist(packaging)", + "python3dist(packaging) >= 23", + "python3dist(pip) >= 19", + "python3dist(platformdirs) >= 3.10", + "python3dist(pluggy) >= 1", + "python3dist(pycosat) >= 0.6.3", + "python3dist(ruamel-yaml) >= 0.11.14", + "python3dist(setuptools) >= 60", + "python3dist(tqdm) >= 4", + "python3dist(zstandard) >= 0.15", + "sed" + ] + } + ], + "python-conda-libmamba-solver": [ + { + "withouts": [ + "tests" + ], + "id": "python-conda-libmamba-solver:tests::::", + "srpm_filename": "python-conda-libmamba-solver-25.11.0-3.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(hatch-vcs)", + "python3dist(hatchling)", + "python3dist(packaging)", + "python3dist(pip) >= 19" + ] + } + ], + "python-conda-index": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-conda-index::bootstrap:::", + "srpm_filename": "python-conda-index-0.10.0-3.fc45~bootstrap.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "make", + "pyproject-rpm-macros", + "python3-conda-build", + "python3-devel", + "python3-myst-parser", + "python3-sphinx-click", + "python3dist(click) >= 8", + "python3dist(conda-package-streaming) >= 0.7", + "python3dist(filelock)", + "python3dist(hatch-vcs) >= 0.2", + "python3dist(hatchling) >= 1.12.2", + "python3dist(jinja2)", + "python3dist(msgpack)", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(ruamel-yaml)", + "python3dist(zstandard)" + ] + } + ], + "python-conda-package-streaming": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-conda-package-streaming::bootstrap:::", + "srpm_filename": "python-conda-package-streaming-0.11.0-9.fc45~bootstrap.1.src.rpm", + "buildrequires": [ + "(python3dist(flit-core) < 4~~ with python3dist(flit-core) >= 3.2)", + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(boto3)", + "python3dist(bottle)", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(pytest) >= 7", + "python3dist(pytest-mock)", + "python3dist(requests)", + "python3dist(zstandard) >= 0.15" + ] + } + ], + "python-googleapis-common-protos": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-googleapis-common-protos::bootstrap:::", + "srpm_filename": "python-googleapis-common-protos-1.63.0-15.fc45~bootstrap.src.rpm", + "buildrequires": [ + "((python3dist(protobuf) < 3.20 or python3dist(protobuf) > 3.20) with (python3dist(protobuf) < 3.20.1 or python3dist(protobuf) > 3.20.1) with (python3dist(protobuf) < 4.21.1 or python3dist(protobuf) > 4.21.1) with (python3dist(protobuf) < 4.21.2 or python3dist(protobuf) > 4.21.2) with (python3dist(protobuf) < 4.21.3 or python3dist(protobuf) > 4.21.3) with (python3dist(protobuf) < 4.21.4 or python3dist(protobuf) > 4.21.4) with (python3dist(protobuf) < 4.21.5 or python3dist(protobuf) > 4.21.5) with python3dist(protobuf) < 6~~dev0 with python3dist(protobuf) >= 3.19.5)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(pytest)", + "python3dist(setuptools) >= 40.8" + ] + } + ], + "python-proto-plus": [ + { + "withouts": [ + "tests" + ], + "id": "python-proto-plus:tests::::", + "srpm_filename": "python-proto-plus-1.22.3-13.fc45.src.rpm", + "buildrequires": [ + "(python3dist(protobuf) < 5~~dev0 with python3dist(protobuf) >= 3.19)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(google-api-core) >= 1.31.5", + "python3dist(google-api-core[grpc]) >= 1.31.5", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools) >= 40.8" + ] + } + ], + "python-dns": [ + { + "withouts": [ + "trio", + "curio", + "doh" + ], + "id": "python-dns:curio-doh-trio::::", + "srpm_filename": "python-dns-2.8.0-3.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3-pytest", + "python3dist(aioquic) >= 1.2", + "python3dist(cryptography) >= 45", + "python3dist(hatchling) >= 1.21", + "python3dist(idna) >= 3.10", + "python3dist(packaging)", + "python3dist(pip) >= 19" + ] + } + ], + "python-beautifulsoup4": [ + { + "withouts": [ + "soupsieve", + "tests" + ], + "id": "python-beautifulsoup4:soupsieve-tests::::", + "srpm_filename": "python-beautifulsoup4-4.14.3-2.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3-setuptools", + "python3dist(hatchling)", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(typing-extensions) >= 4" + ] + } + ], + "python-lxml": [ + { + "withouts": [ + "extras" + ], + "id": "python-lxml:extras::::", + "srpm_filename": "python-lxml-6.0.2-3.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "gcc", + "libxml2-devel", + "libxslt-devel", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(cython)", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools)", + "python3dist(setuptools) >= 40.8" + ] + } + ], + "python-constantly": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-constantly::bootstrap:::", + "srpm_filename": "python-constantly-23.10.4-14.fc45~bootstrap.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools) >= 68.2", + "python3dist(sphinx-rtd-theme)", + "python3dist(versioneer) = 0.29", + "python3dist(versioneer[toml]) = 0.29" + ] + } + ], + "python-pysocks": [ + { + "replacements": { + "with_python3_tests": "0" + }, + "id": "python-pysocks:::with_python3_tests::", + "srpm_filename": "python-pysocks-1.7.1-32.fc45.src.rpm", + "buildrequires": [ + "python3-devel", + "python3-setuptools" + ] + } + ], + "ara": [ + { + "replacements": { + "with_docs": "0" + }, + "id": "ara:::with_docs::", + "srpm_filename": "ara-1.7.4-3.fc45.src.rpm", + "buildrequires": [ + "((python3dist(dynaconf) < 3.1.3 or python3dist(dynaconf) > 3.1.3) with python3dist(dynaconf) < 4~~ with python3dist(dynaconf) >= 3)", + "((python3dist(pbr) < 2.1 or python3dist(pbr) > 2.1) with python3dist(pbr) >= 2)", + "(python3dist(django) < 5.3~~ with python3dist(django) >= 4.2)", + "git-core", + "pyproject-rpm-macros", + "python3-devel", + "python3-factory-boy", + "python3-faker", + "python3dist(cliff)", + "python3dist(django-cors-headers)", + "python3dist(django-filter)", + "python3dist(django-health-check)", + "python3dist(djangorestframework) >= 3.9.1", + "python3dist(packaging)", + "python3dist(pbr)", + "python3dist(pip) >= 19", + "python3dist(pygments)", + "python3dist(requests) >= 2.14.2", + "python3dist(ruamel-yaml)", + "python3dist(setuptools) >= 40.8", + "python3dist(tzlocal) >= 4", + "python3dist(whitenoise)" + ] + } + ], + "python-httpretty": [ + { + "replacements": { + "run_tests": "0" + }, + "id": "python-httpretty:::run_tests::", + "srpm_filename": "python-httpretty-1.1.4-38.fc45.src.rpm", + "buildrequires": [ + "python3-devel", + "python3-pip", + "python3-setuptools", + "python3-wheel" + ] + } + ], + "python-ruamel-yaml": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-ruamel-yaml::bootstrap:::", + "srpm_filename": "python-ruamel-yaml-0.19.1-3.fc45~bootstrap.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3-pytest", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools)" + ] + } + ], + "python-rq": [ + { + "withouts": [ + "tests" + ], + "id": "python-rq:tests::::", + "srpm_filename": "python-rq-2.6.1-1.fc45.src.rpm", + "buildrequires": [ + "((python3dist(redis) < 6 or python3dist(redis) > 6) with python3dist(redis) >= 3.5)", + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(click) >= 5", + "python3dist(croniter)", + "python3dist(hatchling)", + "python3dist(packaging)", + "python3dist(pip) >= 19" + ] + } + ], + "python-markdown-it-py": [ + { + "withouts": [ + "plugins" + ], + "id": "python-markdown-it-py:plugins::::", + "srpm_filename": "python-markdown-it-py-4.0.0-2.fc45.src.rpm", + "buildrequires": [ + "(python3dist(flit-core) < 4~~ with python3dist(flit-core) >= 3.4)", + "(python3dist(linkify-it-py) < 3~~ with python3dist(linkify-it-py) >= 1)", + "(python3dist(mdurl) >= 0.1 with python3dist(mdurl) < 1)", + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(pytest)", + "python3dist(pytest-regressions)", + "python3dist(requests)" + ] + } + ], + "python-factory-boy": [ + { + "withouts": [ + "tests" + ], + "id": "python-factory-boy:tests::::", + "srpm_filename": "python-factory-boy-3.3.3-6.fc45.src.rpm", + "buildrequires": [ + "pyproject-rpm-macros", + "python3-devel", + "python3dist(faker) >= 0.7", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools) >= 40.8" + ] + } + ], + "python-tox-current-env": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-tox-current-env::bootstrap:::", + "srpm_filename": "python-tox-current-env-0.0.16-9.fc45~bootstrap.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools)", + "python3dist(tox) >= 3.28", + "python3dist(wheel)" + ] + } + ], + "python-pikepdf": [ + { + "withouts": [ + "docs", + "tests" + ], + "id": "python-pikepdf:docs-tests::::", + "srpm_filename": "python-pikepdf-10.2.0-3.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "gcc-c++", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(deprecated)", + "python3dist(lxml) >= 4.8", + "python3dist(packaging)", + "python3dist(pillow) >= 10.0.1", + "python3dist(pip) >= 19", + "python3dist(pybind11) >= 3", + "python3dist(setuptools) >= 77.0.3", + "qpdf-devel >= 11.5.0", + "tomcli" + ] + } + ], + "python-executing": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-executing::bootstrap:::", + "srpm_filename": "python-executing-2.2.1-5.fc45~bootstrap.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools)", + "python3dist(setuptools-scm)", + "python3dist(setuptools-scm[toml])", + "python3dist(wheel)" + ] + } + ], + "python-editables": [ + { + "withouts": [ + "doc" + ], + "id": "python-editables:doc::::", + "srpm_filename": "python-editables-0.5-16.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(flit-core) >= 3.3", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(pytest)" + ] + } + ], + "python-jsonschema-specifications": [ + { + "replacements": { + "with_doc": "0" + }, + "id": "python-jsonschema-specifications:::with_doc::", + "srpm_filename": "python-jsonschema-specifications-2024.10.1-7.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(hatch-vcs)", + "python3dist(hatchling)", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(pytest)", + "python3dist(referencing) >= 0.31" + ] + } + ], + "python-openstackclient": [ + { + "replacements": { + "with_doc": "0" + }, + "id": "python-openstackclient:::with_doc::", + "srpm_filename": "python-openstackclient-9.0.0-2.fc45.src.rpm", + "buildrequires": [ + "((python3dist(pbr) < 2.1 or python3dist(pbr) > 2.1) with python3dist(pbr) >= 2)", + "(python3dist(tomli) if python3-devel < 3.11)", + "/usr/bin/gpgv2", + "git-core", + "pyproject-rpm-macros", + "python3-babel", + "python3-devel", + "python3dist(cliff) >= 4.13", + "python3dist(cryptography) >= 2.7", + "python3dist(ddt) >= 1.0.1", + "python3dist(fixtures) >= 3", + "python3dist(iso8601) >= 0.1.11", + "python3dist(openstacksdk) >= 4.6", + "python3dist(osc-lib) >= 2.3", + "python3dist(oslo-i18n) >= 3.15.3", + "python3dist(packaging)", + "python3dist(pbr) >= 2", + "python3dist(pbr) >= 6.1.1", + "python3dist(pip) >= 19", + "python3dist(python-cinderclient) >= 3.3", + "python3dist(python-keystoneclient) >= 3.22", + "python3dist(requests) >= 2.27", + "python3dist(requests-mock) >= 1.2", + "python3dist(stestr) >= 1", + "python3dist(stevedore) >= 2.0.1", + "python3dist(testtools) >= 2.2", + "python3dist(tox) >= 4.3", + "python3dist(tox-current-env) >= 0.0.16", + "python3dist(wrapt) >= 1.7" + ] + } + ], + "python-oslo-config": [ + { + "replacements": { + "repo_bootstrap": "1" + }, + "id": "python-oslo-config:::repo_bootstrap::", + "srpm_filename": "python-oslo-config-10.3.0-2.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "/usr/bin/gpgv2", + "git-core", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(netaddr) >= 0.7.18", + "python3dist(oslo-i18n) >= 3.15.3", + "python3dist(packaging)", + "python3dist(pbr) >= 2", + "python3dist(pbr) >= 6.1.1", + "python3dist(pip) >= 19", + "python3dist(pyyaml) >= 5.1", + "python3dist(requests) >= 2.18", + "python3dist(rfc3986) >= 1.2", + "python3dist(stevedore) >= 5.6" + ] + } + ], + "python-neutronclient": [ + { + "replacements": { + "with_doc": "0" + }, + "id": "python-neutronclient:::with_doc::", + "srpm_filename": "python-neutronclient-11.3.1-6.fc45.src.rpm", + "buildrequires": [ + "((python3dist(coverage) < 4.4 or python3dist(coverage) > 4.4) with python3dist(coverage) >= 4)", + "((python3dist(oslo-serialization) < 2.19.1 or python3dist(oslo-serialization) > 2.19.1) with python3dist(oslo-serialization) >= 2.18)", + "((python3dist(pbr) < 2.1 or python3dist(pbr) > 2.1) with python3dist(pbr) >= 2)", + "/usr/bin/gpgv2", + "git-core", + "pyproject-rpm-macros", + "python3-devel", + "python3-osc-lib-tests", + "python3dist(cliff) >= 3.4", + "python3dist(debtcollector) >= 1.2", + "python3dist(fixtures) >= 3", + "python3dist(iso8601) >= 0.1.11", + "python3dist(keystoneauth1) >= 3.8", + "python3dist(netaddr) >= 0.7.18", + "python3dist(openstacksdk) >= 1.5", + "python3dist(os-client-config) >= 1.28", + "python3dist(osc-lib) >= 1.12", + "python3dist(oslo-i18n) >= 3.15.3", + "python3dist(oslo-log) >= 3.36", + "python3dist(oslo-utils) >= 3.33", + "python3dist(oslotest) >= 3.2", + "python3dist(packaging)", + "python3dist(pbr) >= 2", + "python3dist(pip) >= 19", + "python3dist(python-keystoneclient) >= 3.8", + "python3dist(python-openstackclient) >= 3.12", + "python3dist(python-subunit) >= 1", + "python3dist(requests) >= 2.14.2", + "python3dist(requests-mock) >= 1.2", + "python3dist(setuptools) >= 40.8", + "python3dist(stestr) >= 2", + "python3dist(testscenarios) >= 0.4", + "python3dist(testtools) >= 2.2", + "python3dist(tox)", + "python3dist(tox-current-env) >= 0.0.16" + ] + } + ], + "python-glanceclient": [ + { + "replacements": { + "with_doc": "0" + }, + "id": "python-glanceclient:::with_doc::", + "srpm_filename": "python-glanceclient-4.11.0-2.fc45.src.rpm", + "buildrequires": [ + "((python3dist(coverage) < 4.4 or python3dist(coverage) > 4.4) with python3dist(coverage) >= 4)", + "((python3dist(pbr) < 2.1 or python3dist(pbr) > 2.1) with python3dist(pbr) >= 2)", + "/usr/bin/gpgv2", + "git-core", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(ddt) >= 1.2.1", + "python3dist(fixtures) >= 3", + "python3dist(keystoneauth1) >= 3.6.2", + "python3dist(openstacksdk) >= 0.10", + "python3dist(oslo-i18n) >= 3.15.3", + "python3dist(oslo-utils) >= 3.33", + "python3dist(packaging)", + "python3dist(pbr) >= 2", + "python3dist(pip) >= 19", + "python3dist(prettytable) >= 0.7.1", + "python3dist(pyopenssl) >= 17.1", + "python3dist(requests) >= 2.14.2", + "python3dist(requests-mock) >= 1.2", + "python3dist(setuptools) >= 40.8", + "python3dist(stestr) >= 2", + "python3dist(testscenarios) >= 0.4", + "python3dist(testtools) >= 2.2", + "python3dist(tox) >= 3.18", + "python3dist(tox-current-env) >= 0.0.16", + "python3dist(warlock) >= 1.2", + "python3dist(wrapt) >= 1.7" + ] + } + ], + "python-domdf-python-tools": [ + { + "withouts": [ + "tests" + ], + "id": "python-domdf-python-tools:tests::::", + "srpm_filename": "python-domdf-python-tools-3.9.0-9.fc45.src.rpm", + "buildrequires": [ + "((python3dist(setuptools) < 61~~ or python3dist(setuptools) >= 62) with python3dist(setuptools) >= 40.6)", + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(natsort) >= 7.0.1", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(typing-extensions) >= 3.7.4.1", + "python3dist(wheel) >= 0.34.2" + ] + } + ], + "python-tiny-proxy": [ + { + "withouts": [ + "tests" + ], + "id": "python-tiny-proxy:tests::::", + "srpm_filename": "python-tiny-proxy-0.2.1-13.fc45.src.rpm", + "buildrequires": [ + "(python3dist(anyio) < 5~~ with python3dist(anyio) >= 3.6.1)", + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools) >= 40.8" + ] + } + ], + "python-werkzeug": [ + { + "withouts": [ + "tests" + ], + "id": "python-werkzeug:tests::::", + "srpm_filename": "python-werkzeug-3.1.6-3.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "make", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(flit-core) < 4~~", + "python3dist(markupsafe) >= 2.1.1", + "python3dist(packaging)", + "python3dist(pallets-sphinx-themes)", + "python3dist(pip) >= 19", + "python3dist(sphinx)", + "python3dist(sphinxcontrib-log-cabinet)" + ] + } + ], + "python-netaddr": [ + { + "withouts": [ + "docs" + ], + "id": "python-netaddr:docs::::", + "srpm_filename": "python-netaddr-1.3.0-11.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3-pytest", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools)" + ] + } + ], + "python-dirty-equals": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-dirty-equals::bootstrap:::", + "srpm_filename": "python-dirty-equals-0.11-3.fc45~bootstrap.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(hatchling)", + "python3dist(packaging)", + "python3dist(packaging) >= 25", + "python3dist(pip) >= 19", + "python3dist(pydantic) >= 2.10.6", + "python3dist(pytest) >= 8.3.5" + ] + } + ], + "python-snakemake-interface-storage-plugins": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-snakemake-interface-storage-plugins::bootstrap:::", + "srpm_filename": "python-snakemake-interface-storage-plugins-4.4.1-2.fc45~bootstrap.src.rpm", + "buildrequires": [ + "(python3dist(humanfriendly) < 11~~ with python3dist(humanfriendly) >= 10)", + "(python3dist(tenacity) < 10~~ with python3dist(tenacity) >= 9.1.4)", + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(poetry-core)", + "python3dist(snakemake-interface-common) >= 1.12", + "python3dist(throttler) >= 1.2.2", + "python3dist(wrapt) >= 1.15" + ] + } + ], + "python-snakemake-interface-executor-plugins": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-snakemake-interface-executor-plugins::bootstrap:::", + "srpm_filename": "python-snakemake-interface-executor-plugins-9.4.0-2.fc45~bootstrap.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(argparse-dataclass) >= 2", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(poetry-core)", + "python3dist(snakemake-interface-common) >= 1.19", + "python3dist(throttler) >= 1.2.2" + ] + } + ], + "python-snakemake-interface-report-plugins": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-snakemake-interface-report-plugins::bootstrap:::", + "srpm_filename": "python-snakemake-interface-report-plugins-1.3.0-4.fc45~bootstrap.src.rpm", + "buildrequires": [ + "(python3dist(snakemake-interface-common) < 2~~ with python3dist(snakemake-interface-common) >= 1.16)", + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(poetry-core)" + ] + } + ], + "snakemake": [ + { + "withs": [ + "bootstrap" + ], + "id": "snakemake::bootstrap:::", + "srpm_filename": "snakemake-9.16.3-3.fc45~bootstrap.src.rpm", + "buildrequires": [ + "(python3dist(conda-inject) < 2~~ with python3dist(conda-inject) >= 1.3.1)", + "(python3dist(dpath) < 3~~ with python3dist(dpath) >= 2.1.6)", + "(python3dist(jinja2) < 4~~ with python3dist(jinja2) >= 3)", + "(python3dist(pulp) < 3.4~~ with python3dist(pulp) >= 2.3.1)", + "(python3dist(requests) < 3~~ with python3dist(requests) >= 2.8.1)", + "(python3dist(smart-open) < 8~~ with python3dist(smart-open) >= 4)", + "(python3dist(snakemake-interface-common) < 2~~ with python3dist(snakemake-interface-common) >= 1.20.1)", + "(python3dist(snakemake-interface-executor-plugins) < 10~~ with python3dist(snakemake-interface-executor-plugins) >= 9.3.2)", + "(python3dist(snakemake-interface-logger-plugins) < 3~~ with python3dist(snakemake-interface-logger-plugins) >= 1.1)", + "(python3dist(snakemake-interface-report-plugins) < 2~~ with python3dist(snakemake-interface-report-plugins) >= 1.2)", + "(python3dist(snakemake-interface-scheduler-plugins) < 3~~ with python3dist(snakemake-interface-scheduler-plugins) >= 2)", + "(python3dist(snakemake-interface-storage-plugins) < 5~~ with python3dist(snakemake-interface-storage-plugins) >= 4.3.2)", + "(python3dist(tomli) if python3-devel < 3.11)", + "(python3dist(yte) < 2~~ with python3dist(yte) >= 1.5.5)", + "R-core", + "help2man", + "pyproject-rpm-macros", + "python-unversioned-command", + "python3-devel", + "python3dist(appdirs)", + "python3dist(configargparse)", + "python3dist(connection-pool) >= 0.0.3", + "python3dist(docutils)", + "python3dist(flask)", + "python3dist(gitpython)", + "python3dist(google-cloud-storage)", + "python3dist(humanfriendly)", + "python3dist(immutables)", + "python3dist(jsonschema)", + "python3dist(nbformat)", + "python3dist(packaging)", + "python3dist(packaging) >= 24", + "python3dist(pip) >= 19", + "python3dist(psutil)", + "python3dist(pygments)", + "python3dist(pyyaml)", + "python3dist(referencing)", + "python3dist(reretry)", + "python3dist(setuptools) >= 64", + "python3dist(setuptools-scm) >= 8", + "python3dist(tabulate)", + "python3dist(throttler)", + "python3dist(wrapt)", + "vim-filesystem" + ] + } + ], + "python-sphinx-theme-builder": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-sphinx-theme-builder::bootstrap:::", + "srpm_filename": "python-sphinx-theme-builder-0.3.2-4.fc45~bootstrap.src.rpm", + "buildrequires": [ + "(python3dist(flit-core) < 4~~ with python3dist(flit-core) >= 3.2)", + "(python3dist(tomli) if python3-devel < 3.11)", + "help2man", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(diagnostic) >= 2", + "python3dist(nodeenv)", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(pyproject-metadata) >= 0.10", + "python3dist(rich)", + "python3dist(setuptools)" + ] + } + ], + "python-httpx": [ + { + "withouts": [ + "tests" + ], + "id": "python-httpx:tests::::", + "srpm_filename": "python-httpx-0.28.1-12.fc45.src.rpm", + "buildrequires": [ + "(python3dist(click) >= 8 with python3dist(click) < 9)", + "(python3dist(h2) < 5~~ with python3dist(h2) >= 3)", + "(python3dist(httpcore) >= 1 with python3dist(httpcore) < 2)", + "(python3dist(pygments) >= 2 with python3dist(pygments) < 3)", + "(python3dist(socksio) >= 1 with python3dist(socksio) < 2)", + "(python3dist(tomli) if python3-devel < 3.11)", + "help2man", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(anyio)", + "python3dist(brotli)", + "python3dist(certifi)", + "python3dist(hatch-fancy-pypi-readme)", + "python3dist(hatchling)", + "python3dist(idna)", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(rich) >= 10" + ] + } + ], + "python-oslo-i18n": [ + { + "replacements": { + "with_doc": "0" + }, + "id": "python-oslo-i18n:::with_doc::", + "srpm_filename": "python-oslo-i18n-6.7.2-1.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "git-core", + "pyproject-rpm-macros", + "python3-babel", + "python3-devel", + "python3dist(packaging)", + "python3dist(pbr) >= 2", + "python3dist(pbr) >= 6.1.1", + "python3dist(pip) >= 19" + ] + } + ], + "python-pymssql": [ + { + "withouts": [ + "tests" + ], + "id": "python-pymssql:tests::::", + "srpm_filename": "python-pymssql-2.3.13-1.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "freetds-devel", + "gcc", + "krb5-devel", + "openssl-devel", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(cython)", + "python3dist(cython) >= 3.1", + "python3dist(packaging)", + "python3dist(packaging) >= 24.2", + "python3dist(pip) >= 19", + "python3dist(setuptools) > 80.0", + "python3dist(setuptools) >= 40.8", + "python3dist(setuptools-scm) >= 8", + "python3dist(setuptools-scm[toml]) >= 8", + "python3dist(tomli)", + "python3dist(wheel) >= 0.36.2", + "tomcli" + ] + } + ], + "python-authlib": [ + { + "withouts": [ + "tests" + ], + "id": "python-authlib:tests::::", + "srpm_filename": "python-authlib-1.5.2-3.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(cryptography)", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools)", + "python3dist(wheel)" + ] + } + ], + "python-oslotest": [ + { + "withs": [ + "repo_bootstrap" + ], + "id": "python-oslotest::repo_bootstrap:::", + "srpm_filename": "python-oslotest-5.0.0-12.fc45.src.rpm", + "buildrequires": [ + "/usr/bin/gpgv2", + "git-core", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(fixtures) >= 3", + "python3dist(packaging)", + "python3dist(pbr) >= 2", + "python3dist(pip) >= 19", + "python3dist(python-subunit) >= 1", + "python3dist(setuptools) >= 40.8", + "python3dist(testtools) >= 2.2" + ] + } + ], + "python-gunicorn": [ + { + "withouts": [ + "extras" + ], + "id": "python-gunicorn:extras::::", + "srpm_filename": "python-gunicorn-23.0.0-7.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "make", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(pytest) >= 7.2", + "python3dist(setuptools) >= 61.2", + "python3dist(setuptools) >= 68", + "python3dist(sphinx)", + "python3dist(sphinx-rtd-theme)" + ] + } + ], + "python-execnet": [ + { + "withouts": [ + "optional_test_deps" + ], + "id": "python-execnet:optional_test_deps::::", + "srpm_filename": "python-execnet-2.1.2-2.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "/usr/bin/ps", + "/usr/bin/sphinx-build-3", + "make", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(hatch-vcs)", + "python3dist(hatchling) >= 1.26", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(pytest)", + "python3dist(pytest-timeout)", + "python3dist(tox)", + "python3dist(tox-current-env) >= 0.0.16" + ] + } + ], + "python-threadpoolctl": [ + { + "withouts": [ + "check" + ], + "id": "python-threadpoolctl:check::::", + "srpm_filename": "python-threadpoolctl-3.5.0-12.fc45.src.rpm", + "buildrequires": [ + "(python3dist(flit-core) < 4~~ with python3dist(flit-core) >= 2)", + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pip) >= 19" + ] + } + ], + "python-tabulate": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-tabulate::bootstrap:::", + "srpm_filename": "python-tabulate-0.10.0-7.fc45~bootstrap.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(numpy)", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(prettytable)", + "python3dist(pytest)", + "python3dist(setuptools) >= 77.0.3", + "python3dist(setuptools-scm) >= 3.4.3", + "python3dist(setuptools-scm[toml]) >= 3.4.3", + "python3dist(texttable)", + "python3dist(wcwidth)" + ] + } + ], + "python-repoze-sphinx-autointerface": [ + { + "withouts": [ + "tests" + ], + "id": "python-repoze-sphinx-autointerface:tests::::", + "srpm_filename": "python-repoze-sphinx-autointerface-1.0.0-12.fc45.src.rpm", + "buildrequires": [ + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools)", + "python3dist(setuptools) >= 40.8", + "python3dist(sphinx) >= 4", + "python3dist(zope-interface)" + ] + } + ], + "python-zope-exceptions": [ + { + "withouts": [ + "tests" + ], + "id": "python-zope-exceptions:tests::::", + "srpm_filename": "python-zope-exceptions-6.0-4.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools)", + "python3dist(setuptools) >= 78.1.1", + "python3dist(wheel)", + "python3dist(zope-interface)" + ] + } + ], + "python-deepdiff": [ + { + "withouts": [ + "tests" + ], + "id": "python-deepdiff:tests::::", + "srpm_filename": "python-deepdiff-8.6.1-3.fc45.src.rpm", + "buildrequires": [ + "(python3dist(flit-core) < 4~~ with python3dist(flit-core) >= 3.9)", + "(python3dist(orderly-set) < 6~~ with python3dist(orderly-set) >= 5.4.1)", + "(python3dist(pyyaml) >= 6 with python3dist(pyyaml) < 6.1)", + "(python3dist(tomli) if python3-devel < 3.11)", + "make", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(click) >= 8.1", + "python3dist(orjson)", + "python3dist(packaging)", + "python3dist(pip) >= 19" + ] + } + ], + "python-sentry-sdk": [ + { + "withouts": [ + "tests" + ], + "id": "python-sentry-sdk:tests::::", + "srpm_filename": "python-sentry-sdk-2.48.0-3.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "protobuf-compiler", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(certifi)", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools) >= 40.8", + "python3dist(urllib3) >= 1.26.11" + ] + } + ], + "python-dask": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-dask::bootstrap:::", + "srpm_filename": "python-dask-2026.1.2-2.fc45~bootstrap.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(aiohttp)", + "python3dist(bottleneck)", + "python3dist(click) >= 8.1", + "python3dist(cloudpickle) >= 3", + "python3dist(crick)", + "python3dist(fastavro)", + "python3dist(fsspec) >= 2021.9", + "python3dist(graphviz)", + "python3dist(h5py)", + "python3dist(ipython)", + "python3dist(jsonschema)", + "python3dist(matplotlib)", + "python3dist(numpy) >= 1.24", + "python3dist(packaging)", + "python3dist(packaging) >= 20", + "python3dist(pandas)", + "python3dist(pandas) >= 2", + "python3dist(pandas[test])", + "python3dist(partd) >= 1.4", + "python3dist(pip) >= 19", + "python3dist(psutil)", + "python3dist(pyarrow) >= 16", + "python3dist(pytest)", + "python3dist(pytest-mock)", + "python3dist(pytest-rerunfailures)", + "python3dist(pytest-timeout)", + "python3dist(pytest-xdist)", + "python3dist(python-snappy)", + "python3dist(pyyaml) >= 5.3.1", + "python3dist(requests)", + "python3dist(setuptools) >= 80", + "python3dist(setuptools-scm) >= 9", + "python3dist(sqlalchemy)", + "python3dist(tables)", + "python3dist(toolz) >= 0.12", + "python3dist(zarr)" + ] + } + ], + "python-build": [ + { + "withouts": [ + "extras" + ], + "id": "python-build:extras::::", + "srpm_filename": "python-build-1.4.0-3.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "pyproject-rpm-macros >= 0-41", + "python3-devel", + "python3dist(filelock) >= 3", + "python3dist(flit-core) >= 3.11", + "python3dist(packaging)", + "python3dist(packaging) >= 24", + "python3dist(pip) >= 19", + "python3dist(pyproject-hooks)", + "python3dist(pytest) >= 6.2.4", + "python3dist(pytest-cov) >= 2.12", + "python3dist(pytest-mock) >= 2", + "python3dist(pytest-rerunfailures) >= 9.1", + "python3dist(pytest-xdist) >= 1.34", + "python3dist(setuptools) >= 67.8", + "python3dist(setuptools-scm) >= 6", + "python3dist(uv) >= 0.1.18", + "python3dist(virtualenv) >= 20.0.35", + "python3dist(wheel) >= 0.36" + ] + } + ], + "meson": [ + { + "withouts": [ + "check" + ], + "id": "meson:check::::", + "srpm_filename": "meson-1.10.2-2.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools) >= 42", + "python3dist(wheel)" + ] + } + ], + "python-gmpy2": [ + { + "withouts": [ + "tests" + ], + "id": "python-gmpy2:tests::::", + "srpm_filename": "python-gmpy2-2.3.0-2.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "gcc", + "gmp-devel", + "libmpc-devel", + "make", + "pkgconfig(mpfr)", + "pyproject-rpm-macros", + "python3-devel", + "python3-docs", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools) >= 77", + "python3dist(setuptools-scm) >= 6", + "python3dist(setuptools-scm[toml]) >= 6", + "python3dist(sphinx)", + "python3dist(sphinx) >= 4", + "python3dist(sphinx-rtd-theme) >= 1" + ] + } + ], + "python-humanfriendly": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-humanfriendly::bootstrap:::", + "srpm_filename": "python-humanfriendly-10.0-20.fc45~bootstrap.1.src.rpm", + "buildrequires": [ + "python3-devel", + "python3-setuptools", + "python3-sphinx" + ] + } + ], + "python-cascadio": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-cascadio::bootstrap:::", + "srpm_filename": "python-cascadio-0.0.17-6.fc45~bootstrap.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "cmake(OpenCASCADE)", + "gcc-c++", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(pybind11)", + "python3dist(scikit-build-core) >= 0.9", + "rapidjson-static" + ] + } + ], + "python-pint": [ + { + "withouts": [ + "xarray" + ], + "id": "python-pint:xarray::::", + "srpm_filename": "python-pint-0.25.3-2.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(flexcache) >= 0.3", + "python3dist(flexparser) >= 0.4", + "python3dist(hatch-vcs)", + "python3dist(hatchling)", + "python3dist(matplotlib)", + "python3dist(numpy) >= 1.23", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(platformdirs) >= 2.1", + "python3dist(pytest)", + "python3dist(pytest-benchmark)", + "python3dist(pytest-mpl)", + "python3dist(pytest-subtests)", + "python3dist(scipy)", + "python3dist(typing-extensions) >= 4", + "tomcli" + ] + } + ], + "python-astropy-iers-data": [ + { + "withouts": [ + "tests" + ], + "id": "python-astropy-iers-data:tests::::", + "srpm_filename": "python-astropy-iers-data-0.2026.3.2.0.47.4-2.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(hatch-vcs)", + "python3dist(hatchling) >= 1.5", + "python3dist(packaging)", + "python3dist(pip) >= 19" + ] + } + ], + "python-pymongo": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-pymongo::bootstrap:::", + "srpm_filename": "python-pymongo-4.13.2-4.fc45~bootstrap.1.src.rpm", + "buildrequires": [ + "(python3dist(dnspython) < 3~~ with python3dist(dnspython) >= 1.16)", + "(python3dist(tomli) if python3-devel < 3.11)", + "gcc", + "make", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(cryptography) >= 2.5", + "python3dist(hatch-requirements-txt) >= 0.4.1", + "python3dist(hatchling) > 1.24.0", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(pyopenssl) >= 17.2", + "python3dist(pytest) >= 8.2", + "python3dist(pytest-asyncio) >= 0.23", + "python3dist(python-snappy)", + "python3dist(requests) < 3~~", + "python3dist(service-identity) >= 18.1", + "python3dist(setuptools) >= 65", + "python3dist(zstandard)" + ] + } + ], + "python-pyogrio": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-pyogrio::bootstrap:::", + "srpm_filename": "python-pyogrio-0.12.1-3.fc45~bootstrap.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "gcc", + "gdal-devel >= 2.4.0", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(certifi)", + "python3dist(cython) >= 3.1", + "python3dist(numpy)", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(pyarrow)", + "python3dist(pytest)", + "python3dist(setuptools)", + "python3dist(versioneer) >= 0.28", + "python3dist(versioneer[toml]) >= 0.28" + ] + } + ], + "python-fastapi": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-fastapi::bootstrap:::", + "srpm_filename": "python-fastapi-0.136.0-3.fc45~bootstrap.src.rpm", + "buildrequires": [ + "(python3dist(httpx) < 1~~ with python3dist(httpx) >= 0.23)", + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(a2wsgi) >= 1.9", + "python3dist(annotated-doc) >= 0.0.2", + "python3dist(anyio[trio]) >= 3.2.1", + "python3dist(dirty-equals) >= 0.9", + "python3dist(email-validator) >= 2", + "python3dist(fastar) >= 0.9", + "python3dist(flask) >= 3", + "python3dist(httpx) >= 0.23", + "python3dist(inline-snapshot) >= 0.21.1", + "python3dist(itsdangerous) >= 1.1", + "python3dist(jinja2) >= 3.1.5", + "python3dist(orjson) >= 3.9.3", + "python3dist(packaging)", + "python3dist(pdm-backend)", + "python3dist(pip) >= 19", + "python3dist(pydantic) >= 2.9", + "python3dist(pydantic-extra-types) >= 2", + "python3dist(pydantic-settings) >= 2", + "python3dist(pytest) >= 8", + "python3dist(pytest-timeout) >= 2.4", + "python3dist(pytest-xdist[psutil]) >= 2.5", + "python3dist(python-multipart) >= 0.0.18", + "python3dist(pyyaml) >= 5.3.1", + "python3dist(sqlalchemy)", + "python3dist(starlette) >= 0.46", + "python3dist(typing-extensions) >= 4.8", + "python3dist(typing-inspection) >= 0.4.2", + "python3dist(ujson) >= 5.8", + "python3dist(uvicorn) >= 0.12", + "python3dist(uvicorn[standard]) >= 0.12" + ] + } + ], + "python-pydantic-core": [ + { + "withouts": [ + "inline_snapshot_tests" + ], + "id": "python-pydantic-core:inline_snapshot_tests::::", + "srpm_filename": "python-pydantic-core-2.41.5-4.fc45.src.rpm", + "buildrequires": [ + "(crate(ahash/default) >= 0.8.12 with crate(ahash/default) < 0.9.0~)", + "(crate(base64/default) >= 0.22.1 with crate(base64/default) < 0.23.0~)", + "(crate(enum_dispatch/default) >= 0.3.13 with crate(enum_dispatch/default) < 0.4.0~)", + "(crate(hex/default) >= 0.4.3 with crate(hex/default) < 0.5.0~)", + "(crate(idna/default) >= 1.1.0 with crate(idna/default) < 2.0.0~)", + "(crate(jiter/default) >= 0.11.1 with crate(jiter/default) < 0.12.0~)", + "(crate(jiter/python) >= 0.11.1 with crate(jiter/python) < 0.12.0~)", + "(crate(num-bigint/default) >= 0.4.6 with crate(num-bigint/default) < 0.5.0~)", + "(crate(num-traits/default) >= 0.2.19 with crate(num-traits/default) < 0.3.0~)", + "(crate(percent-encoding/default) >= 2.3.2 with crate(percent-encoding/default) < 3.0.0~)", + "(crate(pyo3-build-config/default) >= 0.26.0 with crate(pyo3-build-config/default) < 0.27.0~)", + "(crate(pyo3/default) >= 0.26.0 with crate(pyo3/default) < 0.27.0~)", + "(crate(pyo3/num-bigint) >= 0.26.0 with crate(pyo3/num-bigint) < 0.27.0~)", + "(crate(pyo3/py-clone) >= 0.26.0 with crate(pyo3/py-clone) < 0.27.0~)", + "(crate(regex/default) >= 1.12.2 with crate(regex/default) < 2.0.0~)", + "(crate(serde/default) >= 1.0.219 with crate(serde/default) < 2.0.0~)", + "(crate(serde/derive) >= 1.0.219 with crate(serde/derive) < 2.0.0~)", + "(crate(serde_json/arbitrary_precision) >= 1.0.145 with crate(serde_json/arbitrary_precision) < 2.0.0~)", + "(crate(serde_json/default) >= 1.0.145 with crate(serde_json/default) < 2.0.0~)", + "(crate(smallvec/default) >= 1.15.1 with crate(smallvec/default) < 2.0.0~)", + "(crate(speedate/default) >= 0.17.0 with crate(speedate/default) < 0.18.0~)", + "(crate(strum/default) >= 0.27.0 with crate(strum/default) < 0.28.0~)", + "(crate(strum/derive) >= 0.27.0 with crate(strum/derive) < 0.28.0~)", + "(crate(strum_macros/default) >= 0.27.0 with crate(strum_macros/default) < 0.28.0~)", + "(crate(url/default) >= 2.5.4 with crate(url/default) < 3.0.0~)", + "(crate(uuid/default) >= 1.18.1 with crate(uuid/default) < 2.0.0~)", + "(crate(version_check/default) >= 0.9.5 with crate(version_check/default) < 0.10.0~)", + "(python3dist(maturin) < 2~~ with python3dist(maturin) >= 1.9.4)", + "(python3dist(tomli) if python3-devel < 3.11)", + "cargo-rpm-macros >= 24", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(dirty-equals)", + "python3dist(hypothesis)", + "python3dist(maturin)", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(pytest)", + "python3dist(pytest-mock)", + "python3dist(python-dateutil)", + "python3dist(typing-extensions) >= 4.14.1", + "python3dist(typing-inspection) >= 0.4.1", + "rust >= 1.75", + "tomcli" + ] + } + ], + "python-django5": [ + { + "withouts": [ + "tests" + ], + "id": "python-django5:tests::::", + "srpm_filename": "python-django5-5.2.11-2.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "bash-completion", + "make", + "pyproject-rpm-macros", + "python3-asgiref", + "python3-devel", + "python3dist(asgiref) >= 3.8.1", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(pyenchant)", + "python3dist(setuptools) >= 77.0.3", + "python3dist(sphinx) >= 4.5", + "python3dist(sphinxcontrib-spelling)", + "python3dist(sqlparse) >= 0.3.1" + ] + } + ], + "python-django6": [ + { + "withouts": [ + "tests" + ], + "id": "python-django6:tests::::", + "srpm_filename": "python-django6-6.0.2-2.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "bash-completion", + "make", + "pyproject-rpm-macros", + "python3-asgiref", + "python3-devel", + "python3dist(asgiref) >= 3.9.1", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(pyenchant)", + "python3dist(setuptools) >= 77.0.3", + "python3dist(sphinx) >= 4.5", + "python3dist(sphinx-lint)", + "python3dist(sphinxcontrib-spelling)", + "python3dist(sqlparse) >= 0.5" + ] + } + ], + "python-pyproject-hooks": [ + { + "withouts": [ + "tests" + ], + "id": "python-pyproject-hooks:tests::::", + "srpm_filename": "python-pyproject-hooks-1.2.0-10.fc45.src.rpm", + "buildrequires": [ + "(python3dist(flit-core) < 4~~ with python3dist(flit-core) >= 3.2)", + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pip) >= 19" + ] + } + ], + "python-Automat": [ + { + "withouts": [ + "doc" + ], + "id": "python-Automat:doc::::", + "srpm_filename": "python-Automat-24.8.1-9.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(coverage)", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(pytest)", + "python3dist(setuptools) >= 35.0.2", + "python3dist(setuptools-scm)", + "python3dist(tox)", + "python3dist(tox-current-env) >= 0.0.16", + "python3dist(wheel) >= 0.29" + ] + } + ], + "python-psutil": [ + { + "withouts": [ + "xdist" + ], + "id": "python-psutil:xdist::::", + "srpm_filename": "python-psutil-7.2.2-4.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "gcc", + "make", + "procps-ng", + "pyproject-rpm-macros", + "python3-devel", + "python3-pytest", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools) >= 43", + "sed" + ] + } + ], + "python-apkinspector": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-apkinspector::bootstrap:::", + "srpm_filename": "python-apkinspector-1.3.2-9.fc45~bootstrap.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3-sphinx_lv2_theme", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(poetry-core)", + "python3dist(sphinx)" + ] + } + ], + "python-androguard": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-androguard::bootstrap:::", + "srpm_filename": "python-androguard-3.4.0a1-9.fc45~bootstrap.src.rpm", + "buildrequires": [ + "pyproject-rpm-macros", + "python3-devel", + "python3-pyperclip", + "python3-qt5", + "python3dist(asn1crypto) >= 0.24", + "python3dist(click) >= 7", + "python3dist(colorama) >= 0.4.1", + "python3dist(ipython) >= 5", + "python3dist(lxml) >= 4.3", + "python3dist(matplotlib) >= 3.0.2", + "python3dist(networkx) >= 2.2", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(pydot) >= 1.4.1", + "python3dist(pygments) >= 2.3.1", + "python3dist(setuptools)", + "python3dist(setuptools) >= 40.8" + ] + } + ], + "python-service-identity": [ + { + "withouts": [ + "docs" + ], + "id": "python-service-identity:docs::::", + "srpm_filename": "python-service-identity-24.2.0-9.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(attrs) >= 19.1", + "python3dist(cryptography)", + "python3dist(hatch-vcs)", + "python3dist(hatchling)", + "python3dist(idna)", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(pyasn1)", + "python3dist(pyasn1-modules)", + "python3dist(pytest)" + ] + } + ], + "mkdocs-material": [ + { + "withs": [ + "bootstrap" + ], + "id": "mkdocs-material::bootstrap:::", + "srpm_filename": "mkdocs-material-9.7.1-3.fc45~bootstrap.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(babel) >= 2.10", + "python3dist(backrefs) >= 5.7^post1", + "python3dist(colorama) >= 0.4", + "python3dist(hatch-nodejs-version) >= 0.3", + "python3dist(hatch-requirements-txt)", + "python3dist(hatchling)", + "python3dist(jinja2) >= 3.1", + "python3dist(markdown) >= 3.2", + "python3dist(mkdocs) >= 1.6", + "python3dist(mkdocs-material-extensions) >= 1.3", + "python3dist(packaging)", + "python3dist(paginate) >= 0.5", + "python3dist(pip) >= 19", + "python3dist(pygments) >= 2.16", + "python3dist(pymdown-extensions) >= 10.2", + "python3dist(requests) >= 2.30", + "python3dist(trove-classifiers) >= 2023.10.18", + "sed" + ] + } + ], + "python-mkdocs-material-extensions": [ + { + "withouts": [ + "tests" + ], + "id": "python-mkdocs-material-extensions:tests::::", + "srpm_filename": "python-mkdocs-material-extensions-1.3.1-11.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(hatchling) >= 0.21.1", + "python3dist(packaging)", + "python3dist(pip) >= 19" + ] + } + ], + "python-zope-testing": [ + { + "withouts": [ + "tests" + ], + "id": "python-zope-testing:tests::::", + "srpm_filename": "python-zope-testing-6.1-2.fc45.src.rpm", + "buildrequires": [ + "(python3dist(setuptools) < 81~~ with python3dist(setuptools) >= 78.1.1)", + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(wheel)" + ] + } + ], + "python-xnat": [ + { + "withouts": [ + "tests" + ], + "id": "python-xnat:tests::::", + "srpm_filename": "python-xnat-0.7.2-13.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "git-core", + "help2man", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(click)", + "python3dist(click-option-group)", + "python3dist(hatchling)", + "python3dist(importlib-metadata)", + "python3dist(isodate)", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(progressbar2)", + "python3dist(pydicom)", + "python3dist(python-dateutil)", + "python3dist(pyyaml)", + "python3dist(requests)", + "python3dist(versioningit)" + ] + } + ], + "python-tablib": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-tablib::bootstrap:::", + "srpm_filename": "python-tablib-3.8.0-9.fc45~bootstrap.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(odfpy)", + "python3dist(openpyxl) >= 2.6", + "python3dist(packaging)", + "python3dist(pandas)", + "python3dist(pip) >= 19", + "python3dist(pytest)", + "python3dist(pyyaml)", + "python3dist(setuptools) >= 58", + "python3dist(setuptools-scm) >= 6.2", + "python3dist(setuptools-scm[toml]) >= 6.2", + "python3dist(tabulate)", + "python3dist(xlrd)", + "python3dist(xlwt)" + ] + } + ], + "llvm21": [ + { + "withouts": [ + "pgo" + ], + "id": "llvm21:pgo::::", + "srpm_filename": "llvm21-21.1.8-4.fc45.src.rpm", + "buildrequires": [ + "binutils-devel", + "binutils-gold", + "chrpath", + "clang(major) = 21", + "cmake", + "doxygen", + "elfutils-libelf-devel", + "emacs", + "gcc", + "gcc-c++", + "gnupg2", + "graphviz", + "libatomic", + "libedit-devel", + "libffi-devel", + "libxml2-devel", + "libzstd-devel", + "multilib-rpm-config", + "ncurses-devel", + "ninja-build", + "perl", + "perl(Digest::MD5)", + "perl(File::Copy)", + "perl(File::Find)", + "perl(File::Path)", + "perl(File::Temp)", + "perl(FindBin)", + "perl(Hash::Util)", + "perl(Sys::Hostname)", + "perl(Term::ANSIColor)", + "perl(Text::ParseWords)", + "perl(lib)", + "perl-Data-Dumper", + "perl-Encode", + "perl-generators", + "procps-ng", + "python3-devel", + "python3-myst-parser", + "python3-pexpect", + "python3-psutil", + "python3-sphinx", + "swig", + "valgrind-devel", + "zlib-devel" + ] + } + ], + "python-pathspec": [ + { + "withouts": [ + "tests" + ], + "id": "python-pathspec:tests::::", + "srpm_filename": "python-pathspec-1.0.4-3.fc45.src.rpm", + "buildrequires": [ + "(python3dist(flit-core) < 5~~ with python3dist(flit-core) >= 3.2)", + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pip) >= 19" + ] + } + ], + "python-hatch-vcs": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-hatch-vcs::bootstrap:::", + "srpm_filename": "python-hatch-vcs-0.5.0-8.fc45~bootstrap.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(hatchling) >= 1.1", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools-scm) >= 8.2" + ] + } + ], + "python-cobalt": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-cobalt::bootstrap:::", + "srpm_filename": "python-cobalt-9.0.1-9.fc45~bootstrap.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3-pytest", + "python3dist(iso8601) >= 0.1", + "python3dist(lxml) >= 3.4.1", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools)", + "python3dist(setuptools-scm) >= 6.2", + "python3dist(setuptools-scm[toml]) >= 6.2", + "python3dist(wheel)" + ] + } + ], + "python-trove-classifiers": [ + { + "withouts": [ + "tests" + ], + "id": "python-trove-classifiers:tests::::", + "srpm_filename": "python-trove-classifiers-2026.1.14.14-3.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools) >= 61" + ] + } + ], + "python-snakemake-interface-common": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-snakemake-interface-common::bootstrap:::", + "srpm_filename": "python-snakemake-interface-common-1.23.0-2.fc45~bootstrap.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(argparse-dataclass) >= 2", + "python3dist(configargparse) >= 1.7", + "python3dist(packaging)", + "python3dist(packaging) >= 24", + "python3dist(pip) >= 19", + "python3dist(pytest)", + "python3dist(setuptools) >= 42", + "python3dist(wheel)" + ] + } + ], + "python-snakemake-interface-scheduler-plugins": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-snakemake-interface-scheduler-plugins::bootstrap:::", + "srpm_filename": "python-snakemake-interface-scheduler-plugins-2.0.2-4.fc45~bootstrap.src.rpm", + "buildrequires": [ + "(python3dist(snakemake-interface-common) < 2~~ with python3dist(snakemake-interface-common) >= 1.20.1)", + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(hatchling)", + "python3dist(packaging)", + "python3dist(pip) >= 19" + ] + } + ], + "python-snakemake-interface-logger-plugins": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-snakemake-interface-logger-plugins::bootstrap:::", + "srpm_filename": "python-snakemake-interface-logger-plugins-2.0.1-2.fc45~bootstrap.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(hatchling)", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(snakemake-interface-common) >= 1.17.4" + ] + } + ], + "python-azure-mgmt-core": [ + { + "withouts": [ + "tests" + ], + "id": "python-azure-mgmt-core:tests::::", + "srpm_filename": "python-azure-mgmt-core-1.6.0-3.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(azure-core) >= 1.32", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools) >= 40.8" + ] + } + ], + "python-networkx": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-networkx::bootstrap:::", + "srpm_filename": "python-networkx-3.6.1-4.fc45~bootstrap.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "make", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools) >= 77.0.3" + ] + } + ], + "python-paste-deploy": [ + { + "withouts": [ + "tests" + ], + "id": "python-paste-deploy:tests::::", + "srpm_filename": "python-paste-deploy-3.1.0-13.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools) >= 41" + ] + } + ], + "python-llm": [ + { + "withouts": [ + "tests" + ], + "id": "python-llm:tests::::", + "srpm_filename": "python-llm-0.28-3.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "make", + "pyproject-rpm-macros", + "python3-devel", + "python3-furo", + "python3-myst-parser", + "python3-sphinx-copybutton", + "python3-sphinx-markdown-builder", + "python3dist(click)", + "python3dist(click-default-group) >= 1.2.2", + "python3dist(condense-json) >= 0.1.3", + "python3dist(openai) >= 1.55.3", + "python3dist(packaging)", + "python3dist(pip)", + "python3dist(pip) >= 19", + "python3dist(pluggy)", + "python3dist(puremagic)", + "python3dist(pydantic) >= 2", + "python3dist(python-ulid)", + "python3dist(pyyaml)", + "python3dist(setuptools)", + "python3dist(sqlite-migrate) >= 0.1~a2", + "python3dist(sqlite-utils) >= 3.37" + ] + } + ], + "python-rjsmin": [ + { + "withouts": [ + "docs" + ], + "id": "python-rjsmin:docs::::", + "srpm_filename": "python-rjsmin-1.2.5-3.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "gcc", + "pyproject-rpm-macros", + "python3-devel", + "python3-pytest", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools)" + ] + } + ], + "python-webtest": [ + { + "withouts": [ + "tests" + ], + "id": "python-webtest:tests::::", + "srpm_filename": "python-webtest-3.0.7-2.fc45.src.rpm", + "buildrequires": [ + "pyproject-rpm-macros", + "python3-devel", + "python3dist(beautifulsoup4)", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools) >= 40.8", + "python3dist(waitress) >= 3.0.2", + "python3dist(webob) >= 1.2" + ] + } + ], + "python-menuinst": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-menuinst::bootstrap:::", + "srpm_filename": "python-menuinst-2.4.2-2.fc45~bootstrap.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python-unversioned-command", + "python3-devel", + "python3-pydantic", + "python3-pytest", + "python3dist(packaging)", + "python3dist(pip) >= 19", + "python3dist(setuptools) >= 45", + "python3dist(setuptools-scm) >= 6.2", + "python3dist(setuptools-scm[toml]) >= 6.2" + ] + } + ], + "python-cliff": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-cliff::bootstrap:::", + "srpm_filename": "python-cliff-4.13.2-3.fc45~bootstrap.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "bash", + "pyproject-rpm-macros", + "python3-devel", + "python3-docutils", + "python3dist(autopage) >= 0.4", + "python3dist(cmd2) >= 1", + "python3dist(packaging)", + "python3dist(pbr) >= 2", + "python3dist(pbr) >= 6.1.1", + "python3dist(pip) >= 19", + "python3dist(prettytable) >= 0.7.2", + "python3dist(pyyaml) >= 3.12", + "python3dist(stevedore) >= 5.6", + "which" + ] + } + ], + "python-stevedore": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-stevedore::bootstrap:::", + "srpm_filename": "python-stevedore-5.7.0-4.fc45~bootstrap.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "/usr/bin/gpgv2", + "git-core", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(pbr) >= 2", + "python3dist(pbr) >= 6.1.1", + "python3dist(pip) >= 19" + ] + } + ], + "python-vcs-versioning": [ + { + "withs": [ + "bootstrap" + ], + "id": "python-vcs-versioning::bootstrap:::", + "srpm_filename": "python-vcs-versioning-1.1.1-2.fc45~bootstrap.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(packaging)", + "python3dist(packaging) >= 20", + "python3dist(pip) >= 19", + "python3dist(setuptools) >= 77.0.3" + ] + } + ], + "python-scikit-build-core": [ + { + "withouts": [ + "optional_tests" + ], + "id": "python-scikit-build-core:optional_tests::::", + "srpm_filename": "python-scikit-build-core-0.12.1-3.fc45.src.rpm", + "buildrequires": [ + "(python3dist(tomli) if python3-devel < 3.11)", + "cmake", + "gcc", + "gcc-c++", + "git", + "ninja-build", + "pyproject-rpm-macros", + "python3-devel", + "python3dist(build) >= 0.8", + "python3dist(cattrs) >= 22.2", + "python3dist(filelock) >= 3.8", + "python3dist(hatch-vcs) >= 0.4", + "python3dist(hatchling) >= 1.24", + "python3dist(packaging)", + "python3dist(packaging) >= 23.2", + "python3dist(pathspec) >= 0.10.1", + "python3dist(pip) >= 19", + "python3dist(pip) >= 24.1", + "python3dist(pytest) >= 7.2", + "python3dist(pytest-subprocess) >= 1.5", + "python3dist(setuptools) >= 66.1", + "python3dist(virtualenv) >= 20.20", + "python3dist(wheel) >= 0.40" + ] + } + ] + } +} \ No newline at end of file diff --git a/build.py b/build.py index 9d5383cf..03c95dc0 100644 --- a/build.py +++ b/build.py @@ -1,73 +1,162 @@ import pathlib import sys -# this module reuses bconds functions heavily -# XXX move to a common module? -from bconds import clone_into, refresh_gitrepo, patch_spec, run - -# the following bcond things actually do stay there from bconds import reverse_id_lookup, build_reverse_id_lookup - -from utils import CONFIG +from gitrepo import patch_spec, refresh_or_clone +from utils import CONFIG, run PATCHDIR = pathlib.Path('patches_dir') FEDPKG_CACHEDIR = pathlib.Path(CONFIG['cache_dir']['fedpkg']) +SPEC_EXTENSION = '.spec' + + +def parse_component_argument(component_arg): + """ + Parse component name and optional bcond configuration from argument. + + Args: + component_arg: Either a plain component name or bcond identifier + + Returns: + Tuple of (component_name, bootstrap_config or None) + """ + if ':' not in component_arg: + return component_arg, None + + build_reverse_id_lookup() + bootstrap = reverse_id_lookup[component_arg] + component_name = component_arg.partition(':')[0] + return component_name, bootstrap + + +def get_patch_path(component_name): + """Get the path to a component's patch file.""" + return PATCHDIR / f'{component_name}.patch' + + +def get_spec_path(repopath, component_name): + """Get the path to a component's spec file.""" + return repopath / f'{component_name}{SPEC_EXTENSION}' + + +def revert_existing_patch(repopath, patch_path): + """ + Revert an existing patch if it exists. + + Args: + repopath: Path to the repository + patch_path: Path to the patch file + + Raises: + NotImplementedError: If trying to apply double bootstrap + """ + if not patch_path.exists(): + return + + with patch_path.open('r') as patchfile: + run('patch', '-R', '-p1', stdin=patchfile, cwd=repopath) + patch_path.unlink() -if __name__ == '__main__': - try: - if len(sys.argv) != 2: - sys.exit('This requires one argument.') - component_name = sys.argv[1] - - bootstrap = None - if ':' in component_name: - build_reverse_id_lookup() - bootstrap = reverse_id_lookup[component_name] - component_name, *_ = component_name.partition(':') - - # XXX make a reusable function with just refresh_gitrepo/clone_into - repopath = FEDPKG_CACHEDIR / component_name - if repopath.exists(): - refresh_gitrepo(repopath, prune_exisitng=True) - else: - FEDPKG_CACHEDIR.mkdir(exist_ok=True) - clone_into(component_name, repopath) - - specpath = repopath / f'{component_name}.spec' - - # Find any patches from previous bootstrap builds - patch = PATCHDIR / f'{component_name}.patch' - if patch.exists(): - if bootstrap: - raise NotImplementedError('Double bootstrap is not yet supported') - with patch.open('r') as patchfile: - run('patch', '-R', '-p1', stdin=patchfile, cwd=repopath) - patch.unlink() +def prepare_bootstrap_build(repopath, component_name, specpath, bootstrap): + """ + Prepare a bootstrap build by patching spec and saving diff. + + Args: + repopath: Path to the repository + component_name: Name of the component + specpath: Path to the spec file + bootstrap: Bootstrap configuration + + Returns: + str: Commit message for bootstrap build + """ + message = CONFIG['distgit']['bootstrap_commit_message'] + patch_spec(specpath, bootstrap) + diff = run('git', '-C', repopath, 'diff').stdout + patch_path = get_patch_path(component_name) + patch_path.write_text(diff) + return message + + +def commit_and_push_changes(repopath, component_name, specpath, message): + """ + Bump spec, commit changes, and push to remote. + + Args: + repopath: Path to the repository + component_name: Name of the component + specpath: Path to the spec file + message: Commit message + """ + run('rpmdev-bumpspec', '-c', message, '--userstring', CONFIG['distgit']['author'], specpath) + run('git', '-C', repopath, 'commit', '--allow-empty', + f'{component_name}{SPEC_EXTENSION}', '-m', message, + '--author', CONFIG['distgit']['author']) + # raise NotImplementedError('no pushing yet') + run('git', '-C', repopath, 'push') + + +def submit_koji_build(repopath): + """ + Submit a Koji build for the component. + + Args: + repopath: Path to the repository + """ + run('fedpkg', 'build', '--fail-fast', '--nowait', + '--target', CONFIG['koji']['target'], cwd=repopath) # '--background' + + +def build_component(component_arg): + """ + Build a component with optional bootstrap configuration. + + Args: + component_arg: Component name or bcond identifier (name:config) + + Raises: + NotImplementedError: If double bootstrap is attempted + """ + component_name, bootstrap = parse_component_argument(component_arg) + repopath = FEDPKG_CACHEDIR / component_name + + refresh_or_clone(repopath, component_name, prune_existing=True) + + specpath = get_spec_path(repopath, component_name) + patch_path = get_patch_path(component_name) + + # Handle existing patches from previous builds + if patch_path.exists(): if bootstrap: - message = CONFIG['distgit']['bootstrap_commit_message'] - patch_spec(specpath, bootstrap) - diff = run('git', '-C', repopath, 'diff').stdout - patch.write_text(diff) - else: - message = CONFIG['distgit']['commit_message'] - - # Bump and commit only if we haven't already, XXX ability to force this - head_commit_msg = run('git', '-C', repopath, 'log', '--format=%B', '-n1', 'HEAD').stdout.rstrip() - if False: # and bootstrap or head_commit_msg != message: - run('rpmdev-bumpspec', '-c', message, '--userstring', CONFIG['distgit']['author'], specpath) - run('git', '-C', repopath, 'commit', '--allow-empty', f'{component_name}.spec', '-m', message, '--author', CONFIG['distgit']['author']) - - #raise NotImplementedError('no pushing yet') - run('git', '-C', repopath, 'push') - run('fedpkg', 'build', '--fail-fast', '--nowait', '--target', CONFIG['koji']['target'], cwd=repopath) # '--background' - - # XXX prune this directory becasue we don't want no thousands clones? - # maybe we are not gonna need this? + raise NotImplementedError('Double bootstrap is not yet supported') + revert_existing_patch(repopath, patch_path) + + if bootstrap: + message = prepare_bootstrap_build(repopath, component_name, specpath, bootstrap) + else: + message = CONFIG['distgit']['commit_message'] + + # Bump and commit only if we haven't already, XXX ability to force this + head_commit_msg = run('git', '-C', repopath, 'log', '--format=%B', '-n1', 'HEAD').stdout.rstrip() + if bootstrap: # or head_commit_msg != message: + commit_and_push_changes(repopath, component_name, specpath, message) + + submit_koji_build(repopath) + + +def main(): + """Main entry point for building components.""" + if len(sys.argv) != 2: + sys.exit('Usage: build.py ') + + try: + build_component(sys.argv[1]) except Exception: print(sys.argv[1]) raise - # XXX prune this directory becasue we don't want no thousands clones? - # maybe we are not gonna need this? + +if __name__ == '__main__': + main() diff --git a/commonly-needed-report.json b/commonly-needed-report.json new file mode 100644 index 00000000..fcd2e9ec --- /dev/null +++ b/commonly-needed-report.json @@ -0,0 +1,622 @@ +{ + "most_commonly_needed": [ + { + "component": "python-sqlmodel", + "count": 24, + "blocked_by": [ + "python-fastapi" + ] + }, + { + "component": "python-smart_open", + "count": 24, + "blocked_by": [ + "python-google-api-core", + "python-google-cloud-core", + "python-google-cloud-storage", + "python-proto-plus" + ] + }, + { + "component": "snakemake", + "count": 22, + "blocked_by": [ + "python-google-api-core", + "python-google-cloud-core", + "python-google-cloud-storage", + "python-proto-plus", + "python-smart_open", + "python-snakemake-executor-plugin-cluster-generic", + "python-snakemake-storage-plugin-fs", + "python-snakemake-storage-plugin-http", + "python-snakemake-storage-plugin-s3", + "python-sqlmodel" + ] + }, + { + "component": "python-proto-plus", + "count": 14, + "blocked_by": [ + "python-google-api-core", + "python-proto-plus" + ] + }, + { + "component": "python-uvicorn", + "count": 14, + "blocked_by": [ + "python-watchfiles" + ] + }, + { + "component": "python-google-api-core", + "count": 12, + "blocked_by": [ + "python-proto-plus" + ] + }, + { + "component": "python-partd", + "count": 11 + }, + { + "component": "python-dask", + "count": 10, + "blocked_by": [ + "python-dask", + "python-distributed", + "python-partd", + "python-xarray" + ] + }, + { + "component": "python-watchfiles", + "count": 9 + }, + { + "component": "python-backrefs", + "count": 9 + }, + { + "component": "python-fastapi", + "count": 7, + "blocked_by": [ + "fastapi-cli", + "fastapi-cloud-cli", + "python-sqlmodel", + "python-uvicorn", + "python-watchfiles" + ] + }, + { + "component": "python-xarray", + "count": 7, + "blocked_by": [ + "python-dask", + "python-partd", + "python-rasterio", + "python-seaborn" + ] + }, + { + "component": "mkdocs-material", + "count": 7, + "blocked_by": [ + "python-backrefs", + "python-mkdocs-git-revision-date-localized-plugin", + "python-mkdocs-rss-plugin" + ] + }, + { + "component": "python-beartype", + "count": 6 + }, + { + "component": "python-google-cloud-core", + "count": 6, + "blocked_by": [ + "python-google-api-core", + "python-proto-plus" + ] + }, + { + "component": "python-fslpy", + "count": 5, + "blocked_by": [ + "python-trimesh" + ] + }, + { + "component": "python-readme-renderer", + "count": 5 + }, + { + "component": "python-seaborn", + "count": 5, + "blocked_by": [ + "python-patsy", + "python-statsmodels" + ] + }, + { + "component": "python-eth-keys", + "count": 5 + }, + { + "component": "python-django-allauth", + "count": 4 + }, + { + "component": "python-patsy", + "count": 4 + }, + { + "component": "python-sse-starlette", + "count": 4, + "blocked_by": [ + "python-fastapi", + "python-uvicorn" + ] + }, + { + "component": "python-lsp-server", + "count": 4 + }, + { + "component": "python-google-cloud-storage", + "count": 4, + "blocked_by": [ + "python-google-api-core", + "python-google-cloud-core", + "python-proto-plus" + ] + }, + { + "component": "python-niaaml", + "count": 3 + }, + { + "component": "python-django-mailman3", + "count": 3, + "blocked_by": [ + "python-django-allauth" + ] + }, + { + "component": "python-trimesh", + "count": 3, + "blocked_by": [ + "blender", + "python-ezdxf", + "usd" + ] + }, + { + "component": "python-rasterio", + "count": 3 + }, + { + "component": "python-statsmodels", + "count": 3, + "blocked_by": [ + "python-patsy" + ] + }, + { + "component": "python-eth-keyfile", + "count": 3, + "blocked_by": [ + "python-eth-keys" + ] + }, + { + "component": "python-pygls", + "count": 3 + }, + { + "component": "python-httpx-sse", + "count": 3, + "blocked_by": [ + "python-sse-starlette" + ] + }, + { + "component": "pyqtwebengine", + "count": 2 + }, + { + "component": "python-matplotlib", + "count": 2 + }, + { + "component": "python-nifti-mrs", + "count": 2, + "blocked_by": [ + "python-fslpy" + ] + }, + { + "component": "usd", + "count": 2, + "blocked_by": [ + "materialx" + ] + }, + { + "component": "materialx", + "count": 2, + "prerel_abi_blocked": true + }, + { + "component": "google-api-python-client", + "count": 2, + "blocked_by": [ + "python-google-api-core", + "python-proto-plus" + ] + }, + { + "component": "fastapi-cloud-cli", + "count": 2, + "blocked_by": [ + "python-uvicorn", + "python-watchfiles" + ] + }, + { + "component": "python-spdx-tools", + "count": 2, + "blocked_by": [ + "python-beartype" + ] + }, + { + "component": "go-vendor-tools", + "count": 2, + "blocked_by": [ + "python-beartype", + "python-spdx-tools", + "scancode-toolkit" + ] + }, + { + "component": "osc", + "count": 2 + }, + { + "component": "qpid-proton", + "count": 2, + "prerel_abi_blocked": true + }, + { + "component": "python-reproject", + "count": 2, + "blocked_by": [ + "python-dask", + "python-partd" + ] + }, + { + "component": "mupdf", + "count": 2, + "prerel_abi_blocked": true, + "blocked_by": [ + "llvm21" + ] + }, + { + "component": "conda-build", + "count": 2, + "blocked_by": [ + "python-conda-index" + ] + }, + { + "component": "python-distributed", + "count": 2, + "blocked_by": [ + "python-dask", + "python-partd" + ] + }, + { + "component": "python-eth-account", + "count": 2, + "blocked_by": [ + "python-eth-keyfile", + "python-eth-keys" + ] + }, + { + "component": "python-twine", + "count": 2, + "blocked_by": [ + "python-readme-renderer" + ] + }, + { + "component": "python-neo", + "count": 2 + } + ], + "most_commonly_last_blocking": [ + { + "component": "python-seaborn", + "count": 3, + "blocked_by": [ + "python-patsy", + "python-statsmodels" + ] + }, + { + "component": "python-pygls", + "count": 3 + }, + { + "component": "python-xarray", + "count": 3, + "blocked_by": [ + "python-dask", + "python-partd", + "python-rasterio", + "python-seaborn" + ] + }, + { + "component": "python-lsp-server", + "count": 3 + }, + { + "component": "python-fastapi", + "count": 3, + "blocked_by": [ + "fastapi-cli", + "fastapi-cloud-cli", + "python-sqlmodel", + "python-uvicorn", + "python-watchfiles" + ] + }, + { + "component": "python-niaaml", + "count": 2 + }, + { + "component": "python-matplotlib", + "count": 2 + }, + { + "component": "go-vendor-tools", + "count": 2, + "blocked_by": [ + "python-beartype", + "python-spdx-tools", + "scancode-toolkit" + ] + }, + { + "component": "osc", + "count": 2 + }, + { + "component": "qpid-proton", + "count": 2, + "prerel_abi_blocked": true + }, + { + "component": "python-trimesh", + "count": 2, + "blocked_by": [ + "blender", + "python-ezdxf", + "usd" + ] + }, + { + "component": "conda-build", + "count": 2, + "blocked_by": [ + "python-conda-index" + ] + }, + { + "component": "python-neo", + "count": 2 + }, + { + "component": "python-eth-keys", + "count": 2 + }, + { + "component": "python-fslpy", + "count": 2, + "blocked_by": [ + "python-trimesh" + ] + }, + { + "component": "python-proto-plus", + "count": 2, + "blocked_by": [ + "python-google-api-core", + "python-proto-plus" + ] + }, + { + "component": "python-uvicorn", + "count": 2, + "blocked_by": [ + "python-watchfiles" + ] + }, + { + "component": "python-watchfiles", + "count": 2 + }, + { + "component": "xapian-bindings", + "count": 2 + }, + { + "component": "pyqtwebengine", + "count": 1 + } + ], + "most_commonly_last_blocking_combinations": [ + { + "components": [ + "python-smart_open", + "python-sqlmodel", + "snakemake" + ], + "count": 18 + }, + { + "components": [ + "mkdocs-material", + "python-backrefs" + ], + "count": 5 + }, + { + "components": [ + "python-dask", + "python-partd" + ], + "count": 4 + }, + { + "components": [ + "python-google-api-core", + "python-proto-plus" + ], + "count": 4 + }, + { + "components": [ + "python-uvicorn", + "python-watchfiles" + ], + "count": 3 + }, + { + "components": [ + "python-patsy", + "python-statsmodels" + ], + "count": 3 + }, + { + "components": [ + "google-api-python-client", + "python-google-api-core", + "python-proto-plus" + ], + "count": 2 + }, + { + "components": [ + "python-fastapi", + "python-uvicorn" + ], + "count": 2 + }, + { + "components": [ + "python-dask", + "python-partd", + "python-reproject" + ], + "count": 2 + }, + { + "components": [ + "python-readme-renderer", + "python-twine" + ], + "count": 2 + }, + { + "components": [ + "python-google-api-core", + "python-google-cloud-core", + "python-proto-plus" + ], + "count": 2 + }, + { + "components": [ + "python-azure-mgmt-advisor", + "python-azure-mgmt-billing", + "python-azure-mgmt-datamigration", + "python-azure-mgmt-maps", + "python-azure-mgmt-marketplaceordering" + ], + "count": 1 + }, + { + "components": [ + "python-duecredit", + "python-fslpy", + "python-nifti-mrs", + "spec2nii" + ], + "count": 1 + }, + { + "components": [ + "materialx", + "usd" + ], + "count": 1 + }, + { + "components": [ + "fastapi-cloud-cli", + "python-fastapi", + "python-uvicorn", + "python-watchfiles" + ], + "count": 1 + }, + { + "components": [ + "python-beartype", + "python-spdx-tools", + "scancode-toolkit" + ], + "count": 1 + }, + { + "components": [ + "python-django-allauth", + "python-django-mailman3" + ], + "count": 1 + }, + { + "components": [ + "python-backrefs", + "python-mkdocs-git-revision-date-localized-plugin", + "python-mkdocs-rss-plugin" + ], + "count": 1 + }, + { + "components": [ + "python-django-allauth", + "python-django-mailman3", + "python-readme-renderer" + ], + "count": 1 + }, + { + "components": [ + "python-xarray", + "python-xarray-einstats" + ], + "count": 1 + } + ], + "dependency_loops": [] +} \ No newline at end of file diff --git a/config.toml b/config.toml index dc3cc480..9f66c0cb 100644 --- a/config.toml +++ b/config.toml @@ -4,36 +4,28 @@ ## build python3.N-1 together with python3.N without any bcond, verify python3.N-1 is nonmain python #- python3.N: # macros: -# _with_bootstrap: 1 # bump the release so it's greater even with ~bootstrap -# _without_rpmwheels: 1 -# _without_tests: 1 -# _without_optimizations: 1 +# %global _with_bootstrap 1 # bump the release so it's greater even with ~bootstrap #- gdb: # not blocking (yet) # macros: -# _without_python: 1 +# %global _without_python 1 #- python-flit-core: # macros: -# _with_bootstrap: 1 # bump the release so it's greater even with ~bootstrap +# %global _with_bootstrap 1 # bump the release so it's greater even with ~bootstrap #- python-packaging: # macros: -# _with_bootstrap: 1 # bump the release so it's greater even with ~bootstrap +# %global _with_bootstrap 1 # bump the release so it's greater even with ~bootstrap #- python-setuptools: # macros: -# _with_bootstrap: 1 # bump the release so it's greater even with ~bootstrap -#- python-wheel: -# macros: -# _with_bootstrap: 1 # bump the release so it's greater even with ~bootstrap +# %global _with_bootstrap 1 # bump the release so it's greater even with ~bootstrap #- python-pip: # macros: -# _without_tests: 1 -# _without_doc: 1 -#- python-setuptools: -# macros: -# _without_tests: 1 +# %global _without_tests 1 +# %global _without_man 1 #- pyparsing: # python BuildRequires systemtap-sdt-devel Requires pyparsing # macros: -# _without_tests: 1 -# _without_doc: 1 +# %global _without_tests 1 +# %global _without_doc 1 +# %global _without_extras 1 ## wait for gdb #- python3.N # VERIFY all of the above have correct Provides/Requires @@ -41,11 +33,12 @@ [koji] target = 'rawhide' +# target = 'f43-python' [distgit] branch = "rawhide" -commit_message = "Rebuilt for Python 3.12" -bootstrap_commit_message = "Bootstrap for Python 3.12" +commit_message = "Rebuilt for Python 3.15" +bootstrap_commit_message = "Bootstrap for Python 3.15" author = "Python Maint " [architectures] @@ -53,12 +46,12 @@ repoquery = "x86_64" # used for repository querying koji = "x86_64" # used to scratch build packages with bconds [deps] -old = ["python(abi) = 3.11", "libpython3.11.so.1.0()(64bit)", "libpython3.11d.so.1.0()(64bit)"] -new = ["python(abi) = 3.12", "libpython3.12.so.1.0()(64bit)", "libpython3.12d.so.1.0()(64bit)"] +old = ["python(abi) = 3.14", "libpython3.14.so.1.0()(64bit)", "libpython3.14d.so.1.0()(64bit)"] +new = ["python(abi) = 3.15", "libpython3.15.so.1.0()(64bit)", "libpython3.15d.so.1.0()(64bit)"] [components] -excluded = ["python3.11", "python3.12"] -extra = ["python3-docs"] +excluded = ["python3.14", "python3.15"] +extra = [] [cache_dir] dnf = "_dnf_cache_dir" @@ -68,34 +61,31 @@ fedpkg = "_fedpkg_cache_dir" [[repos.rawhide]] repoid = "rawhide" # metalink = "https://mirrors.fedoraproject.org/metalink?repo=rawhide&arch=$basearch" -baseurl = ["https://kojipkgs.fedoraproject.org/compose/rawhide/Fedora-Rawhide-20230705.n.0/compose/Everything/$basearch/os/"] -metadata_expire = 600000000 +baseurl = ["http://kojipkgs.fedoraproject.org/repos/rawhide/latest/$basearch/"] [[repos.rawhide]] repoid = "rawhide-source" # metalink = "https://mirrors.fedoraproject.org/metalink?repo=rawhide-source&arch=$basearch" -baseurl = ["https://kojipkgs.fedoraproject.org/compose/rawhide/Fedora-Rawhide-20230705.n.0/compose/Everything/source/tree/"] -metadata_expire = 600000000 +baseurl = ["http://kojipkgs.fedoraproject.org/repos/rawhide/latest/src/"] [[repos.target]] -repoid = "python3.12" -#baseurl = ["https://copr-be.cloud.fedoraproject.org/results/@python/python3.12/fedora-rawhide-$basearch/"] -baseurl = ["http://kojipkgs.fedoraproject.org/repos/f39-build/latest/$basearch/"] +repoid = "python3.15" +baseurl = ["https://copr-be.cloud.fedoraproject.org/results/@python/python3.15/fedora-rawhide-$basearch/"] +# baseurl = ["http://kojipkgs.fedoraproject.org/repos/f44-build/latest/$basearch/"] metadata_expire = 60 +[prerel] +current_version = "3.15.0~alpha8" + [bconds] [[bconds.python-setuptools]] withs = ["bootstrap"] -withouts = ["tests"] [[bconds.python-packaging]] withs = ["bootstrap"] -[[bconds.python-wheel]] -withs = ["bootstrap"] - [[bconds.python-pip]] -withouts = ["tests", "doc"] +withouts = ["tests", "man"] [[bconds.python-six]] withouts = ["tests"] @@ -110,15 +100,12 @@ withouts = ["tests"] withouts = ["docs", "tests"] [[bconds.python-chardet]] -withouts = ["doc_pdf"] +withouts = ["doc"] [[bconds.python-pbr]] withs = ["bootstrap"] withouts = ["tests"] -[[bconds.python-mock]] -withouts = ["tests"] - [[bconds.python-extras]] withs = ["bootstrap"] @@ -158,9 +145,6 @@ withouts = ["tests"] [[bconds.python-more-itertools]] withouts = ["tests"] -[[bconds.python-atomicwrites]] -withouts = ["docs", "tests"] - [[bconds.python-fixtures]] withs = ["bootstrap"] @@ -171,13 +155,13 @@ withouts = ["tests"] withouts = ["timeout", "tests", "docs"] [[bconds.python-virtualenv]] -withouts = ["tests"] +withs = ["bootstrap"] [[bconds.babel]] withs = ["bootstrap"] [[bconds.python-jinja2]] -withouts = ["docs"] +withouts = ["docs", "asyncio_tests"] [[bconds.python-sphinx_rtd_theme]] withs = ["bootstrap"] @@ -186,31 +170,13 @@ withs = ["bootstrap"] withs = ["bootstrap"] [[bconds.python-urllib3]] -withouts = ["tests"] +withouts = ["tests", "extradeps"] [[bconds.python-requests]] withouts = ["tests"] -[[bconds.python-sphinxcontrib-applehelp]] -withouts = ["check"] - -[[bconds.python-sphinxcontrib-devhelp]] -withouts = ["check"] - -[[bconds.python-sphinxcontrib-htmlhelp]] -withouts = ["check"] - -[[bconds.python-sphinxcontrib-jsmath]] -withouts = ["check"] - -[[bconds.python-sphinxcontrib-qthelp]] -withouts = ["check"] - -[[bconds.python-sphinxcontrib-serializinghtml]] -withouts = ["check"] - [[bconds.python-sphinx]] -withouts = ["tests", "websupport"] +withouts = ["tests", "sphinxcontrib"] [[bconds.python-jedi]] withouts = ["tests"] @@ -227,9 +193,6 @@ withouts = ["optional_tests"] [[bconds.python-soupsieve]] withouts = ["tests"] -[[bconds.python-towncrier]] -withouts = ["tests"] - [[bconds.python-pytest-asyncio]] withouts = ["tests"] @@ -242,17 +205,8 @@ withouts = ["tests"] [[bconds.python-async-timeout]] withouts = ["tests"] -[[bconds.python-trio]] -withouts = ["tests"] - -[[bconds.python-Automat]] -withouts = ["tests"] - -[[bconds.python-invoke]] -withouts = ["tests"] - [[bconds.python-jupyter-client]] -withouts = ["doc","tests"] +withouts = ["tests"] [[bconds.python-jupyter-server]] withouts = ["tests"] @@ -264,10 +218,10 @@ withouts = ["check"] withouts = ["check","doc"] [[bconds.python-ipykernel]] -withouts = ["tests","doc"] +withouts = ["tests"] [[bconds.pybind11]] -withouts = ["tests"] +withouts = ["optional_tests"] [[bconds.python-nbconvert]] withouts = ["check", "doc"] @@ -284,36 +238,21 @@ withouts = ["tests"] [[bconds.freeipa-healthcheck]] withouts = ["tests"] -[[bconds.python-zbase32]] -withs = ["bootstrap"] - [[bconds.python-Traits]] withs = ["bootstrap"] -[[bconds.python-lit]] -withouts = ["check"] - [[bconds.python-pcodedmp]] withs = ["bootstrap"] [[bconds.python-libcst]] -withouts = ["tests", "docs"] - -[[bconds.python-databases]] withs = ["bootstrap"] -[[bconds.python-molecule]] -withouts = ["doc"] - [[bconds.scipy]] withouts = ["pythran"] [[bconds.python-pandas]] withs = ["bootstrap"] -[[bconds.grpc]] -withs = ["bootstrap"] - [[bconds.python-fasteners]] withouts = ["tests"] @@ -322,7 +261,7 @@ withs = ["bootstrap"] withouts = ["docs"] [[bconds.python-zope-interface]] -withouts = ["tests", "docs"] +withouts = ["docs"] [[bconds.python-tqdm]] withouts = ["tests"] @@ -334,7 +273,7 @@ withouts = ["tests"] withouts = ["tests"] [[bconds.python-geopandas]] -withouts = ["tests"] +withs = ["bootstrap"] [[bconds.python-astropy]] withouts = ["check"] @@ -345,29 +284,22 @@ withouts = ["tests"] [[bconds.python-aiohttp]] withouts = ["tests"] -[[bconds.python-pep517]] -withouts = ["tests"] - [[bconds.python-azure-core]] withouts = ["tests"] -[[bconds.python-azure-common]] -withouts = ["tests"] - -[[bconds.python-azure-mgmt-core]] -withouts = ["tests"] +# this bcond is disabled in spec on purpose +# it might be enabled in the future, so leaving this here, commend out +#[[bconds.python-azure-common]] +#withouts = ["tests"] [[bconds.python-subprocess-tee]] withouts = ["tests"] [[bconds.python-typeguard]] -withouts = ["doc_pdf"] +withs = ["bootstrap"] [[bconds.python-tox]] -withouts = ["tests"] - -[[bconds.python-pytest-rerunfailures]] -withouts = ["tests"] +withs = ["bootstrap"] [[bconds.python-fsspec]] withs = ["bootstrap"] @@ -381,9 +313,6 @@ withs = ["bootstrap"] [[bconds.python-msrest]] withouts = ["tests"] -[[bconds.numpy]] -withouts = ["tests"] - [[bconds.python-oletools]] withs = ["bootstrap"] @@ -391,8 +320,17 @@ withs = ["bootstrap"] withouts = ["tests"] [[bconds.fonttools]] +withouts = ["tests", "plot_extra", "symfont_extra", "ufo_extra", "woff_extra", "graphite_extra", "interpolatable_extra"] + +[[bconds.conda]] +withs = ["bootstrap"] + +[[bconds.python-conda-libmamba-solver]] withouts = ["tests"] +[[bconds.python-conda-index]] +withs = ["bootstrap"] + [[bconds.python-conda-package-streaming]] withs = ["bootstrap"] @@ -405,15 +343,6 @@ withouts = ["tests"] [[bconds.python-dns]] withouts = ["trio", "curio", "doh"] -[[bconds.python-poetry-plugin-export]] -withouts = ["bootstrap"] - -[[bconds.poetry]] -withs = ["bootstrap"] - -[[bconds.python-networkx]] -withs = ["bootstrap"] - [[bconds.python-beautifulsoup4]] withouts = ["soupsieve", "tests"] @@ -421,10 +350,13 @@ withouts = ["soupsieve", "tests"] withouts = ["extras"] [[bconds.python-constantly]] -withouts = ["tests"] +withs = ["bootstrap"] -[[bconds.gdb]] -replacements = {_without_python = "1"} +# this is intentionally commented out +# 1) is is part of the initial bootstrap +# 2) replacement does not work, the macro isn't there (needs to be added) +#[[bconds.gdb]] +#replacements = {_without_python = "1"} [[bconds.python-pysocks]] replacements = {with_python3_tests = "0"} @@ -448,10 +380,250 @@ withouts = ["plugins"] withouts = ["tests"] [[bconds.python-tox-current-env]] -withouts = ["tests"] +withs = ["bootstrap"] [[bconds.python-pikepdf]] -withs = ["docs", "tests"] +withouts = ["docs", "tests"] + +[[bconds.python-executing]] +withs = ["bootstrap"] + +[[bconds.python-editables]] +withouts = ["doc"] + +[[bconds.python-jsonschema-specifications]] +replacements = {with_doc = "0"} + +[[bconds.python-openstackclient]] +replacements = {with_doc = "0"} + +[[bconds.python-oslo-config]] +replacements = {repo_bootstrap = "1"} + +[[bconds.python-neutronclient]] +replacements = {with_doc = "0"} + +[[bconds.python-glanceclient]] +replacements = {with_doc = "0"} + +[[bconds.python-domdf-python-tools]] +withouts = ["tests"] + +[[bconds.python-tiny-proxy]] +withouts = ["tests"] + +[[bconds.python-werkzeug]] +withouts = ["tests"] + +[[bconds.python-netaddr]] +withouts = ["docs"] + +[[bconds.python-dirty-equals]] +withs = ["bootstrap"] + +[[bconds.python-snakemake-interface-storage-plugins]] +withs = ["bootstrap"] + +[[bconds.python-snakemake-interface-executor-plugins]] +withs = ["bootstrap"] + +[[bconds.python-snakemake-interface-report-plugins]] +withs = ["bootstrap"] + +[[bconds.snakemake]] +withs = ["bootstrap"] + +[[bconds.python-sphinx-theme-builder]] +withs = ["bootstrap"] + +[[bconds.python-httpx]] +withouts = ["tests"] + +[[bconds.python-oslo-i18n]] +replacements = {with_doc = "0"} + +[[bconds.python-pymssql]] +withouts = ["tests"] + +[[bconds.python-authlib]] +withouts = ["tests"] + +[[bconds.python-oslotest]] +withs = ["repo_bootstrap"] + +[[bconds.python-gunicorn]] +withouts = ["extras"] + +[[bconds.python-execnet]] +withouts = ["optional_test_deps"] + +[[bconds.python-threadpoolctl]] +withouts = ["check"] + +[[bconds.python-tabulate]] +withs = ["bootstrap"] + +[[bconds.python-repoze-sphinx-autointerface]] +withouts = ["tests"] + +[[bconds.python-zope-exceptions]] +withouts = ["tests"] + +[[bconds.python-deepdiff]] +withouts = ["tests"] + +[[bconds.python-sentry-sdk]] +withouts = ["tests"] + +[[bconds.python-dask]] +withs = ["bootstrap"] + +[[bconds.python-build]] +withouts = ["extras"] + +[[bconds.meson]] +withouts = ["check"] + +[[bconds.python-gmpy2]] +withouts = ["tests"] + +[[bconds.python-humanfriendly]] +withs = ["bootstrap"] + +[[bconds.python-cascadio]] +withs = ["bootstrap"] + +[[bconds.python-pint]] +withouts = ["xarray"] + +[[bconds.python-qcengine]] +withouts = ["tests"] + +[[bconds.python-astropy-iers-data]] +withouts = ["tests"] + +[[bconds.python-pymongo]] +withs = ["bootstrap"] + +[[bconds.python-pyogrio]] +withs = ["bootstrap"] + +[[bconds.python-fastapi]] +withs = ["bootstrap"] + +[[bconds.python-pydantic-core]] +withouts = ["inline_snapshot_tests"] + +[[bconds.python-optking]] +withouts = ["tests"] + +[[bconds.python-django5]] +withouts = ["tests"] + +[[bconds.python-django6]] +withouts = ["tests"] + +[[bconds.python-pyproject-hooks]] +withouts = ["tests"] + +[[bconds.python-Automat]] +withouts = ["doc"] + +[[bconds.python-psutil]] +withouts = ["xdist"] + +[[bconds.python-apkinspector]] +withs = ["bootstrap"] + +[[bconds.python-androguard]] +withs = ["bootstrap"] + +[[bconds.python-service-identity]] +withouts = ["docs"] + +[[bconds.mkdocs-material]] +withs = ["bootstrap"] + +[[bconds.python-mkdocs-material-extensions]] +withouts = ["tests"] + +[[bconds.python-zope-testing]] +withouts = ["tests"] + +[[bconds.python-xnat]] +withouts = ["tests"] + +[[bconds.python-tablib]] +withs = ["bootstrap"] + +[[bconds.llvm]] +withouts = ["pgo"] + +[[bconds.llvm21 ]] +withouts = ["pgo"] + +[[bconds.python-pathspec]] +withouts = ["tests"] + +[[bconds.python-hatch-vcs]] +withs = ["bootstrap"] + +[[bconds.python-cobalt]] +withs = ["bootstrap"] + +[[bconds.vtk]] +withs = ["bootstrap"] + +[[bconds.python-trove-classifiers]] +withouts = ["tests"] + +[[bconds.python-snakemake-interface-common]] +withs = ["bootstrap"] + +[[bconds.python-snakemake-interface-scheduler-plugins]] +withs = ["bootstrap"] + +[[bconds.python-snakemake-interface-logger-plugins]] +withs = ["bootstrap"] + +[[bconds.python-azure-mgmt-core]] +withouts = ["tests"] + +[[bconds.python-networkx]] +withs = ["bootstrap"] + +[[bconds.python-questionary]] +withs = ["bootstrap"] + +[[bconds.python-paste-deploy]] +withouts = ["tests"] + +[[bconds.python-llm]] +withouts = ["tests"] + +[[bconds.python-rjsmin]] +withouts = ["docs"] + +[[bconds.python-webtest]] +withouts = ["tests"] + +[[bconds.python-menuinst]] +withs = ["bootstrap"] + +[[bconds.python-cliff]] +withs = ["bootstrap"] + +[[bconds.python-stevedore]] +withs = ["bootstrap"] + +[[bconds.python-vcs-versioning]] +withs = ["bootstrap"] + +[[bconds.python-scikit-build-core]] +withouts = ["optional_tests"] + +[[bconds.pyproj]] +withouts = ["xarray"] # [[bconds.OpenColorIO]] # %global bootstrap 1 diff --git a/gitrepo.py b/gitrepo.py new file mode 100644 index 00000000..6b7fe278 --- /dev/null +++ b/gitrepo.py @@ -0,0 +1,156 @@ +""" +This module contains helper functions to manipulate with distgit repositories: +clone them, refresh their local copies and patch the specfiles. +""" + +import re + +from utils import CONFIG, log, run + + +def clone_into(component_name, target, branch=''): + branch = branch or CONFIG['distgit']['branch'] + log(f' • Cloning {component_name} into "{target}"...', end=' ') + # I would like to use --depth=1 but that breaks rpmautospec + # https://pagure.io/fedora-infra/rpmautospec/issue/227 + run('fedpkg', 'clone', component_name, target, f'--branch={branch}') + log('done.') + + +def refresh_gitrepo(repopath, prune_existing=False): + log(f' • Refreshing "{repopath}" git repo...', end=' ') + git = 'git', '-C', repopath + head_before = run(*git, 'rev-parse', 'HEAD').stdout.rstrip() + run(*git, 'stash') + run(*git, 'reset', '--hard') + run(*git, 'pull') + head_after = run(*git, 'rev-parse', 'HEAD').stdout.rstrip() + if head_before == head_after: + if not prune_existing: + # we try to preserve the changes for local inspection, but if it fails, meh + run(*git, 'stash', 'pop', check=False) + log('already up to date.') + return False + else: + log(f'updated {head_before[:10]}..{head_after[:10]}.') + return True + + +def validate_bcond_config(bcond_config): + """ + Validate that bcond configuration doesn't have conflicting with/without. + + Args: + bcond_config: Configuration dict with 'withs' and 'withouts' keys + + Raises: + ValueError: If the same bcond appears in both 'withs' and 'withouts' + """ + withs = set(bcond_config.get('withs', ())) + withouts = set(bcond_config.get('withouts', ())) + overlap = withs & withouts + + if overlap: + raise ValueError(f'Cannot have the same with and without: {", ".join(sorted(overlap))}') + + +def generate_bcond_lines(bcond_config): + """ + Generate RPM macro lines for bcond configuration. + + Args: + bcond_config: Configuration dict with 'withs' and 'withouts' keys + + Returns: + List of strings containing RPM macro definitions to prepend to spec + """ + lines = [] + + for without in sorted(bcond_config.get('withouts', ())): + lines.append(f'%global _without_{without} 1') + + for with_ in sorted(bcond_config.get('withs', ())): + lines.append(f'%global _with_{with_} 1') + + return lines + + +def apply_macro_replacements(spec_text, replacements): + """ + Apply macro replacements to spec file text. + + Finds lines like '%define macro value' or '%global macro value' and + replaces the value with the one specified in replacements. + + Args: + spec_text: Original spec file text + replacements: Dict mapping macro names to new values + + Returns: + Modified spec file text with replacements applied + """ + for macro, value in replacements.items(): + # Escape the macro name in case it contains special regex characters + escaped_macro = re.escape(macro) + spec_text = re.sub( + fr'^(\s*)%(define|global)(\s+){escaped_macro}(\s+)\S.*$', + fr'\1%\2\g<3>{macro}\g<4>{value}', + spec_text, + flags=re.MULTILINE + ) + + return spec_text + + +def patch_spec(specpath, bcond_config): + """ + Patch a spec file with bcond configuration and macro replacements. + + This function: + 1. Resets any uncommitted changes in the repository + 2. Validates bcond configuration + 3. Generates bcond macro definitions to prepend + 4. Applies macro replacements in the spec file + 5. Writes the modified spec file + + Args: + specpath: Path to the spec file to patch + bcond_config: Configuration dict with: + - 'withs': list of bconds to enable (optional) + - 'withouts': list of bconds to disable (optional) + - 'replacements': dict of macro names to values (optional) + + Raises: + ValueError: If the same bcond appears in both withs and withouts + """ + log(f' • Patching {specpath.name}') + + run('git', '-C', specpath.parent, 'reset', '--hard') + + validate_bcond_config(bcond_config) + + spec_text = specpath.read_text() + spec_text = apply_macro_replacements(spec_text, bcond_config.get('replacements', {})) + + bcond_lines = generate_bcond_lines(bcond_config) + bcond_lines.append(spec_text) + + specpath.write_text('\n'.join(bcond_lines)) + + +def refresh_or_clone(repopath, component_name, *, prune_existing=False, no_git_refresh=False, branch=''): + """ + Returns True if there's new contents of the repository. + Returns False if the content of the repository remains the same + or if no_git_refresh option is set to True + (we skip the repository update and assume nothing has changed). + """ + if repopath.exists(): + if no_git_refresh: + return False + else: + return refresh_gitrepo(repopath, prune_existing=prune_existing) + else: + repopath.parent.mkdir(exist_ok=True) + clone_into(component_name, repopath, branch=branch) + return True diff --git a/jobs.py b/jobs.py index 2782d5a8..a5b5a4ef 100644 --- a/jobs.py +++ b/jobs.py @@ -1,5 +1,8 @@ import collections +from dataclasses import dataclass, field import functools +import json +import os import sys from sacks import MULTILIB, rawhide_sack, target_sack @@ -36,25 +39,49 @@ def all_values(self): return {value for lst in self.values() for value in lst} -@functools.lru_cache(maxsize=1) -def packages_to_rebuild(old_deps, *, excluded_components=()): +@dataclass +class RebuildContext: """ - Given a hashable collection of string-dependencies that are "old", - queries rawhide for all binary packages that require those - and returns them in a dict: - - keys: SRPM-names - - values: lists of hawkey.Packages + Context object holding all shared data and state for rebuild analysis. + + Attributes: + components: All components that need rebuilding (name -> list of packages) + components_done: Components that have been rebuilt (name -> list of packages) + binary_rpms: Set of all binary RPM packages to rebuild + blocker_counter: Statistics about blocking components + loop_detector: Map of components to their blocking components + missing_packages: Map of components to their missing package names + unresolvable_components: Set of components whose dependencies cannot be resolved + prerel_abi_blocked_components: Set of components blocked by unsatisfied prerel-abi dependencies + """ + components: ReverseLookupDict + components_done: ReverseLookupDict + binary_rpms: set + blocker_counter: dict = field(default_factory=lambda: { + 'general': collections.Counter(), + 'single': collections.Counter(), + 'combinations': collections.Counter(), + }) + loop_detector: dict = field(default_factory=dict) + missing_packages: dict = field(default_factory=lambda: collections.defaultdict(set)) + unresolvable_components: set = field(default_factory=set) + prerel_abi_blocked_components: set = field(default_factory=set) - Excluded_components is an optional hashable collection of component names - to exclude from the results. - If rawhide does not contain our newly rebuilt packages (which is expected here), - the dict will also contain packages that already successfully rebuilt - (in our side tag or copr, etc.). +def _query_packages_by_deps(sack_getter, deps, excluded_components): """ - sack = rawhide_sack() - log('• Querying all packages to rebuild...', end=' ') - results = sack.query().filter(requires=old_deps, arch__neq='src', latest=1) + Common logic for querying packages by dependencies. + + Args: + sack_getter: Function that returns a DNF sack (e.g., rawhide_sack or target_sack) + deps: Hashable collection of string-dependencies to query + excluded_components: Hashable collection of component names to exclude + + Returns: + ReverseLookupDict with component names as keys and lists of hawkey.Packages as values + """ + sack = sack_getter() + results = sack.query().filter(requires=deps, arch__neq='src', latest=1) if CONFIG['architectures']['repoquery'] in MULTILIB: results = results.filter(arch__neq=MULTILIB[CONFIG['architectures']['repoquery']]) components = ReverseLookupDict() @@ -70,6 +97,30 @@ def packages_to_rebuild(old_deps, *, excluded_components=()): return components +@functools.lru_cache(maxsize=1) +def packages_to_rebuild(old_deps, *, excluded_components=()): + """ + Given a hashable collection of string-dependencies that are "old", + queries rawhide for all binary packages that require those + and returns them in a dict: + - keys: SRPM-names + - values: lists of hawkey.Packages + + Excluded_components is an optional hashable collection of component names + to exclude from the results. + + If rawhide does not contain our newly rebuilt packages (which is expected here), + the dict will also contain packages that already successfully rebuilt + (in our side tag or copr, etc.). + """ + log('• Querying all packages to rebuild...', end=' ') + return _query_packages_by_deps( + rawhide_sack, + old_deps, + excluded_components, + ) + + @functools.lru_cache(maxsize=1) def packages_built(new_deps, *, excluded_components=()): """ @@ -82,71 +133,132 @@ def packages_built(new_deps, *, excluded_components=()): Excluded_components is an optional hashable collection of component names to exclude from the results. """ - sack = target_sack() log('• Querying all successfully rebuilt packages...', end=' ') - results = sack.query().filter(requires=new_deps, arch__neq='src', latest=1) - if CONFIG['architectures']['repoquery'] in MULTILIB: - results = results.filter(arch__neq=MULTILIB[CONFIG['architectures']['repoquery']]) - components = ReverseLookupDict() - anticount = 0 - for result in results: - if result.source_name not in excluded_components: - components[result.source_name].append(result) - else: - anticount += 1 - # no longer create lists on access to avoid mistakes: - components.default_factory = None - log(f'found {len(components)} components ({len(results)-anticount} binary packages).') - return components + return _query_packages_by_deps( + target_sack, + new_deps, + excluded_components, + ) -def are_all_done(*, packages_to_check, all_components, components_done, blocker_counter, loop_detector): +def _group_packages_by_component(packages_to_check, all_components): """ - Given a collection of (binary) packages_to_check, and dicts of all_components and components_done, - returns True if ALL packages_to_check are considered "done" (i.e. installable). + Group packages by their source component. + + Args: + packages_to_check: Collection of binary packages to check + all_components: ReverseLookupDict mapping components to packages + + Returns: + ReverseLookupDict with component names as keys and lists of packages as values """ relevant_components = ReverseLookupDict() for pkg in packages_to_check: relevant_components[all_components.key(pkg)].append(pkg) relevant_components.default_factory = None + return relevant_components + + +def _is_package_available(required_package, done_packages): + """ + Check if a required package is available in the list of done packages. + + The done packages are from a different repo and might have different EVR. + We compare by name or by virtual provides. + + Args: + required_package: The package we need + done_packages: List of packages that have been built + + Returns: + Tuple of (is_available, has_older_version) + """ + has_older = False + for done_package in done_packages: + # The done packages are from different repo and might have different EVR + # Hence, we only compare the names + # For Copr rebuilds, the Copr EVR must be >= Fedora EVR + # For koji rebuilds, this will be always true anyway + if done_package.name == required_package.name: + # if not done_package.evr_lt(required_package): + if True: + return True, False + else: + has_older = True + else: + # Check the virtual provides - maybe one of them matches what we look for + for provide in done_package.provides: + if provide.name == required_package.name: + return True, False + + return False, has_older + + +def _update_blocker_statistics(blocking_components, blocker_counter, loop_detector, component): + """ + Update blocker statistics based on which components are blocking. + + Args: + blocking_components: Set of component names that are blocking + blocker_counter: Dict with 'general', 'single', and 'combinations' counters + loop_detector: Dict mapping components to their blocking components + component: The component being checked + """ + if len(blocking_components) == 1: + blocker_counter['single'][next(iter(blocking_components))] += 1 + elif 1 < len(blocking_components) < 10: # this is an arbitrarily chosen number to avoid cruft + blocker_counter['combinations'][tuple(sorted(blocking_components))] += 1 + loop_detector[component] = sorted(blocking_components) + + +def are_all_done(component, packages_to_check, ctx): + """ + Given a component name and a collection of (binary) packages_to_check, + check if ALL packages are considered "done" (i.e. installable). + + Args: + component: Name of the component being checked + packages_to_check: Collection of binary packages to verify + ctx: RebuildContext with shared data and state + + Returns: + bool: True if ALL packages_to_check are considered "done" (i.e. installable). + """ + relevant_components = _group_packages_by_component(packages_to_check, ctx.components) log(f' • {component}: {len(packages_to_check)} packages / {len(relevant_components)} ' f'components relevant to our problem') + all_available = True blocking_components = set() + for relevant_component, required_packages in relevant_components.items(): log(f' • {relevant_component}') - count_component = False + component_is_blocking = False + for required_package in required_packages: - has_older = False - for done_package in components_done.get(relevant_component, ()): - # The done packages are from different repo and might have different EVR - # Hence, we only compare the names - # For Copr rebuilds, the Copr EVR must be >= Fedora EVR - # For koji rebuilds, this will be always true anyway - # XXX cython was renamed after the rebuild - if done_package.name == required_package.name or required_package.name == 'python3-Cython': - #if not done_package.evr_lt(required_package): - if True: - log(f' ✔ {required_package.name}') - break - else: - has_older = True + is_available, has_older = _is_package_available( + required_package, + ctx.components_done.get(relevant_component, ()) + ) + + if is_available: + log(f' ✔ {required_package.name}') else: if has_older: log(f' ✗ {required_package.name} (older EVR available)') else: log(f' ✗ {required_package.name}') + + ctx.missing_packages[component].add(relevant_component) all_available = False - count_component = True - if count_component: - blocker_counter['general'][relevant_component] += 1 + component_is_blocking = True + + if component_is_blocking: + ctx.blocker_counter['general'][relevant_component] += 1 blocking_components.add(relevant_component) - if len(blocking_components) == 1: - blocker_counter['single'][blocking_components.pop()] += 1 - elif 1 < len(blocking_components) < 10: # this is an arbitrarily chosen number to avoid cruft - blocker_counter['combinations'][tuple(sorted(blocking_components))] += 1 - loop_detector[component] = sorted(blocking_components) + + _update_blocker_statistics(blocking_components, ctx.blocker_counter, ctx.loop_detector, component) return all_available @@ -157,6 +269,7 @@ def _sort_loop(loop): def _detect_loop(loop_detector, probed_component, depchain, loops, seen): for component in loop_detector[probed_component]: + recursedown = component not in seen seen.add(component) if component in CONFIG['bconds']: # we assume bconds are manually crafted not to have loops @@ -166,7 +279,8 @@ def _detect_loop(loop_detector, probed_component, depchain, loops, seen): if component in depchain: loops.add(_sort_loop(depchain[depchain.index(component):])) continue - _detect_loop(loop_detector, component, depchain + [component], loops, seen) + if recursedown: + _detect_loop(loop_detector, component, depchain + [component], loops, seen) def report_blocking_components(loop_detector): loops = set() @@ -174,85 +288,359 @@ def report_blocking_components(loop_detector): for component in loop_detector: if component not in seen: _detect_loop(loop_detector, component, [component], loops, seen) + seen.add(component) log('\nDetected dependency loops:') for loop in sorted(loops, key=lambda t: -len(t)): log(' • ' + ' → '.join(loop)) + return loops + + +def get_component_status_info(component, missing_packages, components, unresolvable_components, prerel_abi_blocked_components=None): + """Generate status information for a component explaining why it's blocked.""" + if prerel_abi_blocked_components and component in prerel_abi_blocked_components: + return " (unsatisfied prerel-abi dependency)" + elif component in unresolvable_components: + return " (can't resolve dependencies)" + elif component in components: + if component in missing_packages: + missing_deps = missing_packages[component] + if missing_deps: + missing_deps_str = ", ".join(sorted(missing_deps)[:3]) + if len(missing_deps) > 3: + missing_deps_str += "..." + return f" (blocked by: {missing_deps_str})" + else: + return " (blocked for unknown reason)" # This should never happen, but keep it for debugging + else: + return " (ready)" # This should never happen, but keep it for debugging + else: + return f" (build failed)" -if __name__ == '__main__': - # this is spaghetti code that will be split into functions later: - from resolve_buildroot import resolve_buildrequires_of, resolve_requires - from bconds import bcond_cache_identifier, extract_buildrequires_if_possible - components = packages_to_rebuild(tuple(CONFIG['deps']['old']), excluded_components=tuple(CONFIG['components']['excluded'])) +def _filter_components_with_unsatisfied_prerel_abi(components_done): + """ + Filter out components that have unsatisfied python(prerel-abi) dependencies. + + Some packages require python(prerel-abi) = X.Y.Z~alphaN which should match + the current_version defined in config.toml [prerel] section. + + Args: + components_done: ReverseLookupDict of components that have been built + + Returns: + Tuple of (filtered_components, blocked_components) where: + - filtered_components: ReverseLookupDict with components that have matching prerel-abi removed + - blocked_components: set of component names that were filtered (should not be rebuilt) + """ + import re + + # Get the expected prerel-abi version from config + expected_version = CONFIG.get('prerel', {}).get('current_version') + if not expected_version: + log(' • Warning: No prerel.current_version found in config.toml, skipping prerel-abi filtering') + return components_done, set() + + filtered = ReverseLookupDict() + blocked_components = set() + + for component, packages in components_done.items(): + component_has_unsatisfied_prerel = False + + for pkg in packages: + for req in pkg.requires: + req_str = str(req) + if 'python(prerel-abi)' in req_str: + # Extract version from requirement string + # Format: python(prerel-abi) = X.Y.Z~alphaN + match = re.search(r'python\(prerel-abi\)\s*=\s*([^\s]+)', req_str) + if match: + required_version = match.group(1) + if required_version != expected_version: + log(f' • Filtering {component}: required {req_str}, expected python(prerel-abi) = {expected_version}') + component_has_unsatisfied_prerel = True + blocked_components.add(component) + break + else: + log(f' • Warning: Could not parse prerel-abi requirement: {req_str}') + + if component_has_unsatisfied_prerel: + break + + # Only include this component if it doesn't have unsatisfied prerel-abi deps + if not component_has_unsatisfied_prerel: + filtered[component] = packages + + filtered.default_factory = None + + if blocked_components: + log(f'• Filtered out {len(blocked_components)} components with unsatisfied prerel-abi dependencies.') + + return filtered, blocked_components + + +def initialize_component_data(): + """ + Load and prepare all component data needed for rebuild analysis. + + Returns: + RebuildContext containing all component data and state + """ + components = packages_to_rebuild( + tuple(CONFIG['deps']['old']), + excluded_components=tuple(CONFIG['components']['excluded']) + ) for component in CONFIG['components']['extra']: components[component] = [] - components_done = packages_built(tuple(CONFIG['deps']['new']), excluded_components=tuple(CONFIG['components']['excluded'])) + + components_done = packages_built( + tuple(CONFIG['deps']['new']), + excluded_components=tuple(CONFIG['components']['excluded']) + ) + + # Filter out components with unsatisfied prerel-abi dependencies + components_done, prerel_abi_blocked = _filter_components_with_unsatisfied_prerel_abi(components_done) + binary_rpms = components.all_values() + + return RebuildContext( + components=components, + components_done=components_done, + binary_rpms=binary_rpms, + prerel_abi_blocked_components=prerel_abi_blocked + ) - blocker_counter = { - 'general': collections.Counter(), - 'single': collections.Counter(), - 'combinations': collections.Counter(), - } - loop_detector = {} - for component in components: - if len(sys.argv) > 1 and component not in sys.argv[1:]: - continue +def check_regular_build(component, ctx): + """ + Check if a component's regular (non-bconded) build is ready. + + Args: + component: Name of the component to check + ctx: RebuildContext with shared data and state + + Returns: + Tuple of (ready_to_rebuild, number_of_resolved) + - ready_to_rebuild: bool indicating if component can be rebuilt + - number_of_resolved: int count of resolved packages, or None if resolution failed + """ + from resolve_buildroot import resolve_buildrequires_of + + try: + component_buildroot = resolve_buildrequires_of(component) + except ValueError as e: + log(f'\n ✗ {e}') + ctx.unresolvable_components.add(component) + return False, None + + number_of_resolved = len(component_buildroot) + ready_to_rebuild = are_all_done( + component, + set(component_buildroot) & ctx.binary_rpms, + ctx + ) + + return ready_to_rebuild, number_of_resolved + + +def check_bcond_build(component, bcond_config, number_of_resolved, ctx): + """ + Check if a component's bcond build is ready. + + Args: + component: Name of the component to check + bcond_config: Bcond configuration dictionary + number_of_resolved: Number of packages in regular build (for comparison) + ctx: RebuildContext with shared data and state + + Returns: + bool indicating if the bcond build can be rebuilt + """ + from resolve_buildroot import resolve_requires + from bconds import extract_buildrequires_if_possible + + if 'buildrequires' not in bcond_config: + extract_buildrequires_if_possible(bcond_config) + + if 'buildrequires' not in bcond_config: + log(f' • {bcond_config["id"]} bcond SRPM not present yet, skipping') + return False + + try: + component_buildroot = resolve_requires(tuple(sorted(bcond_config['buildrequires']))) + except ValueError as e: + log(f'\n ✗ {e}') + ctx.unresolvable_components.add(component) + return False + + if number_of_resolved == len(component_buildroot): + # XXX when this happens, the bcond might be bogus + # figure out a way to present that information + pass + + ready_to_rebuild = are_all_done( + component, + set(component_buildroot) & ctx.binary_rpms, + ctx + ) + + return ready_to_rebuild + + +def check_bcond_builds(component, number_of_resolved, ctx): + """ + Check all bcond builds for a component that isn't ready for regular build. + + Args: + component: Name of the component to check + number_of_resolved: Number of packages in regular build (for comparison) + ctx: RebuildContext with shared data and state + + Prints any bcond build identifiers that are ready to rebuild. + """ + from bconds import bcond_cache_identifier + + if component not in CONFIG['bconds']: + return + + for bcond_config in CONFIG['bconds'][component]: + bcond_config['id'] = bcond_cache_identifier(component, bcond_config) + log(f'• {component} not ready and {bcond_config["id"]} bcond found, will check that one') + + ready_to_rebuild = check_bcond_build(component, bcond_config, number_of_resolved, ctx) + + if ready_to_rebuild: + if should_print_component(component, ctx.components_done): + print(bcond_config['id']) - try: - component_buildroot = resolve_buildrequires_of(component) - except ValueError as e: - log(f'\n ✗ {e}') - continue - ready_to_rebuild = are_all_done( - packages_to_check=set(component_buildroot) & binary_rpms, - all_components=components, - components_done=components_done, - blocker_counter=blocker_counter, - loop_detector=loop_detector, - ) +def should_print_component(component, components_done): + """ + Determine if a component should be printed based on PRINT_ALL env var. + + Returns: + bool indicating if component should be printed + """ + return os.environ.get('PRINT_ALL') or component not in components_done - if ready_to_rebuild: - # XXX make this configurable - if component not in components_done: - print(component) - elif component in CONFIG['bconds']: - for bcond_config in CONFIG['bconds'][component]: - bcond_config['id'] = bcond_cache_identifier(component, bcond_config) - log(f'• {component} not ready and {bcond_config["id"]} bcond found, will check that one') - if 'buildrequires' not in bcond_config: - extract_buildrequires_if_possible(component, bcond_config) - if 'buildrequires' in bcond_config: - try: - component_buildroot = resolve_requires(tuple(sorted(bcond_config['buildrequires']))) - except ValueError as e: - log(f'\n ✗ {e}') - continue - ready_to_rebuild = are_all_done( - packages_to_check=set(component_buildroot) & binary_rpms, - all_components=components, - components_done=components_done, - blocker_counter=blocker_counter, - loop_detector=loop_detector, - ) - if ready_to_rebuild: - if component not in components_done: - print(bcond_config['id']) - else: - log(f' • {bcond_config["id"]} bcond SRPM not present yet, skipping') + +def process_component(component, ctx): + """ + Process a single component: check regular build and bcond builds if needed. + + Args: + component: Name of the component to process + ctx: RebuildContext with shared data and state + + Prints the component or bcond identifier if ready to rebuild. + """ + ready_to_rebuild, number_of_resolved = check_regular_build(component, ctx) + + if ready_to_rebuild: + if should_print_component(component, ctx.components_done): + print(component) + else: + check_bcond_builds(component, number_of_resolved, ctx) + + +def assemble_component_info(component, count, ctx): + entry = { + 'component': component, + 'count': count, + } + if component in ctx.unresolvable_components: + entry['unresolvable'] = True + if component in ctx.prerel_abi_blocked_components: + entry['prerel_abi_blocked'] = True + if component in ctx.missing_packages: + entry['blocked_by'] = sorted(ctx.missing_packages[component]) + return entry + + +def generate_reports(ctx): + """ + Generate and print all summary reports. + Also saves the report data to commonly-needed-report.json. + + Args: + ctx: RebuildContext with statistics and component data + """ + report_data = { + 'most_commonly_needed': [], + 'most_commonly_last_blocking': [], + 'most_commonly_last_blocking_combinations': [], + 'dependency_loops': [] + } log('\nThe 50 most commonly needed components are:') - for component, count in blocker_counter['general'].most_common(50): - log(f'{count:>5} {component}') + for component, count in ctx.blocker_counter['general'].most_common(50): + status_info = get_component_status_info( + component, ctx.missing_packages, ctx.components, + ctx.unresolvable_components, ctx.prerel_abi_blocked_components + ) + log(f'{count:>5} {component:<35} {status_info}') + + entry = assemble_component_info(component, count, ctx) + report_data['most_commonly_needed'].append(entry) log('\nThe 20 most commonly last-blocking components are:') - for component, count in blocker_counter['single'].most_common(20): - log(f'{count:>5} {component}') + for component, count in ctx.blocker_counter['single'].most_common(20): + status_info = get_component_status_info( + component, ctx.missing_packages, ctx.components, + ctx.unresolvable_components, ctx.prerel_abi_blocked_components + ) + log(f'{count:>5} {component:<35} {status_info}') + + entry = assemble_component_info(component, count, ctx) + report_data['most_commonly_last_blocking'].append(entry) log('\nThe 20 most commonly last-blocking small combinations of components are:') - for components, count in blocker_counter['combinations'].most_common(20): - log(f'{count:>5} {", ".join(components)}') + for components_tuple, count in ctx.blocker_counter['combinations'].most_common(20): + log(f'{count:>5} {", ".join(components_tuple)}') + + report_data['most_commonly_last_blocking_combinations'].append({ + 'components': list(components_tuple), + 'count': count + }) + + loops = report_blocking_components(ctx.loop_detector) + report_data['dependency_loops'] = [list(loop) for loop in loops] - report_blocking_components(loop_detector) + with open('commonly-needed-report.json', 'w') as f: + json.dump(report_data, f, indent=2) + log('\nReport saved to commonly-needed-report.json') + + +def main(): + """ + Main entry point for rebuild analysis. + + Analyzes which components are ready to rebuild based on dependency resolution. + Prints ready components to stdout and detailed reports to stderr. + """ + # Load cached bcond data by default unless --no-cache flag provided + if '--no-cache' not in sys.argv: + from bconds import read_bconds_cache_if_exists + read_bconds_cache_if_exists() + else: + # Remove flag from argv so component filtering still works + sys.argv = [arg for arg in sys.argv if arg != '--no-cache'] + + ctx = initialize_component_data() + + # Filter components based on command line arguments + components_to_process = ctx.components + if len(sys.argv) > 1: + components_to_process = { + comp: ctx.components[comp] + for comp in ctx.components + if comp in sys.argv[1:] + } + + for component in components_to_process: + process_component(component, ctx) + + generate_reports(ctx) + + +if __name__ == '__main__': + main() diff --git a/progress.pkgs b/progress.pkgs index a4359833..62f91aa8 100644 --- a/progress.pkgs +++ b/progress.pkgs @@ -1,107 +1,100 @@ -andriller -androguard -androwarn -awscli -brd -btest -condor -cvc4 -swid-tools +alot +beets +bodhi-server eric -fail2ban -fedora-gather-easyfix -imgbased -python-mathics3 -pico-wizard -pyflowtools -python-acora -python-adext -python-aioambient -python-aioguardian -python-aiozeroconf -python-airthings -python-alarmdecoder -python-ansi -python-ansible-pygments -python-async-generator -awake -python-blockdiag -python-bluepy -python-box -python-certbot-dns-cloudxns -python-click-spinner -python-cypari2 -python-cypy +kicad +novelwriter +osc +pagure-exporter +pyp2rpm +python-streamlink +python-accelerate +python-alembic1.14 +python-ansible-runner +python-astroquery +python-azure-loganalytics +python-azure-mgmt-advisor +python-azure-mgmt-billing +python-azure-mgmt-consumption +python-azure-mgmt-databoxedge +python-azure-mgmt-datamigration +python-azure-mgmt-devtestlabs +python-azure-mgmt-dns +python-azure-mgmt-managedservices +python-azure-mgmt-maps +python-azure-mgmt-marketplaceordering +python-backrefs +python-beartype +bmap-tools +bout++ +python-bytecode +python-cartopy +python-chai +llvm21 +subscription-manager dionaea -python-django-contact-form -python-django-robots -python-django3 -python-dominate -python-editdistance-s -python-flask-bootstrap -python-fpylll -python-gccinvocation -python-geomet -python-grako -python-graphitesend -python-igor -python-ipgetter -python-jep -python-jsonrpc-server -python-lacrosse -python-lark-parser -python-lasagne -python-lazr-smtptest -lensfun -python-leveldb -python-liblarch -libssh2-python -python-logging-tree -python-logutils -python-mpd2 -mraa -python-networkmanager -python-nipy -python-nose_fixes -python-notario +root +python-django-allauth +python-einops +espresso +python-eth-event +python-eth-keys +python-exiv2 +python-giacpy +python-glanceclient +python-graph-tool +python-inquirer +jc +libCombine +libkml +python-lsp-server +python-markdown-callouts +materialx +Mayavi +python-metakernel +python-mirrors-countme +moose +morphio +python-mplcairo +python-neo +netplan +python-niaaml +python-numpoly openshadinglanguage -python-optcomplete -python-podman-api -python-pvc -python-py9p -python-pyaes -python-pybv -python-pydiffx -pyftpdlib -libgpuarray -python-pymc3 -python-pyngus -python-pyoptical -python-pyside2 -python-pytelegrambotapi -python-pytest-flake8 -python-pytest-metadata -python-pytest-virtualenv -python3-script -python-simpleparse -python-simplewrap -python-sklearn-genetic-opt -python-slip -python-smart-gardena -python-smbpasswd -python-sphinxcontrib-actdiag -python-stdio-mgr -python-streamlink -sword -python-tambo -python-test_server -python-upoints -python-uri-templates -python-uvicorn -python-vcstools -vertica-python -python-visionegg-quest -python-yamlordereddictloader -python-yourls -transmageddon -python3-docs +python-opytimizer +python-partd +python-patsy +petsc +python-pygls +python-pygtkspellcheck +python-jnius +python-pynwb +python-pyvmomi +python-qcustomplot-pyqt +qpid-proton +pyqtwebengine +python-qudida +python-rasterio +python-readme-renderer +python-rstcheck +python-rstr +python-saharaclient +python-SALib +python-simple-salesforce +python-sphinx-book-theme +python-steps +sundials +python-superqt +python-symengine +python-textual +python-toml-cli +python-translitcodec +python-treq +python-uranium +python-watchfiles +weasyprint +xapian-bindings +xcfun +rebase-helper +recoll +renderdoc diff --git a/resolve_buildroot.py b/resolve_buildroot.py index dc817d55..755d426b 100644 --- a/resolve_buildroot.py +++ b/resolve_buildroot.py @@ -9,8 +9,8 @@ # Some deps are only pulled in when those are installed: DEFAULT_GROUPS = ( - 'buildsys-build', # for composed repo - #'build', # for koji repo + # 'buildsys-build', # for composed repo + 'build', # for koji repo ) diff --git a/sacks.py b/sacks.py index 7f3c281c..06c03482 100644 --- a/sacks.py +++ b/sacks.py @@ -5,6 +5,7 @@ from utils import CONFIG, log MULTILIB = {'x86_64': 'i686'} # architectures to exclude in certain queries +ARCH = CONFIG['architectures']['repoquery'] @functools.cache def _base(repo_key): @@ -15,10 +16,10 @@ def _base(repo_key): """ base = dnf.Base() dnf_conf = base.conf - dnf_conf.arch = CONFIG['architectures']['repoquery'] + dnf_conf.arch = ARCH dnf_conf.cachedir = CONFIG['cache_dir']['dnf'] dnf_conf.substitutions['releasever'] = 'rawhide' - dnf_conf.substitutions['basearch'] = CONFIG['architectures']['repoquery'] + dnf_conf.substitutions['basearch'] = ARCH for repo in CONFIG['repos'][repo_key]: base.repos.add_new_repo(conf=dnf_conf, skip_if_unavailable=False, **repo) log(f'• Filling the DNF {repo_key} sack to/from {CONFIG["cache_dir"]["dnf"]}...', end=' ') diff --git a/tests/test_bconds.py b/tests/test_bconds.py new file mode 100644 index 00000000..25a038d9 --- /dev/null +++ b/tests/test_bconds.py @@ -0,0 +1,147 @@ +"""Tests for bconds module.""" + +import datetime +from unittest.mock import patch + +from bconds import bcond_cache_identifier, koji_id_is_older_than_week + + +class TestBcondCacheIdentifier: + """Tests for bcond_cache_identifier function.""" + + def test_simple_component_no_bconds(self): + """Should generate identifier for component with no bconds.""" + identifier = bcond_cache_identifier('mypackage', {}) + assert identifier.startswith('mypackage:') + assert identifier == 'mypackage:::::' + + def test_with_withouts_only(self): + """Should include withouts in identifier.""" + config = {'withouts': ['tests', 'docs']} + identifier = bcond_cache_identifier('pkg', config) + assert identifier == 'pkg:docs-tests::::' + + def test_with_withs_only(self): + """Should include withs in identifier.""" + config = {'withs': ['bootstrap', 'feature']} + identifier = bcond_cache_identifier('pkg', config) + assert identifier == 'pkg::bootstrap-feature:::' + + def test_with_replacements_only(self): + """Should include replacement macro names in identifier.""" + config = {'replacements': {'macro1': 'value1', 'macro2': 'value2'}} + identifier = bcond_cache_identifier('pkg', config) + assert identifier == 'pkg:::macro1-macro2::' + + def test_complete_config(self): + """Should include all parts in identifier.""" + config = { + 'withouts': ['tests', 'docs'], + 'withs': ['bootstrap'], + 'replacements': {'debug': '0'} + } + identifier = bcond_cache_identifier('pkg', config) + assert identifier == 'pkg:docs-tests:bootstrap:debug::' + + def test_canonical_ordering(self): + """Should generate same identifier regardless of input order.""" + config1 = { + 'withouts': ['tests', 'docs', 'man'], + 'withs': ['feature', 'bootstrap'] + } + config2 = { + 'withouts': ['man', 'docs', 'tests'], + 'withs': ['bootstrap', 'feature'] + } + + id1 = bcond_cache_identifier('pkg', config1) + id2 = bcond_cache_identifier('pkg', config2) + assert id1 == id2 + assert id1 == 'pkg:docs-man-tests:bootstrap-feature:::' + + def test_with_branch(self): + """Should include non-default branch in identifier.""" + config = {'withs': ['bootstrap']} + identifier = bcond_cache_identifier('pkg', config, branch='f38') + assert identifier == 'pkg::bootstrap::f38:' + + def test_with_target(self): + """Should include target in identifier.""" + config = {'withs': ['bootstrap']} + identifier = bcond_cache_identifier('pkg', config, target='f38-python') + assert identifier == 'pkg::bootstrap:::f38-python' + + @patch('bconds.CONFIG', {'distgit': {'branch': 'rawhide'}}) + def test_default_branch_omitted(self): + """Should omit default branch from identifier.""" + config = {} + identifier = bcond_cache_identifier('pkg', config, branch='rawhide') + # Default branch should be represented as empty string + assert identifier == 'pkg:::::' + + def test_single_bcond(self): + """Should handle single bcond correctly.""" + config = {'withouts': ['tests']} + identifier = bcond_cache_identifier('pkg', config) + assert identifier == 'pkg:tests::::' + + def test_hyphen_separated(self): + """Should use hyphens to separate multiple options.""" + config = {'withouts': ['a', 'b', 'c']} + identifier = bcond_cache_identifier('pkg', config) + assert 'a-b-c' in identifier + + +class TestKojiIdIsOlderThanWeek: + """Tests for koji_id_is_older_than_week function.""" + + def test_file_older_than_retention(self, tmp_path): + """Should return True for files older than retention period.""" + koji_file = tmp_path / 'koji.id' + koji_file.write_text('12345') + + eight_days_ago = datetime.datetime.now() - datetime.timedelta(days=8) + + with patch('os.path.getmtime', return_value=eight_days_ago.timestamp()): + assert koji_id_is_older_than_week(koji_file) + + def test_file_newer_than_retention(self, tmp_path): + """Should return False for files newer than retention period.""" + koji_file = tmp_path / 'koji.id' + koji_file.write_text('12345') + + five_days_ago = datetime.datetime.now() - datetime.timedelta(days=5) + + with patch('os.path.getmtime', return_value=five_days_ago.timestamp()): + assert not koji_id_is_older_than_week(koji_file) + + def test_file_exactly_at_retention(self, tmp_path): + """Should return True for files exactly at retention boundary (< not <=).""" + koji_file = tmp_path / 'koji.id' + koji_file.write_text('12345') + + seven_days_ago = datetime.datetime.now() - datetime.timedelta(days=7) + + with patch('os.path.getmtime', return_value=seven_days_ago.timestamp()): + assert koji_id_is_older_than_week(koji_file) + + def test_very_old_file(self, tmp_path): + """Should return True for very old files.""" + koji_file = tmp_path / 'koji.id' + koji_file.write_text('12345') + + thirty_days_ago = datetime.datetime.now() - datetime.timedelta(days=30) + + with patch('os.path.getmtime', return_value=thirty_days_ago.timestamp()): + assert koji_id_is_older_than_week(koji_file) + + def test_fresh_file(self, tmp_path): + """Should return False for freshly created files.""" + koji_file = tmp_path / 'koji.id' + koji_file.write_text('12345') + + one_hour_ago = datetime.datetime.now() - datetime.timedelta(hours=1) + + with patch('os.path.getmtime', return_value=one_hour_ago.timestamp()): + assert not koji_id_is_older_than_week(koji_file) + diff --git a/tests/test_build.py b/tests/test_build.py new file mode 100644 index 00000000..5966f74d --- /dev/null +++ b/tests/test_build.py @@ -0,0 +1,90 @@ +"""Tests for build module.""" + +import pathlib +from unittest.mock import patch + +from build import parse_component_argument, get_patch_path, get_spec_path + + +class TestParseComponentArgument: + """Tests for parse_component_argument function.""" + + def test_simple_component_name(self): + """Should parse simple component name without bcond.""" + component, bootstrap = parse_component_argument('python-pytest') + assert component == 'python-pytest' + assert bootstrap is None + + @patch('build.build_reverse_id_lookup') + @patch('build.reverse_id_lookup', {'pkg:tests:::': {'withouts': ['tests']}}) + def test_bcond_identifier(self, mock_build): + """Should parse bcond identifier and lookup config.""" + component, bootstrap = parse_component_argument('pkg:tests:::') + assert component == 'pkg' + assert bootstrap == {'withouts': ['tests']} + mock_build.assert_called_once() + + @patch('build.build_reverse_id_lookup') + @patch('build.reverse_id_lookup', {'complex:docs-tests:bootstrap::': {'withouts': ['docs', 'tests'], 'withs': ['bootstrap']}}) + def test_complex_bcond_identifier(self, mock_build): + """Should parse complex bcond identifier.""" + component, bootstrap = parse_component_argument('complex:docs-tests:bootstrap::') + assert component == 'complex' + assert bootstrap == {'withouts': ['docs', 'tests'], 'withs': ['bootstrap']} + + def test_component_with_hyphen(self): + """Should handle component names with hyphens.""" + component, bootstrap = parse_component_argument('python-some-package') + assert component == 'python-some-package' + assert bootstrap is None + + def test_component_with_number(self): + """Should handle component names with numbers.""" + component, bootstrap = parse_component_argument('python3') + assert component == 'python3' + assert bootstrap is None + + +class TestGetPatchPath: + """Tests for get_patch_path function.""" + + def test_simple_component(self): + """Should generate correct patch path.""" + path = get_patch_path('mypackage') + assert isinstance(path, pathlib.Path) + assert path.name == 'mypackage.patch' + assert 'patches_dir' in str(path) + + def test_component_with_hyphen(self): + """Should handle hyphens in component name.""" + path = get_patch_path('python-pytest') + assert path.name == 'python-pytest.patch' + assert path.parent.name == 'patches_dir' + + +class TestGetSpecPath: + """Tests for get_spec_path function.""" + + def test_simple_spec_path(self): + """Should generate correct spec path.""" + repopath = pathlib.Path('/tmp/repos/mypackage') + specpath = get_spec_path(repopath, 'mypackage') + + assert isinstance(specpath, pathlib.Path) + assert specpath.name == 'mypackage.spec' + assert specpath.parent == repopath + + def test_spec_path_with_hyphen(self): + """Should handle hyphens in component name.""" + repopath = pathlib.Path('/tmp/repos/python-pytest') + specpath = get_spec_path(repopath, 'python-pytest') + + assert specpath.name == 'python-pytest.spec' + + def test_spec_path_relative(self): + """Should work with relative paths.""" + repopath = pathlib.Path('repos/pkg') + specpath = get_spec_path(repopath, 'pkg') + + assert specpath == pathlib.Path('repos/pkg/pkg.spec') + diff --git a/tests/test_gitrepo.py b/tests/test_gitrepo.py new file mode 100644 index 00000000..89510b02 --- /dev/null +++ b/tests/test_gitrepo.py @@ -0,0 +1,205 @@ +"""Tests for gitrepo module.""" + +import pytest + +from gitrepo import validate_bcond_config, generate_bcond_lines, apply_macro_replacements + + +class TestValidateBcondConfig: + """Tests for validate_bcond_config function.""" + + def test_no_conflict(self): + """Should not raise when withs and withouts don't overlap.""" + config = {'withs': ['bootstrap'], 'withouts': ['tests']} + validate_bcond_config(config) + + def test_empty_config(self): + """Should not raise for empty configuration.""" + config = {} + validate_bcond_config(config) + + def test_only_withs(self): + """Should not raise when only withs are specified.""" + config = {'withs': ['bootstrap', 'feature']} + validate_bcond_config(config) + + def test_only_withouts(self): + """Should not raise when only withouts are specified.""" + config = {'withouts': ['tests', 'docs']} + validate_bcond_config(config) + + def test_single_conflict(self): + """Should raise ValueError for single overlapping bcond.""" + config = {'withs': ['bootstrap'], 'withouts': ['bootstrap']} + with pytest.raises(ValueError, match='Cannot have the same with and without: bootstrap'): + validate_bcond_config(config) + + def test_multiple_conflicts(self): + """Should raise ValueError listing all overlapping bconds.""" + config = { + 'withs': ['bootstrap', 'tests', 'feature'], + 'withouts': ['tests', 'docs', 'feature'] + } + with pytest.raises(ValueError) as exc_info: + validate_bcond_config(config) + + error_msg = str(exc_info.value) + assert 'feature' in error_msg + assert 'tests' in error_msg + assert 'bootstrap' not in error_msg # bootstrap is not in conflict + assert 'docs' not in error_msg # docs is not in conflict + + +class TestGenerateBcondLines: + """Tests for generate_bcond_lines function.""" + + def test_empty_config(self): + """Should return empty list for empty configuration.""" + config = {} + assert generate_bcond_lines(config) == [] + + def test_only_withouts(self): + """Should generate only _without_* macros.""" + config = {'withouts': ['tests', 'docs']} + lines = generate_bcond_lines(config) + assert lines == [ + '%global _without_docs 1', + '%global _without_tests 1' + ] + + def test_only_withs(self): + """Should generate only _with_* macros.""" + config = {'withs': ['bootstrap', 'feature']} + lines = generate_bcond_lines(config) + assert lines == [ + '%global _with_bootstrap 1', + '%global _with_feature 1' + ] + + def test_both_withs_and_withouts(self): + """Should generate both types of macros in correct order.""" + config = { + 'withouts': ['tests', 'docs'], + 'withs': ['bootstrap'] + } + lines = generate_bcond_lines(config) + assert lines == [ + '%global _without_docs 1', + '%global _without_tests 1', + '%global _with_bootstrap 1' + ] + + def test_sorted_output(self): + """Should sort bconds alphabetically.""" + config = { + 'withouts': ['zed', 'alpha', 'middle'], + 'withs': ['zebra', 'aardvark'] + } + lines = generate_bcond_lines(config) + assert lines == [ + '%global _without_alpha 1', + '%global _without_middle 1', + '%global _without_zed 1', + '%global _with_aardvark 1', + '%global _with_zebra 1' + ] + + def test_single_bcond(self): + """Should handle single bcond correctly.""" + config = {'withouts': ['tests']} + lines = generate_bcond_lines(config) + assert lines == ['%global _without_tests 1'] + + +class TestApplyMacroReplacements: + """Tests for apply_macro_replacements function.""" + + def test_no_replacements(self): + """Should return unchanged text when no replacements.""" + spec_text = '%global version 1.0\n%define release 1' + result = apply_macro_replacements(spec_text, {}) + assert result == spec_text + + def test_simple_global_replacement(self): + """Should replace %global macro value.""" + spec_text = '%global version 1.0' + replacements = {'version': '2.0'} + result = apply_macro_replacements(spec_text, replacements) + assert result == '%global version 2.0' + + def test_simple_define_replacement(self): + """Should replace %define macro value.""" + spec_text = '%define release 1' + replacements = {'release': '2'} + result = apply_macro_replacements(spec_text, replacements) + assert result == '%define release 2' + + def test_multiple_replacements(self): + """Should handle multiple macro replacements.""" + spec_text = '''%global version 1.0 +%define release 1 +%global someflag 0''' + replacements = {'version': '2.0', 'someflag': '1'} + result = apply_macro_replacements(spec_text, replacements) + expected = '''%global version 2.0 +%define release 1 +%global someflag 1''' + assert result == expected + + def test_macro_with_special_chars(self): + """Should handle macro names with special regex characters.""" + spec_text = '%global some.macro 0' + replacements = {'some.macro': '1'} + result = apply_macro_replacements(spec_text, replacements) + assert result == '%global some.macro 1' + + def test_macro_with_underscores(self): + """Should handle macro names with underscores.""" + spec_text = '%global _with_feature 0' + replacements = {'_with_feature': '1'} + result = apply_macro_replacements(spec_text, replacements) + assert result == '%global _with_feature 1' + + def test_preserves_indentation(self): + """Should preserve leading whitespace.""" + spec_text = ' %global version 1.0\n\t%define release 1' + replacements = {'version': '2.0'} + result = apply_macro_replacements(spec_text, replacements) + assert result == ' %global version 2.0\n\t%define release 1' + + def test_preserves_spacing(self): + """Should preserve spacing between elements.""" + spec_text = '%global version 1.0' + replacements = {'version': '2.0'} + result = apply_macro_replacements(spec_text, replacements) + # The regex should preserve the spacing structure + assert '%global' in result + assert 'version' in result + assert '2.0' in result + + def test_only_replaces_matching_macro(self): + """Should only replace the specified macro, not similar ones.""" + spec_text = '''%global version 1.0 +%global version_major 1 +%global my_version 1.0''' + replacements = {'version': '2.0'} + result = apply_macro_replacements(spec_text, replacements) + expected = '''%global version 2.0 +%global version_major 1 +%global my_version 1.0''' + assert result == expected + + def test_multiline_spec(self): + """Should work correctly with multiline spec files.""" + spec_text = '''Name: mypackage +Version: 1.0 +%global debug_package %{nil} +%global with_tests 0 + +Summary: A test package''' + replacements = {'with_tests': '1'} + result = apply_macro_replacements(spec_text, replacements) + assert '%global with_tests 1' in result + assert 'Name: mypackage' in result + assert 'Summary: A test package' in result + diff --git a/tests/test_jobs.py b/tests/test_jobs.py new file mode 100644 index 00000000..964c5426 --- /dev/null +++ b/tests/test_jobs.py @@ -0,0 +1,191 @@ +"""Tests for jobs module.""" + +import pytest + +from jobs import ReverseLookupDict, get_component_status_info + + +class TestReverseLookupDict: + """Tests for ReverseLookupDict class.""" + + def test_basic_usage(self): + """Should work as a defaultdict(list).""" + rld = ReverseLookupDict() + rld['key1'].append('value1') + rld['key1'].append('value2') + rld['key2'].append('value3') + + assert rld['key1'] == ['value1', 'value2'] + assert rld['key2'] == ['value3'] + + def test_key_lookup(self): + """Should be able to reverse-lookup keys from values.""" + rld = ReverseLookupDict() + rld['component1'].append('pkg1') + rld['component1'].append('pkg2') + rld['component2'].append('pkg3') + + assert rld.key('pkg1') == 'component1' + assert rld.key('pkg2') == 'component1' + assert rld.key('pkg3') == 'component2' + + def test_key_lookup_caches(self): + """Should cache reverse lookups.""" + rld = ReverseLookupDict() + rld['key1'].append('value1') + + # First lookup + result1 = rld.key('value1') + # Second lookup should use cache + result2 = rld.key('value1') + + assert result1 == result2 == 'key1' + assert 'value1' in rld._reverse_lookup_cache + + def test_key_lookup_not_found(self): + """Should raise KeyError for values not in any list.""" + rld = ReverseLookupDict() + rld['key1'].append('value1') + + with pytest.raises(KeyError, match="Value 'nonexistent' found in no list"): + rld.key('nonexistent') + + def test_all_values(self): + """Should return set of all values across all keys.""" + rld = ReverseLookupDict() + rld['key1'].append('value1') + rld['key1'].append('value2') + rld['key2'].append('value3') + rld['key2'].append('value4') + + all_vals = rld.all_values() + assert all_vals == {'value1', 'value2', 'value3', 'value4'} + + def test_all_values_empty(self): + """Should return empty set for empty dict.""" + rld = ReverseLookupDict() + assert rld.all_values() == set() + + def test_all_values_deduplicates(self): + """Should return unique values even if duplicated across keys.""" + rld = ReverseLookupDict() + rld['key1'].append('value1') + rld['key2'].append('value1') # Duplicate + rld['key3'].append('value2') + + all_vals = rld.all_values() + assert all_vals == {'value1', 'value2'} + + def test_default_factory_can_be_disabled(self): + """Should be able to disable auto-creation of lists.""" + rld = ReverseLookupDict() + rld['key1'].append('value1') + + # Disable default factory + rld.default_factory = None + + # Now accessing non-existent key should raise KeyError + with pytest.raises(KeyError): + _ = rld['nonexistent'] + + def test_multiple_values_per_key(self): + """Should handle multiple values per key.""" + rld = ReverseLookupDict() + values = ['v1', 'v2', 'v3', 'v4', 'v5'] + for val in values: + rld['key1'].append(val) + + assert rld['key1'] == values + for val in values: + assert rld.key(val) == 'key1' + + +class TestGetComponentStatusInfo: + """Tests for get_component_status_info function.""" + + def test_component_blocked_with_missing_packages(self): + """Should show blocked status with missing packages.""" + missing_packages = {'comp1': {'pkg1', 'pkg2', 'pkg3'}} + components = {'comp1': ['some_pkg']} + unresolvable_components = set() + + status = get_component_status_info('comp1', missing_packages, components, unresolvable_components) + assert '(blocked by:' in status + # Should show first 3 packages + assert any(pkg in status for pkg in ['pkg1', 'pkg2', 'pkg3']) + + def test_component_blocked_with_many_packages(self): + """Should truncate and add ... for many missing packages.""" + missing_packages = {'comp1': {f'pkg{i}' for i in range(10)}} + components = {'comp1': ['some_pkg']} + unresolvable_components = set() + + status = get_component_status_info('comp1', missing_packages, components, unresolvable_components) + assert '(blocked by:' in status + assert '...' in status + + def test_component_blocked_unknown_reason(self): + """Should show unknown reason if in missing_packages but empty.""" + missing_packages = {'comp1': set()} + components = {'comp1': ['some_pkg']} + unresolvable_components = set() + + status = get_component_status_info('comp1', missing_packages, components, unresolvable_components) + assert '(blocked for unknown reason)' in status + + def test_component_ready(self): + """Should show ready status if in components but not in missing_packages.""" + missing_packages = {} + components = {'comp1': ['some_pkg']} + unresolvable_components = set() + + status = get_component_status_info('comp1', missing_packages, components, unresolvable_components) + assert '(ready)' in status + + def test_component_build_failed(self): + """Should show build failed if not in components.""" + missing_packages = {} + components = {} + unresolvable_components = set() + + status = get_component_status_info('comp1', missing_packages, components, unresolvable_components) + assert '(build failed)' in status + + def test_component_unresolvable(self): + """Should show can't resolve dependencies if in unresolvable_components.""" + missing_packages = {} + components = {'comp1': ['some_pkg']} + unresolvable_components = {'comp1'} + + status = get_component_status_info('comp1', missing_packages, components, unresolvable_components) + assert "(can't resolve dependencies)" in status + + def test_blocked_with_few_packages(self): + """Should show all packages if 3 or fewer.""" + missing_packages = {'comp1': {'pkg1', 'pkg2'}} + components = {'comp1': ['some_pkg']} + unresolvable_components = set() + + status = get_component_status_info('comp1', missing_packages, components, unresolvable_components) + assert 'pkg1' in status + assert 'pkg2' in status + assert '...' not in status + + def test_blocked_with_exactly_three_packages(self): + """Should show all 3 packages without truncation.""" + missing_packages = {'comp1': {'pkg1', 'pkg2', 'pkg3'}} + components = {'comp1': ['some_pkg']} + unresolvable_components = set() + + status = get_component_status_info('comp1', missing_packages, components, unresolvable_components) + assert '...' not in status + + def test_blocked_with_four_packages(self): + """Should truncate at 4 packages.""" + missing_packages = {'comp1': {'pkg1', 'pkg2', 'pkg3', 'pkg4'}} + components = {'comp1': ['some_pkg']} + unresolvable_components = set() + + status = get_component_status_info('comp1', missing_packages, components, unresolvable_components) + assert '...' in status + diff --git a/tests/test_resolve_buildroot.py b/tests/test_resolve_buildroot.py index 89269977..6a59a078 100644 --- a/tests/test_resolve_buildroot.py +++ b/tests/test_resolve_buildroot.py @@ -37,7 +37,7 @@ def test_mandatory_packages_in_groups_contains_limited_number_of_packages(): # XXX this test has data copied from simple specfiles, but it can change @pytest.mark.parametrize('package_name, expected', [ ('fedora-obsolete-packages', ()), - ('fedora-repos', ('gnupg', 'sed')), + ('fedora-repos', ('gnupg', 'rpm', 'sed')), ('fedora-release', ('redhat-rpm-config > 121-1', 'systemd-rpm-macros')), ('redhat-rpm-config', ('perl-generators',)), ]) diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 00000000..bb521def --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,117 @@ +"""Tests for utils module.""" + +from unittest.mock import Mock + +from utils import name_or_str, stringify + + +class TestNameOrStr: + """Tests for name_or_str function.""" + + def test_object_with_name_attribute(self): + """Should return name attribute if present.""" + obj = Mock() + obj.name = 'test_name' + assert name_or_str(obj) == 'test_name' + + def test_object_without_name_attribute(self): + """Should return str() representation if no name attribute.""" + # Use a simple class without name attribute + class NoNameObj: + def __str__(self): + return 'string_repr' + + obj = NoNameObj() + assert name_or_str(obj) == 'string_repr' + + def test_simple_string(self): + """Should return string as-is.""" + assert name_or_str('hello') == 'hello' + + def test_number(self): + """Should convert number to string.""" + assert name_or_str(42) == '42' + + def test_hawkey_package_like(self): + """Should work with objects that have name like hawkey.Package.""" + pkg = Mock() + pkg.name = 'python3-pytest' + pkg.version = '7.4.0' + assert name_or_str(pkg) == 'python3-pytest' + + def test_none_value(self): + """Should convert None to string.""" + result = name_or_str(None) + assert result == 'None' + + +class TestStringify: + """Tests for stringify function.""" + + def test_empty_list(self): + """Should return empty string for empty list.""" + assert stringify([]) == '' + + def test_single_item(self): + """Should return single item as string.""" + assert stringify(['item1']) == 'item1' + + def test_multiple_items_default_separator(self): + """Should join items with comma and space by default.""" + items = ['item1', 'item2', 'item3'] + assert stringify(items) == 'item1, item2, item3' + + def test_custom_separator(self): + """Should use custom separator when provided.""" + items = ['item1', 'item2', 'item3'] + assert stringify(items, '\n') == 'item1\nitem2\nitem3' + + def test_objects_with_name_attribute(self): + """Should extract name from objects.""" + obj1 = Mock() + obj1.name = 'package1' + obj2 = Mock() + obj2.name = 'package2' + + result = stringify([obj1, obj2]) + assert result == 'package1, package2' + + def test_mixed_objects_and_strings(self): + """Should handle mix of objects and strings.""" + obj = Mock() + obj.name = 'package1' + + result = stringify([obj, 'string_item']) + assert result == 'package1, string_item' + + def test_numbers(self): + """Should convert numbers to strings.""" + items = [1, 2, 3] + assert stringify(items) == '1, 2, 3' + + def test_newline_separator(self): + """Should work with newline separator.""" + items = ['pkg1', 'pkg2', 'pkg3'] + result = stringify(items, '\n') + assert result == 'pkg1\npkg2\npkg3' + + def test_pipe_separator(self): + """Should work with pipe separator.""" + items = ['a', 'b', 'c'] + assert stringify(items, '|') == 'a|b|c' + + def test_empty_separator(self): + """Should concatenate with no separator.""" + items = ['a', 'b', 'c'] + assert stringify(items, '') == 'abc' + + def test_generator_input(self): + """Should work with generator expressions.""" + gen = (x for x in ['a', 'b', 'c']) + assert stringify(gen) == 'a, b, c' + + def test_tuple_input(self): + """Should work with tuples.""" + items = ('x', 'y', 'z') + assert stringify(items) == 'x, y, z' + diff --git a/utils.py b/utils.py index 58e195fd..ecc9a582 100644 --- a/utils.py +++ b/utils.py @@ -1,3 +1,4 @@ +import subprocess import sys import tomllib @@ -26,3 +27,10 @@ def stringify(lst, separator=', '): If no separator is given, separates the items by comma and space. """ return separator.join(name_or_str(i) for i in lst) + + +def run(*cmd, **kwargs): + kwargs.setdefault('check', True) + kwargs.setdefault('capture_output', True) + kwargs.setdefault('text', True) + return subprocess.run(cmd, **kwargs)