From 0dad74819ef88444ab2bc2d912e6fddc18994ce1 Mon Sep 17 00:00:00 2001 From: Smaug123 <3138005+Smaug123@users.noreply.github.com> Date: Wed, 2 Oct 2024 23:25:20 +0100 Subject: [PATCH] Initial MVP --- .config/dotnet-tools.json | 18 + .editorconfig | 40 ++ .github/workflows/dotnet.yaml | 187 ++++++ .gitignore | 14 + CONTRIBUTING.md | 55 ++ Directory.Build.props | 19 + LICENSE | 21 + README.md | 13 + WoofWare.Whippet.Core/Domain.fs | 28 + WoofWare.Whippet.Core/README.md | 19 + WoofWare.Whippet.Core/SurfaceBaseline.txt | 11 + .../WoofWare.Whippet.Core.fsproj | 33 + WoofWare.Whippet.Core/version.json | 11 + WoofWare.Whippet.Test/TestSurface.fs | 28 + .../WoofWare.Whippet.Test.fsproj | 26 + WoofWare.Whippet.sln | 28 + WoofWare.Whippet/Program.fs | 175 ++++++ WoofWare.Whippet/WoofWare.Whippet.fsproj | 39 ++ WoofWare.Whippet/version.json | 12 + analyzers/analyzers.fsproj | 16 + flake.lock | 61 ++ flake.nix | 68 +++ nix/deps.nix | 569 ++++++++++++++++++ 23 files changed, 1491 insertions(+) create mode 100644 .config/dotnet-tools.json create mode 100644 .editorconfig create mode 100644 .github/workflows/dotnet.yaml create mode 100644 .gitignore create mode 100644 CONTRIBUTING.md create mode 100644 Directory.Build.props create mode 100644 LICENSE create mode 100644 README.md create mode 100644 WoofWare.Whippet.Core/Domain.fs create mode 100644 WoofWare.Whippet.Core/README.md create mode 100644 WoofWare.Whippet.Core/SurfaceBaseline.txt create mode 100644 WoofWare.Whippet.Core/WoofWare.Whippet.Core.fsproj create mode 100644 WoofWare.Whippet.Core/version.json create mode 100644 WoofWare.Whippet.Test/TestSurface.fs create mode 100644 WoofWare.Whippet.Test/WoofWare.Whippet.Test.fsproj create mode 100644 WoofWare.Whippet.sln create mode 100644 WoofWare.Whippet/Program.fs create mode 100644 WoofWare.Whippet/WoofWare.Whippet.fsproj create mode 100644 WoofWare.Whippet/version.json create mode 100644 analyzers/analyzers.fsproj create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 nix/deps.nix diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 0000000..383189c --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,18 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "fantomas": { + "version": "6.3.15", + "commands": [ + "fantomas" + ] + }, + "fsharp-analyzers": { + "version": "0.27.0", + "commands": [ + "fsharp-analyzers" + ] + } + } +} \ No newline at end of file diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..e207c42 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,40 @@ +root=true + +[*] +charset=utf-8 +trim_trailing_whitespace=true +insert_final_newline=true +indent_style=space +indent_size=4 + +# ReSharper properties +resharper_xml_indent_size=2 +resharper_xml_max_line_length=100 +resharper_xml_tab_width=2 + +[*.{csproj,fsproj,sqlproj,targets,props,ts,tsx,css,json}] +indent_style=space +indent_size=2 + +[*.{fs,fsi}] +fsharp_bar_before_discriminated_union_declaration=true +fsharp_space_before_uppercase_invocation=true +fsharp_space_before_class_constructor=true +fsharp_space_before_member=true +fsharp_space_before_colon=true +fsharp_space_before_semicolon=true +fsharp_multiline_bracket_style=aligned +fsharp_newline_between_type_definition_and_members=true +fsharp_align_function_signature_to_indentation=true +fsharp_alternative_long_member_definitions=true +fsharp_multi_line_lambda_closing_newline=true +fsharp_experimental_keep_indent_in_branch=true +fsharp_max_value_binding_width=80 +fsharp_max_record_width=0 +max_line_length=120 +end_of_line=lf + +[*.{appxmanifest,build,dtd,nuspec,xaml,xamlx,xoml,xsd}] +indent_style=space +indent_size=2 +tab_width=2 diff --git a/.github/workflows/dotnet.yaml b/.github/workflows/dotnet.yaml new file mode 100644 index 0000000..d61f4d6 --- /dev/null +++ b/.github/workflows/dotnet.yaml @@ -0,0 +1,187 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/SchemaStore/schemastore/master/src/schemas/json/github-workflow.json +name: .NET + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +env: + DOTNET_NOLOGO: true + DOTNET_CLI_TELEMETRY_OPTOUT: true + DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true + NUGET_XMLDOC_MODE: '' + DOTNET_MULTILEVEL_LOOKUP: 0 + +jobs: + build: + strategy: + matrix: + config: + - Release + - Debug + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # so that NerdBank.GitVersioning has access to history + - name: Install Nix + uses: cachix/install-nix-action@v29 + with: + extra_nix_config: | + access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} + - name: Restore dependencies + run: nix develop --command dotnet restore + - name: Build + run: nix develop --command dotnet build --no-restore --configuration ${{matrix.config}} + - name: Test + run: nix develop --command dotnet test --no-build --verbosity normal --configuration ${{matrix.config}} + + analyzers: + runs-on: ubuntu-latest + permissions: + security-events: write + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 # so that NerdBank.GitVersioning has access to history + - name: Install Nix + uses: cachix/install-nix-action@v29 + with: + extra_nix_config: | + access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} + - name: Prepare analyzers + run: nix develop --command dotnet restore analyzers/analyzers.fsproj + - name: Build project + run: nix develop --command dotnet build ./WoofWare.Whippet/WoofWare.Whippet.fsproj + - name: Run analyzers + run: nix run .#fsharp-analyzers -- --project ./WoofWare.Whippet/WoofWare.Whippet.fsproj --analyzers-path ./.analyzerpackages/g-research.fsharp.analyzers/*/ --verbosity detailed --report ./analysis.sarif --treat-as-error GRA-STRING-001 GRA-STRING-002 GRA-STRING-003 GRA-UNIONCASE-001 GRA-INTERPOLATED-001 GRA-TYPE-ANNOTATE-001 GRA-VIRTUALCALL-001 GRA-IMMUTABLECOLLECTIONEQUALITY-001 GRA-JSONOPTS-001 GRA-LOGARGFUNCFULLAPP-001 GRA-DISPBEFOREASYNC-001 --exclude-analyzers PartialAppAnalyzer + + build-nix: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install Nix + uses: cachix/install-nix-action@v29 + with: + extra_nix_config: | + access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} + - name: Build + run: nix build + - name: Reproducibility check + run: nix build --rebuild + + check-dotnet-format: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install Nix + uses: cachix/install-nix-action@v29 + with: + extra_nix_config: | + access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} + - name: Run Fantomas + run: nix run .#fantomas -- --check . + + check-nix-format: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install Nix + uses: cachix/install-nix-action@v29 + with: + extra_nix_config: | + access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} + - name: Run Alejandra + run: nix develop --command alejandra --check . + + linkcheck: + name: Check links + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: Install Nix + uses: cachix/install-nix-action@v29 + with: + extra_nix_config: | + access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} + - name: Run link checker + run: nix develop --command markdown-link-check README.md CONTRIBUTING.md + + flake-check: + name: Check flake + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: Install Nix + uses: cachix/install-nix-action@v29 + with: + extra_nix_config: | + access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} + - name: Flake check + run: nix flake check + + nuget-pack: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # so that NerdBank.GitVersioning has access to history + - name: Install Nix + uses: cachix/install-nix-action@v29 + with: + extra_nix_config: | + access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} + - name: Restore dependencies + run: nix develop --command dotnet restore + - name: Build + run: nix develop --command dotnet build --no-restore --configuration Release + - name: Pack + run: nix develop --command dotnet pack --configuration Release + - name: Upload NuGet artifact (runner) + uses: actions/upload-artifact@v4 + with: + name: nuget-package-runner + path: WoofWare.Whippet/bin/Release/WoofWare.Whippet.*.nupkg + - name: Upload NuGet artifact (core) + uses: actions/upload-artifact@v4 + with: + name: nuget-package-core + path: WoofWare.Whippet.Core/bin/Release/WoofWare.Whippet.Core.*.nupkg + + expected-pack: + needs: [nuget-pack] + runs-on: ubuntu-latest + steps: + - name: Download NuGet artifact (runner) + uses: actions/download-artifact@v4 + with: + name: nuget-package-runner + path: packed-runner + - name: Check NuGet contents + # Verify that there is exactly one nupkg in the artifact that would be NuGet published + run: if [[ $(find packed-runner -maxdepth 1 -name 'WoofWare.Whippet.*.nupkg' -printf c | wc -c) -ne "1" ]]; then exit 1; fi + - name: Download NuGet artifact (core) + uses: actions/download-artifact@v4 + with: + name: nuget-package-core + path: packed-core + - name: Check NuGet contents + # Verify that there is exactly one nupkg in the artifact that would be NuGet published + run: if [[ $(find packed-core -maxdepth 1 -name 'WoofWare.Whippet.Core.*.nupkg' -printf c | wc -c) -ne "1" ]]; then exit 1; fi + + all-required-checks-complete: + needs: [check-dotnet-format, check-nix-format, build, build-nix, linkcheck, flake-check, nuget-pack, expected-pack, analyzers] + if: ${{ always() }} + runs-on: ubuntu-latest + steps: + - uses: G-Research/common-actions/check-required-lite@2b7dc49cb14f3344fbe6019c14a31165e258c059 + with: + needs-context: ${{ toJSON(needs) }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e904e54 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +bin/ +obj/ +/packages/ +riderModule.iml +/_ReSharper.Caches/ +.idea/ +*.sln.DotSettings.user +.DS_Store +result +.analyzerpackages/ +analysis.sarif +.direnv/ +.venv/ +.vs/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..b8f3b16 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,55 @@ +# Contributing + +The main project fork lives [on GitHub](https://github.com/Smaug123/WoofWare.Whippet). + +Contributions are welcome, but I am generally very opinionated about both style and content. +I also can't commit to looking at anything in a particularly timely manner (or at all, though I expect I will try). + +In general my aesthetics lead me to accept correctness fixes much more readily than other changes. + +## Issues + +Please raise bug reports and feature requests as Issues on [the main GitHub project](https://github.com/Smaug123/WoofWare.Whippet/issues). + +## Pull requests + +Before embarking on a large change, I strongly recommend checking via a GitHub Issue first that I'm likely to accept it. + +You may find that the following guidelines will help you produce a change that I accept: + +* Keep your change as small and focused as is practical. +* Ensure that your change is thoroughly tested. +* Document any choices you make which are not immediately obvious. +* Explain why your change is necessary or desirable. + +## On your first checkout + +There are pull request checks on this repo, enforcing [Fantomas](https://github.com/fsprojects/fantomas/)-compliant formatting according to the [G-Research style guidelines](https://github.com/G-Research/fsharp-formatting-conventions/). +After checking out the repo, you may wish to add a pre-push hook to ensure locally that formatting is complete, rather than having to wait for the CI checks to tell you that you haven't formatted your code. +Consider performing the following command to set this up in the repo: +```bash +git config core.hooksPath hooks/ +``` +Before your first push (but only once), you will need to install the [.NET local tools](https://docs.microsoft.com/en-us/dotnet/core/tools/local-tools-how-to-use) which form part of the pre-push hook: +```bash +dotnet tool restore +``` + +In future, some commits (such as big-bang formatting commits) may be recorded for convenience in `.git-blame-ignore-revs`. +Consider performing the following command to have `git blame` ignore these commits, when we ever create any: +```bash +git config blame.ignoreRevsFile .git-blame-ignore-revs +``` + +## Dependencies + +I try to keep this repository's dependencies as few as possible, because (for example) any consumer of the source generator will also consume this project via the attributes. +When adding dependencies, you will need to `nix run .#fetchDeps` to obtain a new copy of [the dependency lockfile](./nix/deps.nix). + +## Branch strategy + +Releases are made from the `main` branch. + +## License + +This project is licensed with the MIT license, a copy of which you can find at the repository root. diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..4ea13b0 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,19 @@ + + + embedded + true + [UNDEFINED] + true + true + true + embedded + FS3388,FS3559 + + + + + + + true + + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..605abb0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Patrick Stevens + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..89c2554 --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# WoofWare.Whippet + +Whippet is a source generator for F#, inspired by [Myriad](https://github.com/MoiraeSoftware/myriad). + +It is currently vapourware; please do not use it. +With some modest changes to [WoofWare.Myriad.Plugins](https://github.com/Smaug123/WoofWare.Myriad/) I was able to use Whippet to generate source files in that repo. +However, it currently lacks any of Myriad's ease of invocation, and indeed any of the future features intended to distinguish Whippet from Myriad. + +Differentiating features: + +* Whippet expands the range of information available to a source-generating plugin. Eventually we intend for it to supply type-checking information. +* Whippet will eventually support the Fantomas [Oak](https://fsprojects.github.io/fantomas/docs/end-users/GeneratingCode.html) format, rather than just a plain AST. +* Whippet is intended to be more modular, providing various different helper assemblies a plugin author can optionally use depending on what features they want. diff --git a/WoofWare.Whippet.Core/Domain.fs b/WoofWare.Whippet.Core/Domain.fs new file mode 100644 index 0000000..90da65d --- /dev/null +++ b/WoofWare.Whippet.Core/Domain.fs @@ -0,0 +1,28 @@ +namespace WoofWare.Whippet.Core + +(* +These types should take no dependencies and should only change additively; otherwise consumers will break! +*) + +/// When decorating a type, indicates that the type contains Whippet generators. +/// +/// If you don't want to take a dependency on WoofWare.Whippet.Core, you can define your own attribute with this name, +/// and we'll detect it happily when running the plugin. +type WhippetGeneratorAttribute () = + inherit System.Attribute () + +/// The arguments we'll give you (a plugin) when we call you to generate some code from raw file input. +type RawSourceGenerationArgs = + { + /// Full path to the file, on disk, which you're taking as input. + FilePath : string + /// Contents of the file; you might want to `System.Text.Encoding.UTF8.GetString` this. + FileContents : byte[] + } + +/// We provide this interface as a helper to give you compile-time safety, but you don't have to use it. +/// At runtime, we'll find any member with the right name and signature. +/// You must use `RawSourceGenerationArgs`, though! +type IGenerateRawFromRaw = + /// Return `null` to indicate "I don't want to do any updates". + abstract member GenerateRawFromRaw : RawSourceGenerationArgs -> string diff --git a/WoofWare.Whippet.Core/README.md b/WoofWare.Whippet.Core/README.md new file mode 100644 index 0000000..1d834e1 --- /dev/null +++ b/WoofWare.Whippet.Core/README.md @@ -0,0 +1,19 @@ +# WoofWare.Whippet.Core + +This library defines the types you will need to create a plugin that works with the WoofWare.Whippet source generator, +as well as some types which you may find convenient for this purpose. + +To the greatest extent possible, WoofWare.Whippet is structured so that if you wish, you do not need to use this library. +However, there are some types you *must* use. + +## Mandatory types + +When defining any method which performs source generation, you must use the appropriate input type +(such as `RawSourceGenerationArgs`). +We try *very hard* to ensure we never break backward compatibility, so you should safely be able to use old versions of +WoofWare.Whippet.Core even with new versions of the WoofWare.Whippet generator. + +## Optional types + +* You must decorate your plugin types with an attribute named `[]`. We supply one you can use. +* We supply interfaces from which you can inherit, to ensure that you are providing members of the right type signature. diff --git a/WoofWare.Whippet.Core/SurfaceBaseline.txt b/WoofWare.Whippet.Core/SurfaceBaseline.txt new file mode 100644 index 0000000..e3ee1b6 --- /dev/null +++ b/WoofWare.Whippet.Core/SurfaceBaseline.txt @@ -0,0 +1,11 @@ +WoofWare.Whippet.Core.IGenerateRawFromRaw - interface with 1 member(s) +WoofWare.Whippet.Core.IGenerateRawFromRaw.GenerateRawFromRaw [method]: WoofWare.Whippet.Core.RawSourceGenerationArgs -> string +WoofWare.Whippet.Core.RawSourceGenerationArgs inherit obj, implements WoofWare.Whippet.Core.RawSourceGenerationArgs System.IEquatable, System.Collections.IStructuralEquatable, WoofWare.Whippet.Core.RawSourceGenerationArgs System.IComparable, System.IComparable, System.Collections.IStructuralComparable +WoofWare.Whippet.Core.RawSourceGenerationArgs..ctor [constructor]: (string, System.Byte []) +WoofWare.Whippet.Core.RawSourceGenerationArgs.Equals [method]: (WoofWare.Whippet.Core.RawSourceGenerationArgs, System.Collections.IEqualityComparer) -> bool +WoofWare.Whippet.Core.RawSourceGenerationArgs.FileContents [property]: [read-only] System.Byte [] +WoofWare.Whippet.Core.RawSourceGenerationArgs.FilePath [property]: [read-only] string +WoofWare.Whippet.Core.RawSourceGenerationArgs.get_FileContents [method]: unit -> System.Byte [] +WoofWare.Whippet.Core.RawSourceGenerationArgs.get_FilePath [method]: unit -> string +WoofWare.Whippet.Core.WhippetGeneratorAttribute inherit System.Attribute +WoofWare.Whippet.Core.WhippetGeneratorAttribute..ctor [constructor]: unit \ No newline at end of file diff --git a/WoofWare.Whippet.Core/WoofWare.Whippet.Core.fsproj b/WoofWare.Whippet.Core/WoofWare.Whippet.Core.fsproj new file mode 100644 index 0000000..68cc50b --- /dev/null +++ b/WoofWare.Whippet.Core/WoofWare.Whippet.Core.fsproj @@ -0,0 +1,33 @@ + + + + netstandard2.0 + true + Patrick Stevens + Copyright (c) Patrick Stevens 2024 + Core library types to allow you to use the WoofWare.Whippet source generator. + git + https://github.com/Smaug123/WoofWare.Whippet + MIT + README.md + fsharp;source-generator;source-gen + true + FS3559 + WoofWare.Whippet.Core + + + + + + + + True + \ + + + + + + + + diff --git a/WoofWare.Whippet.Core/version.json b/WoofWare.Whippet.Core/version.json new file mode 100644 index 0000000..790e212 --- /dev/null +++ b/WoofWare.Whippet.Core/version.json @@ -0,0 +1,11 @@ +{ + "version": "0.1", + "publicReleaseRefSpec": [ + "^refs/heads/main$" + ], + "pathFilters": [ + "./", + ":/global.json", + ":/Directory.Build.props" + ] +} diff --git a/WoofWare.Whippet.Test/TestSurface.fs b/WoofWare.Whippet.Test/TestSurface.fs new file mode 100644 index 0000000..163aadf --- /dev/null +++ b/WoofWare.Whippet.Test/TestSurface.fs @@ -0,0 +1,28 @@ +namespace WoofWare.Whippet.Test + +open NUnit.Framework +open WoofWare.Whippet.Core +open ApiSurface + +[] +module TestSurface = + + let coreAssembly = typeof.Assembly + + [] + let ``Ensure API surface has not been modified`` () = ApiSurface.assertIdentical coreAssembly + + (* + [] + // https://github.com/nunit/nunit3-vs-adapter/issues/876 + let CheckVersionAgainstRemote () = + MonotonicVersion.validate assembly "WoofWare.Myriad.Core" + *) + + [] + let ``Update API surface: core`` () = + ApiSurface.writeAssemblyBaseline coreAssembly + + [] + let ``Ensure public API is fully documented: core`` () = + DocCoverage.assertFullyDocumented coreAssembly diff --git a/WoofWare.Whippet.Test/WoofWare.Whippet.Test.fsproj b/WoofWare.Whippet.Test/WoofWare.Whippet.Test.fsproj new file mode 100644 index 0000000..751b43a --- /dev/null +++ b/WoofWare.Whippet.Test/WoofWare.Whippet.Test.fsproj @@ -0,0 +1,26 @@ + + + + net8.0 + + false + true + + + + + + + + + + + + + + + + + + + diff --git a/WoofWare.Whippet.sln b/WoofWare.Whippet.sln new file mode 100644 index 0000000..00c7d31 --- /dev/null +++ b/WoofWare.Whippet.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "WoofWare.Whippet", "WoofWare.Whippet\WoofWare.Whippet.fsproj", "{85B34488-847A-4784-8001-E4D1715F5ADD}" +EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "WoofWare.Whippet.Core", "WoofWare.Whippet.Core\WoofWare.Whippet.Core.fsproj", "{BFBE848C-E9C0-4152-B956-B8A25DFFDA9C}" +EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "WoofWare.Whippet.Test", "WoofWare.Whippet.Test\WoofWare.Whippet.Test.fsproj", "{A2E2A639-D17D-426D-B424-0139B4DBCF8F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {85B34488-847A-4784-8001-E4D1715F5ADD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {85B34488-847A-4784-8001-E4D1715F5ADD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {85B34488-847A-4784-8001-E4D1715F5ADD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {85B34488-847A-4784-8001-E4D1715F5ADD}.Release|Any CPU.Build.0 = Release|Any CPU + {BFBE848C-E9C0-4152-B956-B8A25DFFDA9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BFBE848C-E9C0-4152-B956-B8A25DFFDA9C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BFBE848C-E9C0-4152-B956-B8A25DFFDA9C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BFBE848C-E9C0-4152-B956-B8A25DFFDA9C}.Release|Any CPU.Build.0 = Release|Any CPU + {A2E2A639-D17D-426D-B424-0139B4DBCF8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A2E2A639-D17D-426D-B424-0139B4DBCF8F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A2E2A639-D17D-426D-B424-0139B4DBCF8F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A2E2A639-D17D-426D-B424-0139B4DBCF8F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/WoofWare.Whippet/Program.fs b/WoofWare.Whippet/Program.fs new file mode 100644 index 0000000..acdec5a --- /dev/null +++ b/WoofWare.Whippet/Program.fs @@ -0,0 +1,175 @@ +namespace WoofWare.Whippet + +open System +open System.IO +open System.Reflection +open Ionide.ProjInfo +open Ionide.ProjInfo.Types + +open WoofWare.Whippet.Core + +type Args = + { + PluginDll : FileInfo + InputFile : FileInfo + } + +type WhippetTarget = + { + InputSource : FileInfo + GeneratedDest : FileInfo + } + +module Program = + let parseArgs (argv : string array) = + let inputFile = argv.[0] |> FileInfo + let pluginDll = argv.[1] |> FileInfo + + { + InputFile = inputFile + PluginDll = pluginDll + } + + let getGenerateRawFromRaw (host : obj) : (RawSourceGenerationArgs -> string) option = + let pluginType = host.GetType () + + let generateRawFromRaw = + match + pluginType.GetMethod ( + "GenerateRawFromRaw", + BindingFlags.Instance ||| BindingFlags.Public ||| BindingFlags.FlattenHierarchy + ) + |> Option.ofObj + with + | None -> + pluginType.GetInterfaces () + |> Array.tryPick (fun interf -> + interf.GetMethod ( + "GenerateRawFromRaw", + BindingFlags.Instance ||| BindingFlags.Public ||| BindingFlags.FlattenHierarchy + ) + |> Option.ofObj + ) + | Some generateRawFromRaw -> Some generateRawFromRaw + + match generateRawFromRaw with + | None -> None + | Some generateRawFromRaw -> + let pars = generateRawFromRaw.GetParameters () + + if pars.Length <> 1 then + failwith + $"Expected GenerateRawFromRaw to take exactly one parameter: a RawSourceGenerationArgs. Got %i{pars.Length}" + + if pars.[0].ParameterType <> typeof then + failwith + $"Expected GenerateRawFromRaw to take exactly one parameter: a RawSourceGenerationArgs. Got %s{pars.[0].ParameterType.FullName}" + + let retType = generateRawFromRaw.ReturnType + + if retType <> typeof then + failwith + $"Expected GenerateRawFromRaw method to have return type `string`, but was: %s{retType.FullName}" + + fun args -> generateRawFromRaw.Invoke (host, [| args |]) |> unbox + |> Some + + [] + let main argv = + let args = parseArgs argv + + let projectDirectory = args.InputFile.Directory + let toolsPath = Init.init projectDirectory None + let defaultLoader = WorkspaceLoader.Create (toolsPath, []) + + use subscription = + defaultLoader.Notifications.Subscribe (fun msg -> + match msg with + | WorkspaceProjectState.Loading projectFilePath -> + Console.Error.WriteLine $"Loading: %s{projectFilePath}" + | WorkspaceProjectState.Loaded (loadedProject, _knownProjects, fromCache) -> + let fromCache = if fromCache then " (from cache)" else "" + Console.Error.WriteLine $"Loaded %s{loadedProject.ProjectFileName}%s{fromCache}" + | WorkspaceProjectState.Failed (projectFilePath, errors) -> + let errors = errors.ToString () + failwith $"Failed to load project %s{projectFilePath}: %s{errors}" + ) + + let projectOptions = + defaultLoader.LoadProjects ([ args.InputFile.FullName ]) |> Seq.toArray + + let desiredProject = + projectOptions + |> Array.find (fun po -> po.ProjectFileName = args.InputFile.FullName) + + let toGenerate = + desiredProject.Items + |> List.choose (fun (ProjectItem.Compile (name, fullPath, metadata)) -> + match metadata with + | None -> None + | Some metadata -> + + match Map.tryFind "WhippetFile" metadata with + | None -> None + | Some myriadFile -> + + { + GeneratedDest = FileInfo fullPath + InputSource = + FileInfo (Path.Combine (Path.GetDirectoryName desiredProject.ProjectFileName, myriadFile)) + } + |> Some + ) + + Console.Error.WriteLine $"Loading plugin: %s{args.PluginDll.FullName}" + let pluginAssembly = Assembly.LoadFrom args.PluginDll.FullName + // We will look up any member called GenerateRawFromRaw and/or GenerateFromRaw. + // It's your responsibility to decide whether to do anything with this call; you return null if you don't want + // to do anything. + // Alternatively, return the text you want to output. + // We provide you with the input file contents. + // GenerateRawFromRaw should return plain text. + // GenerateFromRaw should return a Fantomas AST. + let applicablePlugins = + pluginAssembly.ExportedTypes + |> Seq.choose (fun ty -> + if + ty.CustomAttributes + |> Seq.exists (fun attr -> attr.AttributeType.Name = typeof.Name) + then + Some (ty, Activator.CreateInstance ty) + else + None + ) + |> Seq.toList + + for item in toGenerate do + use output = item.GeneratedDest.Open (FileMode.Create, FileAccess.Write) + use outputWriter = new StreamWriter (output, leaveOpen = true) + + for plugin, hostClass in applicablePlugins do + match getGenerateRawFromRaw hostClass with + | None -> () + | Some generateRawFromRaw -> + let fileContents = File.ReadAllBytes item.InputSource.FullName + + let args = + { + RawSourceGenerationArgs.FilePath = item.InputSource.FullName + FileContents = fileContents + } + + let result = generateRawFromRaw args + + match result with + | null -> () + | result -> + Console.Error.WriteLine + $"Writing output for generator %s{plugin.Name} to file %s{item.GeneratedDest.FullName}" + + outputWriter.Write result + outputWriter.Write "\n" + + () + + 0 diff --git a/WoofWare.Whippet/WoofWare.Whippet.fsproj b/WoofWare.Whippet/WoofWare.Whippet.fsproj new file mode 100644 index 0000000..4f1a25e --- /dev/null +++ b/WoofWare.Whippet/WoofWare.Whippet.fsproj @@ -0,0 +1,39 @@ + + + + Exe + net8.0 + Patrick Stevens + Copyright (c) Patrick Stevens 2024 + A source generator for F#. + git + https://github.com/Smaug123/WoofWare.Whippet + MIT + README.md + fsharp;source-generator;source-gen + true + FS3559 + WoofWare.Whippet + + + + + + True + \ + + + + + + + + + + + + + + + + diff --git a/WoofWare.Whippet/version.json b/WoofWare.Whippet/version.json new file mode 100644 index 0000000..ad147c0 --- /dev/null +++ b/WoofWare.Whippet/version.json @@ -0,0 +1,12 @@ +{ + "version": "0.1", + "publicReleaseRefSpec": [ + "^refs/heads/main$" + ], + "pathFilters": [ + "./", + ":/WoofWare.Whippet.Core/", + ":/global.json", + ":/Directory.Build.props" + ] +} diff --git a/analyzers/analyzers.fsproj b/analyzers/analyzers.fsproj new file mode 100644 index 0000000..9d5d0c3 --- /dev/null +++ b/analyzers/analyzers.fsproj @@ -0,0 +1,16 @@ + + + + false + false + ../.analyzerpackages/ + net6.0 + true + false + + + + + + + diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..1f3d98c --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1726560853, + "narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1727811607, + "narHash": "sha256-2ByOBflaIUJKeF9q6efVcYHljZXGZ7MnCWtseRvmpm8=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "1839883cd0068572aed75fb9442b508bbd9ef09c", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..e8f7b6b --- /dev/null +++ b/flake.nix @@ -0,0 +1,68 @@ +{ + description = "Source generators for F#"; + + inputs = { + flake-utils.url = "github:numtide/flake-utils"; + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + }; + + outputs = { + nixpkgs, + flake-utils, + ... + }: + flake-utils.lib.eachDefaultSystem (system: let + pkgs = nixpkgs.legacyPackages.${system}; + pname = "WoofWare.Whippet"; + dotnet-sdk = pkgs.dotnet-sdk_8; + dotnet-runtime = pkgs.dotnetCorePackages.runtime_8_0; + version = "0.1"; + dotnetTool = dllOverride: toolName: toolVersion: hash: + pkgs.stdenvNoCC.mkDerivation rec { + name = toolName; + version = toolVersion; + nativeBuildInputs = [pkgs.makeWrapper]; + src = pkgs.fetchNuGet { + pname = name; + version = version; + hash = hash; + installPhase = ''mkdir -p $out/bin && cp -r tools/net6.0/any/* $out/bin''; + }; + installPhase = let + dll = + if isNull dllOverride + then name + else dllOverride; + in '' + runHook preInstall + mkdir -p "$out/lib" + cp -r ./bin/* "$out/lib" + makeWrapper "${dotnet-runtime}/bin/dotnet" "$out/bin/${name}" --add-flags "$out/lib/${dll}.dll" + runHook postInstall + ''; + }; + in { + packages = { + fantomas = dotnetTool null "fantomas" (builtins.fromJSON (builtins.readFile ./.config/dotnet-tools.json)).tools.fantomas.version (builtins.head (builtins.filter (elem: elem.pname == "fantomas") ((import ./nix/deps.nix) {fetchNuGet = x: x;}))).hash; + fsharp-analyzers = dotnetTool "FSharp.Analyzers.Cli" "fsharp-analyzers" (builtins.fromJSON (builtins.readFile ./.config/dotnet-tools.json)).tools.fsharp-analyzers.version (builtins.head (builtins.filter (elem: elem.pname == "fsharp-analyzers") ((import ./nix/deps.nix) {fetchNuGet = x: x;}))).hash; + default = pkgs.buildDotnetModule { + inherit pname version dotnet-sdk dotnet-runtime; + name = "WoofWare.Whippet"; + src = ./.; + projectFile = "./WoofWare.Whippet/WoofWare.Whippet.fsproj"; + testProjectFile = "./WoofWare.Whippet.Test/WoofWare.Whippet.Test.fsproj"; + disabledTests = ["WoofWare.Whippet.Test.TestSurface.CheckVersionAgainstRemote"]; + nugetDeps = ./nix/deps.nix; # `nix build .#default.passthru.fetch-deps && ./result nix/deps.nix` + doCheck = true; + }; + }; + devShell = pkgs.mkShell { + buildInputs = [dotnet-sdk]; + packages = [ + pkgs.alejandra + pkgs.nodePackages.markdown-link-check + pkgs.shellcheck + ]; + }; + }); +} diff --git a/nix/deps.nix b/nix/deps.nix new file mode 100644 index 0000000..fa38e65 --- /dev/null +++ b/nix/deps.nix @@ -0,0 +1,569 @@ +# This file was automatically generated by passthru.fetch-deps. +# Please dont edit it manually, your changes might get overwritten! +{fetchNuGet}: [ + (fetchNuGet { + pname = "ApiSurface"; + version = "4.1.5"; + hash = "sha256-Kbt18XLk1gvZfzGca885HaXZB119APay85KzI546PYM="; + }) + (fetchNuGet { + pname = "fantomas"; + version = "6.3.15"; + hash = "sha256-Gjw7MxjUNckMWSfnOye4UTe5fZWnor6RHCls3PNsuG8="; + }) + (fetchNuGet { + pname = "fsharp-analyzers"; + version = "0.27.0"; + hash = "sha256-QhLi2veTY1wZlQKJLTyVPgx/ImkaZugQNjSN5VJCNEA="; + }) + (fetchNuGet { + pname = "FSharp.Core"; + version = "4.3.4"; + hash = "sha256-styyo+6mJy+yxE0NZG/b1hxkAjPOnJfMgd9zWzCJ5uk="; + }) + (fetchNuGet { + pname = "FSharp.Core"; + version = "8.0.400"; + hash = "sha256-wlrcAjjvI5YtnHR7kFH8uRUA4GomJYmqr41K5LYjCGs="; + }) + (fetchNuGet { + pname = "FsUnit"; + version = "6.0.1"; + hash = "sha256-vka/aAgWhDCl5tu+kgO7GtSaHOOvlSaWxG+tExwGXpI="; + }) + (fetchNuGet { + pname = "Ionide.ProjInfo"; + version = "0.67.0"; + hash = "sha256-brgOnch4WuVe5TLx0RCa0wLDs3/Vu4HZP04YZyuAFYM="; + }) + (fetchNuGet { + pname = "Ionide.ProjInfo.Sln"; + version = "0.67.0"; + hash = "sha256-PyDq0Efv/vussW2Bgy+xl05SuyELH5NcYUMfSks4bT0="; + }) + (fetchNuGet { + pname = "Microsoft.Build"; + version = "17.2.0"; + hash = "sha256-JzPqbxFyotNhSr5tokVevdqB9+nJKx4YH2hPkC05GiY="; + }) + (fetchNuGet { + pname = "Microsoft.Build.Framework"; + version = "17.2.0"; + hash = "sha256-jG+p2tlyX5nWT4pcmgIC4M8LNruKLSZ2+I29S/ZI/yE="; + }) + (fetchNuGet { + pname = "Microsoft.CodeCoverage"; + version = "17.11.1"; + hash = "sha256-1dLlK3NGh88PuFYZiYpT+izA96etxhU3BSgixDgdtGA="; + }) + (fetchNuGet { + pname = "Microsoft.NET.StringTools"; + version = "1.0.0"; + hash = "sha256-smmwm1XbKsk0SPW74rd2uDubWzfd7RhfSkPr932cyhs="; + }) + (fetchNuGet { + pname = "Microsoft.NET.Test.Sdk"; + version = "17.11.1"; + hash = "sha256-0JUEucQ2lzaPgkrjm/NFLBTbqU1dfhvhN3Tl3moE6mI="; + }) + (fetchNuGet { + pname = "Microsoft.NETCore.Platforms"; + version = "1.0.1"; + hash = "sha256-mZotlGZqtrqDSoBrZhsxFe6fuOv5/BIo0w2Z2x0zVAU="; + }) + (fetchNuGet { + pname = "Microsoft.NETCore.Platforms"; + version = "1.1.0"; + hash = "sha256-FeM40ktcObQJk4nMYShB61H/E8B7tIKfl9ObJ0IOcCM="; + }) + (fetchNuGet { + pname = "Microsoft.NETCore.Platforms"; + version = "2.0.0"; + hash = "sha256-IEvBk6wUXSdyCnkj6tHahOJv290tVVT8tyemYcR0Yro="; + }) + (fetchNuGet { + pname = "Microsoft.NETCore.Platforms"; + version = "3.1.0"; + hash = "sha256-cnygditsEaU86bnYtIthNMymAHqaT/sf9Gjykhzqgb0="; + }) + (fetchNuGet { + pname = "Microsoft.NETCore.Targets"; + version = "1.0.1"; + hash = "sha256-lxxw/Gy32xHi0fLgFWNj4YTFBSBkjx5l6ucmbTyf7V4="; + }) + (fetchNuGet { + pname = "Microsoft.NETCore.Targets"; + version = "1.1.0"; + hash = "sha256-0AqQ2gMS8iNlYkrD+BxtIg7cXMnr9xZHtKAuN4bjfaQ="; + }) + (fetchNuGet { + pname = "Microsoft.TestPlatform.ObjectModel"; + version = "17.11.1"; + hash = "sha256-5vX+vCzFY3S7xfMVIv8OlMMFtdedW9UIJzc0WEc+vm4="; + }) + (fetchNuGet { + pname = "Microsoft.TestPlatform.TestHost"; + version = "17.11.1"; + hash = "sha256-wSkY0H1fQAq0H3LcKT4u7Y5RzhAAPa6yueVN84g8HxU="; + }) + (fetchNuGet { + pname = "Microsoft.Win32.Registry"; + version = "4.3.0"; + hash = "sha256-50XwFbyRfZkTD/bBn76WV/NIpOy/mzXD3MMEVFX/vr8="; + }) + (fetchNuGet { + pname = "Microsoft.Win32.SystemEvents"; + version = "4.7.0"; + hash = "sha256-GHxnD1Plb32GJWVWSv0Y51Kgtlb+cdKgOYVBYZSgVF4="; + }) + (fetchNuGet { + pname = "Nerdbank.GitVersioning"; + version = "3.6.143"; + hash = "sha256-OhOtMzP+2obDIR+npR7SsoXo0KrmcsL+VCE8Z3t5gzQ="; + }) + (fetchNuGet { + pname = "NETStandard.Library"; + version = "2.0.3"; + hash = "sha256-Prh2RPebz/s8AzHb2sPHg3Jl8s31inv9k+Qxd293ybo="; + }) + (fetchNuGet { + pname = "Newtonsoft.Json"; + version = "13.0.1"; + hash = "sha256-K2tSVW4n4beRPzPu3rlVaBEMdGvWSv/3Q1fxaDh4Mjo="; + }) + (fetchNuGet { + pname = "Newtonsoft.Json"; + version = "13.0.3"; + hash = "sha256-hy/BieY4qxBWVVsDqqOPaLy1QobiIapkbrESm6v2PHc="; + }) + (fetchNuGet { + pname = "NuGet.Common"; + version = "6.11.0"; + hash = "sha256-eb7G07RyZv4AQT6ItRqdBuUf9e9BXcQygsy5RNEXfNE="; + }) + (fetchNuGet { + pname = "NuGet.Configuration"; + version = "6.11.0"; + hash = "sha256-2SNZkX64SB15glzQx3k+vI7btr8Yqg4CayaaaK1B0AQ="; + }) + (fetchNuGet { + pname = "NuGet.Frameworks"; + version = "6.11.0"; + hash = "sha256-8DC7V2IlCjiMDQ9yWbl7QQHia6OpBrbWh5rL0qa0Opw="; + }) + (fetchNuGet { + pname = "NuGet.Frameworks"; + version = "6.11.1"; + hash = "sha256-p25Oa7wJjwF+2puIhBkZZkKSuk4jGq7xikYSCdfp9Vc="; + }) + (fetchNuGet { + pname = "NuGet.Packaging"; + version = "6.11.0"; + hash = "sha256-LVLvxcB6SMdayxAsrc5bCuLLt25fqPr6KfYcYoWWIQk="; + }) + (fetchNuGet { + pname = "NuGet.Protocol"; + version = "6.11.0"; + hash = "sha256-3vdB/8IiJ2LMHhFXLWOzf0H59Ow/zcoq6W4uCHbihCQ="; + }) + (fetchNuGet { + pname = "NuGet.Versioning"; + version = "6.11.0"; + hash = "sha256-03edgWvbqUtbzpBBTIxTwsSRoj1T2muGVL+vTuIHXag="; + }) + (fetchNuGet { + pname = "NUnit"; + version = "4.2.2"; + hash = "sha256-+0OS67ITalmG9arYCgQF/+YbmPRnB3pIIykew0kvoCc="; + }) + (fetchNuGet { + pname = "NUnit3TestAdapter"; + version = "4.6.0"; + hash = "sha256-9Yav2fYhC4w0OgsyUwU4/5rDy4FVDTpKnWHuwl/uKJQ="; + }) + (fetchNuGet { + pname = "runtime.any.System.Collections"; + version = "4.3.0"; + hash = "sha256-4PGZqyWhZ6/HCTF2KddDsbmTTjxs2oW79YfkberDZS8="; + }) + (fetchNuGet { + pname = "runtime.any.System.Globalization"; + version = "4.3.0"; + hash = "sha256-PaiITTFI2FfPylTEk7DwzfKeiA/g/aooSU1pDcdwWLU="; + }) + (fetchNuGet { + pname = "runtime.any.System.IO"; + version = "4.3.0"; + hash = "sha256-vej7ySRhyvM3pYh/ITMdC25ivSd0WLZAaIQbYj/6HVE="; + }) + (fetchNuGet { + pname = "runtime.any.System.Reflection"; + version = "4.3.0"; + hash = "sha256-ns6f++lSA+bi1xXgmW1JkWFb2NaMD+w+YNTfMvyAiQk="; + }) + (fetchNuGet { + pname = "runtime.any.System.Reflection.Primitives"; + version = "4.3.0"; + hash = "sha256-LkPXtiDQM3BcdYkAm5uSNOiz3uF4J45qpxn5aBiqNXQ="; + }) + (fetchNuGet { + pname = "runtime.any.System.Resources.ResourceManager"; + version = "4.3.0"; + hash = "sha256-9EvnmZslLgLLhJ00o5MWaPuJQlbUFcUF8itGQNVkcQ4="; + }) + (fetchNuGet { + pname = "runtime.any.System.Runtime"; + version = "4.3.0"; + hash = "sha256-qwhNXBaJ1DtDkuRacgHwnZmOZ1u9q7N8j0cWOLYOELM="; + }) + (fetchNuGet { + pname = "runtime.any.System.Runtime.Handles"; + version = "4.3.0"; + hash = "sha256-PQRACwnSUuxgVySO1840KvqCC9F8iI9iTzxNW0RcBS4="; + }) + (fetchNuGet { + pname = "runtime.any.System.Runtime.InteropServices"; + version = "4.3.0"; + hash = "sha256-Kaw5PnLYIiqWbsoF3VKJhy7pkpoGsUwn4ZDCKscbbzA="; + }) + (fetchNuGet { + pname = "runtime.any.System.Text.Encoding"; + version = "4.3.0"; + hash = "sha256-Q18B9q26MkWZx68exUfQT30+0PGmpFlDgaF0TnaIGCs="; + }) + (fetchNuGet { + pname = "runtime.any.System.Threading.Tasks"; + version = "4.3.0"; + hash = "sha256-agdOM0NXupfHbKAQzQT8XgbI9B8hVEh+a/2vqeHctg4="; + }) + (fetchNuGet { + pname = "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl"; + version = "4.3.0"; + hash = "sha256-LXUPLX3DJxsU1Pd3UwjO1PO9NM2elNEDXeu2Mu/vNps="; + }) + (fetchNuGet { + pname = "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl"; + version = "4.3.0"; + hash = "sha256-qeSqaUI80+lqw5MK4vMpmO0CZaqrmYktwp6L+vQAb0I="; + }) + (fetchNuGet { + pname = "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl"; + version = "4.3.0"; + hash = "sha256-SrHqT9wrCBsxILWtaJgGKd6Odmxm8/Mh7Kh0CUkZVzA="; + }) + (fetchNuGet { + pname = "runtime.native.System"; + version = "4.3.0"; + hash = "sha256-ZBZaodnjvLXATWpXXakFgcy6P+gjhshFXmglrL5xD5Y="; + }) + (fetchNuGet { + pname = "runtime.native.System.Security.Cryptography.OpenSsl"; + version = "4.3.0"; + hash = "sha256-Jy01KhtcCl2wjMpZWH+X3fhHcVn+SyllWFY8zWlz/6I="; + }) + (fetchNuGet { + pname = "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl"; + version = "4.3.0"; + hash = "sha256-wyv00gdlqf8ckxEdV7E+Ql9hJIoPcmYEuyeWb5Oz3mM="; + }) + (fetchNuGet { + pname = "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl"; + version = "4.3.0"; + hash = "sha256-zi+b4sCFrA9QBiSGDD7xPV27r3iHGlV99gpyVUjRmc4="; + }) + (fetchNuGet { + pname = "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl"; + version = "4.3.0"; + hash = "sha256-gybQU6mPgaWV3rBG2dbH6tT3tBq8mgze3PROdsuWnX0="; + }) + (fetchNuGet { + pname = "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl"; + version = "4.3.0"; + hash = "sha256-VsP72GVveWnGUvS/vjOQLv1U80H2K8nZ4fDAmI61Hm4="; + }) + (fetchNuGet { + pname = "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl"; + version = "4.3.0"; + hash = "sha256-4yKGa/IrNCKuQ3zaDzILdNPD32bNdy6xr5gdJigyF5g="; + }) + (fetchNuGet { + pname = "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl"; + version = "4.3.0"; + hash = "sha256-HmdJhhRsiVoOOCcUvAwdjpMRiyuSwdcgEv2j9hxi+Zc="; + }) + (fetchNuGet { + pname = "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl"; + version = "4.3.0"; + hash = "sha256-pVFUKuPPIx0edQKjzRon3zKq8zhzHEzko/lc01V/jdw="; + }) + (fetchNuGet { + pname = "runtime.unix.System.Private.Uri"; + version = "4.3.0"; + hash = "sha256-c5tXWhE/fYbJVl9rXs0uHh3pTsg44YD1dJvyOA0WoMs="; + }) + (fetchNuGet { + pname = "runtime.unix.System.Runtime.Extensions"; + version = "4.3.0"; + hash = "sha256-l8S9gt6dk3qYG6HYonHtdlYtBKyPb29uQ6NDjmrt3V4="; + }) + (fetchNuGet { + pname = "SemanticVersioning"; + version = "2.0.2"; + hash = "sha256-d5tUJshDHk/rhNqt7Rl9S/Fg526el1faeanNHKcqtAg="; + }) + (fetchNuGet { + pname = "System.Collections"; + version = "4.0.11"; + hash = "sha256-puoFMkx4Z55C1XPxNw3np8nzNGjH+G24j43yTIsDRL0="; + }) + (fetchNuGet { + pname = "System.Collections"; + version = "4.3.0"; + hash = "sha256-afY7VUtD6w/5mYqrce8kQrvDIfS2GXDINDh73IjxJKc="; + }) + (fetchNuGet { + pname = "System.Collections.Immutable"; + version = "5.0.0"; + hash = "sha256-GdwSIjLMM0uVfE56VUSLVNgpW0B//oCeSFj8/hSlbM8="; + }) + (fetchNuGet { + pname = "System.Configuration.ConfigurationManager"; + version = "4.7.0"; + hash = "sha256-rYjp/UmagI4ZULU1ocia/AiXxLNL8uhMV8LBF4QFW10="; + }) + (fetchNuGet { + pname = "System.Drawing.Common"; + version = "4.7.0"; + hash = "sha256-D3qG+xAe78lZHvlco9gHK2TEAM370k09c6+SQi873Hk="; + }) + (fetchNuGet { + pname = "System.Formats.Asn1"; + version = "6.0.0"; + hash = "sha256-KaMHgIRBF7Nf3VwOo+gJS1DcD+41cJDPWFh+TDQ8ee8="; + }) + (fetchNuGet { + pname = "System.Globalization"; + version = "4.0.11"; + hash = "sha256-rbSgc2PIEc2c2rN6LK3qCREAX3DqA2Nq1WcLrZYsDBw="; + }) + (fetchNuGet { + pname = "System.Globalization"; + version = "4.3.0"; + hash = "sha256-caL0pRmFSEsaoeZeWN5BTQtGrAtaQPwFi8YOZPZG5rI="; + }) + (fetchNuGet { + pname = "System.IO"; + version = "4.1.0"; + hash = "sha256-V6oyQFwWb8NvGxAwvzWnhPxy9dKOfj/XBM3tEC5aHrw="; + }) + (fetchNuGet { + pname = "System.IO"; + version = "4.3.0"; + hash = "sha256-ruynQHekFP5wPrDiVyhNiRIXeZ/I9NpjK5pU+HPDiRY="; + }) + (fetchNuGet { + pname = "System.IO.Abstractions"; + version = "4.2.13"; + hash = "sha256-nkC/PiqE6+c1HJ2yTwg3x+qdBh844Z8n3ERWDW8k6Gg="; + }) + (fetchNuGet { + pname = "System.IO.FileSystem.AccessControl"; + version = "4.5.0"; + hash = "sha256-ck44YBQ0M+2Im5dw0VjBgFD1s0XuY54cujrodjjSBL8="; + }) + (fetchNuGet { + pname = "System.Memory"; + version = "4.5.4"; + hash = "sha256-3sCEfzO4gj5CYGctl9ZXQRRhwAraMQfse7yzKoRe65E="; + }) + (fetchNuGet { + pname = "System.Private.Uri"; + version = "4.3.0"; + hash = "sha256-fVfgcoP4AVN1E5wHZbKBIOPYZ/xBeSIdsNF+bdukIRM="; + }) + (fetchNuGet { + pname = "System.Reflection"; + version = "4.1.0"; + hash = "sha256-idZHGH2Yl/hha1CM4VzLhsaR8Ljo/rV7TYe7mwRJSMs="; + }) + (fetchNuGet { + pname = "System.Reflection"; + version = "4.3.0"; + hash = "sha256-NQSZRpZLvtPWDlvmMIdGxcVuyUnw92ZURo0hXsEshXY="; + }) + (fetchNuGet { + pname = "System.Reflection.Metadata"; + version = "1.6.0"; + hash = "sha256-JJfgaPav7UfEh4yRAQdGhLZF1brr0tUWPl6qmfNWq/E="; + }) + (fetchNuGet { + pname = "System.Reflection.Primitives"; + version = "4.0.1"; + hash = "sha256-SFSfpWEyCBMAOerrMCOiKnpT+UAWTvRcmoRquJR6Vq0="; + }) + (fetchNuGet { + pname = "System.Reflection.Primitives"; + version = "4.3.0"; + hash = "sha256-5ogwWB4vlQTl3jjk1xjniG2ozbFIjZTL9ug0usZQuBM="; + }) + (fetchNuGet { + pname = "System.Resources.ResourceManager"; + version = "4.0.1"; + hash = "sha256-cZ2/3/fczLjEpn6j3xkgQV9ouOVjy4Kisgw5xWw9kSw="; + }) + (fetchNuGet { + pname = "System.Resources.ResourceManager"; + version = "4.3.0"; + hash = "sha256-idiOD93xbbrbwwSnD4mORA9RYi/D/U48eRUsn/WnWGo="; + }) + (fetchNuGet { + pname = "System.Runtime"; + version = "4.1.0"; + hash = "sha256-FViNGM/4oWtlP6w0JC0vJU+k9efLKZ+yaXrnEeabDQo="; + }) + (fetchNuGet { + pname = "System.Runtime"; + version = "4.3.0"; + hash = "sha256-51813WXpBIsuA6fUtE5XaRQjcWdQ2/lmEokJt97u0Rg="; + }) + (fetchNuGet { + pname = "System.Runtime.CompilerServices.Unsafe"; + version = "5.0.0"; + hash = "sha256-neARSpLPUzPxEKhJRwoBzhPxK+cKIitLx7WBYncsYgo="; + }) + (fetchNuGet { + pname = "System.Runtime.CompilerServices.Unsafe"; + version = "6.0.0"; + hash = "sha256-bEG1PnDp7uKYz/OgLOWs3RWwQSVYm+AnPwVmAmcgp2I="; + }) + (fetchNuGet { + pname = "System.Runtime.Extensions"; + version = "4.1.0"; + hash = "sha256-X7DZ5CbPY7jHs20YZ7bmcXs9B5Mxptu/HnBUvUnNhGc="; + }) + (fetchNuGet { + pname = "System.Runtime.Extensions"; + version = "4.3.0"; + hash = "sha256-wLDHmozr84v1W2zYCWYxxj0FR0JDYHSVRaRuDm0bd/o="; + }) + (fetchNuGet { + pname = "System.Runtime.Handles"; + version = "4.0.1"; + hash = "sha256-j2QgVO9ZOjv7D1het98CoFpjoYgxjupuIhuBUmLLH7w="; + }) + (fetchNuGet { + pname = "System.Runtime.Handles"; + version = "4.3.0"; + hash = "sha256-KJ5aXoGpB56Y6+iepBkdpx/AfaJDAitx4vrkLqR7gms="; + }) + (fetchNuGet { + pname = "System.Runtime.InteropServices"; + version = "4.1.0"; + hash = "sha256-QceAYlJvkPRJc/+5jR+wQpNNI3aqGySWWSO30e/FfQY="; + }) + (fetchNuGet { + pname = "System.Runtime.InteropServices"; + version = "4.3.0"; + hash = "sha256-8sDH+WUJfCR+7e4nfpftj/+lstEiZixWUBueR2zmHgI="; + }) + (fetchNuGet { + pname = "System.Security.AccessControl"; + version = "4.5.0"; + hash = "sha256-AFsKPb/nTk2/mqH/PYpaoI8PLsiKKimaXf+7Mb5VfPM="; + }) + (fetchNuGet { + pname = "System.Security.AccessControl"; + version = "4.7.0"; + hash = "sha256-/9ZCPIHLdhzq7OW4UKqTsR0O93jjHd6BRG1SRwgHE1g="; + }) + (fetchNuGet { + pname = "System.Security.Cryptography.Pkcs"; + version = "6.0.4"; + hash = "sha256-2e0aRybote+OR66bHaNiYpF//4fCiaO3zbR2e9GABUI="; + }) + (fetchNuGet { + pname = "System.Security.Cryptography.ProtectedData"; + version = "4.4.0"; + hash = "sha256-Ri53QmFX8I8UH0x4PikQ1ZA07ZSnBUXStd5rBfGWFOE="; + }) + (fetchNuGet { + pname = "System.Security.Cryptography.ProtectedData"; + version = "4.7.0"; + hash = "sha256-dZfs5q3Ij1W1eJCfYjxI2o+41aSiFpaAugpoECaCOug="; + }) + (fetchNuGet { + pname = "System.Security.Permissions"; + version = "4.7.0"; + hash = "sha256-BGgXMLUi5rxVmmChjIhcXUxisJjvlNToXlyaIbUxw40="; + }) + (fetchNuGet { + pname = "System.Security.Principal.Windows"; + version = "4.5.0"; + hash = "sha256-BkUYNguz0e4NJp1kkW7aJBn3dyH9STwB5N8XqnlCsmY="; + }) + (fetchNuGet { + pname = "System.Security.Principal.Windows"; + version = "4.7.0"; + hash = "sha256-rWBM2U8Kq3rEdaa1MPZSYOOkbtMGgWyB8iPrpIqmpqg="; + }) + (fetchNuGet { + pname = "System.Text.Encoding"; + version = "4.0.11"; + hash = "sha256-PEailOvG05CVgPTyKLtpAgRydlSHmtd5K0Y8GSHY2Lc="; + }) + (fetchNuGet { + pname = "System.Text.Encoding"; + version = "4.3.0"; + hash = "sha256-GctHVGLZAa/rqkBNhsBGnsiWdKyv6VDubYpGkuOkBLg="; + }) + (fetchNuGet { + pname = "System.Text.Encoding.CodePages"; + version = "4.0.1"; + hash = "sha256-wxtwWQSTv5tuFP79KhUAhaL6bL4d8lSzSWkCn9aolwM="; + }) + (fetchNuGet { + pname = "System.Text.Encodings.Web"; + version = "6.0.0"; + hash = "sha256-UemDHGFoQIG7ObQwRluhVf6AgtQikfHEoPLC6gbFyRo="; + }) + (fetchNuGet { + pname = "System.Text.Encodings.Web"; + version = "7.0.0"; + hash = "sha256-tF8qt9GZh/nPy0mEnj6nKLG4Lldpoi/D8xM5lv2CoYQ="; + }) + (fetchNuGet { + pname = "System.Text.Json"; + version = "6.0.0"; + hash = "sha256-9AE/5ds4DqEfb0l+27fCBTSeYCdRWhxh2Bhg8IKvIuo="; + }) + (fetchNuGet { + pname = "System.Text.Json"; + version = "7.0.3"; + hash = "sha256-aSJZ17MjqaZNQkprfxm/09LaCoFtpdWmqU9BTROzWX4="; + }) + (fetchNuGet { + pname = "System.Threading"; + version = "4.0.11"; + hash = "sha256-mob1Zv3qLQhQ1/xOLXZmYqpniNUMCfn02n8ZkaAhqac="; + }) + (fetchNuGet { + pname = "System.Threading.Tasks"; + version = "4.0.11"; + hash = "sha256-5SLxzFg1df6bTm2t09xeI01wa5qQglqUwwJNlQPJIVs="; + }) + (fetchNuGet { + pname = "System.Threading.Tasks"; + version = "4.3.0"; + hash = "sha256-Z5rXfJ1EXp3G32IKZGiZ6koMjRu0n8C1NGrwpdIen4w="; + }) + (fetchNuGet { + pname = "System.Threading.Tasks.Dataflow"; + version = "6.0.0"; + hash = "sha256-bEx7t8jvo7HEkqexhyYyrgFojiMVYyd2nG2mHJv0m6w="; + }) + (fetchNuGet { + pname = "System.Windows.Extensions"; + version = "4.7.0"; + hash = "sha256-yW+GvQranReaqPw5ZFv+mSjByQ5y1pRLl05JIEf3tYU="; + }) +]