diff options
author | Emmanuele Bassi <ebassi@gmail.com> | 2020-11-09 15:00:43 +0000 |
---|---|---|
committer | Emmanuele Bassi <ebassi@gmail.com> | 2020-11-09 15:00:43 +0000 |
commit | 375201f6a193ac04d46d1b1461fc7d210905e0d7 (patch) | |
tree | 0e9d7ee3741ca0291d6b90743fe6872a950f5831 | |
parent | e56b3f57d0393b68f40538fb0e633e2d6d4a9acd (diff) | |
parent | 3c1ee32c33d74e893414e5a56b78666514ea0bf0 (diff) | |
download | gdk-pixbuf-375201f6a193ac04d46d1b1461fc7d210905e0d7.tar.gz |
Merge branch 'ci-update' into 'master'
Ci update
See merge request GNOME/gdk-pixbuf!87
-rw-r--r-- | .clang-format | 11 | ||||
-rw-r--r-- | .gitlab-ci.yml | 76 | ||||
-rw-r--r-- | .gitlab/ci/fedora.Dockerfile (renamed from .gitlab/ci/Dockerfile) | 15 | ||||
-rwxr-xr-x | .gitlab/ci/run-docker.sh | 136 | ||||
-rwxr-xr-x | .gitlab/scripts/clang-format-diff.py | 133 | ||||
-rwxr-xr-x | .gitlab/scripts/meson-junit-report.py | 114 | ||||
-rwxr-xr-x | .gitlab/scripts/run-style-check-diff.sh | 44 | ||||
-rwxr-xr-x | .gitlab/scripts/run-tests.sh | 24 | ||||
-rw-r--r-- | meson.build | 4 |
9 files changed, 531 insertions, 26 deletions
diff --git a/.clang-format b/.clang-format new file mode 100644 index 000000000..13fd0fbd4 --- /dev/null +++ b/.clang-format @@ -0,0 +1,11 @@ +# See https://wiki.apertis.org/Guidelines/Coding_conventions#Code_formatting +BasedOnStyle: GNU +AlwaysBreakAfterDefinitionReturnType: All +BreakBeforeBinaryOperators: None +BinPackParameters: false +SpaceAfterCStyleCast: true +# Our column limit is actually 80, but setting that results in clang-format +# making a lot of dubious hanging-indent choices; disable it and assume the +# developer will line wrap appropriately. clang-format will still check +# existing hanging indents. +ColumnLimit: 0 diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6118483ab..847d2d512 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,47 +3,71 @@ stages: - docs - deploy +# Common variables variables: CCACHE_DIR: _ccache + COMMON_MESON_FLAGS: "-Dwerror=true -Dglib:werror=false" + LOADERS_FLAGS: "-Dpng=true -Djpeg=true -Dtiff=true" + MESON_TEST_TIMEOUT_MULTIPLIER: 3 + FEDORA_IMAGE: "registry.gitlab.gnome.org/gnome/gdk-pixbuf/fedora:v1" + +.only-default: + only: + - branches + except: + - tags .build-linux: - image: registry.gitlab.gnome.org/gnome/gdk-pixbuf/master:v2 - stage: build before_script: - mkdir -p _ccache script: - - meson ${BUILD_OPTS} _build . + - meson ${COMMON_MESON_FLAGS} ${LOADERS_FLAGS} ${BUILD_OPTS} _build . - ninja -C _build + - .gitlab/scripts/run-tests.sh _build artifacts: - when: on_failure + when: always name: "gdk-pixbuf-${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}" paths: - - "${CI_PROJECT_DIR}/build_*/meson-logs" + - "${CI_PROJECT_DIR}/_build/meson-logs" + - "${CI_PROJECT_DIR}/_build/report-${CI_JOB_NAME}.xml" + reports: + junit: + - "${CI_PROJECT_DIR}/_build/report-${CI_JOB_NAME}.xml" cache: key: "$CI_JOB_NAME" paths: - _ccache/ +style-check-diff: + extends: .only-default + image: $FEDORA_IMAGE + stage: .pre + allow_failure: true + script: + - .gitlab/scripts/run-style-check-diff.sh + meson-fedora-x86_64: + stage: build extends: .build-linux - variables: - BUILD_OPTS: "-Dpng=true -Djpeg=true -Dtiff=true -Djasper=true" - after_script: - - meson test -C _build -t 3 + image: ${FEDORA_IMAGE} + needs: [] release-build: + stage: build extends: .build-linux + image: ${FEDORA_IMAGE} + needs: [] variables: - BUILD_OPTS: "-Dpng=true -Djpeg=true -Dtiff=true -Dbuildtype=release" - after_script: - - meson test -C _build -t 3 + BUILD_OPTS: "-Dbuildtype=release" reference: - extends: .build-linux stage: docs + image: ${FEDORA_IMAGE} + needs: [] variables: - BUILD_OPTS: "-Dgtk_doc=true" - after_script: + BUILD_OPTS: "-Dbuildtype=release -Dgtk_doc=true" + script: + - meson ${COMMON_MESON_FLAGS} ${LOADERS_FLAGS} ${BUILD_OPTS} _build . - ninja -C _build gdk-pixbuf-doc - mv _build/docs/html _reference artifacts: @@ -51,8 +75,29 @@ reference: paths: - _reference +release-dist: + stage: build + image: ${FEDORA_IMAGE} + needs: [] + variables: + BUILD_OPTS: "-Dbuildtype=release -Dgtk_doc=true" + script: + - meson ${COMMON_MESON_FLAGS} ${LOADERS_FLAGS} ${BUILD_OPTS} _build . + - meson compile -C _build + - meson dist -C _build + - ninja -C _build gdk-pixbuf-doc + - tar -c -J -f _build/gdk-pixbuf-docs-${CI_COMMIT_TAG}.tar.xz _build/docs/ + artifacts: + when: on_success + paths: + - _build/meson-dist/gdk-pixbuf-${CI_COMMIT_TAG}.tar.xz + - _build/gdk-pixbuf-docs-${CI_COMMIT_TAG}.tar.xz + only: + - tags + pages: stage: deploy + needs: [reference] script: - mv _reference public artifacts: @@ -63,6 +108,7 @@ pages: msys2-mingw64: stage: build + needs: [] tags: - win32-ps variables: diff --git a/.gitlab/ci/Dockerfile b/.gitlab/ci/fedora.Dockerfile index 5996b9fb0..83fe27d98 100644 --- a/.gitlab/ci/Dockerfile +++ b/.gitlab/ci/fedora.Dockerfile @@ -1,7 +1,9 @@ -FROM fedora:28 +FROM fedora:33 RUN dnf -y install \ ccache \ + clang \ + clang-analyzer \ gcc \ gcc-c++ \ gettext \ @@ -12,17 +14,24 @@ RUN dnf -y install \ gtk-doc \ itstool \ jasper-devel \ + lcov \ + libasan \ libjpeg-turbo-devel \ libpng-devel \ libtiff-devel \ libX11-devel \ - make \ meson \ + python3 \ + python3-jinja2 \ + python3-pip \ + python3-pygments \ + python3-wheel \ redhat-rpm-config \ shared-mime-info \ + which \ && dnf clean all -RUN pip3 install meson==0.48.1 +RUN pip3 install meson==0.55.3 ARG HOST_USER_ID=5555 ENV HOST_USER_ID ${HOST_USER_ID} diff --git a/.gitlab/ci/run-docker.sh b/.gitlab/ci/run-docker.sh index b2e79a1b7..85bce6548 100755 --- a/.gitlab/ci/run-docker.sh +++ b/.gitlab/ci/run-docker.sh @@ -1,11 +1,135 @@ #!/bin/bash +read_arg() { + # $1 = arg name + # $2 = arg value + # $3 = arg parameter + local rematch='^[^=]*=(.*)$' + if [[ $2 =~ $rematch ]]; then + read "$1" <<< "${BASH_REMATCH[1]}" + else + read "$1" <<< "$3" + # There is no way to shift our callers args, so + # return 1 to indicate they should do it instead. + return 1 + fi +} + set -e -TAG="registry.gitlab.gnome.org/gnome/gdk-pixbuf/master:v1" +build=0 +run=0 +push=0 +list=0 +print_help=0 +no_login=0 + +while (($# > 0)); do + case "${1%%=*}" in + build) build=1;; + run) run=1;; + push) push=1;; + list) list=1;; + help) print_help=1;; + --base|-b) read_arg base "$@" || shift;; + --base-version) read_arg base_version "$@" || shift;; + --no-login) no_login=1;; + *) echo -e "\e[1;31mERROR\e[0m: Unknown option '$1'"; exit 1;; + esac + shift +done + +if [ $print_help == 1 ]; then + echo "$0 - Build and run Docker images" + echo "" + echo "Usage: $0 <command> [options] [basename]" + echo "" + echo "Available commands" + echo "" + echo " build --base=<BASENAME> - Build Docker image <BASENAME>.Dockerfile" + echo " run --base=<BASENAME> - Run Docker image <BASENAME>" + echo " push --base=<BASENAME> - Push Docker image <BASENAME> to the registry" + echo " list - List available images" + echo " help - This help message" + echo "" + exit 0 +fi + +cd "$(dirname "$0")" + +if [ $list == 1 ]; then + echo "Available Docker images:" + for f in *.Dockerfile; do + filename=$( basename -- "$f" ) + basename="${filename%.*}" + + echo -e " \e[1;39m$basename\e[0m" + done + exit 0 +fi + +# All commands after this require --base to be set +if [ -z $base ]; then + echo "Usage: $0 <command>" + exit 1 +fi + +if [ ! -f "$base.Dockerfile" ]; then + echo -e "\e[1;31mERROR\e[0m: Dockerfile for '$base' not found" + exit 1 +fi + +if [ -z $base_version ]; then + base_version="latest" +elif [ $base_version != "latest" ]; then + base_version="v$base_version" +fi + +if [ ! -x "$(command -v docker)" ] || [ docker --help |& grep -q podman ]; then + # Docker is actually implemented by podman, and its OCI output + # is incompatible with some of the dockerd instances on GitLab + # CI runners. + echo "Using: Podman" + format="--format docker" + CMD="podman" +else + echo "Using: Docker" + format="" + CMD="sudo docker" +fi + +REGISTRY="registry.gitlab.gnome.org" +TAG="${REGISTRY}/gnome/gdk-pixbuf/${base}:${base_version}" + +if [ $build == 1 ]; then + echo -e "\e[1;32mBUILDING\e[0m: ${base} as ${TAG}" + ${CMD} build \ + ${format} \ + --build-arg HOST_USER_ID="$UID" \ + --tag "${TAG}" \ + --file "${base}.Dockerfile" . + exit $? +fi + +if [ $push == 1 ]; then + echo -e "\e[1;32mPUSHING\e[0m: ${base} as ${TAG}" + + if [ $no_login == 0 ]; then + ${CMD} login ${REGISTRY} + fi + + ${CMD} push ${TAG} + exit $? +fi -sudo docker build --build-arg HOST_USER_ID="$UID" --tag "${TAG}" \ - --file "Dockerfile" . -sudo docker run --rm --security-opt label=disable \ - --volume "$(pwd)/../..:/home/user/app" --workdir "/home/user/app" \ - --tty --interactive "${TAG}" bash +if [ $run == 1 ]; then + echo -e "\e[1;32mRUNNING\e[0m: ${base} as ${TAG}" + ${CMD} run \ + --rm \ + --volume "$(pwd)/..:/home/user/app" \ + --workdir "/home/user/app" \ + --tty \ + --interactive "${TAG}" \ + bash + exit $? +fi diff --git a/.gitlab/scripts/clang-format-diff.py b/.gitlab/scripts/clang-format-diff.py new file mode 100755 index 000000000..3fb776c59 --- /dev/null +++ b/.gitlab/scripts/clang-format-diff.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +# +# === clang-format-diff.py - ClangFormat Diff Reformatter ---*- python -*-=== # +# +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# ===---------------------------------------------------------------------=== # + +""" +This script reads input from a unified diff and reformats all the changed +lines. This is useful to reformat all the lines touched by a specific patch. +Example usage for git/svn users: + + git diff -U0 --no-color HEAD^ | clang-format-diff.py -p1 -i + svn diff --diff-cmd=diff -x-U0 | clang-format-diff.py -i + +""" +from __future__ import absolute_import, division, print_function + +import argparse +import difflib +import re +import subprocess +import sys + +if sys.version_info.major >= 3: + from io import StringIO +else: + from io import BytesIO as StringIO + + +def main(): + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument('-i', action='store_true', default=False, + help='apply edits to files instead of displaying a ' + 'diff') + parser.add_argument('-p', metavar='NUM', default=0, + help='strip the smallest prefix containing P slashes') + parser.add_argument('-regex', metavar='PATTERN', default=None, + help='custom pattern selecting file paths to reformat ' + '(case sensitive, overrides -iregex)') + parser.add_argument('-iregex', metavar='PATTERN', + default=r'.*\.(cpp|cc|c\+\+|cxx|c|cl|h|hh|hpp|m|mm|inc' + r'|js|ts|proto|protodevel|java|cs)', + help='custom pattern selecting file paths to reformat ' + '(case insensitive, overridden by -regex)') + parser.add_argument('-sort-includes', action='store_true', default=False, + help='let clang-format sort include blocks') + parser.add_argument('-v', '--verbose', action='store_true', + help='be more verbose, ineffective without -i') + parser.add_argument('-style', + help='formatting style to apply (LLVM, Google, ' + 'Chromium, Mozilla, WebKit)') + parser.add_argument('-binary', default='clang-format', + help='location of binary to use for clang-format') + args = parser.parse_args() + + # Extract changed lines for each file. + filename = None + lines_by_file = {} + for line in sys.stdin: + match = re.search(r'^\+\+\+\ (.*?/){%s}(\S*)' % args.p, line) + if match: + filename = match.group(2) + if filename is None: + continue + + if args.regex is not None: + if not re.match('^%s$' % args.regex, filename): + continue + else: + if not re.match('^%s$' % args.iregex, filename, re.IGNORECASE): + continue + + match = re.search(r'^@@.*\+(\d+)(,(\d+))?', line) + if match: + start_line = int(match.group(1)) + line_count = 1 + if match.group(3): + line_count = int(match.group(3)) + if line_count == 0: + continue + end_line = start_line + line_count - 1 + lines_by_file.setdefault(filename, []).extend( + ['-lines', str(start_line) + ':' + str(end_line)]) + + # Reformat files containing changes in place. + # We need to count amount of bytes generated in the output of + # clang-format-diff. If clang-format-diff doesn't generate any bytes it + # means there is nothing to format. + format_line_counter = 0 + for filename, lines in lines_by_file.items(): + if args.i and args.verbose: + print('Formatting {}'.format(filename)) + command = [args.binary, filename] + if args.i: + command.append('-i') + if args.sort_includes: + command.append('-sort-includes') + command.extend(lines) + if args.style: + command.extend(['-style', args.style]) + p = subprocess.Popen(command, + stdout=subprocess.PIPE, + stderr=None, + stdin=subprocess.PIPE, + universal_newlines=True) + stdout, _ = p.communicate() + if p.returncode != 0: + sys.exit(p.returncode) + + if not args.i: + with open(filename) as f: + code = f.readlines() + formatted_code = StringIO(stdout).readlines() + diff = difflib.unified_diff(code, formatted_code, + filename, filename, + '(before formatting)', + '(after formatting)') + diff_string = ''.join(diff) + if diff_string: + format_line_counter += sys.stdout.write(diff_string) + + if format_line_counter > 0: + sys.exit(1) + + +if __name__ == '__main__': + main() diff --git a/.gitlab/scripts/meson-junit-report.py b/.gitlab/scripts/meson-junit-report.py new file mode 100755 index 000000000..532305cdc --- /dev/null +++ b/.gitlab/scripts/meson-junit-report.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 + +# Turns a Meson testlog.json file into a JUnit XML report +# +# Copyright 2019 GNOME Foundation +# +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# Original author: Emmanuele Bassi + +import argparse +import datetime +import json +import os +import sys +import xml.etree.ElementTree as ET + +aparser = argparse.ArgumentParser(description='Turns a Meson test log into a JUnit report') +aparser.add_argument('--project-name', metavar='NAME', + help='The project name', + default='unknown') +aparser.add_argument('--backend', metavar='NAME', + help='The used backend', + default='unknown') +aparser.add_argument('--job-id', metavar='ID', + help='The job ID for the report', + default='Unknown') +aparser.add_argument('--branch', metavar='NAME', + help='Branch of the project being tested', + default='master') +aparser.add_argument('--output', metavar='FILE', + help='The output file, stdout by default', + type=argparse.FileType('w', encoding='UTF-8'), + default=sys.stdout) +aparser.add_argument('infile', metavar='FILE', + help='The input testlog.json, stdin by default', + type=argparse.FileType('r', encoding='UTF-8'), + default=sys.stdin) + +args = aparser.parse_args() + +outfile = args.output + +testsuites = ET.Element('testsuites') +testsuites.set('id', '{}/{}'.format(args.job_id, args.branch)) +testsuites.set('package', args.project_name) +testsuites.set('timestamp', datetime.datetime.utcnow().isoformat(timespec='minutes')) + +suites = {} +for line in args.infile: + data = json.loads(line) + (full_suite, unit_name) = data['name'].split(' / ') + (project_name, suite_name) = full_suite.split(':') + + duration = data['duration'] + return_code = data['returncode'] + result = data['result'] + log = data['stdout'] + + unit = { + 'suite': suite_name, + 'name': unit_name, + 'duration': duration, + 'returncode': return_code, + 'result': result, + 'stdout': log, + } + + units = suites.setdefault(suite_name, []) + units.append(unit) + +for name, units in suites.items(): + print('Processing suite {} (units: {})'.format(name, len(units))) + + def if_failed(unit): + if unit['result'] in ['ERROR', 'FAIL', 'UNEXPECTEDPASS', 'TIMEOUT']: + return True + return False + + def if_succeded(unit): + if unit['result'] in ['OK', 'EXPECTEDFAIL', 'SKIP']: + return True + return False + + successes = list(filter(if_succeded, units)) + failures = list(filter(if_failed, units)) + print(' - {}: {} pass, {} fail'.format(name, len(successes), len(failures))) + + testsuite = ET.SubElement(testsuites, 'testsuite') + testsuite.set('name', '{}/{}'.format(args.project_name, name)) + testsuite.set('tests', str(len(units))) + testsuite.set('errors', str(len(failures))) + testsuite.set('failures', str(len(failures))) + + for unit in successes: + testcase = ET.SubElement(testsuite, 'testcase') + testcase.set('classname', '{}/{}'.format(args.project_name, unit['suite'])) + testcase.set('name', '{}/{}'.format(args.backend, unit['name'])) + testcase.set('time', str(unit['duration'])) + + for unit in failures: + testcase = ET.SubElement(testsuite, 'testcase') + testcase.set('classname', '{}/{}'.format(args.project_name, unit['suite'])) + testcase.set('name', '{}/{}'.format(args.backend, unit['name'])) + testcase.set('time', str(unit['duration'])) + + failure = ET.SubElement(testcase, 'failure') + failure.set('classname', '{}/{}'.format(args.project_name, unit['suite'])) + testcase.set('name', '{}/{}'.format(args.backend, unit['name'])) + failure.set('type', 'error') + failure.text = unit['stdout'] + +output = ET.tostring(testsuites, encoding='unicode') +outfile.write(output) diff --git a/.gitlab/scripts/run-style-check-diff.sh b/.gitlab/scripts/run-style-check-diff.sh new file mode 100755 index 000000000..1b4963a6d --- /dev/null +++ b/.gitlab/scripts/run-style-check-diff.sh @@ -0,0 +1,44 @@ +#!/bin/bash + +set -e + +# We need to add a new remote for the upstream master, since this script could +# be running in a personal fork of the repository which has out of date branches. +if [ "${CI_PROJECT_NAMESPACE}" != "GNOME" ]; then + echo "Retrieving the current upstream repository from ${CI_PROJECT_NAMESPACE}/${CI_PROJECT_NAME}..." + git remote add upstream https://gitlab.gnome.org/GNOME/gdk-pixbuf.git + git fetch upstream + ORIGIN="upstream" +else + echo "Reusing the existing repository on ${CI_PROJECT_NAMESPACE}/${CI_PROJECT_NAME}" + ORIGIN="origin" +fi + +# Work out the newest common ancestor between the detached HEAD that this CI job +# has checked out, and the upstream target branch (which will typically be +# `upstream/master` or `upstream/gtk-3-24`). +# +# `${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}` is only defined if we’re running in +# a merge request pipeline; fall back to `${CI_DEFAULT_BRANCH}` otherwise. +newest_common_ancestor_sha=$(diff --old-line-format='' --new-line-format='' <(git rev-list --first-parent "${ORIGIN}/${CI_MERGE_REQUEST_TARGET_BRANCH_NAME:-${CI_DEFAULT_BRANCH}}") <(git rev-list --first-parent HEAD) | head -1) +git diff -U0 --no-color "${newest_common_ancestor_sha}" | .gitlab/scripts/clang-format-diff.py -binary "clang-format" -p1 +exit_status=$? + +# The style check is not infallible. The clang-format configuration cannot +# perfectly describe GTK’s coding style: in particular, it cannot align +# function arguments. The documented coding style for GTK takes priority over +# clang-format suggestions. Hopefully we can eventually improve clang-format to +# be configurable enough for our coding style. That’s why this CI check is OK +# to fail: the idea is that people can look through the output and ignore it if +# it’s wrong. (That situation can also happen if someone touches pre-existing +# badly formatted code and it doesn’t make sense to tidy up the wider coding +# style with the changes they’re making.) +echo "" +echo "Note that clang-format output is advisory and cannot always match the" +echo "GTK coding style, documented at:" +echo " https://gitlab.gnome.org/GNOME/gtk/blob/master/docs/CODING-STYLE" +echo "Warnings from this tool can be ignored in favour of the documented " +echo "coding style, or in favour of matching the style of existing" +echo "surrounding code." + +exit ${exit_status} diff --git a/.gitlab/scripts/run-tests.sh b/.gitlab/scripts/run-tests.sh new file mode 100755 index 000000000..1212d7cda --- /dev/null +++ b/.gitlab/scripts/run-tests.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +set +x +set +e + +srcdir=$( pwd ) +builddir=$1 +backend=$2 + +meson test -C ${builddir} + +# Store the exit code for the CI run, but always +# generate the reports +exit_code=$? + +cd ${builddir} + +${srcdir}/.gitlab/scripts/meson-junit-report.py \ + --project-name=gdk-pixbuf \ + --job-id="${CI_JOB_NAME}" \ + --output=report-${CI_JOB_NAME}.xml \ + meson-logs/testlog.json + +exit $exit_code diff --git a/meson.build b/meson.build index 7d63498b4..267539d81 100644 --- a/meson.build +++ b/meson.build @@ -1,12 +1,12 @@ project('gdk-pixbuf', 'c', version: '2.42.1', - license: 'LGPLv2.1+', + license: 'LGPL-2.1-or-later', default_options: [ 'buildtype=debugoptimized', 'warning_level=1', 'c_std=c99', ], - meson_version: '>= 0.48.0') + meson_version: '>= 0.55.3') add_project_arguments([ '-D_POSIX_C_SOURCE=200809L', '-D_DEFAULT_SOURCE', '-D_XOPEN_SOURCE=700' ], language: 'c') |