parent
934863ba3b
commit
6310f188be
11 changed files with 220 additions and 19 deletions
@ -0,0 +1,2 @@ |
||||
/build_* |
||||
/test_* |
@ -1,4 +0,0 @@ |
||||
FROM archlinux:latest |
||||
|
||||
ADD install.sh /usr/sbin/docker-arch-install |
||||
RUN docker-arch-install |
@ -0,0 +1,6 @@ |
||||
{ |
||||
"base_image": "archlinux:latest", |
||||
"env": { |
||||
"CI": "1" |
||||
} |
||||
} |
@ -0,0 +1,182 @@ |
||||
#!/usr/bin/env python3 |
||||
|
||||
import json |
||||
import argparse |
||||
import stat |
||||
import textwrap |
||||
import shutil |
||||
import subprocess |
||||
from tempfile import TemporaryDirectory |
||||
from pathlib import Path |
||||
import typing as T |
||||
|
||||
image_namespace = 'mesonbuild' |
||||
|
||||
image_def_file = 'image.json' |
||||
install_script = 'install.sh' |
||||
|
||||
class ImageDef: |
||||
def __init__(self, image_dir: Path) -> None: |
||||
path = image_dir / image_def_file |
||||
data = json.loads(path.read_text()) |
||||
|
||||
assert isinstance(data, dict) |
||||
assert all([x in data for x in ['base_image', 'env']]) |
||||
assert isinstance(data['base_image'], str) |
||||
assert isinstance(data['env'], dict) |
||||
|
||||
self.base_image: str = data['base_image'] |
||||
self.env: T.Dict[str, str] = data['env'] |
||||
|
||||
class BuilderBase(): |
||||
def __init__(self, data_dir: Path, temp_dir: Path) -> None: |
||||
self.data_dir = data_dir |
||||
self.temp_dir = temp_dir |
||||
|
||||
self.validate_data_dir() |
||||
|
||||
self.image_def = ImageDef(self.data_dir) |
||||
|
||||
self.docker = shutil.which('docker') |
||||
self.git = shutil.which('git') |
||||
if self.docker is None: |
||||
raise RuntimeError('Unable to find docker') |
||||
if self.git is None: |
||||
raise RuntimeError('Unable to find git') |
||||
|
||||
def validate_data_dir(self) -> None: |
||||
files = [ |
||||
self.data_dir / image_def_file, |
||||
self.data_dir / install_script, |
||||
] |
||||
if not self.data_dir.exists(): |
||||
raise RuntimeError(f'{self.data_dir.as_posix()} does not exist') |
||||
for i in files: |
||||
if not i.exists(): |
||||
raise RuntimeError(f'{i.as_posix()} does not exist') |
||||
if not i.is_file(): |
||||
raise RuntimeError(f'{i.as_posix()} is not a regular file') |
||||
|
||||
class Builder(BuilderBase): |
||||
def gen_bashrc(self) -> None: |
||||
out_file = self.temp_dir / 'env_vars.sh' |
||||
out_data = '' |
||||
|
||||
for key, val in self.image_def.env.items(): |
||||
out_data += f'export {key}="{val}"\n' |
||||
|
||||
out_file.write_text(out_data) |
||||
|
||||
# make it executable |
||||
mode = out_file.stat().st_mode |
||||
out_file.chmod(mode | stat.S_IEXEC) |
||||
|
||||
def gen_dockerfile(self) -> None: |
||||
out_file = self.temp_dir / 'Dockerfile' |
||||
out_data = textwrap.dedent(f'''\ |
||||
FROM {self.image_def.base_image} |
||||
|
||||
ADD install.sh /usr/sbin/docker-do-install |
||||
ADD env_vars.sh /env_vars.sh |
||||
RUN docker-do-install |
||||
''') |
||||
|
||||
out_file.write_text(out_data) |
||||
|
||||
def do_build(self) -> None: |
||||
# copy files |
||||
for i in self.data_dir.iterdir(): |
||||
shutil.copy(str(i), str(self.temp_dir)) |
||||
|
||||
self.gen_bashrc() |
||||
self.gen_dockerfile() |
||||
|
||||
cmd_git = [self.git, 'rev-parse', '--short', 'HEAD'] |
||||
res = subprocess.run(cmd_git, cwd=self.data_dir, stdout=subprocess.PIPE) |
||||
if res.returncode != 0: |
||||
raise RuntimeError('Failed to get the current commit hash') |
||||
commit_hash = res.stdout.decode().strip() |
||||
|
||||
cmd = [ |
||||
self.docker, 'build', |
||||
'-t', f'{image_namespace}/{self.data_dir.name}:latest', |
||||
'-t', f'{image_namespace}/{self.data_dir.name}:{commit_hash}', |
||||
'--pull', |
||||
self.temp_dir.as_posix(), |
||||
] |
||||
if subprocess.run(cmd).returncode != 0: |
||||
raise RuntimeError('Failde to build the docker image') |
||||
|
||||
class ImageTester(BuilderBase): |
||||
def __init__(self, data_dir: Path, temp_dir: Path, ci_root: Path) -> None: |
||||
super().__init__(data_dir, temp_dir) |
||||
self.meson_root = ci_root.parent.parent.resolve() |
||||
|
||||
def gen_dockerfile(self) -> None: |
||||
out_file = self.temp_dir / 'Dockerfile' |
||||
out_data = textwrap.dedent(f'''\ |
||||
FROM {image_namespace}/{self.data_dir.name} |
||||
|
||||
ADD meson /meson |
||||
''') |
||||
|
||||
out_file.write_text(out_data) |
||||
|
||||
def copy_meson(self) -> None: |
||||
shutil.copytree( |
||||
self.meson_root, |
||||
self.temp_dir / 'meson', |
||||
ignore=shutil.ignore_patterns( |
||||
'.git', |
||||
'*_cache', |
||||
'work area', |
||||
self.temp_dir.name, |
||||
) |
||||
) |
||||
|
||||
def do_test(self): |
||||
self.copy_meson() |
||||
self.gen_dockerfile() |
||||
|
||||
try: |
||||
build_cmd = [ |
||||
self.docker, 'build', |
||||
'-t', 'meson_test_image', |
||||
self.temp_dir.as_posix(), |
||||
] |
||||
if subprocess.run(build_cmd).returncode != 0: |
||||
raise RuntimeError('Failde to build the test docker image') |
||||
|
||||
test_cmd = [ |
||||
self.docker, 'run', '--rm', '-t', 'meson_test_image', |
||||
'/usr/bin/bash', '-c', 'source /env_vars.sh; cd meson; ./run_tests.py' |
||||
] |
||||
if subprocess.run(test_cmd).returncode != 0: |
||||
raise RuntimeError('Running tests failed') |
||||
finally: |
||||
cleanup_cmd = [self.docker, 'rmi', '-f', 'meson_test_image'] |
||||
subprocess.run(cleanup_cmd).returncode |
||||
|
||||
def main() -> None: |
||||
parser = argparse.ArgumentParser(description='Meson CI image builder') |
||||
parser.add_argument('what', type=str, help='Which image to build / test') |
||||
parser.add_argument('-t', '--type', choices=['build', 'test'], help='What to do', required=True) |
||||
|
||||
args = parser.parse_args() |
||||
|
||||
ci_root = Path(__file__).parent |
||||
ci_data = ci_root / args.what |
||||
|
||||
with TemporaryDirectory(prefix=f'{args.type}_{args.what}_', dir=ci_root) as td: |
||||
ci_build = Path(td) |
||||
print(f'Build dir: {ci_build}') |
||||
|
||||
if args.type == 'build': |
||||
builder = Builder(ci_data, ci_build) |
||||
builder.do_build() |
||||
elif args.type == 'test': |
||||
tester = ImageTester(ci_data, ci_build, ci_root) |
||||
tester.do_test() |
||||
|
||||
if __name__ == '__main__': |
||||
main() |
@ -1,4 +0,0 @@ |
||||
FROM fedora:latest |
||||
|
||||
ADD install.sh /usr/sbin/docker-fedora-install |
||||
RUN docker-fedora-install |
@ -0,0 +1,8 @@ |
||||
{ |
||||
"base_image": "fedora:latest", |
||||
"env": { |
||||
"CI": "1", |
||||
"SKIP_SCIENTIFIC": "1", |
||||
"SKIP_STATIC_BOOST": "1" |
||||
} |
||||
} |
@ -1,4 +0,0 @@ |
||||
FROM opensuse/tumbleweed:latest |
||||
|
||||
ADD install.sh /usr/sbin/docker-opensuse-install |
||||
RUN docker-opensuse-install |
@ -0,0 +1,9 @@ |
||||
{ |
||||
"base_image": "opensuse/tumbleweed:latest", |
||||
"env": { |
||||
"CI": "1", |
||||
"SKIP_SCIENTIFIC": "1", |
||||
"SKIP_STATIC_BOOST": "1", |
||||
"SINGLE_DUB_COMPILER": "1" |
||||
} |
||||
} |
Loading…
Reference in new issue