fsprojects / FAKE

FAKE - F# Make
https://fake.build
Other
1.28k stars 581 forks source link

Async calls make script invalid #2650

Closed otto-gebb closed 2 years ago

otto-gebb commented 2 years ago

Description

If I make a call to ReadAsStringAsync, the script fails with the following error:

Script is not valid: unknown (1,0)-(1,0): Error FS0193: The module/namespace 'System' from compilation unit 'netstandard' did not contain the namespace, module or type 'IAsyncDisposable'

Repro steps

  1. Create a file build.fsx with the following content:

    #r "paket:
    nuget Fake.Core.Target //"
    
    #if !FAKE
    #load "./.fake/build.fsx/intellisense.fsx"
    #endif
    
    open Fake.Core
    open System.Net.Http
    open System.Net.Http.Headers
    open System.Threading.Tasks
    
    let myCall (response: HttpResponseMessage) : Task<string> =
       task {
         let! content = response.Content.ReadAsStringAsync()
         return content
       }
    
    // Default target
    Target.create "Default" (fun _ ->
       Trace.trace "Hello World from FAKE"
    )
    
    // start build
    Target.runOrDefault "Default"
  2. Run the script like this: fake run build.fsx -t Default

Expected behavior

The script runs successfully and outputs "Hello World from FAKE".

Actual behavior

The fake command outputs the following:

The last restore is still up to date. Nothing left to do.
Script is not valid:
    unknown (1,0)-(1,0): Error FS0193: The module/namespace 'System' from compilation unit 'netstandard' did not contain the namespace, module or type 'IAsyncDisposable'
Performance:
 - Cli parsing: 140 milliseconds
 - Packages: 56 milliseconds
 - Script compiling: 1 second
 - Script analyzing: 105 milliseconds
 - Runtime: 1 second

Known workarounds

Run the script via dotnet fsi (see this comment).

Related information

yazeedobaid commented 2 years ago

Can you please add a global.json file beside the script and use a Net6 SDK? From the error message, it seems that the runner has defaulted to Nestandard2.0 reference assemblies and they don't contain this method. It is only specified in Net5 and Net6 per the documentation of the method ReadAsStringAsync.

otto-gebb commented 2 years ago

I added a global.json file with the following content:

{
  "sdk": {
    "version": "6.0.100"
  }
}

And now I get a different error:

There was a problem while setting up the environment: -> Could not find referenced assemblies in path: '/usr/share/dotnet/packs/Microsoft.NETCore.App.Ref/6.0.0/ref/net6.0', please check installed SDK and runtime versions

❯ ls /usr/share/dotnet/packs/Microsoft.NETCore.App.Ref                                                                
3.1.0  6.0.1
otto-gebb commented 2 years ago

Adding a global.json file alongside all my build scripts could be a workaround (not a solution), but unfortunately it doesn't work.

yazeedobaid commented 2 years ago

Adding a global.json file is not a workaround, it is used to enable Net6 reference assemblies. If no global.json is found beside script or in another directory it will fall back to Netstandard2.0 reference assemblies. This is by design, to not introduce a breaking change.

The specified SDK version of 6.0.100 has a matching runtime version of 6.0.0 which is the one that FAKE runner resolves as in the error message: /usr/share/dotnet/packs/Microsoft.NETCore.App.Ref/6.0.0/ref/net6.0

However, from that directory, you have a runtime with version 6.0.1 which matches SDK version 6.0.101

Not sure how this got mixed up! Can you please download SDK version 6.0.100 and try or download SDK version 6.0.101 and specify it in your global.json?

otto-gebb commented 2 years ago

@yazeedobaid I installed SDK 6.0.101.

❯ sudo ./dotnet-install.sh --install-dir /usr/share/dotnet -channel Current -version latest
...
dotnet-install: Extracting zip from https://dotnetcli.azureedge.net/dotnet/Sdk/6.0.101/dotnet-sdk-6.0.101-linux-x64.tar.gz
...
❯ dotnet --info                   
.NET SDK (reflecting any global.json):
 Version:   6.0.101
 Commit:    ef49f6213a

Runtime Environment:
 OS Name:     arch
 OS Version:  
 OS Platform: Linux
 RID:         arch-x64
 Base Path:   /usr/share/dotnet/sdk/6.0.101/

Host (useful for support):
  Version: 6.0.1
  Commit:  3a25a7f1cc

.NET SDKs installed:
  3.1.120 [/usr/share/dotnet/sdk]
  6.0.100 [/usr/share/dotnet/sdk]
  6.0.101 [/usr/share/dotnet/sdk]

.NET runtimes installed:
  Microsoft.AspNetCore.App 3.1.20 [/usr/share/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 6.0.0 [/usr/share/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 6.0.1 [/usr/share/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 3.1.20 [/usr/share/dotnet/shared/Microsoft.NETCore.App]
  Microsoft.NETCore.App 6.0.0 [/usr/share/dotnet/shared/Microsoft.NETCore.App]
  Microsoft.NETCore.App 6.0.1 [/usr/share/dotnet/shared/Microsoft.NETCore.App]

And added the global.json file:

{
  "sdk": {
    "version": "6.0.101"
  }
}

And now the script runs successfully. Thanks for your help!

It's a bit sad that I now have to go and create a global.json file in a lot of places, especially considering that everything has worked fine without it for many years until now.

Is there a way to specify the runtime for FAKE (and only for FAKE) differently (e.g. an env variable or some parameter in paket.dependencies?

yazeedobaid commented 2 years ago

No, we didn't expose this API. But we plan to make this the default. In which it will use Net6 reference assemblies in all cases. No need for a global.json, the runner will look for a Net 6 installation by default. But this will introduce a breaking change, so are delaying it..

otto-gebb commented 2 years ago

Found another workaround:

After the adjustmets the script from the original post looks like this:

#r "nuget: Fake.Core.Target"

open Fake.Core
open System
open System.Net.Http
open System.Threading.Tasks

#if !FAKE
Environment.GetCommandLineArgs()
|> Array.skip 2
|> Array.toList
|> Fake.Core.Context.FakeExecutionContext.Create false __SOURCE_FILE__
|> Fake.Core.Context.RuntimeContext.Fake
|> Fake.Core.Context.setExecutionContext
#endif

let myCall (response: HttpResponseMessage) : Task<string> =
    task {
      let! content = response.Content.ReadAsStringAsync()
      return content
    }

// Default target
Target.create "Default" (fun _ ->
    Trace.trace "Hello World from FAKE"
)

// start build
Target.runOrDefault "Default"