Skip to content
Draft
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
3 changes: 3 additions & 0 deletions FSharp.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@
<Project Path="src/FSharp.Build/FSharp.Build.fsproj">
<BuildType Solution="Proto|*" Project="Release" />
</Project>
<Project Path="tests/UseLocalCompiler.FSharp.Build.Tasks/UseLocalCompiler.FSharp.Build.Tasks.fsproj">
<BuildType Solution="Proto|*" Project="Release" />
</Project>
<Project Path="src/FSharp.Compiler.Interactive.Settings/FSharp.Compiler.Interactive.Settings.fsproj">
<BuildType Solution="Proto|*" Project="Release" />
</Project>
Expand Down
59 changes: 23 additions & 36 deletions UseLocalCompiler.Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,45 +1,32 @@
<Project>
<!-- Import target frameworks from single source of truth.
For regression tests: TargetFrameworks.props is co-located with this props file.
For local usage: use eng/TargetFrameworks.props relative to this file's directory. -->
<Import Project="$(MSBuildThisFileDirectory)TargetFrameworks.props" Condition="Exists('$(MSBuildThisFileDirectory)TargetFrameworks.props')" />
<Import Project="$(MSBuildThisFileDirectory)eng/TargetFrameworks.props" Condition="!Exists('$(MSBuildThisFileDirectory)TargetFrameworks.props')" />

<PropertyGroup>
<LoadLocalFSharpBuild Condition="'$(LoadLocalFSharpBuild)' == ''">False</LoadLocalFSharpBuild>

<LocalFSharpCompilerConfiguration Condition="'$(LocalFSharpCompilerConfiguration)' == ''">Release</LocalFSharpCompilerConfiguration>
<LocalFSharpCompilerPath Condition="'$(LocalFSharpCompilerPath)' == ''">$(MSBuildThisFileDirectory)</LocalFSharpCompilerPath>
<LocalFSharpBuildTaskAssembly Condition="'$(LocalFSharpBuildTaskAssembly)' == ''">$([System.IO.Path]::Combine('$(LocalFSharpCompilerPath)', 'artifacts', 'bin', 'UseLocalCompiler.FSharp.Build.Tasks', '$(LocalFSharpCompilerConfiguration)', 'netstandard2.0', 'UseLocalCompiler.FSharp.Build.Tasks.dll'))</LocalFSharpBuildTaskAssembly>
</PropertyGroup>

<LocalFSharpCompilerPath Condition=" '$(LocalFSharpCompilerPath)' == '' ">$(MSBuildThisFileDirectory)</LocalFSharpCompilerPath>

<DisableImplicitFSharpCoreReference>true</DisableImplicitFSharpCoreReference>
<DisableAutoSetFscCompilerPath>true</DisableAutoSetFscCompilerPath>
<FscToolPath Condition="'$(FscToolPath)' == ''">$([System.IO.Path]::GetDirectoryName($(DOTNET_HOST_PATH)))</FscToolPath>
<FscToolExe Condition="'$(FscToolExe)' == ''">$([System.IO.Path]::GetFileName($(DOTNET_HOST_PATH)))</FscToolExe>

<DotnetFscCompilerPath>$(LocalFSharpCompilerPath)/artifacts/bin/fsc/$(LocalFSharpCompilerConfiguration)/$(FSharpNetCoreProductTargetFramework)/fsc.dll</DotnetFscCompilerPath>
<Fsc_DotNET_DotnetFscCompilerPath>$(LocalFSharpCompilerPath)/artifacts/bin/fsc/$(LocalFSharpCompilerConfiguration)/$(FSharpNetCoreProductTargetFramework)/fsc.dll</Fsc_DotNET_DotnetFscCompilerPath>
<UsingTask TaskName="Fsc" AssemblyFile="$(LocalFSharpBuildTaskAssembly)" Override="true" Condition="'$(LoadLocalFSharpBuild)' == 'True'" />

