From f146c2eb70715e94d1e31931cdebb08c86f81799 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrico=20Tr=C3=B6ger?= Date: Sun, 16 Oct 2022 16:15:57 +0200 Subject: [PATCH 1/2] CI: Use MSYS2 for Windows build --- .github/workflows/build.yml | 126 +++++++------ scripts/ci_mingw64_geany.sh | 305 +++++++++++++++++++++++++++++++ scripts/gtk-bundle-from-msys2.sh | 1 - 3 files changed, 379 insertions(+), 53 deletions(-) create mode 100644 scripts/ci_mingw64_geany.sh diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 02afbf2cee..cbe61fbabc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -43,7 +43,7 @@ jobs: CXX: ccache g++ steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 # create and use a timestamp for the cache key: GH Actions will never update a cache # only use an existing cache item or create a new one. To use an existing cache *and* @@ -51,10 +51,10 @@ jobs: # the creation of the cache item at the end) in combination with "restore-keys" as fallback - name: Prepare ccache timestamp id: ccache_cache_timestamp - run: echo "::set-output name=timestamp::$(date +%Y-%m-%d-%H-%M)" + run: echo "timestamp=$(date +%Y-%m-%d-%H-%M)" >> $GITHUB_OUTPUT - name: Configure ccache - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ${{ env.CCACHE_DIR }} key: ${{ runner.os }}-${{ github.job }}-ccache-${{ steps.ccache_cache_timestamp.outputs.timestamp }} @@ -102,6 +102,7 @@ jobs: - name: ccache statistics run: ccache --show-stats if: ${{ env.DEBUG == '1' }} + linux-meson: name: Linux Meson Build runs-on: ubuntu-20.04 @@ -114,7 +115,7 @@ jobs: CXX: ccache g++ steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 # create and use a timestamp for the cache key: GH Actions will never update a cache # only use an existing cache item or create a new one. To use an existing cache *and* @@ -122,10 +123,10 @@ jobs: # the creation of the cache item at the end) in combination with "restore-keys" as fallback - name: Prepare ccache timestamp id: ccache_cache_timestamp - run: echo "::set-output name=timestamp::$(date +%Y-%m-%d-%H-%M)" + run: echo "timestamp=$(date +%Y-%m-%d-%H-%M)" >> $GITHUB_OUTPUT - name: Configure ccache - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ${{ env.CCACHE_DIR }} key: ${{ runner.os }}-${{ github.job }}-ccache-${{ steps.ccache_cache_timestamp.outputs.timestamp }} @@ -167,62 +168,83 @@ jobs: run: ccache --show-stats if: ${{ env.DEBUG == '1' }} - mingw: - name: Mingw Build - # i686-w64-mingw32-pkg-config fails with weird error message on 20.04 - runs-on: ubuntu-18.04 + mingw64: + name: Mingw-w64 Build (Windows) + runs-on: ubuntu-20.04 + env: - CC: ccache i686-w64-mingw32-gcc - CXX: ccache i686-w64-mingw32-g++ + INFRASTRUCTURE_PATH: ${{ github.workspace }}/.infrastructure + BUILDER_PATH: ${{ github.workspace }}/.infrastructure/builders + DOCKER_REGISTRY: "ghcr.io" + DOCKER_IMAGE_NAME: "geany-mingw64-ci" + DOCKER_IMAGE_TAG: "ghcr.io/geany/geany-mingw64-ci:latest" steps: - - uses: actions/checkout@v2 - - # create and use a timestamp for the cache key: GH Actions will never update a cache - # only use an existing cache item or create a new one. To use an existing cache *and* - # push back the the updated cache after build, we use a always new cache key (to force - # the creation of the cache item at the end) in combination with "restore-keys" as fallback - - name: Prepare ccache timestamp - id: ccache_cache_timestamp - run: echo "::set-output name=timestamp::$(date +%Y-%m-%d-%H-%M)" + - name: Checkout Geany + uses: actions/checkout@v3 - - name: Configure ccache - uses: actions/cache@v2 + - name: Checkout Build Scripts + uses: actions/checkout@v3 with: - path: ${{ env.CCACHE_DIR }} - key: ${{ runner.os }}-${{ github.job }}-ccache-${{ steps.ccache_cache_timestamp.outputs.timestamp }} - restore-keys: ${{ runner.os }}-${{ github.job }}-ccache- + ref: master + repository: geany/infrastructure + path: ${{ env.INFRASTRUCTURE_PATH }} + token: ${{ github.token }} - - name: Show environment - run: env | sort - if: ${{ env.DEBUG == '1' }} + - uses: jwalton/gh-find-current-pr@v1 + id: find_pull_request + with: + state: open - - name: Install dependencies + - name: Define environment variables run: | - sudo apt-get update -qq - sudo apt-get install --assume-yes --no-install-recommends \ - ccache \ - gettext autopoint \ - libtool \ - mingw-w64-tools \ - g++-mingw-w64-i686 \ - gcc-mingw-w64-i686 \ - binutils-mingw-w64-i686 \ - doxygen \ - python3-docutils \ - python3-lxml \ - rst2pdf - # fix broken pkg-config-crosswrapper, see https://bugs.launchpad.net/ubuntu/+source/mingw-w64/+bug/1327242 - sudo sed -e 's/PKG_CONFIG_PATH=/&$PKG_CONFIG_PATH:/' -i /usr/bin/i686-w64-mingw32-pkg-config + echo "SHORT_SHA=$(git rev-parse --short HEAD)" >> $GITHUB_ENV + echo "GITHUB_PULL_REQUEST=${GITHUB_PULL_REQUEST}" >> $GITHUB_ENV + echo "GITHUB_PULL_REQUEST_OR_REF=${GITHUB_PULL_REQUEST:-${{ github.ref_name }}}" | tr '/' '-' >> $GITHUB_ENV + env: + GITHUB_PULL_REQUEST: ${{ steps.find_pull_request.outputs.pr }} - - name: Run autogen.sh + - name: Show Environment + if: ${{ env.DEBUG == '1' }} run: | - NOCONFIGURE=1 ./autogen.sh + env | sort - - name: Build + - 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: Pull Or Build Docker Image + working-directory: ${{ env.BUILDER_PATH }} + run: | + docker_image_created= + docker pull ${{ env.DOCKER_IMAGE_TAG }} || true + if $(docker image inspect ${{ env.DOCKER_IMAGE_TAG }} --format="ignored" >/dev/null 2>&1); then + docker tag ${{ env.DOCKER_IMAGE_TAG }} ${{ env.DOCKER_IMAGE_NAME }} + docker_image_created=$(docker image inspect ${{ env.DOCKER_IMAGE_NAME }} --format='{{ index .Config.Labels "org.opencontainers.image.created" }}') + echo "Docker image built on: ${docker_image_created}" + fi + + bash start_build.sh --log-to-stdout --mingw64 --rebuild-images + docker_image_created_new=$(docker image inspect ${{ env.DOCKER_IMAGE_NAME }} --format='{{ index .Config.Labels "org.opencontainers.image.created" }}') + # tag the image + if [ "${docker_image_created}" != "${docker_image_created_new}" ]; then + docker tag ${{ env.DOCKER_IMAGE_NAME }} ${{ env.DOCKER_IMAGE_TAG }} + fi + + - name: Build Geany + if: steps.geany_cache.outputs.cache-hit != 'true' + working-directory: ${{ env.BUILDER_PATH }} run: | - sh ./scripts/cross-build-mingw.sh; + bash start_build.sh --log-to-stdout --mingw64 --geany --geany-source "${{ github.workspace }}" - - name: ccache statistics - run: ccache --show-stats - if: ${{ env.DEBUG == '1' }} + - name: Archive Geany Installer + uses: actions/upload-artifact@v3 + with: + name: geany-installer-ci-${{ env.SHORT_SHA }}-pr${{ env.GITHUB_PULL_REQUEST_OR_REF }} + retention-days: 30 + path: | + ${{ env.BUILDER_PATH }}/output/mingw64/geany-*.exe + ${{ env.BUILDER_PATH }}/output/mingw64/geany-*.zip diff --git a/scripts/ci_mingw64_geany.sh b/scripts/ci_mingw64_geany.sh new file mode 100644 index 0000000000..c337c3f184 --- /dev/null +++ b/scripts/ci_mingw64_geany.sh @@ -0,0 +1,305 @@ +#!/bin/bash +# +# Copyright 2022 The Geany contributors +# License: GPLv2 +# +# Helper script to build Geany for Windows in a Docker container. +# The following steps are performed: +# - clone Geany repository if necessary (i.e. if it is not bind-mounted into the container) +# - cross-compile Geany for Windows 64bit and GTK3 +# - sign all binaries and installer (if /certificates exist and contains cert.pem and key.pem) +# - download Geany-Themes for bundling +# - create GTK3 bundle with all dependencies (including grep and sort) +# - create the NSIS installer in ${OUTPUT_DIRECTORY} +# - test the created NSIS installer and compiled Geany +# - test uninstaller and check there is nothing left after uninstalling +# +# This script has to be executed within the Docker container. +# The Docker container should have a bind-mount for ${OUTPUT_DIRECTORY} +# where the resulting installer binary is stored. +# +# To test the installer and Geany binary "wine" is used. +# Please note that we need to use wine32 and wine64 as the +# created installer and uninstaller binaries are 32bit whereas the created +# Geany binary is 64bit. +# + +GEANY_VERSION= # will be set below from configure.ac +GEANY_GIT_REVISION= # will be set below from configure.ac +OUTPUT_DIRECTORY="/output" +GEANY_GIT_REPOSITORY="https://github.com/geany/geany.git" +GEANY_THEMES_REPOSITORY="https://github.com/geany/geany-themes.git" + +# rather static values, unlikely to be changed +GTK_BUNDLE_DIR="/build/gtk-bundle" +GEANY_SOURCE_DIR="/geany-source" +GEANY_BUILD_DIR="/build/geany-build" +GEANY_RELEASE_DIR="/build/geany-release" +GEANY_INSTALLER_FILENAME= # will be set below +GEANY_INSTALLATION_DIR_WIN="C:\\geany_install" +GEANY_INSTALLATION_DIR=$(winepath --unix ${GEANY_INSTALLATION_DIR_WIN}) +GEANY_THEMES_DIR="/build/geany-themes" +GEANY_RELEASE_BINARY_PATTERNS=( + "${GEANY_RELEASE_DIR}/bin/geany.exe" + "${GEANY_RELEASE_DIR}/bin/*.dll" + "${GEANY_RELEASE_DIR}/lib/geany/*.dll" +) + +# CI CFLAGS +CFLAGS="\ + -Wall \ + -Wextra \ + -O2 \ + -Wunused \ + -Wno-unused-parameter \ + -Wunreachable-code \ + -Wformat=2 \ + -Wundef \ + -Wpointer-arith \ + -Wwrite-strings \ + -Waggregate-return \ + -Wmissing-prototypes \ + -Wmissing-declarations \ + -Wmissing-noreturn \ + -Wmissing-format-attribute \ + -Wredundant-decls \ + -Wnested-externs \ + -Wno-deprecated-declarations" + +# cross-compilation environment +ARCH="x86_64" +MINGW_ARCH="mingw64" +HOST="x86_64-w64-mingw32" +export CC="/usr/bin/${HOST}-gcc" +export CPP="/usr/bin/${HOST}-cpp" +export CXX="/usr/bin/${HOST}-g++" +export AR="/usr/bin/${HOST}-ar" +export STRIP="/usr/bin/${HOST}-strip" +export WINDRES="/usr/bin/${HOST}-windres" +export CFLAGS="-I/windows/${MINGW_ARCH}/include/ ${CFLAGS}" +export LDFLAGS="-static-libgcc ${LDFLAGS}" +export PKG_CONFIG_SYSROOT_DIR="/windows" +export PKG_CONFIG_PATH="/windows/${MINGW_ARCH}/lib/pkgconfig/" +export PKG_CONFIG="/usr/bin/pkg-config" +export NOCONFIGURE=1 + +# stop on errors +set -e + + +log() { + echo "=========== $(date '+%Y-%m-%d %H:%M:%S %Z') $* ===========" +} + + +git_clone_geany_if_necessary() { + if [ -d ${GEANY_SOURCE_DIR} ]; then + log "Copying Geany source" + cp --archive ${GEANY_SOURCE_DIR}/ ${GEANY_BUILD_DIR}/ + else + log "Cloning Geany repository from ${GEANY_GIT_REPOSITORY}" + git clone --depth 1 ${GEANY_GIT_REPOSITORY} ${GEANY_BUILD_DIR} + fi +} + + +parse_geany_version() { + GEANY_VERSION=$(sed -n -E -e 's/^AC_INIT.\[Geany\], \[(.+)\],/\1/p' ${GEANY_BUILD_DIR}/configure.ac) + GEANY_GIT_REVISION=$(cd ${GEANY_BUILD_DIR} && git rev-parse --short --revs-only HEAD 2>/dev/null || true) + TIMESTAMP=$(date +%Y%m%d%H%M%S) + # add pull request number if this is a CI and a PR build + if [ "${GITHUB_PULL_REQUEST}" ]; then + GEANY_VERSION="${GEANY_VERSION}_ci_${TIMESTAMP}_${GEANY_GIT_REVISION}_pr${GITHUB_PULL_REQUEST}" + elif [ "${CI}" -a "${GEANY_GIT_REVISION}" ]; then + GEANY_VERSION="${GEANY_VERSION}_ci_${TIMESTAMP}_${GEANY_GIT_REVISION}" + elif [ "${GEANY_GIT_REVISION}" ]; then + GEANY_VERSION="${GEANY_VERSION}_git_${TIMESTAMP}_${GEANY_GIT_REVISION}" + fi + GEANY_INSTALLER_FILENAME="geany-${GEANY_VERSION}_setup.exe" +} + + +log_environment() { + log "Using environment" + CONFIGURE_OPTIONS="--disable-silent-rules --host=${HOST} --prefix=${GEANY_RELEASE_DIR} --with-libiconv-prefix=/windows/mingw64" + echo "Geany version : ${GEANY_VERSION}" + echo "Geany GIT revision : ${GEANY_GIT_REVISION}" + echo "PATH : ${PATH}" + echo "HOST : ${HOST}" + echo "CC : ${CC}" + echo "CFLAGS : ${CFLAGS}" + echo "Configure : ${CONFIGURE_OPTIONS}" +} + + +patch_version_information() { + log "Patching version information" + + if [ -z "${GEANY_GIT_REVISION}" ] && [ -z "${CI}" ]; then + return + fi + + # parse version string and decrement the patch and/or minor levels to keep nightly build + # versions below the next release version + regex='^([0-9]*)[.]([0-9]*)([.]([0-9]*))?' + if [[ ${GEANY_VERSION} =~ $regex ]]; then + MAJOR="${BASH_REMATCH[1]}" + MINOR="${BASH_REMATCH[2]}" + PATCH="${BASH_REMATCH[4]}" + if [ -z "${PATCH}" ] || [ "${PATCH}" = "0" ]; then + MINOR="$((MINOR-1))" + PATCH="90" + else + PATCH="$((PATCH-1))" + fi + else + echo "Could not extract or parse version tag" >&2 + exit 1 + fi + # replace version information in configure.ac and for Windows binaries + sed -i -E "s/^AC_INIT.\[Geany\], \[(.+)\],/AC_INIT(\[Geany\], \[${GEANY_VERSION}\],/" ${GEANY_BUILD_DIR}/configure.ac + sed -i -E "s/^#define VER_FILEVERSION_STR[[:space:]]+\".*\"+/#define VER_FILEVERSION_STR \"${GEANY_VERSION}\"/" ${GEANY_BUILD_DIR}/geany_private.rc + sed -i -E "s/^#define VER_FILEVERSION[[:space:]]+[0-9,]+/#define VER_FILEVERSION ${MAJOR},${MINOR},${PATCH},90/" ${GEANY_BUILD_DIR}/geany_private.rc + sed -i -E "s/^[[:space:]]+version=\"[0-9.]+\"/version=\"${MAJOR}.${MINOR}.${PATCH}.90\"/" ${GEANY_BUILD_DIR}/geany.exe.manifest + sed -i -E "s/^!define PRODUCT_VERSION \"@VERSION@\"/!define PRODUCT_VERSION \"${GEANY_VERSION}\"/" ${GEANY_BUILD_DIR}/geany.nsi.in + sed -i -E "s/^!define PRODUCT_VERSION_ID \"@VERSION@.0.0\"/!define PRODUCT_VERSION_ID \"${MAJOR}.${MINOR}.${PATCH}.90\"/" ${GEANY_BUILD_DIR}/geany.nsi.in +} + + +build_geany() { + cd ${GEANY_BUILD_DIR} + log "Running autogen.sh" + ./autogen.sh + log "Running configure" + ./configure ${CONFIGURE_OPTIONS} + log "Running make" + make + log "Running install-strip" + make install-strip +} + + +sign_file() { + echo "Sign file $1" + if [ -f /certificates/cert.pem ] && [ -f /certificates/key.pem ]; then + osslsigncode sign \ + -certs /certificates/cert.pem \ + -key /certificates/key.pem \ + -n "Geany Binary" \ + -i "https://www.geany.org/" \ + -ts http://zeitstempel.dfn.de/ \ + -h sha512 \ + -in ${1} \ + -out ${1}-signed + mv ${1}-signed ${1} + else + echo "Skip signing due to missing certificate" + fi +} + + +sign_geany_binaries() { + log "Signing Geany binary files" + for binary_file_pattern in ${GEANY_RELEASE_BINARY_PATTERNS[@]}; do + for binary_file in $(ls ${binary_file_pattern}); do + sign_file ${binary_file} + done + done +} + + +create_gtk_bundle() { + log "Creating GTK bundle" + mkdir ${GTK_BUNDLE_DIR} + cd ${GTK_BUNDLE_DIR} + bash ${GEANY_BUILD_DIR}/scripts/gtk-bundle-from-msys2.sh -x -3 +} + + +fetch_geany_themes() { + log "Cloning Geany-Themes repository from ${GEANY_THEMES_REPOSITORY}" + git clone --depth 1 ${GEANY_THEMES_REPOSITORY} ${GEANY_THEMES_DIR} +} + + +create_installer() { + log "Creating NSIS installer" + makensis \ + -V3 \ + -WX \ + -DGEANY_INSTALLER_NAME="${GEANY_INSTALLER_FILENAME}" \ + -DGEANY_RELEASE_DIR=${GEANY_RELEASE_DIR} \ + -DGEANY_THEMES_DIR=${GEANY_THEMES_DIR} \ + -DGTK_BUNDLE_DIR=${GTK_BUNDLE_DIR} \ + ${GEANY_BUILD_DIR}/geany.nsi +} + + +sign_installer() { + log "Signing NSIS installer" + sign_file ${GEANY_BUILD_DIR}/${GEANY_INSTALLER_FILENAME} +} + + +test_installer() { + log "Test NSIS installer" + exiftool -FileName -FileType -FileVersion -FileVersionNumber ${GEANY_BUILD_DIR}/${GEANY_INSTALLER_FILENAME} + # install Geany: perform a silent install and check for installed files + mingw-w64-i686-wine ${GEANY_BUILD_DIR}/${GEANY_INSTALLER_FILENAME} /S /D=${GEANY_INSTALLATION_DIR_WIN} + # check if we have something installed + test -f ${GEANY_INSTALLATION_DIR}/uninst.exe || exit 1 + test -f ${GEANY_INSTALLATION_DIR}/bin/geany.exe || exit 1 + test -f ${GEANY_INSTALLATION_DIR}/lib/geany/export.dll || exit 1 + test ! -f ${GEANY_INSTALLATION_DIR}/lib/geany/demoplugin.dll || exit 1 +} + + +log_geany_version() { + log "Log installed Geany version" + mingw-w64-x86_64-wine ${GEANY_INSTALLATION_DIR}/bin/geany.exe --version 2>/dev/null + exiftool -FileName -FileType -FileVersion -FileVersionNumber ${GEANY_INSTALLATION_DIR}/bin/geany.exe +} + + +test_uninstaller() { + log "Test NSIS uninstaller" + # uninstall Geany and test if everything is clean + mingw-w64-i686-wine ${GEANY_INSTALLATION_DIR}/uninst.exe /S + sleep 10 # it seems the uninstaller returns earlier than the files are actually removed, so wait a moment + test ! -e ${GEANY_INSTALLATION_DIR} +} + + +copy_installer_and_bundle() { + log "Copying NSIS installer and GTK bundle" + cp ${GEANY_BUILD_DIR}/${GEANY_INSTALLER_FILENAME} ${OUTPUT_DIRECTORY} + cd ${GTK_BUNDLE_DIR} + zip --quiet --recurse-paths ${OUTPUT_DIRECTORY}/gtk_bundle_$(date '+%Y%m%dT%H%M%S').zip * +} + + +main() { + git_clone_geany_if_necessary + + parse_geany_version + log_environment + patch_version_information + build_geany + sign_geany_binaries + + create_gtk_bundle + fetch_geany_themes + create_installer + sign_installer + + test_installer + log_geany_version + test_uninstaller + + copy_installer_and_bundle + + log "Done." +} + + +main diff --git a/scripts/gtk-bundle-from-msys2.sh b/scripts/gtk-bundle-from-msys2.sh index 0efeaa3c12..e6f4466cf5 100644 --- a/scripts/gtk-bundle-from-msys2.sh +++ b/scripts/gtk-bundle-from-msys2.sh @@ -286,7 +286,6 @@ Other dependencies are provided by the MSYS2 project (https://msys2.github.io) and were downloaded from: EOF echo -e "${package_urls}" >> "${filename}" - unix2dos "${filename}" } create_zip_archive() { From e2066d4f5f9600b0d8acd0ebeaeddebc93d1f15a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrico=20Tr=C3=B6ger?= Date: Sun, 15 Jan 2023 18:10:32 +0100 Subject: [PATCH 2/2] Use Ubuntu 20.04 for Linux CI builds --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cbe61fbabc..ae83753679 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,7 +28,7 @@ env: jobs: linux: name: Linux Build (BINRELOC=${{ matrix.binreloc }}) - runs-on: ubuntu-18.04 + runs-on: ubuntu-20.04 strategy: fail-fast: false