Skip to content

Commit bef46af

Browse files
committed
konflux: Decouple lockfile generation and merging
Refactor `konflux-rpm-lockfile` to support parallel, per-arch builds. The script is now split into `generate` and `merge` subcommands. `cmd-fetch` now calls `generate` to create arch-specific lockfile (e.g., `x86_64.rpms.lock.yaml`). It also look for an existing lockfile and call generate in that case. The `merge` subcommand combines these files in one, while injecting overrides. The override mechanism allow developpers to add extra RPMs needed in the build, e.g. in `buildroot-prep` [1] The pipeline will call merge after running `generate` on each builder. [1] coreos/fedora-coreos-config@fb167ed See: coreos#4298 (comment)
1 parent d014342 commit bef46af

File tree

2 files changed

+120
-51
lines changed

2 files changed

+120
-51
lines changed

src/cmd-fetch

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ fi
109109

110110
prepare_build
111111

112+
112113
lock_args=
113114
extra_args=
114115

@@ -181,9 +182,10 @@ if [ -n "${UPDATE_LOCKFILE}" ]; then
181182
echo "Wrote out lockfile ${outfile}"
182183
fi
183184

184-
if [ -n "${KONFLUX}" ]; then
185+
KONFLUX_LOCKFILE=rpms.lock.yaml
186+
if [ -n "${KONFLUX}" ] || [ -f "${configdir}/${KONFLUX_LOCKFILE}" ]; then
185187
echo "Generating hermeto lockfile..."
186-
/usr/lib/coreos-assembler/konflux-rpm-lockfile "${flattened_manifest}" --context "${configdir}" --output "${tmprepo}/tmp/rpms.lock.yaml" --arch all
187-
(cd "${workdir}" && mv -f "${tmprepo}/tmp/rpms.lock.yaml" "konflux-rpms-lock.yaml")
188-
echo "Wrote out hermeto lockfile: konflux-rpms-lock.yaml"
188+
/usr/lib/coreos-assembler/konflux-rpm-lockfile generate "${flattened_manifest}" --context "${configdir}" --output "${tmprepo}/tmp/${arch}.${KONFLUX_LOCKFILE}"
189+
mv -f "${tmprepo}/tmp/${arch}.${KONFLUX_LOCKFILE}" "${configdir}/${arch}.${KONFLUX_LOCKFILE}"
190+
echo "Wrote out hermeto (konflux) lockfile: ${configdir}/${arch}.${KONFLUX_LOCKFILE}"
189191
fi

src/konflux-rpm-lockfile

Lines changed: 114 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -62,33 +62,38 @@ def write_hermeto_lockfile(arch_packages, repos):
6262
return lockfile
6363

6464

65-
def merge_lockfiles(base_lockfile, override_lockfile):
65+
def merge_lockfiles(base_lockfile, next_lockfile, override=False):
6666
"""
67-
Merges an override lockfile into a base lockfile.
67+
Merges a lockfile into a base lockfile.
68+
69+
If is_override is True, it will only add packages to existing
70+
architectures. Otherwise, it will add new architectures.
6871
"""
69-
if not override_lockfile:
72+
if not next_lockfile:
7073
return base_lockfile
7174

7275
# Create a dictionary for base arches for easy lookup
7376
base_arches = {arch['arch']: arch for arch in base_lockfile.get('arches', [])}
7477

75-
override = override_lockfile.get('arches', [])
76-
if not override:
78+
next_arches_list = next_lockfile.get('arches', [])
79+
if not next_arches_list:
7780
return base_lockfile
7881

79-
for override_entry in override:
80-
# override_entry is a dict like {'arch': x86_64','packages': [...]}
81-
if not isinstance(override_entry, dict):
82+
for next_arch_entry in next_arches_list:
83+
if not isinstance(next_arch_entry, dict):
84+
continue
85+
arch = next_arch_entry.get('arch', None)
86+
if not arch:
8287
continue
83-
arch = override_entry.get('arch', None)
84-
override_packages = override_entry.get('packages', [])
88+
89+
next_packages = next_arch_entry.get('packages', [])
8590
if arch in base_arches:
86-
# Merge packages
91+
# Arch exists, merge packages
8792
base_packages = base_arches[arch].get('packages', [])
88-
base_packages += override_packages
89-
else:
90-
# Add the arch from the override file
91-
base_arches[arch] = override_packages
93+
base_packages += next_packages
94+
elif not override:
95+
# Arch is new and this is not an override, so add it
96+
base_arches[arch] = next_arch_entry
9297