<FSharpPreferNetFrameworkTools>False</FSharpPreferNetFrameworkTools>
<FSharpPrefer64BitTools>True</FSharpPrefer64BitTools>
</PropertyGroup>
<Target Name="UseLocalFSharpCoreReference" BeforeTargets="ResolveAssemblyReferences" Condition="'$(LoadLocalFSharpBuild)' == 'True' and '$(MSBuildProjectExtension)' == '.fsproj'">
<PropertyGroup>
<_LocalFSharpCoreTargetFramework>netstandard2.1</_LocalFSharpCoreTargetFramework>
<_TargetFrameworkVersionWithoutV>$(TargetFrameworkVersion)</_TargetFrameworkVersionWithoutV>
<_TargetFrameworkVersionWithoutV Condition="'$(_TargetFrameworkVersionWithoutV)' != '' and $([System.String]::Copy('$(_TargetFrameworkVersionWithoutV)').StartsWith('v'))">$([System.String]::Copy('$(_TargetFrameworkVersionWithoutV)').Substring(1))</_TargetFrameworkVersionWithoutV>
<_LocalFSharpCoreTargetFramework Condition="'$(TargetFrameworkIdentifier)' == '.NETFramework'">netstandard2.0</_LocalFSharpCoreTargetFramework>
<_LocalFSharpCoreTargetFramework Condition="'$(TargetFrameworkIdentifier)' == '.NETStandard' and '$(_TargetFrameworkVersionWithoutV)' != '' and $([System.Version]::Parse('$(_TargetFrameworkVersionWithoutV)')) &lt; $([System.Version]::Parse('2.1'))">netstandard2.0</_LocalFSharpCoreTargetFramework>
<_LocalFSharpCoreTargetFramework Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp' and '$(_TargetFrameworkVersionWithoutV)' != '' and $([System.Version]::Parse('$(_TargetFrameworkVersionWithoutV)')) &lt; $([System.Version]::Parse('3.0'))">netstandard2.0</_LocalFSharpCoreTargetFramework>
<LocalFSharpCoreAssemblyPath>$([System.IO.Path]::Combine('$(LocalFSharpCompilerPath)', 'artifacts', 'bin', 'FSharp.Core', '$(LocalFSharpCompilerConfiguration)', '$(_LocalFSharpCoreTargetFramework)', 'FSharp.Core.dll'))</LocalFSharpCoreAssemblyPath>
</PropertyGroup>

<!--
Use FSharpTargetsShim to redirect the SDK to use the locally built F# targets.
This replaces all the individual UsingTask overrides since Microsoft.FSharp.NetSdk.targets
imports Microsoft.FSharp.Targets which registers all the F# build tasks.
See: https://github.com/dotnet/sdk/blob/main/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.FSharpTargetsShim.targets
-->
<PropertyGroup Condition="'$(LoadLocalFSharpBuild)' == 'True'">
<LocalFSharpBuildBinPath>$(LocalFSharpCompilerPath)/artifacts/bin/fsc/$(LocalFSharpCompilerConfiguration)/$(FSharpNetCoreProductTargetFramework)</LocalFSharpBuildBinPath>
<FSharpBuildAssemblyFile>$(LocalFSharpBuildBinPath)/FSharp.Build.dll</FSharpBuildAssemblyFile>
<FSharpTargetsPath>$(LocalFSharpBuildBinPath)/Microsoft.FSharp.Targets</FSharpTargetsPath>
<FSharpPropsShim>$(LocalFSharpBuildBinPath)/Microsoft.FSharp.NetSdk.props</FSharpPropsShim>
<FSharpTargetsShim>$(LocalFSharpBuildBinPath)/Microsoft.FSharp.NetSdk.targets</FSharpTargetsShim>
<FSharpOverridesTargetsShim>$(LocalFSharpBuildBinPath)/Microsoft.FSharp.Overrides.NetSdk.targets</FSharpOverridesTargetsShim>
</PropertyGroup>
<Error Condition="!Exists('$(LocalFSharpCoreAssemblyPath)')" Text="The local FSharp.Core override could not be found at '$(LocalFSharpCoreAssemblyPath)'." />

