Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 137 additions & 1 deletion bconds.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import argparse
import datetime
import functools
import json
import os
import pathlib
import sys
Expand Down Expand Up @@ -291,6 +292,125 @@ 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(
Expand All @@ -299,6 +419,12 @@ def parse_args():
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='*',
Expand All @@ -310,11 +436,19 @@ def 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():
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
Expand All @@ -332,8 +466,10 @@ def parse_args():
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.')
Loading