Skip to content

Commit d28e5fe

Browse files
authored
Merge pull request ceph#63887 from phlogistonjohn/jjm-bwc-improvements
Assorted build-with-container improvements
2 parents b90d65a + 8f6bcce commit d28e5fe

File tree

2 files changed

+107
-15
lines changed

2 files changed

+107
-15
lines changed

src/script/build-with-container.py

Lines changed: 106 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@
7070
import contextlib
7171
import enum
7272
import glob
73+
import hashlib
74+
import functools
75+
import json
7376
import logging
7477
import os
7578
import pathlib
@@ -78,6 +81,7 @@
7881
import shutil
7982
import subprocess
8083
import sys
84+
import time
8185

8286
log = logging.getLogger()
8387

@@ -149,6 +153,18 @@ class DidNotExecute(Exception):
149153
pass
150154

151155

156+
_CONTAINER_SOURCES = [
157+
"Dockerfile.build",
158+
"src/script/lib-build.sh",
159+
"src/script/run-make.sh",
160+
"ceph.spec.in",
161+
"do_cmake.sh",
162+
"install-deps.sh",
163+
"run-make-check.sh",
164+
"src/script/buildcontainer-setup.sh",
165+
]
166+
167+
152168
def _cmdstr(cmd):
153169
return " ".join(shlex.quote(c) for c in cmd)
154170

@@ -218,6 +234,9 @@ def _git_command(ctx, args):
218234
return cmd
219235

220236

237+
# Assume that the git version will not be changing after the 1st time
238+
# the command is run.
239+
@functools.cache
221240
def _git_current_branch(ctx):
222241
cmd = _git_command(ctx, ["rev-parse", "--abbrev-ref", "HEAD"])
223242
res = _run(cmd, check=True, capture_output=True)
@@ -234,6 +253,20 @@ def _git_current_sha(ctx, short=True):
234253
return res.stdout.decode("utf8").strip()
235254

236255

256+
@functools.cache
257+
def _hash_sources(bsize=4096):
258+
hh = hashlib.sha256()
259+
buf = bytearray(bsize)
260+
for path in sorted(_CONTAINER_SOURCES):
261+
with open(path, "rb") as fh:
262+
while True:
263+
rlen = fh.readinto(buf)
264+
hh.update(buf[:rlen])
265+
if rlen < len(buf):
266+
break
267+
return f"sha256:{hh.hexdigest()}"
268+
269+
237270
class Steps(StrEnum):
238271
DNF_CACHE = "dnfcache"
239272
BUILD_CONTAINER = "build-container"
@@ -396,6 +429,7 @@ class Builder:
396429

397430
def __init__(self):
398431
self._did_steps = set()
432+
self._reported_failed = False
399433

400434
def wants(self, step, ctx, *, force=False, top=False):
401435
log.info("want to execute build step: %s", step)
@@ -407,13 +441,35 @@ def wants(self, step, ctx, *, force=False, top=False):
407441
return
408442
if not self._did_steps:
409443
prepare_env_once(ctx)
410-
self._steps[step](ctx)
411-
self._did_steps.add(step)
412-
log.info("step done: %s", step)
444+
with self._timer(step):
445+
self._steps[step](ctx)
446+
self._did_steps.add(step)
413447

414448
def available_steps(self):
415449
return [str(k) for k in self._steps]
416450

451+
@contextlib.contextmanager
452+
def _timer(self, step):
453+
ns = argparse.Namespace(start=time.monotonic())
454+
status = "not-started"
455+
try:
456+
yield ns
457+
status = "completed"
458+
except Exception:
459+
status = "failed"
460+
raise
461+
finally:
462+
ns.end = time.monotonic()
463+
ns.duration = int(ns.end - ns.start)
464+
hrs, _rest = map(int, divmod(ns.duration, 3600))
465+
mins, secs = map(int, divmod(_rest, 60))
466+
ns.duration_hms = f"{hrs:02}:{mins:02}:{secs:02}"
467+
if not self._reported_failed:
468+
log.info(
469+
"step done: %s %s in %s", step, status, ns.duration_hms
470+
)
471+
self._reported_failed = status == "failed"
472+
417473
@classmethod
418474
def set(self, step):
419475
def wrap(f):
@@ -462,6 +518,7 @@ def build_container(ctx):
462518
"--pull",
463519
"-t",
464520
ctx.image_name,
521+
f"--label=io.ceph.build-with-container.src={_hash_sources()}",
465522
f"--build-arg=JENKINS_HOME={ctx.cli.homedir}",
466523
f"--build-arg=CEPH_BASE_BRANCH={ctx.base_branch()}",
467524
]
@@ -482,32 +539,58 @@ def build_container(ctx):
482539
_run(cmd, check=True, ctx=ctx)
483540

484541

