diff --git a/.github/ISSUE_TEMPLATE.yml b/.github/ISSUE_TEMPLATE.yml new file mode 100644 index 0000000..58a9843 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.yml @@ -0,0 +1,20 @@ +### Prerequisites + +* [ ] Can you reproduce the problem? If so, please the relevant code. +* [ ] Are you running the latest version? +* [ ] Are you reporting to the correct repository? +* [ ] Did you perform a cursory search? + +### Description + +[Description of the bug or feature] + +### Steps to Reproduce + +1. [First Step] +2. [Second Step] +3. [and so on...] + +**Expected behavior:** [What you expected to happen] + +**Actual behavior:** [What actually happened] diff --git a/.github/PULL_REQUEST_TEMPLATE.yml b/.github/PULL_REQUEST_TEMPLATE.yml new file mode 100644 index 0000000..b86bc43 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.yml @@ -0,0 +1,11 @@ +## Types of changes + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to change) +- [ ] I have read the **CONTRIBUTING** document. +- [ ] My code follows the code style of this project. +- [ ] My change requires a change to the documentation. +- [ ] I have updated the documentation accordingly. +- [ ] I have added tests to cover my changes. +- [ ] All new and existing tests passed. diff --git a/.github/workflows/build-container.yml b/.github/workflows/build-container.yml new file mode 100644 index 0000000..784d529 --- /dev/null +++ b/.github/workflows/build-container.yml @@ -0,0 +1,41 @@ +name: "[Nix] Build Container" + +on: + push: + branches: [ "master" ] + paths: + - '.github/**' + - 'flake.nix' + - 'flake.lock' + - 'src/**' + - 'tests/**' + pull_request: + paths: + - '.github/**' + - 'flake.nix' + - 'flake.lock' + - 'src/**' + - 'tests/**' + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + token: ${{ github.token }} + + - name: Install Nix + uses: cachix/install-nix-action@v24 + with: + # Mostly to avoid GitHub rate limiting + extra_nix_config: | + access-tokens = github.com=${{ github.token }} + + - name: Install Nix Cache + uses: DeterminateSystems/magic-nix-cache-action@main + + - name: Build OCI Image + run: nix build .#dockerImage diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..fbb54fc --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,43 @@ +name: "[.Net] Build & Test" + +on: + push: + branches: [ "master" ] + paths: + - '.github/**' + - 'flake.nix' + - 'flake.lock' + - 'src/**' + - 'tests/**' + pull_request: + paths: + - '.github/**' + - 'flake.nix' + - 'flake.lock' + - 'src/**' + - 'tests/**' + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + token: ${{ github.token }} + + - name: Install Nix + uses: cachix/install-nix-action@v24 + with: + # Mostly to avoid GitHub rate limiting + extra_nix_config: | + access-tokens = github.com=${{ github.token }} + + - name: Install Nix Cache + uses: DeterminateSystems/magic-nix-cache-action@main + + - name: Build + run: nix develop .#ci --impure -c just build + - name: Run testing suite + run: nix develop .#ci --impure -c just test diff --git a/.github/workflows/clear-cache.yml b/.github/workflows/clear-cache.yml new file mode 100644 index 0000000..8232dbf --- /dev/null +++ b/.github/workflows/clear-cache.yml @@ -0,0 +1,61 @@ +name: Clear cache + +on: + workflow_dispatch: + schedule: + - cron: "*/15 2-4 * * *" + pull_request: + types: + - closed + +permissions: + actions: write + +jobs: + # https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#force-deleting-cache-entries + pr-cleanup: + if: ${{ github.event_name == 'pull_request' && github.event.action == 'closed' }} + runs-on: ubuntu-latest + steps: + - name: Closed PR Cleanup + run: | + gh extension install actions/gh-actions-cache + + echo "Fetching list of cache key" + cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH -L 100 | cut -f 1 ) + + ## Setting this to not fail the workflow while deleting cache keys. + set +e + echo "Deleting caches..." + for cacheKey in $cacheKeysForPR + do + gh actions-cache delete $cacheKey -R $REPO -B $BRANCH --confirm + done + echo "Done" + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + BRANCH: refs/pull/${{ github.event.pull_request.number }}/merge + + scheduled-cleanup: + if: ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }} + runs-on: ubuntu-latest + steps: + - name: Clear Cache + uses: actions/github-script@v7 + with: + script: | + console.log("About to clear") + const caches = await github.rest.actions.getActionsCacheList({ + owner: context.repo.owner, + repo: context.repo.repo, + }) + for (const cache of caches.data.actions_caches) { + console.log(cache) + github.rest.actions.deleteActionsCacheById({ + owner: context.repo.owner, + repo: context.repo.repo, + cache_id: cache.id, + }) + } + console.log("Clear completed") diff --git a/.github/workflows/nuget.yml b/.github/workflows/nuget.yml new file mode 100644 index 0000000..0dc5c59 --- /dev/null +++ b/.github/workflows/nuget.yml @@ -0,0 +1,39 @@ +name: Nuget + +on: + push: + tags: + - "v[0-9]+.[0-9]+.[0-9]+" + +jobs: + push: + name: Push to Nuget + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + token: ${{ github.token }} + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: 6.0.x + + - name: Install Nix + uses: cachix/install-nix-action@v24 + with: + # Mostly to avoid GitHub rate limiting + extra_nix_config: | + access-tokens = github.com=${{ github.token }} + + - name: Install Nix Cache + uses: DeterminateSystems/magic-nix-cache-action@main + + - name: Test before releasing + run: nix develop .#ci --impure -c just test + - name: Pack release + run: nix develop .#ci --impure -c just pack + - name: Publish the package to nuget.org + run: nix develop .#ci --impure -c just release + env: + NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..de0ae3e --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,22 @@ +name: Create Release + +on: + push: + tags: + - "v[0-9]+.[0-9]+.[0-9]+" + +jobs: + create-github-release: + name: Create GitHub Release + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + token: ${{ github.token }} + - name: Create Release + run: gh release create ${{ github.ref }} --generate-notes + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..177278f --- /dev/null +++ b/.gitignore @@ -0,0 +1,492 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from `dotnet new gitignore` + +# dotenv files +.env + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET +project.lock.json +project.fragment.lock.json +artifacts/ + +# Tye +.tye/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml +.idea + +## +## Visual studio for Mac +## + + +# globs +Makefile.in +*.userprefs +*.usertasks +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.tar.gz +tarballs/ +test-results/ + +# Mac bundle stuff +*.dmg +*.app + +# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# Vim temporary swap files +*.swp + +# ----- +# Extra +# ----- + +# Nix +result +out diff --git a/README.md b/README.md index 2285716..e693738 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,79 @@ -# fsharp-nix -An opinionated F# template and development environment that's powered by Nix +# F# + Nix + +[![built with nix](https://builtwithnix.org/badge.svg)](https://builtwithnix.org)
+[![[.Net] Build & Test](https://github.com/mtrsk/fsharp-nix/actions/workflows/build.yml/badge.svg)](https://github.com/mtrsk/fsharp-nix/actions/workflows/build.yml) +[![[Nix] Build Container](https://github.com/mtrsk/fsharp-nix/actions/workflows/build-container.yml/badge.svg)](https://github.com/mtrsk/fsharp-nix/actions/workflows/build-container.yml) + +An opinionated F# template and development environment that's powered by Nix. + +- [F# + Nix](#f--nix) + - [Development](#development) + - [Devenv](#devenv) + - [Justfile](#justfile) + - [Nix Build](#nix-build) + - [Github Actions (+Nix)](#github-actions-nix) + - [Docker](#docker) + +## Development + +### Devenv + +```shell +nix develop --impure +``` +or `direnv allow`, if you have [direnv](https://github.com/direnv/direnv) installed. + +#### Justfile + +If you type `just` for a list of commands. + +```shell +$ just +PROJECT: Sample - RELEASE: +just --list +Available recipes: + build # Builds the project + b # alias for `build` + build-nix # Builds the project (with Nix) + bnix # alias for `build-nix` + default # Lists all availiable targets + delete # Deletes a release + gen-deps # Generates nix dependencies for deps.nix + gd # alias for `gen-deps` + pack # Packages current tag as a .Net release + push # Pushes release to NUGET + test # Runs testing suite + t # alias for `test` +``` +to build this project, you can run: + +```shell +# or just b, if you're lazy +just build +``` +and for running the testing suite: + +```shell +# or just b, if you're lazy +just test +``` + +### Nix Build + +To build this with Nix: + +```shell +nix build +``` + +### Github Actions (+Nix) + +I've also pre-configured some [Github Actions](https://github.com/mtrsk/fsharp-nix/actions). + +### Docker + +A container image can also be generated via: +```shell +nix build .#dockerImage +``` + diff --git a/Sample.sln b/Sample.sln new file mode 100644 index 0000000..ac796c9 --- /dev/null +++ b/Sample.sln @@ -0,0 +1,27 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{94B0AEAC-5C05-4D41-884A-4FBD64F6F0FD}" +EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "App", "src\App\App.fsproj", "{566657C6-8981-4257-8907-D8F2C9CE5747}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {566657C6-8981-4257-8907-D8F2C9CE5747}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {566657C6-8981-4257-8907-D8F2C9CE5747}.Debug|Any CPU.Build.0 = Debug|Any CPU + {566657C6-8981-4257-8907-D8F2C9CE5747}.Release|Any CPU.ActiveCfg = Release|Any CPU + {566657C6-8981-4257-8907-D8F2C9CE5747}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {566657C6-8981-4257-8907-D8F2C9CE5747} = {94B0AEAC-5C05-4D41-884A-4FBD64F6F0FD} + EndGlobalSection +EndGlobal diff --git a/deps.nix b/deps.nix new file mode 100644 index 0000000..e3dde96 --- /dev/null +++ b/deps.nix @@ -0,0 +1,3 @@ +{ fetchNuGet }: [ + (fetchNuGet { pname = "FsToolkit.ErrorHandling"; version = "4.16.0"; hash = "sha256-4pRixOtRDgLt4/z71o1XnkuXRa/43LUhl/pDRpofX7s="; }) +] diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..6a294a8 --- /dev/null +++ b/flake.lock @@ -0,0 +1,642 @@ +{ + "nodes": { + "cachix": { + "inputs": { + "devenv": "devenv_2", + "flake-compat": [ + "devenv", + "flake-compat" + ], + "git-hooks": [ + "devenv", + "pre-commit-hooks" + ], + "nixpkgs": [ + "devenv", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1724232775, + "narHash": "sha256-6u2DycIEgrgNYlLxyGqdFVmBNiKIitnQKJ1pbRP5oko=", + "owner": "cachix", + "repo": "cachix", + "rev": "03b6cb3f953097bff378fb8b9ea094bd091a4ec7", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "cachix", + "type": "github" + } + }, + "cachix_2": { + "inputs": { + "devenv": "devenv_3", + "flake-compat": [ + "devenv", + "cachix", + "devenv", + "flake-compat" + ], + "nixpkgs": [ + "devenv", + "cachix", + "devenv", + "nixpkgs" + ], + "pre-commit-hooks": [ + "devenv", + "cachix", + "devenv", + "pre-commit-hooks" + ] + }, + "locked": { + "lastModified": 1712055811, + "narHash": "sha256-7FcfMm5A/f02yyzuavJe06zLa9hcMHsagE28ADcmQvk=", + "owner": "cachix", + "repo": "cachix", + "rev": "02e38da89851ec7fec3356a5c04bc8349cae0e30", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "cachix", + "type": "github" + } + }, + "devenv": { + "inputs": { + "cachix": "cachix", + "flake-compat": "flake-compat_2", + "nix": "nix_3", + "nixpkgs": [ + "nixpkgs" + ], + "pre-commit-hooks": "pre-commit-hooks_2" + }, + "locked": { + "lastModified": 1726232533, + "narHash": "sha256-rhho/HLlDkJ/d3k6oQivgCSdVz4C1LLklPtO/aBhC2I=", + "owner": "cachix", + "repo": "devenv", + "rev": "199a23e3bcfbfacaec3836d1c884918e13239b50", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "devenv", + "type": "github" + } + }, + "devenv_2": { + "inputs": { + "cachix": "cachix_2", + "flake-compat": [ + "devenv", + "cachix", + "flake-compat" + ], + "nix": "nix_2", + "nixpkgs": [ + "devenv", + "cachix", + "nixpkgs" + ], + "pre-commit-hooks": [ + "devenv", + "cachix", + "git-hooks" + ] + }, + "locked": { + "lastModified": 1723156315, + "narHash": "sha256-0JrfahRMJ37Rf1i0iOOn+8Z4CLvbcGNwa2ChOAVrp/8=", + "owner": "cachix", + "repo": "devenv", + "rev": "ff5eb4f2accbcda963af67f1a1159e3f6c7f5f91", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "devenv", + "type": "github" + } + }, + "devenv_3": { + "inputs": { + "flake-compat": [ + "devenv", + "cachix", + "devenv", + "cachix", + "flake-compat" + ], + "nix": "nix", + "nixpkgs": "nixpkgs", + "poetry2nix": "poetry2nix", + "pre-commit-hooks": [ + "devenv", + "cachix", + "devenv", + "cachix", + "pre-commit-hooks" + ] + }, + "locked": { + "lastModified": 1708704632, + "narHash": "sha256-w+dOIW60FKMaHI1q5714CSibk99JfYxm0CzTinYWr+Q=", + "owner": "cachix", + "repo": "devenv", + "rev": "2ee4450b0f4b95a1b90f2eb5ffea98b90e48c196", + "type": "github" + }, + "original": { + "owner": "cachix", + "ref": "python-rewrite", + "repo": "devenv", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1673956053, + "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-compat_2": { + "flake": false, + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": [ + "devenv", + "nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1712014858, + "narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "9126214d0a59633752a136528f5f3b9aa8565b7d", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1689068808, + "narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_2": { + "locked": { + "lastModified": 1667395993, + "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "devenv", + "pre-commit-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1709087332, + "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "libgit2": { + "flake": false, + "locked": { + "lastModified": 1697646580, + "narHash": "sha256-oX4Z3S9WtJlwvj0uH9HlYcWv+x1hqp8mhXl7HsLu2f0=", + "owner": "libgit2", + "repo": "libgit2", + "rev": "45fd9ed7ae1a9b74b957ef4f337bc3c8b3df01b5", + "type": "github" + }, + "original": { + "owner": "libgit2", + "repo": "libgit2", + "type": "github" + } + }, + "nix": { + "inputs": { + "flake-compat": "flake-compat", + "nixpkgs": [ + "devenv", + "cachix", + "devenv", + "cachix", + "devenv", + "nixpkgs" + ], + "nixpkgs-regression": "nixpkgs-regression" + }, + "locked": { + "lastModified": 1712911606, + "narHash": "sha256-BGvBhepCufsjcUkXnEEXhEVjwdJAwPglCC2+bInc794=", + "owner": "domenkozar", + "repo": "nix", + "rev": "b24a9318ea3f3600c1e24b4a00691ee912d4de12", + "type": "github" + }, + "original": { + "owner": "domenkozar", + "ref": "devenv-2.21", + "repo": "nix", + "type": "github" + } + }, + "nix-github-actions": { + "inputs": { + "nixpkgs": [ + "devenv", + "cachix", + "devenv", + "cachix", + "devenv", + "poetry2nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1688870561, + "narHash": "sha256-4UYkifnPEw1nAzqqPOTL2MvWtm3sNGw1UTYTalkTcGY=", + "owner": "nix-community", + "repo": "nix-github-actions", + "rev": "165b1650b753316aa7f1787f3005a8d2da0f5301", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nix-github-actions", + "type": "github" + } + }, + "nix_2": { + "inputs": { + "flake-compat": [ + "devenv", + "cachix", + "devenv", + "flake-compat" + ], + "nixpkgs": [ + "devenv", + "cachix", + "devenv", + "nixpkgs" + ], + "nixpkgs-regression": "nixpkgs-regression_2" + }, + "locked": { + "lastModified": 1712911606, + "narHash": "sha256-BGvBhepCufsjcUkXnEEXhEVjwdJAwPglCC2+bInc794=", + "owner": "domenkozar", + "repo": "nix", + "rev": "b24a9318ea3f3600c1e24b4a00691ee912d4de12", + "type": "github" + }, + "original": { + "owner": "domenkozar", + "ref": "devenv-2.21", + "repo": "nix", + "type": "github" + } + }, + "nix_3": { + "inputs": { + "flake-compat": [ + "devenv", + "flake-compat" + ], + "flake-parts": "flake-parts", + "libgit2": "libgit2", + "nixpkgs": "nixpkgs_2", + "nixpkgs-23-11": "nixpkgs-23-11", + "nixpkgs-regression": "nixpkgs-regression_3", + "pre-commit-hooks": "pre-commit-hooks" + }, + "locked": { + "lastModified": 1725980365, + "narHash": "sha256-uDwWyizzlQ0HFzrhP6rVp2+2NNA+/TM5zT32dR8GUlg=", + "owner": "domenkozar", + "repo": "nix", + "rev": "1e61e9f40673f84c3b02573145492d8af581bec5", + "type": "github" + }, + "original": { + "owner": "domenkozar", + "ref": "devenv-2.24", + "repo": "nix", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1692808169, + "narHash": "sha256-x9Opq06rIiwdwGeK2Ykj69dNc2IvUH1fY55Wm7atwrE=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "9201b5ff357e781bf014d0330d18555695df7ba8", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-23-11": { + "locked": { + "lastModified": 1717159533, + "narHash": "sha256-oamiKNfr2MS6yH64rUn99mIZjc45nGJlj9eGth/3Xuw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "a62e6edd6d5e1fa0329b8653c801147986f8d446", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "a62e6edd6d5e1fa0329b8653c801147986f8d446", + "type": "github" + } + }, + "nixpkgs-regression": { + "locked": { + "lastModified": 1643052045, + "narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", + "type": "github" + } + }, + "nixpkgs-regression_2": { + "locked": { + "lastModified": 1643052045, + "narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", + "type": "github" + } + }, + "nixpkgs-regression_3": { + "locked": { + "lastModified": 1643052045, + "narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", + "type": "github" + } + }, + "nixpkgs-stable": { + "locked": { + "lastModified": 1720386169, + "narHash": "sha256-NGKVY4PjzwAa4upkGtAMz1npHGoRzWotlSnVlqI40mo=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "194846768975b7ad2c4988bdb82572c00222c0d7", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-24.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1717432640, + "narHash": "sha256-+f9c4/ZX5MWDOuB1rKoWj+lBNm0z0rs4CK47HBLxy1o=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "88269ab3044128b7c2f4c7d68448b2fb50456870", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "release-24.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_3": { + "locked": { + "lastModified": 1726062873, + "narHash": "sha256-IiA3jfbR7K/B5+9byVi9BZGWTD4VSbWe8VLpp9B/iYk=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "4f807e8940284ad7925ebd0a0993d2a1791acb2f", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "poetry2nix": { + "inputs": { + "flake-utils": "flake-utils", + "nix-github-actions": "nix-github-actions", + "nixpkgs": [ + "devenv", + "cachix", + "devenv", + "cachix", + "devenv", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1692876271, + "narHash": "sha256-IXfZEkI0Mal5y1jr6IRWMqK8GW2/f28xJenZIPQqkY0=", + "owner": "nix-community", + "repo": "poetry2nix", + "rev": "d5006be9c2c2417dafb2e2e5034d83fabd207ee3", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "poetry2nix", + "type": "github" + } + }, + "pre-commit-hooks": { + "inputs": { + "flake-compat": [ + "devenv", + "nix" + ], + "flake-utils": "flake-utils_2", + "gitignore": [ + "devenv", + "nix" + ], + "nixpkgs": [ + "devenv", + "nix", + "nixpkgs" + ], + "nixpkgs-stable": [ + "devenv", + "nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1712897695, + "narHash": "sha256-nMirxrGteNAl9sWiOhoN5tIHyjBbVi5e2tgZUgZlK3Y=", + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "rev": "40e6053ecb65fcbf12863338a6dcefb3f55f1bf8", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "type": "github" + } + }, + "pre-commit-hooks_2": { + "inputs": { + "flake-compat": [ + "devenv", + "flake-compat" + ], + "gitignore": "gitignore", + "nixpkgs": [ + "devenv", + "nixpkgs" + ], + "nixpkgs-stable": "nixpkgs-stable" + }, + "locked": { + "lastModified": 1725513492, + "narHash": "sha256-tyMUA6NgJSvvQuzB7A1Sf8+0XCHyfSPRx/b00o6K0uo=", + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "rev": "7570de7b9b504cfe92025dd1be797bf546f66528", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "type": "github" + } + }, + "root": { + "inputs": { + "devenv": "devenv", + "nixpkgs": "nixpkgs_3" + } + }, + "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..5917356 --- /dev/null +++ b/flake.nix @@ -0,0 +1,134 @@ +{ + description = "F# Development Environment"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + devenv = { + url = "github:cachix/devenv"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = + inputs@{ + self, + devenv, + nixpkgs, + ... + }: + let + # System types to support. + supportedSystems = [ + "x86_64-linux" + "x86_64-darwin" + "aarch64-darwin" + ]; + + # Helper function to generate an attrset '{ x86_64-linux = f "x86_64-linux"; ... }'. + forAllSystems = nixpkgs.lib.genAttrs supportedSystems; + + # Nixpkgs instantiated for supported system types. + nixpkgsFor = forAllSystems ( + system: + import nixpkgs { + inherit system; + } + ); + in + { + packages = forAllSystems ( + system: + let + pkgs = nixpkgsFor."${system}"; + name = "sample"; + version = "0.1.0"; + in rec + { + # `nix build` + default = pkgs.buildDotnetModule { + pname = name; + version = version; + src = ./.; + projectFile = "src/App/App.fsproj"; + nugetDeps = ./deps.nix; + + dotnet-sdk = + with pkgs.dotnetCorePackages; + combinePackages [ + sdk_8_0 + ]; + dotnet-runtime = pkgs.dotnetCorePackages.sdk_8_0; + }; + + # nix build .#dockerImage + dockerImage = pkgs.dockerTools.buildLayeredImage { + name = "sample"; + tag = "latest"; + created = "now"; + contents = [ default ]; + config = { + Cmd = [ + "${default}/bin/App" + ]; + }; + }; + } + ); + + devShells = forAllSystems ( + system: + let + pkgs = nixpkgsFor."${system}"; + dotnet = + with pkgs.dotnetCorePackages; + combinePackages [ + sdk_8_0 + ]; + in + { + # `nix develop .#ci` + # reduce the number of packages to the bare minimum needed for CI + ci = pkgs.mkShell { + buildInputs = with pkgs; [ + git + just + dotnet + ]; + }; + + # `nix develop --impure` + default = devenv.lib.mkShell { + inherit inputs pkgs; + modules = [ + ( + { pkgs, lib, ... }: + { + packages = with pkgs; [ + bash + just + + # for dotnet + netcoredbg + fsautocomplete + fantomas + ]; + + languages.dotnet = { + enable = true; + package = dotnet; + }; + + # looks for the .env by default additionaly, there is .filename + # if an arbitrary file is desired + dotenv.enable = true; + } + ) + ]; + }; + } + ); + + # nix fmt + formatter = forAllSystems (system: nixpkgs.legacyPackages.${system}.nixfmt-rfc-style); + }; +} diff --git a/justfile b/justfile new file mode 100644 index 0000000..ea0a332 --- /dev/null +++ b/justfile @@ -0,0 +1,62 @@ +set export := true + +package_name := "Sample" +nuget_source := "https://api.nuget.org/v3/index.json" +nuget_api_key := env_var_or_default("NUGET_API_KEY", "None") +nuget_delete_key := env_var_or_default("NUGET_DELETE_KEY", "None") + +source := justfile_directory() + "/src" +tests := justfile_directory() + "/tests" +release := `git tag -l --sort=-creatordate | head -n 1` +replace := if os() == "linux" { "sed -i" } else { "sed -i '' -e" } + +# For lazy people +alias b := build +alias t := test +alias bnix := build-nix +alias gd := gen-deps + +# Lists all availiable targets +default: + @echo "PROJECT: {{package_name}} - RELEASE: {{release}}" + just --list + +# https://github.com/NixOS/nixpkgs/blob/master/doc/languages-frameworks/dotnet.section.md#generating-and-updating-nuget-dependencies-generating-and-updating-nuget-dependencies +# Generates nix dependencies for deps.nix +gen-deps: + dotnet restore --packages out + nix run nixpkgs#nuget-to-nix -- out > deps.nix + +# Builds the project +build: + dotnet restore + dotnet build + +# Builds the project (with Nix) +build-nix: + nix build + +# Runs testing suite +test: + ls {{tests}}/*.Tests/*.fsproj | xargs -L1 dotnet test --logger:trx + +# Packages current tag as a .Net release +pack: + @echo "PACKING RELEASE: {{release}}" + rm -f */bin/Release/*.nupkg + dotnet pack -c Release /p:Version=$(echo {{release}} | sed 's/v//g') + +# Pushes release to NUGET +[confirm("Are you sure you want to push the current release?")] +push: + @echo "Pushing release '{{release}}' to {{nuget_source}}" + dotnet nuget push */bin/Release/*.nupkg -k "{{nuget_api_key}}" -s {{nuget_source}} --skip-duplicate + +# Deletes a release +[confirm("Are you sure you want to delete the current release?")] +delete: + @echo "Removing release '{{release}}' from {{nuget_source}}" + dotnet nuget delete {{package_name}} $(echo {{release}} | sed 's/v//g') \ + -k "{{nuget_delete_key}}" \ + -s {{nuget_source}} \ + --non-interactive diff --git a/nuget.config b/nuget.config new file mode 100644 index 0000000..6f82b6b --- /dev/null +++ b/nuget.config @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/App/App.fsproj b/src/App/App.fsproj new file mode 100644 index 0000000..54b29c4 --- /dev/null +++ b/src/App/App.fsproj @@ -0,0 +1,16 @@ + + + + Exe + net8.0 + + + + + + + + + + + diff --git a/src/App/Program.fs b/src/App/Program.fs new file mode 100644 index 0000000..0d7e47f --- /dev/null +++ b/src/App/Program.fs @@ -0,0 +1,8 @@ +open System + +open FsToolkit.ErrorHandling + +[] +let main _ = + printfn "Test" + 0 diff --git a/tests/App.Tests/App.Tests.fsproj b/tests/App.Tests/App.Tests.fsproj new file mode 100644 index 0000000..0e110fb --- /dev/null +++ b/tests/App.Tests/App.Tests.fsproj @@ -0,0 +1,27 @@ + + + + net8.0 + + false + false + true + + + + + + + + + + + + + + + + + + + diff --git a/tests/App.Tests/Program.fs b/tests/App.Tests/Program.fs new file mode 100644 index 0000000..fdc31cd --- /dev/null +++ b/tests/App.Tests/Program.fs @@ -0,0 +1 @@ +module Program = let [] main _ = 0 diff --git a/tests/App.Tests/Tests.fs b/tests/App.Tests/Tests.fs new file mode 100644 index 0000000..b908e37 --- /dev/null +++ b/tests/App.Tests/Tests.fs @@ -0,0 +1,8 @@ +module Tests + +open System +open Xunit + +[] +let ``My test`` () = + Assert.True(true)