diff options
Diffstat (limited to 'debian/patches/v7.2.13.diff')
-rw-r--r-- | debian/patches/v7.2.13.diff | 3859 |
1 files changed, 3859 insertions, 0 deletions
diff --git a/debian/patches/v7.2.13.diff b/debian/patches/v7.2.13.diff new file mode 100644 index 00000000..26761b80 --- /dev/null +++ b/debian/patches/v7.2.13.diff @@ -0,0 +1,3859 @@ +Subject: v7.2.13 +Date: Tue Jul 16 08:40:38 2024 +0300 +From: Michael Tokarev <mjt@tls.msk.ru> +Forwarded: not-needed + +This is a difference between upstream qemu v7.2.12 +and upstream qemu v7.2.13. + + .gitlab-ci.d/buildtest.yml | 34 ++++------ + .gitlab-ci.d/container-core.yml | 4 +- + .gitlab-ci.d/container-cross.yml | 1 + + VERSION | 2 +- + block.c | 76 ++++++++++++++-------- + block/qcow2.c | 17 ++++- + chardev/char-stdio.c | 4 ++ + docs/devel/testing.rst | 6 ++ + hw/display/vga.c | 7 ++ + hw/net/virtio-net.c | 18 +++-- + hw/virtio/virtio.c | 1 - + linux-user/syscall.c | 10 ++- + target/arm/vec_helper.c | 4 +- + target/i386/cpu.c | 6 +- + target/i386/tcg/translate.c | 2 +- + tcg/loongarch64/tcg-target.c.inc | 32 +++++---- + .../dockerfiles/{centos8.docker => centos9.docker} | 11 ++-- + tests/docker/dockerfiles/fedora-win32-cross.docker | 4 +- + tests/docker/dockerfiles/fedora-win64-cross.docker | 4 +- + tests/docker/dockerfiles/fedora.docker | 4 +- + tests/docker/dockerfiles/opensuse-leap.docker | 22 +++---- + tests/docker/dockerfiles/ubuntu2004.docker | 2 +- + tests/lcitool/libvirt-ci | 2 +- + tests/lcitool/mappings.yml | 60 +++++++++++++++++ + tests/lcitool/refresh | 8 +-- + tests/lcitool/targets/centos-stream-8.yml | 3 + + tests/lcitool/targets/opensuse-leap-153.yml | 3 + + tests/qemu-iotests/061 | 6 +- + tests/qemu-iotests/061.out | 8 ++- + tests/qemu-iotests/244 | 19 +++++- + tests/qemu-iotests/270 | 14 +++- + tests/vm/centos | 4 +- + 32 files changed, 270 insertions(+), 128 deletions(-) + +diff --git a/.gitlab-ci.d/buildtest.yml b/.gitlab-ci.d/buildtest.yml +index 7243b8079b..9b6da37582 100644 +--- a/.gitlab-ci.d/buildtest.yml ++++ b/.gitlab-ci.d/buildtest.yml +@@ -162,9 +162,9 @@ crash-test-fedora: + build-system-centos: + extends: .native_build_job_template + needs: +- job: amd64-centos8-container ++ job: amd64-centos9-container + variables: +- IMAGE: centos8 ++ IMAGE: centos9 + CONFIGURE_ARGS: --disable-nettle --enable-gcrypt --enable-fdt=system + --enable-modules --enable-trace-backends=dtrace --enable-docs + --enable-vfio-user-server +@@ -182,7 +182,7 @@ check-system-centos: + - job: build-system-centos + artifacts: true + variables: +- IMAGE: centos8 ++ IMAGE: centos9 + MAKE_CHECK_ARGS: check + + avocado-system-centos: +@@ -191,7 +191,7 @@ avocado-system-centos: + - job: build-system-centos + artifacts: true + variables: +- IMAGE: centos8 ++ IMAGE: centos9 + MAKE_CHECK_ARGS: check-avocado + + build-system-opensuse: +@@ -237,9 +237,9 @@ avocado-system-opensuse: + build-tcg-disabled: + extends: .native_build_job_template + needs: +- job: amd64-centos8-container ++ job: amd64-centos9-container + variables: +- IMAGE: centos8 ++ IMAGE: centos9 + script: + - mkdir build + - cd build +@@ -469,7 +469,6 @@ tsan-build: + CONFIGURE_ARGS: --enable-tsan --cc=clang-10 --cxx=clang++-10 + --enable-trace-backends=ust --enable-fdt=system --disable-slirp + TARGETS: x86_64-softmmu ppc64-softmmu riscv64-softmmu x86_64-linux-user +- MAKE_CHECK_ARGS: bench V=1 + + # gprof/gcov are GCC features + build-gprof-gcov: +@@ -560,29 +559,22 @@ build-coroutine-sigaltstack: + MAKE_CHECK_ARGS: check-unit + + # Check our reduced build configurations +-build-without-default-devices: ++build-without-defaults: + extends: .native_build_job_template + needs: +- job: amd64-centos8-container ++ job: amd64-centos9-container + variables: +- IMAGE: centos8 +- CONFIGURE_ARGS: --without-default-devices --disable-user +- +-build-without-default-features: +- extends: .native_build_job_template +- needs: +- job: amd64-fedora-container +- variables: +- IMAGE: fedora ++ IMAGE: centos9 + CONFIGURE_ARGS: ++ --without-default-devices + --without-default-features +- --disable-capstone ++ --disable-fdt + --disable-pie + --disable-qom-cast-debug + --disable-strip +- TARGETS: avr-softmmu i386-softmmu mips64-softmmu s390x-softmmu sh4-softmmu ++ TARGETS: avr-softmmu mips64-softmmu s390x-softmmu sh4-softmmu + sparc64-softmmu hexagon-linux-user i386-linux-user s390x-linux-user +- MAKE_CHECK_ARGS: check-unit check-qtest SPEED=slow ++ MAKE_CHECK_ARGS: check-unit check-qtest-avr check-qtest-mips64 + + build-libvhost-user: + extends: .base_job_template +diff --git a/.gitlab-ci.d/container-core.yml b/.gitlab-ci.d/container-core.yml +index 08f8450fa1..5459447676 100644 +--- a/.gitlab-ci.d/container-core.yml ++++ b/.gitlab-ci.d/container-core.yml +@@ -1,10 +1,10 @@ + include: + - local: '/.gitlab-ci.d/container-template.yml' + +-amd64-centos8-container: ++amd64-centos9-container: + extends: .container_job_template + variables: +- NAME: centos8 ++ NAME: centos9 + + amd64-fedora-container: + extends: .container_job_template +diff --git a/.gitlab-ci.d/container-cross.yml b/.gitlab-ci.d/container-cross.yml +index 2d560e9764..24343192ac 100644 +--- a/.gitlab-ci.d/container-cross.yml ++++ b/.gitlab-ci.d/container-cross.yml +@@ -115,6 +115,7 @@ riscv64-debian-cross-container: + allow_failure: true + variables: + NAME: debian-riscv64-cross ++ QEMU_JOB_OPTIONAL: 1 + + # we can however build TCG tests using a non-sid base + riscv64-debian-test-cross-container: +diff --git a/VERSION b/VERSION +index 4625f55e26..c0d5d580b2 100644 +--- a/VERSION ++++ b/VERSION +@@ -1 +1 @@ +-7.2.12 ++7.2.13 +diff --git a/block.c b/block.c +index a18f052374..ea369a3fe5 100644 +--- a/block.c ++++ b/block.c +@@ -85,6 +85,7 @@ static BlockDriverState *bdrv_open_inherit(const char *filename, + BlockDriverState *parent, + const BdrvChildClass *child_class, + BdrvChildRole child_role, ++ bool parse_filename, + Error **errp); + + static bool bdrv_recurse_has_child(BlockDriverState *bs, +@@ -2051,7 +2052,8 @@ static void parse_json_protocol(QDict *options, const char **pfilename, + * block driver has been specified explicitly. + */ + static int bdrv_fill_options(QDict **options, const char *filename, +- int *flags, Error **errp) ++ int *flags, bool allow_parse_filename, ++ Error **errp) + { + const char *drvname; + bool protocol = *flags & BDRV_O_PROTOCOL; +@@ -2093,7 +2095,7 @@ static int bdrv_fill_options(QDict **options, const char *filename, + if (protocol && filename) { + if (!qdict_haskey(*options, "filename")) { + qdict_put_str(*options, "filename", filename); +- parse_filename = true; ++ parse_filename = allow_parse_filename; + } else { + error_setg(errp, "Can't specify 'file' and 'filename' options at " + "the same time"); +@@ -3516,7 +3518,8 @@ int bdrv_open_backing_file(BlockDriverState *bs, QDict *parent_options, + } + + backing_hd = bdrv_open_inherit(backing_filename, reference, options, 0, bs, +- &child_of_bds, bdrv_backing_role(bs), errp); ++ &child_of_bds, bdrv_backing_role(bs), true, ++ errp); + if (!backing_hd) { + bs->open_flags |= BDRV_O_NO_BACKING; + error_prepend(errp, "Could not open backing file: "); +@@ -3549,7 +3552,8 @@ free_exit: + static BlockDriverState * + bdrv_open_child_bs(const char *filename, QDict *options, const char *bdref_key, + BlockDriverState *parent, const BdrvChildClass *child_class, +- BdrvChildRole child_role, bool allow_none, Error **errp) ++ BdrvChildRole child_role, bool allow_none, ++ bool parse_filename, Error **errp) + { + BlockDriverState *bs = NULL; + QDict *image_options; +@@ -3580,7 +3584,8 @@ bdrv_open_child_bs(const char *filename, QDict *options, const char *bdref_key, + } + + bs = bdrv_open_inherit(filename, reference, image_options, 0, +- parent, child_class, child_role, errp); ++ parent, child_class, child_role, parse_filename, ++ errp); + if (!bs) { + goto done; + } +@@ -3590,6 +3595,28 @@ done: + return bs; + } + ++static BdrvChild *bdrv_open_child_common(const char *filename, ++ QDict *options, const char *bdref_key, ++ BlockDriverState *parent, ++ const BdrvChildClass *child_class, ++ BdrvChildRole child_role, ++ bool allow_none, bool parse_filename, ++ Error **errp) ++{ ++ BlockDriverState *bs; ++ ++ GLOBAL_STATE_CODE(); ++ ++ bs = bdrv_open_child_bs(filename, options, bdref_key, parent, child_class, ++ child_role, allow_none, parse_filename, errp); ++ if (bs == NULL) { ++ return NULL; ++ } ++ ++ return bdrv_attach_child(parent, bs, bdref_key, child_class, child_role, ++ errp); ++} ++ + /* + * Opens a disk image whose options are given as BlockdevRef in another block + * device's options. +@@ -3611,18 +3638,9 @@ BdrvChild *bdrv_open_child(const char *filename, + BdrvChildRole child_role, + bool allow_none, Error **errp) + { +- BlockDriverState *bs; +- +- GLOBAL_STATE_CODE(); +- +- bs = bdrv_open_child_bs(filename, options, bdref_key, parent, child_class, +- child_role, allow_none, errp); +- if (bs == NULL) { +- return NULL; +- } +- +- return bdrv_attach_child(parent, bs, bdref_key, child_class, child_role, +- errp); ++ return bdrv_open_child_common(filename, options, bdref_key, parent, ++ child_class, child_role, allow_none, false, ++ errp); + } + + /* +@@ -3639,8 +3657,8 @@ int bdrv_open_file_child(const char *filename, + role = parent->drv->is_filter ? + (BDRV_CHILD_FILTERED | BDRV_CHILD_PRIMARY) : BDRV_CHILD_IMAGE; + +- if (!bdrv_open_child(filename, options, bdref_key, parent, +- &child_of_bds, role, false, errp)) ++ if (!bdrv_open_child_common(filename, options, bdref_key, parent, ++ &child_of_bds, role, false, true, errp)) + { + return -EINVAL; + } +@@ -3685,7 +3703,8 @@ BlockDriverState *bdrv_open_blockdev_ref(BlockdevRef *ref, Error **errp) + + } + +- bs = bdrv_open_inherit(NULL, reference, qdict, 0, NULL, NULL, 0, errp); ++ bs = bdrv_open_inherit(NULL, reference, qdict, 0, NULL, NULL, 0, false, ++ errp); + obj = NULL; + qobject_unref(obj); + visit_free(v); +@@ -3775,7 +3794,7 @@ static BlockDriverState *bdrv_open_inherit(const char *filename, + BlockDriverState *parent, + const BdrvChildClass *child_class, + BdrvChildRole child_role, +- Error **errp) ++ bool parse_filename, Error **errp) + { + int ret; + BlockBackend *file = NULL; +@@ -3819,9 +3838,11 @@ static BlockDriverState *bdrv_open_inherit(const char *filename, + } + + /* json: syntax counts as explicit options, as if in the QDict */ +- parse_json_protocol(options, &filename, &local_err); +- if (local_err) { +- goto fail; ++ if (parse_filename) { ++ parse_json_protocol(options, &filename, &local_err); ++ if (local_err) { ++ goto fail; ++ } + } + + bs->explicit_options = qdict_clone_shallow(options); +@@ -3846,7 +3867,8 @@ static BlockDriverState *bdrv_open_inherit(const char *filename, + parent->open_flags, parent->options); + } + +- ret = bdrv_fill_options(&options, filename, &flags, &local_err); ++ ret = bdrv_fill_options(&options, filename, &flags, parse_filename, ++ &local_err); + if (ret < 0) { + goto fail; + } +@@ -3915,7 +3937,7 @@ static BlockDriverState *bdrv_open_inherit(const char *filename, + + file_bs = bdrv_open_child_bs(filename, options, "file", bs, + &child_of_bds, BDRV_CHILD_IMAGE, +- true, &local_err); ++ true, true, &local_err); + if (local_err) { + goto fail; + } +@@ -4062,7 +4084,7 @@ BlockDriverState *bdrv_open(const char *filename, const char *reference, + GLOBAL_STATE_CODE(); + + return bdrv_open_inherit(filename, reference, options, flags, NULL, +- NULL, 0, errp); ++ NULL, 0, true, errp); + } + + /* Return true if the NULL-terminated @list contains @str */ +diff --git a/block/qcow2.c b/block/qcow2.c +index 4d6666d3ff..c810424feb 100644 +--- a/block/qcow2.c ++++ b/block/qcow2.c +@@ -1614,7 +1614,22 @@ static int coroutine_fn qcow2_do_open(BlockDriverState *bs, QDict *options, + goto fail; + } + +- if (open_data_file) { ++ if (open_data_file && (flags & BDRV_O_NO_IO)) { ++ /* ++ * Don't open the data file for 'qemu-img info' so that it can be used ++ * to verify that an untrusted qcow2 image doesn't refer to external ++ * files. ++ * ++ * Note: This still makes has_data_file() return true. ++ */ ++ if (s->incompatible_features & QCOW2_INCOMPAT_DATA_FILE) { ++ s->data_file = NULL; ++ } else { ++ s->data_file = bs->file; ++ } ++ qdict_extract_subqdict(options, NULL, "data-file."); ++ qdict_del(options, "data-file"); ++ } else if (open_data_file) { + /* Open external data file */ + s->data_file = bdrv_open_child(NULL, options, "data-file", bs, + &child_of_bds, BDRV_CHILD_DATA, +diff --git a/chardev/char-stdio.c b/chardev/char-stdio.c +index 3c648678ab..b960ddd4e4 100644 +--- a/chardev/char-stdio.c ++++ b/chardev/char-stdio.c +@@ -41,6 +41,7 @@ + /* init terminal so that we can grab keys */ + static struct termios oldtty; + static int old_fd0_flags; ++static int old_fd1_flags; + static bool stdio_in_use; + static bool stdio_allow_signal; + static bool stdio_echo_state; +@@ -50,6 +51,8 @@ static void term_exit(void) + if (stdio_in_use) { + tcsetattr(0, TCSANOW, &oldtty); + fcntl(0, F_SETFL, old_fd0_flags); ++ fcntl(1, F_SETFL, old_fd1_flags); ++ stdio_in_use = false; + } + } + +@@ -102,6 +105,7 @@ static void qemu_chr_open_stdio(Chardev *chr, + + stdio_in_use = true; + old_fd0_flags = fcntl(0, F_GETFL); ++ old_fd1_flags = fcntl(1, F_GETFL); + tcgetattr(0, &oldtty); + if (!g_unix_set_fd_nonblocking(0, true, NULL)) { + error_setg_errno(errp, errno, "Failed to set FD nonblocking"); +diff --git a/docs/devel/testing.rst b/docs/devel/testing.rst +index 98c26ecf18..b4c99be195 100644 +--- a/docs/devel/testing.rst ++++ b/docs/devel/testing.rst +@@ -473,6 +473,12 @@ thus some extra preparation steps will be required first + the ``libvirt-ci`` submodule to point to a commit that contains + the ``mappings.yml`` update. + ++For enterprise distros that default to old, end-of-life versions of the ++Python runtime, QEMU uses a separate set of mappings that work with more ++recent versions. These can be found in ``tests/lcitool/mappings.yml``. ++Modifying this file should not be necessary unless the new pre-requisite ++is a Python library or tool. ++ + + Adding new OS distros + ^^^^^^^^^^^^^^^^^^^^^ +diff --git a/hw/display/vga.c b/hw/display/vga.c +index 0cb26a791b..8e2d44bea3 100644 +--- a/hw/display/vga.c ++++ b/hw/display/vga.c +@@ -1746,6 +1746,13 @@ static void vga_draw_blank(VGACommonState *s, int full_update) + if (s->last_scr_width <= 0 || s->last_scr_height <= 0) + return; + ++ if (is_buffer_shared(surface)) { ++ /* unshare buffer, otherwise the blanking corrupts vga vram */ ++ surface = qemu_create_displaysurface(s->last_scr_width, ++ s->last_scr_height); ++ dpy_gfx_replace_surface(s->con, surface); ++ } ++ + w = s->last_scr_width * surface_bytes_per_pixel(surface); + d = surface_data(surface); + for(i = 0; i < s->last_scr_height; i++) { +diff --git a/hw/net/virtio-net.c b/hw/net/virtio-net.c +index b6177a6afe..beadea5bf8 100644 +--- a/hw/net/virtio-net.c ++++ b/hw/net/virtio-net.c +@@ -2646,18 +2646,14 @@ static int32_t virtio_net_flush_tx(VirtIONetQueue *q) + out_sg = elem->out_sg; + if (out_num < 1) { + virtio_error(vdev, "virtio-net header not in first element"); +- virtqueue_detach_element(q->tx_vq, elem, 0); +- g_free(elem); +- return -EINVAL; ++ goto detach; + } + + if (n->has_vnet_hdr) { + if (iov_to_buf(out_sg, out_num, 0, &vhdr, n->guest_hdr_len) < + n->guest_hdr_len) { + virtio_error(vdev, "virtio-net header incorrect"); +- virtqueue_detach_element(q->tx_vq, elem, 0); +- g_free(elem); +- return -EINVAL; ++ goto detach; + } + if (n->needs_vnet_hdr_swap) { + virtio_net_hdr_swap(vdev, (void *) &vhdr); +@@ -2688,6 +2684,11 @@ static int32_t virtio_net_flush_tx(VirtIONetQueue *q) + n->guest_hdr_len, -1); + out_num = sg_num; + out_sg = sg; ++ ++ if (out_num < 1) { ++ virtio_error(vdev, "virtio-net nothing to send"); ++ goto detach; ++ } + } + + ret = qemu_sendv_packet_async(qemu_get_subqueue(n->nic, queue_index), +@@ -2708,6 +2709,11 @@ drop: + } + } + return num_packets; ++ ++detach: ++ virtqueue_detach_element(q->tx_vq, elem, 0); ++ g_free(elem); ++ return -EINVAL; + } + + static void virtio_net_tx_timer(void *opaque); +diff --git a/hw/virtio/virtio.c b/hw/virtio/virtio.c +index 4a35d7cb0c..1227e3d692 100644 +--- a/hw/virtio/virtio.c ++++ b/hw/virtio/virtio.c +@@ -732,7 +732,6 @@ static void vring_packed_event_read(VirtIODevice *vdev, + /* Make sure flags is seen before off_wrap */ + smp_rmb(); + e->off_wrap = virtio_lduw_phys_cached(vdev, cache, off_off); +- virtio_tswap16s(vdev, &e->flags); + } + + static void vring_packed_off_wrap_write(VirtIODevice *vdev, +diff --git a/linux-user/syscall.c b/linux-user/syscall.c +index 74240f99ad..53c46ae951 100644 +--- a/linux-user/syscall.c ++++ b/linux-user/syscall.c +@@ -7228,11 +7228,17 @@ static inline int tswapid(int id) + #else + #define __NR_sys_setresgid __NR_setresgid + #endif ++#ifdef __NR_setgroups32 ++#define __NR_sys_setgroups __NR_setgroups32 ++#else ++#define __NR_sys_setgroups __NR_setgroups ++#endif + + _syscall1(int, sys_setuid, uid_t, uid) + _syscall1(int, sys_setgid, gid_t, gid) + _syscall3(int, sys_setresuid, uid_t, ruid, uid_t, euid, uid_t, suid) + _syscall3(int, sys_setresgid, gid_t, rgid, gid_t, egid, gid_t, sgid) ++_syscall2(int, sys_setgroups, int, size, gid_t *, grouplist) + + void syscall_init(void) + { +@@ -11453,7 +11459,7 @@ static abi_long do_syscall1(CPUArchState *cpu_env, int num, abi_long arg1, + unlock_user(target_grouplist, arg2, + gidsetsize * sizeof(target_id)); + } +- return get_errno(setgroups(gidsetsize, grouplist)); ++ return get_errno(sys_setgroups(gidsetsize, grouplist)); + } + case TARGET_NR_fchown: + return get_errno(fchown(arg1, low2highuid(arg2), low2highgid(arg3))); +@@ -11789,7 +11795,7 @@ static abi_long do_syscall1(CPUArchState *cpu_env, int num, abi_long arg1, + } + unlock_user(target_grouplist, arg2, 0); + } +- return get_errno(setgroups(gidsetsize, grouplist)); ++ return get_errno(sys_setgroups(gidsetsize, grouplist)); + } + #endif + #ifdef TARGET_NR_fchown32 +diff --git a/target/arm/vec_helper.c b/target/arm/vec_helper.c +index f59d3b26ea..859366e264 100644 +--- a/target/arm/vec_helper.c ++++ b/target/arm/vec_helper.c +@@ -842,7 +842,7 @@ void HELPER(gvec_fcmlah_idx)(void *vd, void *vn, void *vm, void *va, + intptr_t index = extract32(desc, SIMD_DATA_SHIFT + 2, 2); + uint32_t neg_real = flip ^ neg_imag; + intptr_t elements = opr_sz / sizeof(float16); +- intptr_t eltspersegment = 16 / sizeof(float16); ++ intptr_t eltspersegment = MIN(16 / sizeof(float16), elements); + intptr_t i, j; + + /* Shift boolean to the sign bit so we can xor to negate. */ +@@ -904,7 +904,7 @@ void HELPER(gvec_fcmlas_idx)(void *vd, void *vn, void *vm, void *va, + intptr_t index = extract32(desc, SIMD_DATA_SHIFT + 2, 2); + uint32_t neg_real = flip ^ neg_imag; + intptr_t elements = opr_sz / sizeof(float32); +- intptr_t eltspersegment = 16 / sizeof(float32); ++ intptr_t eltspersegment = MIN(16 / sizeof(float32), elements); + intptr_t i, j; + + /* Shift boolean to the sign bit so we can xor to negate. */ +diff --git a/target/i386/cpu.c b/target/i386/cpu.c +index 52a3020032..9c3e64c54b 100644 +--- a/target/i386/cpu.c ++++ b/target/i386/cpu.c +@@ -5297,10 +5297,8 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, + int host_vcpus_per_cache = 1 + ((*eax & 0x3FFC000) >> 14); + int vcpus_per_socket = env->nr_dies * cs->nr_cores * + cs->nr_threads; +- if (cs->nr_cores > 1) { +- *eax &= ~0xFC000000; +- *eax |= (pow2ceil(cs->nr_cores) - 1) << 26; +- } ++ *eax &= ~0xFC000000; ++ *eax |= (pow2ceil(cs->nr_cores) - 1) << 26; + if (host_vcpus_per_cache > vcpus_per_socket) { + *eax &= ~0x3FFC000; + *eax |= (pow2ceil(vcpus_per_socket) - 1) << 14; +diff --git a/target/i386/tcg/translate.c b/target/i386/tcg/translate.c +index 417bc26e8f..8eb6a974e5 100644 +--- a/target/i386/tcg/translate.c ++++ b/target/i386/tcg/translate.c +@@ -2696,7 +2696,7 @@ static void gen_enter(DisasContext *s, int esp_addend, int level) + } + + /* Copy the FrameTemp value to EBP. */ +- gen_op_mov_reg_v(s, a_ot, R_EBP, s->T1); ++ gen_op_mov_reg_v(s, d_ot, R_EBP, s->T1); + + /* Compute the final value of ESP. */ + tcg_gen_subi_tl(s->T1, s->T1, esp_addend + size * level); +diff --git a/tcg/loongarch64/tcg-target.c.inc b/tcg/loongarch64/tcg-target.c.inc +index d326e28740..f1934b6d7b 100644 +--- a/tcg/loongarch64/tcg-target.c.inc ++++ b/tcg/loongarch64/tcg-target.c.inc +@@ -332,8 +332,7 @@ static void tcg_out_movi(TCGContext *s, TCGType type, TCGReg rd, + * back to the slow path. + */ + +- intptr_t pc_offset; +- tcg_target_long val_lo, val_hi, pc_hi, offset_hi; ++ intptr_t src_rx, pc_offset; + tcg_target_long hi32, hi52; + bool rd_high_bits_are_ones; + +@@ -344,24 +343,23 @@ static void tcg_out_movi(TCGContext *s, TCGType type, TCGReg rd, + } + + /* PC-relative cases. */ +- pc_offset = tcg_pcrel_diff(s, (void *)val); +- if (pc_offset == sextreg(pc_offset, 0, 22) && (pc_offset & 3) == 0) { +- /* Single pcaddu2i. */ +- tcg_out_opc_pcaddu2i(s, rd, pc_offset >> 2); +- return; ++ src_rx = (intptr_t)tcg_splitwx_to_rx(s->code_ptr); ++ if ((val & 3) == 0) { ++ pc_offset = val - src_rx; ++ if (pc_offset == sextreg(pc_offset, 0, 22)) { ++ /* Single pcaddu2i. */ ++ tcg_out_opc_pcaddu2i(s, rd, pc_offset >> 2); ++ return; ++ } + } + +- if (pc_offset == (int32_t)pc_offset) { +- /* Offset within 32 bits; load with pcalau12i + ori. */ +- val_lo = sextreg(val, 0, 12); +- val_hi = val >> 12; +- pc_hi = (val - pc_offset) >> 12; +- offset_hi = val_hi - pc_hi; +- +- tcg_debug_assert(offset_hi == sextreg(offset_hi, 0, 20)); +- tcg_out_opc_pcalau12i(s, rd, offset_hi); ++ pc_offset = (val >> 12) - (src_rx >> 12); ++ if (pc_offset == sextreg(pc_offset, 0, 20)) { ++ /* Load with pcalau12i + ori. */ ++ tcg_target_long val_lo = val & 0xfff; ++ tcg_out_opc_pcalau12i(s, rd, pc_offset); + if (val_lo != 0) { +- tcg_out_opc_ori(s, rd, rd, val_lo & 0xfff); ++ tcg_out_opc_ori(s, rd, rd, val_lo); + } + return; + } +diff --git a/tests/docker/dockerfiles/centos8.docker b/tests/docker/dockerfiles/centos9.docker +similarity index 91% +rename from tests/docker/dockerfiles/centos8.docker +rename to tests/docker/dockerfiles/centos9.docker +index 1f70d41aeb..62c4896191 100644 +--- a/tests/docker/dockerfiles/centos8.docker ++++ b/tests/docker/dockerfiles/centos9.docker +@@ -1,15 +1,14 @@ + # THIS FILE WAS AUTO-GENERATED + # +-# $ lcitool dockerfile --layers all centos-stream-8 qemu ++# $ lcitool dockerfile --layers all centos-stream-9 qemu + # + # https://gitlab.com/libvirt/libvirt-ci + +-FROM quay.io/centos/centos:stream8 ++FROM quay.io/centos/centos:stream9 + + RUN dnf distro-sync -y && \ + dnf install 'dnf-command(config-manager)' -y && \ +- dnf config-manager --set-enabled -y powertools && \ +- dnf install -y centos-release-advanced-virtualization && \ ++ dnf config-manager --set-enabled -y crb && \ + dnf install -y epel-release && \ + dnf install -y epel-next-release && \ + dnf install -y \ +@@ -43,7 +42,6 @@ RUN dnf distro-sync -y && \ + glib2-static \ + glibc-langpack-en \ + glibc-static \ +- glusterfs-api-devel \ + gnutls-devel \ + gtk3-devel \ + hostname \ +@@ -102,19 +100,18 @@ RUN dnf distro-sync -y && \ + python3-pip \ + python3-sphinx \ + python3-sphinx_rtd_theme \ ++ python3-tomli \ + rdma-core-devel \ + rpm \ + sed \ + snappy-devel \ + spice-protocol \ +- spice-server-devel \ + systemd-devel \ + systemtap-sdt-devel \ + tar \ + texinfo \ + usbredir-devel \ + util-linux \ +- virglrenderer-devel \ + vte291-devel \ + which \ + xfsprogs-devel \ +diff --git a/tests/docker/dockerfiles/fedora-win32-cross.docker b/tests/docker/dockerfiles/fedora-win32-cross.docker +index 75383ba185..cc5d1ac4be 100644 +--- a/tests/docker/dockerfiles/fedora-win32-cross.docker ++++ b/tests/docker/dockerfiles/fedora-win32-cross.docker +@@ -1,10 +1,10 @@ + # THIS FILE WAS AUTO-GENERATED + # +-# $ lcitool dockerfile --layers all --cross mingw32 fedora-35 qemu ++# $ lcitool dockerfile --layers all --cross mingw32 fedora-37 qemu + # + # https://gitlab.com/libvirt/libvirt-ci + +-FROM registry.fedoraproject.org/fedora:35 ++FROM registry.fedoraproject.org/fedora:37 + + RUN dnf install -y nosync && \ + echo -e '#!/bin/sh\n\ +diff --git a/tests/docker/dockerfiles/fedora-win64-cross.docker b/tests/docker/dockerfiles/fedora-win64-cross.docker +index 98c03dc13b..cabbf4edfc 100644 +--- a/tests/docker/dockerfiles/fedora-win64-cross.docker ++++ b/tests/docker/dockerfiles/fedora-win64-cross.docker +@@ -1,10 +1,10 @@ + # THIS FILE WAS AUTO-GENERATED + # +-# $ lcitool dockerfile --layers all --cross mingw64 fedora-35 qemu ++# $ lcitool dockerfile --layers all --cross mingw64 fedora-37 qemu + # + # https://gitlab.com/libvirt/libvirt-ci + +-FROM registry.fedoraproject.org/fedora:35 ++FROM registry.fedoraproject.org/fedora:37 + + RUN dnf install -y nosync && \ + echo -e '#!/bin/sh\n\ +diff --git a/tests/docker/dockerfiles/fedora.docker b/tests/docker/dockerfiles/fedora.docker +index d200c7fc10..f44b005000 100644 +--- a/tests/docker/dockerfiles/fedora.docker ++++ b/tests/docker/dockerfiles/fedora.docker +@@ -1,10 +1,10 @@ + # THIS FILE WAS AUTO-GENERATED + # +-# $ lcitool dockerfile --layers all fedora-35 qemu ++# $ lcitool dockerfile --layers all fedora-37 qemu + # + # https://gitlab.com/libvirt/libvirt-ci + +-FROM registry.fedoraproject.org/fedora:35 ++FROM registry.fedoraproject.org/fedora:37 + + RUN dnf install -y nosync && \ + echo -e '#!/bin/sh\n\ +diff --git a/tests/docker/dockerfiles/opensuse-leap.docker b/tests/docker/dockerfiles/opensuse-leap.docker +index 4361b01464..4f1191dc05 100644 +--- a/tests/docker/dockerfiles/opensuse-leap.docker ++++ b/tests/docker/dockerfiles/opensuse-leap.docker +@@ -90,16 +90,9 @@ RUN zypper update -y && \ + pcre-devel-static \ + perl-base \ + pkgconfig \ +- python3-Pillow \ +- python3-PyYAML \ +- python3-Sphinx \ +- python3-base \ +- python3-numpy \ +- python3-opencv \ +- python3-pip \ +- python3-setuptools \ +- python3-sphinx_rtd_theme \ +- python3-wheel \ ++ python39-base \ ++ python39-pip \ ++ python39-setuptools \ + rdma-core-devel \ + rpm \ + sed \ +@@ -131,10 +124,15 @@ RUN zypper update -y && \ + ln -s /usr/bin/ccache /usr/libexec/ccache-wrappers/g++ && \ + ln -s /usr/bin/ccache /usr/libexec/ccache-wrappers/gcc + +-RUN /usr/bin/pip3 install meson==0.56.0 ++RUN /usr/bin/pip3.9 install \ ++ PyYAML \ ++ meson==0.63.2 \ ++ pillow \ ++ sphinx \ ++ sphinx-rtd-theme + + ENV CCACHE_WRAPPERSDIR "/usr/libexec/ccache-wrappers" + ENV LANG "en_US.UTF-8" + ENV MAKE "/usr/bin/make" + ENV NINJA "/usr/bin/ninja" +-ENV PYTHON "/usr/bin/python3" ++ENV PYTHON "/usr/bin/python3.9" +diff --git a/tests/docker/dockerfiles/ubuntu2004.docker b/tests/docker/dockerfiles/ubuntu2004.docker +index 9417bca2fa..39c744eba9 100644 +--- a/tests/docker/dockerfiles/ubuntu2004.docker ++++ b/tests/docker/dockerfiles/ubuntu2004.docker +@@ -140,7 +140,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ + ln -s /usr/bin/ccache /usr/libexec/ccache-wrappers/g++ && \ + ln -s /usr/bin/ccache /usr/libexec/ccache-wrappers/gcc + +-RUN /usr/bin/pip3 install meson==0.56.0 ++RUN /usr/bin/pip3 install meson==0.63.2 + + ENV CCACHE_WRAPPERSDIR "/usr/libexec/ccache-wrappers" + ENV LANG "en_US.UTF-8" +Submodule tests/lcitool/libvirt-ci e3eb28cf2e..319a534c22: +diff --git a/tests/lcitool/libvirt-ci/.gitlab-ci.yml b/tests/lcitool/libvirt-ci/.gitlab-ci.yml +index 21ccc2e6..1c9c8b70 100644 +--- a/tests/lcitool/libvirt-ci/.gitlab-ci.yml ++++ b/tests/lcitool/libvirt-ci/.gitlab-ci.yml +@@ -148,9 +148,10 @@ unittests: + needs: [] + before_script: + - apk update +- - apk add git ++ - apk add git ansible + script: +- - pip3 install setuptools pytest pyyaml ++ - pip3 install setuptools pytest ++ - pip3 install -r requirements.txt ansible-runner + - python3 -m pytest --verbose + + x86_64-check-almalinux-8: +@@ -201,15 +202,15 @@ x86_64-check-debian-sid: + variables: + NAME: debian-sid + +-x86_64-check-fedora-35: ++x86_64-check-fedora-36: + extends: .check_container_template + variables: +- NAME: fedora-35 ++ NAME: fedora-36 + +-x86_64-check-fedora-36: ++x86_64-check-fedora-37: + extends: .check_container_template + variables: +- NAME: fedora-36 ++ NAME: fedora-37 + + x86_64-check-fedora-rawhide: + extends: .check_container_template +diff --git a/tests/lcitool/libvirt-ci/docs/installation.rst b/tests/lcitool/libvirt-ci/docs/installation.rst +index 7a7f53e8..7134195d 100644 +--- a/tests/lcitool/libvirt-ci/docs/installation.rst ++++ b/tests/lcitool/libvirt-ci/docs/installation.rst +@@ -58,6 +58,8 @@ in the previous section) + + $ pip3 install --user -r test-requirements.txt + ++In addition, the ``ansible-inventory`` executable needs to be installed. ++ + Installing lcitool + ------------------ + +diff --git a/tests/lcitool/libvirt-ci/examples/manifest.yml b/tests/lcitool/libvirt-ci/examples/manifest.yml +index 7bd9b4b1..2acfbd35 100644 +--- a/tests/lcitool/libvirt-ci/examples/manifest.yml ++++ b/tests/lcitool/libvirt-ci/examples/manifest.yml +@@ -135,7 +135,7 @@ targets: + - arch: ppc64le + - arch: s390x + +- fedora-35: x86_64 ++ fedora-37: x86_64 + + fedora-rawhide: + jobs: +diff --git a/tests/lcitool/libvirt-ci/lcitool/ansible/playbooks/build/jobs/defaults.yml b/tests/lcitool/libvirt-ci/lcitool/ansible/playbooks/build/jobs/defaults.yml +index 25099c74..70691565 100644 +--- a/tests/lcitool/libvirt-ci/lcitool/ansible/playbooks/build/jobs/defaults.yml ++++ b/tests/lcitool/libvirt-ci/lcitool/ansible/playbooks/build/jobs/defaults.yml +@@ -7,8 +7,8 @@ all_machines: + - debian-10 + - debian-11 + - debian-sid +- - fedora-35 + - fedora-36 ++ - fedora-37 + - fedora-rawhide + - freebsd-12 + - freebsd-13 +@@ -22,8 +22,8 @@ rpm_machines: + - almalinux-9 + - centos-stream-8 + - centos-stream-9 +- - fedora-35 + - fedora-36 ++ - fedora-37 + - fedora-rawhide + global_env: | + . ~/lcitool_build_env +diff --git a/tests/lcitool/libvirt-ci/lcitool/ansible/playbooks/build/projects/libvirt-dbus.yml b/tests/lcitool/libvirt-ci/lcitool/ansible/playbooks/build/projects/libvirt-dbus.yml +index 79f81abb..273be6a8 100644 +--- a/tests/lcitool/libvirt-ci/lcitool/ansible/playbooks/build/projects/libvirt-dbus.yml ++++ b/tests/lcitool/libvirt-ci/lcitool/ansible/playbooks/build/projects/libvirt-dbus.yml +@@ -19,8 +19,8 @@ + - debian-10 + - debian-11 + - debian-sid +- - fedora-35 + - fedora-36 ++ - fedora-37 + - fedora-rawhide + - opensuse-leap-153 + - opensuse-tumbleweed +diff --git a/tests/lcitool/libvirt-ci/lcitool/ansible/playbooks/build/projects/libvirt-sandbox.yml b/tests/lcitool/libvirt-ci/lcitool/ansible/playbooks/build/projects/libvirt-sandbox.yml +index bcf62b91..231c65bf 100644 +--- a/tests/lcitool/libvirt-ci/lcitool/ansible/playbooks/build/projects/libvirt-sandbox.yml ++++ b/tests/lcitool/libvirt-ci/lcitool/ansible/playbooks/build/projects/libvirt-sandbox.yml +@@ -6,8 +6,8 @@ + - debian-10 + - debian-11 + - debian-sid +- - fedora-35 + - fedora-36 ++ - fedora-37 + - fedora-rawhide + - opensuse-leap-153 + - opensuse-tumbleweed +@@ -25,6 +25,6 @@ + - import_tasks: 'jobs/autotools-rpm-job.yml' + vars: + machines: +- - fedora-35 + - fedora-36 ++ - fedora-37 + - fedora-rawhide +diff --git a/tests/lcitool/libvirt-ci/lcitool/ansible/playbooks/build/projects/libvirt-tck.yml b/tests/lcitool/libvirt-ci/lcitool/ansible/playbooks/build/projects/libvirt-tck.yml +index e4beddd7..6451b440 100644 +--- a/tests/lcitool/libvirt-ci/lcitool/ansible/playbooks/build/projects/libvirt-tck.yml ++++ b/tests/lcitool/libvirt-ci/lcitool/ansible/playbooks/build/projects/libvirt-tck.yml +@@ -14,6 +14,6 @@ + - import_tasks: 'jobs/perl-modulebuild-rpm-job.yml' + vars: + machines: +- - fedora-35 + - fedora-36 ++ - fedora-37 + - fedora-rawhide +diff --git a/tests/lcitool/libvirt-ci/lcitool/ansible/playbooks/build/projects/libvirt.yml b/tests/lcitool/libvirt-ci/lcitool/ansible/playbooks/build/projects/libvirt.yml +index 1b120441..7ec9d975 100644 +--- a/tests/lcitool/libvirt-ci/lcitool/ansible/playbooks/build/projects/libvirt.yml ++++ b/tests/lcitool/libvirt-ci/lcitool/ansible/playbooks/build/projects/libvirt.yml +@@ -19,8 +19,8 @@ + - debian-10 + - debian-11 + - debian-sid +- - fedora-35 + - fedora-36 ++ - fedora-37 + - fedora-rawhide + - opensuse-leap-153 + - opensuse-tumbleweed +diff --git a/tests/lcitool/libvirt-ci/lcitool/ansible/playbooks/build/projects/virt-viewer.yml b/tests/lcitool/libvirt-ci/lcitool/ansible/playbooks/build/projects/virt-viewer.yml +index 209885a1..eda0c0e6 100644 +--- a/tests/lcitool/libvirt-ci/lcitool/ansible/playbooks/build/projects/virt-viewer.yml ++++ b/tests/lcitool/libvirt-ci/lcitool/ansible/playbooks/build/projects/virt-viewer.yml +@@ -13,6 +13,6 @@ + # The spec file for virt-viewer requires a very recent version + # of spice-gtk, so we have to skip this job on older distros + machines: +- - fedora-35 + - fedora-36 ++ - fedora-37 + - fedora-rawhide +diff --git a/tests/lcitool/libvirt-ci/lcitool/application.py b/tests/lcitool/libvirt-ci/lcitool/application.py +index 97cb0884..090c1b07 100644 +--- a/tests/lcitool/libvirt-ci/lcitool/application.py ++++ b/tests/lcitool/libvirt-ci/lcitool/application.py +@@ -13,12 +13,13 @@ from pkg_resources import resource_filename + from lcitool import util, LcitoolError + from lcitool.config import Config + from lcitool.inventory import Inventory +-from lcitool.package import package_names_by_type ++from lcitool.packages import Packages + from lcitool.projects import Projects ++from lcitool.targets import Targets, BuildTarget + from lcitool.formatters import DockerfileFormatter, ShellVariablesFormatter, JSONVariablesFormatter, ShellBuildEnvFormatter +-from lcitool.singleton import Singleton + from lcitool.manifest import Manifest + ++ + log = logging.getLogger(__name__) + + +@@ -44,7 +45,7 @@ class ApplicationError(LcitoolError): + super().__init__(message, "Application") + + +-class Application(metaclass=Singleton): ++class Application: + def __init__(self): + # make sure the lcitool cache dir exists + cache_dir_path = util.get_cache_dir() +@@ -68,11 +69,13 @@ class Application(metaclass=Singleton): + + base = resource_filename(__name__, "ansible") + config = Config() +- inventory = Inventory() ++ targets = Targets() ++ inventory = Inventory(targets, config) ++ packages = Packages() + projects = Projects() + + hosts_expanded = inventory.expand_hosts(hosts_pattern) +- projects_expanded = Projects().expand_names(projects_pattern) ++ projects_expanded = projects.expand_names(projects_pattern) + + if git_revision is not None: + tokens = git_revision.split("/") +@@ -102,38 +105,16 @@ class Application(metaclass=Singleton): + ansible_runner = AnsibleWrapper() + + for host in hosts_expanded: +- facts = inventory.host_facts[host] +- target = facts["target"] +- + # packages are evaluated on a target level and since the + # host->target mapping is N-1, we can skip hosts belonging to a + # target group for which we already evaluated the package list +- if target in group_vars: ++ target_name = inventory.get_host_target_name(host) ++ if target_name in group_vars: + continue + +- # resolve the package mappings to actual package names +- internal_wanted_projects = ["base", "developer", "vm"] +- if config.values["install"]["cloud_init"]: +- internal_wanted_projects.append("cloud-init") +- +- selected_projects = internal_wanted_projects + projects_expanded +- pkgs_install = projects.get_packages(selected_projects, facts) +- pkgs_early_install = projects.get_packages(["early_install"], facts) +- pkgs_remove = projects.get_packages(["unwanted"], facts) +- package_names = package_names_by_type(pkgs_install) +- package_names_remove = package_names_by_type(pkgs_remove) +- package_names_early_install = package_names_by_type(pkgs_early_install) +- +- # merge the package lists to the Ansible group vars +- packages = {} +- packages["packages"] = package_names["native"] +- packages["pypi_packages"] = package_names["pypi"] +- packages["cpan_packages"] = package_names["cpan"] +- packages["unwanted_packages"] = package_names_remove["native"] +- packages["early_install_packages"] = package_names_early_install["native"] +- +- group_vars[target] = packages +- group_vars[target].update(inventory.target_facts[target]) ++ target = BuildTarget(targets, packages, target_name) ++ group_vars[target_name] = inventory.get_group_vars(target, projects, ++ projects_expanded) + + ansible_runner.prepare_env(playbookdir=playbook_base, + inventories=[inventory.ansible_inventory], +@@ -149,17 +130,19 @@ class Application(metaclass=Singleton): + def _action_hosts(self, args): + self._entrypoint_debug(args) + +- inventory = Inventory() ++ config = Config() ++ targets = Targets() ++ inventory = Inventory(targets, config) + for host in sorted(inventory.hosts): + print(host) + + def _action_targets(self, args): + self._entrypoint_debug(args) + +- inventory = Inventory() +- for target in sorted(inventory.targets): ++ targets = Targets() ++ for target in sorted(targets.targets): + if args.containerized: +- facts = inventory.target_facts[target] ++ facts = targets.target_facts[target] + + if facts["packaging"]["format"] not in ["apk", "deb", "rpm"]: + continue +@@ -180,7 +163,9 @@ class Application(metaclass=Singleton): + self._entrypoint_debug(args) + + facts = {} +- inventory = Inventory() ++ config = Config() ++ targets = Targets() ++ inventory = Inventory(targets, config) + host = args.host + target = args.target + +@@ -193,10 +178,10 @@ class Application(metaclass=Singleton): + "to your inventory or use '--target <target>'" + ) + +- if target not in inventory.targets: ++ if target not in targets.targets: + raise ApplicationError(f"Unsupported target OS '{target}'") + +- facts = inventory.target_facts[target] ++ facts = targets.target_facts[target] + else: + if target is not None: + raise ApplicationError( +@@ -236,16 +221,19 @@ class Application(metaclass=Singleton): + def _action_variables(self, args): + self._entrypoint_debug(args) + +- projects_expanded = Projects().expand_names(args.projects) ++ targets = Targets() ++ packages = Packages() ++ projects = Projects() ++ projects_expanded = projects.expand_names(args.projects) + + if args.format == "shell": +- formatter = ShellVariablesFormatter() ++ formatter = ShellVariablesFormatter(projects) + else: +- formatter = JSONVariablesFormatter() ++ formatter = JSONVariablesFormatter(projects) + +- variables = formatter.format(args.target, +- projects_expanded, +- args.cross_arch) ++ target = BuildTarget(targets, packages, args.target, args.cross_arch) ++ variables = formatter.format(target, ++ projects_expanded) + + # No comments in json ! + if args.format != "json": +@@ -262,12 +250,16 @@ class Application(metaclass=Singleton): + def _action_dockerfile(self, args): + self._entrypoint_debug(args) + +- projects_expanded = Projects().expand_names(args.projects) ++ targets = Targets() ++ packages = Packages() ++ projects = Projects() ++ projects_expanded = projects.expand_names(args.projects) ++ target = BuildTarget(targets, packages, args.target, args.cross_arch) + +- dockerfile = DockerfileFormatter(args.base, +- args.layers).format(args.target, +- projects_expanded, +- args.cross_arch) ++ dockerfile = DockerfileFormatter(projects, ++ args.base, ++ args.layers).format(target, ++ projects_expanded) + + cliargv = [args.action] + if args.base is not None: +@@ -283,11 +275,14 @@ class Application(metaclass=Singleton): + def _action_buildenvscript(self, args): + self._entrypoint_debug(args) + +- projects_expanded = Projects().expand_names(args.projects) ++ targets = Targets() ++ packages = Packages() ++ projects = Projects() ++ projects_expanded = projects.expand_names(args.projects) ++ target = BuildTarget(targets, packages, args.target, args.cross_arch) + +- buildenvscript = ShellBuildEnvFormatter().format(args.target, +- projects_expanded, +- args.cross_arch) ++ buildenvscript = ShellBuildEnvFormatter(projects).format(target, ++ projects_expanded) + + cliargv = [args.action] + if args.cross_arch: +@@ -302,7 +297,10 @@ class Application(metaclass=Singleton): + if args.base_dir is not None: + base_path = Path(args.base_dir) + ci_path = Path(args.ci_dir) +- manifest = Manifest(args.manifest, args.quiet, ci_path, base_path) ++ targets = Targets() ++ packages = Packages() ++ projects = Projects() ++ manifest = Manifest(targets, packages, projects, args.manifest, args.quiet, ci_path, base_path) + manifest.generate(args.dry_run) + + def run(self, args): +diff --git a/tests/lcitool/libvirt-ci/lcitool/config.py b/tests/lcitool/libvirt-ci/lcitool/config.py +index b83899b4..d42cda8c 100644 +--- a/tests/lcitool/libvirt-ci/lcitool/config.py ++++ b/tests/lcitool/libvirt-ci/lcitool/config.py +@@ -12,7 +12,6 @@ from pathlib import Path + from pkg_resources import resource_filename + + from lcitool import util, LcitoolError +-from lcitool.singleton import Singleton + + log = logging.getLogger(__name__) + +@@ -47,7 +46,7 @@ class ValidationError(ConfigError): + super().__init__(message) + + +-class Config(metaclass=Singleton): ++class Config: + + @property + def values(self): +@@ -60,11 +59,15 @@ class Config(metaclass=Singleton): + + def __init__(self): + self._values = None ++ self._config_file_dir = util.get_config_dir() + self._config_file_paths = [ +- Path(util.get_config_dir(), fname) for fname in ++ self.get_config_path(fname) for fname in + ["config.yml", "config.yaml"] + ] + ++ def get_config_path(self, *args): ++ return Path(self._config_file_dir, *args) ++ + def _load_config(self): + # Load the template config containing the defaults first, this must + # always succeed. +@@ -149,7 +152,7 @@ class Config(metaclass=Singleton): + + def _validate(self): + if self._values is None: +- paths = ", ".join([str(p) for p in self._config_file_paths()]) ++ paths = ", ".join([str(p) for p in self._config_file_paths]) + raise ValidationError(f"Missing or empty configuration file, tried {paths}") + + self._validate_section("install", ["root_password"]) +diff --git a/tests/lcitool/libvirt-ci/lcitool/configs/kickstart.cfg b/tests/lcitool/libvirt-ci/lcitool/configs/kickstart.cfg +index cc3e103f..51db9963 100644 +--- a/tests/lcitool/libvirt-ci/lcitool/configs/kickstart.cfg ++++ b/tests/lcitool/libvirt-ci/lcitool/configs/kickstart.cfg +@@ -38,7 +38,7 @@ rootpw --plaintext root + # remaining space to the root partition + ignoredisk --only-use=vda + zerombr +-clearpart --none ++clearpart --drives=vda --all --disklabel=msdos + part / --fstype=ext4 --size=2048 --grow + part swap --fstype=swap --size=256 + +diff --git a/tests/lcitool/libvirt-ci/lcitool/facts/mappings.yml b/tests/lcitool/libvirt-ci/lcitool/facts/mappings.yml +index 06c8032a..c54241e4 100644 +--- a/tests/lcitool/libvirt-ci/lcitool/facts/mappings.yml ++++ b/tests/lcitool/libvirt-ci/lcitool/facts/mappings.yml +@@ -1291,7 +1291,6 @@ mappings: + CentOSStream8: netcf-devel + Debian10: libnetcf-dev + Debian11: libnetcf-dev +- Fedora35: netcf-devel + Ubuntu1804: libnetcf-dev + Ubuntu2004: libnetcf-dev + cross-policy-default: skip +@@ -1639,7 +1638,6 @@ mappings: + + publican: + deb: publican +- Fedora35: publican + Fedora36: publican + + pulseaudio: +@@ -2203,8 +2201,9 @@ pypi_mappings: + python3-dbus: + MacOS: dbus-python + ++ # higher versions are rejected by python3-sphinx-rtd-theme + python3-docutils: +- default: docutils ++ default: docutils<0.18 + + python3-gobject: + MacOS: PyGObject +diff --git a/tests/lcitool/libvirt-ci/lcitool/facts/projects/libvirt-php.yml b/tests/lcitool/libvirt-ci/lcitool/facts/projects/libvirt-php.yml +index f10c6894..4dcfde49 100644 +--- a/tests/lcitool/libvirt-ci/lcitool/facts/projects/libvirt-php.yml ++++ b/tests/lcitool/libvirt-ci/lcitool/facts/projects/libvirt-php.yml +@@ -10,7 +10,6 @@ packages: + - libxml2 + - make + - php +- - php-imagick + - pkg-config + - rpmbuild + - xmllint +diff --git a/tests/lcitool/libvirt-ci/lcitool/facts/projects/qemu.yml b/tests/lcitool/libvirt-ci/lcitool/facts/projects/qemu.yml +index 425459c5..117307ee 100644 +--- a/tests/lcitool/libvirt-ci/lcitool/facts/projects/qemu.yml ++++ b/tests/lcitool/libvirt-ci/lcitool/facts/projects/qemu.yml +@@ -89,6 +89,7 @@ packages: + - pulseaudio + - python3 + - python3-PyYAML ++ - python3-docutils + - python3-numpy + - python3-opencv + - python3-pillow +diff --git a/tests/lcitool/libvirt-ci/lcitool/facts/targets/fedora-35.yml b/tests/lcitool/libvirt-ci/lcitool/facts/targets/fedora-37.yml +similarity index 82% +rename from lcitool/facts/targets/fedora-35.yml +rename to lcitool/facts/targets/fedora-37.yml +index 2e8e320c..5513995c 100644 +--- a/tests/lcitool/libvirt-ci/lcitool/facts/targets/fedora-35.yml ++++ b/tests/lcitool/libvirt-ci/lcitool/facts/targets/fedora-37.yml +@@ -1,7 +1,7 @@ + --- + os: + name: 'Fedora' +- version: '35' ++ version: '37' + + packaging: + format: 'rpm' +@@ -21,7 +21,7 @@ ansible_python_package: python3 + ansible_python_interpreter: /usr/bin/python3 + + install: +- url: https://download.fedoraproject.org/pub/fedora/linux/releases/35/Everything/x86_64/os ++ url: https://download.fedoraproject.org/pub/fedora/linux/releases/37/Everything/x86_64/os + + containers: +- base: registry.fedoraproject.org/fedora:35 ++ base: registry.fedoraproject.org/fedora:37 +diff --git a/tests/lcitool/libvirt-ci/lcitool/formatters.py b/tests/lcitool/libvirt-ci/lcitool/formatters.py +index 114499a6..f799b466 100644 +--- a/tests/lcitool/libvirt-ci/lcitool/formatters.py ++++ b/tests/lcitool/libvirt-ci/lcitool/formatters.py +@@ -7,13 +7,12 @@ + import abc + import json + import logging ++import shlex + + from pkg_resources import resource_filename + + from lcitool import util, LcitoolError +-from lcitool.inventory import Inventory +-from lcitool.projects import Projects +-from lcitool.package import package_names_by_type ++from lcitool.packages import package_names_by_type + + + log = logging.getLogger(__name__) +@@ -50,6 +49,9 @@ class Formatter(metaclass=abc.ABCMeta): + This an abstract base class that each formatter must subclass. + """ + ++ def __init__(self, projects): ++ self._projects = projects ++ + @abc.abstractmethod + def format(self): + """ +@@ -70,28 +72,27 @@ class Formatter(metaclass=abc.ABCMeta): + return c.read().rstrip() + + def _generator_build_varmap(self, +- facts, +- selected_projects, +- cross_arch): +- projects = Projects() ++ target, ++ selected_projects): ++ projects = self._projects + + # we need the 'base' internal project here, but packages for internal + # projects are not resolved via the public API, so it requires special + # handling + pkgs = {} +- pkgs.update(projects.internal_projects["base"].get_packages(facts, cross_arch)) ++ pkgs.update(projects.internal["base"].get_packages(target)) + + # we can now load packages for the rest of the projects +- pkgs.update(projects.get_packages(selected_projects, facts, cross_arch)) ++ pkgs.update(projects.get_packages(selected_projects, target)) + package_names = package_names_by_type(pkgs) + + varmap = { +- "packaging_command": facts["packaging"]["command"], +- "paths_ccache": facts["paths"]["ccache"], +- "paths_make": facts["paths"]["make"], +- "paths_ninja": facts["paths"]["ninja"], +- "paths_python": facts["paths"]["python"], +- "paths_pip3": facts["paths"]["pip3"], ++ "packaging_command": target.facts["packaging"]["command"], ++ "paths_ccache": target.facts["paths"]["ccache"], ++ "paths_make": target.facts["paths"]["make"], ++ "paths_ninja": target.facts["paths"]["ninja"], ++ "paths_python": target.facts["paths"]["python"], ++ "paths_pip3": target.facts["paths"]["pip3"], + + "cross_arch": None, + "cross_abi": None, +@@ -104,44 +105,22 @@ class Formatter(metaclass=abc.ABCMeta): + "cpan_pkgs": package_names["cpan"], + } + +- if cross_arch: +- varmap["cross_arch"] = cross_arch +- varmap["cross_abi"] = util.native_arch_to_abi(cross_arch) ++ if target.cross_arch: ++ varmap["cross_arch"] = target.cross_arch ++ varmap["cross_abi"] = util.native_arch_to_abi(target.cross_arch) + +- if facts["packaging"]["format"] == "deb": +- cross_arch_deb = util.native_arch_to_deb_arch(cross_arch) ++ if target.facts["packaging"]["format"] == "deb": ++ cross_arch_deb = util.native_arch_to_deb_arch(target.cross_arch) + varmap["cross_arch_deb"] = cross_arch_deb + + log.debug(f"Generated varmap: {varmap}") + return varmap + +- def _generator_prepare(self, target, selected_projects, cross_arch): +- log.debug(f"Generating varmap for " +- f"target='{target}', " +- f"projects='{selected_projects}', " +- f"cross_arch='{cross_arch}'") +- +- name = self.__class__.__name__.lower() +- +- try: +- facts = Inventory().target_facts[target] +- except KeyError: +- raise FormatterError(f"Invalid target '{target}'") +- +- # We can only generate Dockerfiles for Linux +- if (name == "dockerfileformatter" and +- facts["packaging"]["format"] not in ["apk", "deb", "rpm"]): +- raise FormatterError(f"Target {target} doesn't support this generator") +- +- varmap = self._generator_build_varmap(facts, +- selected_projects, +- cross_arch) +- return facts, cross_arch, varmap +- + + class BuildEnvFormatter(Formatter): + +- def __init__(self, indent=0, pkgcleanup=False, nosync=False): ++ def __init__(self, inventory, indent=0, pkgcleanup=False, nosync=False): ++ super().__init__(inventory) + self._indent = indent + self._pkgcleanup = pkgcleanup + self._nosync = nosync +@@ -151,23 +130,22 @@ class BuildEnvFormatter(Formatter): + return strings[0] + + align = " \\\n" + (" " * (self._indent + len(command + " "))) ++ strings = [shlex.quote(x) for x in strings] + return align[1:] + align.join(strings) + + def _generator_build_varmap(self, +- facts, +- selected_projects, +- cross_arch): +- varmap = super()._generator_build_varmap(facts, +- selected_projects, +- cross_arch) ++ target, ++ selected_projects): ++ varmap = super()._generator_build_varmap(target, ++ selected_projects) + + varmap["nosync"] = "" + if self._nosync: +- if facts["packaging"]["format"] == "deb": ++ if target.facts["packaging"]["format"] == "deb": + varmap["nosync"] = "eatmydata " +- elif facts["packaging"]["format"] == "rpm" and facts["os"]["name"] == "Fedora": ++ elif target.facts["packaging"]["format"] == "rpm" and target.facts["os"]["name"] == "Fedora": + varmap["nosync"] = "nosync " +- elif facts["packaging"]["format"] == "apk": ++ elif target.facts["packaging"]["format"] == "apk": + # TODO: 'libeatmydata' package is present in 'testing' repo + # for Alpine Edge. Once it graduates to 'main' repo we + # should use it here, and see later comment about adding +@@ -176,14 +154,14 @@ class BuildEnvFormatter(Formatter): + pass + + nosync = varmap["nosync"] +- varmap["pkgs"] = self._align(nosync + facts["packaging"]["command"], ++ varmap["pkgs"] = self._align(nosync + target.facts["packaging"]["command"], + varmap["pkgs"]) + + if varmap["cross_pkgs"]: +- varmap["cross_pkgs"] = self._align(nosync + facts["packaging"]["command"], ++ varmap["cross_pkgs"] = self._align(nosync + target.facts["packaging"]["command"], + varmap["cross_pkgs"]) + if varmap["pypi_pkgs"]: +- varmap["pypi_pkgs"] = self._align(nosync + facts["paths"]["pip3"], ++ varmap["pypi_pkgs"] = self._align(nosync + target.facts["paths"]["pip3"], + varmap["pypi_pkgs"]) + if varmap["cpan_pkgs"]: + varmap["cpan_pkgs"] = self._align(nosync + "cpanm", +@@ -191,7 +169,7 @@ class BuildEnvFormatter(Formatter): + + return varmap + +- def _format_commands_ccache(self, cross_arch, varmap): ++ def _format_commands_ccache(self, target, varmap): + commands = [] + compilers = set() + +@@ -213,14 +191,15 @@ class BuildEnvFormatter(Formatter): + ]) + + for compiler in sorted(compilers): +- if cross_arch: ++ if target.cross_arch: + compiler = "{cross_abi}-" + compiler + commands.extend([ + "ln -s {paths_ccache} /usr/libexec/ccache-wrappers/" + compiler, + ]) + return commands + +- def _format_commands_pkglist(self, facts): ++ def _format_commands_pkglist(self, target): ++ facts = target.facts + commands = [] + if facts["packaging"]["format"] == "apk": + commands.extend(["apk list | sort > /packages.txt"]) +@@ -232,7 +211,8 @@ class BuildEnvFormatter(Formatter): + commands.extend(["rpm -qa | sort > /packages.txt"]) + return commands + +- def _format_commands_native(self, facts, cross_arch, varmap): ++ def _format_commands_native(self, target, varmap): ++ facts = target.facts + commands = [] + osname = facts["os"]["name"] + osversion = facts["os"]["version"] +@@ -356,9 +336,9 @@ class BuildEnvFormatter(Formatter): + "{nosync}{packaging_command} clean all -y", + ]) + +- if not cross_arch: +- commands.extend(self._format_commands_pkglist(facts)) +- commands.extend(self._format_commands_ccache(None, varmap)) ++ if not target.cross_arch: ++ commands.extend(self._format_commands_pkglist(target)) ++ commands.extend(self._format_commands_ccache(target, varmap)) + + commands = [c.format(**varmap) for c in commands] + +@@ -386,7 +366,8 @@ class BuildEnvFormatter(Formatter): + + return env + +- def _format_commands_foreign(self, facts, cross_arch, varmap): ++ def _format_commands_foreign(self, target, varmap): ++ facts = target.facts + cross_commands = [] + + if facts["packaging"]["format"] == "deb": +@@ -394,7 +375,7 @@ class BuildEnvFormatter(Formatter): + "export DEBIAN_FRONTEND=noninteractive", + "dpkg --add-architecture {cross_arch_deb}", + ]) +- if cross_arch == "riscv64": ++ if target.cross_arch == "riscv64": + cross_commands.extend([ + "{nosync}{packaging_command} install debian-ports-archive-keyring", + "{nosync}echo 'deb http://ftp.ports.debian.org/debian-ports/ sid main' > /etc/apt/sources.list.d/ports.list", +@@ -420,7 +401,7 @@ class BuildEnvFormatter(Formatter): + "{nosync}{packaging_command} clean all -y", + ]) + +- if not cross_arch.startswith("mingw"): ++ if not target.cross_arch.startswith("mingw"): + cross_commands.extend([ + "mkdir -p /usr/local/share/meson/cross", + "echo \"{cross_meson}\" > /usr/local/share/meson/cross/{cross_abi}", +@@ -429,14 +410,14 @@ class BuildEnvFormatter(Formatter): + cross_meson = self._get_meson_cross(varmap["cross_abi"]) + varmap["cross_meson"] = cross_meson.replace("\n", "\\n\\\n") + +- cross_commands.extend(self._format_commands_pkglist(facts)) +- cross_commands.extend(self._format_commands_ccache(cross_arch, varmap)) ++ cross_commands.extend(self._format_commands_pkglist(target)) ++ cross_commands.extend(self._format_commands_ccache(target, varmap)) + + cross_commands = [c.format(**varmap) for c in cross_commands] + + return cross_commands + +- def _format_env_foreign(self, cross_arch, varmap): ++ def _format_env_foreign(self, target, varmap): + env = {} + env["ABI"] = varmap["cross_abi"] + +@@ -444,7 +425,7 @@ class BuildEnvFormatter(Formatter): + env["CONFIGURE_OPTS"] = "--host=" + varmap["cross_abi"] + + if "meson" in varmap["mappings"]: +- if cross_arch.startswith("mingw"): ++ if target.cross_arch.startswith("mingw"): + env["MESON_OPTS"] = "--cross-file=/usr/share/mingw/toolchain-" + varmap["cross_arch"] + ".meson" + else: + env["MESON_OPTS"] = "--cross-file=" + varmap["cross_abi"] +@@ -454,8 +435,9 @@ class BuildEnvFormatter(Formatter): + + class DockerfileFormatter(BuildEnvFormatter): + +- def __init__(self, base=None, layers="all"): +- super().__init__(indent=len("RUN "), ++ def __init__(self, inventory, base=None, layers="all"): ++ super().__init__(inventory, ++ indent=len("RUN "), + pkgcleanup=True, + nosync=True) + self._base = base +@@ -469,17 +451,17 @@ class DockerfileFormatter(BuildEnvFormatter): + lines.append(f"\nENV {key} \"{val}\"") + return "".join(lines) + +- def _format_section_base(self, facts): ++ def _format_section_base(self, target): + strings = [] + if self._base: + base = self._base + else: +- base = facts["containers"]["base"] ++ base = target.facts["containers"]["base"] + strings.append(f"FROM {base}") + return strings + +- def _format_section_native(self, facts, cross_arch, varmap): +- groups = self._format_commands_native(facts, cross_arch, varmap) ++ def _format_section_native(self, target, varmap): ++ groups = self._format_commands_native(target, varmap) + + strings = [] + for commands in groups: +@@ -489,25 +471,25 @@ class DockerfileFormatter(BuildEnvFormatter): + strings.append(self._format_env(env)) + return strings + +- def _format_section_foreign(self, facts, cross_arch, varmap): +- commands = self._format_commands_foreign(facts, cross_arch, varmap) ++ def _format_section_foreign(self, target, varmap): ++ commands = self._format_commands_foreign(target, varmap) + + strings = ["\nRUN " + " && \\\n ".join(commands)] + +- env = self._format_env_foreign(cross_arch, varmap) ++ env = self._format_env_foreign(target, varmap) + strings.append(self._format_env(env)) + return strings + +- def _format_dockerfile(self, target, project, facts, cross_arch, varmap): ++ def _format_dockerfile(self, target, project, varmap): + strings = [] +- strings.extend(self._format_section_base(facts)) ++ strings.extend(self._format_section_base(target)) + if self._layers in ["all", "native"]: +- strings.extend(self._format_section_native(facts, cross_arch, varmap)) +- if cross_arch and self._layers in ["all", "foreign"]: +- strings.extend(self._format_section_foreign(facts, cross_arch, varmap)) ++ strings.extend(self._format_section_native(target, varmap)) ++ if target.cross_arch and self._layers in ["all", "foreign"]: ++ strings.extend(self._format_section_foreign(target, varmap)) + return strings + +- def format(self, target, selected_projects, cross_arch): ++ def format(self, target, selected_projects): + """ + Generates and formats a Dockerfile. + +@@ -521,17 +503,18 @@ class DockerfileFormatter(BuildEnvFormatter): + """ + + log.debug(f"Generating Dockerfile for projects '{selected_projects}' " +- f"on target '{target}' (cross_arch={cross_arch})") ++ f"on target {target}") ++ ++ # We can only generate Dockerfiles for Linux ++ if (target.facts["packaging"]["format"] not in ["apk", "deb", "rpm"]): ++ raise DockerfileError(f"Target {target} doesn't support this generator") + + try: +- facts, cross_arch, varmap = self._generator_prepare(target, +- selected_projects, +- cross_arch) ++ varmap = self._generator_build_varmap(target, selected_projects) + except FormatterError as ex: + raise DockerfileError(str(ex)) + +- return '\n'.join(self._format_dockerfile(target, selected_projects, +- facts, cross_arch, varmap)) ++ return '\n'.join(self._format_dockerfile(target, selected_projects, varmap)) + + + class VariablesFormatter(Formatter): +@@ -559,7 +542,7 @@ class VariablesFormatter(Formatter): + def _format_variables(varmap): + pass + +- def format(self, target, selected_projects, cross_arch): ++ def format(self, target, selected_projects): + """ + Generates and formats environment variables as KEY=VAL pairs. + +@@ -572,12 +555,10 @@ class VariablesFormatter(Formatter): + """ + + log.debug(f"Generating variables for projects '{selected_projects} on " +- f"target '{target}' (cross_arch={cross_arch})") ++ f"target {target}") + + try: +- _, _, varmap = self._generator_prepare(target, +- selected_projects, +- cross_arch) ++ varmap = self._generator_build_varmap(target, selected_projects) + except FormatterError as ex: + raise VariablesError(str(ex)) + +@@ -608,8 +589,9 @@ class JSONVariablesFormatter(VariablesFormatter): + + class ShellBuildEnvFormatter(BuildEnvFormatter): + +- def __init__(self, base=None, layers="all"): +- super().__init__(indent=len(" "), ++ def __init__(self, inventory, base=None, layers="all"): ++ super().__init__(inventory, ++ indent=len(" "), + pkgcleanup=False, + nosync=False) + +@@ -621,25 +603,25 @@ class ShellBuildEnvFormatter(BuildEnvFormatter): + exp.append(f"export {key}=\"{val}\"") + return "\n" + "\n".join(exp) + +- def _format_buildenv(self, target, project, facts, cross_arch, varmap): ++ def _format_buildenv(self, target, project, varmap): + strings = [ + "function install_buildenv() {", + ] +- groups = self._format_commands_native(facts, cross_arch, varmap) ++ groups = self._format_commands_native(target, varmap) + for commands in groups: + strings.extend([" " + c for c in commands]) +- if cross_arch: +- for command in self._format_commands_foreign(facts, cross_arch, varmap): ++ if target.cross_arch: ++ for command in self._format_commands_foreign(target, varmap): + strings.append(" " + command) + strings.append("}") + + strings.append(self._format_env(self._format_env_native(varmap))) +- if cross_arch: ++ if target.cross_arch: + strings.append(self._format_env( +- self._format_env_foreign(cross_arch, varmap))) ++ self._format_env_foreign(target, varmap))) + return strings + +- def format(self, target, selected_projects, cross_arch): ++ def format(self, target, selected_projects): + """ + Generates and formats a Shell script for preparing a build env. + +@@ -653,14 +635,11 @@ class ShellBuildEnvFormatter(BuildEnvFormatter): + """ + + log.debug(f"Generating Shell Build Env for projects '{selected_projects}' " +- f"on target '{target}' (cross_arch={cross_arch})") ++ f"on target {target}") + + try: +- facts, cross_arch, varmap = self._generator_prepare(target, +- selected_projects, +- cross_arch) ++ varmap = self._generator_build_varmap(target, selected_projects) + except FormatterError as ex: + raise ShellBuildEnvError(str(ex)) + +- return '\n'.join(self._format_buildenv(target, selected_projects, +- facts, cross_arch, varmap)) ++ return '\n'.join(self._format_buildenv(target, selected_projects, varmap)) +diff --git a/tests/lcitool/libvirt-ci/lcitool/inventory.py b/tests/lcitool/libvirt-ci/lcitool/inventory.py +index 9752ca60..fe2b929b 100644 +--- a/tests/lcitool/libvirt-ci/lcitool/inventory.py ++++ b/tests/lcitool/libvirt-ci/lcitool/inventory.py +@@ -4,15 +4,10 @@ + # + # SPDX-License-Identifier: GPL-2.0-or-later + +-import copy + import logging +-import yaml +- +-from pathlib import Path +-from pkg_resources import resource_filename + + from lcitool import util, LcitoolError +-from lcitool.singleton import Singleton ++from lcitool.packages import package_names_by_type + + log = logging.getLogger(__name__) + +@@ -24,7 +19,7 @@ class InventoryError(LcitoolError): + super().__init__(message, "Inventory") + + +-class Inventory(metaclass=Singleton): ++class Inventory(): + + @property + def ansible_inventory(self): +@@ -32,16 +27,6 @@ class Inventory(metaclass=Singleton): + self._ansible_inventory = self._get_ansible_inventory() + return self._ansible_inventory + +- @property +- def target_facts(self): +- if self._target_facts is None: +- self._target_facts = self._load_target_facts() +- return self._target_facts +- +- @property +- def targets(self): +- return list(self.target_facts.keys()) +- + @property + def host_facts(self): + if self._host_facts is None: +@@ -52,22 +37,18 @@ class Inventory(metaclass=Singleton): + def hosts(self): + return list(self.host_facts.keys()) + +- def __init__(self): +- self._target_facts = None ++ def __init__(self, targets, config): ++ self._targets = targets ++ self._config = config + self._host_facts = None + self._ansible_inventory = None + +- @staticmethod +- def _read_facts_from_file(yaml_path): +- log.debug(f"Loading facts from '{yaml_path}'") +- with open(yaml_path, "r") as infile: +- return yaml.safe_load(infile) +- + def _get_ansible_inventory(self): + from lcitool.ansible_wrapper import AnsibleWrapper, AnsibleWrapperError + + inventory_sources = [] +- inventory_path = Path(util.get_config_dir(), "inventory") ++ inventory_path = self._config.get_config_path("inventory") ++ log.debug(f"Using '{inventory_path}' for lcitool inventory") + if inventory_path.exists(): + inventory_sources.append(inventory_path) + +@@ -76,7 +57,7 @@ class Inventory(metaclass=Singleton): + + ansible_runner = AnsibleWrapper() + ansible_runner.prepare_env(inventories=inventory_sources, +- group_vars=self.target_facts) ++ group_vars=self._targets.target_facts) + + log.debug(f"Running ansible-inventory on '{inventory_sources}'") + try: +@@ -100,56 +81,6 @@ class Inventory(metaclass=Singleton): + + return inventory + +- @staticmethod +- def _validate_target_facts(target_facts, target): +- fname = target + ".yml" +- +- actual_osname = target_facts["os"]["name"].lower() +- if not target.startswith(actual_osname + "-"): +- raise InventoryError(f'OS name "{target_facts["os"]["name"]}" does not match file name {fname}') +- target = target[len(actual_osname) + 1:] +- +- actual_version = target_facts["os"]["version"].lower() +- expected_version = target.replace("-", "") +- if expected_version != actual_version: +- raise InventoryError(f'OS version "{target_facts["os"]["version"]}" does not match version in file name {fname} ({expected_version})') +- +- def _load_target_facts(self): +- def merge_dict(source, dest): +- for key in source.keys(): +- if key not in dest: +- dest[key] = copy.deepcopy(source[key]) +- continue +- +- if isinstance(source[key], list) or isinstance(dest[key], list): +- raise InventoryError("cannot merge lists") +- if isinstance(source[key], dict) != isinstance(dest[key], dict): +- raise InventoryError("cannot merge dictionaries with non-dictionaries") +- if isinstance(source[key], dict): +- merge_dict(source[key], dest[key]) +- +- facts = {} +- targets_path = Path(resource_filename(__name__, "facts/targets/")) +- targets_all_path = Path(targets_path, "all.yml") +- +- # first load the shared facts from targets/all.yml +- shared_facts = self._read_facts_from_file(targets_all_path) +- +- # then load the rest of the facts +- for entry in targets_path.iterdir(): +- if not entry.is_file() or entry.suffix != ".yml" or entry.name == "all.yml": +- continue +- +- target = entry.stem +- facts[target] = self._read_facts_from_file(entry) +- self._validate_target_facts(facts[target], target) +- facts[target]["target"] = target +- +- # missing per-distro facts fall back to shared facts +- merge_dict(shared_facts, facts[target]) +- +- return facts +- + def _load_host_facts(self): + facts = {} + groups = {} +@@ -184,7 +115,7 @@ class Inventory(metaclass=Singleton): + + _rec(self.ansible_inventory["all"], "all") + +- targets = set(self.targets) ++ targets = set(self._targets.targets) + for host_name, host_groups in groups.items(): + host_targets = host_groups.intersection(targets) + +@@ -209,3 +140,29 @@ class Inventory(metaclass=Singleton): + except Exception as ex: + log.debug(f"Failed to load expand '{pattern}'") + raise InventoryError(f"Failed to expand '{pattern}': {ex}") ++ ++ def get_host_target_name(self, host): ++ return self.host_facts[host]["target"] ++ ++ def get_group_vars(self, target, projects, projects_expanded): ++ # resolve the package mappings to actual package names ++ internal_wanted_projects = ["base", "developer", "vm"] ++ if self._config.values["install"]["cloud_init"]: ++ internal_wanted_projects.append("cloud-init") ++ ++ selected_projects = internal_wanted_projects + projects_expanded ++ pkgs_install = projects.get_packages(selected_projects, target) ++ pkgs_early_install = projects.get_packages(["early_install"], target) ++ pkgs_remove = projects.get_packages(["unwanted"], target) ++ package_names = package_names_by_type(pkgs_install) ++ package_names_remove = package_names_by_type(pkgs_remove) ++ package_names_early_install = package_names_by_type(pkgs_early_install) ++ ++ # merge the package lists to the Ansible group vars ++ group_vars = dict(target.facts) ++ group_vars["packages"] = package_names["native"] ++ group_vars["pypi_packages"] = package_names["pypi"] ++ group_vars["cpan_packages"] = package_names["cpan"] ++ group_vars["unwanted_packages"] = package_names_remove["native"] ++ group_vars["early_install_packages"] = package_names_early_install["native"] ++ return group_vars +diff --git a/tests/lcitool/libvirt-ci/lcitool/manifest.py b/tests/lcitool/libvirt-ci/lcitool/manifest.py +index c4cb8c0a..2ad53582 100644 +--- a/tests/lcitool/libvirt-ci/lcitool/manifest.py ++++ b/tests/lcitool/libvirt-ci/lcitool/manifest.py +@@ -9,8 +9,8 @@ import yaml + from pathlib import Path + + from lcitool.formatters import DockerfileFormatter, ShellVariablesFormatter, ShellBuildEnvFormatter +-from lcitool.inventory import Inventory + from lcitool import gitlab, util, LcitoolError ++from lcitool.targets import BuildTarget + + log = logging.getLogger(__name__) + +@@ -24,7 +24,10 @@ class ManifestError(LcitoolError): + + class Manifest: + +- def __init__(self, configfp, quiet=False, cidir=Path("ci"), basedir=None): ++ def __init__(self, targets, packages, projects, configfp, quiet=False, cidir=Path("ci"), basedir=None): ++ self._targets = targets ++ self._packages = packages ++ self._projects = projects + self.configpath = configfp.name + self.values = yaml.safe_load(configfp) + self.quiet = quiet +@@ -88,7 +91,6 @@ class Manifest: + targets = self.values["targets"] = {} + have_containers = False + have_cirrus = False +- inventory = Inventory() + for target, targetinfo in targets.items(): + if type(targetinfo) == str: + targets[target] = {"jobs": [{"arch": targetinfo}]} +@@ -99,7 +101,7 @@ class Manifest: + jobsinfo = targetinfo["jobs"] + + try: +- facts = inventory.target_facts[target] ++ facts = self._targets.target_facts[target] + except KeyError: + raise ValueError(f"Invalid target '{target}'") + +@@ -205,27 +207,26 @@ class Manifest: + if not dryrun: + header = util.generate_file_header(["manifest", + self.configpath]) +- payload = formatter.format(target, +- wantprojects, +- arch) ++ payload = formatter.format(BuildTarget(self._targets, self._packages, target, arch), ++ wantprojects) + util.atomic_write(filename, header + payload + "\n") + + return generated + + def _generate_containers(self, dryrun): +- formatter = DockerfileFormatter() ++ formatter = DockerfileFormatter(self._projects) + return self._generate_formatter(dryrun, + "containers", "Dockerfile", + formatter, "containers") + + def _generate_cirrus(self, dryrun): +- formatter = ShellVariablesFormatter() ++ formatter = ShellVariablesFormatter(self._projects) + return self._generate_formatter(dryrun, + "cirrus", "vars", + formatter, "cirrus") + + def _generate_buildenv(self, dryrun): +- formatter = ShellBuildEnvFormatter() ++ formatter = ShellBuildEnvFormatter(self._projects) + return self._generate_formatter(dryrun, + "buildenv", "sh", + formatter, "containers") +@@ -416,7 +417,6 @@ class Manifest: + + def _generate_build_jobs(self, targettype, cross, jobfunc): + jobs = [] +- inventory = Inventory() + for target, targetinfo in self.values["targets"].items(): + if not targetinfo["enabled"]: + continue +@@ -424,7 +424,7 @@ class Manifest: + continue + + try: +- facts = inventory.target_facts[target] ++ facts = self._targets.target_facts[target] + except KeyError: + raise ManifestError(f"Invalid target '{target}'") + +diff --git a/tests/lcitool/libvirt-ci/lcitool/package.py b/tests/lcitool/libvirt-ci/lcitool/packages.py +similarity index 61% +rename from lcitool/package.py +rename to lcitool/packages.py +index d267e2f2..6f3c3139 100644 +--- a/tests/lcitool/libvirt-ci/lcitool/package.py ++++ b/tests/lcitool/libvirt-ci/lcitool/packages.py +@@ -30,7 +30,7 @@ Exported classes: + - CrossPackage + - PyPIPackage + - CPANPackage +- - PackageFactory ++ - Packages + + Exported functions: + - package_names_by_type +@@ -39,6 +39,9 @@ Exported functions: + + import abc + import logging ++import yaml ++ ++from pkg_resources import resource_filename + + from lcitool import util, LcitoolError + +@@ -90,8 +93,8 @@ class Package(metaclass=abc.ABCMeta): + - PyPIPackage + - CPANPackage + +- Do not instantiate any of the specific package subclasses, instead, use an +- instance of the PackageFactory class which does that for you transparently. ++ Do not instantiate any of the specific package subclasses, instead, use ++ the Packages class which does that for you transparently. + Then use this public interface to interact with the instance itself. + + Attributes: +@@ -99,7 +102,7 @@ class Package(metaclass=abc.ABCMeta): + :ivar mapping: the generic package name that will resolve to @name + """ + +- def __init__(self, pkg_mapping): ++ def __init__(self, mappings, pkg_mapping, keys, target): + """ + Initialize the package with a generic package name + +@@ -107,9 +110,11 @@ class Package(metaclass=abc.ABCMeta): + """ + + self.mapping = pkg_mapping +- self.name = None ++ self.name = self._eval(mappings, target, keys) ++ if self.name is None: ++ raise PackageEval(f"No mapping for '{pkg_mapping}'") + +- def _eval(self, mappings, keys=["default"]): ++ def _eval(self, mappings, target, keys): + """ + Resolves package mapping to the actual name of the package. + +@@ -138,32 +143,26 @@ class CrossPackage(Package): + def __init__(self, + mappings, + pkg_mapping, +- pkg_format, + base_keys, +- cross_arch): +- +- super().__init__(pkg_mapping) +- +- self.name = self._eval(mappings, pkg_format, base_keys, cross_arch) +- if self.name is None: +- raise PackageEval(f"No mapping for '{pkg_mapping}'") +- +- def _eval(self, mappings, pkg_format, base_keys, cross_arch): +- cross_keys = ["cross-" + cross_arch + "-" + k for k in base_keys] ++ target): ++ cross_keys = ["cross-" + target.cross_arch + "-" + k for k in base_keys] + +- if pkg_format == "deb": ++ if target.facts["packaging"]["format"] == "deb": + # For Debian-based distros, the name of the foreign package + # is usually the same as the native package, but there might + # be architecture-specific overrides, so we have to look both + # at the neutral keys and at the specific ones +- arch_keys = [cross_arch + "-" + k for k in base_keys] ++ arch_keys = [target.cross_arch + "-" + k for k in base_keys] + cross_keys.extend(arch_keys + base_keys) + +- pkg_name = super()._eval(mappings, keys=cross_keys) ++ super().__init__(mappings, pkg_mapping, cross_keys, target) ++ ++ def _eval(self, mappings, target, keys): ++ pkg_name = super()._eval(mappings, target, keys) + if pkg_name is None: + return None + +- if pkg_format == "deb": ++ if target.facts["packaging"]["format"] == "deb": + # For Debian-based distros, the name of the foreign package + # is obtained by appending the foreign architecture (in + # Debian format) to the name of the native package. +@@ -171,7 +170,7 @@ class CrossPackage(Package): + # The exception to this is cross-compilers, where we have + # to install the package for the native architecture in + # order to be able to build for the foreign architecture +- cross_arch_deb = util.native_arch_to_deb_arch(cross_arch) ++ cross_arch_deb = util.native_arch_to_deb_arch(target.cross_arch) + if self.mapping not in ["gcc", "g++"]: + pkg_name = pkg_name + ":" + cross_arch_deb + return pkg_name +@@ -182,154 +181,150 @@ class NativePackage(Package): + def __init__(self, + mappings, + pkg_mapping, +- base_keys): +- +- super().__init__(pkg_mapping) +- +- self.name = self._eval(mappings, base_keys) +- if self.name is None: +- raise PackageEval(f"No mapping for '{pkg_mapping}'") +- +- def _eval(self, mappings, base_keys): ++ base_keys, ++ target): + native_arch = util.get_native_arch() + native_keys = [native_arch + "-" + k for k in base_keys] + base_keys +- +- return super()._eval(mappings, keys=native_keys) ++ super().__init__(mappings, pkg_mapping, native_keys, target) + + + class PyPIPackage(Package): +- +- def __init__(self, +- mappings, +- pkg_mapping, +- base_keys): +- +- super().__init__(pkg_mapping) +- +- self.name = self._eval(mappings, keys=base_keys) +- if self.name is None: +- raise PackageEval(f"No mapping for '{pkg_mapping}'") ++ pass + + + class CPANPackage(Package): +- +- def __init__(self, +- mappings, +- pkg_mapping, +- base_keys): +- +- super().__init__(pkg_mapping) +- +- self.name = self._eval(mappings, keys=base_keys) +- if self.name is None: +- raise PackageEval(f"No mapping for '{pkg_mapping}'") ++ pass + + +-class PackageFactory: ++class Packages: + """ +- Factory producing Package instances. +- +- Creates Package class instances based on the generic package mapping name +- which will be resolved to the actual package name the moment a Package +- instance is created by this factory. ++ Database of package mappings. Package class representing the actual ++ package name are created based on the generic package mapping. + + """ + +- def __init__(self, mappings, facts): +- """ +- Initialize package factory model. +- +- :param mappings: dictionary of ALL existing package mappings, i.e. +- including Python and CPAN ones +- :param facts: dictionary of target OS facts +- """ +- +- def _generate_base_keys(facts): +- base_keys = [ +- # keys are ordered by priority +- facts["os"]["name"] + facts["os"]["version"], +- facts["os"]["name"], +- facts["packaging"]["format"], +- "default" +- ] +- return base_keys +- +- self._mappings = mappings["mappings"] +- self._pypi_mappings = mappings["pypi_mappings"] +- self._cpan_mappings = mappings["cpan_mappings"] +- self._facts = facts +- self._base_keys = _generate_base_keys(facts) +- +- def _get_cross_policy(self, pkg_mapping): +- for k in ["cross-policy-" + k for k in self._base_keys]: +- if k in self._mappings[pkg_mapping]: +- cross_policy = self._mappings[pkg_mapping][k] ++ def __init__(self): ++ self._mappings = None ++ self._pypi_mappings = None ++ self._cpan_mappings = None ++ ++ @staticmethod ++ def _base_keys(target): ++ return [ ++ target.facts["os"]["name"] + target.facts["os"]["version"], ++ target.facts["os"]["name"], ++ target.facts["packaging"]["format"], ++ "default" ++ ] ++ ++ def _get_cross_policy(self, pkg_mapping, target): ++ base_keys = self._base_keys(target) ++ for k in ["cross-policy-" + k for k in base_keys]: ++ if k in self.mappings[pkg_mapping]: ++ cross_policy = self.mappings[pkg_mapping][k] + if cross_policy not in ["native", "foreign", "skip"]: + raise Exception( + f"Unexpected cross arch policy {cross_policy} for " + f"{pkg_mapping}" + ) + return cross_policy +- return None ++ return "native" + +- def _get_native_package(self, pkg_mapping): +- return NativePackage(self._mappings, pkg_mapping, self._base_keys) ++ def _get_native_package(self, pkg_mapping, target): ++ base_keys = self._base_keys(target) ++ return NativePackage(self.mappings, pkg_mapping, base_keys, target) + +- def _get_pypi_package(self, pkg_mapping): +- return PyPIPackage(self._pypi_mappings, pkg_mapping, self._base_keys) ++ def _get_pypi_package(self, pkg_mapping, target): ++ base_keys = self._base_keys(target) ++ return PyPIPackage(self.pypi_mappings, pkg_mapping, base_keys, target) + +- def _get_cpan_package(self, pkg_mapping): +- return CPANPackage(self._cpan_mappings, pkg_mapping, self._base_keys) ++ def _get_cpan_package(self, pkg_mapping, target): ++ base_keys = self._base_keys(target) ++ return CPANPackage(self.cpan_mappings, pkg_mapping, base_keys, target) + +- def _get_noncross_package(self, pkg_mapping): ++ def _get_noncross_package(self, pkg_mapping, target): + package_resolvers = [self._get_native_package, + self._get_pypi_package, + self._get_cpan_package] + + for resolver in package_resolvers: + try: +- return resolver(pkg_mapping) ++ return resolver(pkg_mapping, target) + except PackageEval: + continue + + # This package doesn't exist on the given platform + return None + +- def _get_cross_package(self, pkg_mapping, cross_arch): ++ def _get_cross_package(self, pkg_mapping, target): + + # query the cross policy for the mapping to see whether we need + # a cross- or non-cross version of a package +- cross_policy = self._get_cross_policy(pkg_mapping) ++ cross_policy = self._get_cross_policy(pkg_mapping, target) + if cross_policy == "skip": + return None + +- elif cross_policy == "native" or cross_policy is None: +- return self._get_noncross_package(pkg_mapping) ++ elif cross_policy == "native": ++ return self._get_noncross_package(pkg_mapping, target) + + try: +- return CrossPackage(self._mappings, pkg_mapping, +- self._facts["packaging"]["format"], +- self._base_keys, cross_arch) ++ base_keys = self._base_keys(target) ++ return CrossPackage(self.mappings, pkg_mapping, base_keys, target) + except PackageEval: + pass + + # This package doesn't exist on the given platform + return None + +- def get_package(self, pkg_mapping, cross_arch=None): ++ @property ++ def mappings(self): ++ if self._mappings is None: ++ self._load_mappings() ++ ++ return self._mappings ++ ++ @property ++ def pypi_mappings(self): ++ if self._mappings is None: ++ self._load_mappings() ++ ++ return self._pypi_mappings ++ ++ @property ++ def cpan_mappings(self): ++ if self._mappings is None: ++ self._load_mappings() ++ ++ return self._cpan_mappings ++ ++ def get_package(self, pkg_mapping, target): + """ + Resolves the generic mapping name and returns a Package instance. + + :param pkg_mapping: generic package mapping name +- :param cross_arch: cross architecture string (if needed) ++ :param target: target to resolve the package for + :return: instance of Package subclass or None if package mapping could + not be resolved + """ + +- if pkg_mapping not in self._mappings: ++ if pkg_mapping not in self.mappings: + raise PackageMissing(f"Package {pkg_mapping} not present in mappings") + +- if cross_arch is None: +- return self._get_noncross_package(pkg_mapping) ++ if target.cross_arch is None: ++ return self._get_noncross_package(pkg_mapping, target) + else: +- return self._get_cross_package(pkg_mapping, cross_arch) ++ return self._get_cross_package(pkg_mapping, target) ++ ++ def _load_mappings(self): ++ mappings_path = resource_filename(__name__, ++ "facts/mappings.yml") ++ ++ try: ++ with open(mappings_path, "r") as infile: ++ mappings = yaml.safe_load(infile) ++ self._mappings = mappings["mappings"] ++ self._pypi_mappings = mappings["pypi_mappings"] ++ self._cpan_mappings = mappings["cpan_mappings"] ++ except Exception as ex: ++ log.debug("Can't load mappings") ++ raise PackageError(f"Can't load mappings: {ex}") +diff --git a/tests/lcitool/libvirt-ci/lcitool/projects.py b/tests/lcitool/libvirt-ci/lcitool/projects.py +index 06d34803..28e83a27 100644 +--- a/tests/lcitool/libvirt-ci/lcitool/projects.py ++++ b/tests/lcitool/libvirt-ci/lcitool/projects.py +@@ -11,8 +11,7 @@ from pathlib import Path + from pkg_resources import resource_filename + + from lcitool import util, LcitoolError +-from lcitool.package import PackageFactory, PyPIPackage, CPANPackage +-from lcitool.singleton import Singleton ++from lcitool.packages import PyPIPackage, CPANPackage + + log = logging.getLogger(__name__) + +@@ -29,79 +28,58 @@ class ProjectError(LcitoolError): + super().__init__(message, "Project") + + +-class Projects(metaclass=Singleton): ++class Projects: + """ + Attributes: + :ivar names: list of all project names ++ :ivar public: dictionary from project names to ``Project`` objects for public projects ++ :ivar internal: dictionary from project names to ``Project`` objects for internal projects + """ + + @property +- def projects(self): +- if self._projects is None: +- self._projects = self._load_projects() +- return self._projects ++ def public(self): ++ if self._public is None: ++ self._load_public() ++ return self._public + + @property + def names(self): +- return list(self.projects.keys()) ++ return list(self.public.keys()) + + @property +- def internal_projects(self): +- if self._internal_projects is None: +- self._internal_projects = self._load_internal_projects() +- return self._internal_projects +- +- @property +- def mappings(self): +- +- # lazy load mappings +- if self._mappings is None: +- self._mappings = self._load_mappings() +- return self._mappings ++ def internal(self): ++ if self._internal is None: ++ self._load_internal() ++ return self._internal + + def __init__(self): +- self._projects = None +- self._internal_projects = None +- self._mappings = None ++ self._public = None ++ self._internal = None + +- @staticmethod +- def _load_projects_from_path(path): ++ def _load_projects_from_path(self, path): + projects = {} + + for item in path.iterdir(): + if not item.is_file() or item.suffix != ".yml": + continue + +- projects[item.stem] = Project(item.stem, item) ++ projects[item.stem] = Project(self, item.stem, item) + + return projects + +- @staticmethod +- def _load_projects(): ++ def _load_public(self): + source = Path(resource_filename(__name__, "facts/projects")) +- projects = Projects._load_projects_from_path(source) ++ projects = self._load_projects_from_path(source) + + if util.get_extra_data_dir() is not None: + source = Path(util.get_extra_data_dir()).joinpath("projects") +- projects.update(Projects._load_projects_from_path(source)) ++ projects.update(self._load_projects_from_path(source)) + +- return projects ++ self._public = projects + +- @staticmethod +- def _load_internal_projects(): ++ def _load_internal(self): + source = Path(resource_filename(__name__, "facts/projects/internal")) +- return Projects._load_projects_from_path(source) +- +- def _load_mappings(self): +- mappings_path = resource_filename(__name__, +- "facts/mappings.yml") +- +- try: +- with open(mappings_path, "r") as infile: +- return yaml.safe_load(infile) +- except Exception as ex: +- log.debug("Can't load mappings") +- raise ProjectError(f"Can't load mappings: {ex}") ++ self._internal = self._load_projects_from_path(source) + + def expand_names(self, pattern): + try: +@@ -110,18 +88,46 @@ class Projects(metaclass=Singleton): + log.debug(f"Failed to expand '{pattern}'") + raise ProjectError(f"Failed to expand '{pattern}': {ex}") + +- def get_packages(self, projects, facts, cross_arch=None): ++ def get_packages(self, projects, target): + packages = {} + + for proj in projects: + try: +- obj = self.projects[proj] ++ obj = self.public[proj] + except KeyError: +- obj = self.internal_projects[proj] +- packages.update(obj.get_packages(facts, cross_arch)) ++ obj = self.internal[proj] ++ packages.update(obj.get_packages(target)) + + return packages + ++ def eval_generic_packages(self, target, generic_packages): ++ pkgs = {} ++ needs_pypi = False ++ needs_cpan = False ++ ++ for mapping in generic_packages: ++ pkg = target.get_package(mapping) ++ if pkg is None: ++ continue ++ pkgs[pkg.mapping] = pkg ++ ++ if isinstance(pkg, PyPIPackage): ++ needs_pypi = True ++ elif isinstance(pkg, CPANPackage): ++ needs_cpan = True ++ ++ # The get_packages eval_generic_packages cycle is deliberate and ++ # harmless since we'll only ever hit it with the following internal ++ # projects ++ if needs_pypi: ++ proj = self.internal["python-pip"] ++ pkgs.update(proj.get_packages(target)) ++ if needs_cpan: ++ proj = self.internal["perl-cpan"] ++ pkgs.update(proj.get_packages(target)) ++ ++ return pkgs ++ + + class Project: + """ +@@ -129,6 +135,7 @@ class Project: + :ivar name: project name + :ivar generic_packages: list of generic packages needed by the project + to build successfully ++ :ivar projects: parent ``Projects`` instance + """ + + @property +@@ -139,7 +146,8 @@ class Project: + self._generic_packages = self._load_generic_packages() + return self._generic_packages + +- def __init__(self, name, path): ++ def __init__(self, projects, name, path): ++ self.projects = projects + self.name = name + self.path = path + self._generic_packages = None +@@ -156,49 +164,21 @@ class Project: + log.debug(f"Can't load pacakges for '{self.name}'") + raise ProjectError(f"Can't load packages for '{self.name}': {ex}") + +- def _eval_generic_packages(self, facts, cross_arch=None): +- pkgs = {} +- factory = PackageFactory(Projects().mappings, facts) +- needs_pypi = False +- needs_cpan = False +- +- for mapping in self.generic_packages: +- pkg = factory.get_package(mapping, cross_arch) +- if pkg is None: +- continue +- pkgs[pkg.mapping] = pkg +- +- if isinstance(pkg, PyPIPackage): +- needs_pypi = True +- elif isinstance(pkg, CPANPackage): +- needs_cpan = True +- +- # The get_packages _eval_generic_packages cycle is deliberate and +- # harmless since we'll only ever hit it with the following internal +- # projects +- if needs_pypi: +- proj = Projects().internal_projects["python-pip"] +- pkgs.update(proj.get_packages(facts, cross_arch)) +- if needs_cpan: +- proj = Projects().internal_projects["perl-cpan"] +- pkgs.update(proj.get_packages(facts, cross_arch)) +- +- return pkgs +- +- def get_packages(self, facts, cross_arch=None): +- osname = facts["os"]["name"] +- osversion = facts["os"]["version"] ++ def get_packages(self, target): ++ osname = target.facts["os"]["name"] ++ osversion = target.facts["os"]["version"] + target_name = f"{osname.lower()}-{osversion.lower()}" +- if cross_arch is None: ++ if target.cross_arch is None: + target_name = f"{target_name}-x86_64" + else: + try: +- util.validate_cross_platform(cross_arch, osname) ++ util.validate_cross_platform(target.cross_arch, osname) + except ValueError as ex: + raise ProjectError(ex) +- target_name = f"{target_name}-{cross_arch}" ++ target_name = f"{target_name}-{target.cross_arch}" + + # lazy evaluation + caching of package names for a given distro + if self._target_packages.get(target_name) is None: +- self._target_packages[target_name] = self._eval_generic_packages(facts, cross_arch) ++ self._target_packages[target_name] = self.projects.eval_generic_packages(target, ++ self.generic_packages) + return self._target_packages[target_name] +diff --git a/tests/lcitool/libvirt-ci/lcitool/singleton.py b/tests/lcitool/libvirt-ci/lcitool/singleton.py +deleted file mode 100644 +index 46d1379f..00000000 +--- a/tests/lcitool/libvirt-ci/lcitool/singleton.py ++++ /dev/null +@@ -1,14 +0,0 @@ +-# singleton.py - module singleton class definition +-# +-# Copyright (C) 2021 Red Hat, Inc. +-# +-# SPDX-License-Identifier: GPL-2.0-or-later +- +-class Singleton(type): +- _instances = {} +- +- def __call__(cls, *args, **kwargs): +- if cls not in cls._instances: +- instance = super(Singleton, cls).__call__(*args, **kwargs) +- cls._instances[cls] = instance +- return cls._instances[cls] +diff --git a/tests/lcitool/libvirt-ci/lcitool/targets.py b/tests/lcitool/libvirt-ci/lcitool/targets.py +new file mode 100644 +index 00000000..ef6deab1 +--- /dev/null ++++ b/tests/lcitool/libvirt-ci/lcitool/targets.py +@@ -0,0 +1,108 @@ ++# targets.py - module containing accessors to per-target information ++# ++# Copyright (C) 2022 Red Hat, Inc. ++# ++# SPDX-License-Identifier: GPL-2.0-or-later ++ ++import logging ++import yaml ++ ++from pathlib import Path ++from pkg_resources import resource_filename ++ ++from lcitool import util, LcitoolError ++ ++ ++log = logging.getLogger(__name__) ++ ++ ++class TargetsError(LcitoolError): ++ """Global exception type for the targets module.""" ++ ++ def __init__(self, message): ++ super().__init__(message, "Targets") ++ ++ ++class Targets(): ++ ++ @property ++ def target_facts(self): ++ if self._target_facts is None: ++ self._target_facts = self._load_target_facts() ++ return self._target_facts ++ ++ @property ++ def targets(self): ++ return list(self.target_facts.keys()) ++ ++ def __init__(self): ++ self._target_facts = None ++ ++ @staticmethod ++ def _read_facts_from_file(yaml_path): ++ log.debug(f"Loading facts from '{yaml_path}'") ++ with open(yaml_path, "r") as infile: ++ return yaml.safe_load(infile) ++ ++ @staticmethod ++ def _validate_target_facts(target_facts, target): ++ fname = target + ".yml" ++ ++ actual_osname = target_facts["os"]["name"].lower() ++ if not target.startswith(actual_osname + "-"): ++ raise TargetsError(f'OS name "{target_facts["os"]["name"]}" does not match file name {fname}') ++ target = target[len(actual_osname) + 1:] ++ ++ actual_version = target_facts["os"]["version"].lower() ++ expected_version = target.replace("-", "") ++ if expected_version != actual_version: ++ raise TargetsError(f'OS version "{target_facts["os"]["version"]}" does not match version in file name {fname} ({expected_version})') ++ ++ def _load_target_facts(self): ++ facts = {} ++ targets_path = Path(resource_filename(__name__, "facts/targets/")) ++ targets_all_path = Path(targets_path, "all.yml") ++ ++ # first load the shared facts from targets/all.yml ++ shared_facts = self._read_facts_from_file(targets_all_path) ++ ++ # then load the rest of the facts ++ for entry in targets_path.iterdir(): ++ if not entry.is_file() or entry.suffix != ".yml" or entry.name == "all.yml": ++ continue ++ ++ target = entry.stem ++ facts[target] = self._read_facts_from_file(entry) ++ self._validate_target_facts(facts[target], target) ++ facts[target]["target"] = target ++ ++ # missing per-distro facts fall back to shared facts ++ util.merge_dict(shared_facts, facts[target]) ++ ++ return facts ++ ++ ++class BuildTarget: ++ """ ++ Attributes: ++ :ivar _targets: object to retrieve the target facts ++ :ivar name: target name ++ :ivar cross_arch: cross compilation architecture ++ """ ++ ++ def __init__(self, targets, packages, name, cross_arch=None): ++ if name not in targets.target_facts: ++ raise TargetsError(f"Target not found: {name}") ++ self._packages = packages ++ self.name = name ++ self.cross_arch = cross_arch ++ self.facts = targets.target_facts[self.name] ++ ++ def __str__(self): ++ if self.cross_arch: ++ return f"{self.name} (cross_arch={self.cross_arch}" ++ else: ++ return self.name ++ ++ def get_package(self, name): ++ return self._packages.get_package(name, self) +diff --git a/tests/lcitool/libvirt-ci/lcitool/util.py b/tests/lcitool/libvirt-ci/lcitool/util.py +index d2577917..c231df00 100644 +--- a/tests/lcitool/libvirt-ci/lcitool/util.py ++++ b/tests/lcitool/libvirt-ci/lcitool/util.py +@@ -4,6 +4,7 @@ + # + # SPDX-License-Identifier: GPL-2.0-or-later + ++import copy + import fnmatch + import logging + import os +@@ -202,6 +203,20 @@ def get_config_dir(): + return Path(config_dir, "lcitool") + + ++def merge_dict(source, dest): ++ for key in source.keys(): ++ if key not in dest: ++ dest[key] = copy.deepcopy(source[key]) ++ continue ++ ++ if isinstance(source[key], list) or isinstance(dest[key], list): ++ raise ValueError("cannot merge lists") ++ if isinstance(source[key], dict) != isinstance(dest[key], dict): ++ raise ValueError("cannot merge dictionaries with non-dictionaries") ++ if isinstance(source[key], dict): ++ merge_dict(source[key], dest[key]) ++ ++ + extra_data_dir = None + + +diff --git a/tests/lcitool/libvirt-ci/test-requirements.txt b/tests/lcitool/libvirt-ci/test-requirements.txt +index 8e3fd1fd..b816c69f 100644 +--- a/tests/lcitool/libvirt-ci/test-requirements.txt ++++ b/tests/lcitool/libvirt-ci/test-requirements.txt +@@ -1,5 +1,7 @@ + -r requirements.txt +--r vm-requirements.txt ++ ++ansible ++ansible-runner + + flake8 + pytest +diff --git a/tests/lcitool/libvirt-ci/tests/conftest.py b/tests/lcitool/libvirt-ci/tests/conftest.py +index 751eb405..61a7b436 100644 +--- a/tests/lcitool/libvirt-ci/tests/conftest.py ++++ b/tests/lcitool/libvirt-ci/tests/conftest.py +@@ -1,5 +1,16 @@ + import pytest + ++from pathlib import Path ++ ++from lcitool.config import Config ++from lcitool.inventory import Inventory ++from lcitool.packages import Packages ++from lcitool.projects import Projects ++from lcitool.targets import Targets ++from lcitool import util ++ ++import test_utils.utils as test_utils ++ + + def pytest_addoption(parser): + parser.addoption( +@@ -13,3 +24,54 @@ def pytest_addoption(parser): + def pytest_configure(config): + opts = ["regenerate_output"] + pytest.custom_args = {opt: config.getoption(opt) for opt in opts} ++ ++ ++# These needs to be a global in order to compute ALL_PROJECTS and ALL_TARGETS ++# at collection time. Tests do not access it and use the fixtures below. ++_PROJECTS = Projects() ++_TARGETS = Targets() ++ ++ALL_PROJECTS = sorted(_PROJECTS.names + list(_PROJECTS.internal.keys())) ++ALL_TARGETS = sorted(_TARGETS.targets) ++ ++ ++@pytest.fixture ++def config(monkeypatch, request): ++ if 'config_filename' in request.fixturenames: ++ config_filename = request.getfixturevalue('config_filename') ++ actual_path = Path(test_utils.test_data_indir(request.module.__file__), config_filename) ++ ++ # we have to monkeypatch the '_config_file_paths' attribute, since we don't ++ # support custom inventory paths ++ config = Config() ++ monkeypatch.setattr(config, "_config_file_paths", [actual_path]) ++ else: ++ actual_dir = Path(test_utils.test_data_indir(request.module.__file__)) ++ monkeypatch.setattr(util, "get_config_dir", lambda: actual_dir) ++ config = Config() ++ ++ return config ++ ++ ++@pytest.fixture ++def inventory(monkeypatch, targets, config): ++ inventory = Inventory(targets, config) ++ ++ monkeypatch.setattr(inventory, "_get_libvirt_inventory", ++ lambda: {"all": {"children": {}}}) ++ return inventory ++ ++ ++@pytest.fixture(scope="module") ++def packages(): ++ return Packages() ++ ++ ++@pytest.fixture ++def projects(): ++ return _PROJECTS ++ ++ ++@pytest.fixture ++def targets(): ++ return _TARGETS +diff --git a/tests/lcitool/libvirt-ci/tests/data/formatters/out/alpine-315-all-projects.Dockerfile b/tests/lcitool/libvirt-ci/tests/data/formatters/out/alpine-315-all-projects.Dockerfile +index 13e1a542..5fabdb95 100644 +--- a/tests/lcitool/libvirt-ci/tests/data/formatters/out/alpine-315-all-projects.Dockerfile ++++ b/tests/lcitool/libvirt-ci/tests/data/formatters/out/alpine-315-all-projects.Dockerfile +@@ -156,7 +156,6 @@ RUN apk update && \ + perl-xml-xpath \ + perl-yaml \ + php8-dev \ +- php8-pecl-imagick \ + pixman-dev \ + pkgconf \ + polkit \ +diff --git a/tests/lcitool/libvirt-ci/tests/data/formatters/out/alpine-316-all-projects.Dockerfile b/tests/lcitool/libvirt-ci/tests/data/formatters/out/alpine-316-all-projects.Dockerfile +index 7eef4d51..077eacee 100644 +--- a/tests/lcitool/libvirt-ci/tests/data/formatters/out/alpine-316-all-projects.Dockerfile ++++ b/tests/lcitool/libvirt-ci/tests/data/formatters/out/alpine-316-all-projects.Dockerfile +@@ -156,7 +156,6 @@ RUN apk update && \ + perl-xml-xpath \ + perl-yaml \ + php8-dev \ +- php8-pecl-imagick \ + pixman-dev \ + pkgconf \ + polkit \ +diff --git a/tests/lcitool/libvirt-ci/tests/data/formatters/out/alpine-edge-all-projects.Dockerfile b/tests/lcitool/libvirt-ci/tests/data/formatters/out/alpine-edge-all-projects.Dockerfile +index 67b591ea..0d0f7527 100644 +--- a/tests/lcitool/libvirt-ci/tests/data/formatters/out/alpine-edge-all-projects.Dockerfile ++++ b/tests/lcitool/libvirt-ci/tests/data/formatters/out/alpine-edge-all-projects.Dockerfile +@@ -156,7 +156,6 @@ RUN apk update && \ + perl-xml-xpath \ + perl-yaml \ + php8-dev \ +- php8-pecl-imagick \ + pixman-dev \ + pkgconf \ + polkit \ +diff --git a/tests/lcitool/libvirt-ci/tests/data/formatters/out/debian-10-all-projects.Dockerfile b/tests/lcitool/libvirt-ci/tests/data/formatters/out/debian-10-all-projects.Dockerfile +index 0d0b3e57..9d784d24 100644 +--- a/tests/lcitool/libvirt-ci/tests/data/formatters/out/debian-10-all-projects.Dockerfile ++++ b/tests/lcitool/libvirt-ci/tests/data/formatters/out/debian-10-all-projects.Dockerfile +@@ -221,7 +221,6 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ + perl \ + perl-base \ + php-dev \ +- php-imagick \ + pkgconf \ + policykit-1 \ + publican \ +diff --git a/tests/lcitool/libvirt-ci/tests/data/formatters/out/debian-11-all-projects.Dockerfile b/tests/lcitool/libvirt-ci/tests/data/formatters/out/debian-11-all-projects.Dockerfile +index 7ab4c2ca..ee5166c6 100644 +--- a/tests/lcitool/libvirt-ci/tests/data/formatters/out/debian-11-all-projects.Dockerfile ++++ b/tests/lcitool/libvirt-ci/tests/data/formatters/out/debian-11-all-projects.Dockerfile +@@ -224,7 +224,6 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ + perl \ + perl-base \ + php-dev \ +- php-imagick \ + pkgconf \ + policykit-1 \ + publican \ +diff --git a/tests/lcitool/libvirt-ci/tests/data/formatters/out/debian-sid-all-projects.Dockerfile b/tests/lcitool/libvirt-ci/tests/data/formatters/out/debian-sid-all-projects.Dockerfile +index 8ca21e51..09dac3eb 100644 +--- a/tests/lcitool/libvirt-ci/tests/data/formatters/out/debian-sid-all-projects.Dockerfile ++++ b/tests/lcitool/libvirt-ci/tests/data/formatters/out/debian-sid-all-projects.Dockerfile +@@ -224,7 +224,6 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ + perl \ + perl-base \ + php-dev \ +- php-imagick \ + pkgconf \ + policykit-1 \ + publican \ +diff --git a/tests/lcitool/libvirt-ci/tests/data/formatters/out/fedora-36-all-projects.Dockerfile b/tests/lcitool/libvirt-ci/tests/data/formatters/out/fedora-36-all-projects.Dockerfile +index 51b2b96b..cb416150 100644 +--- a/tests/lcitool/libvirt-ci/tests/data/formatters/out/fedora-36-all-projects.Dockerfile ++++ b/tests/lcitool/libvirt-ci/tests/data/formatters/out/fedora-36-all-projects.Dockerfile +@@ -214,7 +214,6 @@ exec "$@"' > /usr/bin/nosync && \ + perl-generators \ + perl-podlators \ + php-devel \ +- php-pecl-imagick \ + pixman-devel \ + pkgconfig \ + polkit \ +diff --git a/tests/lcitool/libvirt-ci/tests/data/formatters/out/fedora-35-all-projects.Dockerfile b/tests/lcitool/libvirt-ci/tests/data/formatters/out/fedora-37-all-projects.Dockerfile +similarity index 98% +rename from tests/data/formatters/out/fedora-35-all-projects.Dockerfile +rename to tests/data/formatters/out/fedora-37-all-projects.Dockerfile +index 35789b6d..e477d3f3 100644 +--- a/tests/lcitool/libvirt-ci/tests/data/formatters/out/fedora-35-all-projects.Dockerfile ++++ b/tests/lcitool/libvirt-ci/tests/data/formatters/out/fedora-37-all-projects.Dockerfile +@@ -1,4 +1,4 @@ +-FROM registry.fedoraproject.org/fedora:35 ++FROM registry.fedoraproject.org/fedora:37 + + RUN dnf install -y nosync && \ + echo -e '#!/bin/sh\n\ +@@ -163,7 +163,6 @@ exec "$@"' > /usr/bin/nosync && \ + nbdkit \ + ncurses-devel \ + net-snmp-devel \ +- netcf-devel \ + nettle-devel \ + nfs-utils \ + ninja-build \ +@@ -215,11 +214,9 @@ exec "$@"' > /usr/bin/nosync && \ + perl-generators \ + perl-podlators \ + php-devel \ +- php-pecl-imagick \ + pixman-devel \ + pkgconfig \ + polkit \ +- publican \ + pulseaudio-libs-devel \ + python3 \ + python3-PyYAML \ +diff --git a/tests/lcitool/libvirt-ci/tests/data/formatters/out/fedora-rawhide-all-projects.Dockerfile b/tests/lcitool/libvirt-ci/tests/data/formatters/out/fedora-rawhide-all-projects.Dockerfile +index bc47cf9f..35852443 100644 +--- a/tests/lcitool/libvirt-ci/tests/data/formatters/out/fedora-rawhide-all-projects.Dockerfile ++++ b/tests/lcitool/libvirt-ci/tests/data/formatters/out/fedora-rawhide-all-projects.Dockerfile +@@ -215,7 +215,6 @@ exec "$@"' > /usr/bin/nosync && \ + perl-generators \ + perl-podlators \ + php-devel \ +- php-pecl-imagick \ + pixman-devel \ + pkgconfig \ + polkit \ +diff --git a/tests/lcitool/libvirt-ci/tests/data/formatters/out/opensuse-leap-153-all-projects.Dockerfile b/tests/lcitool/libvirt-ci/tests/data/formatters/out/opensuse-leap-153-all-projects.Dockerfile +index 16bb069b..c6f116cc 100644 +--- a/tests/lcitool/libvirt-ci/tests/data/formatters/out/opensuse-leap-153-all-projects.Dockerfile ++++ b/tests/lcitool/libvirt-ci/tests/data/formatters/out/opensuse-leap-153-all-projects.Dockerfile +@@ -193,7 +193,6 @@ RUN zypper update -y && \ + perl-YAML \ + perl-base \ + php-devel \ +- php-imagick \ + pkgconfig \ + polkit \ + python3-Pillow \ +diff --git a/tests/lcitool/libvirt-ci/tests/data/formatters/out/opensuse-leap-154-all-projects.Dockerfile b/tests/lcitool/libvirt-ci/tests/data/formatters/out/opensuse-leap-154-all-projects.Dockerfile +index 499ec816..23749f23 100644 +--- a/tests/lcitool/libvirt-ci/tests/data/formatters/out/opensuse-leap-154-all-projects.Dockerfile ++++ b/tests/lcitool/libvirt-ci/tests/data/formatters/out/opensuse-leap-154-all-projects.Dockerfile +@@ -194,7 +194,6 @@ RUN zypper update -y && \ + perl-YAML \ + perl-base \ + php-devel \ +- php-imagick \ + pkgconfig \ + polkit \ + python3-Pillow \ +diff --git a/tests/lcitool/libvirt-ci/tests/data/formatters/out/opensuse-tumbleweed-all-projects.Dockerfile b/tests/lcitool/libvirt-ci/tests/data/formatters/out/opensuse-tumbleweed-all-projects.Dockerfile +index 84e898ae..1e357bfe 100644 +--- a/tests/lcitool/libvirt-ci/tests/data/formatters/out/opensuse-tumbleweed-all-projects.Dockerfile ++++ b/tests/lcitool/libvirt-ci/tests/data/formatters/out/opensuse-tumbleweed-all-projects.Dockerfile +@@ -194,7 +194,6 @@ RUN zypper dist-upgrade -y && \ + perl-YAML \ + perl-base \ + php-devel \ +- php-imagick \ + pkgconfig \ + polkit \ + python3-Pillow \ +diff --git a/tests/lcitool/libvirt-ci/tests/data/formatters/out/ubuntu-1804-all-projects.Dockerfile b/tests/lcitool/libvirt-ci/tests/data/formatters/out/ubuntu-1804-all-projects.Dockerfile +index 9adad777..2b8715e8 100644 +--- a/tests/lcitool/libvirt-ci/tests/data/formatters/out/ubuntu-1804-all-projects.Dockerfile ++++ b/tests/lcitool/libvirt-ci/tests/data/formatters/out/ubuntu-1804-all-projects.Dockerfile +@@ -217,7 +217,6 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ + perl \ + perl-base \ + php-dev \ +- php-imagick \ + pkgconf \ + policykit-1 \ + publican \ +diff --git a/tests/lcitool/libvirt-ci/tests/data/formatters/out/ubuntu-2004-all-projects.Dockerfile b/tests/lcitool/libvirt-ci/tests/data/formatters/out/ubuntu-2004-all-projects.Dockerfile +index 2e26434d..ab976b62 100644 +--- a/tests/lcitool/libvirt-ci/tests/data/formatters/out/ubuntu-2004-all-projects.Dockerfile ++++ b/tests/lcitool/libvirt-ci/tests/data/formatters/out/ubuntu-2004-all-projects.Dockerfile +@@ -221,7 +221,6 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ + perl \ + perl-base \ + php-dev \ +- php-imagick \ + pkgconf \ + policykit-1 \ + publican \ +diff --git a/tests/lcitool/libvirt-ci/tests/data/formatters/out/ubuntu-2204-all-projects.Dockerfile b/tests/lcitool/libvirt-ci/tests/data/formatters/out/ubuntu-2204-all-projects.Dockerfile +index 560b45b9..b9c457b1 100644 +--- a/tests/lcitool/libvirt-ci/tests/data/formatters/out/ubuntu-2204-all-projects.Dockerfile ++++ b/tests/lcitool/libvirt-ci/tests/data/formatters/out/ubuntu-2204-all-projects.Dockerfile +@@ -224,7 +224,6 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ + perl \ + perl-base \ + php-dev \ +- php-imagick \ + pkgconf \ + policykit-1 \ + publican \ +diff --git a/tests/lcitool/libvirt-ci/tests/data/inventory/in/config.yml b/tests/lcitool/libvirt-ci/tests/data/inventory/in/config.yml +new file mode 100644 +index 00000000..0eafdbee +--- /dev/null ++++ b/tests/lcitool/libvirt-ci/tests/data/inventory/in/config.yml +@@ -0,0 +1,3 @@ ++install: ++ cloud_init: true ++ root_password: foo +diff --git a/tests/lcitool/libvirt-ci/tests/data/inventory/in/inventory/sample b/tests/lcitool/libvirt-ci/tests/data/inventory/in/inventory/sample +new file mode 100644 +index 00000000..83b103cd +--- /dev/null ++++ b/tests/lcitool/libvirt-ci/tests/data/inventory/in/inventory/sample +@@ -0,0 +1,12 @@ ++[centos-stream-8] ++centos-stream-8-1 ++centos-stream-8-2 ++some-other-centos-stream-8 ++ ++[fedora-37] ++fedora-test-1 ++fedora-test-2 fully_managed=True ++ ++[debian-10] ++192.168.1.30 ++ +diff --git a/tests/lcitool/libvirt-ci/tests/data/packages/out/fedora-35.yml b/tests/lcitool/libvirt-ci/tests/data/packages/out/fedora-37.yml +similarity index 99% +rename from tests/data/packages/out/fedora-35.yml +rename to tests/data/packages/out/fedora-37.yml +index 2ad108ae..465fbe19 100644 +--- a/tests/lcitool/libvirt-ci/tests/data/packages/out/fedora-35.yml ++++ b/tests/lcitool/libvirt-ci/tests/data/packages/out/fedora-37.yml +@@ -159,7 +159,6 @@ native: + - ncurses-devel + - net-snmp-devel + - net-tools +-- netcf-devel + - nettle-devel + - nfs-utils + - ninja-build +@@ -216,7 +215,6 @@ native: + - pixman-devel + - pkgconfig + - polkit +-- publican + - pulseaudio-libs-devel + - python3 + - python3-PyYAML +diff --git a/tests/lcitool/libvirt-ci/tests/test_config.py b/tests/lcitool/libvirt-ci/tests/test_config.py +index 049340f1..9a2fa7fc 100644 +--- a/tests/lcitool/libvirt-ci/tests/test_config.py ++++ b/tests/lcitool/libvirt-ci/tests/test_config.py +@@ -9,20 +9,11 @@ import pytest + import test_utils.utils as test_utils + + from pathlib import Path +-from lcitool.config import Config, ValidationError +-from lcitool.singleton import Singleton +- +- +-@pytest.fixture(autouse=True) +-def destroy_config(): +- # The following makes sure the Config singleton is deleted after each test +- # See https://docs.pytest.org/en/6.2.x/fixture.html#teardown-cleanup-aka-fixture-finalization +- yield +- del Singleton._instances[Config] ++from lcitool.config import ValidationError + + + @pytest.mark.parametrize( +- "filename", ++ "config_filename", + [ + "full.yml", + "minimal.yml", +@@ -30,21 +21,15 @@ def destroy_config(): + "unknown_key.yml", + ], + ) +-def test_config(monkeypatch, filename): +- actual_path = Path(test_utils.test_data_indir(__file__), filename) +- expected_path = Path(test_utils.test_data_outdir(__file__), filename) +- +- config = Config() ++def test_config(config, config_filename): ++ expected_path = Path(test_utils.test_data_outdir(__file__), config_filename) + +- # we have to monkeypatch the '_config_file_paths' attribute, since we don't +- # support custom inventory paths +- monkeypatch.setattr(config, "_config_file_paths", [actual_path]) + actual = config.values + test_utils.assert_yaml_matches_file(actual, expected_path) + + + @pytest.mark.parametrize( +- "filename", ++ "config_filename", + [ + "empty.yml", + "missing_mandatory_section.yml", +@@ -52,11 +37,6 @@ def test_config(monkeypatch, filename): + "missing_gitlab_section_with_gitlab_flavor.yml", + ], + ) +-def test_config_invalid(monkeypatch, filename): +- actual_path = Path(test_utils.test_data_indir(__file__), filename) +- +- config = Config() +- monkeypatch.setattr(config, "_config_file_paths", [actual_path]) +- ++def test_config_invalid(config, config_filename): + with pytest.raises(ValidationError): + config.values +diff --git a/tests/lcitool/libvirt-ci/tests/test_formatters.py b/tests/lcitool/libvirt-ci/tests/test_formatters.py +index 8abb4b3c..49b4a3c0 100644 +--- a/tests/lcitool/libvirt-ci/tests/test_formatters.py ++++ b/tests/lcitool/libvirt-ci/tests/test_formatters.py +@@ -9,8 +9,7 @@ import pytest + import test_utils.utils as test_utils + from pathlib import Path + +-from lcitool.inventory import Inventory +-from lcitool.projects import Projects ++from lcitool.targets import BuildTarget + from lcitool.formatters import ShellVariablesFormatter, JSONVariablesFormatter, DockerfileFormatter, ShellBuildEnvFormatter + + +@@ -41,56 +40,62 @@ layer_scenarios = [ + + + @pytest.mark.parametrize("project,target,arch", scenarios) +-def test_dockerfiles(project, target, arch, request): +- gen = DockerfileFormatter() +- actual = gen.format(target, [project], arch) ++def test_dockerfiles(packages, projects, targets, project, target, arch, request): ++ gen = DockerfileFormatter(projects) ++ target_obj = BuildTarget(targets, packages, target, arch) ++ actual = gen.format(target_obj, [project]) + expected_path = Path(test_utils.test_data_outdir(__file__), request.node.callspec.id + ".Dockerfile") + test_utils.assert_matches_file(actual, expected_path) + + + @pytest.mark.parametrize("project,target,arch,base,layers", layer_scenarios) +-def test_dockerfile_layers(project, target, arch, base, layers, request): +- gen = DockerfileFormatter(base, layers) +- actual = gen.format(target, [project], arch) ++def test_dockerfile_layers(packages, projects, targets, project, target, arch, base, layers, request): ++ gen = DockerfileFormatter(projects, base, layers) ++ target_obj = BuildTarget(targets, packages, target, arch) ++ actual = gen.format(target_obj, [project]) + expected_path = Path(test_utils.test_data_outdir(__file__), request.node.callspec.id + ".Dockerfile") + test_utils.assert_matches_file(actual, expected_path) + + + @pytest.mark.parametrize("project,target,arch", scenarios) +-def test_variables_shell(project, target, arch, request): +- gen = ShellVariablesFormatter() +- actual = gen.format(target, [project], arch) ++def test_variables_shell(packages, projects, targets, project, target, arch, request): ++ gen = ShellVariablesFormatter(projects) ++ target_obj = BuildTarget(targets, packages, target, arch) ++ actual = gen.format(target_obj, [project]) + expected_path = Path(test_utils.test_data_outdir(__file__), request.node.callspec.id + ".vars") + test_utils.assert_matches_file(actual, expected_path) + + + @pytest.mark.parametrize("project,target,arch", scenarios) +-def test_variables_json(project, target, arch, request): +- gen = JSONVariablesFormatter() +- actual = gen.format(target, [project], arch) ++def test_variables_json(packages, projects, targets, project, target, arch, request): ++ gen = JSONVariablesFormatter(projects) ++ target_obj = BuildTarget(targets, packages, target, arch) ++ actual = gen.format(target_obj, [project]) + expected_path = Path(test_utils.test_data_outdir(__file__), request.node.callspec.id + ".json") + test_utils.assert_matches_file(actual, expected_path) + + + @pytest.mark.parametrize("project,target,arch", scenarios) +-def test_prepbuildenv(project, target, arch, request): +- gen = ShellBuildEnvFormatter() +- actual = gen.format(target, [project], arch) ++def test_prepbuildenv(packages, projects, targets, project, target, arch, request): ++ gen = ShellBuildEnvFormatter(projects) ++ target_obj = BuildTarget(targets, packages, target, arch) ++ actual = gen.format(target_obj, [project]) + expected_path = Path(test_utils.test_data_outdir(__file__), request.node.callspec.id + ".sh") + test_utils.assert_matches_file(actual, expected_path) + + +-def test_all_projects_dockerfiles(): +- inventory = Inventory() +- all_projects = Projects().names ++def test_all_projects_dockerfiles(packages, projects, targets): ++ all_projects = projects.names + +- for target in sorted(inventory.targets): +- facts = inventory.target_facts[target] ++ for target in sorted(targets.targets): ++ target_obj = BuildTarget(targets, packages, target) ++ ++ facts = target_obj.facts + + if facts["packaging"]["format"] not in ["apk", "deb", "rpm"]: + continue + +- gen = DockerfileFormatter() +- actual = gen.format(target, all_projects, None) ++ gen = DockerfileFormatter(projects) ++ actual = gen.format(target_obj, all_projects) + expected_path = Path(test_utils.test_data_outdir(__file__), f"{target}-all-projects.Dockerfile") + test_utils.assert_matches_file(actual, expected_path) +diff --git a/tests/lcitool/libvirt-ci/tests/test_inventory.py b/tests/lcitool/libvirt-ci/tests/test_inventory.py +new file mode 100644 +index 00000000..f8e6e21f +--- /dev/null ++++ b/tests/lcitool/libvirt-ci/tests/test_inventory.py +@@ -0,0 +1,50 @@ ++# test_inventory: test lcitool Ansible inventory ++# ++# Copyright (C) 2022 Red Hat, Inc. ++# ++# SPDX-License-Identifier: GPL-2.0-or-later ++ ++import pytest ++ ++from lcitool.inventory import InventoryError ++from lcitool.targets import BuildTarget ++ ++ ++pytestmark = pytest.mark.filterwarnings("ignore:'pipes' is deprecated:DeprecationWarning") ++ ++ ++@pytest.mark.parametrize("host,target,fully_managed", [ ++ pytest.param("centos-stream-8-1", "centos-stream-8", False, id="centos-stream-8-1"), ++ pytest.param("192.168.1.30", "debian-10", False, id="debian-10"), ++ pytest.param("fedora-test-2", "fedora-37", True, id="fedora-test-2"), ++]) ++def test_host_facts(inventory, targets, host, target, fully_managed): ++ host_facts = inventory.host_facts[host] ++ assert host_facts["target"] == target ++ for key, value in targets.target_facts[target].items(): ++ assert host_facts[key] == value ++ assert host_facts.get("fully_managed", False) == fully_managed ++ ++ ++def test_expand_hosts(inventory): ++ assert sorted(inventory.expand_hosts("*centos*")) == [ ++ "centos-stream-8-1", ++ "centos-stream-8-2", ++ "some-other-centos-stream-8" ++ ] ++ with pytest.raises(InventoryError): ++ inventory.expand_hosts("debian-10") ++ ++ ++def test_host_target_name(inventory): ++ assert inventory.get_host_target_name("fedora-test-1") == "fedora-37" ++ ++ ++def test_group_vars(inventory, targets, packages, projects): ++ target = BuildTarget(targets, packages, "fedora-37") ++ group_vars = inventory.get_group_vars(target, projects, ["nbdkit"]) ++ assert "nano" in group_vars["unwanted_packages"] ++ assert "python3-libselinux" in group_vars["early_install_packages"] ++ ++ for key, value in target.facts.items(): ++ assert group_vars[key] == value +diff --git a/tests/lcitool/libvirt-ci/tests/test_manifest.py b/tests/lcitool/libvirt-ci/tests/test_manifest.py +index 7c923e0b..33ef4e3e 100644 +--- a/tests/lcitool/libvirt-ci/tests/test_manifest.py ++++ b/tests/lcitool/libvirt-ci/tests/test_manifest.py +@@ -14,7 +14,7 @@ from lcitool import util + from lcitool.manifest import Manifest + + +-def test_generate(monkeypatch): ++def test_generate(targets, packages, projects, monkeypatch): + manifest_path = Path(test_utils.test_data_indir(__file__), "manifest.yml") + + # Squish the header that contains argv with paths we don't +@@ -67,7 +67,7 @@ def test_generate(monkeypatch): + m.setattr(Path, 'glob', fake_glob) + + with open(manifest_path, "r") as fp: +- manifest = Manifest(fp, quiet=True) ++ manifest = Manifest(targets, packages, projects, fp, quiet=True) + + manifest.generate() + +@@ -134,7 +134,7 @@ def test_generate(monkeypatch): + finally: + if pytest.custom_args["regenerate_output"]: + with open(manifest_path, "r") as fp: +- manifest = Manifest(fp, quiet=True, ++ manifest = Manifest(targets, packages, projects, fp, quiet=True, + basedir=Path(test_utils.test_data_outdir(__file__))) + + manifest.generate() +diff --git a/tests/lcitool/libvirt-ci/tests/test_packages.py b/tests/lcitool/libvirt-ci/tests/test_packages.py +index dad37611..ec4492e3 100644 +--- a/tests/lcitool/libvirt-ci/tests/test_packages.py ++++ b/tests/lcitool/libvirt-ci/tests/test_packages.py +@@ -13,12 +13,11 @@ from functools import total_ordering + + from pathlib import Path + from lcitool import util +-from lcitool.inventory import Inventory +-from lcitool.projects import Project, Projects, ProjectError +-from lcitool.package import NativePackage, CrossPackage, PyPIPackage, CPANPackage ++from lcitool.projects import Project, ProjectError ++from lcitool.packages import NativePackage, CrossPackage, PyPIPackage, CPANPackage ++from lcitool.targets import BuildTarget + +- +-ALL_TARGETS = sorted(Inventory().targets) ++from conftest import ALL_TARGETS + + + def get_non_cross_targets(): +@@ -43,14 +42,14 @@ def packages_as_dict(raw_pkgs): + + + @pytest.fixture +-def test_project(): +- return Project("packages", ++def test_project(projects): ++ return Project(projects, "packages", + Path(test_utils.test_data_indir(__file__), "packages.yml")) + + +-def test_verify_all_mappings_and_packages(): ++def test_verify_all_mappings_and_packages(packages): + expected_path = Path(test_utils.test_data_indir(__file__), "packages.yml") +- actual = {"packages": sorted(Projects().mappings["mappings"].keys())} ++ actual = {"packages": sorted(packages.mappings.keys())} + + test_utils.assert_yaml_matches_file(actual, expected_path) + +@@ -66,14 +65,14 @@ cross_params = [ + + + @pytest.mark.parametrize("target,arch", native_params + cross_params) +-def test_package_resolution(test_project, target, arch): ++def test_package_resolution(targets, packages, test_project, target, arch): + if arch is None: + outfile = f"{target}.yml" + else: + outfile = f"{target}-cross-{arch}.yml" + expected_path = Path(test_utils.test_data_outdir(__file__), outfile) +- pkgs = test_project.get_packages(Inventory().target_facts[target], +- cross_arch=arch) ++ target_obj = BuildTarget(targets, packages, target, arch) ++ pkgs = test_project.get_packages(target_obj) + actual = packages_as_dict(pkgs) + + test_utils.assert_yaml_matches_file(actual, expected_path) +@@ -83,10 +82,10 @@ def test_package_resolution(test_project, target, arch): + "target", + [pytest.param(target, id=target) for target in get_non_cross_targets()], + ) +-def test_unsupported_cross_platform(test_project, target): ++def test_unsupported_cross_platform(targets, packages, test_project, target): + with pytest.raises(ProjectError): +- test_project.get_packages(Inventory().target_facts[target], +- cross_arch="s390x") ++ target_obj = BuildTarget(targets, packages, target, "s390x") ++ test_project.get_packages(target_obj) + + + @pytest.mark.parametrize( +@@ -96,10 +95,10 @@ def test_unsupported_cross_platform(test_project, target): + pytest.param("fedora-rawhide", "s390x", id="fedora-rawhide-cross-s390x"), + ], + ) +-def test_cross_platform_arch_mismatch(test_project, target, arch): ++def test_cross_platform_arch_mismatch(targets, packages, test_project, target, arch): + with pytest.raises(ProjectError): +- test_project.get_packages(Inventory().target_facts[target], +- cross_arch=arch) ++ target_obj = BuildTarget(targets, packages, target, arch) ++ test_project.get_packages(target_obj) + + + @total_ordering +@@ -124,11 +123,11 @@ class MappingKey(namedtuple('MappingKey', ['components', 'priority'])): + return self.components < other.components + + +-def mapping_keys_product(): ++def mapping_keys_product(targets): + basekeys = set() + + basekeys.add(MappingKey(("default", ), 0)) +- for target, facts in Inventory().target_facts.items(): ++ for target, facts in targets.target_facts.items(): + fmt = facts["packaging"]["format"] + name = facts["os"]["name"] + ver = facts["os"]["version"] +@@ -148,10 +147,11 @@ def mapping_keys_product(): + return basekeys + archkeys + crossarchkeys + crosspolicykeys + + +-def test_project_mappings_sorting(): +- mappings = Projects().mappings["mappings"] ++@pytest.mark.parametrize("key", ["mappings", "pypi_mappings", "cpan_mappings"]) ++def test_project_mappings_sorting(targets, packages, key): ++ mappings = getattr(packages, key) + +- all_expect_keys = mapping_keys_product() ++ all_expect_keys = mapping_keys_product(targets) + for package, entries in mappings.items(): + got_keys = list(entries.keys()) + expect_keys = list(filter(lambda k: k in got_keys, all_expect_keys)) +diff --git a/tests/lcitool/libvirt-ci/tests/test_projects.py b/tests/lcitool/libvirt-ci/tests/test_projects.py +index 760d331c..84493834 100644 +--- a/tests/lcitool/libvirt-ci/tests/test_projects.py ++++ b/tests/lcitool/libvirt-ci/tests/test_projects.py +@@ -6,37 +6,25 @@ + + import pytest + +-from lcitool.projects import Projects +-from lcitool.inventory import Inventory ++from lcitool.targets import BuildTarget + ++from conftest import ALL_PROJECTS + +-projects = Projects() +-ALL_PROJECTS = sorted(projects.names + list(projects.internal_projects.keys())) + +- +-@pytest.mark.parametrize( +- "name", +- ALL_PROJECTS +-) +-def test_project_packages(name): ++@pytest.fixture(params=ALL_PROJECTS) ++def project(request, projects): + try: +- project = projects.projects[name] ++ return projects.public[request.param] + except KeyError: +- project = projects.internal_projects[name] +- target = Inventory().targets[0] +- facts = Inventory().target_facts[target] +- project.get_packages(facts) ++ return projects.internal[request.param] + + +-@pytest.mark.parametrize( +- "name", +- ALL_PROJECTS +-) +-def test_project_package_sorting(name): +- try: +- project = projects.projects[name] +- except KeyError: +- project = projects.internal_projects[name] ++def test_project_packages(targets, packages, project): ++ target = BuildTarget(targets, packages, targets.targets[0]) ++ project.get_packages(target) ++ ++ ++def test_project_package_sorting(project): + pkgs = project._load_generic_packages() + + otherpkgs = sorted(pkgs) +diff --git a/tests/lcitool/libvirt-ci/tests/test_misc.py b/tests/lcitool/libvirt-ci/tests/test_targets.py +similarity index 77% +rename from tests/test_misc.py +rename to tests/test_targets.py +index 5f1af1e9..61985f7f 100644 +--- a/tests/lcitool/libvirt-ci/tests/test_misc.py ++++ b/tests/lcitool/libvirt-ci/tests/test_targets.py +@@ -1,4 +1,4 @@ +-# test_misc: test uncategorized aspects of lcitool ++# test_targets: test lcitool target facts + # + # Copyright (C) 2022 Red Hat, Inc. + # +@@ -6,17 +6,14 @@ + + import pytest + +-from lcitool.inventory import Inventory +- +-inventory = Inventory() +-ALL_TARGETS = sorted(inventory.targets) ++from conftest import ALL_TARGETS + + + @pytest.mark.parametrize("target", ALL_TARGETS) +-def test_group_vars(target): ++def test_group_vars(targets, target): + """Check selected group_vars fields for correctness.""" + +- facts = inventory.target_facts[target] ++ facts = targets.target_facts[target] + split = target.split('-', maxsplit=1) + target_os = split[0] + target_version = split[1].replace("-", "") +diff --git a/tests/lcitool/mappings.yml b/tests/lcitool/mappings.yml +new file mode 100644 +index 0000000000..4b4b44adf1 +--- /dev/null ++++ b/tests/lcitool/mappings.yml +@@ -0,0 +1,60 @@ ++mappings: ++ flake8: ++ OpenSUSELeap153: ++ ++ meson: ++ OpenSUSELeap153: ++ ++ python3: ++ OpenSUSELeap153: python39-base ++ ++ python3-PyYAML: ++ OpenSUSELeap153: ++ ++ python3-devel: ++ OpenSUSELeap153: python39-devel ++ ++ python3-docutils: ++ OpenSUSELeap153: ++ ++ python3-numpy: ++ OpenSUSELeap153: ++ ++ python3-opencv: ++ OpenSUSELeap153: ++ ++ python3-pillow: ++ OpenSUSELeap153: ++ ++ python3-pip: ++ OpenSUSELeap153: python39-pip ++ ++ python3-pillow: ++ OpenSUSELeap153: ++ ++ python3-selinux: ++ OpenSUSELeap153: ++ ++ python3-setuptools: ++ OpenSUSELeap153: python39-setuptools ++ ++ python3-sphinx: ++ OpenSUSELeap153: ++ ++ python3-sphinx-rtd-theme: ++ OpenSUSELeap153: ++ ++ python3-venv: ++ OpenSUSELeap153: python39-base ++ ++ python3-wheel: ++ OpenSUSELeap153: python39-pip ++ ++pypi_mappings: ++ # Request more recent version ++ meson: ++ default: meson==0.63.2 ++ ++ # Drop packages that need devel headers ++ python3-numpy: ++ OpenSUSELeap153: +diff --git a/tests/lcitool/refresh b/tests/lcitool/refresh +index fa966e4009..7a4cd6fd32 100755 +--- a/tests/lcitool/refresh ++++ b/tests/lcitool/refresh +@@ -108,10 +108,10 @@ try: + # Standard native builds + # + generate_dockerfile("alpine", "alpine-316") +- generate_dockerfile("centos8", "centos-stream-8") ++ generate_dockerfile("centos9", "centos-stream-9") + generate_dockerfile("debian-amd64", "debian-11", + trailer="".join(debian11_extras)) +- generate_dockerfile("fedora", "fedora-35") ++ generate_dockerfile("fedora", "fedora-37") + generate_dockerfile("opensuse-leap", "opensuse-leap-153") + generate_dockerfile("ubuntu2004", "ubuntu-2004", + trailer="".join(ubuntu2004_tsanhack)) +@@ -161,12 +161,12 @@ try: + trailer=cross_build("s390x-linux-gnu-", + "s390x-softmmu,s390x-linux-user")) + +- generate_dockerfile("fedora-win32-cross", "fedora-35", ++ generate_dockerfile("fedora-win32-cross", "fedora-37", + cross="mingw32", + trailer=cross_build("i686-w64-mingw32-", + "i386-softmmu")) + +- generate_dockerfile("fedora-win64-cross", "fedora-35", ++ generate_dockerfile("fedora-win64-cross", "fedora-37", + cross="mingw64", + trailer=cross_build("x86_64-w64-mingw32-", + "x86_64-softmmu")) +diff --git a/tests/lcitool/targets/centos-stream-8.yml b/tests/lcitool/targets/centos-stream-8.yml +new file mode 100644 +index 0000000000..6b11160fd1 +--- /dev/null ++++ b/tests/lcitool/targets/centos-stream-8.yml +@@ -0,0 +1,3 @@ ++paths: ++ pip3: /usr/bin/pip3.8 ++ python: /usr/bin/python3.8 +diff --git a/tests/lcitool/targets/opensuse-leap-153.yml b/tests/lcitool/targets/opensuse-leap-153.yml +new file mode 100644 +index 0000000000..683016e007 +--- /dev/null ++++ b/tests/lcitool/targets/opensuse-leap-153.yml +@@ -0,0 +1,3 @@ ++paths: ++ pip3: /usr/bin/pip3.9 ++ python: /usr/bin/python3.9 +diff --git a/tests/qemu-iotests/061 b/tests/qemu-iotests/061 +index 509ad247cd..168a5831dd 100755 +--- a/tests/qemu-iotests/061 ++++ b/tests/qemu-iotests/061 +@@ -326,12 +326,14 @@ $QEMU_IMG amend -o "data_file=foo" "$TEST_IMG" + echo + _make_test_img -o "compat=1.1,data_file=$TEST_IMG.data" 64M + $QEMU_IMG amend -o "data_file=foo" "$TEST_IMG" +-_img_info --format-specific ++$QEMU_IO -c "read 0 4k" "$TEST_IMG" 2>&1 | _filter_testdir | _filter_imgfmt ++$QEMU_IO -c "open -o data-file.filename=$TEST_IMG.data,file.filename=$TEST_IMG" -c "read 0 4k" | _filter_qemu_io + TEST_IMG="data-file.filename=$TEST_IMG.data,file.filename=$TEST_IMG" _img_info --format-specific --image-opts + + echo + $QEMU_IMG amend -o "data_file=" --image-opts "data-file.filename=$TEST_IMG.data,file.filename=$TEST_IMG" +-_img_info --format-specific ++$QEMU_IO -c "read 0 4k" "$TEST_IMG" 2>&1 | _filter_testdir | _filter_imgfmt ++$QEMU_IO -c "open -o data-file.filename=$TEST_IMG.data,file.filename=$TEST_IMG" -c "read 0 4k" | _filter_qemu_io + TEST_IMG="data-file.filename=$TEST_IMG.data,file.filename=$TEST_IMG" _img_info --format-specific --image-opts + + echo +diff --git a/tests/qemu-iotests/061.out b/tests/qemu-iotests/061.out +index 139fc68177..24c33add7c 100644 +--- a/tests/qemu-iotests/061.out ++++ b/tests/qemu-iotests/061.out +@@ -545,7 +545,9 @@ Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 + qemu-img: data-file can only be set for images that use an external data file + + Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 data_file=TEST_DIR/t.IMGFMT.data +-qemu-img: Could not open 'TEST_DIR/t.IMGFMT': Could not open 'foo': No such file or directory ++qemu-io: can't open device TEST_DIR/t.IMGFMT: Could not open 'foo': No such file or directory ++read 4096/4096 bytes at offset 0 ++4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + image: TEST_DIR/t.IMGFMT + file format: IMGFMT + virtual size: 64 MiB (67108864 bytes) +@@ -560,7 +562,9 @@ Format specific information: + corrupt: false + extended l2: false + +-qemu-img: Could not open 'TEST_DIR/t.IMGFMT': 'data-file' is required for this image ++qemu-io: can't open device TEST_DIR/t.IMGFMT: 'data-file' is required for this image ++read 4096/4096 bytes at offset 0 ++4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + image: TEST_DIR/t.IMGFMT + file format: IMGFMT + virtual size: 64 MiB (67108864 bytes) +diff --git a/tests/qemu-iotests/244 b/tests/qemu-iotests/244 +index 3e61fa25bb..bb9cc6512f 100755 +--- a/tests/qemu-iotests/244 ++++ b/tests/qemu-iotests/244 +@@ -215,9 +215,22 @@ $QEMU_IMG convert -f $IMGFMT -O $IMGFMT -n -C "$TEST_IMG.src" "$TEST_IMG" + $QEMU_IMG compare -f $IMGFMT -F $IMGFMT "$TEST_IMG.src" "$TEST_IMG" + + # blkdebug doesn't support copy offloading, so this tests the error path +-$QEMU_IMG amend -f $IMGFMT -o "data_file=blkdebug::$TEST_IMG.data" "$TEST_IMG" +-$QEMU_IMG convert -f $IMGFMT -O $IMGFMT -n -C "$TEST_IMG.src" "$TEST_IMG" +-$QEMU_IMG compare -f $IMGFMT -F $IMGFMT "$TEST_IMG.src" "$TEST_IMG" ++test_img_with_blkdebug="json:{ ++ 'driver': 'qcow2', ++ 'file': { ++ 'driver': 'file', ++ 'filename': '$TEST_IMG' ++ }, ++ 'data-file': { ++ 'driver': 'blkdebug', ++ 'image': { ++ 'driver': 'file', ++ 'filename': '$TEST_IMG.data' ++ } ++ } ++}" ++$QEMU_IMG convert -f $IMGFMT -O $IMGFMT -n -C "$TEST_IMG.src" "$test_img_with_blkdebug" ++$QEMU_IMG compare -f $IMGFMT -F $IMGFMT "$TEST_IMG.src" "$test_img_with_blkdebug" + + echo + echo "=== Flushing should flush the data file ===" +diff --git a/tests/qemu-iotests/270 b/tests/qemu-iotests/270 +index 74352342db..c37b674aa2 100755 +--- a/tests/qemu-iotests/270 ++++ b/tests/qemu-iotests/270 +@@ -60,8 +60,16 @@ _make_test_img -o cluster_size=2M,data_file="$TEST_IMG.orig" \ + # "write" 2G of data without using any space. + # (qemu-img create does not like it, though, because null-co does not + # support image creation.) +-$QEMU_IMG amend -o data_file="json:{'driver':'null-co',,'size':'4294967296'}" \ +- "$TEST_IMG" ++test_img_with_null_data="json:{ ++ 'driver': '$IMGFMT', ++ 'file': { ++ 'filename': '$TEST_IMG' ++ }, ++ 'data-file': { ++ 'driver': 'null-co', ++ 'size':'4294967296' ++ } ++}" + + # This gives us a range of: + # 2^31 - 512 + 768 - 1 = 2^31 + 255 > 2^31 +@@ -74,7 +82,7 @@ $QEMU_IMG amend -o data_file="json:{'driver':'null-co',,'size':'4294967296'}" \ + # on L2 boundaries, we need large L2 tables; hence the cluster size of + # 2 MB. (Anything from 256 kB should work, though, because then one L2 + # table covers 8 GB.) +-$QEMU_IO -c "write 768 $((2 ** 31 - 512))" "$TEST_IMG" | _filter_qemu_io ++$QEMU_IO -c "write 768 $((2 ** 31 - 512))" "$test_img_with_null_data" | _filter_qemu_io + + _check_test_img + +diff --git a/tests/vm/centos b/tests/vm/centos +index 097a9ca14d..d25c8f8b5b 100755 +--- a/tests/vm/centos ++++ b/tests/vm/centos +@@ -26,8 +26,8 @@ class CentosVM(basevm.BaseVM): + export SRC_ARCHIVE=/dev/vdb; + sudo chmod a+r $SRC_ARCHIVE; + tar -xf $SRC_ARCHIVE; +- make docker-test-block@centos8 {verbose} J={jobs} NETWORK=1; +- make docker-test-quick@centos8 {verbose} J={jobs} NETWORK=1; ++ make docker-test-block@centos9 {verbose} J={jobs} NETWORK=1; ++ make docker-test-quick@centos9 {verbose} J={jobs} NETWORK=1; + """ + + def build_image(self, img): |