mirror of
https://github.com/sebastiandine/Card-Collection-Manager-3.git
synced 2026-09-03 17:01:03 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f575f4cec | ||
|
|
7807192ecb | ||
|
|
a691291413 | ||
|
|
108863b795 | ||
|
|
c434ee51a7 |
@@ -37,6 +37,8 @@ GitHub Actions workflows for CI, release automation, and policy checks.
|
|||||||
|
|
||||||
- Prefer minimal, surgical edits; avoid large workflow rewrites unless requested.
|
- Prefer minimal, surgical edits; avoid large workflow rewrites unless requested.
|
||||||
- Reusable workflows should declare explicit `workflow_call` inputs for required context (e.g., version, merge SHA).
|
- Reusable workflows should declare explicit `workflow_call` inputs for required context (e.g., version, merge SHA).
|
||||||
|
- Sonar coverage steps that use `gcovr` must exclude third-party build trees at discovery time with `--exclude-directories` (for example `build/_deps`) so gcov does not process dependency `.gcda` files.
|
||||||
|
- For Linux Sonar coverage jobs, keep compiler and gcov toolchain aligned; because `cmake/Toolchain.cmake` prefers Clang by default, set `-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++` explicitly in the coverage configure step when using gcovr default `gcov`.
|
||||||
- Keep `permissions` least-privilege:
|
- Keep `permissions` least-privilege:
|
||||||
- reusable build workflows: `contents: read`
|
- reusable build workflows: `contents: read`
|
||||||
- release/tag orchestrator: `contents: write`
|
- release/tag orchestrator: `contents: write`
|
||||||
|
|||||||
@@ -27,17 +27,35 @@ jobs:
|
|||||||
sudo apt-get install -y
|
sudo apt-get install -y
|
||||||
cmake
|
cmake
|
||||||
ninja-build
|
ninja-build
|
||||||
|
gcovr
|
||||||
pkg-config
|
pkg-config
|
||||||
libgtk-3-dev
|
libgtk-3-dev
|
||||||
libwxgtk3.2-dev
|
libwxgtk3.2-dev
|
||||||
|
|
||||||
- name: Generate compile commands
|
- name: Configure with coverage instrumentation
|
||||||
run: >
|
run: >
|
||||||
cmake -S . -B build -G Ninja
|
cmake -S . -B build -G Ninja
|
||||||
-DCCM_BUILD_TESTS=OFF
|
-DCMAKE_C_COMPILER=gcc
|
||||||
|
-DCMAKE_CXX_COMPILER=g++
|
||||||
|
-DCCM_BUILD_TESTS=ON
|
||||||
-DCCM_USE_SYSTEM_WX=ON
|
-DCCM_USE_SYSTEM_WX=ON
|
||||||
|
-DCMAKE_BUILD_TYPE=Debug
|
||||||
|
-DCMAKE_C_FLAGS=--coverage
|
||||||
|
-DCMAKE_CXX_FLAGS=--coverage
|
||||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON
|
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON
|
||||||
|
|
||||||
|
- name: Build and run tests for coverage
|
||||||
|
run: >
|
||||||
|
cmake --build build --parallel &&
|
||||||
|
ctest --test-dir build --output-on-failure
|
||||||
|
|
||||||
|
- name: Generate Sonar coverage report
|
||||||
|
run: >
|
||||||
|
gcovr -r .
|
||||||
|
--sonarqube build/sonarqube-coverage.xml
|
||||||
|
--exclude "build/_deps/"
|
||||||
|
--exclude-directories "build/_deps"
|
||||||
|
|
||||||
- name: SonarQube Cloud scan
|
- name: SonarQube Cloud scan
|
||||||
uses: SonarSource/sonarqube-scan-action@v5
|
uses: SonarSource/sonarqube-scan-action@v5
|
||||||
env:
|
env:
|
||||||
@@ -50,6 +68,7 @@ jobs:
|
|||||||
-Dsonar.projectKey=${{ env.SONAR_PROJECT_KEY }}
|
-Dsonar.projectKey=${{ env.SONAR_PROJECT_KEY }}
|
||||||
-Dsonar.sources=core,ui_wx,app
|
-Dsonar.sources=core,ui_wx,app
|
||||||
-Dsonar.cfamily.compile-commands=build/compile_commands.json
|
-Dsonar.cfamily.compile-commands=build/compile_commands.json
|
||||||
|
-Dsonar.coverageReportPaths=build/sonarqube-coverage.xml
|
||||||
|
|
||||||
linux:
|
linux:
|
||||||
name: Linux build + tests
|
name: Linux build + tests
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
name: Master CI
|
name: Master CI
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
types:
|
|
||||||
- closed
|
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
@@ -13,7 +11,6 @@ permissions:
|
|||||||
jobs:
|
jobs:
|
||||||
sonarqube:
|
sonarqube:
|
||||||
name: SonarQube Cloud scan
|
name: SonarQube Cloud scan
|
||||||
if: github.event.pull_request.merged == true
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@@ -30,17 +27,35 @@ jobs:
|
|||||||
sudo apt-get install -y
|
sudo apt-get install -y
|
||||||
cmake
|
cmake
|
||||||
ninja-build
|
ninja-build
|
||||||
|
gcovr
|
||||||
pkg-config
|
pkg-config
|
||||||
libgtk-3-dev
|
libgtk-3-dev
|
||||||
libwxgtk3.2-dev
|
libwxgtk3.2-dev
|
||||||
|
|
||||||
- name: Generate compile commands
|
- name: Configure with coverage instrumentation
|
||||||
run: >
|
run: >
|
||||||
cmake -S . -B build -G Ninja
|
cmake -S . -B build -G Ninja
|
||||||
-DCCM_BUILD_TESTS=OFF
|
-DCMAKE_C_COMPILER=gcc
|
||||||
|
-DCMAKE_CXX_COMPILER=g++
|
||||||
|
-DCCM_BUILD_TESTS=ON
|
||||||
-DCCM_USE_SYSTEM_WX=ON
|
-DCCM_USE_SYSTEM_WX=ON
|
||||||
|
-DCMAKE_BUILD_TYPE=Debug
|
||||||
|
-DCMAKE_C_FLAGS=--coverage
|
||||||
|
-DCMAKE_CXX_FLAGS=--coverage
|
||||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON
|
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON
|
||||||
|
|
||||||
|
- name: Build and run tests for coverage
|
||||||
|
run: >
|
||||||
|
cmake --build build --parallel &&
|
||||||
|
ctest --test-dir build --output-on-failure
|
||||||
|
|
||||||
|
- name: Generate Sonar coverage report
|
||||||
|
run: >
|
||||||
|
gcovr -r .
|
||||||
|
--sonarqube build/sonarqube-coverage.xml
|
||||||
|
--exclude "build/_deps/"
|
||||||
|
--exclude-directories "build/_deps"
|
||||||
|
|
||||||
- name: SonarQube Cloud scan
|
- name: SonarQube Cloud scan
|
||||||
uses: SonarSource/sonarqube-scan-action@v5
|
uses: SonarSource/sonarqube-scan-action@v5
|
||||||
env:
|
env:
|
||||||
@@ -53,23 +68,45 @@ jobs:
|
|||||||
-Dsonar.projectKey=${{ env.SONAR_PROJECT_KEY }}
|
-Dsonar.projectKey=${{ env.SONAR_PROJECT_KEY }}
|
||||||
-Dsonar.sources=core,ui_wx,app
|
-Dsonar.sources=core,ui_wx,app
|
||||||
-Dsonar.cfamily.compile-commands=build/compile_commands.json
|
-Dsonar.cfamily.compile-commands=build/compile_commands.json
|
||||||
|
-Dsonar.coverageReportPaths=build/sonarqube-coverage.xml
|
||||||
|
|
||||||
compute-version:
|
compute-version:
|
||||||
name: Determine semantic version
|
name: Determine semantic version
|
||||||
if: github.event.pull_request.merged == true
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
outputs:
|
outputs:
|
||||||
version: ${{ steps.version.outputs.version }}
|
version: ${{ steps.version.outputs.version }}
|
||||||
release_tag: ${{ steps.version.outputs.release_tag }}
|
release_tag: ${{ steps.version.outputs.release_tag }}
|
||||||
|
pr_title: ${{ steps.pr.outputs.pr_title }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout tags
|
- name: Checkout tags
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Resolve merged PR title for pushed commit
|
||||||
|
id: pr
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const { owner, repo } = context.repo;
|
||||||
|
const commitSha = context.sha;
|
||||||
|
const pulls = await github.rest.repos.listPullRequestsAssociatedWithCommit({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
commit_sha: commitSha,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!pulls.data.length) {
|
||||||
|
core.setFailed(`No PR found for commit ${commitSha}. Release flow expects merges into master through PRs.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mergedPr = pulls.data.find((pr) => pr.merged_at) ?? pulls.data[0];
|
||||||
|
core.setOutput("pr_title", mergedPr.title);
|
||||||
|
|
||||||
- name: Resolve semantic version
|
- name: Resolve semantic version
|
||||||
id: version
|
id: version
|
||||||
run: bash scripts/compute_master_semver.sh "${{ github.event.pull_request.title }}"
|
run: bash scripts/compute_master_semver.sh "${{ steps.pr.outputs.pr_title }}"
|
||||||
|
|
||||||
build-windows:
|
build-windows:
|
||||||
name: Windows build + tests
|
name: Windows build + tests
|
||||||
@@ -79,7 +116,7 @@ jobs:
|
|||||||
uses: ./.github/workflows/master-windows.yml
|
uses: ./.github/workflows/master-windows.yml
|
||||||
with:
|
with:
|
||||||
version: ${{ needs.compute-version.outputs.version }}
|
version: ${{ needs.compute-version.outputs.version }}
|
||||||
merge_commit_sha: ${{ github.event.pull_request.merge_commit_sha }}
|
merge_commit_sha: ${{ github.sha }}
|
||||||
|
|
||||||
release-master:
|
release-master:
|
||||||
name: Tag and release on master
|
name: Tag and release on master
|
||||||
@@ -111,6 +148,6 @@ jobs:
|
|||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
tag_name: ${{ needs.compute-version.outputs.release_tag }}
|
tag_name: ${{ needs.compute-version.outputs.release_tag }}
|
||||||
target_commitish: ${{ github.event.pull_request.merge_commit_sha }}
|
target_commitish: ${{ github.sha }}
|
||||||
generate_release_notes: true
|
generate_release_notes: true
|
||||||
files: release-assets/*
|
files: release-assets/*
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ Run from the **workspace root**.
|
|||||||
- After modifying a domain type's fields or JSON layout you **must** update the matching round-trip test in `tests/domain_json_tests.cpp` and re-run tests.
|
- After modifying a domain type's fields or JSON layout you **must** update the matching round-trip test in `tests/domain_json_tests.cpp` and re-run tests.
|
||||||
- After adding a new `.cpp` to `core/` or `ui_wx/` you **must** add it to that package's `CMakeLists.txt`. There is no glob.
|
- After adding a new `.cpp` to `core/` or `ui_wx/` you **must** add it to that package's `CMakeLists.txt`. There is no glob.
|
||||||
- After adding a new dependency you **must** verify its license is compatible with this repository's MIT license before merging.
|
- After adding a new dependency you **must** verify its license is compatible with this repository's MIT license before merging.
|
||||||
|
- After changing SonarQube coverage generation, keep dependency build outputs excluded at gcov discovery time (for example `gcovr --exclude-directories "build/_deps"`); output-only excludes are not enough for third-party `.gcda` files.
|
||||||
- After adding a new game module you **must**: (1) extend `Game` enum + string mappings in `core/include/ccm/domain/Enums.hpp`, (2) register the module in `app/main.cpp`, (3) add a directory mapping in `app/main.cpp::dirNameForGame`, (4) implement an `IGameView` derived class (or `<Name>GameView`) and add it to `AppContext::gameViews` in the composition root.
|
- After adding a new game module you **must**: (1) extend `Game` enum + string mappings in `core/include/ccm/domain/Enums.hpp`, (2) register the module in `app/main.cpp`, (3) add a directory mapping in `app/main.cpp::dirNameForGame`, (4) implement an `IGameView` derived class (or `<Name>GameView`) and add it to `AppContext::gameViews` in the composition root.
|
||||||
- After changing the per-game seams (`IGameModule`, `IGameView`, the `BaseCard*Panel` template hooks) you **must** update `docs/adding-a-new-game.md` so the canonical "add a new game" walkthrough stays in sync with the code.
|
- After changing the per-game seams (`IGameModule`, `IGameView`, the `BaseCard*Panel` template hooks) you **must** update `docs/adding-a-new-game.md` so the canonical "add a new game" walkthrough stays in sync with the code.
|
||||||
- After changing `formatTextForFs` or `parseIndexFromFilename` you **must** update `tests/fs_names_tests.cpp` — these functions exist to stay byte-compatible with the original Rust `util/fs.rs`.
|
- After changing `formatTextForFs` or `parseIndexFromFilename` you **must** update `tests/fs_names_tests.cpp` — these functions exist to stay byte-compatible with the original Rust `util/fs.rs`.
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
# Card Collection Manager 3
|
# Card Collection Manager 3
|
||||||
|
|
||||||
[](https://sonarcloud.io/summary/new_code?id=sebastiandine_Card-Collection-Manager-3)
|
[](https://sonarcloud.io/summary/new_code?id=sebastiandine_Card-Collection-Manager-3)
|
||||||
|
[](https://sonarcloud.io/summary/new_code?id=sebastiandine_Card-Collection-Manager-3)
|
||||||
|
[](https://sonarcloud.io/summary/new_code?id=sebastiandine_Card-Collection-Manager-3)
|
||||||
|
|
||||||
Card Collection Manager 3 is an extensible desktop application for managing trading card game collections. It is designed as a practical way to track cards and manage per-card images for large collections, with local per-game data, set synchronization workflows, and a desktop-first UX. The app preserves the established JSON layout from earlier CCM versions so existing collections stay compatible.
|
Card Collection Manager 3 is an extensible desktop application for managing trading card game collections. It is designed as a practical way to track cards and manage per-card images for large collections, with local per-game data, set synchronization workflows, and a desktop-first UX. The app preserves the established JSON layout from earlier CCM versions so existing collections stay compatible.
|
||||||
|
|
||||||
@@ -68,3 +70,4 @@ This project continues earlier versions of Card Collection Manager:
|
|||||||
This project is licensed under the [MIT License](LICENSE).
|
This project is licensed under the [MIT License](LICENSE).
|
||||||
|
|
||||||
Third-party dependencies and assets remain under their respective licenses.
|
Third-party dependencies and assets remain under their respective licenses.
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
#include <wx/image.h>
|
#include <wx/image.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstring>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
|
||||||
@@ -68,12 +67,14 @@ namespace {
|
|||||||
|
|
||||||
// Substitute every "@FILL@" occurrence in `tmpl` with `fill`.
|
// Substitute every "@FILL@" occurrence in `tmpl` with `fill`.
|
||||||
std::string applyFill(const char* tmpl, const char* fill) {
|
std::string applyFill(const char* tmpl, const char* fill) {
|
||||||
std::string s(tmpl);
|
std::string s = tmpl != nullptr ? tmpl : "";
|
||||||
constexpr std::string_view kPlaceholder = "@FILL@";
|
constexpr std::string_view kPlaceholder = "@FILL@";
|
||||||
|
const std::string_view fillView = fill != nullptr ? std::string_view(fill)
|
||||||
|
: std::string_view{};
|
||||||
for (std::string::size_type pos = s.find(kPlaceholder);
|
for (std::string::size_type pos = s.find(kPlaceholder);
|
||||||
pos != std::string::npos;
|
pos != std::string::npos;
|
||||||
pos = s.find(kPlaceholder, pos + std::strlen(fill))) {
|
pos = s.find(kPlaceholder, pos + fillView.size())) {
|
||||||
s.replace(pos, kPlaceholder.size(), fill);
|
s.replace(pos, kPlaceholder.size(), fillView);
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user