diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index dda04dd..0d1e16d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,6 +1,9 @@ include: - 'https://source.puri.sm/Librem5/librem5-ci/raw/master/librem5-pipeline-definitions.yml' - 'https://gitlab.gnome.org/GNOME/citemplates/raw/master/flatpak/flatpak_ci_initiative.yml' + - project: 'guidog/meta-phosh' + ref: '2aeb9bfe2b230ca3aeda8275e42c95f94f6723ca' + file: '/ci/phosh-common-jobs.yml' stages: - build @@ -79,16 +82,10 @@ build-gtkdoc: - _reference check-po: - extends: build:native stage: test + extends: .phosh-check-po dependencies: - build:native - script: - # barf on untranslated C or UI files. Seems intltool - # can't be told to exit with non-zero exit status - # in this case - - cd po/ - - intltool-update -m 2>&1 | grep -qs '/.*\.\(c|ui\)' && { intltool-update -m; exit 1; } || exit 0 package:deb-debian-trixie:arm64: variables: diff --git a/.gitlab-ci/check-consistency b/.gitlab-ci/check-consistency new file mode 100755 index 0000000..df37196 --- /dev/null +++ b/.gitlab-ci/check-consistency @@ -0,0 +1,98 @@ +#!/bin/bash +# +# Copyright (C) 2024 The Phosh developers +# SPDX-License-Identifier: GPL-3.0-or-later +# Author: Guido Günther +# +# Check if NEWS, changelog, meson and metainfo are in sync + +set -e + +COLOR= +if [ -n "${TERM}" ] && [ "${TERM}" != "dumb" ]; then + COLOR=1 +fi + +function log +{ + local level="${1}" + local fd=2 + local use_color + + shift + if [ -n "${COLOR}" ]; then + [ "${level}" == warn ] || [ "${level}" == error ] || fd=1 + ! [ -t "${fd}" ] || use_color=1 + + if [ -n "${use_color}" ]; then + case "${level}" in + warn) + tput setaf 1 + ;; + error) + tput bold; tput setaf 1 + ;; + info) + tput setaf 2 + ;; + esac + fi + fi + + echo "$@" + + [ -z "${use_color}" ] || tput sgr0 +} + + +if [ -f debian/changelog ]; then + log info "Fetching version from d/changelog" + VERSION=$(dpkg-parsechangelog -SVersion) +elif [ -f meson.build ]; then + log info "Fetching version from meson build file" + VERSION=$(sed -n "s/.*version\s*:\s*'\([0-9].*\)'.*/\1/p" meson.build) +else + log error "E: Don't know how to get version information" + exit 1 +fi + +echo "I: Checking for '${VERSION}'" + +# News +if ! head -1 NEWS | grep -E -qs "\s+${VERSION}\s*$"; then + log error "E: Version ${VERSION} not in NEWS file" + exit 1 +else + log info "I: Found matching news entry" +fi + +# meson.build +MESON_VERSION="${VERSION/\~/.}" +if [ -f meson.build ]; then + if ! grep -qs "version\s*:\s*'$MESON_VERSION'" meson.build; then + log error "E: Version ${MESON_VERSION} not in meson.build file" + exit 1 + else + log info "I: Found matching meson version entry" + fi +else + log info "I: no meson project" +fi + +# appstream info +METAINFO=$(ls data/*metainfo.xml.in* 2>/dev/null || true) +if [ -z "${METAINFO}" ]; then + log warn "W: No metainfo" + exit 0 +fi + +if ! grep -qs "$MESON_VERSION\"" "${METAINFO}"; then + log error "E: Version ${MESON_VERSION} not in metainfo ${METAINFO}" + if [[ "${VERSION}" =~ ~(alpha|beta|rc) ]]; then + log info "I: Not a stable release, no metainfo is fine" + else + exit 1 + fi +else + log info "I: Found matching metainfo entry" +fi diff --git a/.gitlab-ci/check-po b/.gitlab-ci/check-po new file mode 100755 index 0000000..03aaaee --- /dev/null +++ b/.gitlab-ci/check-po @@ -0,0 +1,25 @@ +#!/bin/bash +# +# Copyright (C) 2024 The Phosh developers +# SPDX-License-Identifier: GPL-3.0-or-later +# Author: Guido Günther + +cd po/ || exit 1 +# barf on untranslated C files. Seems intltool +# can't be told to exit with non-zero exit status +# in this case + +if intltool-update -m 2>&1 | grep -E -qs '/.*\.(c|ui|in)'; then + intltool-update -m + exit 1 +fi + +# Check for broken po files +for file in *.po; do + echo -n "Checking ${file}: " + msgfmt -v -c "${file}" + # Check for errors, msgfmt returns 0 on errors too + if msgfmt -c "${file}" 2>&1 | grep -qs 'fatal error'; then + exit 1 + fi +done diff --git a/.gitlab-ci/check-style.py b/.gitlab-ci/check-style.py new file mode 100755 index 0000000..5783866 --- /dev/null +++ b/.gitlab-ci/check-style.py @@ -0,0 +1,185 @@ +#!/bin/env python3 +# +# Based on check-style.py by +# Carlos Garnacho + +import argparse +import os +import re +import subprocess +import sys +import tempfile + +# Path relative to this script +uncrustify_cfg = ".gitlab-ci/uncrustify.cfg" + + +def run_diff(sha): + proc = subprocess.run( + ["git", "diff", "-U0", "--function-context", sha, "HEAD"], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + encoding="utf-8", + ) + return proc.stdout.strip().splitlines() + + +def find_chunks(diff): + file_entry_re = re.compile(r"^\+\+\+ b/(.*)$") + diff_chunk_re = re.compile(r"^@@ -\d+,\d+ \+(\d+),(\d+)") + file = None + chunks = [] + + for line in diff: + match = file_entry_re.match(line) + if match: + file = match.group(1) + + match = diff_chunk_re.match(line) + if match: + start = int(match.group(1)) + len = int(match.group(2)) + end = start + len + + if len > 0 and ( + file.endswith(".c") or file.endswith(".h") or file.endswith(".vala") + ): + chunks.append({"file": file, "start": start, "end": end}) + + return chunks + + +def reformat_chunks(chunks, rewrite, dry_run): + # Creates temp file with INDENT-ON/OFF comments + def create_temp_file(file, start, end): + with open(file) as f: + tmp = tempfile.NamedTemporaryFile() + if start > 1: + tmp.write(b"/** *INDENT-OFF* **/\n") + for i, line in enumerate(f, start=1): + if i == start - 1: + tmp.write(b"/** *INDENT-ON* **/\n") + + tmp.write(bytes(line, "utf-8")) + if i == end - 1: + tmp.write(b"/** *INDENT-OFF* **/\n") + tmp.seek(0) + return tmp + + # Removes uncrustify INDENT-ON/OFF helper comments + def remove_indent_comments(output): + tmp = tempfile.NamedTemporaryFile() + for line in output: + if line != b"/** *INDENT-OFF* **/\n" and line != b"/** *INDENT-ON* **/\n": + tmp.write(line) + + tmp.seek(0) + return tmp + + changed = None + for chunk in chunks: + # Add INDENT-ON/OFF comments + tmp = create_temp_file(chunk["file"], chunk["start"], chunk["end"]) + + # uncrustify chunk + proc = subprocess.run( + ["uncrustify", "-c", uncrustify_cfg, "-f", tmp.name], + stdout=subprocess.PIPE, + ) + reindented = proc.stdout.splitlines(keepends=True) + if proc.returncode != 0: + continue + + tmp.close() + + # Remove INDENT-ON/OFF comments + formatted = remove_indent_comments(reindented) + + if dry_run is True: + # Show changes + proc = subprocess.run( + ["diff", "-up", "--color=always", chunk["file"], formatted.name], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + encoding="utf-8", + ) + diff = proc.stdout + if diff != "": + output = re.sub("\t", "↦\t", diff) + print(output) + changed = True + else: + # Apply changes + diff = subprocess.run( + ["diff", "-up", chunk["file"], formatted.name], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + subprocess.run(["patch", chunk["file"]], input=diff.stdout) + + formatted.close() + + return changed + + +def main(argv): + parser = argparse.ArgumentParser( + description="Check code style. Needs uncrustify installed." + ) + parser.add_argument( + "--sha", metavar="SHA", type=str, help="SHA for the commit to compare HEAD with" + ) + parser.add_argument( + "--dry-run", + "-d", + type=bool, + action=argparse.BooleanOptionalAction, + help="Only print changes to stdout, do not change code", + ) + parser.add_argument( + "--rewrite", + "-r", + type=bool, + action=argparse.BooleanOptionalAction, + help="Whether to amend the result to the last commit (e.g. 'git rebase --exec \"%(prog)s -r\"')", + ) + + if not os.path.exists(".git"): + print("Not in toplevel of a git repository", fille=sys.stderr) + return 1 + + args = parser.parse_args(argv) + sha = args.sha or "HEAD^" + + diff = run_diff(sha) + chunks = find_chunks(diff) + changed = reformat_chunks(chunks, args.rewrite, args.dry_run) + + if args.dry_run is not True and args.rewrite is True: + proc = subprocess.run(["git", "add", "-p"]) + if proc.returncode == 0: + # Commit the added changes as a squash commit + subprocess.run( + ["git", "commit", "--squash", "HEAD", "-C", "HEAD"], + stdout=subprocess.DEVNULL, + ) + # Delete the unapplied changes + subprocess.run(["git", "reset", "--hard"], stdout=subprocess.DEVNULL) + return 0 + elif args.dry_run is True and changed is True: + print( + f""" +Issue the following commands in your local tree to apply the suggested changes: + + $ git rebase {sha} --exec "./.gitlab-ci/check-style.py -r" + $ git rebase --autosquash {sha} + +Don't trust uncrustify unconditionally. +""" + ) + return 1 + return 0 + + +if __name__ == "__main__": + sys.exit(main(sys.argv[1:])) diff --git a/.gitlab-ci/commit-rules.yml b/.gitlab-ci/commit-rules.yml new file mode 100644 index 0000000..0125df2 --- /dev/null +++ b/.gitlab-ci/commit-rules.yml @@ -0,0 +1,20 @@ +patterns: + deny: + - regex: '^$CI_MERGE_REQUEST_PROJECT_URL/(-/)?merge_requests/$CI_MERGE_REQUEST_IID$' + message: Commit message must not contain a link to its own merge request + - regex: '^[^:]+: [a-z]' + message: "Commit description in commit message subject should be properly Capitalized. E.g. 'monitor: Avoid crash on unplug'" + where: subject + - regex: '^\S*\.(c|h|ui):' + message: Commit message subject prefix should not include .c, .h etc. + where: subject + - regex: '([^.]\.|[:,;])\s*$' + message: Commit message subject should not end with punctuation + where: subject + - regex: '^[A-Z]\S*:' + message: "Identifier in commit message subject should start lowercase 'monitor: Avoid crash on unplug'" + where: subject + require: + - regex: '^[a-z0-9,\.\+\-/#=_]+:' + message: "Commit message should start with a lowercase identifier 'monitor: Avoid crash on unplug'" + where: subject diff --git a/.gitlab-ci/uncrustify.cfg b/.gitlab-ci/uncrustify.cfg new file mode 100644 index 0000000..7db08d9 --- /dev/null +++ b/.gitlab-ci/uncrustify.cfg @@ -0,0 +1,145 @@ +# +# Uncrustify config for phosh and related projects +# + +# A span is the number of lines considered +# A threshold is the maximum number of columns an item is moved + +# Indent by two spaces +indent_columns = 2 +# No tabs +indent_with_tabs = 0 +# Line length +code_width = 100 +# Whether to remove superfluous semicolons +mod_remove_extra_semicolon = true +# indent goto by 1 (or -1 brace level) +indent_label = -1 +# don't indent case after switch +indent_switch_case = 0 + +# +# Keywords and operators +# +# Add between 'do' and '{'. +sp_do_brace_open = add +# Add space between '}' and 'while'. +sp_brace_close_while = add +# Add 'while' and '('. +sp_while_paren_open = add +# Add or remove space around boolean operators '&&' and '||'. +sp_bool = add +# Ternary operator +sp_cond_ternary_short = remove +# Remove newline between 'struct and '{'. +nl_struct_brace = remove +# Remove newline between 'if' and '{'. +nl_if_brace = remove +# Remove newline between '}' and 'else'. +nl_brace_else = remove +# Remove newline between 'else' and '{'. +nl_else_brace = remove +# Remove newline between 'else' and 'if'. +nl_else_if = remove +# Add or remove newline between 'for' and '{'. +nl_for_brace = remove +# Add or remove newline between 'while' and '{'. +nl_while_brace = remove +# Treat iterators as for loops: +set FOR wl_list_for_each wl_list_for_each_reverse wl_list_for_each_safe +# Remove braces on single line if/for/while statements +mod_full_brace_if = remove +mod_full_brace_for = remove +mod_full_brace_while = remove +# If any must be braced, they are all braced. If all can be unbraced, then the braces are removed. +mod_full_brace_if_chain = 1 +# Remove braces around case (when there are no variables declarations) +mod_case_brace = remove +# Don't remove branches if the statement has more than one line +mod_full_brace_nl = 2 + +# +# Function declarations, definitions and calls +# +# Add space between function name and '(' on function declaration. +sp_func_proto_paren = add +# Add or remove space between function name and '()' on function declaration +# without parameters. +sp_func_proto_paren_empty = add +# Add space between function name and '(' on function definition. +sp_func_def_paren = add +# Add or remove space between function name and '(' on function calls. +sp_func_call_paren = add +# Specialcase i18n macros +set func_call_user _ N_ C_ +sp_func_call_user_paren = remove + +# Whether to force indentation of function definitions to start in column 1. +indent_func_def_force_col1 = true +# Add newline between return type and function name in a function definition. +nl_func_type_name = add +# Add newline between function signature and '{'. +nl_fdef_brace = add +# Whether to align variable definitions in prototypes and functions. +align_func_params = true +# The span for aligning function prototypes. +align_func_proto_span = 8 +# Add space between 'decltype(...)' and word. +sp_after_decltype = add +# Add or remove space after a pointer star '*', if followed by a function +# prototype or function definition. +sp_after_ptr_star_func = remove +# Add or remove newline between a function call's ')' and '{', as in +# 'list_for_each(item, &list) { }'. +nl_fcall_brace = add + +# +# Typedefs +# +# Add space between '}' and the name of a typedef on the same line. +sp_brace_typedef = add + +# +# Comments +# +# Add space after the opening of a C++ comment, i.e. '// A' vs. '//A'. +sp_cmt_cpp_start = add + +# +# Preprocessor +# +# Add or remove space between #else or #endif and a trailing comment. +sp_endif_cmt = add + +# Newlines at the start and end of the file. +nl_start_of_file = remove +nl_end_of_file = add +nl_end_of_file_min = 1 + +# +# Variable definitions +# +# How to align the '*' in variable definitions. +# +# 0: Part of the type 'void * foo;' (default) +# 1: Part of the variable 'void *foo;' +# 2: Dangling 'void *foo;' +# Dangling: the '*' will not be taken into account when aligning. +align_var_def_star_style = 2 +# Same for typedefs +align_typedef_star_style = 2 +# The gap for aligning struct/union member definitions. +align_var_struct_gap = 1 +# The span for aligning struct/union member definitions. +align_var_struct_span = 4 +# The threshold for aligning struct/union member definitions. +align_var_struct_thresh = 8 + +# Remove space between pointer stars '*'. +sp_between_ptr_star = remove +# Add space before '(' of control statements ('if', 'for', 'switch', 'while', etc.) +sp_before_sparen = add + +# Add spaces around assignments and arithmethic operators +sp_assign = add +sp_arith = add