Closed kurmann closed 7 months ago
Eine allgemeine Frage: Wie kann ich Semantic Versioning automatisieren wenn ich vorwiegend auf .NET arbeite? Ich bin verunsichert, weil all das hier scheint mir stark auf Node.js zugeschnitten zu sein.
Du hast Recht, dass viele der vorgestellten Tools wie semantic-release
ursprünglich für die Node.js-Entwicklungsumgebung entwickelt wurden. Trotzdem kannst du ähnliche Automatisierungsprozesse auch in .NET-Projekten implementieren, besonders mit Hilfe von CI/CD-Plattformen wie GitHub Actions, die Plattform- und sprachunabhängig sind.
Für .NET-Projekte kannst du die automatische Versionierung mit einigen speziellen Tools oder durch das Anpassen allgemeinerer Werkzeuge erreichen. Hier sind ein paar Optionen, wie du Semantic Versioning in deinen .NET-Projekten automatisieren kannst:
GitVersion ist ein Tool, das speziell dafür entwickelt wurde, die Versionierung in Projekten zu automatisieren, die Git verwenden. Es funktioniert gut mit .NET-Projekten und integriert sich nahtlos in CI/CD-Pipelines.
GitVersion.yml
Datei in deinem Repository, wo du das Versionsschema definieren und anpassen kannst.Du kannst GitHub Actions verwenden, um eine CI/CD-Pipeline für .NET aufzubauen, die das Semantic Versioning automatisch handhabt. Dies könnte das Erstellen von Tags, das Bumpen von Versionsnummern in deinen Projektdateien und das Erstellen von GitHub Releases umfassen.
Wenn du NuGet-Pakete erstellst, kannst du die Versionierung direkt in deinen .csproj
- oder .nuspec
-Dateien steuern. Automatisiere das Update dieser Dateien durch Skripte oder CI/CD-Pipeline-Schritte, die die Versionsnummer basierend auf deinen Versionierungsregeln aktualisieren.
Hier ist ein einfaches Beispiel, wie ein GitHub Actions Workflow für ein .NET-Projekt mit GitVersion aussehen könnte:
name: .NET CI with Semantic Versioning
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0 # Wichtig für GitVersion, um die komplette Historie zu sehen
- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
dotnet-version: 5.0.x
- name: Install GitVersion
run: dotnet tool install --global GitVersion.Tool
- name: Use GitVersion
run: |
gitversion /output buildserver /updateassemblyinfo
- name: Build with dotnet
run: dotnet build MySolution.sln --configuration Release
- name: Test
run: dotnet test MySolution.sln --no-build --verbosity normal
- name: Pack
run: dotnet pack MySolution.sln --no-build --configuration Release
- name: Publish NuGet Package
run: dotnet nuget push "**/*.nupkg" --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_API_KEY }}
In diesem Workflow konfiguriert und verwendet GitVersion, um die Versionsinformationen zu berechnen und das AssemblyInfo zu aktualisieren, bevor der Build, das Testen und das Packen des Projekts erfolgt. Das fertige Paket wird dann veröffentlicht.
Funktioniert GitVersion auch mit Conventional Commits, so wie ich das vorhabe?
Ja, GitVersion kann mit Conventional Commits verwendet werden, um die Versionierung deiner Software automatisch zu handhaben. GitVersion analysiert deine Git-Historie, um die nächste Versionsnummer basierend auf den Commit-Nachrichten und -Tags zu bestimmen, die bestimmten Regeln folgen. Dies ermöglicht eine nahtlose Integration mit dem Conventional Commit Schema.
GitVersion ist hochgradig konfigurierbar und kann an das Conventional Commits Schema angepasst werden. Du kannst in der Konfigurationsdatei GitVersion.yml
spezifische Einstellungen vornehmen, um zu definieren, wie verschiedene Typen von Commits behandelt werden sollten:
BREAKING CHANGE:
), oder durch die Verwendung eines !
direkt nach dem Typ/Scope.feat
(für neue Features) beginnen, können automatisch eine Minor-Version hochzählen.fix
beginnen, können automatisch eine Patch-Version hochzählen.Hier ein Beispiel für eine GitVersion.yml
, die angepasst ist, um das Conventional Commit Schema zu unterstützen:
mode: ContinuousDelivery
branches: {}
ignore:
sha: []
merge-message-formats: {}
commits-before: {}
commit-message-incrementing: Enabled
increment: Inherit
commit-date-format: yyyy-MM-dd
branches:
main:
regex: ^main$
source-branches: ['develop', 'release']
increment: Minor
prevent-increment-of-merged-branch-version: false
tag: ''
is-source-branch-for:
- develop
- release
feature:
regex: ^features?[/-]
source-branches: ['develop', 'feature', 'support', 'hotfix']
increment: Patch
prevent-increment-of-merged-branch-version: false
tag: useBranchName
tracks-release-branches: false
is-source-branch-for:
- develop
- release
- feature
- support
- hotfix
release:
regex: ^releases?[/-]
source-branches: ['develop', 'release']
increment: Patch
prevent-increment-of-merged-branch-version: true
tag: ''
is-source-branch-for:
- develop
- release
ignore:
sha: []
In dieser Konfiguration:
increment
: Steuert, wie die Version basierend auf dem Commit-Typ erhöht wird.commit-message-incrementing
: Aktiviert das Hochzählen der Version basierend auf den Commit-Nachrichten.regex
) für Branch-Namen definieren, wie Branches benannt sein müssen, um erkannt zu werden.GitVersion.yml
entsprechend deinen Bedürfnissen an.Durch die Einbindung von GitVersion in dein Projektmanagement kannst du sicherstellen, dass deine Versionsnummern immer korrekt und basierend auf deinen Commit-Nachrichten aktualisiert werden, was eine klare und nachvollziehbare Versionierungsgeschichte gewährleistet.
Was meinst du mit "Installation"? Würdest du GitVersion zuerst lokal ausprobieren?
Ja, es ist oft eine gute Idee, GitVersion lokal zu installieren und auszuprobieren, bevor du es in deinem CI/CD-System integrierst. Dies ermöglicht es dir, die Konfiguration und das Verhalten von GitVersion zu verstehen und sicherzustellen, dass es wie erwartet funktioniert. Dadurch kannst du Probleme frühzeitig erkennen und lösen, ohne dass dies Auswirkungen auf dein Produktions-Repository oder deinen Build-Prozess hat.
Installation: GitVersion kann als .NET Global Tool über die .NET Core CLI installiert werden. Das macht es einfach, es auf jedem System zu installieren, das .NET Core unterstützt. Hier ist der Befehl zur Installation:
dotnet tool install --global GitVersion.Tool
Ausführen von GitVersion: Nach der Installation kannst du GitVersion in deinem Repository lokal ausführen, um zu sehen, welche Versionsnummer es basierend auf deinem aktuellen Commit-Verlauf generiert. Führe dazu einfach gitversion
im Terminal im Stammverzeichnis deines Projekts aus.
gitversion
Dieser Befehl analysiert deine Git-Historie und zeigt die berechneten Versionsinformationen an. Es ist eine gute Möglichkeit, zu sehen, wie Änderungen in deinen Branches und Commits die Version beeinflussen.
Konfiguration anpassen: Du kannst die GitVersion.yml
Konfigurationsdatei in deinem Projektverzeichnis erstellen oder bearbeiten, um das Verhalten von GitVersion anzupassen. Experimentiere mit verschiedenen Einstellungen, um zu sehen, wie sie die berechnete Version beeinflussen.
Integration in deinen lokalen Build-Prozess: Du kannst GitVersion auch in dein lokales Build-Skript integrieren, um zu testen, wie es mit deinem Build-Prozess interagiert. Zum Beispiel, indem du die Versionsinformationen automatisch in AssemblyInfo-Dateien oder andere Teile deiner Build-Ausgabe schreibst.
GitVersion.yml
zu optimieren, bevor du sie in einer CI/CD-Umgebung verwendest, was zu weniger Fehlern in der Produktion führt.Nachdem du GitVersion lokal getestet und konfiguriert hast, kannst du es in deinem CI/CD-Workflow integrieren, indem du es als Schritt in deinem CI/CD-System hinzufügst. Dadurch wird sichergestellt, dass die Versionierung nahtlos in deinen automatisierten Release-Prozess integriert ist.
(...)
Es funktioniert noch nicht. Lass uns doch den Trigger auf den Feature-Branch ausweiten damit ich die innerhalb dieses Features "gitools-action-verwenden" testen kann.
Und ohne Konfigurationsdatei, einfach die Standardkonfiguration.
Um den Workflow zu testen, ohne eine Konfigurationsdatei zu verwenden und ihn auf Feature-Branches auszuweiten, könntest du den Workflow wie folgt anpassen:
name: Semantic Versioning and Release Draft
on:
push:
branches:
- main
- features/*
jobs:
semantic-versioning:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Install and use GitVersion
id: gitversion # Notwendig, um die Outputs später nutzen zu können
uses: gittools/actions/gitversion/execute@v1.1.1
- name: Output GitVersion calculated values
run: |
echo "Major: ${{ steps.gitversion.outputs.major }}"
echo "Minor: ${{ steps.gitversion.outputs.minor }}"
echo "Patch: ${{ steps.gitversion.outputs.patch }}"
echo "SemVer: ${{ steps.gitversion.outputs.semver }}"
echo "FullSemVer: ${{ steps.gitversion.outputs.fullSemVer }}"
echo "BranchName: ${{ steps.gitversion.outputs.branchName }}"
echo "EscapedBranchName: ${{ steps.gitversion.outputs.escapedBranchName }}"
echo "Sha: ${{ steps.gitversion.outputs.sha }}"
echo "ShortSha: ${{ steps.gitversion.outputs.shortSha }}"
echo "NuGetVersion: ${{ steps.gitversion.outputs.nuGetVersion }}"
echo "NuGetPreReleaseTag: ${{ steps.gitversion.outputs.nuGetPreReleaseTag }}"
echo "VersionSourceSha: ${{ steps.gitversion.outputs.versionSourceSha }}"
echo "CommitsSinceVersionSource: ${{ steps.gitversion.outputs.commitsSinceVersionSource }}"
echo "CommitsSinceVersionSourcePadded: ${{ steps.gitversion.outputs.commitsSinceVersionSourcePadded }}"
echo "CommitDate: ${{ steps.gitversion.outputs.commitDate }}"
- name: Create GitHub Release Draft
if: github.ref == 'refs/heads/main' # Nur ausführen, wenn auf dem Main-Branch gepusht wird
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ steps.gitversion.outputs.semver }}
release_name: Release ${{ steps.gitversion.outputs.semver }}
draft: true
body: |
Changes in this Release:
- Some changes description.
### Features
* New features added
### Fixes
* Bugs fixed
Die Outputs von GitVersion können je nach Setup und Anwendung variieren, überprüfe also die Ausgabe im Workflow-Lauf und stelle sicher, dass du die entsprechenden Werte in deinen nachfolgenden Schritten nutzt.
Dieser Workflow wird jetzt sowohl für Pushes auf den main
Branch als auch auf alle Feature-Branches (angenommen, dass sie mit features/
beginnen) ausgelöst. Beachte, dass die Erstellung des GitHub-Release-Entwurfs so konfiguriert ist, dass sie nur auf dem main
Branch erfolgt, da dies typischerweise der Branch ist, auf dem Releases gebaut werden. Wenn du möchtest, dass ein Entwurf für jeden Branch erstellt wird, kannst du die if
-Bedingung entfernen oder anpassen.
Welchen Trigger muss ich verwenden damit er auf alle Branches funktioniert? Das hier funktioniert nicht.
name: Semantic Versioning and Release Draft
on: push: branches:
Um den Workflow auf alle Branches zu triggern, kannst du das Sternchen (*
) als Wildcard verwenden, um alle Branches zu erfassen. Der korrekte Syntax im Workflow-Datei wäre:
on:
push:
branches:
- '*'
Wenn du dies in deinen Workflow einfügst, wird der Workflow bei jedem Push auf jeden Branch im Repository ausgelöst. Hier ist dein Workflow mit dem aktualisierten Trigger:
name: Semantic Versioning and Release Draft
on:
push:
branches:
- '*'
jobs:
semantic-versioning:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Install and use GitVersion
id: gitversion
uses: gittools/actions/gitversion/execute@v1.1.1
- name: Output GitVersion calculated values
run: |
echo "Major: ${{ steps.gitversion.outputs.major }}"
echo "Minor: ${{ steps.gitversion.outputs.minor }}"
echo "Patch: ${{ steps.gitversion.outputs.patch }}"
echo "SemVer: ${{ steps.gitversion.outputs.semver }}"
echo "FullSemVer: ${{ steps.gitversion.outputs.fullSemVer }}"
echo "BranchName: ${{ steps.gitversion.outputs.branchName }}"
echo "EscapedBranchName: ${{ steps.gitversion.outputs.escapedBranchName }}"
echo "Sha: ${{ steps.gitversion.outputs.sha }}"
echo "ShortSha: ${{ steps.gitversion.outputs.shortSha }}"
echo "NuGetVersion: ${{ steps.gitversion.outputs.nuGetVersion }}"
echo "NuGetPreReleaseTag: ${{ steps.gitversion.outputs.nuGetPreReleaseTag }}"
echo "VersionSourceSha: ${{ steps.gitversion.outputs.versionSourceSha }}"
echo "CommitsSinceVersionSource: ${{ steps.gitversion.outputs.commitsSinceVersionSource }}"
echo "CommitsSinceVersionSourcePadded: ${{ steps.gitversion.outputs.commitsSinceVersionSourcePadded }}"
echo "CommitDate: ${{ steps.gitversion.outputs.commitDate }}"
- name: Create GitHub Release Draft
if: github.ref == 'refs/heads/main'
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ steps.gitversion.outputs.semver }}
release_name: Release ${{ steps.gitversion.outputs.semver }}
draft: true
body: |
Changes in this Release:
- Some changes description.
### Features
* New features added
### Fixes
* Bugs fixed
- name: Tag Repository
if: github.ref == 'refs/heads/main'
run: |
git tag ${{ steps.gitversion.outputs.semver }}
git push origin ${{ steps.gitversion.outputs.semver }}
Bitte beachte, dass die Bedingung if: github.ref == 'refs/heads/main'
immer noch sicherstellt, dass der GitHub-Release-Entwurf und die Tag-Erstellung nur für Änderungen im main
Branch ausgeführt werden. Wenn du möchtest, dass für alle Branches ein Entwurf erstellt wird, müsstest du diese Bedingung entfernen oder anpassen.
Run gittools/actions/gitversion/execute@v1.1.1
with:
disableCache: false
disableNormalization: false
disableShallowCloneCheck: false
useConfigFile: false
configFilePath: GitVersion.yml
updateAssemblyInfo: false
Agent: 'GitHub Actions'
Command: git -C /home/runner/work/GitHub.Workflows/GitHub.Workflows rev-parse --is-shallow-repository
/usr/bin/git -C /home/runner/work/GitHub.Workflows/GitHub.Workflows rev-parse --is-shallow-repository
true
Error: Error: The repository is shallow. Consider disabling shallow clones. See https://github.com/GitTools/actions/blob/main/docs/cloning.md for more information.
Die Fehlermeldung weist darauf hin, dass das Repository nur oberflächlich geklont wurde (shallow clone), was bedeutet, dass nicht die gesamte Git-Historie heruntergeladen wurde. GitVersion benötigt die gesamte Historie, um korrekt zu funktionieren, weil es darauf angewiesen ist, Tags, Branches und die Historie der Commits zu analysieren, um die Version zu berechnen.
Um dieses Problem zu lösen, musst du sicherstellen, dass beim Checkout-Schritt das gesamte Git-Repository und nicht nur der letzte Commit geklont wird. Hierfür setzt du die fetch-depth
Option auf 0
, was bedeutet, dass die gesamte Historie geklont wird:
steps:
- name: Checkout code
uses: actions/checkout@v2
with:
fetch-depth: 0 # Dies wird die gesamte Git-Historie klonen
Nachdem du diese Änderung in deinem Workflow vorgenommen hast, sollte der Fehler bezüglich des shallow Repository behoben sein, und die GitVersion Action sollte in der Lage sein, korrekt zu laufen.
Hier ist ein Beispiel für den aktualisierten Workflow:
name: Semantic Versioning and Release Draft
on:
push:
branches:
- '*' # Dieser Trigger wird für alle Branches aktiviert
jobs:
semantic-versioning:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
with:
fetch-depth: 0 # Wichtig, um sicherzustellen, dass das ganze Repo geklont wird
- name: Install and use GitVersion
uses: gittools/actions/gitversion/execute@v1.1.1
with:
configFilePath: 'GitVersion.yml'
Füge diesen aktualisierten Checkout-Schritt zu Beginn deines Workflows hinzu, bevor du GitVersion oder andere Schritte ausführst, die Zugriff auf die Git-Historie benötigen.
(...)
Es ist durchgelaufen :-) allerdings noch wurde nirgends eine Version festgestellt
Es scheint, dass der Workflow erfolgreich durchgeführt wurde, aber die GitVersion-Ausgabe zeigt keine Werte an. Dies könnte darauf hinweisen, dass der Schritt zur Bestimmung der Version (der Determine Version
-Schritt) nicht die erwarteten Informationen ausgibt.
Normalerweise, wenn GitVersion korrekt ausgeführt wird, sollten Sie Informationen über die berechnete Version wie Major, Minor, Patch, SemVer und so weiter sehen. Wenn diese Informationen fehlen, könnte es sein, dass GitVersion auf ein Problem gestoßen ist, oder dass die Ausgabe nicht korrekt an den nächsten Schritt weitergegeben wurde.
Es ist auch möglich, dass die Konfiguration oder die Nutzung von GitVersion nicht ganz korrekt ist. Hier sind ein paar Vorschläge, was Sie prüfen könnten:
GitVersion.yml
korrekt aufgesetzt und im Root-Verzeichnis Ihres Repositories platziert ist.Wenn diese Punkte geprüft und korrigiert sind, sollten Sie die Versionierungsinformationen in Ihrem Workflow erhalten. Sollten weiterhin Probleme auftreten, wäre es hilfreich, die Logs des Schrittes Determine Version
genau zu überprüfen, um zusätzliche Hinweise zu erhalten, warum keine Versionierungsdaten erzeugt werden.
Eine Frage, bevor ich diesen Weg weiter beschreite ... In Release Drafter kann ich nicht automatisch die Version erhöhen lassen oder?
Den Release Drafter habe ich in kürzester Zeit zum Laufen gebracht, während das hier deutlich komplexer ist und immer noch nicht funktioniert.
Nein, der Release Drafter selbst kann nicht automatisch die Versionsnummer erhöhen. Release Drafter ist darauf ausgelegt, auf Basis von Pull-Requests und den zugehörigen Labels Release Notes zu erstellen und einen Release-Entwurf zu generieren. Es nimmt keine Versionierung vor, sondern erwartet, dass die Versionsnummern manuell verwaltet oder durch ein anderes Tool oder einen Prozess automatisiert werden.
Wenn Sie den Release Drafter weiterhin verwenden möchten und eine automatisierte Versionierung wünschen, müssten Sie dies mit einer zusätzlichen GitHub Action oder einem anderen CI/CD-Tool kombinieren, das auf die Commits reagiert und entsprechend die Versionsnummern erhöht. Semantic Release oder GitVersion könnten dafür genutzt werden, allerdings müssen diese korrekt konfiguriert und eingebunden werden, was, wie Sie schon festgestellt haben, durchaus komplex sein kann.
Falls Sie den Release Drafter bereits erfolgreich implementiert haben und ihn weiterhin nutzen möchten, könnten Sie folgendes tun:
Manuelles Tagging: Sie könnten weiterhin manuell Tags für Ihre Releases setzen, die dann von Release Drafter für die Generierung der Release Notes genutzt werden.
Kombination mit Scripting: Schreiben Sie ein kleines Skript, das beim Merging eines Pull Requests ausgeführt wird und die Versionsnummer basierend auf Ihren festgelegten Regeln erhöht. Dieses Skript könnte dann ein neues Tag erstellen und den Release Drafter triggern.
Integration eines Versionierungstools: Nutzen Sie ein Tool wie Semantic Release oder GitVersion in einem separaten Workflow-Schritt, das dann ein neues Tag basierend auf den Conventional Commits erstellt, welches dann vom Release Drafter für den Release-Entwurf verwendet wird.
Es ist wichtig zu beachten, dass die automatische Versionierung, insbesondere in Kombination mit Conventional Commits, einiges an Initialaufwand für die Einrichtung erfordert. Sobald dies jedoch korrekt konfiguriert ist, kann es den Prozess der Versionsverwaltung und Release-Erstellung erheblich vereinfachen und automatisieren.
Ich stelle jetzt in der Praxis fest, dass es gar nicht wirklich einen Vorteil für mich bringt wenn ich Conventional Commits verwende und stattdessen einfach beim Pullrequest alle Features zusammentragen kann.
Das ist eine völlig legitime Herangehensweise, und in vielen Fällen kann sie sogar einfacher und transparenter sein. Der Hauptvorteil von Conventional Commits liegt in der Automatisierung und der Möglichkeit, aus den Commit-Nachrichten direkt automatisch Versionen zu generieren und ein Changelog zu erstellen. Wenn diese Automatisierung für Sie nicht notwendig oder sogar eher hinderlich ist, spricht nichts dagegen, die Release-Informationen manuell beim Pullrequest zu pflegen.
Das Sammeln von Features, Bugfixes und sonstigen Änderungen direkt im Pull Request bietet Ihnen die Kontrolle darüber, was in den Release Notes erscheint, und kann Ihnen helfen, eine klare und verständliche Historie Ihrer Releases zu erstellen, die für Ihr Team und Ihre Nutzer nützlich ist.
Wenn Sie den Release Drafter nutzen, können Sie den Prozess weiterhin etwas automatisieren, indem Sie Labels in Pull Requests nutzen, um automatisch Abschnitte im Changelog zu erstellen. Aber die finale Entscheidung, welche Änderungen prominent im Changelog erscheinen, und die finale Versionierung nehmen Sie manuell vor.
Letztlich ist der beste Workflow der, der zu Ihnen und Ihrem Team passt, Ihre Produktivität steigert und Transparenz schafft, ohne unnötig zu komplizieren. Wenn Sie feststellen, dass die manuelle Pflege der Release Notes diesen Anforderungen am besten entspricht, dann ist das der richtige Weg für Sie.
Hervorgegangen aus:
3