Merge pull request #6843 from mensinda/ciJSONSCHEMA
CI: Maintain docker images with GitHub Actionspull/6901/head
commit
69a03fa94b
21 changed files with 417 additions and 100 deletions
@ -0,0 +1,54 @@ |
|||||||
|
name: CI image builder |
||||||
|
|
||||||
|
on: |
||||||
|
push: |
||||||
|
branches: |
||||||
|
- master |
||||||
|
paths: |
||||||
|
- 'ci/ciimage/**' |
||||||
|
- '.github/workflows/images.yml' |
||||||
|
|
||||||
|
pull_request: |
||||||
|
branches: |
||||||
|
- master |
||||||
|
paths: |
||||||
|
- 'ci/ciimage/**' |
||||||
|
- '.github/workflows/images.yml' |
||||||
|
|
||||||
|
# Rebuild the images every week (Sunday) |
||||||
|
schedule: |
||||||
|
- cron: '0 0 * * 0' |
||||||
|
|
||||||
|
jobs: |
||||||
|
build: |
||||||
|
name: ${{ matrix.cfg.name }} |
||||||
|
runs-on: ubuntu-latest |
||||||
|
strategy: |
||||||
|
fail-fast: false |
||||||
|
matrix: |
||||||
|
cfg: |
||||||
|
- { name: Arch Linux, id: arch } |
||||||
|
- { name: Fedora, id: fedora } |
||||||
|
- { name: OpenSUSE, id: opensuse } |
||||||
|
- { name: Ubuntu Eoan, id: eoan } |
||||||
|
steps: |
||||||
|
- uses: actions/checkout@v2 |
||||||
|
|
||||||
|
# Login to dockerhub |
||||||
|
- name: Docker login |
||||||
|
if: github.event_name == 'push' || github.event_name == 'schedule' |
||||||
|
uses: azure/docker-login@v1 |
||||||
|
with: |
||||||
|
username: ${{ secrets.DOCKER_USERNAME }} |
||||||
|
password: ${{ secrets.DOCKER_PASSWORD }} |
||||||
|
|
||||||
|
# Build and test |
||||||
|
- name: Building the ${{ matrix.cfg.id }} image |
||||||
|
run: ./ci/ciimage/build.py -t build ${{ matrix.cfg.id }} |
||||||
|
- name: Testing the ${{ matrix.cfg.id }} image |
||||||
|
run: ./ci/ciimage/build.py -t test ${{ matrix.cfg.id }} |
||||||
|
|
||||||
|
# Publish |
||||||
|
- name: Push the ${{ matrix.cfg.id }} image |
||||||
|
if: github.event_name == 'push' || github.event_name == 'schedule' |
||||||
|
run: docker push mesonbuild/${{ matrix.cfg.id }} |
@ -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,186 @@ |
|||||||
|
#!/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.common_sh = self.data_dir.parent / 'common.sh' |
||||||
|
self.common_sh = self.common_sh.resolve(strict=True) |
||||||
|
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 /ci/install.sh |
||||||
|
ADD common.sh /ci/common.sh |
||||||
|
ADD env_vars.sh /ci/env_vars.sh |
||||||
|
RUN /ci/install.sh |
||||||
|
''') |
||||||
|
|
||||||
|
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)) |
||||||
|
shutil.copy(str(self.common_sh), 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 /ci/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() |
@ -0,0 +1,22 @@ |
|||||||
|
#!/bin/bash |
||||||
|
|
||||||
|
### |
||||||
|
### Common functions for CI builder files. |
||||||
|
### All functions can be accessed in install.sh via: |
||||||
|
### |
||||||
|
### $ source /ci/common.sh |
||||||
|
### |
||||||
|
|
||||||
|
set -e |
||||||
|
|
||||||
|
dub_fetch() { |
||||||
|
set +e |
||||||
|
for (( i=1; i<=24; ++i )); do |
||||||
|
dub fetch "$@" |
||||||
|
(( $? == 0 )) && break |
||||||
|
|
||||||
|
echo "Dub Fetch failed. Retrying in $((i*5))s" |
||||||
|
sleep $((i*5)) |
||||||
|
done |
||||||
|
set -e |
||||||
|
} |
@ -1,36 +0,0 @@ |
|||||||
FROM ubuntu:eoan |
|
||||||
|
|
||||||
ENV DEBIAN_FRONTEND noninteractive |
|
||||||
ENV LANG='C.UTF-8' |
|
||||||
ENV DC=gdc |
|
||||||
|
|
||||||
RUN sed -i '/^#\sdeb-src /s/^#//' "/etc/apt/sources.list" \ |
|
||||||
&& apt-get -y update && apt-get -y upgrade \ |
|
||||||
&& apt-get -y install eatmydata \ |
|
||||||
&& eatmydata apt-get -y build-dep meson \ |
|
||||||
&& eatmydata apt-get -y install python3-pytest-xdist \ |
|
||||||
&& eatmydata apt-get -y install python3-pip libxml2-dev libxslt1-dev libyaml-dev libjson-glib-dev \ |
|
||||||
&& eatmydata python3 -m pip install hotdoc codecov \ |
|
||||||
&& eatmydata apt-get -y install wget unzip \ |
|
||||||
&& eatmydata apt-get -y install qt5-default clang \ |
|
||||||
&& eatmydata apt-get -y install pkg-config-arm-linux-gnueabihf \ |
|
||||||
&& eatmydata apt-get -y install qt4-linguist-tools \ |
|
||||||
&& eatmydata apt-get -y install python-dev \ |
|
||||||
&& eatmydata apt-get -y install libomp-dev \ |
|
||||||
&& eatmydata apt-get -y install dub ldc \ |
|
||||||
&& eatmydata apt-get -y install mingw-w64 mingw-w64-tools nim \ |
|
||||||
&& eatmydata apt-get -y install --no-install-recommends wine-stable \ |
|
||||||
&& eatmydata apt-get -y install libclang-dev \ |
|
||||||
&& eatmydata apt-get -y install libgcrypt20-dev \ |
|
||||||
&& eatmydata apt-get -y install libgpgme-dev \ |
|
||||||
&& eatmydata apt-get -y install libhdf5-dev \ |
|
||||||
&& eatmydata apt-get -y install libboost-python-dev libboost-regex-dev \ |
|
||||||
&& eatmydata apt-get -y install libblocksruntime-dev \ |
|
||||||
&& eatmydata apt-get -y install libperl-dev \ |
|
||||||
&& eatmydata apt-get -y install liblapack-dev libscalapack-mpi-dev \ |
|
||||||
&& eatmydata dub fetch urld && dub build urld --compiler=gdc \ |
|
||||||
&& eatmydata dub fetch dubtestproject \ |
|
||||||
&& eatmydata dub build dubtestproject:test1 --compiler=ldc2 \ |
|
||||||
&& eatmydata dub build dubtestproject:test2 --compiler=ldc2 |
|
||||||
# OpenSSH client is needed to run openmpi binaries. |
|
||||||
|
|
@ -0,0 +1,7 @@ |
|||||||
|
{ |
||||||
|
"base_image": "ubuntu:eoan", |
||||||
|
"env": { |
||||||
|
"CI": "1", |
||||||
|
"DC": "gdc" |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,55 @@ |
|||||||
|
#!/bin/bash |
||||||
|
|
||||||
|
set -e |
||||||
|
|
||||||
|
source /ci/common.sh |
||||||
|
|
||||||
|
export DEBIAN_FRONTEND=noninteractive |
||||||
|
export LANG='C.UTF-8' |
||||||
|
export DC=gdc |
||||||
|
|
||||||
|
pkgs=( |
||||||
|
python3-pytest-xdist |
||||||
|
python3-pip libxml2-dev libxslt1-dev libyaml-dev libjson-glib-dev |
||||||
|
wget unzip |
||||||
|
qt5-default clang |
||||||
|
pkg-config-arm-linux-gnueabihf |
||||||
|
qt4-linguist-tools |
||||||
|
python-dev |
||||||
|
libomp-dev |
||||||
|
dub ldc |
||||||
|
mingw-w64 mingw-w64-tools nim |
||||||
|
libclang-dev |
||||||
|
libgcrypt20-dev |
||||||
|
libgpgme-dev |
||||||
|
libhdf5-dev |
||||||
|
libboost-python-dev libboost-regex-dev |
||||||
|
libblocksruntime-dev |
||||||
|
libperl-dev |
||||||
|
liblapack-dev libscalapack-mpi-dev |
||||||
|
) |
||||||
|
|
||||||
|
sed -i '/^#\sdeb-src /s/^#//' "/etc/apt/sources.list" |
||||||
|
apt-get -y update |
||||||
|
apt-get -y upgrade |
||||||
|
apt-get -y install eatmydata |
||||||
|
|
||||||
|
# Base stuff |
||||||
|
eatmydata apt-get -y build-dep meson |
||||||
|
|
||||||
|
# packages |
||||||
|
eatmydata apt-get -y install "${pkgs[@]}" |
||||||
|
eatmydata apt-get -y install --no-install-recommends wine-stable # Wine is special |
||||||
|
|
||||||
|
eatmydata python3 -m pip install hotdoc codecov jsonschema |
||||||
|
|
||||||
|
# dub stuff |
||||||
|
dub_fetch urld |
||||||
|
dub build urld --compiler=gdc |
||||||
|
dub_fetch dubtestproject |
||||||
|
dub build dubtestproject:test1 --compiler=ldc2 |
||||||
|
dub build dubtestproject:test2 --compiler=ldc2 |
||||||
|
|
||||||
|
# cleanup |
||||||
|
apt-get -y clean |
||||||
|
apt-get -y autoclean |
@ -0,0 +1,12 @@ |
|||||||
|
#!/bin/bash |
||||||
|
|
||||||
|
set -e |
||||||
|
|
||||||
|
testFN() { |
||||||
|
set +e |
||||||
|
false |
||||||
|
} |
||||||
|
|
||||||
|
testFN |
||||||
|
false |
||||||
|
exit 0 |
@ -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" |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,9 @@ |
|||||||
|
{ |
||||||
|
"matrix": { |
||||||
|
"options": { |
||||||
|
"warning_level": [ |
||||||
|
{ "val": "1", "skip_on_env": [ "SINGLE_DUB_COMPILER" ] } |
||||||
|
] |
||||||
|
} |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue