From e3c6c070fa485f4d946a60499d104f0e5a599662 Mon Sep 17 00:00:00 2001 From: Ville Penttinen Date: Sun, 3 Dec 2023 20:11:43 +0200 Subject: [PATCH] Add support for NUKE build --- .github/workflows/pull-request.yml | 88 +++++++++++++++++++ .nuke/build.schema.json | 135 +++++++++++++++++++++++++++++ .nuke/parameters.json | 4 + build.cmd | 7 ++ build.ps1 | 74 ++++++++++++++++ build.sh | 67 ++++++++++++++ build/.editorconfig | 11 +++ build/Build.cs | 118 +++++++++++++++++++++++++ build/Directory.Build.props | 8 ++ build/Directory.Build.targets | 8 ++ build/_build.csproj | 21 +++++ build/_build.csproj.DotSettings | 27 ++++++ visp-fs.sln | 4 + 13 files changed, 572 insertions(+) create mode 100644 .github/workflows/pull-request.yml create mode 100644 .nuke/build.schema.json create mode 100644 .nuke/parameters.json create mode 100755 build.cmd create mode 100755 build.ps1 create mode 100755 build.sh create mode 100644 build/.editorconfig create mode 100644 build/Build.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/pull-request.yml b/.github/workflows/pull-request.yml new file mode 100644 index 0000000..b9760f3 --- /dev/null +++ b/.github/workflows/pull-request.yml @@ -0,0 +1,88 @@ +# ------------------------------------------------------------------------------ +# +# +# This code was generated. +# +# - To turn off auto-generation set: +# +# [ExtendedGitHubActions (AutoGenerate = false)] +# +# - To trigger manual generation invoke: +# +# nuke --generate-configuration GitHubActions_pull-request --host GitHubActions +# +# +# ------------------------------------------------------------------------------ + +name: pull-request + +on: + pull_request: + branches: + - main + - develop + +jobs: + windows-latest: + name: windows-latest + runs-on: windows-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Setup dotnet + uses: actions/setup-dotnet@v3 + with: + dotnet-version: | + 8.x + - name: 'Cache: .nuke/temp, ~/.nuget/packages' + uses: actions/cache@v3 + with: + path: | + .nuke/temp + ~/.nuget/packages + key: ${{ runner.os }}-${{ hashFiles('**/global.json', '**/*.csproj', '**/Directory.Packages.props') }} + - name: 'Run: Test, InstallLinters, Lint' + run: ./build.cmd Test InstallLinters Lint + ubuntu-latest: + name: ubuntu-latest + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Setup dotnet + uses: actions/setup-dotnet@v3 + with: + dotnet-version: | + 8.x + - name: 'Cache: .nuke/temp, ~/.nuget/packages' + uses: actions/cache@v3 + with: + path: | + .nuke/temp + ~/.nuget/packages + key: ${{ runner.os }}-${{ hashFiles('**/global.json', '**/*.csproj', '**/Directory.Packages.props') }} + - name: 'Run: Test, InstallLinters, Lint' + run: ./build.cmd Test InstallLinters Lint + macos-latest: + name: macos-latest + runs-on: macos-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Setup dotnet + uses: actions/setup-dotnet@v3 + with: + dotnet-version: | + 8.x + - name: 'Cache: .nuke/temp, ~/.nuget/packages' + uses: actions/cache@v3 + with: + path: | + .nuke/temp + ~/.nuget/packages + key: ${{ runner.os }}-${{ hashFiles('**/global.json', '**/*.csproj', '**/Directory.Packages.props') }} + - name: 'Run: Test, InstallLinters, Lint' + run: ./build.cmd Test InstallLinters Lint diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json new file mode 100644 index 0000000..acb1301 --- /dev/null +++ b/.nuke/build.schema.json @@ -0,0 +1,135 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/build", + "title": "Build Schema", + "definitions": { + "build": { + "type": "object", + "properties": { + "Configuration": { + "type": "string", + "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" + ] + }, + "IgnoreFailedSources": { + "type": "boolean", + "description": "Ignore unreachable sources during Restore" + }, + "NoLogo": { + "type": "boolean", + "description": "Disables displaying the NUKE logo" + }, + "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": [ + "CheckCsharpier", + "CheckFantomas", + "Compile", + "Format", + "FormatCsharpier", + "FormatFantomas", + "InstallCsharpier", + "InstallFantomas", + "InstallLinters", + "Lint", + "Restore", + "RestoreTools", + "Test", + "ValidateFormat" + ] + } + }, + "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": [ + "CheckCsharpier", + "CheckFantomas", + "Compile", + "Format", + "FormatCsharpier", + "FormatFantomas", + "InstallCsharpier", + "InstallFantomas", + "InstallLinters", + "Lint", + "Restore", + "RestoreTools", + "Test", + "ValidateFormat" + ] + } + }, + "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 0000000..d79f357 --- /dev/null +++ b/.nuke/parameters.json @@ -0,0 +1,4 @@ +{ + "$schema": "./build.schema.json", + "Solution": "visp-fs.sln" +} diff --git a/build.cmd b/build.cmd new file mode 100755 index 0000000..b08cc59 --- /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 100755 index 0000000..c0c0e61 --- /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 0000000..2f10dcb --- /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 0000000..31e43dc --- /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.cs b/build/Build.cs new file mode 100644 index 0000000..b490d14 --- /dev/null +++ b/build/Build.cs @@ -0,0 +1,118 @@ +// Copyright 2023 Ville Penttinen +// Distributed under the MIT License. +// https://github.com/vipentti/visp-fs/blob/main/LICENSE.md + +using System.Collections.Generic; +using Nuke.Common; +using Nuke.Common.CI.GitHubActions; +using Nuke.Common.Execution; +using Nuke.Common.IO; +using Nuke.Common.ProjectModel; +using Nuke.Common.Tools.DotNet; +using Nuke.Common.Utilities.Collections; +using Nuke.Components; +using Vipentti.Nuke.Components; +using static Nuke.Common.Tools.DotNet.DotNetTasks; +using static Vipentti.Nuke.Components.StandardNames; + +[ExtendedGitHubActions( + "pull-request", + GitHubActionsImage.WindowsLatest, + GitHubActionsImage.UbuntuLatest, + GitHubActionsImage.MacOsLatest, + OnPullRequestBranches = new[] { MainBranch, DevelopBranch }, + PublishArtifacts = false, + FetchDepth = 0 // fetch full history + , SetupDotnetVersions = new[] + { + "8.x", + } + , InvokedTargets = new[] + { + nameof(ITest.Test), + nameof(IUseLinters.InstallLinters), + nameof(IUseLinters.Lint), + })] +[DisableDefaultOutputForHost(DefaultOutput.Logo)] +class Build : NukeBuild, IUseDotNetFormat, IUseCsharpier, IUseFantomas, IUseLinters, IHazSolution, ITest, ICompile +{ + public T From() + where T : INukeBuild => (T)(object)this; + + public static int Main() => Execute(x => x.From().Compile); + + public Solution CurrentSolution => From().Solution; + + public IEnumerable TestProjects => CurrentSolution.GetAllProjects("*Tests*"); + + bool IUseCsharpier.UseGlobalTool => false; + bool IUseFantomas.UseGlobalTool => false; + + IEnumerable IUseFantomas.DirectoriesToFormat => new[] + { + RootDirectory / "src", + RootDirectory / "tests", + }; + + IEnumerable IUseLinters.Linters => new IProvideLinter[] + { + // From().Linter, + From().Linter, + From().Linter, + }; + + // csharpier-ignore + public Target RestoreTools => _ => _ + .Before(it => it.Restore) + .DependentFor(it => it.InstallLinters) + .Executes(() => DotNetToolRestore()) + ; +} + +public interface IUseFantomas : INukeBuild +{ + bool UseGlobalTool { get; } + + IEnumerable DirectoriesToFormat => new[] { RootDirectory }; + + // csharpier-ignore + Target CheckFantomas => _ => _ + .Executes(() => RunFantomas(check: true)); + + // csharpier-ignore + Target InstallFantomas => _ => _ + .OnlyWhenDynamic(() => UseGlobalTool) + .Executes(ExecuteInstallGlobalFantomas); + + // csharpier-ignore + Target FormatFantomas => _ => _ + .TryBefore(x => x.Format) + .TryDependentFor(x => x.Format) + .Executes(() => RunFantomas(check: false)); + + sealed IProvideLinter Linter => + new LinterDelegate(ExecuteInstallGlobalFantomas, () => RunFantomas(check: true)); + + sealed void ExecuteInstallGlobalFantomas() + { + if (UseGlobalTool) + { + DotNetToolUpdate(_ => _.SetGlobal(true).SetPackageName("fantomas")); + } + } + + sealed void RunFantomas(bool check) + { + var toolname = UseGlobalTool ? "fantomas" : "tool run fantomas"; + + DirectoriesToFormat.ForEach(RunFormat); + + void RunFormat(AbsolutePath path) + { + DotNet( + arguments: $"{toolname} {path}" + (check ? " --check" : ""), + logInvocation: true + ); + } + } +} diff --git a/build/Directory.Build.props b/build/Directory.Build.props new file mode 100644 index 0000000..e147d63 --- /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 0000000..2532609 --- /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 0000000..da1a87a --- /dev/null +++ b/build/_build.csproj @@ -0,0 +1,21 @@ + + + + Exe + net6.0 + + 11.0 + CS0649;CS0169;CA1050;CA1822;CA2211;IDE1006 + .. + .. + 1 + false + + + + + + + + + diff --git a/build/_build.csproj.DotSettings b/build/_build.csproj.DotSettings new file mode 100644 index 0000000..a778f33 --- /dev/null +++ b/build/_build.csproj.DotSettings @@ -0,0 +1,27 @@ + + DO_NOT_SHOW + 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 diff --git a/visp-fs.sln b/visp-fs.sln index 368abc1..09e9aa9 100644 --- a/visp-fs.sln +++ b/visp-fs.sln @@ -17,6 +17,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{CC30ECC0 EndProject Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Visp.Compiler.UnitTests", "tests\Visp.Compiler.UnitTests\Visp.Compiler.UnitTests.fsproj", "{9DF27262-A620-4290-A864-6B54F5311C6F}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", "build\_build.csproj", "{7E06E033-801D-49A7-9E9D-7BDB8AC83739}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -26,6 +28,8 @@ Global HideSolutionNode = FALSE EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7E06E033-801D-49A7-9E9D-7BDB8AC83739}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7E06E033-801D-49A7-9E9D-7BDB8AC83739}.Release|Any CPU.ActiveCfg = Release|Any CPU {908A9DB2-432F-4EB5-AB57-ECE7EA7BF2C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {908A9DB2-432F-4EB5-AB57-ECE7EA7BF2C0}.Debug|Any CPU.Build.0 = Debug|Any CPU {908A9DB2-432F-4EB5-AB57-ECE7EA7BF2C0}.Release|Any CPU.ActiveCfg = Release|Any CPU