2015-02-10 13:19:34 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
|
|
|
|
# This program is free software; you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
|
|
# the Free Software Foundation; version 2 of the License.
|
|
|
|
#
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU Library General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
2016-09-21 12:49:13 +00:00
|
|
|
# along with this program; if not, see <https://gnu.org/licenses/>.
|
2015-02-10 13:19:34 +00:00
|
|
|
|
|
|
|
|
|
|
|
import tempfile
|
2016-05-03 14:31:20 +00:00
|
|
|
import os
|
2015-02-10 13:19:34 +00:00
|
|
|
|
|
|
|
from kobo.shortcuts import run
|
|
|
|
|
2015-03-12 21:12:38 +00:00
|
|
|
from pungi.wrappers.repoclosure import RepoclosureWrapper
|
|
|
|
from pungi.arch import get_valid_arches
|
|
|
|
from pungi.phases.base import PhaseBase
|
|
|
|
from pungi.phases.gather import get_lookaside_repos
|
2016-05-03 14:31:20 +00:00
|
|
|
from pungi.util import rmtree, is_arch_multilib, failable
|
2015-02-10 13:19:34 +00:00
|
|
|
|
|
|
|
|
|
|
|
class TestPhase(PhaseBase):
|
|
|
|
name = "test"
|
|
|
|
|
|
|
|
def run(self):
|
|
|
|
run_repoclosure(self.compose)
|
2016-05-03 14:31:20 +00:00
|
|
|
check_image_sanity(self.compose)
|
2015-02-10 13:19:34 +00:00
|
|
|
|
|
|
|
|
|
|
|
def run_repoclosure(compose):
|
|
|
|
repoclosure = RepoclosureWrapper()
|
|
|
|
|
|
|
|
# TODO: Special handling for src packages (use repoclosure param builddeps)
|
|
|
|
|
|
|
|
msg = "Running repoclosure"
|
|
|
|
compose.log_info("[BEGIN] %s" % msg)
|
|
|
|
|
|
|
|
# Variant repos
|
|
|
|
all_repos = {} # to be used as lookaside for the self-hosting check
|
|
|
|
all_arches = set()
|
|
|
|
for arch in compose.get_arches():
|
2016-01-20 11:53:08 +00:00
|
|
|
is_multilib = is_arch_multilib(compose.conf, arch)
|
2015-02-10 13:19:34 +00:00
|
|
|
arches = get_valid_arches(arch, is_multilib)
|
|
|
|
all_arches.update(arches)
|
|
|
|
for variant in compose.get_variants(arch=arch):
|
2016-02-29 11:38:54 +00:00
|
|
|
if variant.is_empty:
|
|
|
|
continue
|
2015-02-10 13:19:34 +00:00
|
|
|
lookaside = {}
|
|
|
|
if variant.parent:
|
|
|
|
repo_id = "repoclosure-%s.%s" % (variant.parent.uid, arch)
|
|
|
|
repo_dir = compose.paths.compose.repository(arch=arch, variant=variant.parent)
|
|
|
|
lookaside[repo_id] = repo_dir
|
|
|
|
|
|
|
|
repos = {}
|
|
|
|
repo_id = "repoclosure-%s.%s" % (variant.uid, arch)
|
|
|
|
repo_dir = compose.paths.compose.repository(arch=arch, variant=variant)
|
|
|
|
repos[repo_id] = repo_dir
|
|
|
|
|
2016-08-22 14:08:25 +00:00
|
|
|
if compose.conf["release_is_layered"]:
|
2015-02-10 13:19:34 +00:00
|
|
|
for i, lookaside_url in enumerate(get_lookaside_repos(compose, arch, variant)):
|
|
|
|
lookaside["lookaside-%s.%s-%s" % (variant.uid, arch, i)] = lookaside_url
|
|
|
|
|
|
|
|
cmd = repoclosure.get_repoclosure_cmd(repos=repos, lookaside=lookaside, arch=arches)
|
|
|
|
# Use temp working directory directory as workaround for
|
|
|
|
# https://bugzilla.redhat.com/show_bug.cgi?id=795137
|
|
|
|
tmp_dir = tempfile.mkdtemp(prefix="repoclosure_")
|
|
|
|
try:
|
|
|
|
run(cmd, logfile=compose.paths.log.log_file(arch, "repoclosure-%s" % variant), show_cmd=True, can_fail=True, workdir=tmp_dir)
|
|
|
|
finally:
|
|
|
|
rmtree(tmp_dir)
|
|
|
|
|
|
|
|
all_repos.update(repos)
|
|
|
|
all_repos.update(lookaside)
|
|
|
|
repo_id = "repoclosure-%s.%s" % (variant.uid, "src")
|
|
|
|
repo_dir = compose.paths.compose.repository(arch="src", variant=variant)
|
|
|
|
all_repos[repo_id] = repo_dir
|
|
|
|
|
|
|
|
# A SRPM can be built on any arch and is always rebuilt before building on the target arch.
|
|
|
|
# This means the deps can't be always satisfied within one tree arch.
|
|
|
|
# As a workaround, let's run the self-hosting check across all repos.
|
|
|
|
|
|
|
|
# XXX: This doesn't solve a situation, when a noarch package is excluded due to ExcludeArch/ExclusiveArch and it's still required on that arch.
|
|
|
|
# In this case, it's an obvious bug in the test.
|
|
|
|
|
|
|
|
# check BuildRequires (self-hosting)
|
|
|
|
cmd = repoclosure.get_repoclosure_cmd(repos=all_repos, arch=all_arches, builddeps=True)
|
|
|
|
# Use temp working directory directory as workaround for
|
|
|
|
# https://bugzilla.redhat.com/show_bug.cgi?id=795137
|
|
|
|
tmp_dir = tempfile.mkdtemp(prefix="repoclosure_")
|
|
|
|
try:
|
|
|
|
run(cmd, logfile=compose.paths.log.log_file("global", "repoclosure-builddeps"), show_cmd=True, can_fail=True, workdir=tmp_dir)
|
|
|
|
finally:
|
|
|
|
rmtree(tmp_dir)
|
|
|
|
|
|
|
|
compose.log_info("[DONE ] %s" % msg)
|
2016-05-03 14:31:20 +00:00
|
|
|
|
|
|
|
|
|
|
|
def check_image_sanity(compose):
|
|
|
|
"""
|
|
|
|
Go through all images in manifest and make basic sanity tests on them. If
|
2016-08-16 08:11:24 +00:00
|
|
|
any check fails for a failable deliverable, a message will be printed and
|
|
|
|
logged. Otherwise the compose will be aborted.
|
2016-05-03 14:31:20 +00:00
|
|
|
"""
|
|
|
|
im = compose.im
|
2016-06-06 08:29:40 +00:00
|
|
|
for variant in compose.get_variants():
|
|
|
|
if variant.uid not in im.images:
|
|
|
|
continue
|
|
|
|
for arch in variant.arches:
|
|
|
|
if arch not in im.images[variant.uid]:
|
|
|
|
continue
|
2016-08-16 08:11:24 +00:00
|
|
|
for img in im.images[variant.uid][arch]:
|
|
|
|
check(compose, variant, arch, img)
|
2016-05-03 14:31:20 +00:00
|
|
|
|
|
|
|
|
|
|
|
def check(compose, variant, arch, image):
|
|
|
|
path = os.path.join(compose.paths.compose.topdir(), image.path)
|
|
|
|
deliverable = getattr(image, 'deliverable')
|
2016-06-24 07:44:40 +00:00
|
|
|
can_fail = getattr(image, 'can_fail', False)
|
|
|
|
with failable(compose, can_fail, variant, arch, deliverable,
|
|
|
|
subvariant=image.subvariant):
|
2016-05-03 14:31:20 +00:00
|
|
|
with open(path) as f:
|
2016-07-22 11:58:29 +00:00
|
|
|
iso = is_iso(f)
|
|
|
|
if image.format == 'iso' and not iso:
|
2016-05-03 14:31:20 +00:00
|
|
|
raise RuntimeError('%s does not look like an ISO file' % path)
|
2016-08-10 11:02:56 +00:00
|
|
|
if (image.arch in ('x86_64', 'i386') and
|
|
|
|
image.bootable and
|
|
|
|
not has_mbr(f) and
|
|
|
|
not has_gpt(f) and
|
|
|
|
not (iso and has_eltorito(f))):
|
2016-05-03 14:31:20 +00:00
|
|
|
raise RuntimeError(
|
2016-07-22 11:58:29 +00:00
|
|
|
'%s is supposed to be bootable, but does not have MBR nor '
|
|
|
|
'GPT nor is it a bootable ISO' % path)
|
2016-08-16 08:11:24 +00:00
|
|
|
# If exception is raised above, failable may catch it, in which case
|
|
|
|
# nothing else will happen.
|
2016-05-03 14:31:20 +00:00
|
|
|
|
|
|
|
|
|
|
|
def _check_magic(f, offset, bytes):
|
|
|
|
"""Check that the file has correct magic number at correct offset."""
|
|
|
|
f.seek(offset)
|
|
|
|
return f.read(len(bytes)) == bytes
|
|
|
|
|
|
|
|
|
|
|
|
def is_iso(f):
|
|
|
|
return _check_magic(f, 0x8001, 'CD001')
|
|
|
|
|
|
|
|
|
|
|
|
def has_mbr(f):
|
|
|
|
return _check_magic(f, 0x1fe, '\x55\xAA')
|
|
|
|
|
|
|
|
|
|
|
|
def has_gpt(f):
|
|
|
|
return _check_magic(f, 0x200, 'EFI PART')
|
2016-07-22 11:58:29 +00:00
|
|
|
|
|
|
|
|
|
|
|
def has_eltorito(f):
|
|
|
|
return _check_magic(f, 0x8801, 'CD001\1EL TORITO SPECIFICATION')
|