9398
# Reconstruct the arches list
9499
base_lockfile['arches'] = list(base_arches.values())
@@ -119,21 +124,24 @@ def query_packages_location(locks, repoquery_args):
119124
if name not in processed_urls:
120125
processed_urls[name] = url
121126
pkg_urls = list(processed_urls.values())
122-
# sanity check all the packages got resolved
123-
if len(pkg_urls) < len(locked_nevras):
127+
# sanity check all the locked packages got resolved
128+
if len(pkg_urls) != len(locked_nevras):
124129
print("Some packages from the lockfile could not be resolved. The rpm-ostree lockfile is probably out of date.")
125-
for name in locks.keys():
126-
if name not in processed_urls:
127-
print(f"could not resolve package {name}")
128130
sys.exit(1)
129131

132+
print(f"Done. Solved {len(pkg_urls)} packages.")
130133
return pkg_urls
131134

132135

133-
def generate_lockfile(contextdir, manifest, output_path, arches):
136+
def generate_main(args):
134137
"""
135138
Generates the cachi2/hermeto RPM lock file.
136139
"""
140+
contextdir = args.context
141+
manifest = os.path.abspath(args.manifest)
142+
output_path = args.output
143+
arches = args.arch
144+
137145
if not arches:
138146
arches_to_resolve = [get_basearch()]
139147
elif 'all' in arches:
@@ -151,7 +159,7 @@ def generate_lockfile(contextdir, manifest, output_path, arches):
151159
repos = manifest_data.get('repos', [])
152160
repos += manifest_data.get('lockfile-repos', [])
153161

154-
repoquery_args = ["--queryformat", "%{name} %{location}\n", "--disablerepo=*", "--refresh"]
162+
repoquery_args = ["--queryformat", "%{name} %{location}\n", "--disablerepo=*", "--refresh", "--quiet"]
155163
# Tell dnf to load repos files from $contextdir
156164
repoquery_args.extend([f"--setopt=reposdir={contextdir}"])
157165

@@ -165,64 +173,123 @@ def generate_lockfile(contextdir, manifest, output_path, arches):
165173
print(f"This tool derive the konflux lockfile from rpm-ostree lockfiles. No manifest-lock exist for {arch} in {contextdir}")
166174
sys.exit(1)
167175
print(f"Resolving packages for {arch}...")
168-
# append noarch as well because otherwise tose packages get excluded from results
169-
# We use --forcearch here because otherwise dnf still respect the system basearch
170-
# we have to specify both --arch and --forcearch to get both result for $arch and $noarch
171-
args_arch = ['--forcearch', arch, '--arch', arch, '--arch', 'noarch']
172-
pkg_urls = query_packages_location(locks, repoquery_args + args_arch)
176+
arch_args = []
177+
if arch is not get_basearch():
178+
# append noarch as well because otherwise those packages get excluded from results
179+
# We use --forcearch here because otherwise dnf still respect the system basearch
180+
# we have to specify both --arch and --forcearch to get both result for $arch and $noarch
181+
arch_args = ['--forcearch', arch, '--arch', arch, '--arch', 'noarch']
182+
pkg_urls = query_packages_location(locks, repoquery_args + arch_args)
173183
packages.append({'arch': arch, 'packages': pkg_urls})
174184

175185
lockfile = write_hermeto_lockfile(packages, repos)
176186