<ItemGroup>
<Reference Include="$(LocalFSharpCompilerPath)/artifacts/bin/FSharp.Core/$(LocalFSharpCompilerConfiguration)/netstandard2.0/FSharp.Core.dll" />
</ItemGroup>
<ItemGroup>
<Reference Remove="@(Reference)" Condition="'%(Reference.Identity)' == 'FSharp.Core' or '%(Reference.FileName)' == 'FSharp.Core' or '%(Reference.NuGetPackageId)' == 'FSharp.Core'" />
<Reference Include="FSharp.Core">
<HintPath>$(LocalFSharpCoreAssemblyPath)</HintPath>
<Private>true</Private>
</Reference>
</ItemGroup>
</Target>
</Project>
3 changes: 3 additions & 0 deletions VisualFSharp.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@
<Project Path="src/FSharp.Build/FSharp.Build.fsproj">
<BuildType Solution="Proto|*" Project="Release" />
</Project>
<Project Path="tests/UseLocalCompiler.FSharp.Build.Tasks/UseLocalCompiler.FSharp.Build.Tasks.fsproj">
<BuildType Solution="Proto|*" Project="Release" />
</Project>
<Project Path="src/FSharp.Compiler.Interactive.Settings/FSharp.Compiler.Interactive.Settings.fsproj">
<BuildType Solution="Proto|*" Project="Release" />
</Project>
Expand Down
22 changes: 19 additions & 3 deletions azure-pipelines-PR.yml
Original file line number Diff line number Diff line change
Expand Up @@ -656,18 +656,26 @@ stages:
publishLocation: pipeline
condition: succeeded()

- task: PublishPipelineArtifact@1
displayName: Publish UseLocalCompiler task artifacts for Regression Tests
inputs:
targetPath: '$(Build.SourcesDirectory)/artifacts/bin/UseLocalCompiler.FSharp.Build.Tasks'
artifactName: 'UseLocalCompilerTaskArtifacts'
publishLocation: pipeline
condition: succeeded()

- pwsh: |
# Stage UseLocalCompiler props and TargetFrameworks.props together
# Stage the local test props file and TargetFrameworks.props together
$stagingDir = "$(Build.SourcesDirectory)/UseLocalCompilerPropsStaging"
New-Item -ItemType Directory -Force -Path $stagingDir | Out-Null
Copy-Item "$(Build.SourcesDirectory)/UseLocalCompiler.Directory.Build.props" -Destination $stagingDir
Copy-Item "$(Build.SourcesDirectory)/eng/TargetFrameworks.props" -Destination $stagingDir
Write-Host "Staged files for UseLocalCompilerProps artifact:"
Get-ChildItem $stagingDir -Name
displayName: Stage UseLocalCompiler props files
displayName: Stage local test props files

- task: PublishPipelineArtifact@1
displayName: Publish UseLocalCompiler props file for Regression Tests
displayName: Publish local test props file for Regression Tests
inputs:
targetPath: '$(Build.SourcesDirectory)/UseLocalCompilerPropsStaging'
artifactName: 'UseLocalCompilerProps'
Expand Down Expand Up @@ -892,6 +900,14 @@ stages:
commit: e81e00a464b9d35d272f61708a1a0bfbf487b6d5
buildScript: dotnet build Nu.sln --configuration Release
displayName: Nu_Build
- repo: Lanayx/Oxpecker
commit: cb7e4b83e3f2aba7ded46b17a36d1aea8c13c292
buildScript: dotnet build .\Oxpecker.slnx -bl
displayName: Oxpecker_Build
- repo: Lanayx/Oxpecker
commit: cb7e4b83e3f2aba7ded46b17a36d1aea8c13c292
buildScript: dotnet build .\Oxpecker.Solid.slnx -bl
displayName: Oxpecker_Solid_Build
# Design-time provider packaging oracle. Pin d8aba70 (pre-workaround base of FSharp.Data.GraphQL#583):
# it uses the bare IsFSharpDesignTimeProvider gesture, so the client pack drops the provider without this fix.
# Linux-only: the $PWD local feed and the grep content assertion need bash. (nupkg entry names are stored
Expand Down
14 changes: 8 additions & 6 deletions eng/scripts/PrepareRepoForRegressionTesting.fsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/// Script to inject UseLocalCompiler.Directory.Build.props import into a third-party repository's Directory.Build.props
/// Usage: dotnet fsi PrepareRepoForRegressionTesting.fsx <path-to-UseLocalCompiler.Directory.Build.props>
/// Script to inject a local F# test props import into a third-party repository's Directory.Build.props
/// Usage: dotnet fsi PrepareRepoForRegressionTesting.fsx <path-to-local-test-props-file>

