Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,19 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ ubuntu-latest ]
os: [ ubuntu-latest, windows-latest ]
steps:
- name: Checkout
uses: actions/checkout@v5

- name: Setup .NET Core
uses: actions/setup-dotnet@v4

- name: Restore
run: dotnet restore

- name: Build
run: dotnet build -c Release --no-restore

- name: Test
run: dotnet test -c Release
run: dotnet test -c Release --no-build
34 changes: 34 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Test

on:
push:
tags:
- "[0-9]+.[0-9]+.[0-9]+"

env:
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true

jobs:
publish:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4

- name: Restore dependencies
run: dotnet restore

- name: Build
run: dotnet build -c Release --no-restore

- name: Test
run: dotnet test -c Release --no-build

- name: Pack
run: dotnet pack

- name: Push to nuget.org
env:
VERSION: ${{ github.ref_name }}
run: dotnet nuget push "./artifacts/package/release/runfs.${VERSION}.nupkg" -k ${{ secrets.NUGET_KEY }}
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# CHANGELOG

## 1.0.3

### changed

* Direct calls to msbuild, making runfs faster. But still no virtual (in-memory) project file due to sdk dll hell.

## 1.0.2

### added

* Initial version
File renamed without changes.
4 changes: 3 additions & 1 deletion global.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
{
"sdk": {
"version": "9.0.304"
"version": "9.0.304",
"rollForward": "latestMinor",
"allowPrerelease": true
}
}
71 changes: 71 additions & 0 deletions src/Runfs/Build.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
module Runfs.Build

open Microsoft.Build.Construction
open Microsoft.Build.Definition
open Microsoft.Build.Evaluation
open Microsoft.Build.Execution
open Microsoft.Build.Framework
open Microsoft.Build.Logging
open Microsoft.Build.Locator
open System
open System.IO
open System.Xml
open Runfs.ProjectFile

type Project =
{buildManager: BuildManager; projectInstance: ProjectInstance}
interface IDisposable with
member this.Dispose() =
this.buildManager.EndBuild()
this.projectInstance.FullPath |> File.Delete

type MSBuildError = MSBuildError of target: string * result: string

let initMSBuild() = MSBuildLocator.RegisterDefaults() |> ignore

let createProject verbose projectFilePath (projectFileText: string) : Project =
let verbosity = if verbose then "m" else "q"
let loggerArgs = [|$"-verbosity:{verbosity}"; "-tl:off"; "NoSummary"|]
let consoleLogger = TerminalLogger.CreateTerminalOrConsoleLogger loggerArgs
let loggers = [|consoleLogger|]
let globalProperties =
dict [
]
let projectCollection = new ProjectCollection(
globalProperties,
loggers,
ToolsetDefinitionLocations.Default)
let options = ProjectOptions()
options.ProjectCollection <- projectCollection
options.GlobalProperties <- globalProperties

// let reader = new StringReader(projectFileText)
// let xmlReader = XmlReader.Create reader
// let projectRoot = ProjectRootElement.Create(xmlReader, projectCollection)
// projectRoot.FullPath <- projectFilePath
// let projectInstance = ProjectInstance.FromProjectRootElement(projectRoot, options)

File.WriteAllText(projectFilePath, projectFileText)
let projectInstance = ProjectInstance.FromFile(projectFilePath, options)

let parameters = BuildParameters projectCollection
parameters.Loggers <- loggers
parameters.LogTaskInputs <- false
let buildManager = BuildManager.DefaultBuildManager
buildManager.BeginBuild parameters
{buildManager = buildManager; projectInstance = projectInstance}

let build target project =
let flags =
BuildRequestDataFlags.ClearCachesAfterBuild
||| BuildRequestDataFlags.SkipNonexistentTargets
||| BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports
||| BuildRequestDataFlags.FailOnUnresolvedSdk
let buildRequest =
new BuildRequestData(project.projectInstance, [|target|], null, flags)

let buildResult = project.buildManager.BuildRequest buildRequest
if buildResult.OverallResult = BuildResultCode.Success then
Ok()
else
Error(MSBuildError(target, string buildResult))
2 changes: 1 addition & 1 deletion src/Runfs/Dependencies.fs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ open Runfs.Directives
open Runfs.Utilities

let RuntimeVersion = Environment.Version
let SdkVersion = Runtime.InteropServices.RuntimeInformation.FrameworkDescription
let SdkVersion = "t.b.d" // TODO (needed?)
let TargetFramework = $"net{RuntimeVersion.Major}.{RuntimeVersion.Minor}"