177-
override_path = os.path.join(contextdir, 'konflux-lockfile-override.yaml')
178-
if os.path.exists(override_path):
187+
try:
188+
with open(output_path, 'w', encoding='utf-8') as f:
189+
yaml.safe_dump(lockfile, f, default_flow_style=False)
190+
except IOError as e:
191+
print(f"\u274c Error: Could not write to output file '{output_path}'. Reason: {e}")
192+
sys.exit(1)
193+
194+
195+
def merge_main(args):
196+
"""
197+
Merges multiple lockfiles into one, optionally applying an override file.
198+
"""
199+
if not args.input:
200+
print("Error: at least one input file is required for merging.", file=sys.stderr)
201+
sys.exit(1)
202+
203+
try:
204+
with open(args.input[0], 'r', encoding='utf-8') as f:
205+
base_lockfile = yaml.safe_load(f)
206+
except (IOError, yaml.YAMLError) as e:
207+
print(f"Error reading base lockfile {args.input[0]}: {e}", file=sys.stderr)
208+
sys.exit(1)
209+
210+
for subsequent_file in args.input[1:]:
179211
try:
180-
with open(override_path, 'r', encoding="utf8") as f:
212+
with open(subsequent_file, 'r', encoding='utf-8') as f:
213+
next_lockfile = yaml.safe_load(f)
214+
base_lockfile = merge_lockfiles(base_lockfile, next_lockfile)
215+
except (IOError, yaml.YAMLError) as e:
216+
print(f"Error reading or merging {subsequent_file}: {e}", file=sys.stderr)
217+
sys.exit(1)
218+
219+
if os.path.exists(args.override):
220+
try:
221+
with open(args.override, 'r', encoding="utf8") as f:
181222
override_data = yaml.safe_load(f)
182-
print(f"Merging override from {override_path}")
183-
lockfile = merge_lockfiles(lockfile, override_data)
223+
print(f"Merging override from {args.override}")
224+
base_lockfile = merge_lockfiles(base_lockfile, override_data, override=True)
184225
except (IOError, yaml.YAMLError) as e:
185-
print(f"\u274c Error: Could not read or parse override file '{override_path}'. Reason: {e}")
226+
print(f"Error reading or parsing override file '{args.override}': {e}", file=sys.stderr)
186227
sys.exit(1)
187228

188229
try:
189-
with open(output_path, 'w', encoding='utf-8') as f:
190-
yaml.safe_dump(lockfile, f, default_flow_style=False)
230+
with open(args.output, 'w', encoding='utf-8') as f:
231+
yaml.safe_dump(base_lockfile, f, default_flow_style=False)
232+
print(f"Successfully merged lockfiles to {args.output}")
191233
except IOError as e:
192-
print(f"\u274c Error: Could not write to output file '{output_path}'. Reason: {e}")
234+
print(f"Error writing to output file '{args.output}': {e}", file=sys.stderr)
193235
sys.exit(1)
194236

195237

196238
if __name__ == "__main__":
197239
parser = argparse.ArgumentParser(
198-
description="Generate hermeto lock files."
240+
description="Generate and merge hermeto lock files."
199241
)
242+
subparsers = parser.add_subparsers(dest='command', required=True)
200243

201-
parser.add_argument(
244+
# GENERATE command
245+
parser_generate = subparsers.add_parser(
246+
'generate',
247+
help='Resolve RPMs and generate a lockfile for one or more architectures.'
248+
)
249+
parser_generate.add_argument(
202250
'manifest',
203251
help='Path to the flattened rpm-ostree manifest (e.g., tmp/manifest.json)'
204252
)
205-
206-
parser.add_argument(
253+
parser_generate.add_argument(
207254
'--context',
208255
default='.',
209256
help="Path to the directory containing repofiles and lockfiles. (default: '.')"
210257
)
211-
212-
parser.add_argument(
258+
parser_generate.add_argument(
213259
'--output',
214260
default='./rpms.lock.yaml',
215261
help="Path for the hermeto lockfile. (default: './rpms.lock.yaml')"
216262
)
217-
218-
parser.add_argument(
263+
parser_generate.add_argument(
219264
'--arch',
220265
action='append',
221266
choices=['x86_64', 'aarch64', 's390x', 'ppc64le', 'all'],
222267
help="The architecture to resolve. Can be specified multiple times. 'all' resolves all architectures."
223268
)
269+
parser_generate.set_defaults(func=generate_main)
224270

225-
args = parser.parse_args()
271+
# MERGE command
272+
parser_merge = subparsers.add_parser(
273+
'merge',
274+
help='Merge multiple architecture-specific lockfiles into a single file.'
275+
)
276+
parser_merge.add_argument(
277+
'--input',
278+
nargs='+',
279+
required=True,
280+
help='One or more input lockfiles to merge.'
281+
)
282+
parser_merge.add_argument(
283+
'--output',
284+
default='./rpms.lock.yaml',
285+
help="Path for the merged lockfile. (default: './rpms.lock.yaml')"
286+
)
287+
parser_merge.add_argument(
288+
'--override',
289+
default='konflux-lockfile-override.yaml',
290+
help="Path to an override file. (default: 'konflux-lockfile-override.yaml')"
291+
)
292+
parser_merge.set_defaults(func=merge_main)
226293

227-
manifest_abs_path = os.path.abspath(args.manifest)
228-
generate_lockfile(args.context, manifest_abs_path, args.output, args.arch)
294+
args = parser.parse_args()
295+
args.func(args)

0 commit comments

Comments
 (0)