aboutsummaryrefslogtreecommitdiffstats
path: root/meson/ci/ciimage
diff options
context:
space:
mode:
Diffstat (limited to 'meson/ci/ciimage')
-rw-r--r--meson/ci/ciimage/.gitignore3
-rw-r--r--meson/ci/ciimage/arch/image.json7
-rwxr-xr-xmeson/ci/ciimage/arch/install.sh54
-rw-r--r--meson/ci/ciimage/bionic/image.json8
-rwxr-xr-xmeson/ci/ciimage/bionic/install.sh60
-rwxr-xr-xmeson/ci/ciimage/build.py241
-rw-r--r--meson/ci/ciimage/common.sh47
-rw-r--r--meson/ci/ciimage/cuda/image.json8
-rwxr-xr-xmeson/ci/ciimage/cuda/install.sh21
-rw-r--r--meson/ci/ciimage/fedora/image.json8
-rwxr-xr-xmeson/ci/ciimage/fedora/install.sh29
-rw-r--r--meson/ci/ciimage/opensuse/image.json9
-rwxr-xr-xmeson/ci/ciimage/opensuse/install.sh48
-rw-r--r--meson/ci/ciimage/ubuntu-rolling/image.json8
-rwxr-xr-xmeson/ci/ciimage/ubuntu-rolling/install.sh54
-rwxr-xr-xmeson/ci/ciimage/ubuntu-rolling/test.sh12
16 files changed, 617 insertions, 0 deletions
diff --git a/meson/ci/ciimage/.gitignore b/meson/ci/ciimage/.gitignore
new file mode 100644
index 000000000..cff1864bf
--- /dev/null
+++ b/meson/ci/ciimage/.gitignore
@@ -0,0 +1,3 @@
+/build_*
+/test_*
+/user.sh
diff --git a/meson/ci/ciimage/arch/image.json b/meson/ci/ciimage/arch/image.json
new file mode 100644
index 000000000..4a399d69e
--- /dev/null
+++ b/meson/ci/ciimage/arch/image.json
@@ -0,0 +1,7 @@
+{
+ "base_image": "archlinux:latest",
+ "env": {
+ "CI": "1",
+ "MESON_CI_JOBNAME": "linux-arch-gcc"
+ }
+}
diff --git a/meson/ci/ciimage/arch/install.sh b/meson/ci/ciimage/arch/install.sh
new file mode 100755
index 000000000..72816ab95
--- /dev/null
+++ b/meson/ci/ciimage/arch/install.sh
@@ -0,0 +1,54 @@
+#!/bin/bash
+
+set -e
+
+source /ci/common.sh
+
+# Inspired by https://github.com/greyltc/docker-archlinux-aur/blob/master/add-aur.sh
+
+pkgs=(
+ python python-pip
+ ninja make git sudo fakeroot autoconf automake patch
+ libelf gcc gcc-fortran gcc-objc vala rust bison flex cython go dlang-dmd
+ mono boost qt5-base gtkmm3 gtest gmock protobuf wxgtk2 gobject-introspection
+ itstool gtk3 java-environment=8 gtk-doc llvm clang sdl2 graphviz
+ doxygen vulkan-validation-layers openssh mercurial gtk-sharp-2 qt5-tools
+ libwmf valgrind cmake netcdf-fortran openmpi nasm gnustep-base gettext
+ python-lxml hotdoc rust-bindgen qt6-base qt6-tools
+ # cuda
+)
+
+aur_pkgs=(scalapack)
+cleanup_pkgs=(go)
+
+AUR_USER=docker
+PACMAN_OPTS='--needed --noprogressbar --noconfirm'
+
+# Patch config files
+sed -i 's/#Color/Color/g' /etc/pacman.conf
+sed -i 's,#MAKEFLAGS="-j2",MAKEFLAGS="-j$(nproc)",g' /etc/makepkg.conf
+sed -i "s,PKGEXT='.pkg.tar.zst',PKGEXT='.pkg.tar',g" /etc/makepkg.conf
+
+# Install packages
+pacman -Syu $PACMAN_OPTS "${pkgs[@]}"
+install_python_packages
+
+# Setup the user
+useradd -m $AUR_USER
+echo "${AUR_USER}:" | chpasswd -e
+echo "$AUR_USER ALL = NOPASSWD: ALL" >> /etc/sudoers
+
+# Install yay
+su $AUR_USER -c 'cd; git clone https://aur.archlinux.org/yay.git'
+su $AUR_USER -c 'cd; cd yay; makepkg'
+pushd /home/$AUR_USER/yay/
+pacman -U *.pkg.tar --noprogressbar --noconfirm
+popd
+rm -rf /home/$AUR_USER/yay
+
+# Install yay deps
+su $AUR_USER -c "yay -S $PACMAN_OPTS ${aur_pkgs[*]}"
+
+# cleanup
+pacman -Rs --noconfirm "${cleanup_pkgs[@]}"
+su $AUR_USER -c "yes | yay -Scc"
diff --git a/meson/ci/ciimage/bionic/image.json b/meson/ci/ciimage/bionic/image.json
new file mode 100644
index 000000000..5df709e7e
--- /dev/null
+++ b/meson/ci/ciimage/bionic/image.json
@@ -0,0 +1,8 @@
+{
+ "base_image": "ubuntu:bionic",
+ "env": {
+ "CI": "1",
+ "DC": "gdc",
+ "MESON_CI_JOBNAME": "linux-bionic-gcc"
+ }
+}
diff --git a/meson/ci/ciimage/bionic/install.sh b/meson/ci/ciimage/bionic/install.sh
new file mode 100755
index 000000000..4dea73ba8
--- /dev/null
+++ b/meson/ci/ciimage/bionic/install.sh
@@ -0,0 +1,60 @@
+#!/bin/bash
+
+set -e
+
+source /ci/common.sh
+
+export DEBIAN_FRONTEND=noninteractive
+export LANG='C.UTF-8'
+export DC=gdc
+
+pkgs=(
+ python3-pip libxml2-dev libxslt1-dev libyaml-dev libjson-glib-dev
+ wget unzip cmake doxygen
+ clang
+ pkg-config-arm-linux-gnueabihf
+ qt4-linguist-tools qt5-default qtbase5-private-dev
+ python-dev
+ libomp-dev
+ llvm lcov
+ ldc
+ libclang-dev
+ libgcrypt20-dev
+ libgpgme-dev
+ libhdf5-dev openssh-server
+ libboost-python-dev libboost-regex-dev
+ libblocksruntime-dev
+ libperl-dev libscalapack-mpi-dev libncurses-dev
+)
+
+boost_pkgs=(atomic chrono date-time filesystem log regex serialization system test thread)
+
+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
+
+# Add boost packages
+for i in "${boost_pkgs[@]}"; do
+ for j in "1.62.0" "1.65.1"; do
+ pkgs+=("libboost-${i}${j}")
+ done
+done
+
+# packages
+eatmydata apt-get -y install "${pkgs[@]}"
+
+install_python_packages
+
+# Install the ninja 0.10
+wget https://github.com/ninja-build/ninja/releases/download/v1.10.0/ninja-linux.zip
+unzip ninja-linux.zip -d /ci
+
+# cleanup
+apt-get -y remove ninja-build
+apt-get -y clean
+apt-get -y autoclean
+rm ninja-linux.zip
diff --git a/meson/ci/ciimage/build.py b/meson/ci/ciimage/build.py
new file mode 100755
index 000000000..1e1f23811
--- /dev/null
+++ b/meson/ci/ciimage/build.py
@@ -0,0 +1,241 @@
+#!/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(encoding='utf-8'))
+
+ 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.args: T.List[str] = data.get('args', [])
+ 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 = ''
+
+ # run_tests.py parameters
+ self.image_def.env['CI_ARGS'] = ' '.join(self.image_def.args)
+
+ for key, val in self.image_def.env.items():
+ out_data += f'export {key}="{val}"\n'
+
+ # Also add /ci to PATH
+ out_data += 'export PATH="/ci:$PATH"\n'
+
+ out_file.write_text(out_data, encoding='utf-8')
+
+ # 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, encoding='utf-8')
+
+ 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('Failed 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, encoding='utf-8')
+
+ def copy_meson(self) -> None:
+ shutil.copytree(
+ self.meson_root,
+ self.temp_dir / 'meson',
+ ignore=shutil.ignore_patterns(
+ '.git',
+ '*_cache',
+ '__pycache__',
+ # 'work area',
+ self.temp_dir.name,
+ )
+ )
+
+ def do_test(self, tty: bool = False) -> None:
+ 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('Failed to build the test docker image')
+
+ test_cmd = []
+ if tty:
+ test_cmd = [
+ self.docker, 'run', '--rm', '-t', '-i', 'meson_test_image',
+ '/bin/bash', '-c', ''
+ + 'cd meson;'
+ + 'source /ci/env_vars.sh;'
+ + f'echo -e "\\n\\nInteractive test shell in the {image_namespace}/{self.data_dir.name} container with the current meson tree";'
+ + 'echo -e "The file ci/ciimage/user.sh will be sourced if it exists to enable user specific configurations";'
+ + 'echo -e "Run the following command to run all CI tests: ./run_tests.py $CI_ARGS\\n\\n";'
+ + '[ -f ci/ciimage/user.sh ] && exec /bin/bash --init-file ci/ciimage/user.sh;'
+ + 'exec /bin/bash;'
+ ]
+ else:
+ test_cmd = [
+ self.docker, 'run', '--rm', '-t', 'meson_test_image',
+ '/bin/bash', '-c', 'source /ci/env_vars.sh; cd meson; ./run_tests.py $CI_ARGS'
+ ]
+
+ if subprocess.run(test_cmd).returncode != 0 and not tty:
+ raise RuntimeError('Running tests failed')
+ finally:
+ cleanup_cmd = [self.docker, 'rmi', '-f', 'meson_test_image']
+ subprocess.run(cleanup_cmd).returncode
+
+class ImageTTY(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 do_run(self) -> None:
+ try:
+ tty_cmd = [
+ self.docker, 'run',
+ '--name', 'meson_test_container', '-t', '-i', '-v', f'{self.meson_root.as_posix()}:/meson',
+ f'{image_namespace}/{self.data_dir.name}',
+ '/bin/bash', '-c', ''
+ + 'cd meson;'
+ + 'source /ci/env_vars.sh;'
+ + f'echo -e "\\n\\nInteractive test shell in the {image_namespace}/{self.data_dir.name} container with the current meson tree";'
+ + 'echo -e "The file ci/ciimage/user.sh will be sourced if it exists to enable user specific configurations";'
+ + 'echo -e "Run the following command to run all CI tests: ./run_tests.py $CI_ARGS\\n\\n";'
+ + '[ -f ci/ciimage/user.sh ] && exec /bin/bash --init-file ci/ciimage/user.sh;'
+ + 'exec /bin/bash;'
+ ]
+ subprocess.run(tty_cmd).returncode != 0
+ finally:
+ cleanup_cmd = [self.docker, 'rm', '-f', 'meson_test_container']
+ 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', 'testTTY', 'TTY'], 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()
+ elif args.type == 'testTTY':
+ tester = ImageTester(ci_data, ci_build, ci_root)
+ tester.do_test(tty=True)
+ elif args.type == 'TTY':
+ tester = ImageTTY(ci_data, ci_build, ci_root)
+ tester.do_run()
+
+if __name__ == '__main__':
+ main()
diff --git a/meson/ci/ciimage/common.sh b/meson/ci/ciimage/common.sh
new file mode 100644
index 000000000..707b7515f
--- /dev/null
+++ b/meson/ci/ciimage/common.sh
@@ -0,0 +1,47 @@
+#!/bin/bash
+
+###
+### Common functions for CI builder files.
+### All functions can be accessed in install.sh via:
+###
+### $ source /ci/common.sh
+###
+
+set -e
+set -x
+
+base_python_pkgs=(
+ pytest
+ pytest-xdist
+ coverage
+ codecov
+ jsonschema
+)
+
+python_pkgs=(
+ cython
+ gobject
+ PyGObject
+ lxml
+ gcovr
+)
+
+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
+}
+
+install_minimal_python_packages() {
+ python3 -m pip install "${base_python_pkgs[@]}" $*
+}
+
+install_python_packages() {
+ python3 -m pip install "${base_python_pkgs[@]}" "${python_pkgs[@]}" $*
+}
diff --git a/meson/ci/ciimage/cuda/image.json b/meson/ci/ciimage/cuda/image.json
new file mode 100644
index 000000000..f422723c1
--- /dev/null
+++ b/meson/ci/ciimage/cuda/image.json
@@ -0,0 +1,8 @@
+{
+ "base_image": "archlinux:latest",
+ "args": ["--only", "cuda"],
+ "env": {
+ "CI": "1",
+ "MESON_CI_JOBNAME": "linux-cuda-gcc"
+ }
+}
diff --git a/meson/ci/ciimage/cuda/install.sh b/meson/ci/ciimage/cuda/install.sh
new file mode 100755
index 000000000..0d412e00c
--- /dev/null
+++ b/meson/ci/ciimage/cuda/install.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+set -e
+
+source /ci/common.sh
+
+pkgs=(
+ python python-pip
+ ninja gcc gcc-objc git cmake
+ cuda zlib pkgconf
+)
+
+PACMAN_OPTS='--needed --noprogressbar --noconfirm'
+
+pacman -Syu $PACMAN_OPTS "${pkgs[@]}"
+install_minimal_python_packages
+
+# Manually remove cache to avoid GitHub space restrictions
+rm -rf /var/cache/pacman
+
+echo "source /etc/profile.d/cuda.sh" >> /ci/env_vars.sh
diff --git a/meson/ci/ciimage/fedora/image.json b/meson/ci/ciimage/fedora/image.json
new file mode 100644
index 000000000..c6fdc9e28
--- /dev/null
+++ b/meson/ci/ciimage/fedora/image.json
@@ -0,0 +1,8 @@
+{
+ "base_image": "fedora:latest",
+ "env": {
+ "CI": "1",
+ "SKIP_STATIC_BOOST": "1",
+ "MESON_CI_JOBNAME": "linux-fedora-gcc"
+ }
+}
diff --git a/meson/ci/ciimage/fedora/install.sh b/meson/ci/ciimage/fedora/install.sh
new file mode 100755
index 000000000..df1d853cd
--- /dev/null
+++ b/meson/ci/ciimage/fedora/install.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+
+set -e
+
+source /ci/common.sh
+
+pkgs=(
+ python python-pip python3-devel
+ ninja-build make git autoconf automake patch
+ elfutils gcc gcc-c++ gcc-fortran gcc-objc gcc-objc++ vala rust bison flex ldc libasan libasan-static
+ mono-core boost-devel gtkmm30 gtest-devel gmock-devel protobuf-devel wxGTK3-devel gobject-introspection
+ boost-python3-devel
+ itstool gtk3-devel java-latest-openjdk-devel gtk-doc llvm-devel clang-devel SDL2-devel graphviz-devel zlib zlib-devel zlib-static
+ #hdf5-openmpi-devel hdf5-devel netcdf-openmpi-devel netcdf-devel netcdf-fortran-openmpi-devel netcdf-fortran-devel scalapack-openmpi-devel
+ doxygen vulkan-devel vulkan-validation-layers-devel openssh mercurial gtk-sharp2-devel libpcap-devel gpgme-devel
+ qt5-qtbase-devel qt5-qttools-devel qt5-linguist qt5-qtbase-private-devel
+ libwmf-devel valgrind cmake openmpi-devel nasm gnustep-base-devel gettext-devel ncurses-devel
+ libxml2-devel libxslt-devel libyaml-devel glib2-devel json-glib-devel libgcrypt-devel
+)
+
+# Sys update
+dnf -y upgrade
+
+# Install deps
+dnf -y install "${pkgs[@]}"
+install_python_packages hotdoc
+
+# Cleanup
+dnf -y clean all
diff --git a/meson/ci/ciimage/opensuse/image.json b/meson/ci/ciimage/opensuse/image.json
new file mode 100644
index 000000000..6609aa08d
--- /dev/null
+++ b/meson/ci/ciimage/opensuse/image.json
@@ -0,0 +1,9 @@
+{
+ "base_image": "opensuse/tumbleweed:latest",
+ "env": {
+ "CI": "1",
+ "SKIP_STATIC_BOOST": "1",
+ "SINGLE_DUB_COMPILER": "1",
+ "MESON_CI_JOBNAME": "linux-opensuse-gcc"
+ }
+}
diff --git a/meson/ci/ciimage/opensuse/install.sh b/meson/ci/ciimage/opensuse/install.sh
new file mode 100755
index 000000000..41cb96192
--- /dev/null
+++ b/meson/ci/ciimage/opensuse/install.sh
@@ -0,0 +1,48 @@
+#!/bin/bash
+
+set -e
+
+source /ci/common.sh
+
+pkgs=(
+ python3-pip python3 python3-devel
+ ninja make git autoconf automake patch libjpeg-devel
+ elfutils gcc gcc-c++ gcc-fortran gcc-objc gcc-obj-c++ vala rust bison flex curl lcov
+ mono-core gtkmm3-devel gtest gmock protobuf-devel wxGTK3-3_2-devel gobject-introspection-devel
+ itstool gtk3-devel java-15-openjdk-devel gtk-doc llvm-devel clang-devel libSDL2-devel graphviz-devel zlib-devel zlib-devel-static
+ #hdf5-devel netcdf-devel libscalapack2-openmpi3-devel libscalapack2-gnu-openmpi3-hpc-devel openmpi3-devel
+ doxygen vulkan-devel vulkan-validationlayers openssh mercurial gtk-sharp3-complete gtk-sharp2-complete libpcap-devel libgpgme-devel
+ libqt5-qtbase-devel libqt5-qttools-devel libqt5-linguist libqt5-qtbase-private-headers-devel
+ libwmf-devel valgrind cmake nasm gnustep-base-devel gettext-tools gettext-runtime gettext-csharp ncurses-devel
+ libxml2-devel libxslt-devel libyaml-devel glib2-devel json-glib-devel
+ boost-devel libboost_date_time-devel libboost_filesystem-devel libboost_locale-devel libboost_system-devel
+ libboost_test-devel libboost_log-devel libboost_regex-devel
+ libboost_python3-devel libboost_regex-devel
+)
+
+# Sys update
+zypper --non-interactive patch --with-update --with-optional
+zypper --non-interactive update
+
+# Install deps
+zypper install -y "${pkgs[@]}"
+install_python_packages hotdoc
+
+echo 'export PKG_CONFIG_PATH="/usr/lib64/mpi/gcc/openmpi3/lib64/pkgconfig:$PKG_CONFIG_PATH"' >> /ci/env_vars.sh
+
+# dmd is very special on OpenSUSE (as in the packages do not work)
+# see https://bugzilla.opensuse.org/show_bug.cgi?id=1162408
+curl -fsS https://dlang.org/install.sh | bash -s dmd | tee dmd_out.txt
+cat dmd_out.txt | grep source | sed 's/^[^`]*`//g' | sed 's/`.*//g' >> /ci/env_vars.sh
+chmod +x /ci/env_vars.sh
+
+source /ci/env_vars.sh
+
+dub_fetch urld
+dub build urld --compiler=dmd
+dub_fetch dubtestproject
+dub build dubtestproject:test1 --compiler=dmd
+dub build dubtestproject:test2 --compiler=dmd
+
+# Cleanup
+zypper --non-interactive clean --all
diff --git a/meson/ci/ciimage/ubuntu-rolling/image.json b/meson/ci/ciimage/ubuntu-rolling/image.json
new file mode 100644
index 000000000..f9f068d5a
--- /dev/null
+++ b/meson/ci/ciimage/ubuntu-rolling/image.json
@@ -0,0 +1,8 @@
+{
+ "base_image": "ubuntu:rolling",
+ "env": {
+ "CI": "1",
+ "DC": "gdc",
+ "MESON_CI_JOBNAME": "linux-ubuntu-rolling-gcc"
+ }
+}
diff --git a/meson/ci/ciimage/ubuntu-rolling/install.sh b/meson/ci/ciimage/ubuntu-rolling/install.sh
new file mode 100755
index 000000000..770fd8531
--- /dev/null
+++ b/meson/ci/ciimage/ubuntu-rolling/install.sh
@@ -0,0 +1,54 @@
+#!/bin/bash
+
+set -e
+
+source /ci/common.sh
+
+export DEBIAN_FRONTEND=noninteractive
+export LANG='C.UTF-8'
+export DC=gdc
+
+pkgs=(
+ python3-pip libxml2-dev libxslt1-dev libyaml-dev libjson-glib-dev
+ wget unzip
+ qt5-qmake qtbase5-dev qtchooser qtbase5-dev-tools clang
+ pkg-config-arm-linux-gnueabihf
+ libomp-dev
+ llvm lcov
+ 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
+ bindgen
+)
+
+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
+
+install_python_packages hotdoc
+
+# 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
diff --git a/meson/ci/ciimage/ubuntu-rolling/test.sh b/meson/ci/ciimage/ubuntu-rolling/test.sh
new file mode 100755
index 000000000..f6956bb60
--- /dev/null
+++ b/meson/ci/ciimage/ubuntu-rolling/test.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+set -e
+
+testFN() {
+ set +e
+ false
+}
+
+testFN
+false
+exit 0