| # use the verse rocker image, as it contains tidyverse, devtools and some texlive |
| image: rocker/tidyverse |
| |
| variables: |
| # Set `CCACHE_BASEDIR` and `CCACHE_DIR` to point `ccache` towards the cached |
| # path on the gitlab-runner. This enables to cache the output of `ccache` |
| # between various runs. |
| CCACHE_BASEDIR: "${CI_PROJECT_DIR}" |
| CCACHE_DIR: "${CI_PROJECT_DIR}/ccache" |
| # Set `ccache` to `content` to prevent rebuilding of the CI/CD containers to |
| # trigger a recreate of the cache. By using `content` the compiler's `mtime` |
| # is not considered as part of the hash. |
| CCACHE_COMPILERCHECK: "content" |
| # Enable caching for `apt-get`. |
| APT_CACHE_DIR: "${CI_PROJECT_DIR}/apt-cache" |
| # Export `noninteractive` frontend to prevent requesting user input. |
| DEBIAN_FRONTEND: "noninteractive" |
| R_LIBS_USER: "RLIB/library" |
| SOURCE_FILE: "inst/rmarkdown/templates/posterdown_ids/skeleton/skeleton.Rmd" |
| |
| # define stages of runner. at the moment, |
| # just build (no test or deploy). |
| stages: |
| - build |
| |
| build-job: |
| stage: build |
| |
| cache: |
| key: posterdown-ids |
| paths: |
| - cache |
| - apt-cache/ |
| - ccache/ |
| - RLIB/library/ |
| |
| artifacts: |
| paths: |
| - "target/*" |
| |
| before_script: |
| - source `find .. -name section_helper.sh` |
| |
| - start_section apt_mirrors "Configuring apt mirror and retries" |
| - . /etc/os-release || true |
| - | |
| # Prefer mirror lists (automatic, resilient). Optionally force a German mirror |
| # by setting APT_FORCE_DE_MIRROR=1 in CI/CD variables if you want a fixed DE mirror. |
| # Handle both classic sources.list and Deb822 .sources files. |
| set -eu |
| APT_FILES=(/etc/apt/sources.list /etc/apt/sources.list.d/*.list /etc/apt/sources.list.d/*.sources) |
| # shellcheck disable=SC2068 |
| APT_FILES=( ${APT_FILES[@]} ) |
| echo "--- os-release (selected) ---" |
| grep -E '^(ID|VERSION_ID|VERSION_CODENAME|UBUNTU_CODENAME)=' /etc/os-release || true |
| echo "--- resolved APT source files ---" |
| for f in ${APT_FILES[@]}; do [ -e "$f" ] && echo " - $f"; done |
| echo "--- env summary ---" |
| echo "APT_FORCE_DE_MIRROR=${APT_FORCE_DE_MIRROR:-} ID=${ID:-} VERSION_CODENAME=${VERSION_CODENAME:-} UBUNTU_CODENAME=${UBUNTU_CODENAME:-}" |
| echo "--- apt sources (pre) ---" |
| for f in ${APT_FILES[@]}; do |
| [ -e "$f" ] || continue |
| echo "### $f" |
| grep -nE '^(deb|deb-src)|^(Types|URIs|Suites|Components)=' "$f" || sed -n '1,40p' "$f" || true |
| done |
| echo "--- rewrite mode decision ---" |
| AUTOPICK="${APT_AUTOPICK_MIRROR:-}" |
| if [ "${APT_FORCE_DE_MIRROR:-}" = "auto" ]; then AUTOPICK=1; fi |
| if [ "${AUTOPICK}" = "1" ]; then |
| echo "Mode=AUTO_PICK OS=${ID:-unknown}" |
| if [ "${ID:-}" = "ubuntu" ] && command -v curl >/dev/null 2>&1; then |
| CODE_NAME="${UBUNTU_CODENAME:-${VERSION_CODENAME:-}}" |
| LIST_URL="https://mirrors.ubuntu.com/mirrors.txt" |
| echo "Fetching mirror list: $LIST_URL" |
| MIRRORS=$(curl -fsSL "$LIST_URL" | grep -E '^https?://.+' | sed 's#/*$##' | head -n 20 || true) |
| BEST_URL=""; BEST_TIME="9999" |
| echo "Testing up to 20 mirrors for ${CODE_NAME} InRelease" |
| for m in $MIRRORS; do |
| TEST_URL="$m/dists/${CODE_NAME}/InRelease" |
| RES=$(curl -m 3 --connect-timeout 2 -o /dev/null -s -w '%{time_total} %{http_code}' "$TEST_URL" || echo "9.999 000") |
| TME=${RES%% *}; CODE=${RES##* } |
| echo " $(printf '%6s' "$TME")s $CODE $m" |
| if [ "$CODE" = "200" ]; then |
| BETTER=$(awk -v a="$TME" -v b="$BEST_TIME" 'BEGIN{print (a<b)?"1":"0"}') |
| if [ "$BETTER" = "1" ]; then BEST_TIME="$TME"; BEST_URL="$m"; fi |
| fi |
| done |
| if [ -n "$BEST_URL" ]; then |
| MIRROR_URL="${BEST_URL}" |
| echo "Selected fastest: $MIRROR_URL (time=${BEST_TIME}s)" |
| for f in ${APT_FILES[@]}; do |
| [ -e "$f" ] || continue |
| # Replace any Ubuntu archive base with selected mirror |
| sed -i -E "s#https?://[^ ]*/ubuntu#${MIRROR_URL}#g" "$f" || true |
| sed -i -E "s#mirror://mirrors\.ubuntu\.com/mirrors\.txt#${MIRROR_URL}#g" "$f" || true |
| done |
| else |
| echo "Autopick failed to find a working mirror; falling back to mirror list." |
| AUTOPICK=0 |
| fi |
| else |
| echo "Autopick not available (non-Ubuntu or curl missing); falling back." |
| AUTOPICK=0 |
| fi |
| fi |
| if [ "${AUTOPICK}" != "1" ] && [ "${APT_FORCE_DE_MIRROR:-}" = "1" ]; then |
| echo "Mode=FORCED_MIRROR OS=${ID:-unknown}" |
| if [ "${ID:-}" = "ubuntu" ]; then |
| MIRROR_URL="http://de.archive.ubuntu.com/ubuntu" |
| for f in ${APT_FILES[@]}; do |
| [ -e "$f" ] || continue |
| sed -i -E "s#https?://[^ ]*ubuntu.com/ubuntu#${MIRROR_URL}#g" "$f" || true |
| sed -i -E "s#mirror://mirrors\.ubuntu\.com/mirrors\.txt#${MIRROR_URL}#g" "$f" || true |
| done |
| elif [ "${ID:-}" = "debian" ]; then |
| MIRROR_URL="http://ftp.de.debian.org/debian" |
| for f in ${APT_FILES[@]}; do |
| [ -e "$f" ] || continue |
| sed -i -E "s#https?://(deb|ftp)[^ ]*debian.org/debian#${MIRROR_URL}#g" "$f" || true |
| sed -i -E "s#mirror://mirrors\.debian\.org/debian#${MIRROR_URL}#g" "$f" || true |
| done |
| fi |
| elif [ "${AUTOPICK}" != "1" ]; then |
| echo "Mode=MIRROR_LIST OS=${ID:-unknown}" |
| if [ "${ID:-}" = "ubuntu" ]; then |
| for f in ${APT_FILES[@]}; do [ -e "$f" ] && sed -i -E 's#https?://[^ ]*ubuntu.com/ubuntu#mirror://mirrors.ubuntu.com/mirrors.txt#g' "$f" || true; done |
| elif [ "${ID:-}" = "debian" ]; then |
| for f in ${APT_FILES[@]}; do [ -e "$f" ] && sed -i -E 's#https?://(deb|ftp)[^ ]*debian.org/debian#mirror://mirrors.debian.org/debian#g' "$f" || true; done |
| fi |
| fi |
| echo "--- apt sources (post) ---" |
| for f in ${APT_FILES[@]}; do |
| [ -e "$f" ] || continue |
| echo "### $f" |
| grep -nE '^(deb|deb-src)|^(Types|URIs|Suites|Components)=' "$f" || sed -n '1,40p' "$f" || true |
| done |
| # Show which mirror hosts remain after rewriting, to aid debugging |
| echo "APT_FORCE_DE_MIRROR=${APT_FORCE_DE_MIRROR:-} ID=${ID:-} VERSION_CODENAME=${VERSION_CODENAME:-}" |
| grep -R "archive\.ubuntu\.com/ubuntu\|ubuntu\.com/ubuntu\|debian\.org/debian\|mirrors\.ubuntu\.com\|mirrors\.debian\.org" -n /etc/apt || true |
| echo "--- apt origins (apt-cache policy) ---" |
| apt-cache policy | sed -n '1,120p' || true |
| printf 'Acquire::Retries "5";\nAcquire::http::Timeout "30";\nAcquire::https::Timeout "30";\nAcquire::http::Pipeline-Depth "0";\n' > /etc/apt/apt.conf.d/99retries |
| - end_section apt_mirrors |
| |
| - start_section install_linux_packages "Installing missing Linux packages" |
| - mkdir -pv $APT_CACHE_DIR ccache $R_LIBS_USER |
| - apt-get -o Acquire::Retries=5 -o Acquire::http::Timeout=30 -o Acquire::https::Timeout=30 update |
| - apt-get install --no-install-recommends -o dir::cache::archives="$APT_CACHE_DIR" -y libasound2t64 npm curl ccache libvulkan1 libu2f-udev fonts-liberation build-essential libglpk40 libcurl4-gnutls-dev libxml2-dev libsodium-dev libsecret-1-dev libfontconfig1-dev libssl-dev libxt6 libpq-dev imagemagick ghostscript inkscape scour poppler-utils qpdf texlive-extra-utils texlive-latex-extra fonts-stix |
| - command -v pdfjam |
| - kpsewhich pdfpages.sty |
| # The distro Node (18, EOL) is too old for current tooling. Install the |
| # current Node.js LTS (24.x) from the official tarball into /usr/local, which |
| # precedes /usr/bin on PATH so `node`/`npx` resolve to it. We deliberately do |
| # NOT use NodeSource/apt: its `nodejs` package does not satisfy `nodejs:any`, |
| # which breaks the distro `libnode-dev`/`node-acorn` sysdeps pak installs later. |
| - NODE_TARBALL=$(curl -fsSL https://nodejs.org/dist/latest-v24.x/ | grep -oE 'node-v24\.[0-9]+\.[0-9]+-linux-x64\.tar\.gz' | head -n1) |
| - curl -fsSL "https://nodejs.org/dist/latest-v24.x/$NODE_TARBALL" | tar -xz -C /usr/local --strip-components=1 |
| - node --version |
| - npx @puppeteer/browsers install chrome-headless-shell@stable |
| - export CHROME=$(find ~+ -name chrome-headless-shell -type f) |
| - echo "CHROME=$CHROME" |
| - ln -s $CHROME /usr/bin/google-chrome |
| - ln -s $(which ccache) /usr/local/sbin/gcc |
| - ln -s $(which ccache) /usr/local/sbin/g++ |
| - locale-gen de_DE.utf8 en_GB.utf8 en_US.utf8 |
| - end_section install_linux_packages |
| |
| - start_section testing_chromium "Testing chrome" |
| - google-chrome --version |
| - end_section testing_chromium |
| |
| - start_section install_fonts "Installing Fira, Twemoji and emoji fonts" |
| - curl -Ls https://github.com/mozilla/Fira/archive/refs/tags/4.202.tar.gz | tar -C /usr/share/fonts -zx --wildcards "*.otf" |
| - apt-get install --no-install-recommends -o dir::cache::archives="$APT_CACHE_DIR" -y rpm2cpio cpio fonts-noto-color-emoji fonts-symbola |
| # Fedora's twitter-twemoji-fonts package ships a CBDT/CBLC (bitmap) build |
| # of Twemoji. Use that rather than upstream's own SVGinOT release: Chrome |
| # renders SVGinOT in color on screen, but its print-to-PDF path silently |
| # falls back to the font's monochrome outline glyphs for that format -- |
| # CBDT/CBLC (like Noto Color Emoji) embeds correctly as a color PDF font. |
| - curl -sLo /tmp/twemoji.rpm https://dl.fedoraproject.org/pub/fedora/linux/releases/44/Everything/x86_64/os/Packages/t/twitter-twemoji-fonts-14.0.2-10.fc44.noarch.rpm |
| - (cd / && rpm2cpio /tmp/twemoji.rpm | cpio -idm ./usr/share/fonts/twemoji/Twemoji.ttf) |
| - fc-cache |
| - end_section install_fonts |
| |
| - start_section install_r_packages "Installing missing R packages" |
| # - echo -e 'local({r <- getOption("repos")\n r["CRAN"] <- "https://cloud.r-project.org"\n options(repos=r)})' > ~/.Rprofile |
| - echo "options(Ncpus=$(nproc))" >> ~/.Rprofile |
| - R -e "install.packages('pak', dependencies=TRUE)" |
| - R -e "pak::pak(c('devtools', 'scales', 'sp', 'raster', 'kableExtra', 'DT', 'svglite', 'qrcode', 'ggthemes', 'patchwork', 'pagedown'), dependencies=TRUE)" |
| - R -e 'devtools::install_git("https://korap.ids-mannheim.de/gerrit/IDS-Mannheim/idsThemeR", dependencies=TRUE)' |
| - R -e 'devtools::install_git("https://korap.ids-mannheim.de/gerrit/IDS-Mannheim/posterdown", dependencies=TRUE)' |
| - end_section install_r_packages |
| |
| - start_section testing_html2pdf "Testing PDF export" |
| - ./ci/html2pdf ci/html2pdf-smoke.html /tmp/html2pdf-smoke.pdf |
| - qpdf --check /tmp/html2pdf-smoke.pdf |
| - end_section testing_html2pdf |
| |
| script: |
| - start_section installing "Installing posterdown.ids" |
| - R -e 'devtools::install()' |
| - end_section installing |
| - start_section render "Generating demo artifacts" |
| - R_CACHE_ROOTPATH=./cache R -e "require(rmarkdown); render('$SOURCE_FILE', output_format='posterdown.ids::posterdown_ids', output_dir='target')" |
| - export SVG=target/$(basename $SOURCE_FILE .Rmd).svg |
| - export HTML=target/$(basename $SOURCE_FILE .Rmd).html |
| - export PDF=target/$(basename $SOURCE_FILE .Rmd).pdf |
| - ./ci/html2pdf $HTML $PDF 1 |
| # Variant for printing on fabric, where solid black bleeds out: replace all |
| # vector black by 10% grey (keeps QR codes scannable and text crisp). |
| - export FABRIC_PDF=target/$(basename $SOURCE_FILE .Rmd)_fabric.pdf |
| - ./ci/pdf-lighten-black $PDF $FABRIC_PDF |
| # Add 10 mm of blank material on every side for mounting the fabric poster. |
| - pdfjam $FABRIC_PDF --papersize '{861mm,1209mm}' --noautoscale true --outfile target/$(basename $SOURCE_FILE .Rmd)_fabric-1cmborder.pdf |
| - gs -sDEVICE=pdfwrite -dNOPAUSE -dBATCH -sPAPERSIZE=a1 -dFIXEDMEDIA -dPDFFitPage -sOutputFile=target/$(basename $SOURCE_FILE .Rmd)_a1.pdf $PDF |
| - inkscape -b white --pdf-poppler -o $SVG $PDF || true |
| - scour -i $SVG -o target/skeleton_optimized.svg --enable-viewboxing --enable-id-stripping --shorten-ids --indent=none |
| - end_section render |