let PotentialImplicitBuildFileNames = [
Expand Down
6 changes: 2 additions & 4 deletions src/Runfs/Program.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

open Runfs.Runfs
open Runfs.Directives
open Runfs.Build

[<EntryPoint>]
let main argv =
Expand Down Expand Up @@ -37,10 +38,7 @@ let main argv =
| CaughtException ex -> [$"Unexpected: {ex.Message}"]
| InvalidSourcePath s -> [$"Invalid source path: {s}"]
| InvalidSourceDirectory s -> [$"Invalid source directory: {s}"]
| RestoreError(stdoutLines, stderrLines) ->
"Restore error" :: indent stdoutLines @ indent stderrLines
| BuildError(stdoutLines, stderrLines) ->
"Build error" :: indent stdoutLines @ indent stderrLines
| BuildError(MSBuildError(target, result)) -> [$"MSBuild {target} error: {result}"]
| DirectiveError parseErrors ->
let getParseErrorString parseError =
let prefix n = $" Line %3d{n}: "
Expand Down
9 changes: 5 additions & 4 deletions src/Runfs/ProjectFile.fs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ let private escape str = SecurityElement.Escape str |> string

let private sdkLine project (name, version) =
match version with
| Some v -> $""" <Import Project="{project}" Sdk="{escape name}" Version="{escape v}" />"""
| None -> $""" <Import Project="{project}" Sdk="{escape name}" />"""
| Some v -> $""" <Import Project="{escape project}" Sdk="{escape name}" Version="{escape v}" />"""
| None -> $""" <Import Project="{escape project}" Sdk="{escape name}" />"""

let private propertyLine (name, version) =
$""" <{name}>{escape version}</{name}>"""
Expand All @@ -30,7 +30,7 @@ let private packageLine (name, version) =
let createProjectFileLines directives entryPointSourceFullPath artifactsPath assemblyName =
let sdks =
match directives |> List.choose (function Sdk(n, v) -> Some(n, v) | _ -> None) with
| [] -> ["Microsoft.NET.Sdk", None]
| [] -> ["Microsoft.NET.Sdk", None] //DODO verison?
| d -> d
let properties =
directives |> List.choose (function Property(n, v) -> Some(n.ToLowerInvariant(), v) | _ -> None) |> Map
Expand All @@ -41,10 +41,11 @@ let createProjectFileLines directives entryPointSourceFullPath artifactsPath ass
[
"<Project>"
" <PropertyGroup>"
$""" <AssemblyName>{assemblyName}</AssemblyName>"""
$""" <AssemblyName>{escape assemblyName}</AssemblyName>"""
" <UseArtifactsOutput>true</UseArtifactsOutput>"
" <IncludeProjectNameInArtifactsPaths>false</IncludeProjectNameInArtifactsPaths>"
$""" <ArtifactsPath>{escape artifactsPath}</ArtifactsPath>"""
$""" <FileBasedProgram>true</FileBasedProgram>"""
" </PropertyGroup>"
yield! sdks |> List.map (sdkLine "Sdk.props")
" <PropertyGroup>"
Expand Down
79 changes: 31 additions & 48 deletions src/Runfs/Runfs.fs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ open Runfs.Directives
open Runfs.ProjectFile
open Runfs.Dependencies
open Runfs.Utilities
open Runfs.Build

type RunfsError =
| CaughtException of Exception
| InvalidSourcePath of string
| InvalidSourceDirectory of string
| DirectiveError of ParseError list
| RestoreError of stdout: string list * stderr: string list
| BuildError of stdout: string list * stderr: string list
| BuildError of MSBuildError

let ThisPackageName = "Runfs"
let DependenciesHashFileName = "dependencies.hash"
Expand Down Expand Up @@ -58,11 +58,12 @@ let run (options, sourcePath, args) =
let showTimings = Set.contains "time" options
let verbose = Set.contains "verbose" options
let noDependencyCheck = Set.contains "no-dependency-check" options
let withOutput = Set.contains "with-output" options
let inline guardAndTime name f = guardAndTime showTimings name f

initMSBuild()

result {
let! fullSourcePath, fullSourceDir, artifactsDir, projectFilePath,
let! fullSourcePath, fullSourceDir, artifactsDir, virtualProjectFilePath,
savedProjectFilePath, dependenciesHashPath, sourceHashPath, dllPath =
guardAndTime "creating paths" <| fun () -> result {
do! File.Exists sourcePath |> Result.requireTrue (InvalidSourcePath sourcePath)
Expand Down Expand Up @@ -106,7 +107,7 @@ let run (options, sourcePath, args) =
return computeDependenciesHash (string fullSourceDir) directives
}

let! dependenciesChanged, sourceChanged, noDll = guardAndTime "computing build level" <| fun () ->
let! dependenciesChanged, sourceChanged, noExecutable = guardAndTime "computing build level" <| fun () ->
let dependenciesChanged =
if noDependencyCheck then
false
Expand All @@ -119,55 +120,37 @@ let run (options, sourcePath, args) =
let noDll = not (File.Exists dllPath)
Ok (dependenciesChanged, sourceChanged, noDll)

if dependenciesChanged || sourceChanged || noDll then
if dependenciesChanged || noExecutable then
do! guardAndTime "creating and writing project file" <| fun () ->
let projectFileLines = createProjectFileLines directives fullSourcePath artifactsDir AssemblyName
File.WriteAllLines(savedProjectFilePath, projectFileLines) |> Ok

if dependenciesChanged || sourceChanged || noExecutable then
use! project = guardAndTime "creating msbuild project instance" <| fun () ->
let projectFileText = File.ReadAllText savedProjectFilePath
createProject verbose virtualProjectFilePath projectFileText |> Ok

if dependenciesChanged || noExecutable then
do! guardAndTime "running msbuild restore" <| fun () -> result {
File.Delete dependenciesHashPath
do! build "restore" project |> Result.mapError BuildError
}

if dependenciesChanged || noDll then
do! guardAndTime "running dotnet restore" <| fun () ->
File.Delete dependenciesHashPath
if File.Exists projectFilePath then File.Delete projectFilePath
File.Copy(savedProjectFilePath, projectFilePath)
let args = [
"restore"
if not verbose then "-v:q"
projectFilePath
]
let exitCode, stdoutLines, stderrLines =
runCommandCollectOutput "dotnet" args fullSourceDir
File.Delete projectFilePath
if exitCode <> 0 then Error(RestoreError(stdoutLines, stderrLines)) else Ok()

if sourceChanged || dependenciesChanged || noDll then
do! guardAndTime "running dotnet build" <| fun () ->
if File.Exists projectFilePath then File.Delete projectFilePath
File.Copy(savedProjectFilePath, projectFilePath)
let args = [
"build"
"--no-restore"
"-consoleLoggerParameters:NoSummary"
if not verbose then "-v:q"
projectFilePath
]
let exitCode, stdoutLines, stderrLines =
runCommandCollectOutput "dotnet" args fullSourceDir
File.Delete projectFilePath
if exitCode <> 0 then Error(BuildError(stdoutLines, stderrLines)) else Ok()

if dependenciesChanged then
do! guardAndTime "saving dependencies hash" <| fun () ->
File.WriteAllText(dependenciesHashPath, dependenciesHash) |> Ok

if sourceChanged then
do! guardAndTime "saving source hash" <| fun () ->
File.WriteAllText(sourceHashPath, sourceHash) |> Ok
do! guardAndTime "running dotnet build" <| fun () -> result {
File.Delete sourceHash
do! build "build" project |> Result.mapError BuildError
}

if dependenciesChanged then
do! guardAndTime "saving dependencies hash" <| fun () ->
File.WriteAllText(dependenciesHashPath, dependenciesHash) |> Ok

if sourceChanged then
do! guardAndTime "saving source hash" <| fun () ->
File.WriteAllText(sourceHashPath, sourceHash) |> Ok

let! exitCode = guardAndTime "executing program" <| fun () ->
if withOutput then
runCommandCollectOutput "dotnet" (dllPath::args) "." |> Ok
else
runCommand "dotnet" (dllPath::args) "." |> Ok
runCommand "dotnet" (dllPath::args) "." |> Ok

return exitCode
}
Expand Down
13 changes: 9 additions & 4 deletions src/Runfs/Runfs.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<PropertyGroup>
<!-- General -->
<AssemblyName>Runfs</AssemblyName>
<Version>1.0.2</Version>
<Version>1.0.3</Version>
<Description>"dotnet run app.cs" functionality for F#.</Description>
<Copyright>Copyright 2025 by Martin521</Copyright>
<Authors>Martin521 and contributors</Authors>
Expand All @@ -14,7 +14,7 @@
<PackageTags>F#</PackageTags>
<PackAsTool>True</PackAsTool>
<ToolCommandName>runfs</ToolCommandName>
<!-- <PackageReleaseNotes>https://github.com/Martin521/Runfs/RELEASE_NOTES.md</PackageReleaseNotes> -->
<PackageReleaseNotes>https://github.com/Martin521/Runfs/CHANGELOG.md</PackageReleaseNotes>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<PackageIcon>
</PackageIcon>
Expand All @@ -29,13 +29,18 @@
<Compile Include="Directives.fs" />
<Compile Include="Dependencies.fs" />
<Compile Include="ProjectFile.fs" />

<Compile Include="Build.fs" />
<Compile Include="Runfs.fs" />
<Compile Include="Program.fs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="FSharp.SystemTextJson" Version="1.4.36" />
<PackageReference Include="FsToolkit.ErrorHandling" Version="5.0.1" />
<!-- <PackageReference Include="Microsoft.Build" Version="17.14.8" /> -->
<PackageReference Include="Microsoft.Build" Version="17.14.8" ExcludeAssets="runtime" />
<PackageReference Include="Microsoft.Build.Locator" Version="1.9.1" />
<!-- <PackageReference Include="Microsoft.Build" Version="17.15.0-preview-25277-114" />
<PackageReference Include="Microsoft.Build.Framework" Version="17.15.0-preview-25277-114" />
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="17.15.0-preview-25277-114" />
<PackageReference Include="Microsoft.Build.Runtime" Version="17.15.0-preview-25277-114" /> -->
</ItemGroup>
</Project>
Loading
Loading