kurmann / GitHub.Workflows

Zentrale Sammelstelle für Reusable GitHub-Workflows.
Apache License 2.0
0 stars 0 forks source link

Einführung von GitVersion #11

Closed kurmann closed 7 months ago

kurmann commented 7 months ago

Hervorgegangen aus:

kurmann commented 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:

1. Verwendung von GitVersion

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.

2. GitHub Actions für .NET

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.

3. NuGet-Paketierung und Versionierung

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.

Beispiel: GitHub Actions Workflow mit GitVersion

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.

kurmann commented 7 months ago

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.

Konfiguration von GitVersion für Conventional Commits

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:

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:

Einrichtung und Integration

  1. Installation: Füge GitVersion als Tool zu deinem Projekt hinzu, entweder über eine CI/CD-Pipeline oder lokal als globales Tool.
  2. Konfiguration anpassen: Pass deine GitVersion.yml entsprechend deinen Bedürfnissen an.
  3. Integration in Build-Prozesse: Integriere GitVersion in deinen Build-Prozess, um bei jedem Build die Versionsnummer automatisch zu aktualisieren.

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.

kurmann commented 7 months ago

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.

Lokale Installation und Ausprobieren von GitVersion

  1. 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
  2. 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.

  3. 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.

  4. 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.

Vorteile der lokalen Ausprobierung

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.

kurmann commented 7 months ago

(...)

kurmann commented 7 months ago

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.

kurmann commented 7 months ago

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.

kurmann commented 7 months ago
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.

kurmann commented 7 months ago

(...)

kurmann commented 7 months ago

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:

  1. Stellen Sie sicher, dass die Konfigurationsdatei GitVersion.yml korrekt aufgesetzt und im Root-Verzeichnis Ihres Repositories platziert ist.
  2. Überprüfen Sie, ob die GitVersion-Action korrekt konfiguriert ist und ob sie die Ausgabe so erzeugt, dass sie von den nachfolgenden Schritten des Workflows genutzt werden kann.
  3. Stellen Sie sicher, dass die GitVersion-Ausgabe in den Workflow-Step-Variablen gespeichert wird, damit Sie sie in späteren Schritten verwenden können.

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.

kurmann commented 7 months ago

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:

  1. 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.

  2. 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.

  3. 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.

kurmann commented 7 months ago

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.