Automate code metrics and class diagrams with GitHub Actions

Hi friends, in my previous post — .NET GitHub Actions, you were introduced to the GitHub Actions platform and the workflow composition syntax. You learned how common .NET CLI commands and actions can be used as building blocks for creating fully automated CI/CD pipelines, directly from your GitHub repositories.

This is the second post in the series dedicated to the GitHub Actions platform and the relevance it has for .NET developers. In this post, I’ll summarize an existing GitHub Action written in .NET that can be used to maintain a code metrics markdown file. I’ll then explain how you can create your own GitHub Actions with .NET. I’ll show you how to define metadata that’s used to identify a GitHub repo as an action. Finally, I’ll bring it all together with a really cool example: updating an action in the .NET samples repository to include class diagram support using GitHub’s brand new Mermaid diagram support, so it will build updated class diagrams on every commit.

Writing a custom GitHub Action with .NET

Actions support several variations of app development:

Action type
Metadata specifier
Description

Docker
runs.using: ‘docker’
Any app that can run as a Docker container.

JavaScript
runs.using: ‘javascript’
Any Node.js app (includes the benefit of using actions/toolkit).

Composite
runs.using: ‘composite’
Composes multiple run commands and uses actions.

.NET is capable of running in Docker containers, and when you want a fully functioning .NET app to run as your GitHub Action — you’ll have to containerize your app. For more information on .NET and Docker, see .NET Docs: Containerize a .NET app.

If you’re curious about creating JavaScript GitHub Actions, I wrote about that too, see Localize .NET applications with machine-translation. In that blog post, I cover a TypeScript action that relies on Azure Cognitive Services to automatically generate pull requests for target translations.

In addition to containerizing a .NET app, you could alternatively create a .NET global tool that could be installed and called upon using the run syntax instead of uses. This alternative approach is useful for creating a .NET CLI app that can be used as a global tool, but it’s out of scope for this post. For more information on .NET global tools, see .NET Docs: Global tools.

Intent of the tutorial

Over on the .NET Docs, there’s a tutorial on creating a GitHub Action with .NET. It covers exactly how to containerize a .NET app, how to author the action.yml which represents the action’s metadata, as well as how to consume it from a workflow. Rather than repeating the entire tutorial, I’ll summarize the intent of the action, and then we’ll look at how it was updated.

The app in the tutorial performs code metric analysis by:

Scanning and discovering *.csproj and *.vbproj project files in a target repository.
Analyzing the discovered source code within these projects for:

Cyclomatic complexity
Maintainability index
Depth of inheritance
Class coupling
Number of lines of source code
Approximated lines of executable code

Creating (or updating) a _CODEMETRICS.md file.

As part of the consuming workflow composition, a pull request is conditionally (and automatically) created when the _CODEMETRICS.md file changes. In other words, as you push changes to your GitHub repository, the workflow runs and uses the .NET code metrics action — which updates the markdown representation of the code metrics. The _CODEMETRICS.md file itself is navigable with automatic links, and collapsible sections. It uses emoji to highlight code metrics at a glance, for example, when a class has high cyclomatic complexity it bubbles an emoji up to the project level heading the markdown. From there, you can drill down into the class and see the metrics for each method.

The Microsoft.CodeAnalysis.CodeMetrics namespace contains the CodeAnalysisMetricData type, which exposes the CyclomaticComplexity property. This property is a measurement of the structural complexity of the code. It is created by calculating the number of different code paths in the flow of the program. A program that has a complex control flow requires more tests to achieve good code coverage and is less maintainable. When the code is analyzed and the GitHub Action updates the _CODEMETRICS.md file, it writes an emoji in the header using the following code:

internal static string ToCyclomaticComplexityEmoji(
this CodeAnalysisMetricData metric) =>
metric.CyclomaticComplexity switch
{
>= 0 and <= 7 => “:heavy_check_mark:”, // ✔
8 or 9 => “:warning:”, // ⚠
10 or 11 => “:radioactive:”, // ☢
>= 12 and <= 14 => “:x:”, // ❌
_ => “:exploding_head:” // 🤯
};

All of the code for this action is provided in the .NET samples repository and is also part of the .NET docs code samples browser experience. As an example of usage, the action is self-consuming (or dogfooding). As the code updates the action runs, and maintains a _CODEMETRICS.md file via automated pull requests. Consider the following screen captures showing some of the major parts of the markdown file:

_CODEMETRICS.md heading

_CODEMETRICS.md ProjectFileReference drill-down heading

The code metrics markdown file represents the code it analyzes, by providing the following hierarchy:

Project Namespace Named Type Members table

When you drill down into a named type, there is a link at the bottom of the table for the auto-generated class diagram. This is discussed in the Adding new functionality section. To navigate the example file yourself, see .NET samples _CODEMETRICS.md.

Action metadata

For a GitHub repository to be recognized as a GitHub Action, it must define metadata in an action.yml file.

# Name, description, and branding. All of which are used for
# displaying the action in the GitHub Action marketplace.
name: ‘.NET code metric analyzer’
description: ‘A GitHub action that maintains a CODE_METRICS.md file,
reporting cyclomatic complexity, maintainability index, etc.’
branding:
icon: sliders
color: purple

