diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..4195b0ff4 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,207 @@ +name: Build + +on: + push: + branches: [ main ] + tags: [ 'v[0-9]+.[0-9]+.[0-9]+*' ] + +jobs: + unit-tests: + name: "Unit tests" + strategy: + matrix: + os: [ ubuntu-latest, windows-latest, macos-latest ] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Setup .NET SDKs + uses: actions/setup-dotnet@v4 + with: + dotnet-version: | + 6.0.x + 7.0.x + 8.0.x + 9.0.x + - name: Run unit tests (windows) + if: matrix.os == 'windows-latest' + run: ./build.ps1 CodeCoverage + - name: Run unit tests (ubuntu|macos) + if: matrix.os != 'windows-latest' + run: ./build.sh CodeCoverage + - name: Upload artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: ${{ runner.os }}-artifacts + path: | + ./Artifacts/* + ./TestResults/*.trx + + api-tests: + name: "API tests" + runs-on: ubuntu-latest + env: + DOTNET_NOLOGO: true + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Setup .NET SDKs + uses: actions/setup-dotnet@v4 + with: + dotnet-version: | + 6.0.x + 7.0.x + 8.0.x + 9.0.x + - name: API checks + run: ./build.sh ApiChecks + - name: Upload artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: API-tests + path: | + ./Artifacts/* + ./TestResults/*.trx + + static-code-analysis: + name: "Static code analysis" + runs-on: ubuntu-latest + env: + REPORTGENERATOR_LICENSE: ${{ secrets.REPORTGENERATOR_LICENSE }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + DOTNET_NOLOGO: true + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Setup .NET SDKs + uses: actions/setup-dotnet@v4 + with: + dotnet-version: | + 6.0.x + 7.0.x + 8.0.x + 9.0.x + - name: Run sonarcloud analysis + run: ./build.sh CodeAnalysis + + publish-test-results: + name: "Publish Tests Results" + needs: [ api-tests, unit-tests ] + runs-on: ubuntu-latest + permissions: + checks: write + pull-requests: write + if: always() + steps: + - name: Download Artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + - name: Publish Test Results + uses: EnricoMi/publish-unit-test-result-action@v2 + with: + comment_mode: always + files: "artifacts/**/**/*.trx" + + pack: + name: "Pack" + runs-on: ubuntu-latest + needs: [ publish-test-results, static-code-analysis ] + env: + DOTNET_NOLOGO: true + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Setup .NET SDKs + uses: actions/setup-dotnet@v4 + with: + dotnet-version: | + 6.0.x + 7.0.x + 8.0.x + 9.0.x + - name: Pack nuget packages + run: ./build.sh Pack + - name: Upload packages + if: always() + uses: actions/upload-artifact@v4 + with: + path: Artifacts/Packages + name: Packages + + push: + name: "Push" + if: ${{ startsWith(github.ref, 'refs/tags/v') }} + runs-on: macos-latest + environment: production + needs: [ pack ] + permissions: + contents: write + steps: + - name: Download packages + uses: actions/download-artifact@v4 + with: + name: Packages + path: Artifacts/Packages + - name: Publish + run: | + echo "Found the following packages to push:" + search_dir=Artifacts/Packages + for entry in Artifacts/Packages/*.nupkg + do + echo "- $entry" + done + for entry in Artifacts/Packages/*.nupkg + do + nuget push $entry -Source 'https://api.nuget.org/v3/index.json' -ApiKey ${{secrets.NUGET_API_KEY}} -SkipDuplicate + done + - name: Check pre-release + id: check-pre-release + run: | + if [[ ${{ github.event.ref }} =~ ^refs/tags/v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "release=true" >> $GITHUB_OUTPUT + fi + - name: Create GitHub release + if: steps.check-pre-release.outputs.release == 'true' + continue-on-error: true + uses: softprops/action-gh-release@v2 + with: + name: ${{ steps.tag.outputs.release_version }} + tag_name: ${{ steps.tag.outputs.release_version }} + token: ${{ secrets.GITHUB_TOKEN }} + generate_release_notes: true + + finalize-release: + name: "Finalize release" + if: startsWith(github.ref, 'refs/tags/v') + runs-on: ubuntu-latest + needs: [ push ] + permissions: + contents: read + issues: write + pull-requests: write + steps: + - name: Check pre-release + id: check-pre-release + run: | + if [[ ${{ github.event.ref }} =~ ^refs/tags/v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "release=true" >> $GITHUB_OUTPUT + fi + - name: Comment relevant issues and pull requests + if: steps.check-pre-release.outputs.release == 'true' + continue-on-error: true + uses: apexskier/github-release-commenter@v1.3.6 + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + comment-template: | + This is addressed in release {release_link}. + label-template: | + state: released + skip-label: | + state: released diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 638b2c4cb..fb1cd2dc1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,23 +1,22 @@ -name: Continuous Integration +name: CI + on: - push: - branches: [main] + workflow_dispatch: pull_request: - branches: [main] + branches: [ main ] + jobs: - test: - name: Test + unit-tests: + name: "Unit tests" strategy: - fail-fast: false matrix: - os: [ubuntu-latest, windows-latest, macos-latest] + os: [ ubuntu-latest, windows-latest, macos-latest ] runs-on: ${{ matrix.os }} steps: - - name: Checkout sources - uses: actions/checkout@v4 + - uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Setup .NET + - name: Setup .NET SDKs uses: actions/setup-dotnet@v4 with: dotnet-version: | @@ -25,22 +24,31 @@ jobs: 7.0.x 8.0.x 9.0.x - - name: Run tests - run: dotnet test --filter FullyQualifiedName\!~TestableIO.System.IO.Abstractions.Api.Tests --collect:"XPlat Code Coverage" --logger "GitHubActions" - - name: Upload coverage + - name: Run unit tests (windows) + if: matrix.os == 'windows-latest' + run: ./build.ps1 CodeCoverage + - name: Run unit tests (ubuntu|macos) + if: matrix.os != 'windows-latest' + run: ./build.sh CodeCoverage + - name: Upload artifacts + if: always() uses: actions/upload-artifact@v4 with: - name: Code coverage ${{ matrix.os }} - path: "**/coverage.cobertura.xml" - api-test: - name: API Test + name: ${{ runner.os }}-artifacts + path: | + ./Artifacts/* + ./TestResults/*.trx + + api-tests: + name: "API tests" runs-on: ubuntu-latest + env: + DOTNET_NOLOGO: true steps: - - name: Checkout sources - uses: actions/checkout@v4 + - uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Setup .NET + - name: Setup .NET SDKs uses: actions/setup-dotnet@v4 with: dotnet-version: | @@ -48,106 +56,55 @@ jobs: 7.0.x 8.0.x 9.0.x - - name: Build solution - run: dotnet build - - name: Run tests - run: dotnet test --filter FullyQualifiedName~TestableIO.System.IO.Abstractions.Api.Tests --logger "GitHubActions" - coverage: - name: Coverage - needs: [test, api-test] - runs-on: ubuntu-latest - steps: - - name: Checkout sources - uses: actions/checkout@v4 - - name: Setup .NET - uses: actions/setup-dotnet@v4 - - uses: actions/download-artifact@v4 - with: - name: Code coverage ubuntu-latest - path: coverage-ubuntu - - uses: actions/download-artifact@v4 - with: - name: Code coverage windows-latest - path: coverage-windows - - uses: actions/download-artifact@v4 - with: - name: Code coverage macos-latest - path: coverage-macos - - name: Generate coverage report - uses: danielpalme/ReportGenerator-GitHub-Action@v5.4.4 - with: - reports: "**/coverage.cobertura.xml" - targetdir: "coverage-report" - reporttypes: "Cobertura" - - name: Publish coverage report to Codacy - uses: codacy/codacy-coverage-reporter-action@master - if: github.repository == 'TestableIO/System.IO.Abstractions' && github.event_name == 'push' + - name: API checks + run: ./build.sh ApiChecks + - name: Upload artifacts + if: always() + uses: actions/upload-artifact@v4 with: - project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} - coverage-reports: coverage-report/Cobertura.xml - pack: - name: Pack - needs: [test, api-test] + name: API-tests + path: | + ./Artifacts/* + ./TestResults/*.trx + + static-code-analysis: + name: "Static code analysis" + if: ${{ github.actor != 'dependabot[bot]' }} runs-on: ubuntu-latest + env: + REPORTGENERATOR_LICENSE: ${{ secrets.REPORTGENERATOR_LICENSE }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + DOTNET_NOLOGO: true steps: - - name: Checkout sources - uses: actions/checkout@v4 + - uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Setup .NET + - name: Setup .NET SDKs uses: actions/setup-dotnet@v4 - - name: Create packages - run: dotnet pack --configuration Release --output ./packages - - name: Upload a Build Artifact - uses: actions/upload-artifact@v4 with: - name: NuGet packages - path: packages/*.* - deploy: - name: Deploy - if: | - github.ref == 'refs/heads/main' && - github.event_name == 'push' && - ( - startsWith(github.event.head_commit.message, 'feat:') || - startsWith(github.event.head_commit.message, 'feat!:') || - startsWith(github.event.head_commit.message, 'fix:') || - startsWith(github.event.head_commit.message, 'fix!:') || - contains(github.event.head_commit.message, 'breaking change') - ) - needs: [pack] + dotnet-version: | + 6.0.x + 7.0.x + 8.0.x + 9.0.x + - name: Run sonarcloud analysis + run: ./build.sh CodeAnalysis + + publish-test-results: + name: "Publish Tests Results" + needs: [ api-tests, unit-tests ] runs-on: ubuntu-latest + permissions: + checks: write + pull-requests: write + if: always() steps: - - name: Checkout sources - uses: actions/checkout@v4 + - name: Download Artifacts + uses: actions/download-artifact@v4 with: - fetch-depth: 0 - - name: Setup .NET - uses: actions/setup-dotnet@v4 - - uses: actions/download-artifact@v4 - with: - name: NuGet packages - path: packages - - name: Push packages - run: dotnet nuget push "packages/*.nupkg" --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate - - uses: dotnet/nbgv@v0.4.2 - id: nbgv - - name: Create GitHub release - uses: softprops/action-gh-release@v2 + path: artifacts + - name: Publish Test Results + uses: EnricoMi/publish-unit-test-result-action@v2 with: - name: v${{ steps.nbgv.outputs.SemVer2 }} - tag_name: v${{ steps.nbgv.outputs.SemVer2 }} - token: ${{ secrets.GITHUB_TOKEN }} - generate_release_notes: true - - name: Wait for GitHub release to be available - run: sleep 30s - shell: bash - - name: Comment on relevant PRs and issues - uses: apexskier/github-release-commenter@v1.3.6 - with: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - comment-template: | - This is addressed in release {release_link}. - label-template: | - state: released - + comment_mode: always + files: "artifacts/**/**/*.trx" diff --git a/.gitignore b/.gitignore index 843a9fc23..2a6761c23 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +# Artifact and test results directories +/Artifacts +/TestResults + ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json new file mode 100644 index 000000000..47d67fd11 --- /dev/null +++ b/.nuke/build.schema.json @@ -0,0 +1,141 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "definitions": { + "Host": { + "type": "string", + "enum": [ + "AppVeyor", + "AzurePipelines", + "Bamboo", + "Bitbucket", + "Bitrise", + "GitHubActions", + "GitLab", + "Jenkins", + "Rider", + "SpaceAutomation", + "TeamCity", + "Terminal", + "TravisCI", + "VisualStudio", + "VSCode" + ] + }, + "ExecutableTarget": { + "type": "string", + "enum": [ + "ApiChecks", + "CalculateNugetVersion", + "Clean", + "CodeAnalysis", + "CodeAnalysisBegin", + "CodeAnalysisEnd", + "CodeCoverage", + "Compile", + "DotNetFrameworkUnitTests", + "DotNetUnitTests", + "Pack", + "Restore", + "UnitTests", + "UpdateReadme" + ] + }, + "Verbosity": { + "type": "string", + "description": "", + "enum": [ + "Verbose", + "Normal", + "Minimal", + "Quiet" + ] + }, + "NukeBuild": { + "properties": { + "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": { + "description": "Host for execution. Default is 'automatic'", + "$ref": "#/definitions/Host" + }, + "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": { + "$ref": "#/definitions/ExecutableTarget" + } + }, + "Target": { + "type": "array", + "description": "List of targets to be invoked. Default is '{default_target}'", + "items": { + "$ref": "#/definitions/ExecutableTarget" + } + }, + "Verbosity": { + "description": "Logging verbosity during build execution. Default is 'Normal'", + "$ref": "#/definitions/Verbosity" + } + } + } + }, + "allOf": [ + { + "properties": { + "Configuration": { + "type": "string", + "description": "Configuration to build - Default is 'Debug' (local) or 'Release' (server)", + "enum": [ + "Debug", + "Release" + ] + }, + "GithubToken": { + "type": "string", + "description": "Github Token" + }, + "Solution": { + "type": "string", + "description": "Path to a solution file that is automatically loaded" + }, + "SonarToken": { + "type": "string", + "description": "The key to push to sonarcloud", + "default": "Secrets must be entered via 'nuke :secrets [profile]'" + } + } + }, + { + "$ref": "#/definitions/NukeBuild" + } + ] +} diff --git a/.nuke/parameters.json b/.nuke/parameters.json new file mode 100644 index 000000000..629b58457 --- /dev/null +++ b/.nuke/parameters.json @@ -0,0 +1,4 @@ +{ + "$schema": "build.schema.json", + "Solution": "System.IO.Abstractions.sln" +} diff --git a/Directory.Build.props b/Directory.Build.props index a7a394986..0fce8c99e 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -8,6 +8,7 @@ $(MSBuildThisFileDirectory)StrongName.snk latest testing + CS1591 https://github.com/TestableIO/System.IO.Abstractions MIT README.md @@ -18,6 +19,12 @@ $(DefineConstants);FEATURE_PATH_SPAN;FEATURE_FILE_SPAN $(DefineConstants);FEATURE_SERIALIZABLE + + true + true + true + snupkg + runtime; build; native; contentfiles; analyzers diff --git a/Directory.Packages.props b/Directory.Packages.props index f771b06d0..ffdf1c746 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -25,4 +25,10 @@ + + + + + + diff --git a/Pipeline/.editorconfig b/Pipeline/.editorconfig new file mode 100644 index 000000000..31e43dcd8 --- /dev/null +++ b/Pipeline/.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/Pipeline/Build.ApiChecks.cs b/Pipeline/Build.ApiChecks.cs new file mode 100644 index 000000000..f99768d39 --- /dev/null +++ b/Pipeline/Build.ApiChecks.cs @@ -0,0 +1,28 @@ +using Nuke.Common; +using Nuke.Common.ProjectModel; +using Nuke.Common.Tooling; +using Nuke.Common.Tools.DotNet; +using static Nuke.Common.Tools.DotNet.DotNetTasks; + +// ReSharper disable AllUnderscoreLocalParameterName + +namespace Build; + +partial class Build +{ + Target ApiChecks => _ => _ + .DependsOn(Compile) + .Executes(() => + { + Project project = Solution.Tests.TestableIO_System_IO_Abstractions_Api_Tests; + + DotNetTest(s => s + .SetConfiguration(Configuration == Configuration.Debug ? "Debug" : "Release") + .SetProcessEnvironmentVariable("DOTNET_CLI_UI_LANGUAGE", "en-US") + .EnableNoBuild() + .SetResultsDirectory(TestResultsDirectory) + .CombineWith(cc => cc + .SetProjectFile(project) + .AddLoggers($"trx;LogFileName={project.Name}.trx")), completeOnFailure: true); + }); +} diff --git a/Pipeline/Build.CodeAnalysis.cs b/Pipeline/Build.CodeAnalysis.cs new file mode 100644 index 000000000..9f3e9b035 --- /dev/null +++ b/Pipeline/Build.CodeAnalysis.cs @@ -0,0 +1,42 @@ +using Nuke.Common; +using Nuke.Common.Tools.SonarScanner; + +// ReSharper disable AllUnderscoreLocalParameterName + +namespace Build; + +partial class Build +{ + [Parameter("The key to push to sonarcloud")] [Secret] readonly string SonarToken; + + Target CodeAnalysisBegin => _ => _ + .Unlisted() + .Before(Compile) + .Before(CodeCoverage) + .Executes(() => + { + SonarScannerTasks.SonarScannerBegin(s => s + .SetOrganization("testableio") + .SetProjectKey("TestableIO_System.IO.Abstractions") + .AddVSTestReports(TestResultsDirectory / "*.trx") + .AddOpenCoverPaths(TestResultsDirectory / "reports" / "OpenCover.xml") + .SetPullRequestOrBranchName(GitHubActions, GitVersion) + .SetVersion(GitVersion.SemVer) + .SetToken(SonarToken)); + }); + + Target CodeAnalysisEnd => _ => _ + .Unlisted() + .DependsOn(Compile) + .DependsOn(CodeCoverage) + .OnlyWhenDynamic(() => IsServerBuild) + .Executes(() => + { + SonarScannerTasks.SonarScannerEnd(s => s + .SetToken(SonarToken)); + }); + + Target CodeAnalysis => _ => _ + .DependsOn(CodeAnalysisBegin) + .DependsOn(CodeAnalysisEnd); +} diff --git a/Pipeline/Build.CodeCoverage.cs b/Pipeline/Build.CodeCoverage.cs new file mode 100644 index 000000000..890fd9cb5 --- /dev/null +++ b/Pipeline/Build.CodeCoverage.cs @@ -0,0 +1,25 @@ +using Nuke.Common; +using Nuke.Common.Tooling; +using Nuke.Common.Tools.ReportGenerator; +using static Nuke.Common.Tools.ReportGenerator.ReportGeneratorTasks; + +// ReSharper disable AllUnderscoreLocalParameterName + +namespace Build; + +partial class Build +{ + Target CodeCoverage => _ => _ + .DependsOn(UnitTests) + .Executes(() => + { + ReportGenerator(s => s + .SetProcessToolPath(NuGetToolPathResolver.GetPackageExecutable("ReportGenerator", "ReportGenerator.dll", + framework: "net8.0")) + .SetTargetDirectory(TestResultsDirectory / "reports") + .AddReports(TestResultsDirectory / "**/coverage.cobertura.xml") + .AddReportTypes(ReportTypes.OpenCover) + .AddFileFilters("-*.g.cs") + .SetAssemblyFilters("+TestableIO*")); + }); +} diff --git a/Pipeline/Build.Compile.cs b/Pipeline/Build.Compile.cs new file mode 100644 index 000000000..ba5da0b1c --- /dev/null +++ b/Pipeline/Build.Compile.cs @@ -0,0 +1,92 @@ +using System; +using System.Linq; +using Nuke.Common; +using Nuke.Common.IO; +using Nuke.Common.Tools.DotNet; +using Nuke.Common.Utilities; +using Nuke.Common.Utilities.Collections; +using Serilog; +using static Nuke.Common.Tools.DotNet.DotNetTasks; + +// ReSharper disable AllUnderscoreLocalParameterName + +namespace Build; + +partial class Build +{ + string BranchName; + string SemVer; + + Target CalculateNugetVersion => _ => _ + .Unlisted() + .Executes(() => + { + SemVer = GitVersion.SemVer; + BranchName = GitVersion.BranchName; + + if (GitHubActions?.IsPullRequest == true) + { + string buildNumber = GitHubActions.RunNumber.ToString(); + Console.WriteLine( + $"Branch spec is a pull request. Adding build number {buildNumber}"); + + SemVer = string.Join('.', GitVersion.SemVer.Split('.').Take(3).Union([buildNumber,])); + } + + Console.WriteLine($"SemVer = {SemVer}"); + }); + + Target Clean => _ => _ + .Unlisted() + .Before(Restore) + .Executes(() => + { + ArtifactsDirectory.CreateOrCleanDirectory(); + Log.Information("Cleaned {path}...", ArtifactsDirectory); + + TestResultsDirectory.CreateOrCleanDirectory(); + Log.Information("Cleaned {path}...", TestResultsDirectory); + }); + + Target Restore => _ => _ + .Unlisted() + .DependsOn(Clean) + .Executes(() => + { + DotNetRestore(s => s + .SetProjectFile(Solution) + .EnableNoCache() + .SetConfigFile(RootDirectory / "nuget.config")); + }); + + Target Compile => _ => _ + .DependsOn(Restore) + .DependsOn(CalculateNugetVersion) + .Executes(() => + { + string preRelease = "-CI"; + if (GitHubActions == null) + { + preRelease = "-DEV"; + } + else if (GitHubActions.Ref.StartsWith("refs/tags/", StringComparison.OrdinalIgnoreCase)) + { + int preReleaseIndex = GitHubActions.Ref.IndexOf('-'); + preRelease = preReleaseIndex > 0 ? GitHubActions.Ref[preReleaseIndex..] : ""; + } + + ReportSummary(s => s + .WhenNotNull(SemVer, (summary, semVer) => summary + .AddPair("Version", semVer + preRelease))); + + DotNetBuild(s => s + .SetProjectFile(Solution) + .SetConfiguration(Configuration) + .EnableNoLogo() + .EnableNoRestore() + .SetVersion(SemVer + preRelease) + .SetAssemblyVersion(GitVersion.AssemblySemVer) + .SetFileVersion(GitVersion.AssemblySemFileVer) + .SetInformationalVersion(GitVersion.InformationalVersion)); + }); +} diff --git a/Pipeline/Build.Pack.cs b/Pipeline/Build.Pack.cs new file mode 100644 index 000000000..30de44836 --- /dev/null +++ b/Pipeline/Build.Pack.cs @@ -0,0 +1,98 @@ +using System.IO; +using System.Linq; +using System.Text; +using Nuke.Common; +using Nuke.Common.IO; +using Nuke.Common.ProjectModel; +using Nuke.Common.Utilities; +using Nuke.Common.Utilities.Collections; +using static Serilog.Log; + +// ReSharper disable AllUnderscoreLocalParameterName + +namespace Build; + +partial class Build +{ + Target UpdateReadme => _ => _ + .DependsOn(Clean) + .Before(Compile) + .Executes(() => + { + string version = string.Join('.', GitVersion.SemVer.Split('.').Take(3)); + if (version.IndexOf('-') != -1) + { + version = version.Substring(0, version.IndexOf('-')); + } + + StringBuilder sb = new(); + string[] lines = File.ReadAllLines(Solution.Directory / "README.md"); + sb.AppendLine(lines.First()); + sb.AppendLine( + $"[![Changelog](https://img.shields.io/badge/Changelog-v{version}-blue)](https://github.com/TestableIO/System.IO.Abstractions/releases/tag/v{version})"); + foreach (string line in lines.Skip(1)) + { + if (line.StartsWith("[![Build](https://github.com/TestableIO/System.IO.Abstractions/actions/workflows/build.yml") || + line.StartsWith("[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure")) + { + continue; + } + + if (line.StartsWith("[![Coverage](https://sonarcloud.io/api/project_badges/measure")) + { + sb.AppendLine(line + .Replace(")", $"&branch=release/v{version})")); + continue; + } + + if (line.StartsWith("[![Mutation testing badge](https://img.shields.io/endpoint")) + { + sb.AppendLine(line + .Replace("%2Fmain)", $"%2Frelease%2Fv{version})") + .Replace("/main)", $"/release/v{version})")); + continue; + } + + sb.AppendLine(line); + } + + File.WriteAllText(ArtifactsDirectory / "README.md", sb.ToString()); + }); + + Target Pack => _ => _ + .DependsOn(UpdateReadme) + .DependsOn(Compile) + .Executes(() => + { + ReportSummary(s => s + .WhenNotNull(SemVer, (c, semVer) => c + .AddPair("Packed version", semVer))); + + AbsolutePath packagesDirectory = ArtifactsDirectory / "Packages"; + packagesDirectory.CreateOrCleanDirectory(); + + foreach (Project project in new[] + { + Solution.TestableIO_System_IO_Abstractions_Wrappers, + Solution.TestableIO_System_IO_Abstractions_TestingHelpers, + Solution.Meta.TestableIO_System_IO_Abstractions, + Solution.Meta.System_IO_Abstractions, + Solution.Meta.System_IO_Abstractions_TestingHelpers, + }) + { + foreach (string package in + Directory.EnumerateFiles(project.Directory / "bin", "*.nupkg", SearchOption.AllDirectories)) + { + File.Move(package, packagesDirectory / Path.GetFileName(package)); + Debug("Found nuget package: {PackagePath}", package); + } + + foreach (string symbolPackage in + Directory.EnumerateFiles(project.Directory / "bin", "*.snupkg", SearchOption.AllDirectories)) + { + File.Move(symbolPackage, packagesDirectory / Path.GetFileName(symbolPackage)); + Debug("Found symbol package: {PackagePath}", symbolPackage); + } + } + }); +} diff --git a/Pipeline/Build.UnitTest.cs b/Pipeline/Build.UnitTest.cs new file mode 100644 index 000000000..c88133073 --- /dev/null +++ b/Pipeline/Build.UnitTest.cs @@ -0,0 +1,79 @@ +using System.IO; +using System.Linq; +using Nuke.Common; +using Nuke.Common.IO; +using Nuke.Common.ProjectModel; +using Nuke.Common.Tooling; +using Nuke.Common.Tools.DotNet; +using Nuke.Common.Tools.NUnit; +using static Nuke.Common.Tools.DotNet.DotNetTasks; + +// ReSharper disable AllUnderscoreLocalParameterName + +namespace Build; + +partial class Build +{ + Target UnitTests => _ => _ + .DependsOn(DotNetFrameworkUnitTests) + .DependsOn(DotNetUnitTests); + + Project[] UnitTestProjects => + [ + Solution.Tests.TestableIO_System_IO_Abstractions_Wrappers_Tests, + Solution.Tests.TestableIO_System_IO_Abstractions_TestingHelpers_Tests, + Solution.Tests.TestableIO_System_IO_Abstractions_Parity_Tests, + ]; + + Target DotNetFrameworkUnitTests => _ => _ + .Unlisted() + .DependsOn(Compile) + .OnlyWhenDynamic(() => EnvironmentInfo.IsWin) + .Executes(() => + { + var testAssemblies = UnitTestProjects + .SelectMany(project => + project.Directory.GlobFiles( + $"bin/{(Configuration == Configuration.Debug ? "Debug" : "Release")}/net472/*.Tests.dll")) + .Select(p => p.ToString()) + .ToArray(); + + Assert.NotEmpty(testAssemblies.ToList()); + + foreach (var testAssembly in testAssemblies) + { + var currentDirectory = Path.GetDirectoryName(testAssembly); + + NUnitTasks.NUnit3(s => s + .SetInputFiles(testAssembly) + .SetProcessWorkingDirectory(currentDirectory) + ); + } + }); + + Target DotNetUnitTests => _ => _ + .Unlisted() + .DependsOn(Compile) + .Executes(() => + { + string[] excludedFrameworks = ["net48",]; + DotNetTest(s => s + .SetConfiguration(Configuration) + .SetProcessEnvironmentVariable("DOTNET_CLI_UI_LANGUAGE", "en-US") + .EnableNoBuild() + .SetDataCollector("XPlat Code Coverage") + .SetResultsDirectory(TestResultsDirectory) + .CombineWith( + UnitTestProjects, + (settings, project) => settings + .SetProjectFile(project) + .CombineWith( + project.GetTargetFrameworks()?.Except(excludedFrameworks), + (frameworkSettings, framework) => frameworkSettings + .SetFramework(framework) + .AddLoggers($"trx;LogFileName={project.Name}_{framework}.trx") + ) + ), completeOnFailure: true + ); + }); +} \ No newline at end of file diff --git a/Pipeline/Build.cs b/Pipeline/Build.cs new file mode 100644 index 000000000..10372f655 --- /dev/null +++ b/Pipeline/Build.cs @@ -0,0 +1,31 @@ +using Nuke.Common; +using Nuke.Common.CI.GitHubActions; +using Nuke.Common.IO; +using Nuke.Common.ProjectModel; +using Nuke.Common.Tools.GitVersion; + +namespace Build; + +[GitHubActions( + "Build", + GitHubActionsImage.UbuntuLatest, + AutoGenerate = false, + ImportSecrets = [nameof(GithubToken),] +)] +partial class Build : NukeBuild +{ + [Parameter("Configuration to build - Default is 'Debug' (local) or 'Release' (server)")] + readonly Configuration Configuration = IsLocalBuild ? Configuration.Debug : Configuration.Release; + + [Parameter("Github Token")] readonly string GithubToken; + + [Required] [GitVersion(Framework = "net8.0", NoCache = true, NoFetch = true)] readonly GitVersion GitVersion; + + [Solution(GenerateProjects = true)] readonly Solution Solution; + + AbsolutePath ArtifactsDirectory => RootDirectory / "Artifacts"; + AbsolutePath TestResultsDirectory => RootDirectory / "TestResults"; + GitHubActions GitHubActions => GitHubActions.Instance; + + public static int Main() => Execute(x => x.UnitTests, x => x.Pack); +} diff --git a/Pipeline/Build.csproj b/Pipeline/Build.csproj new file mode 100644 index 000000000..074ab05ea --- /dev/null +++ b/Pipeline/Build.csproj @@ -0,0 +1,31 @@ + + + + Exe + net8.0 + + CS0649;CS0169;CA1050;CA1822;CA2211;IDE1006 + .. + .. + 1 + false + + + + + + + + + + + + + + + + + + + + diff --git a/Pipeline/Build.csproj.DotSettings b/Pipeline/Build.csproj.DotSettings new file mode 100644 index 000000000..88a8824cd --- /dev/null +++ b/Pipeline/Build.csproj.DotSettings @@ -0,0 +1,31 @@ + + 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" /> + <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></Policy> + <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static fields (private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></Policy> + True + True + True + True + True + True + True + True + True + True diff --git a/Pipeline/BuildExtensions.cs b/Pipeline/BuildExtensions.cs new file mode 100644 index 000000000..cd3e593a6 --- /dev/null +++ b/Pipeline/BuildExtensions.cs @@ -0,0 +1,36 @@ +using System; +using Nuke.Common.CI.GitHubActions; +using Nuke.Common.Tools.GitVersion; +using Nuke.Common.Tools.SonarScanner; +using Serilog; + +namespace Build; + +public static class BuildExtensions +{ + public static SonarScannerBeginSettings SetPullRequestOrBranchName( + this SonarScannerBeginSettings settings, + GitHubActions gitHubActions, + GitVersion gitVersion) + { + if (gitHubActions?.IsPullRequest == true) + { + Log.Information("Use pull request analysis"); + return settings + .SetPullRequestKey(gitHubActions.PullRequestNumber.ToString()) + .SetPullRequestBranch(gitHubActions.Ref) + .SetPullRequestBase(gitHubActions.BaseRef); + } + + if (gitHubActions?.Ref.StartsWith("refs/tags/", StringComparison.OrdinalIgnoreCase) == true) + { + string version = gitHubActions.Ref.Substring("refs/tags/".Length); + string branchName = "release/" + version; + Log.Information("Use release branch analysis for '{BranchName}'", branchName); + return settings.SetBranchName(branchName); + } + + Log.Information("Use branch analysis for '{BranchName}'", gitVersion.BranchName); + return settings.SetBranchName(gitVersion.BranchName); + } +} diff --git a/Pipeline/Configuration.cs b/Pipeline/Configuration.cs new file mode 100644 index 000000000..9c08b1ae9 --- /dev/null +++ b/Pipeline/Configuration.cs @@ -0,0 +1,16 @@ +using System; +using System.ComponentModel; +using System.Linq; +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/Pipeline/Directory.Build.props b/Pipeline/Directory.Build.props new file mode 100644 index 000000000..e147d6352 --- /dev/null +++ b/Pipeline/Directory.Build.props @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/Pipeline/Directory.Build.targets b/Pipeline/Directory.Build.targets new file mode 100644 index 000000000..253260956 --- /dev/null +++ b/Pipeline/Directory.Build.targets @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/System.IO.Abstractions.sln b/System.IO.Abstractions.sln index 13f0b57a8..4014cdb00 100644 --- a/System.IO.Abstractions.sln +++ b/System.IO.Abstractions.sln @@ -1,3 +1,4 @@ + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.3.32825.248 @@ -24,18 +25,19 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestableIO.System.IO.Abstra EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_", "_", "{BBF7AD8D-5522-48C0-A906-00CBB72308A0}" ProjectSection(SolutionItems) = preProject + .gitignore = .gitignore Directory.Build.props = Directory.Build.props + Directory.Packages.props = Directory.Packages.props global.json = global.json README.md = README.md version.json = version.json - Directory.Packages.props = Directory.Packages.props - .gitignore = .gitignore EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{2BE9161B-A3F3-4511-81DB-DB1DCB6375C9}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{D905E09D-6DC3-4F7C-8E83-82FADAE2C9E5}" ProjectSection(SolutionItems) = preProject + .github\workflows\build.yml = .github\workflows\build.yml .github\workflows\ci.yml = .github\workflows\ci.yml .github\workflows\pr.yml = .github\workflows\pr.yml EndProjectSection @@ -54,6 +56,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{5971F640 tests\Directory.Build.props = tests\Directory.Build.props EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Build", "Pipeline\Build.csproj", "{DC39543E-0A17-4869-96C9-B876EB1BB117}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Pipeline", "Pipeline", "{8091BCA3-70F8-4506-9701-C3074E79A66D}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestableIO.System.IO.Abstractions.Parity.Tests", "tests\TestableIO.System.IO.Abstractions.Parity.Tests\TestableIO.System.IO.Abstractions.Parity.Tests.csproj", "{93B79631-E220-414B-8F0D-3885825DE1AC}" EndProject Global @@ -98,6 +103,8 @@ Global {7DE6AD74-E2B3-498E-90C5-DDF3188F333A}.Debug|Any CPU.Build.0 = Debug|Any CPU {7DE6AD74-E2B3-498E-90C5-DDF3188F333A}.Release|Any CPU.ActiveCfg = Release|Any CPU {7DE6AD74-E2B3-498E-90C5-DDF3188F333A}.Release|Any CPU.Build.0 = Release|Any CPU + {DC39543E-0A17-4869-96C9-B876EB1BB117}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DC39543E-0A17-4869-96C9-B876EB1BB117}.Release|Any CPU.ActiveCfg = Release|Any CPU {93B79631-E220-414B-8F0D-3885825DE1AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {93B79631-E220-414B-8F0D-3885825DE1AC}.Debug|Any CPU.Build.0 = Debug|Any CPU {93B79631-E220-414B-8F0D-3885825DE1AC}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -109,15 +116,16 @@ Global GlobalSection(NestedProjects) = preSolution {B7DA254D-496F-4C50-969C-CF925758E2ED} = {C078E0B6-9747-475F-A999-B9E775DF6643} {0103D32A-6124-4A1A-AD6C-30EB957F09B0} = {C078E0B6-9747-475F-A999-B9E775DF6643} + {015B3812-E01D-479C-895D-BDDF16E798CA} = {10F39E91-97F2-4812-9D2F-79BE18EC6B08} {7105D748-1253-409F-A624-4879412EF3C2} = {BCEC61BD-4941-41EC-975A-ACEFC7AC1780} + {1B8388D2-58A7-47B8-89EC-C5A94A0FEED5} = {C078E0B6-9747-475F-A999-B9E775DF6643} {919888D2-E37D-40E7-8AD0-600F9429316D} = {BCEC61BD-4941-41EC-975A-ACEFC7AC1780} {2BE9161B-A3F3-4511-81DB-DB1DCB6375C9} = {BBF7AD8D-5522-48C0-A906-00CBB72308A0} {D905E09D-6DC3-4F7C-8E83-82FADAE2C9E5} = {2BE9161B-A3F3-4511-81DB-DB1DCB6375C9} - {1B8388D2-58A7-47B8-89EC-C5A94A0FEED5} = {C078E0B6-9747-475F-A999-B9E775DF6643} {B66A0B3F-6A00-482E-99E2-27D8DECB075E} = {BBF7AD8D-5522-48C0-A906-00CBB72308A0} - {015B3812-E01D-479C-895D-BDDF16E798CA} = {10F39E91-97F2-4812-9D2F-79BE18EC6B08} {7DE6AD74-E2B3-498E-90C5-DDF3188F333A} = {BCEC61BD-4941-41EC-975A-ACEFC7AC1780} {5971F640-8BBA-4264-BCD6-60A97DD54B4A} = {BBF7AD8D-5522-48C0-A906-00CBB72308A0} + {DC39543E-0A17-4869-96C9-B876EB1BB117} = {8091BCA3-70F8-4506-9701-C3074E79A66D} {93B79631-E220-414B-8F0D-3885825DE1AC} = {BCEC61BD-4941-41EC-975A-ACEFC7AC1780} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution diff --git a/build.cmd b/build.cmd new file mode 100755 index 000000000..b08cc590f --- /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 000000000..8b218a560 --- /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\Pipeline\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 + +########################################################################### +# 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" + $env:PATH = "$DotNetDirectory;$env:PATH" +} + +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 000000000..46f2bac0a --- /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/Pipeline/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 + +########################################################################### +# 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" + export PATH="$DOTNET_DIRECTORY:$PATH" +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 -- "$@"