485-
@Builder.set(Steps.CONTAINER)
486-
def get_container(ctx):
487-
"""Build or fetch a container image that we will build in."""
542+
def _check_cached_image(ctx):
488543
inspect_cmd = [
489544
ctx.container_engine,
490545
"image",
491546
"inspect",
492547
ctx.image_name,
493548
]
549+
res = _run(inspect_cmd, check=False, capture_output=True)
550+
if res.returncode != 0:
551+
log.info("Container image %s not present", ctx.image_name)
552+
return False, False
553+
554+
log.info("Container image %s present", ctx.image_name)
555+
ctr_info = json.loads(res.stdout)[0]
556+
labels = {}
557+
if "Labels" in ctr_info:
558+
labels = ctr_info["Labels"]
559+
elif "Labels" in ctr_info.get("ContainerConfig", {}):
560+
labels = ctr_info["ContainerConfig"]["Labels"]
561+
elif "Labels" in ctr_info.get("Config", {}):
562+
labels = ctr_info["Config"]["Labels"]
563+
saved_hash = labels.get("io.ceph.build-with-container.src", "")
564+
curr_hash = _hash_sources()
565+
if saved_hash == curr_hash:
566+
log.info("Container passes source check")
567+
return True, True
568+
log.info("Container sources do not match: %s", curr_hash)
569+
return True, False
570+
571+
572+
@Builder.set(Steps.CONTAINER)
573+
def get_container(ctx):
574+
"""Build or fetch a container image that we will build in."""
494575
pull_cmd = [
495576
ctx.container_engine,
496577
"pull",
497578
ctx.image_name,
498579
]
499580
allowed = ctx.cli.image_sources or ImageSource
500581
if ImageSource.CACHE in allowed:
501-
res = _run(inspect_cmd, check=False, capture_output=True)
502-
if res.returncode == 0:
503-
log.info("Container image %s present", ctx.image_name)
582+
log.info("Checking for cached image")
583+
present, hash_ok = _check_cached_image(ctx)
584+
if present and hash_ok or len(allowed) == 1:
504585
return
505-
log.info("Container image %s not present", ctx.image_name)
506586
if ImageSource.PULL in allowed:
587+
log.info("Checking for image in remote repository")
507588
res = _run(pull_cmd, check=False, capture_output=True)
508589
if res.returncode == 0:
509590
log.info("Container image %s pulled successfully", ctx.image_name)
510-
return
591+
present, hash_ok = _check_cached_image(ctx)
592+
if present and hash_ok:
593+
return
511594
log.info("Container image %s needed", ctx.image_name)
512595
if ImageSource.BUILD in allowed:
513596
ctx.build.wants(Steps.BUILD_CONTAINER, ctx)
@@ -598,6 +681,11 @@ def bc_make_source_rpm(ctx):
598681
_run(cmd, check=True, ctx=ctx)
599682

600683

684+
def _glob_search(ctx, pattern):
685+
overlay = ctx.overlay()
686+
return glob.glob(pattern, root_dir=overlay.upper if overlay else None)
687+
688+
601689
@Builder.set(Steps.RPM)
602690
def bc_build_rpm(ctx):
603691
"""Build RPMs from SRPM."""
@@ -618,7 +706,7 @@ def bc_build_rpm(ctx):
618706
ctx.cli.ceph_version
619707
)
620708
srpm_glob = f"ceph-{srpm_version}.*.src.rpm"
621-
paths = glob.glob(srpm_glob)
709+
paths = _glob_search(ctx, srpm_glob)
622710
if len(paths) > 1:
623711
raise RuntimeError(
624712
"too many matching source rpms"
@@ -628,8 +716,11 @@ def bc_build_rpm(ctx):
628716
if not paths:
629717
# no matches. build a new srpm
630718
ctx.build.wants(Steps.SOURCE_RPM, ctx)
631-
paths = glob.glob(srpm_glob)
632-
assert paths
719+
paths = _glob_search(ctx, srpm_glob)
720+
if not paths:
721+
raise RuntimeError(
722+
f"unable to find source rpm(s) matching {srpm_glob}"
723+
)
633724
srpm_path = pathlib.Path(ctx.cli.homedir) / paths[0]
634725
topdir = pathlib.Path(ctx.cli.homedir) / "rpmbuild"
635726
if ctx.cli.build_dir:
@@ -640,7 +731,7 @@ def bc_build_rpm(ctx):
640731
'rpmbuild',
641732
'--rebuild',
642733
f'-D_topdir {topdir}',
643-
] + list(ctx.cli.rpmbuild_arg) + [str(srpm_path)]
734+
] + list(ctx.cli.rpmbuild_arg or []) + [str(srpm_path)]
644735
rpmbuild_cmd = ' '.join(shlex.quote(cmd) for cmd in rpmbuild_args)
645736
cmd = _container_cmd(
646737
ctx,

src/script/buildcontainer-setup.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ install_container_deps() {
55
# set JENKINS_HOME in order to have the build container look as much
66
# like an existing jenkins build environment as possible
77
export JENKINS_HOME=/ceph
8+
export WITH_CRIMSON=true
89
prepare
910
}
1011

0 commit comments

Comments
 (0)