open System
open System.IO
Expand All @@ -14,21 +14,23 @@ let useLocalCompilerPropsPath =
if scriptArgs.Length > 0 then
scriptArgs.[0]
else
failwith "Usage: dotnet fsi PrepareRepoForRegressionTesting.fsx <path-to-UseLocalCompiler.Directory.Build.props>"
failwith "Usage: dotnet fsi PrepareRepoForRegressionTesting.fsx <path-to-local-test-props-file>"

printfn "PrepareRepoForRegressionTesting.fsx"
printfn "==================================="
printfn "UseLocalCompiler props path: %s" useLocalCompilerPropsPath

if not (File.Exists(useLocalCompilerPropsPath)) then
failwithf "UseLocalCompiler.Directory.Build.props not found at: %s" useLocalCompilerPropsPath
failwithf "Local test props file not found at: %s" useLocalCompilerPropsPath

printfn "✓ UseLocalCompiler.Directory.Build.props found"
printfn "✓ Local test props file found"

let absolutePropsPath =
Path.GetFullPath(useLocalCompilerPropsPath).Replace("\\", "/")
printfn "Absolute path: %s" absolutePropsPath

let propsFileName = Path.GetFileName(absolutePropsPath)

if File.Exists(propsFilePath) then
printfn "Directory.Build.props exists, modifying it..."

Expand All @@ -40,7 +42,7 @@ if File.Exists(propsFilePath) then
if isNull projectElement then
failwith "Could not find Project element in Directory.Build.props"

let xpath = "//Import[contains(@Project, 'UseLocalCompiler.Directory.Build.props')]"
let xpath = sprintf "//Import[contains(@Project, '%s')]" propsFileName
let existingImport = doc.SelectSingleNode(xpath)

if isNull existingImport then
Expand Down
8 changes: 7 additions & 1 deletion eng/templates/regression-test-jobs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,13 @@ jobs:
downloadPath: '$(Pipeline.Workspace)/FSharpCompiler/artifacts/bin/FSharp.Core'

- task: DownloadPipelineArtifact@2
displayName: Download UseLocalCompiler props
displayName: Download UseLocalCompiler task artifacts
inputs:
artifactName: 'UseLocalCompilerTaskArtifacts'
downloadPath: '$(Pipeline.Workspace)/FSharpCompiler/artifacts/bin/UseLocalCompiler.FSharp.Build.Tasks'

- task: DownloadPipelineArtifact@2
displayName: Download local test props
inputs:
artifactName: 'UseLocalCompilerProps'
downloadPath: '$(Pipeline.Workspace)/Props'
Expand Down
118 changes: 118 additions & 0 deletions tests/UseLocalCompiler.FSharp.Build.Tasks/Fsc.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
namespace UseLocalCompiler.FSharp.Build.Tasks

open System
open System.IO
open Microsoft.Build.Framework
open Microsoft.Build.Utilities

type public Fsc() =
inherit FSharp.Build.Fsc()

static member private TryGetBuildContext() =
try
let assemblyDir = DirectoryInfo(Path.GetDirectoryName(typeof<Fsc>.Assembly.Location))

if
not (isNull assemblyDir.Parent)
&& not (isNull assemblyDir.Parent.Parent)
&& not (isNull assemblyDir.Parent.Parent.Parent)
&& not (isNull assemblyDir.Parent.Parent.Parent.Parent)
&& not (isNull assemblyDir.Parent.Parent.Parent.Parent.Parent)
&& StringComparer.OrdinalIgnoreCase.Equals(assemblyDir.Parent.Parent.Parent.Name, "bin")
&& StringComparer.OrdinalIgnoreCase.Equals(assemblyDir.Parent.Parent.Parent.Parent.Name, "artifacts")
then
Some(assemblyDir.Parent.Parent.Parent.Parent.Parent.FullName, assemblyDir.Parent.Name)
else
None
with _ ->
None

static member private TryGetLocalDotnetFscCompilerPath(repoRoot: string, configuration: string) =
let fscOutputRoot = Path.Combine(repoRoot, "artifacts", "bin", "fsc", configuration)

if Directory.Exists fscOutputRoot then
fscOutputRoot
|> Directory.GetDirectories
|> Array.choose (fun dir ->
let candidate = Path.Combine(dir, "fsc.dll")

if File.Exists candidate then
Some candidate
else
None)
|> function
| [| candidate |] -> Some candidate
| _ -> None
else
None

