简体   繁体   English

在 F# Interactive 中加载 dotnet 项目文件

[英]Load dotnet project files in F# Interactive

I have a F# dotnet project with some dependencies in my .fsproj (eg <PackageReference Include="FSharp.Data" Version="3.0.0-beta4" /> ).我有一个 F# dotnet 项目,在我的.fsproj有一些依赖.fsproj (例如<PackageReference Include="FSharp.Data" Version="3.0.0-beta4" /> )。

Now I want to test some functions in a module I wrote, so I start fsharpi and try to load my module with #load "Program.fs";;现在我想测试我编写的模块中的一些功能,所以我启动fsharpi并尝试使用#load "Program.fs";;加载我的模块#load "Program.fs";; . .

However, then I get error FS0039: The type 'CsvProvider' is not defined .但是,然后我收到error FS0039: The type 'CsvProvider' is not defined

How can I load my module with the correct dependencies?如何使用正确的依赖项加载我的模块?

I have seen workarounds which include manually loading all required dll s from some obscure system dependent path (eg #load a package in F# interactive (FSharpChart.fsx) ), but I guess there must be a better way to do this.我已经看到了一些解决方法,其中包括从一些模糊的系统相关路径(例如#load a package in F# interactive (FSharpChart.fsx) )手动加载所有必需的dll ,但我想必须有更好的方法来做到这一点。

In order to get access to the code in imported libraries, you need to tell the FSI to load them.为了访问导入库中的代码,您需要告诉 FSI 加载它们。

At the lowest level, this can be done with an #r directive:在最低级别,这可以通过#r指令完成:

#r "./packages/FSharp.Data/lib/whatever/FSharp.Data.dll"

This is often sufficient for one-off purposes.这通常足以满足一次性目的。

However, if there are a lot of references, your IDE can usually automate this for you.但是,如果有很多引用,您的 IDE 通常可以为您自动执行此操作。 For example, in Visual Studio, you can right-click the project and select either "Send References to F# Interactive" or "Generate Script File with References" (not sure about the exact wording).例如,在 Visual Studio 中,您可以右键单击项目并选择“将引用发送到 F# Interactive”或“生成带有引用的脚本文件”(不确定确切的措辞)。 The same options are available in VSCode if you have the Ionide extension installed.如果您安装了 Ionide 扩展,则 VSCode 中也有相同的选项。

Also, there is an active proposal+prototype for supporting packages in FSI directly - see https://github.com/fsharp/fslang-suggestions/issues/542 .此外,还有一个用于直接在 FSI 中支持包的活动提案+原型 - 参见https://github.com/fsharp/fslang-suggestions/issues/542 However, that is not yet merged and seems to have stalled a bit.然而,这还没有合并,似乎有点停滞不前。

There are a few option for doing this, none of them perfect.有几个选项可以做到这一点,但没有一个是完美的。

Ionide电离

If you are using Ionide, then you can right click on a project in F# view and choose "Generate References for FSI".如果您使用的是 Ionide,那么您可以在 F# 视图中右键单击一个项目并选择“为 FSI 生成引用”。 This will create an F# script that loads all of the dependencies and project files for you.这将创建一个 F# 脚本,为您加载所有依赖项和项目文件。 Write your test code below this.在下面写你的测试代码。

The downsides of this approach:这种方法的缺点:

  • You will need to run the procedure every time the project files or dependencies change.每次项目文件或依赖项更改时,您都需要运行该过程。
  • Not applicable where you don't have access to VS Code不适用于您无法访问 VS Code 的情况

Paket

If you are using Paket, then you can generate "load scripts" for your project:如果您使用的是 Paket,那么您可以为您的项目生成“加载脚本”:

dotnet paket generate-load-scripts

This will generate F# scripts in ./paket/load .这将在./paket/load生成 F# 脚本。 There will be a load script for each dependency (and its transitive dependencies) and for each Paket "group".每个依赖项(及其传递依赖项)和每个包“组”都有一个加载脚本。

This can automatically sync with your paket.dependencies if you add this line:如果添加以下行,这可以自动与您的paket.dependencies同步:

generate_load_scripts: true

See: https://fsprojects.github.io/Paket/paket-generate-load-scripts.html参见: https : //fsprojects.github.io/Paket/paket-generate-load-scripts.html

The downsides of this approach:这种方法的缺点:

  • It may load more dependencies than you actually need, which has a performance cost它可能会加载比您实际需要更多的依赖项,这会带来性能成本
  • You must #load you project files manually您必须手动#load您的项目文件

Dotnet + Nuget点网 + Nuget

New with .NET 5, you can write Nuget strings directly in your fsx files. .NET 5 的新功能,您可以直接在fsx文件中编写 Nuget 字符串。 This is a great feature for quick scripts!这是快速脚本的一个很棒的功能!

#r "nuget: FParsec"

// Package with a specific version
// #r "nuget: FParsec,1.1.1"

open FParsec

#load "./Types.fs"

// etc...

The downsides of this approach:这种方法的缺点:

  • It does not sync package versions with Paket, if you are using that for your project code如果您在项目代码中使用它,它不会与 Paket 同步包版本
  • You must #load you project files manually您必须手动#load您的项目文件

One Day?一天?

In an ideal world, we would be able to do something like this:在理想的世界中,我们将能够做这样的事情:

#r "fsproj: ./my-proj.fsproj"

open FParsec

// etc...

There is an issue for this here: https://github.com/dotnet/fsharp/issues/8764这里有一个问题: https : //github.com/dotnet/fsharp/issues/8764

As requested in the comments on Fyodor's answer, here's a script that I've used in the past to generate the #r and #load directives required to load an .fsproj in F# Interactive:正如在Fyodor的答案意见要求,这里就是我已经在过去用来生成一个脚本#r#load来加载所需的指令.fsproj在F#互动:

module References

#r "System.Xml"
#r "System.Xml.Linq"

open System
open System.IO
open System.Linq
open System.Xml.Linq

let inline isNotNull o = o |> isNull |> not

let private getProject path =
    Directory.EnumerateFiles(path, "*.*proj") |> Seq.head |> XDocument.Load    

let generateDlls path =
    let projectFile = getProject path
    let references =
        projectFile.Descendants <| XName.Get("HintPath", "http://schemas.microsoft.com/developer/msbuild/2003")
        |> Seq.filter (fun reference -> reference.Value.ToLower().EndsWith(".dll"))
        |> Seq.filter (fun reference -> reference.Value.StartsWith("$") |> not)
        |> Seq.map (fun reference -> reference.Value)
    let projects =
        projectFile.Descendants <| XName.Get("ProjectReference", "http://schemas.microsoft.com/developer/msbuild/2003")
        |> Seq.map (fun reference -> reference.Elements(XName.Get("Name", "http://schemas.microsoft.com/developer/msbuild/2003")).SingleOrDefault())
        |> Seq.filter (fun nameElement -> nameElement |> isNotNull)
        |> Seq.map (fun nameElement -> nameElement.Value)
        |> Seq.map (fun reference -> sprintf "../%s/bin/debug/%s.dll" reference reference)

    references 
    |> Seq.append projects
    |> Seq.iter (fun reference -> printfn "#r @\"%s\"" reference)

let generateFs path =
    let projectFile = getProject path
    projectFile.Descendants <| XName.Get("Compile", "http://schemas.microsoft.com/developer/msbuild/2003")
    |> Seq.map (fun reference -> reference.Attribute("Include" |> XName.op_Implicit))
    |> Seq.filter (fun reference -> reference |> isNotNull && reference.Value.ToLower().EndsWith("fs"))
    |> Seq.filter (fun reference -> reference.Value.Equals("AssemblyInfo.fs", StringComparison.CurrentCultureIgnoreCase) |> not)
    |> Seq.iter (fun reference -> printfn "#load @\"%s\"" reference.Value)


// Example Usage:
// #load @"GenerateReferences.fsx"
// References.generateDlls __SOURCE_DIRECTORY__
// References.generateFs __SOURCE_DIRECTORY__

I'm not sure this is completely perfect, for instance, I don't think it gets dependency-ordering right.我不确定这是否完全完美,例如,我认为它没有正确的依赖顺序。 However, it should be a reasonable starting point if you want to enhance it.但是,如果您想增强它,它应该是一个合理的起点。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM