From 30b3f93d502b0d4ec830b03ce584c51c63290bab Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Wed, 5 Oct 2022 18:27:28 +0300 Subject: [PATCH] Use NUKE for build automation * NET 6.0 as test project target framework * Upgrade to latest Fluid.Core * Upgrade Newtonsoft.Json to non-vulnerable version --- .github/workflows/build.yml | 52 ++++++ .github/workflows/dotnet-core.yml | 23 --- .github/workflows/pr.yml | 56 +++++++ .github/workflows/publish-packages.yml | 90 ----------- .gitignore | 12 +- .nuke/build.schema.json | 124 +++++++++++++++ .nuke/parameters.json | 4 + FluentEmail.sln | 4 + build.cmd | 7 + build.ps1 | 74 +++++++++ build.sh | 67 ++++++++ build/.editorconfig | 11 ++ build/Build.CI.GitHubActions.cs | 28 ++++ build/Build.Publish.cs | 56 +++++++ build/Build.cs | 149 ++++++++++++++++++ build/Configuration.cs | 14 ++ build/Directory.Build.props | 8 + build/Directory.Build.targets | 8 + build/_build.csproj | 19 +++ build/_build.csproj.DotSettings | 27 ++++ src/Directory.Build.props | 4 +- .../FluentEmail.Liquid.csproj | 2 +- .../FluentEmail.Mailgun.csproj | 2 +- test/Directory.Build.props | 6 - .../FluentEmail.Core.Tests.csproj | 30 ++-- .../MailgunSenderTests.cs | 1 + .../FluentEmail.Liquid.Tests.csproj | 16 +- .../FluentEmail.Razor.Tests.csproj | 10 +- 28 files changed, 736 insertions(+), 168 deletions(-) create mode 100644 .github/workflows/build.yml delete mode 100644 .github/workflows/dotnet-core.yml create mode 100644 .github/workflows/pr.yml delete mode 100644 .github/workflows/publish-packages.yml create mode 100644 .nuke/build.schema.json create mode 100644 .nuke/parameters.json create mode 100755 build.cmd create mode 100644 build.ps1 create mode 100755 build.sh create mode 100644 build/.editorconfig create mode 100644 build/Build.CI.GitHubActions.cs create mode 100644 build/Build.Publish.cs create mode 100644 build/Build.cs create mode 100644 build/Configuration.cs create mode 100644 build/Directory.Build.props create mode 100644 build/Directory.Build.targets create mode 100644 build/_build.csproj create mode 100644 build/_build.csproj.DotSettings diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..c611527d --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,52 @@ +# ------------------------------------------------------------------------------ +# +# +# This code was generated. +# +# - To turn off auto-generation set: +# +# [GitHubActions (AutoGenerate = false)] +# +# - To trigger manual generation invoke: +# +# nuke --generate-configuration GitHubActions_build --host GitHubActions +# +# +# ------------------------------------------------------------------------------ + +name: build + +on: + push: + branches: + - master + - main + tags: + - 'v*.*.*' + paths: + - '**/*.*' + - '!**/*.md' + +jobs: + ubuntu-latest: + name: ubuntu-latest + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: 'Cache: .nuke/temp, ~/.nuget/packages' + uses: actions/cache@v3 + with: + path: | + .nuke/temp + ~/.nuget/packages + key: ${{ runner.os }}-${{ hashFiles('global.json', 'src/**/*.csproj', 'src/**/package.json') }} + - name: 'Run: Compile, Test, Pack, Publish' + run: ./build.cmd Compile Test Pack Publish + env: + NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} + MYGET_API_KEY: ${{ secrets.MYGET_API_KEY }} + - name: 'Publish: artifacts' + uses: actions/upload-artifact@v3 + with: + name: artifacts + path: artifacts diff --git a/.github/workflows/dotnet-core.yml b/.github/workflows/dotnet-core.yml deleted file mode 100644 index eb3d4556..00000000 --- a/.github/workflows/dotnet-core.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: Build & Test - -on: - pull_request: - branches: [ master ] - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - name: Setup .NET Core - uses: actions/setup-dotnet@v1 - with: - dotnet-version: 3.1.101 - - name: Install dependencies - run: dotnet restore - - name: Build - run: dotnet build --configuration Release --no-restore - - name: Test - run: dotnet test --no-restore --verbosity normal diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml new file mode 100644 index 00000000..2ae90d48 --- /dev/null +++ b/.github/workflows/pr.yml @@ -0,0 +1,56 @@ +# ------------------------------------------------------------------------------ +# +# +# This code was generated. +# +# - To turn off auto-generation set: +# +# [GitHubActions (AutoGenerate = false)] +# +# - To trigger manual generation invoke: +# +# nuke --generate-configuration GitHubActions_pr --host GitHubActions +# +# +# ------------------------------------------------------------------------------ + +name: pr + +on: + pull_request: + branches: + - master + - main + paths: + - '**/*.*' + - '!**/*.md' + +jobs: + windows-latest: + name: windows-latest + runs-on: windows-latest + steps: + - uses: actions/checkout@v3 + - name: 'Cache: .nuke/temp, ~/.nuget/packages' + uses: actions/cache@v3 + with: + path: | + .nuke/temp + ~/.nuget/packages + key: ${{ runner.os }}-${{ hashFiles('global.json', 'src/**/*.csproj', 'src/**/package.json') }} + - name: 'Run: Compile, Test, Pack' + run: ./build.cmd Compile Test Pack + ubuntu-latest: + name: ubuntu-latest + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: 'Cache: .nuke/temp, ~/.nuget/packages' + uses: actions/cache@v3 + with: + path: | + .nuke/temp + ~/.nuget/packages + key: ${{ runner.os }}-${{ hashFiles('global.json', 'src/**/*.csproj', 'src/**/package.json') }} + - name: 'Run: Compile, Test, Pack' + run: ./build.cmd Compile Test Pack diff --git a/.github/workflows/publish-packages.yml b/.github/workflows/publish-packages.yml deleted file mode 100644 index 485bbe27..00000000 --- a/.github/workflows/publish-packages.yml +++ /dev/null @@ -1,90 +0,0 @@ -name: Publish Packages - -on: - push: - branches: [ master ] - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - name: Setup .NET Core - uses: actions/setup-dotnet@v1 - with: - dotnet-version: 3.1.101 - - name: Install dependencies - run: dotnet restore - - name: Build - run: dotnet build --configuration Release --no-restore - - - name: Publish FluentEmail.Core - uses: brandedoutcast/publish-nuget@v2.5.2 - with: - PROJECT_FILE_PATH: src/FluentEmail.Core/FluentEmail.Core.csproj - VERSION_FILE_PATH: src/Directory.Build.props - NUGET_KEY: ${{secrets.NUGET_API_KEY}} - NUGET_SOURCE: https://api.nuget.org - INCLUDE_SYMBOLS: true - - name: Publish FluentEmail.Smtp - uses: brandedoutcast/publish-nuget@v2.5.2 - with: - PROJECT_FILE_PATH: src/Senders/FluentEmail.Smtp/FluentEmail.Smtp.csproj - VERSION_FILE_PATH: src/Directory.Build.props - NUGET_KEY: ${{secrets.NUGET_API_KEY}} - NUGET_SOURCE: https://api.nuget.org - INCLUDE_SYMBOLS: true - - name: Publish FluentEmail.Sendgrid - uses: brandedoutcast/publish-nuget@v2.5.2 - with: - PROJECT_FILE_PATH: src/Senders/FluentEmail.SendGrid/FluentEmail.SendGrid.csproj - VERSION_FILE_PATH: src/Directory.Build.props - NUGET_KEY: ${{secrets.NUGET_API_KEY}} - NUGET_SOURCE: https://api.nuget.org - INCLUDE_SYMBOLS: true - #- name: Publish FluentEmail.MailTrap - # uses: brandedoutcast/publish-nuget@v2.5.2 - # with: - # PROJECT_FILE_PATH: src/Senders/FluentEmail.Mailtrap/FluentEmail.Mailtrap.csproj - # NUGET_KEY: ${{secrets.NUGET_API_KEY}} - # NUGET_SOURCE: https://api.nuget.org - - name: Publish FluentEmail.MailKit - uses: brandedoutcast/publish-nuget@v2.5.2 - with: - PROJECT_FILE_PATH: src/Senders/FluentEmail.MailKit/FluentEmail.MailKit.csproj - VERSION_FILE_PATH: src/Directory.Build.props - NUGET_KEY: ${{secrets.NUGET_API_KEY}} - NUGET_SOURCE: https://api.nuget.org - INCLUDE_SYMBOLS: true - - name: Publish FluentEmail.Mailgun - uses: brandedoutcast/publish-nuget@v2.5.2 - with: - PROJECT_FILE_PATH: src/Senders/FluentEmail.Mailgun/FluentEmail.Mailgun.csproj - VERSION_FILE_PATH: src/Directory.Build.props - NUGET_KEY: ${{secrets.NUGET_API_KEY}} - NUGET_SOURCE: https://api.nuget.org - INCLUDE_SYMBOLS: true - - name: Publish FluentEmail.Razor - uses: brandedoutcast/publish-nuget@v2.5.2 - with: - PROJECT_FILE_PATH: src/Renderers/FluentEmail.Razor/FluentEmail.Razor.csproj - VERSION_FILE_PATH: src/Directory.Build.props - NUGET_KEY: ${{secrets.NUGET_API_KEY}} - NUGET_SOURCE: https://api.nuget.org - INCLUDE_SYMBOLS: true - - name: Publish FluentEmail.Liquid - uses: brandedoutcast/publish-nuget@v2.5.2 - with: - PROJECT_FILE_PATH: src/Renderers/FluentEmail.Liquid/FluentEmail.Liquid.csproj - VERSION_FILE_PATH: src/Directory.Build.props - NUGET_KEY: ${{secrets.NUGET_API_KEY}} - NUGET_SOURCE: https://api.nuget.org - INCLUDE_SYMBOLS: true - # This is currently maintained separately - #- name: Publish FluentEmail.Graph - # uses: brandedoutcast/publish-nuget@v2.5.2 - # with: - # PROJECT_FILE_PATH: src/Senders/FluentEmail.Graph/FluentEmail.Graph.csproj - # NUGET_KEY: ${{secrets.NUGET_API_KEY}} - # NUGET_SOURCE: https://api.nuget.org diff --git a/.gitignore b/.gitignore index 47a88458..e9b9b7f6 100644 --- a/.gitignore +++ b/.gitignore @@ -12,16 +12,8 @@ appsettings.development.json *.userprefs # Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -[Xx]64/ -[Xx]86/ -[Bb]uild/ -bld/ -[Bb]in/ -[Oo]bj/ +**/bin/** +**/obj/** # Visual Studio 2015 cache/options directory .vs/ diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json new file mode 100644 index 00000000..77854d44 --- /dev/null +++ b/.nuke/build.schema.json @@ -0,0 +1,124 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/build", + "title": "Build Schema", + "definitions": { + "build": { + "type": "object", + "properties": { + "Configuration": { + "type": "string", + "description": "Configuration to build - Default is 'Debug' (local) or 'Release' (server)", + "enum": [ + "Debug", + "Release" + ] + }, + "Continue": { + "type": "boolean", + "description": "Indicates to continue a previously failed build attempt" + }, + "Help": { + "type": "boolean", + "description": "Shows the help text for this build assembly" + }, + "Host": { + "type": "string", + "description": "Host for execution. Default is 'automatic'", + "enum": [ + "AppVeyor", + "AzurePipelines", + "Bamboo", + "Bitbucket", + "Bitrise", + "GitHubActions", + "GitLab", + "Jenkins", + "Rider", + "SpaceAutomation", + "TeamCity", + "Terminal", + "TravisCI", + "VisualStudio", + "VSCode" + ] + }, + "MyGetApiKey": { + "type": "string", + "default": "Secrets must be entered via 'nuke :secrets [profile]'" + }, + "NoLogo": { + "type": "boolean", + "description": "Disables displaying the NUKE logo" + }, + "NuGetApiKey": { + "type": "string", + "default": "Secrets must be entered via 'nuke :secrets [profile]'" + }, + "Partition": { + "type": "string", + "description": "Partition to use on CI" + }, + "Plan": { + "type": "boolean", + "description": "Shows the execution plan (HTML)" + }, + "Profile": { + "type": "array", + "description": "Defines the profiles to load", + "items": { + "type": "string" + } + }, + "Root": { + "type": "string", + "description": "Root directory during build execution" + }, + "Skip": { + "type": "array", + "description": "List of targets to be skipped. Empty list skips all dependencies", + "items": { + "type": "string", + "enum": [ + "Clean", + "Compile", + "Pack", + "Publish", + "Restore", + "Test" + ] + } + }, + "Solution": { + "type": "string", + "description": "Path to a solution file that is automatically loaded" + }, + "Target": { + "type": "array", + "description": "List of targets to be invoked. Default is '{default_target}'", + "items": { + "type": "string", + "enum": [ + "Clean", + "Compile", + "Pack", + "Publish", + "Restore", + "Test" + ] + } + }, + "Verbosity": { + "type": "string", + "description": "Logging verbosity during build execution. Default is 'Normal'", + "enum": [ + "Minimal", + "Normal", + "Quiet", + "Verbose" + ] + } + } + } + } +} diff --git a/.nuke/parameters.json b/.nuke/parameters.json new file mode 100644 index 00000000..59c4f855 --- /dev/null +++ b/.nuke/parameters.json @@ -0,0 +1,4 @@ +{ + "$schema": "./build.schema.json", + "Solution": "FluentEmail.sln" +} \ No newline at end of file diff --git a/FluentEmail.sln b/FluentEmail.sln index 7b3ce08e..036074d2 100644 --- a/FluentEmail.sln +++ b/FluentEmail.sln @@ -43,12 +43,16 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FluentEmail.Liquid", "src\R EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FluentEmail.Liquid.Tests", "test\FluentEmail.Liquid.Tests\FluentEmail.Liquid.Tests.csproj", "{C8063CBA-D8F3-467A-A75C-63843F0DE862}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", "build\_build.csproj", "{FBF3809D-D89B-4CE4-89D8-3B2A9B7B7F9D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FBF3809D-D89B-4CE4-89D8-3B2A9B7B7F9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FBF3809D-D89B-4CE4-89D8-3B2A9B7B7F9D}.Release|Any CPU.ActiveCfg = Release|Any CPU {386B8AB1-E99A-4E08-83BE-DD1B8C2EE876}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {386B8AB1-E99A-4E08-83BE-DD1B8C2EE876}.Debug|Any CPU.Build.0 = Debug|Any CPU {386B8AB1-E99A-4E08-83BE-DD1B8C2EE876}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/build.cmd b/build.cmd new file mode 100755 index 00000000..b08cc590 --- /dev/null +++ b/build.cmd @@ -0,0 +1,7 @@ +:; set -eo pipefail +:; SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) +:; ${SCRIPT_DIR}/build.sh "$@" +:; exit $? + +@ECHO OFF +powershell -ExecutionPolicy ByPass -NoProfile -File "%~dp0build.ps1" %* diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 00000000..c0c0e612 --- /dev/null +++ b/build.ps1 @@ -0,0 +1,74 @@ +[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_SKIP_FIRST_TIME_EXPERIENCE = 1 +$env:DOTNET_CLI_TELEMETRY_OPTOUT = 1 +$env:DOTNET_MULTILEVEL_LOOKUP = 0 + +########################################################################### +# EXECUTION +########################################################################### + +function ExecSafe([scriptblock] $cmd) { + & $cmd + if ($LASTEXITCODE) { exit $LASTEXITCODE } +} + +# 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" +} + +Write-Output "Microsoft (R) .NET SDK version $(& $env:DOTNET_EXE --version)" + +if (Test-Path env:NUKE_ENTERPRISE_TOKEN) { + & $env:DOTNET_EXE nuget remove source "nuke-enterprise" > $null + & $env:DOTNET_EXE nuget add source "https://f.feedz.io/nuke/enterprise/nuget" --name "nuke-enterprise" --username "PAT" --password $env:NUKE_ENTERPRISE_TOKEN > $null +} + +ExecSafe { & $env:DOTNET_EXE build $BuildProjectFile /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet } +ExecSafe { & $env:DOTNET_EXE run --project $BuildProjectFile --no-build -- $BuildArguments } diff --git a/build.sh b/build.sh new file mode 100755 index 00000000..2f10dcb5 --- /dev/null +++ b/build.sh @@ -0,0 +1,67 @@ +#!/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_SKIP_FIRST_TIME_EXPERIENCE=1 +export DOTNET_MULTILEVEL_LOOKUP=0 + +########################################################################### +# EXECUTION +########################################################################### + +function FirstJsonValue { + perl -nle 'print $1 if m{"'"$1"'": "([^"]+)",?}' <<< "${@:2}" +} + +# 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" +fi + +echo "Microsoft (R) .NET SDK version $("$DOTNET_EXE" --version)" + +if [[ ! -z ${NUKE_ENTERPRISE_TOKEN+x} && "$NUKE_ENTERPRISE_TOKEN" != "" ]]; then + "$DOTNET_EXE" nuget remove source "nuke-enterprise" &>/dev/null || true + "$DOTNET_EXE" nuget add source "https://f.feedz.io/nuke/enterprise/nuget" --name "nuke-enterprise" --username "PAT" --password "$NUKE_ENTERPRISE_TOKEN" --store-password-in-clear-text &>/dev/null || true +fi + +"$DOTNET_EXE" build "$BUILD_PROJECT_FILE" /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet +"$DOTNET_EXE" run --project "$BUILD_PROJECT_FILE" --no-build -- "$@" diff --git a/build/.editorconfig b/build/.editorconfig new file mode 100644 index 00000000..31e43dcd --- /dev/null +++ b/build/.editorconfig @@ -0,0 +1,11 @@ +[*.cs] +dotnet_style_qualification_for_field = false:warning +dotnet_style_qualification_for_property = false:warning +dotnet_style_qualification_for_method = false:warning +dotnet_style_qualification_for_event = false:warning +dotnet_style_require_accessibility_modifiers = never:warning + +csharp_style_expression_bodied_methods = true:silent +csharp_style_expression_bodied_properties = true:warning +csharp_style_expression_bodied_indexers = true:warning +csharp_style_expression_bodied_accessors = true:warning diff --git a/build/Build.CI.GitHubActions.cs b/build/Build.CI.GitHubActions.cs new file mode 100644 index 00000000..74d67155 --- /dev/null +++ b/build/Build.CI.GitHubActions.cs @@ -0,0 +1,28 @@ +using Nuke.Common.CI.GitHubActions; + +[GitHubActions( + "pr", + GitHubActionsImage.WindowsLatest, + GitHubActionsImage.UbuntuLatest, + OnPullRequestBranches = new[] { "master", "main" }, + OnPullRequestIncludePaths = new[] { "**/*.*" }, + OnPullRequestExcludePaths = new[] { "**/*.md" }, + PublishArtifacts = false, + InvokedTargets = new[] { nameof(Compile), nameof(Test), nameof(Pack) }, + CacheKeyFiles = new[] { "global.json", "src/**/*.csproj", "src/**/package.json" }), +] +[GitHubActions( + "build", + GitHubActionsImage.UbuntuLatest, + OnPushBranches = new[] { "master", "main" }, + OnPushTags = new[] { "v*.*.*" }, + OnPushIncludePaths = new[] { "**/*.*" }, + OnPushExcludePaths = new[] { "**/*.md" }, + PublishArtifacts = true, + InvokedTargets = new[] { nameof(Compile), nameof(Test), nameof(Pack), nameof(Publish) }, + ImportSecrets = new [] { "NUGET_API_KEY", "MYGET_API_KEY" }, + CacheKeyFiles = new[] { "global.json", "src/**/*.csproj", "src/**/package.json" }) +] +public partial class Build +{ +} diff --git a/build/Build.Publish.cs b/build/Build.Publish.cs new file mode 100644 index 00000000..dd477d3e --- /dev/null +++ b/build/Build.Publish.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; + +using Nuke.Common; +using Nuke.Common.Git; +using Nuke.Common.IO; +using Nuke.Common.Tooling; +using Nuke.Common.Tools.DotNet; + +using static Nuke.Common.Tools.DotNet.DotNetTasks; + +public partial class Build +{ + string NuGetSource => "https://api.nuget.org/v3/index.json"; + [Parameter] [Secret] string NuGetApiKey; + + string MyGetGetSource => "https://www.myget.org/F/fluentemail/api/v2/package"; + [Parameter] [Secret] string MyGetApiKey; + + string ApiKeyToUse => IsTaggedBuild ? NuGetApiKey : MyGetApiKey; + string SourceToUse => IsTaggedBuild ? NuGetSource : MyGetGetSource; + + Target Publish => _ => _ + .OnlyWhenDynamic(() => IsRunningOnLinux && (GitRepository.IsOnMainOrMasterBranch() || IsTaggedBuild)) + .DependsOn(Pack) + .Requires(() => NuGetApiKey, () => MyGetApiKey) + .Executes(() => + { + if (SourceToUse == MyGetGetSource && !string.IsNullOrWhiteSpace(MyGetApiKey)) + { + Serilog.Log.Warning("MyGet not configured, skipping publish"); + return; + } + + DotNetNuGetPush(_ => _ + .Apply(PushSettingsBase) + .Apply(PushSettings) + .CombineWith(PushPackageFiles, (_, v) => _ + .SetTargetPath(v)) + .Apply(PackagePushSettings), + PushDegreeOfParallelism, + PushCompleteOnFailure); + }); + + Configure PushSettingsBase => _ => _ + .SetSource(SourceToUse) + .SetApiKey(ApiKeyToUse) + .SetSkipDuplicate(true); + + Configure PushSettings => _ => _; + Configure PackagePushSettings => _ => _; + + IEnumerable PushPackageFiles => ArtifactsDirectory.GlobFiles("*.nupkg"); + + bool PushCompleteOnFailure => true; + int PushDegreeOfParallelism => 2; +} \ No newline at end of file diff --git a/build/Build.cs b/build/Build.cs new file mode 100644 index 00000000..02c5c5d0 --- /dev/null +++ b/build/Build.cs @@ -0,0 +1,149 @@ +using System; +using System.Linq; +using System.Runtime.InteropServices; +using System.Xml.Linq; +using Nuke.Common; +using Nuke.Common.Git; +using Nuke.Common.IO; +using Nuke.Common.ProjectModel; +using Nuke.Common.Tools.DotNet; +using Nuke.Common.Utilities.Collections; + +using static Nuke.Common.Tools.DotNet.DotNetTasks; + +partial class Build : NukeBuild +{ + /// Support plugins are available for: + /// - JetBrains ReSharper https://nuke.build/resharper + /// - JetBrains Rider https://nuke.build/rider + /// - Microsoft VisualStudio https://nuke.build/visualstudio + /// - Microsoft VSCode https://nuke.build/vscode + + public static int Main () => Execute(x => x.Compile); + + [Solution] readonly Solution Solution; + [GitRepository] readonly GitRepository GitRepository; + + [Parameter("Configuration to build - Default is 'Debug' (local) or 'Release' (server)")] + readonly Configuration Configuration = IsLocalBuild ? Configuration.Debug : Configuration.Release; + + AbsolutePath SourceDirectory => RootDirectory / "src"; + + AbsolutePath ArtifactsDirectory => RootDirectory / "artifacts"; + + bool IsTaggedBuild; + string VersionPrefix; + string VersionSuffix; + + static bool IsRunningOnLinux => RuntimeInformation.IsOSPlatform(OSPlatform.Linux); + + string DetermineVersionPrefix() + { + var versionPrefix = GitRepository.Tags.SingleOrDefault(x => x.StartsWith("v"))?[1..]; + if (!string.IsNullOrWhiteSpace(versionPrefix)) + { + IsTaggedBuild = true; + Serilog.Log.Information("Tag version {VersionPrefix} from Git found, using it as version prefix", VersionPrefix); + } + else + { + var propsDocument = XDocument.Parse((SourceDirectory / "Directory.Build.props").ReadAllText()); + versionPrefix = propsDocument.Element("Project").Element("PropertyGroup").Element("VersionPrefix").Value; + Serilog.Log.Information("Version prefix {VersionPrefix} read from Directory.Build.props", versionPrefix); + } + + return versionPrefix; + } + + protected override void OnBuildInitialized() + { + VersionPrefix = DetermineVersionPrefix(); + + VersionSuffix = !IsTaggedBuild + ? $"preview-{DateTime.UtcNow:yyyyMMdd-HHmm}" + : ""; + + if (IsLocalBuild) + { + VersionSuffix = $"dev-{DateTime.UtcNow:yyyyMMdd-HHmm}"; + } + + Serilog.Log.Information("BUILD SETUP"); + Serilog.Log.Information("Configuration:\t{Configuration}", Configuration); + Serilog.Log.Information("Version prefix:\t{VersionPrefix}", VersionPrefix); + Serilog.Log.Information("Version suffix:\t{VersionSuffix}", VersionSuffix); + Serilog.Log.Information("Tagged build:\t{IsTaggedBuild}", IsTaggedBuild); + } + + Target Clean => _ => _ + .Before(Restore) + .Executes(() => + { + SourceDirectory.GlobDirectories("**/bin", "**/obj").ForEach(x => x.DeleteDirectory()); + ArtifactsDirectory.CreateOrCleanDirectory(); + }); + + Target Restore => _ => _ + .Executes(() => + { + DotNetRestore(s => s + .SetProjectFile(Solution)); + }); + + Target Compile => _ => _ + .DependsOn(Restore) + .Executes(() => + { + DotNetBuild(s => s + .SetProjectFile(Solution) + .SetAssemblyVersion(VersionPrefix) + .SetFileVersion(VersionPrefix) + .SetInformationalVersion(VersionPrefix) + .SetConfiguration(Configuration) + .EnableNoRestore() + .SetDeterministic(IsServerBuild) + .SetContinuousIntegrationBuild(IsServerBuild) + ); + }); + + Target Test => _ => _ + .After(Compile) + .Executes(() => + { + DotNetTest(s => s + .SetProjectFile(Solution) + .SetConfiguration(Configuration) + .EnableNoRestore() + ); + }); + + Target Pack => _ => _ + .After(Test, Compile) + .Produces(ArtifactsDirectory / "*.*") + .Executes(() => + { + if (Configuration != Configuration.Release) + { + throw new InvalidOperationException("Cannot pack if compilation hasn't been done in Release mode, use --configuration Release"); + } + + var nugetVersion = VersionPrefix; + if (!string.IsNullOrWhiteSpace(VersionSuffix)) + { + nugetVersion += "-" + VersionSuffix; + } + + ArtifactsDirectory.CreateOrCleanDirectory(); + + DotNetPack(s => s + .SetAssemblyVersion(VersionPrefix) + .SetFileVersion(VersionPrefix) + .SetInformationalVersion(VersionPrefix) + .SetVersion(nugetVersion) + .SetConfiguration(Configuration) + .SetOutputDirectory(ArtifactsDirectory) + .SetDeterministic(IsServerBuild) + .SetContinuousIntegrationBuild(IsServerBuild) + ); + }); +} diff --git a/build/Configuration.cs b/build/Configuration.cs new file mode 100644 index 00000000..9b22a3bd --- /dev/null +++ b/build/Configuration.cs @@ -0,0 +1,14 @@ +using System.ComponentModel; +using Nuke.Common.Tooling; + +[TypeConverter(typeof(TypeConverter))] +public class Configuration : Enumeration +{ + public static Configuration Debug = new Configuration { Value = nameof(Debug) }; + public static Configuration Release = new Configuration { Value = nameof(Release) }; + + public static implicit operator string(Configuration configuration) + { + return configuration.Value; + } +} diff --git a/build/Directory.Build.props b/build/Directory.Build.props new file mode 100644 index 00000000..e147d635 --- /dev/null +++ b/build/Directory.Build.props @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/build/Directory.Build.targets b/build/Directory.Build.targets new file mode 100644 index 00000000..25326095 --- /dev/null +++ b/build/Directory.Build.targets @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/build/_build.csproj b/build/_build.csproj new file mode 100644 index 00000000..1ba62d5e --- /dev/null +++ b/build/_build.csproj @@ -0,0 +1,19 @@ + + + + Exe + net6.0 + + CS0649;CS0169 + .. + .. + 1 + true + true + + + + + + + diff --git a/build/_build.csproj.DotSettings b/build/_build.csproj.DotSettings new file mode 100644 index 00000000..7bc28484 --- /dev/null +++ b/build/_build.csproj.DotSettings @@ -0,0 +1,27 @@ + + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + Implicit + Implicit + ExpressionBody + 0 + NEXT_LINE + True + False + 120 + IF_OWNER_IS_SINGLE_LINE + WRAP_IF_LONG + False + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + True + True + True + True + True + True + True + True + True diff --git a/src/Directory.Build.props b/src/Directory.Build.props index e2b23164..6d86d5b6 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -9,8 +9,8 @@ https://github.com/lukencode/FluentEmail https://github.com/lukencode/FluentEmail MIT - 3.0.2 + 3.0.2 true true @@ -36,7 +36,7 @@ - + diff --git a/src/Renderers/FluentEmail.Liquid/FluentEmail.Liquid.csproj b/src/Renderers/FluentEmail.Liquid/FluentEmail.Liquid.csproj index dceefbab..6c650514 100644 --- a/src/Renderers/FluentEmail.Liquid/FluentEmail.Liquid.csproj +++ b/src/Renderers/FluentEmail.Liquid/FluentEmail.Liquid.csproj @@ -12,7 +12,7 @@ - + diff --git a/src/Senders/FluentEmail.Mailgun/FluentEmail.Mailgun.csproj b/src/Senders/FluentEmail.Mailgun/FluentEmail.Mailgun.csproj index 2ad7eff6..02782a0a 100644 --- a/src/Senders/FluentEmail.Mailgun/FluentEmail.Mailgun.csproj +++ b/src/Senders/FluentEmail.Mailgun/FluentEmail.Mailgun.csproj @@ -8,7 +8,7 @@ - + diff --git a/test/Directory.Build.props b/test/Directory.Build.props index b35672c9..8919cacb 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -4,12 +4,6 @@ false true - - - - - - diff --git a/test/FluentEmail.Core.Tests/FluentEmail.Core.Tests.csproj b/test/FluentEmail.Core.Tests/FluentEmail.Core.Tests.csproj index cc26ca67..ce07c919 100644 --- a/test/FluentEmail.Core.Tests/FluentEmail.Core.Tests.csproj +++ b/test/FluentEmail.Core.Tests/FluentEmail.Core.Tests.csproj @@ -1,24 +1,24 @@  - netcoreapp3.1 + net6.0 true - - + + + + + - - - - - - + + + @@ -26,16 +26,10 @@ - - PreserveNewest - + - - PreserveNewest - - - PreserveNewest - + + diff --git a/test/FluentEmail.Core.Tests/MailgunSenderTests.cs b/test/FluentEmail.Core.Tests/MailgunSenderTests.cs index 40cb8bf9..072b4390 100644 --- a/test/FluentEmail.Core.Tests/MailgunSenderTests.cs +++ b/test/FluentEmail.Core.Tests/MailgunSenderTests.cs @@ -7,6 +7,7 @@ namespace FluentEmail.Mailgun.Tests { + [Explicit("Requires a valid sender name and API key")] public class MailgunSenderTests { const string toEmail = "bentest1@mailinator.com"; diff --git a/test/FluentEmail.Liquid.Tests/FluentEmail.Liquid.Tests.csproj b/test/FluentEmail.Liquid.Tests/FluentEmail.Liquid.Tests.csproj index e2c5dcfc..78d329a6 100644 --- a/test/FluentEmail.Liquid.Tests/FluentEmail.Liquid.Tests.csproj +++ b/test/FluentEmail.Liquid.Tests/FluentEmail.Liquid.Tests.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1 + net6.0 true @@ -14,23 +14,17 @@ - - - + - - - - - - - + + + diff --git a/test/FluentEmail.Razor.Tests/FluentEmail.Razor.Tests.csproj b/test/FluentEmail.Razor.Tests/FluentEmail.Razor.Tests.csproj index 2b40ef07..7edafa8d 100644 --- a/test/FluentEmail.Razor.Tests/FluentEmail.Razor.Tests.csproj +++ b/test/FluentEmail.Razor.Tests/FluentEmail.Razor.Tests.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1 + net6.0 true @@ -23,16 +23,14 @@ - - - - - + + +