-
Notifications
You must be signed in to change notification settings - Fork 274
Add GitHub Actions workflow to build website via Nuke and Jekyll #391
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
918520a
f01f4d7
8279fb7
55d76a8
509774e
f16399c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| name: Build | ||
|
|
||
| on: | ||
| push: | ||
| branches: [ "master" ] | ||
| pull_request: | ||
| branches: [ "master" ] | ||
|
|
||
| jobs: | ||
| build: | ||
| runs-on: windows-latest | ||
| permissions: | ||
| contents: read | ||
|
|
||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 0 | ||
|
|
||
| - name: Run Nuke build | ||
| run: .\build.ps1 Default | ||
|
|
||
| # The Nuke build and Jekyll build are independent: | ||
| # - Nuke compiles the guidelines into standalone HTML documents using Pandoc | ||
| # - Jekyll builds the static website from the same source Markdown files | ||
| # They share no artifacts and can run in parallel. | ||
| jekyll: | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: read | ||
|
|
||
| steps: | ||
| - uses: actions/checkout@v4 | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @copilot Running Jekyll should also become part of the Nuke script
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done in commit f16399c. Target JekyllBuild => _ => _
.Executes(() =>
{
ProcessTasks.StartProcess("bundle", "install", RootDirectory)
.AssertZeroExitCode();
ProcessTasks.StartProcess("bundle", "exec jekyll build", RootDirectory)
.AssertZeroExitCode();
}); |
||
|
|
||
| - name: Build Jekyll site via Nuke | ||
| run: ./build.sh JekyllBuild | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,3 +6,8 @@ _site | |
|
|
||
| .idea/ | ||
| /node_modules/ | ||
|
|
||
| # Nuke build artifacts | ||
| /build/bin/ | ||
| /build/obj/ | ||
| /.nuke/temp/ | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| [CmdletBinding()] | ||
| Param( | ||
| [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)] | ||
| [string[]]$BuildArguments | ||
| ) | ||
|
|
||
| Write-Output "PowerShell $($PSVersionTable.PSEdition) version $($PSVersionTable.PSVersion)" | ||
|
|
||
| Set-StrictMode -Version 2.0; $ErrorActionPreference = "Stop"; $ConfirmPreference = "None"; trap { Write-Error $_ -ErrorAction Continue; exit 1 } | ||
| $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent | ||
|
|
||
| ########################################################################### | ||
| # CONFIGURATION | ||
| ########################################################################### | ||
|
|
||
| $BuildProjectFile = "$PSScriptRoot\build\_build.csproj" | ||
| $TempDirectory = "$PSScriptRoot\.nuke\temp" | ||
|
|
||
| $DotNetGlobalFile = "$PSScriptRoot\global.json" | ||
| $DotNetInstallUrl = "https://dot.net/v1/dotnet-install.ps1" | ||
| $DotNetChannel = "STS" | ||
|
|
||
| $env:DOTNET_CLI_TELEMETRY_OPTOUT = 1 | ||
| $env:DOTNET_NOLOGO = 1 | ||
| $env:DOTNET_ROLL_FORWARD = "Major" | ||
| $env:NUKE_TELEMETRY_OPTOUT = 1 | ||
|
|
||
| ########################################################################### | ||
| # EXECUTION | ||
| ########################################################################### | ||
|
|
||
| function ExecSafe([scriptblock] $cmd) { | ||
| & $cmd | ||
| if ($LASTEXITCODE) { exit $LASTEXITCODE } | ||
| } | ||
|
|
||
| # Check if any dotnet is installed | ||
| if ($null -ne (Get-Command "dotnet" -ErrorAction SilentlyContinue)) { | ||
| ExecSafe { & dotnet --info } | ||
| } | ||
|
|
||
| # If dotnet CLI is installed globally and it matches requested version, use for execution | ||
| if ($null -ne (Get-Command "dotnet" -ErrorAction SilentlyContinue) -and ` | ||
| $(dotnet --version) -and $LASTEXITCODE -eq 0) { | ||
| $env:DOTNET_EXE = (Get-Command "dotnet").Path | ||
| } | ||
| else { | ||
| # Download install script | ||
| $DotNetInstallFile = "$TempDirectory\dotnet-install.ps1" | ||
| New-Item -ItemType Directory -Path $TempDirectory -Force | Out-Null | ||
| [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 | ||
| (New-Object System.Net.WebClient).DownloadFile($DotNetInstallUrl, $DotNetInstallFile) | ||
|
|
||
| # If global.json exists, load expected version | ||
| if (Test-Path $DotNetGlobalFile) { | ||
| $DotNetGlobal = $(Get-Content $DotNetGlobalFile | Out-String | ConvertFrom-Json) | ||
| if ($DotNetGlobal.PSObject.Properties["sdk"] -and $DotNetGlobal.sdk.PSObject.Properties["version"]) { | ||
| $DotNetVersion = $DotNetGlobal.sdk.version | ||
| } | ||
| } | ||
|
|
||
| # Install by channel or version | ||
| $DotNetDirectory = "$TempDirectory\dotnet-win" | ||
| if (!(Test-Path variable:DotNetVersion)) { | ||
| ExecSafe { & powershell $DotNetInstallFile -InstallDir $DotNetDirectory -Channel $DotNetChannel -NoPath } | ||
| } else { | ||
| ExecSafe { & powershell $DotNetInstallFile -InstallDir $DotNetDirectory -Version $DotNetVersion -NoPath } | ||
| } | ||
| $env:DOTNET_EXE = "$DotNetDirectory\dotnet.exe" | ||
| $env:PATH = "$DotNetDirectory;$env:PATH" | ||
| } | ||
|
|
||
| Write-Output "Microsoft (R) .NET SDK version $(& $env:DOTNET_EXE --version)" | ||
|
|
||
| ExecSafe { & $env:DOTNET_EXE build $BuildProjectFile /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary } | ||
| ExecSafe { & $env:DOTNET_EXE run --project $BuildProjectFile --no-build -- $BuildArguments } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| #!/usr/bin/env bash | ||
|
|
||
| bash --version 2>&1 | head -n 1 | ||
|
|
||
| set -eo pipefail | ||
| SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) | ||
|
|
||
| ########################################################################### | ||
| # CONFIGURATION | ||
| ########################################################################### | ||
|
|
||
| BUILD_PROJECT_FILE="$SCRIPT_DIR/build/_build.csproj" | ||
| TEMP_DIRECTORY="$SCRIPT_DIR/.nuke/temp" | ||
|
|
||
| DOTNET_GLOBAL_FILE="$SCRIPT_DIR/global.json" | ||
| DOTNET_INSTALL_URL="https://dot.net/v1/dotnet-install.sh" | ||
| DOTNET_CHANNEL="STS" | ||
|
|
||
| export DOTNET_CLI_TELEMETRY_OPTOUT=1 | ||
| export DOTNET_NOLOGO=1 | ||
| export DOTNET_ROLL_FORWARD="Major" | ||
| export NUKE_TELEMETRY_OPTOUT=1 | ||
|
|
||
| ########################################################################### | ||
| # EXECUTION | ||
| ########################################################################### | ||
|
|
||
| function FirstJsonValue { | ||
| perl -nle 'print $1 if m{"'"$1"'": "([^"]+)",?}' <<< "${@:2}" | ||
| } | ||
|
|
||
| # Check if any dotnet is installed | ||
| if [[ -x "$(command -v dotnet)" ]]; then | ||
| dotnet --info | ||
| fi | ||
|
|
||
| # If dotnet CLI is installed globally and it matches requested version, use for execution | ||
| if [ -x "$(command -v dotnet)" ] && dotnet --version &>/dev/null; then | ||
| export DOTNET_EXE="$(command -v dotnet)" | ||
| else | ||
| # Download install script | ||
| DOTNET_INSTALL_FILE="$TEMP_DIRECTORY/dotnet-install.sh" | ||
| mkdir -p "$TEMP_DIRECTORY" | ||
| curl -Lsfo "$DOTNET_INSTALL_FILE" "$DOTNET_INSTALL_URL" | ||
| chmod +x "$DOTNET_INSTALL_FILE" | ||
|
|
||
| # If global.json exists, load expected version | ||
| if [[ -f "$DOTNET_GLOBAL_FILE" ]]; then | ||
| DOTNET_VERSION=$(FirstJsonValue "version" "$(cat "$DOTNET_GLOBAL_FILE")") | ||
| if [[ "$DOTNET_VERSION" == "" ]]; then | ||
| unset DOTNET_VERSION | ||
| fi | ||
| fi | ||
|
|
||
| # Install by channel or version | ||
| DOTNET_DIRECTORY="$TEMP_DIRECTORY/dotnet-unix" | ||
| if [[ -z ${DOTNET_VERSION+x} ]]; then | ||
| "$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --channel "$DOTNET_CHANNEL" --no-path | ||
| else | ||
| "$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --version "$DOTNET_VERSION" --no-path | ||
| fi | ||
| export DOTNET_EXE="$DOTNET_DIRECTORY/dotnet" | ||
| export PATH="$DOTNET_DIRECTORY:$PATH" | ||
| fi | ||
|
|
||
| echo "Microsoft (R) .NET SDK version $("$DOTNET_EXE" --version)" | ||
|
|
||
| "$DOTNET_EXE" build "$BUILD_PROJECT_FILE" /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary | ||
| "$DOTNET_EXE" run --project "$BUILD_PROJECT_FILE" --no-build -- "$@" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,185 @@ | ||
| using System; | ||
| using System.IO; | ||
| using System.Linq; | ||
| using System.Text.RegularExpressions; | ||
| using Nuke.Common; | ||
| using Nuke.Common.IO; | ||
| using Nuke.Common.Tooling; | ||
| using Nuke.Common.Tools.GitVersion; | ||
| using Serilog; | ||
|
|
||
| using GitVersionInfo = Nuke.Common.Tools.GitVersion.GitVersion; | ||
|
|
||
| class Build : NukeBuild | ||
| { | ||
| public static int Main() => Execute<Build>(x => x.Default); | ||
|
|
||
| AbsolutePath ArtifactsDirectory => RootDirectory / "Artifacts"; | ||
| AbsolutePath LibDirectory => RootDirectory / "Lib"; | ||
|
|
||
| const string DefaultRulePrefix = "AV"; | ||
|
|
||
| string semVer = string.Empty; | ||
| string commitDate = string.Empty; | ||
|
|
||
| Target Clean => _ => _ | ||
| .Executes(() => | ||
| { | ||
| ArtifactsDirectory.CreateOrCleanDirectory(); | ||
| }); | ||
|
|
||
| Target ExtractVersionsFromGit => _ => _ | ||
| .DependsOn(Clean) | ||
| .Executes(() => | ||
| { | ||
| var result = GitVersionTasks.GitVersion(s => s | ||
| .SetProcessToolPath(LibDirectory / "GitVersion.exe") | ||
| .SetProcessWorkingDirectory(RootDirectory)); | ||
|
|
||
| semVer = result.Result.SemVer; | ||
| commitDate = DateTime.Parse(result.Result.CommitDate).ToString("MMMM d, yyyy"); | ||
| }); | ||
|
|
||
| Target Compile => _ => _ | ||
| .DependsOn(ExtractVersionsFromGit) | ||
| .Executes(() => | ||
| { | ||
| var guidelineFiles = new AbsolutePath[] | ||
| { | ||
| RootDirectory / "_pages" / "0000_CoverAndStyles.md", | ||
| RootDirectory / "_includes" / "0001_Introduction.md", | ||
| RootDirectory / "_pages" / "1000_ClassDesignGuidelines.md", | ||
| RootDirectory / "_pages" / "1100_MemberDesignGuidelines.md", | ||
| RootDirectory / "_pages" / "1200_MiscellaneousDesignGuidelines.md", | ||
| RootDirectory / "_pages" / "1500_MaintainabilityGuidelines.md", | ||
| RootDirectory / "_pages" / "1700_NamingGuidelines.md", | ||
| RootDirectory / "_pages" / "1800_PerformanceGuidelines.md", | ||
| RootDirectory / "_pages" / "2200_FrameworkGuidelines.md", | ||
| RootDirectory / "_pages" / "2300_DocumentationGuidelines.md", | ||
| RootDirectory / "_pages" / "2400_LayoutGuidelines.md", | ||
| RootDirectory / "_pages" / "9999_ResourcesAndLinks.md", | ||
| }; | ||
|
|
||
| var outputDir = ArtifactsDirectory / "Guidelines"; | ||
| outputDir.CreateOrCleanDirectory(); | ||
|
|
||
| var outfile = outputDir / "CSharpCodingGuidelines.md"; | ||
|
|
||
| foreach (var file in guidelineFiles) | ||
| { | ||
| var rawContent = File.ReadAllText(file) | ||
| .Replace("%semver%", semVer) | ||
| .Replace("%commitdate%", commitDate) | ||
| .Replace("; | ||
|
|
||
| var title = ExtractFrontmatterField(rawContent, "title"); | ||
| var category = ExtractFrontmatterField(rawContent, "rule_category"); | ||
|
|
||
| rawContent = RemoveFrontmatter(rawContent); | ||
|
|
||
| string content; | ||
| if (string.IsNullOrEmpty(category)) | ||
| { | ||
| Log.Information("Including {File}", (string)file); | ||
| content = rawContent; | ||
| } | ||
| else | ||
| { | ||
| Log.Information("Including rules of category {Category}", category); | ||
| content = string.Empty; | ||
|
|
||
| foreach (var ruleFile in (RootDirectory / "_rules").GetFiles().OrderBy(f => (string)f)) | ||
| { | ||
| var rule = File.ReadAllText(ruleFile); | ||
| if (!Regex.IsMatch(rule, $@"---(.|\n)*rule_category\: {Regex.Escape(category)}")) | ||
| continue; | ||
|
|
||
| var ruleTitle = ExtractFrontmatterField(rule, "title"); | ||
| var ruleSeverity = ExtractFrontmatterField(rule, "severity"); | ||
| var ruleId = ExtractFrontmatterField(rule, "rule_id"); | ||
| var ruleIdPrefix = ExtractFrontmatterField(rule, "custom_prefix") is { Length: > 0 } cp | ||
| ? cp | ||
| : "{{ site.default_rule_prefix }}"; | ||
|
|
||
| content += $"<div id=\"{ruleIdPrefix}{ruleId}\"></div>### {ruleTitle} ({ruleIdPrefix}{ruleId}) <img src=\"assets/images/{ruleSeverity}.png\" />\n"; | ||
| content += RemoveFrontmatter(rule); | ||
| content += "\n"; | ||
| } | ||
| } | ||
|
|
||
| content = content.Replace("{{ site.default_rule_prefix }}", DefaultRulePrefix); | ||
| content = Regex.Replace(content, @"\(\/.+?(#\w+)\)", "($1)"); | ||
|
|
||
| if (!string.IsNullOrEmpty(title)) | ||
| content = $"<h1>{title}</h1>\n" + content; | ||
|
|
||
| File.AppendAllText(outfile, content); | ||
| } | ||
|
|
||
| (RootDirectory / "assets" / "css" / "Guidelines.css") | ||
| .Copy(outputDir / "style.css", ExistsPolicy.FileOverwrite); | ||
| (RootDirectory / "assets" / "images") | ||
| .Copy(outputDir / "assets" / "images", ExistsPolicy.MergeAndOverwrite); | ||
| }); | ||
|
|
||
| Target CompileCheatsheet => _ => _ | ||
| .DependsOn(ExtractVersionsFromGit) | ||
| .Executes(() => | ||
| { | ||
| var outputDir = ArtifactsDirectory / "Cheatsheet"; | ||
| outputDir.CreateOrCleanDirectory(); | ||
|
|
||
| var content = File.ReadAllText(RootDirectory / "_pages" / "Cheatsheet.md") | ||
| .Replace("%semver%", semVer) | ||
| .Replace("%commitdate%", commitDate) | ||
| .Replace("{{ site.default_rule_prefix }}", DefaultRulePrefix); | ||
|
|
||
| File.WriteAllText(outputDir / "Cheatsheet.md", content); | ||
|
|
||
| (RootDirectory / "assets" / "css" / "CheatSheet.css") | ||
| .Copy(outputDir / "style.css", ExistsPolicy.FileOverwrite); | ||
| (RootDirectory / "assets" / "images") | ||
| .Copy(outputDir / "assets" / "images", ExistsPolicy.MergeAndOverwrite); | ||
| }); | ||
|
|
||
| Target BuildHtml => _ => _ | ||
| .DependsOn(Compile, CompileCheatsheet) | ||
| .Executes(() => | ||
| { | ||
| var pandoc = LibDirectory / "Pandoc" / "pandoc.exe"; | ||
|
|
||
| ProcessTasks.StartProcess( | ||
| pandoc, | ||
| $"CSharpCodingGuidelines.md -f markdown_phpextra -s -o \"{ArtifactsDirectory / "CSharpCodingGuidelines.htm"}\" --self-contained", | ||
| ArtifactsDirectory / "Guidelines" | ||
| ).AssertZeroExitCode(); | ||
|
|
||
| ProcessTasks.StartProcess( | ||
| pandoc, | ||
| $"Cheatsheet.md -f markdown+markdown_in_html_blocks -s -o \"{ArtifactsDirectory / "CSharpCodingGuidelinesCheatsheet.htm"}\" --self-contained", | ||
| ArtifactsDirectory / "Cheatsheet" | ||
| ).AssertZeroExitCode(); | ||
| }); | ||
|
|
||
| Target JekyllBuild => _ => _ | ||
| .Executes(() => | ||
| { | ||
| ProcessTasks.StartProcess("bundle", "install", RootDirectory) | ||
| .AssertZeroExitCode(); | ||
|
|
||
| ProcessTasks.StartProcess("bundle", "exec jekyll build", RootDirectory) | ||
| .AssertZeroExitCode(); | ||
| }); | ||
|
|
||
| Target Default => _ => _ | ||
| .DependsOn(BuildHtml); | ||
|
|
||
| static string ExtractFrontmatterField(string content, string fieldName) | ||
| { | ||
| var match = Regex.Match(content, $@"---(.|\n)*{Regex.Escape(fieldName)}\: (.+)"); | ||
| return match.Success ? match.Groups[2].Value.Trim() : string.Empty; | ||
| } | ||
|
|
||
| static string RemoveFrontmatter(string content) => | ||
| Regex.Replace(content, @"---\r?\n(.|\r?\n)+?---\r?\n", string.Empty); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
|
|
||
| <PropertyGroup> | ||
| <OutputType>Exe</OutputType> | ||
| <TargetFramework>net8.0</TargetFramework> | ||
| <RootNamespace>_build</RootNamespace> | ||
| <Nullable>enable</Nullable> | ||
| <IsPackable>false</IsPackable> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <PackageReference Include="Nuke.Common" Version="8.1.4" /> | ||
| </ItemGroup> | ||
|
|
||
| </Project> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I want this to be part of the Nuke script as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in commit 509774e. Added a
JekyllBuildtarget tobuild/Build.cs:The
jekyllCI job now invokes this via./build.sh JekyllBuildinstead of callingbundle exec jekyll builddirectly.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot Then you can remove it from the build.yml, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in commit f16399c.
ruby/setup-ruby@v1is removed from the workflow. Thejekylljob now just does checkout +./build.sh JekyllBuild.