# Specify inputs, some are required and some are not.
inputs:
owner:
description: ‘The owner of the repo. Assign from github.repository_owner. Example, “dotnet”.’
required: true
name:
description: ‘The repository name. Example, “samples”.’
required: true
branch:
description: ‘The branch name. Assign from github.ref. Example, “refs/heads/main”.’
required: true
dir:
description: ‘The root directory to work from. Example, “path/to/code”.’
required: true
workspace:
description: ‘The workspace directory.’
required: false
default: ‘/github/workspace’

# The action outputs the following values.
outputs:
summary-title:
description: ‘The title of the code metrics action.’
summary-details:
description: ‘A detailed summary of all the projects that were flagged.’
updated-metrics:
description: ‘A boolean value, indicating whether or not the CODE_METRICS.md
was updated as a result of running this action.’

# The action runs using docker and accepts the following arguments.
runs:
using: ‘docker’
image: ‘Dockerfile’
args:
– ‘-o’
– ${{ inputs.owner }}
– ‘-n’
– ${{ inputs.name }}
– ‘-b’
– ${{ inputs.branch }}
– ‘-d’
– ${{ inputs.dir }}
– ‘-w’
– ${{ inputs.workspace }}

The metadata for the .NET samples code metrics action is nested within a subdirectory, as such, it is not recognized as an action that can be displayed in the GitHub Action marketplace. However, it can still be used as an action.

For more information on metadata, see GitHub Docs: Metadata syntax for GitHub Actions.

Consuming workflow

To consume the .NET code metrics action, a workflow file must exist in the .github/workflows directory from the root of the GitHub repository. Consider the following workflow file:

name: ‘.NET code metrics’

on:
push:
branches: [ main ]
paths-ignore:
# Ignore CODE_METRICS.md and README.md files
– ‘**.md’

jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write

steps:
– uses: actions/[email protected]

# Analyze repositories source metrics:
# Create (or update) CODE_METRICS.md file.
– name: .NET code metrics
id: dotnet-code-metrics
uses: dotnet/samples/github-actions/[email protected]
with:
owner: ${{ github.repository_owner }}
name: ${{ github.repository }}
branch: ${{ github.ref }}
dir: ${{ ‘./github-actions/DotNet.GitHubAction’ }}

# Create a pull request if there are changes.
– name: Create pull request
uses: peter-evans/[email protected]
if: ${{ steps.dotnet-code-metrics.outputs.updated-metrics }} == ‘true’
with:
title: ‘${{ steps.dotnet-code-metrics.outputs.summary-title }}’
body: ‘${{ steps.dotnet-code-metrics.outputs.summary-details }}’
commit-message: ‘.NET code metrics, automated pull request.’

This workflow makes use of jobs.<job_id>.permissions, setting contents and pull-requests to write. This is required for the action to update contents in the repo and create a pull request from those changes. For more information on permissions, see GitHub Docs: Workflow syntax for GitHub Actions – permissions.

To help visualize how this workflow functions, see the following sequence diagram:

The preceding sequence diagram shows the workflow for the .NET code metrics action:

When a developer pushes code to the GitHub repository.

The workflow is triggered and starts to run.

The source code is checked out into the $GITHUB_WORKSPACE.

The .NET code metrics action is invoked.

The source code is analyzed, and the _CODEMETRICS.md file is updated.

If the .NET code metrics step (dotnet-code-metrics) outputs that metrics were updated, the create-pull-request action is invoked.

The _CODEMETRICS.md file is checked into the repository.
An automated pull request is created. For example pull requests created by the app/github-actions bot, see .NET samples / pull requests.

Adding new functionality

GitHub recently announced diagram support for Markdown powered by Mermaid. Since our custom action is capable of analyzing C# as part of its execution, it has a semantic understanding of the classes it’s analyzing. This is used to automatically create Mermaid class diagrams in the _CODEMETRICS.md file.

The .NET code metrics GitHub Action sample code was updated to include Mermaid support.

static void AppendMermaidClassDiagrams(
MarkdownDocument document,
List<(string Id, string Class, string MermaidCode)> diagrams)
{
document.AppendHeader(“Mermaid class diagrams”, 2);

foreach (var (id, className, code) in diagrams)
{
document.AppendParagraph($”<div id=”{id}”></div>”);
document.AppendHeader($”`{className}` class diagram”, 5);
document.AppendCode(“mermaid”, code);
}
}

If you’re interested in seeing the code that generates the diagrams argument, see the ToMermaidClassDiagram extension method.
As an example of what this renders like within the _CODEMETRICS.md Markdown file, see the following diagram:

Summary

In this post, you learned about the different types of GitHub Actions with an emphasis on Docker and .NET. I explained how the .NET code metrics GitHub Action was updated to include Mermaid class diagram support. You also saw an example action.yml file that serves as the metadata for a GitHub Action. You then saw a visualization of the consuming workflow, and how the code metrics action is consumed. I also covered how to add new functionality to the code metrics action.

What will you build, and how will it help others? I encourage you to create and share your own .NET GitHub Actions. For more information on .NET and custom GitHub Actions, see the following resources:

GitHub Docs: Creating custom actions
.NET Docs: GitHub Actions and .NET

The post Automate code metrics and class diagrams with GitHub Actions appeared first on .NET Blog.

Leave a Reply

Your email address will not be published.