Compare commits
18
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3d047b838 | ||
|
|
e3d6cd21be | ||
|
|
95044caecd | ||
|
|
0a5abbb1ee | ||
|
|
78de7eae91 | ||
|
|
684ad1cd3a | ||
|
|
58c3cd0559 | ||
|
|
5910e6747d | ||
|
|
87b2580e9a | ||
|
|
6d986b8967 | ||
|
|
9e1f6007ca | ||
|
|
40d825e6dd | ||
|
|
2d681d6c2d | ||
|
|
c661f4393e | ||
|
|
70b0641129 | ||
|
|
78fa327f99 | ||
|
|
fe1207ef48 | ||
|
|
fccf36294f |
No files matched your search
@@ -0,0 +1,28 @@
|
||||
BasedOnStyle: LLVM
|
||||
IndentWidth: 4
|
||||
TabWidth: 4
|
||||
UseTab: Never
|
||||
ColumnLimit: 120
|
||||
Language: Cpp
|
||||
Standard: c++20
|
||||
PointerAlignment: Left
|
||||
ReferenceAlignment: Left
|
||||
SortIncludes: true
|
||||
IncludeBlocks: Regroup
|
||||
BreakBeforeBraces: Custom
|
||||
BraceWrapping:
|
||||
AfterClass: true
|
||||
AfterControlStatement: true
|
||||
AfterFunction: true
|
||||
AfterNamespace: true
|
||||
AfterStruct: true
|
||||
BeforeCatch: true
|
||||
BeforeElse: true
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: Empty
|
||||
NamespaceIndentation: None
|
||||
IndentCaseLabels: true
|
||||
AlignTrailingComments: true
|
||||
SeparateDefinitionBlocks: Always
|
||||
SpaceBeforeParens: ControlStatements
|
||||
Cpp11BracedListStyle: true
|
||||
@@ -0,0 +1,24 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
project(sandbox LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
add_executable(sandbox main.cpp)
|
||||
|
||||
enable_testing()
|
||||
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
googletest
|
||||
URL https://github.com/google/googletest/archive/refs/tags/v1.14.0.zip
|
||||
)
|
||||
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
|
||||
FetchContent_MakeAvailable(googletest)
|
||||
|
||||
add_executable(test_main tests/test_main.cpp)
|
||||
target_link_libraries(test_main GTest::gtest_main)
|
||||
|
||||
include(GoogleTest)
|
||||
gtest_discover_tests(test_main)
|
||||
@@ -0,0 +1,17 @@
|
||||
FROM debian:bookworm-slim
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
python3 \
|
||||
clang-format \
|
||||
cmake \
|
||||
ninja-build \
|
||||
git \
|
||||
g++ \
|
||||
make \
|
||||
ca-certificates && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /workspace
|
||||
Vendored
+43
-3
@@ -1,22 +1,62 @@
|
||||
pipeline {
|
||||
agent any
|
||||
agent {
|
||||
docker {
|
||||
image 'lifemaps/ci-cpp:latest'
|
||||
reuseNode true
|
||||
args '-u root:root'
|
||||
}
|
||||
}
|
||||
|
||||
options {
|
||||
timestamps()
|
||||
disableConcurrentBuilds()
|
||||
}
|
||||
|
||||
environment {
|
||||
BUILD_DIR = "build"
|
||||
CHANGE_TARGET = "${env.CHANGE_TARGET ?: 'origin/main'}"
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('Checkout') {
|
||||
steps {
|
||||
checkout scm
|
||||
sh 'git fetch --all --prune'
|
||||
sh 'git submodule update --init --recursive || true'
|
||||
}
|
||||
}
|
||||
|
||||
stage('Policy Checks') {
|
||||
steps {
|
||||
sh 'python3 ci/check_pr_policies.py'
|
||||
}
|
||||
}
|
||||
|
||||
stage('Configure') {
|
||||
steps {
|
||||
sh 'cmake -S . -B ${BUILD_DIR} -G Ninja -DCMAKE_BUILD_TYPE=Debug'
|
||||
}
|
||||
}
|
||||
|
||||
stage('Build') {
|
||||
steps {
|
||||
sh 'echo Build works'
|
||||
sh 'cmake --build ${BUILD_DIR} --parallel'
|
||||
}
|
||||
}
|
||||
|
||||
stage('Test') {
|
||||
steps {
|
||||
sh 'echo Tests work'
|
||||
sh 'ctest --test-dir ${BUILD_DIR} --output-on-failure'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post {
|
||||
always {
|
||||
script {
|
||||
if (getContext(hudson.FilePath)) {
|
||||
cleanWs deleteDirs: true, disableDeferredWipeout: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,231 @@
|
||||
# sandbox
|
||||
|
||||
jenkins testing repobranch protection test
|
||||
Test- und Beispiel-Repository für einen lokalen Dev-Platform-Stack mit:
|
||||
|
||||
- **Gitea 1.22.x**
|
||||
- **Jenkins Multibranch Pipeline**
|
||||
- **Webhook-Trigger von Gitea nach Jenkins**
|
||||
- **C++/CMake/Ninja Build**
|
||||
- **PR Policy Checks**
|
||||
- **Docker-basiertem CI-Agent**
|
||||
|
||||
---
|
||||
|
||||
## Aktueller Funktionsumfang
|
||||
|
||||
Dieses Repository und das zugehörige Jenkins/Gitea-Setup können aktuell:
|
||||
|
||||
### SCM / Repository
|
||||
- Repository in **Gitea** hosten
|
||||
- Änderungen per **Git Push** empfangen
|
||||
- **Pull Requests** in Gitea verwalten
|
||||
- Webhooks von Gitea an Jenkins senden
|
||||
|
||||
### Jenkins / CI
|
||||
- Jenkins als **Multibranch Pipeline** verwenden
|
||||
- Branches und Pull Requests automatisch erkennen
|
||||
- Builds durch **Push Events** automatisch starten
|
||||
- Build-Status an Gitea zurückmelden
|
||||
|
||||
### Build-Umgebung
|
||||
- Pipeline-Schritte in einem **Docker-Container** ausführen
|
||||
- dafür ein vorbereitetes CI-Image verwenden
|
||||
- folgende Tools im CI bereitstellen:
|
||||
- `python3`
|
||||
- `clang-format`
|
||||
- `cmake`
|
||||
- `ninja`
|
||||
- `git`
|
||||
- `g++`
|
||||
- `make`
|
||||
|
||||
### C++ Projektbasis
|
||||
- minimales Beispielprogramm über `main.cpp`
|
||||
- Build mit **CMake**
|
||||
- Testausführung über **CTest**
|
||||
- Testdateien unter `tests/`
|
||||
|
||||
---
|
||||
|
||||
## Repository-Struktur
|
||||
|
||||
```text
|
||||
.
|
||||
├── .clang-format
|
||||
├── CMakeLists.txt
|
||||
├── Dockerfile.ci
|
||||
├── Jenkinsfile
|
||||
├── README.md
|
||||
├── ci/
|
||||
│ └── check_pr_policies.py
|
||||
├── main.cpp
|
||||
└── tests/
|
||||
└── test_main.cpp
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CI / Jenkins Pipeline
|
||||
|
||||
Die Jenkins-Pipeline umfasst aktuell diese Schritte:
|
||||
|
||||
1. **Checkout**
|
||||
- Repository aus Gitea auschecken
|
||||
- zusätzliche Fetch-/Submodule-Schritte ausführen
|
||||
|
||||
2. **Policy Checks**
|
||||
- `ci/check_pr_policies.py` ausführen
|
||||
- Formatierung und Testbezug prüfen
|
||||
|
||||
3. **Configure**
|
||||
- CMake-Buildverzeichnis erzeugen
|
||||
|
||||
4. **Build**
|
||||
- Projekt mit CMake/Ninja bauen
|
||||
|
||||
5. **Test**
|
||||
- Tests über `ctest` ausführen
|
||||
|
||||
6. **Cleanup**
|
||||
- Workspace am Ende bereinigen
|
||||
|
||||
---
|
||||
|
||||
## PR Policy Checks
|
||||
|
||||
Für geänderte C/C++-Dateien werden in der CI folgende Regeln geprüft:
|
||||
|
||||
- `.clang-format` muss im Repository vorhanden sein
|
||||
- geänderte C/C++-Dateien müssen mit `clang-format` konform sein
|
||||
- zu geänderten C/C++-Dateien muss ein passender Test unter `tests/` oder `test/` vorhanden sein
|
||||
|
||||
### Vergleichsbasis der Policy
|
||||
|
||||
Die Policy unterscheidet zwischen Pull-Request-Builds und normalen Branch-Builds:
|
||||
|
||||
- **bei Pull Requests** wird gegen den Zielbranch verglichen, z. B. `origin/main`
|
||||
- **bei normalen Branch-Builds** wird auf eine lokale Vergleichsbasis zurückgefallen, z. B. `HEAD~1`, falls kein Zielbranch-Ref verfügbar ist
|
||||
|
||||
Damit funktioniert die Policy sowohl in PR-Builds als auch bei regulären Branch-Indexing- oder Branch-Build-Läufen.
|
||||
|
||||
---
|
||||
|
||||
## Docker-basierter CI-Agent
|
||||
|
||||
Die Pipeline verwendet ein vorbereitetes Docker-Image statt bei jedem Lauf ein neues Build-Image zu erzeugen.
|
||||
|
||||
### Beispiel
|
||||
Das Image wird lokal oder auf dem Build-Host gebaut:
|
||||
|
||||
```bash
|
||||
docker build -t lifemaps/ci-cpp:latest -f Dockerfile.ci .
|
||||
```
|
||||
|
||||
Danach nutzt Jenkins dieses Image als Build-Agent.
|
||||
|
||||
### Vorteile
|
||||
- schnellere PR-Builds
|
||||
- reproduzierbare Toolchain
|
||||
- keine langen `apt install`-Phasen pro Build
|
||||
- saubere Trennung zwischen Jenkins-Controller und Build-Umgebung
|
||||
|
||||
---
|
||||
|
||||
## Branch- und PR-Workflow
|
||||
|
||||
Der aktuelle Workflow unterstützt:
|
||||
|
||||
- Arbeiten auf Feature-Branches
|
||||
- Öffnen von Pull Requests gegen `main`
|
||||
- automatisches Bauen von PRs
|
||||
- Rückmeldung des Build-Status an Gitea
|
||||
|
||||
Empfohlen ist:
|
||||
|
||||
- keine direkten Änderungen auf `main`
|
||||
- Änderungen über Feature-Branch + Pull Request
|
||||
- Merge erst nach erfolgreichem CI-Lauf
|
||||
|
||||
---
|
||||
|
||||
## Lokales Arbeiten
|
||||
|
||||
### Formatierung prüfen
|
||||
```bash
|
||||
clang-format --dry-run --Werror main.cpp tests/test_main.cpp
|
||||
```
|
||||
|
||||
### Formatierung anwenden
|
||||
```bash
|
||||
clang-format -i main.cpp tests/test_main.cpp
|
||||
```
|
||||
|
||||
### CI-Policy lokal testen
|
||||
```bash
|
||||
python3 ci/check_pr_policies.py
|
||||
```
|
||||
|
||||
### Build lokal ausführen
|
||||
```bash
|
||||
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug
|
||||
cmake --build build --parallel
|
||||
ctest --test-dir build --output-on-failure
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Voraussetzungen auf Jenkins-Seite
|
||||
|
||||
Für das Setup werden u. a. folgende Jenkins-Plugins benötigt:
|
||||
|
||||
- `docker-workflow`
|
||||
- `pipeline-model-definition`
|
||||
- `workflow-scm-step`
|
||||
- `workflow-cps`
|
||||
- `ws-cleanup`
|
||||
- `gitea`
|
||||
- `git`
|
||||
- `git-client`
|
||||
- `scm-api`
|
||||
- `workflow-multibranch`
|
||||
|
||||
Zusätzlich muss der Jenkins-Container Zugriff auf Docker haben, z. B. über:
|
||||
|
||||
- Docker CLI im Jenkins-Container
|
||||
- Mount von `/var/run/docker.sock`
|
||||
|
||||
---
|
||||
|
||||
## Webhook / Automatischer Trigger
|
||||
|
||||
Gitea ist so konfiguriert, dass **Push Events** an Jenkins gemeldet werden.
|
||||
|
||||
Beispiel-Endpunkt:
|
||||
|
||||
```text
|
||||
https://jenkins.life-maps.com/gitea-webhook/post
|
||||
```
|
||||
|
||||
Damit können automatisch Jenkins-Builds für Branches und Pull Requests ausgelöst werden.
|
||||
|
||||
---
|
||||
|
||||
## Beispielziel des Repositories
|
||||
|
||||
Dieses Repository dient aktuell als:
|
||||
|
||||
- Referenz für Jenkins/Gitea-Integration
|
||||
- Testumgebung für PR-Policies
|
||||
- Minimalbeispiel für C++/CMake-basierte CI
|
||||
- Grundlage für weitere CI- und Policy-Erweiterungen
|
||||
|
||||
---
|
||||
|
||||
## Status
|
||||
|
||||
Aktuell ist das Setup geeignet für:
|
||||
|
||||
- Jenkins/Gitea-Webhook-Integration
|
||||
- Pull-Request-basierte CI
|
||||
- C++/CMake/Ninja Beispielprojekte
|
||||
- Policy-basierte Qualitätsprüfungen im PR-Prozess
|
||||
@@ -1 +0,0 @@
|
||||
auto-trigger-check Di 4. Aug 00:25:36 CEST 2026
|
||||
@@ -0,0 +1,148 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(".").resolve()
|
||||
TEST_DIRS = [ROOT / "tests", ROOT / "test"]
|
||||
CPP_EXTENSIONS = {".c", ".cc", ".cpp", ".cxx", ".h", ".hh", ".hpp", ".hxx"}
|
||||
|
||||
TEST_PATTERNS = [
|
||||
"test_{name}.cpp",
|
||||
"test_{name}.h",
|
||||
"{name}_test.cpp",
|
||||
"{name}_test.h",
|
||||
"{name}test.cpp",
|
||||
"{name}test.h",
|
||||
"{name}gtest.cpp",
|
||||
"{name}gtest.h",
|
||||
]
|
||||
|
||||
def run(cmd: str) -> str:
|
||||
return subprocess.check_output(cmd, shell=True, text=True).strip()
|
||||
|
||||
def git_ref_exists(ref: str) -> bool:
|
||||
try:
|
||||
subprocess.check_call(
|
||||
f"git rev-parse --verify --quiet '{ref}'",
|
||||
shell=True,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
return True
|
||||
except subprocess.CalledProcessError:
|
||||
return False
|
||||
|
||||
def is_pull_request_build() -> bool:
|
||||
return bool(os.environ.get("CHANGE_ID"))
|
||||
|
||||
def resolve_target_ref() -> str | None:
|
||||
raw = os.environ.get("CHANGE_TARGET", "").strip()
|
||||
|
||||
candidates = []
|
||||
if raw:
|
||||
candidates.append(raw)
|
||||
if not raw.startswith("origin/"):
|
||||
candidates.append(f"origin/{raw}")
|
||||
|
||||
candidates.extend(["origin/main", "main"])
|
||||
|
||||
seen = set()
|
||||
for ref in candidates:
|
||||
if ref and ref not in seen:
|
||||
seen.add(ref)
|
||||
if git_ref_exists(ref):
|
||||
return ref
|
||||
|
||||
return None
|
||||
|
||||
def changed_files():
|
||||
target = resolve_target_ref()
|
||||
|
||||
if target:
|
||||
print(f"Vergleiche gegen Ziel-Ref: {target}")
|
||||
out = run(f"git diff --name-only {target}...HEAD")
|
||||
return [line.strip() for line in out.splitlines() if line.strip()]
|
||||
|
||||
if is_pull_request_build():
|
||||
print("ERROR: Kein gültiger Ziel-Branch-Ref für PR-Build gefunden.")
|
||||
sys.exit(1)
|
||||
|
||||
if git_ref_exists("HEAD~1"):
|
||||
print("Kein Ziel-Branch-Ref gefunden, verwende HEAD~1 für Branch-Build.")
|
||||
out = run("git diff --name-only HEAD~1..HEAD")
|
||||
return [line.strip() for line in out.splitlines() if line.strip()]
|
||||
|
||||
print("Keine Vergleichsbasis gefunden, behandle alle versionierten Dateien als geändert.")
|
||||
out = run("git ls-files")
|
||||
return [line.strip() for line in out.splitlines() if line.strip()]
|
||||
|
||||
def is_cpp_file(path: Path) -> bool:
|
||||
return path.suffix.lower() in CPP_EXTENSIONS
|
||||
|
||||
def require_clang_format():
|
||||
if not (ROOT / ".clang-format").exists():
|
||||
print("ERROR: .clang-format fehlt im Repository-Root.")
|
||||
sys.exit(1)
|
||||
|
||||
def check_format(path: Path):
|
||||
subprocess.check_call(f"clang-format --dry-run --Werror '{path}'", shell=True)
|
||||
|
||||
def candidate_names(path: Path):
|
||||
stem = path.stem.lower()
|
||||
names = {stem}
|
||||
for suffix in ["_p", "_impl", "_private", "_priv", "_ui"]:
|
||||
if stem.endswith(suffix):
|
||||
names.add(stem[:-len(suffix)])
|
||||
return sorted(n for n in names if n)
|
||||
|
||||
def iter_test_files():
|
||||
for test_dir in TEST_DIRS:
|
||||
if test_dir.exists():
|
||||
yield from (p for p in test_dir.rglob("*") if p.is_file())
|
||||
|
||||
def has_related_test(path: Path) -> bool:
|
||||
test_files = list(iter_test_files())
|
||||
if not test_files:
|
||||
return False
|
||||
|
||||
test_names = [p.name.lower() for p in test_files]
|
||||
|
||||
for name in candidate_names(path):
|
||||
for pattern in TEST_PATTERNS:
|
||||
if pattern.format(name=name).lower() in test_names:
|
||||
return True
|
||||
for test_name in test_names:
|
||||
if name in test_name and ("test" in test_name or "gtest" in test_name):
|
||||
return True
|
||||
return False
|
||||
|
||||
def main():
|
||||
require_clang_format()
|
||||
|
||||
files = changed_files()
|
||||
if not files:
|
||||
print("Keine geänderten Dateien erkannt.")
|
||||
return
|
||||
|
||||
for rel in files:
|
||||
path = ROOT / rel
|
||||
if not path.exists():
|
||||
continue
|
||||
if is_cpp_file(path):
|
||||
print(f"Prüfe: {rel}")
|
||||
try:
|
||||
check_format(path)
|
||||
except subprocess.CalledProcessError:
|
||||
print(f"ERROR: Formatfehler in {rel}")
|
||||
sys.exit(1)
|
||||
|
||||
if not has_related_test(path):
|
||||
print(f"ERROR: Kein passender Test unter tests/ oder test/ für {rel} gefunden.")
|
||||
sys.exit(1)
|
||||
|
||||
print("Policy-Checks erfolgreich.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1 +0,0 @@
|
||||
hook test Di 4. Aug 00:08:29 CEST 2026
|
||||
@@ -0,0 +1,7 @@
|
||||
#include <iostream>
|
||||
|
||||
int main()
|
||||
{
|
||||
std::cout << "Hello from sandbox\n";
|
||||
return 0;
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
push-hook-test Di 4. Aug 00:11:58 CEST 2026
|
||||
@@ -0,0 +1,6 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
TEST(MainTest, Smoke)
|
||||
{
|
||||
EXPECT_TRUE(true);
|
||||
}
|
||||
Reference in new issue
Block a user