diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..c98b3cb --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,49 @@ +# +# Copyright: 2022, The Geany contributors +# License: GNU GPL v2 or later + +name: Build CI Docker Images + +on: + push: + branches: + - master + workflow_dispatch: + schedule: + # Run weekly on Friday + - cron: '34 5 * * FRI' + +# cancel already running builds of the same branch or pull request +concurrency: + group: ci-${{ github.head_ref }} || concat(${{ github.ref }} + cancel-in-progress: true + +jobs: + mingw64: + name: Build Docker image for mingw64 CI builds + runs-on: ubuntu-22.04 + permissions: + packages: write + + env: + DOCKER_REGISTRY: "ghcr.io" + DOCKER_IMAGE_NAME: "geany-mingw64-ci" + DOCKER_IMAGE_TAG: "ghcr.io/geany/geany-mingw64-ci:latest" + + steps: + - name: Checkout Build Scripts + uses: actions/checkout@v3 + + - name: Log In To The Container Registry + uses: docker/login-action@v2 + with: + registry: ${{ env.DOCKER_REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build And Push Docker Image + run: | + cd builders + bash start_build.sh --log-to-stdout --mingw64 --rebuild-images + docker tag ${{ env.DOCKER_IMAGE_NAME }} ${{ env.DOCKER_IMAGE_TAG }} + docker push ${{ env.DOCKER_IMAGE_TAG }} diff --git a/README.md b/README.md index 8534be9..8ad0879 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,17 @@ If you want to add or remove a repository maintained by these scripts, follow th * Open http://git.geany.org/ in your browser and check whether the new repository is visible and has files. + +CI / Nightly-Builders +===================== + +The `builders` directory contains Dockerfiles and scripts to create Debian packages +as well as a cross-compiled Windows installer for Geany and Geany-Plugins. +These scripts are used for the nightly builds, for details see +[builders/README.md](builders/README.md). + + License -=============== +======= Unless stated otherwise all code in this repository is licensed of under the terms of the GNU General Public License version 2 (see COPYING in this repository). diff --git a/builders/.dockerignore b/builders/.dockerignore new file mode 100644 index 0000000..d4cd1e8 --- /dev/null +++ b/builders/.dockerignore @@ -0,0 +1,5 @@ +/.git +/certificates +/output +/scripts +/start_build.sh diff --git a/builders/Dockerfile.mingw64 b/builders/Dockerfile.mingw64 new file mode 100644 index 0000000..db0c5b0 --- /dev/null +++ b/builders/Dockerfile.mingw64 @@ -0,0 +1,119 @@ +# +# Copyright 2022 The Geany contributors +# License: GPLv2 +# +# Docker image for Geany and Geany-Plugins cross-build to Windows +# The image contains a self-compiled Pacman to install mingw-w64 +# packages and all other dependencies necessary to build the code +# and create a ready-use installer. +# For more details, see build_mingw64_geany.sh where this image is used. +# +# Intermediate container for building pacman +FROM debian:bullseye as build-pacman + +ENV PACMAN_VERSION=6.0.1 +ENV PACMAN_SHA256="0db61456e56aa49e260e891c0b025be210319e62b15521f29d3e93b00d3bf731" +ENV MSYS2_KEYRING_PKG="msys2-keyring-1~20220623-1-any.pkg.tar.zst" +ENV MSYS2_KEYRING_PKG_SHA256="3508c7fca2f8b9722139666459eb8716f2413fd6daaf997abf0df41d7f285dc9" + +RUN set -ex && \ + apt-get update && \ + apt-get install --no-install-recommends --assume-yes \ + build-essential meson wget xz-utils zstd gnupg2 file zstd ca-certificates \ + pkg-config m4 libarchive-dev libssl-dev libcurl4-gnutls-dev libgpgme-dev \ + python3-setuptools + +# compile Pacman +RUN set -ex && \ + wget --no-verbose https://sources.archlinux.org/other/pacman/pacman-${PACMAN_VERSION}.tar.xz && \ + echo "${PACMAN_SHA256} *pacman-${PACMAN_VERSION}.tar.xz" | sha256sum --check --strict - && \ + tar xf pacman-${PACMAN_VERSION}.tar.xz && \ + cd /pacman-${PACMAN_VERSION} && \ + meson \ + --prefix /usr/local \ + --sysconfdir=/windows/etc \ + --localstatedir=/windows/var \ + --buildtype release \ + --strip \ + -Dscriptlet-shell='/bin/bash' \ + -Ddoc='disabled' \ + -Ddoxygen='disabled' \ + -Ddoc='disabled' \ + -Di18n=false \ + build && \ + ninja -C build && \ + ninja -C build install && \ + ldconfig + +COPY mingw64/etc/ /windows/etc/ + +# setup pacman-key +RUN set -ex && \ + # download MSYS2 keyring + mkdir -p /usr/local/share/pacman/keyrings/ && \ + wget --no-verbose "https://repo.msys2.org/msys/x86_64/${MSYS2_KEYRING_PKG}" && \ + echo "${MSYS2_KEYRING_PKG_SHA256} *${MSYS2_KEYRING_PKG}" | sha256sum --check --strict - && \ + tar -x -C /usr/local/share/pacman/keyrings/ -f "${MSYS2_KEYRING_PKG}" --strip-components 4 usr && \ + # initialize keyring + pacman-key --init && \ + pacman-key --populate msys2 + + +# Main image +FROM debian:bullseye + +LABEL org.opencontainers.image.title="Geany-Mingw-w64-CI" +LABEL org.opencontainers.image.description="Build image for Geany CI to support automatic building of Windows installers." +LABEL org.opencontainers.image.url="https://github.com/geany/infrastructure" +LABEL org.opencontainers.image.source="https://github.com/geany/infrastructure" +LABEL org.opencontainers.image.authors="The Geany contributors" +LABEL org.opencontainers.image.licenses="GPL-2.0" + +# install native tools and libraries +RUN set -ex && \ + dpkg --add-architecture i386 && \ + apt-get update && \ + apt-get install --no-install-recommends --assume-yes \ + # libraries \ + libcurl3-gnutls libgpgme11 libarchive13 libssl1.1 \ + # common useful utilities \ + wget curl less nano git gnupg2 file ca-certificates dos2unix \ + zip unzip xz-utils zstd \ + # build tools \ + build-essential automake autoconf autopoint gettext libtool check cppcheck \ + # genay-plugins autogen.sh requirements + intltool libglib2.0-dev \ + # mingw-w64 \ + gcc-mingw-w64-x86-64 g++-mingw-w64-x86-64 mingw-w64-x86-64-dev mingw-w64-tools \ + # install wine to test installer and created binaries + wine wine32 wine64 \ + # install NSIS and exiftool to inspect binary metadata + nsis libimage-exiftool-perl osslsigncode \ + # Geany build dependencies \ + python3-lxml python3-docutils + + +# copy pacman and scripts +COPY --from=build-pacman /windows /windows +COPY --from=build-pacman /usr/local /usr/local +COPY mingw64/bin/ /usr/local/bin/ +RUN mkdir /build + +WORKDIR /build + +# start wine to initially create config directory +RUN /usr/local/bin/mingw-w64-i686-wine hostname.exe && \ + /usr/local/bin/mingw-w64-x86_64-wine hostname.exe && \ + # install GTK3 and all its dependencies + ldconfig && \ + pacman --noconfirm -Sy mingw-w64-x86_64-gtk3 && \ + # cleanup + apt-get clean && \ + rm -rf /var/lib/apt/lists/* && \ + yes | pacman -Scc && \ + rm -r /usr/share/doc \ + /usr/share/locale \ + /usr/share/man \ + /windows/mingw64/share/icons \ + /windows/mingw64/share/locale \ + /windows/mingw64/share/doc diff --git a/builders/README.md b/builders/README.md new file mode 100644 index 0000000..00137c3 --- /dev/null +++ b/builders/README.md @@ -0,0 +1,102 @@ +CI / Nightly-Builders +===================== + +## About + +Scripts and Dockerfiles for Geany and Geany-Plugins nightly builds. +`start_build.sh` will create (if missing) a Docker image for +Mingw-w64 cross-compilaton to Windows. + +## Scripts and files + + ├── Dockerfile.mingw64 -> Dockerfile for Mingw-64 build image + ├── README.md + ├── certificates -> Certificate for signing Windows binaries and installer + │   ├── cert.pem -> Certificate public key (the filename is important) + │   └── key.pem -> Certificate secret key (the filename is important) + │ + ├── mingw64 -> Helpers and configuration for Pacman and Windows builds + │   ├── bin (these files will be built into the Windows Docker image) + │   │   ├── mingw-w64-i686-wine + │   │   └── mingw-w64-x86_64-wine + │   └── etc + │   ├── pacman.conf + │   └── pacman.d + │   └── mirrorlist.mingw64 + ├── output -> Directory where all build results are stored + │ + └── start_build.sh -> Run Windows build containers and start builds + +## Geany sources + +All of the scripts can either use an existing source distribution +of Geany (and Geany-Plugins) if it is mounted into the build Docker +container (as `/geany-source` resp. `/geany-plugins-source`). +If no existing source distribution is found, the scripts will clone +Geany resp. Geany-Plugins from GIT master. + +## start_build.sh + +Main entry point to (re-)build the necessary Docker images and trigger +the builds of Geany and Geany-Plugins for the various targets. + + usage: start_build.sh [-m|--mingw64] [-r|--rebuild-images] + -f, --force-rebuild Force rebuilding of immages even if not necessary + -g, --geany Build Geany + --geany-script Path to the script to be executed to build Geany + --geany-source Path to a Geany source directory (optional, cloned from GIT if missing) + --geany-plugins-script Path to the script to be executed to build Geany-Plugins + --geany-plugins-source Path to a Geany-Plugins source directory (optional, cloned from GIT if missing) + -h Show this help screen + -l, --log-to-stdout Log build output additionally to stdout + -m, --mingw64 Build for target Mingw-w64 + -p, --geany-plugins Build Geany-Plugins + -r, --rebuild-images Rebuild Docker images before start building + (images are rebuilt automatically every 30 days) + -s, --sudo Use "sudo" for Docker commands + + +Example to build Geany and Geany-Plugins for Windows: + + bash start_build.sh --geany --geany-plugins --mingw64 + +## Windows (Mingw64) build + +Geany and Geany-Plugins are built for Windows by cross-compiling them in a Docker container +containing all necessary tools. + +If the build was started via Github Actions from a pull request, the pull request number +will be appended to the resulting installer filename. For all other builds, the used GIT +commit short hash is used. + +The created installer for Geany will contain the +[Geany-Themes](https://github.com/geany/geany-themes) collection as well as the GTK +runtime with all necessary dependencies. + +The created installer for Geany-Plugins will contain all necessary dependencies +for the plugins to work. + +For more details, see the scripts `scripts/ci_mingw64_geany.sh` and `build/ci_mingw64_geany_plugins.sh` +in the Geany resp. Geany-Plugins repository. + +In theory, it is also possible to create release installers with this method. + +### Docker image + +The Docker image for the Windows build is based on a Debian image but has the full toolchain +for cross-compiling to mingw64 included. Additionally, the image contains a self-compiled +Pacman package manager to install packages from the MSYS2 repositories. + +A Github Action workflow is configured in this repository to build and push the image +to the Github Docker image registry as ghcr.io/geany/geany-mingw64-ci:latest. The workflow +is triggered once a week automatically. + +### Code sign certificate + +If the directory `certificates` contains the two files `cert.pem` and `key.pem`, +then they will be used to digitally sign all created binary files (all built +`.exe` and `.dll` files). + +If the directory is empty, code signing will be skipped. + +The certificate should be in the PEM format and the key should not require a passphrase. diff --git a/builders/certificates/.gitkeep b/builders/certificates/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/builders/mingw64/bin/mingw-w64-i686-wine b/builders/mingw64/bin/mingw-w64-i686-wine new file mode 100755 index 0000000..722e5b8 --- /dev/null +++ b/builders/mingw64/bin/mingw-w64-i686-wine @@ -0,0 +1,4 @@ +#!/bin/sh -e + +WINEPATH="${WINEPATH};/windows/mingw32/bin" wine $@ + diff --git a/builders/mingw64/bin/mingw-w64-x86_64-wine b/builders/mingw64/bin/mingw-w64-x86_64-wine new file mode 100755 index 0000000..193291e --- /dev/null +++ b/builders/mingw64/bin/mingw-w64-x86_64-wine @@ -0,0 +1,3 @@ +#!/bin/sh -e + +WINEPATH="${WINEPATH};/windows/mingw64/bin" wine64 $@ diff --git a/builders/mingw64/etc/pacman.conf b/builders/mingw64/etc/pacman.conf new file mode 100644 index 0000000..8a911e4 --- /dev/null +++ b/builders/mingw64/etc/pacman.conf @@ -0,0 +1,40 @@ + +[options] +RootDir = /windows +DBPath = /windows/var/lib/pacman/ +CacheDir = /windows/var/cache/pacman/pkg/ +LogFile = /windows/var/log/pacman.log +GPGDir = /windows/etc/pacman.d/gnupg/ +HoldPkg = pacman +#XferCommand = /usr/bin/curl -C - -f %u > %o +#XferCommand = /usr/bin/wget --passive-ftp -c -O %o %u +#CleanMethod = KeepInstalled +#UseDelta = 0.7 +Architecture = x86_64 + +# Pacman won't upgrade packages listed in IgnorePkg and members of IgnoreGroup +#IgnorePkg = +#IgnoreGroup = + +#NoUpgrade = +#NoExtract = + +# Misc options +#UseSyslog +#Color +#TotalDownload +CheckSpace +#VerbosePkgLists + +# By default, pacman accepts packages signed by keys that its local keyring +# trusts (see pacman-key and its man page), as well as unsigned packages. +#SigLevel = Never +SigLevel = Required DatabaseOptional +LocalFileSigLevel = Optional +#RemoteFileSigLevel = Required + +[mingw64] +Include = /windows/etc/pacman.d/mirrorlist.mingw64 + +[msys] +Include = /windows/etc/pacman.d/mirrorlist.msys diff --git a/builders/mingw64/etc/pacman.d/mirrorlist.mingw64 b/builders/mingw64/etc/pacman.d/mirrorlist.mingw64 new file mode 100644 index 0000000..dc16ae5 --- /dev/null +++ b/builders/mingw64/etc/pacman.d/mirrorlist.mingw64 @@ -0,0 +1,6 @@ +## +## 64-bit Mingw-w64 repository mirrorlist +## + +Server = http://repo.msys2.org/mingw/x86_64 +Server = http://www2.futureware.at/~nickoe/msys2-mirror/mingw/x86_64 diff --git a/builders/mingw64/etc/pacman.d/mirrorlist.msys b/builders/mingw64/etc/pacman.d/mirrorlist.msys new file mode 100644 index 0000000..0c27b68 --- /dev/null +++ b/builders/mingw64/etc/pacman.d/mirrorlist.msys @@ -0,0 +1,6 @@ +## +## MSYS repository mirrorlist +## + +Server = http://repo.msys2.org/msys/$arch/ +Server = http://www2.futureware.at/~nickoe/msys2-mirror/msys/$arch/ diff --git a/builders/output/.gitkeep b/builders/output/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/builders/start_build.sh b/builders/start_build.sh new file mode 100644 index 0000000..a8ad64f --- /dev/null +++ b/builders/start_build.sh @@ -0,0 +1,231 @@ +#!/bin/bash +# +# Copyright 2022 The Geany contributors +# License: GPLv2 +# +# Run Windows build containers and start builds within. +# The Docker image for the containers are rebuilt automatically +# every 30 days to keep them up to date. +# +# This script has to be run outside of the containers. +# +# usage: start_build.sh [-m|--mingw64] [-r|--rebuild-images] +# -f, --force-rebuild Force rebuilding of immages even if not necessary +# -g, --geany Build Geany +# --geany-source Path to a Geany source directory (optional, cloned from GIT if missing) +# --geany-plugins-source Path to a Geany-Plugins source directory (optional, cloned from GIT if missing) +# -h Show this help screen +# -l, --log-to-stdout Log build output additionally to stdout +# -m, --mingw64 Build for target Mingw-w64 +# -p, --geany-plugins Build Geany-Plugins +# -r, --rebuild-images Rebuild Docker images before start building +# (images are rebuilt automatically every 30 days) +# -s, --sudo Use "sudo" for Docker commands + + +DOCKER_IMAGE_MAX_AGE_DAYS=30 + +BASE_OUTPUT_DIRECTORY="${PWD}/output/" +DOCKER_CMD="docker" +GEANY_BUILD_SCRIPT=/geany-source/scripts/ci_mingw64_geany.sh +GEANY_SOURCE= +GEANY_PLUGINS_BUILD_SCRIPT=/geany-plugins-source/build/ci_mingw64_geany_plugins.sh +GEANY_PLUGINS_SOURCE= + +# stop on errors +set -e +set -o pipefail + + +log() { + log_filename="$1" + shift + if [ "${log_filename}" = "-" ]; then + echo "========== $(date '+%Y-%m-%d %H:%M:%S %Z') $@ ==========" + else + if [ -n "${DO_LOG_TO_STDOUT}" ]; then + $@ 2>&1 | tee "${log_filename}" + else + $@ > "${log_filename}" 2>&1 + fi + fi +} + + +rebuild_image() { + image_name="$1" + dockerfile="$2" + build_args="$3" + # query image created date or use 0 if the image does not exist to trigger a build + image_date=$(${DOCKER_CMD} image inspect --format='{{.Created}}' "${image_name}" 2>/dev/null || echo -n 1970-01-01) + image_date=$(echo "${image_date}" | xargs) # trim leading and trailing whitespace + image_date_seconds=$(date --date="${image_date}" "+%s") + expire_date_seconds=$(date --date="${DOCKER_IMAGE_MAX_AGE_DAYS} days ago" "+%s") + if [ "${image_date_seconds}" -lt "${expire_date_seconds}" ] || [ -n "${DO_FORCE_IMAGE_REBUILD}" ]; then + log - "Building image ${image_name} (last build: ${image_date})" + log "${BASE_OUTPUT_DIRECTORY}/docker_image_build_${image_name}_$(date '+%Y_%m_%d_%H_%M_%S').log" \ + ${DOCKER_CMD} build \ + --no-cache \ + --file "${dockerfile}" \ + --label "org.opencontainers.image.created=$(date --iso-8601=seconds)" \ + --tag "${image_name}" \ + ${build_args} \ + . + fi +} + + +build_mingw64() { + IMAGE_NAME_WINDOWS="geany-mingw64-ci" + MINGW64_OUTPUT_DIRECTORY=${BASE_OUTPUT_DIRECTORY}/mingw64 + LOGFILE_MINGW64_GEANY=${MINGW64_OUTPUT_DIRECTORY}/build_mingw64_geany_$(date '+%Y_%m_%d_%H_%M_%S').log + LOGFILE_MINGW64_GEANY_PLUGINS=${MINGW64_OUTPUT_DIRECTORY}/build_mingw64_geany_plugins_$(date '+%Y_%m_%d_%H_%M_%S').log + mkdir -p "${MINGW64_OUTPUT_DIRECTORY}" + + # (re)build Docker image + rebuild_image ${IMAGE_NAME_WINDOWS} Dockerfile.mingw64 + + # Build Geany + if [ -n "${DO_GEANY}" ]; then + log - "Building Geany for Windows" + if [ -n "${GEANY_SOURCE}" ]; then + source_volume="--volume ${GEANY_SOURCE}:/geany-source/:ro" + else + source_volume= + fi + log "${LOGFILE_MINGW64_GEANY}" \ + ${DOCKER_CMD} run \ + --rm \ + --env=GITHUB_PULL_REQUEST="${GITHUB_PULL_REQUEST}" \ + --env=CI="${CI}" \ + ${source_volume} \ + --volume "${PWD}/scripts:/scripts/" \ + --volume "${PWD}/certificates/:/certificates/" \ + --volume "${MINGW64_OUTPUT_DIRECTORY}:/output/" \ + "${IMAGE_NAME_WINDOWS}:latest" \ + bash ${GEANY_BUILD_SCRIPT} + fi + + # Build Geany-Plugins + if [ -n "${DO_GEANY_PLUGINS}" ]; then + log - "Building Geany-Plugins for Windows" + if [ -n "${GEANY_PLUGINS_SOURCE}" ]; then + source_volume="--volume ${GEANY_PLUGINS_SOURCE}:/geany-plugins-source/:ro" + else + source_volume= + fi + log "${LOGFILE_MINGW64_GEANY_PLUGINS}" \ + ${DOCKER_CMD} run \ + --rm \ + --env=GITHUB_PULL_REQUEST="${GITHUB_PULL_REQUEST}" \ + --env=CI="${CI}" \ + ${source_volume} \ + --volume "${PWD}/scripts:/scripts/" \ + --volume "${PWD}/certificates/:/certificates/" \ + --volume "${MINGW64_OUTPUT_DIRECTORY}:/output/" \ + "${IMAGE_NAME_WINDOWS}:latest" \ + bash ${GEANY_PLUGINS_BUILD_SCRIPT} + fi +} + + +usage() { + echo "usage: start_build.sh [-m|--mingw64]" + echo " [-r|--rebuild-images]" + echo " -g, --geany Build Geany" + echo "--geany-script Path to the script to be executed to build Geany" + echo "--geany-source Path to a Geany source directory (optional, cloned from GIT if missing)" + echo "--geany-plugins-script Path to the script to be executed to build Geany-Plugins" + echo "--geany-plugins-source Path to a Geany-Plugins source directory (optional, cloned from GIT if missing)" + echo " -h Show this help screen" + echo " -l, --log-to-stdout Log build output additionally to stdout" + echo " -m, --mingw64 Build for target Mingw-w64" + echo " -p, --geany-plugins Build Geany-Plugins" + echo " -r, --rebuild-images Rebuild Docker images before start building" + echo " (images are rebuilt automatically every ${DOCKER_IMAGE_MAX_AGE_DAYS} days)" + echo " -s, --sudo Use \"sudo\" for Docker commands" + exit 1 +} + + +parse_command_line_options() { + if [ $# -eq 0 ]; then + usage + fi + for opt in "$@"; do + case "$opt" in + -f|--force-rebuild) + DO_FORCE_IMAGE_REBUILD=1 + shift + ;; + -g|--geany) + DO_GEANY=1 + shift + ;; + --geany-script) + GEANY_BUILD_SCRIPT="${2}" + shift + shift + ;; + --geany-source) + GEANY_SOURCE="${2}" + shift + shift + ;; + --geany-plugins-script) + GEANY_PLUGINS_BUILD_SCRIPT="${2}" + shift + shift + ;; + --geany-plugins-source) + GEANY_PLUGINS_SOURCE="${2}" + shift + shift + ;; + -l|--log-to-stdout) + DO_LOG_TO_STDOUT=1 + shift + ;; + -m|--mingw64) + DO_MINGW64=1 + shift + ;; + -p|--geany-plugins) + DO_GEANY_PLUGINS=1 + shift + ;; + -r|--rebuild-images) + DO_IMAGE_REBUILD=1 + shift + ;; + -s|--sudo) + DOCKER_CMD="sudo docker" + shift + ;; + -h|--help) + usage + ;; + esac + done +} + + +DO_MINGW64= +DO_IMAGE_REBUILD= +DO_FORCE_IMAGE_REBUILD= +DO_GEANY= +DO_GEANY_PLUGINS= +DO_LOG_TO_STDOUT= +GEANY_SOURCE= +GEANY_PLUGINS_SOURCE= + + +main() { + if [ -n "${DO_MINGW64}" ]; then + build_mingw64 + fi +} + + +parse_command_line_options $@ +main