static member private IsFSharpCoreReference(item: ITaskItem) =
let itemName = Path.GetFileNameWithoutExtension(item.ItemSpec)
StringComparer.OrdinalIgnoreCase.Equals(itemName, "FSharp.Core")

static member private TryGetReferenceTargetFramework(item: ITaskItem) =
try
Path.GetDirectoryName(item.ItemSpec)
|> Path.GetFileName
|> function
| null
| "" -> None
| tfm -> Some tfm
with _ ->
None

static member private TryGetLocalFSharpCoreReference(responseFileCommands: string) =
let referenceLines =
responseFileCommands.Split([| '\r'; '\n' |], StringSplitOptions.RemoveEmptyEntries)
|> Array.filter (fun line -> line.StartsWith("-r:", StringComparison.Ordinal))

let fsharpCoreTargetFrameworks =
referenceLines
|> Array.map (fun line -> line.Substring(3).Trim([| '"' |]))
|> Array.filter (fun path -> Fsc.IsFSharpCoreReference(TaskItem(path) :> ITaskItem))
|> Array.choose (fun path -> Fsc.TryGetReferenceTargetFramework(TaskItem(path) :> ITaskItem))
|> Array.distinct

match Fsc.TryGetBuildContext(), fsharpCoreTargetFrameworks with
| Some(repoRoot, configuration), [| fsharpCoreTargetFramework |] ->
let localFSharpCorePath =
Path.Combine(repoRoot, "artifacts", "bin", "FSharp.Core", configuration, fsharpCoreTargetFramework, "FSharp.Core.dll")

if File.Exists localFSharpCorePath then
Some localFSharpCorePath
else
None
| _ -> None

override _.GenerateCommandLineCommands() =
match Fsc.TryGetBuildContext() with
| Some(repoRoot, configuration) ->
match Fsc.TryGetLocalDotnetFscCompilerPath(repoRoot, configuration) with
| Some localDotnetFscCompilerPath -> localDotnetFscCompilerPath
| None -> base.GenerateCommandLineCommands()
| None -> base.GenerateCommandLineCommands()

override _.GenerateResponseFileCommands() =
let responseFileCommands = base.GenerateResponseFileCommands()

match Fsc.TryGetLocalFSharpCoreReference(responseFileCommands) with
| None -> responseFileCommands
| Some localFSharpCorePath ->
let mutable emittedLocalFSharpCoreReference = false

responseFileCommands.Split([| '\r'; '\n' |], StringSplitOptions.RemoveEmptyEntries)
|> Array.choose (fun line ->
if line.StartsWith("-r:", StringComparison.Ordinal) then
let referencePath = line.Substring(3).Trim([| '"' |])

if Fsc.IsFSharpCoreReference(TaskItem(referencePath) :> ITaskItem) then
if emittedLocalFSharpCoreReference then
None
else
emittedLocalFSharpCoreReference <- true
Some("-r:" + localFSharpCorePath)
else
Some line
else
Some line)
|> String.concat Environment.NewLine
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. -->
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<OutputType>Library</OutputType>
<AssemblyName>UseLocalCompiler.FSharp.Build.Tasks</AssemblyName>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<DisableImplicitFSharpCoreReference>true</DisableImplicitFSharpCoreReference>
</PropertyGroup>

<PropertyGroup Condition="'$(BUILDING_USING_DOTNET)' == 'true'">
<OutputPath>$(ArtifactsDir)/bin/$(MSBuildProjectName)/$(Configuration)/</OutputPath>
<IntermediateOutputPath>$(ArtifactsDir)obj/$(MSBuildProjectName)/$(Configuration)/</IntermediateOutputPath>
</PropertyGroup>

<ItemGroup>
<Compile Include="Fsc.fs" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="$(FSharpSourcesRoot)\FSharp.Build\FSharp.Build.fsproj" />
<ProjectReference Include="$(FSharpSourcesRoot)\FSharp.Core\FSharp.Core.fsproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ private static string CreateStandaloneProjectFile()

return $@"<Project Sdk=""Microsoft.NET.Sdk"">
<PropertyGroup>
<LoadLocalFSharpBuild>True</LoadLocalFSharpBuild>
<LocalFSharpCompilerConfiguration>Debug</LocalFSharpCompilerConfiguration>
<LocalFSharpCompilerPath>{RepoRoot}</LocalFSharpCompilerPath>
</PropertyGroup>
Expand Down
Loading