Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable packaging Dafny as a .NET tool #2051

Merged
merged 38 commits into from
Jun 10, 2022
Merged

Conversation

atomb
Copy link
Member

@atomb atomb commented Apr 22, 2022

This makes the first steps toward packaging Dafny as a .NET CLI tool.

Mostly fixes #1779, but doesn't yet auto-install Z3. We can do that as part of a later PR.

By submitting this pull request, I confirm that my contribution is made under the terms of the MIT license.

@mschlaipfer
Copy link
Member

What about the target language runtimes and supporting the /compile:3 argument? How is that different from z3? Is it because the default is not /noVerify so z3 is required whereas the target compilers are optional?

@atomb
Copy link
Member Author

atomb commented Apr 22, 2022

What about the target language runtimes and supporting the /compile:3 argument? How is that different from z3? Is it because the default is not /noVerify so z3 is required whereas the target compilers are optional?

That's a good question. I think the idea is that all the target language runtimes would be available through the packaging system for the respective languages (and the C# runtime is prepared to be available through NuGet, so C# compilation and execution should work smoothly).

Alternatively, I expect the source files for the target language runtimes could be included as data files in the Dafny package, and spit out alongside the generated code (as I think would be necessary for C++?). They're at least not platform-dependent, whereas Z3 binaries are.

@robin-aws
Copy link
Member

Alternatively, I expect the source files for the target language runtimes could be included as data files in the Dafny package, and spit out alongside the generated code (as I think would be necessary for C++?).

This is the approach we should use for now, including the DafnyRuntime.cs file even if/when it's possible to get the DafnyRuntime.dll artifact from nuget. The default behavior is still to prepend that code to the compiler output anyway.

I'm assuming that dotnet tool install --global ... works as well? That's how I'll want to document the common installation process for users, as that will add the dafny CLI to the PATH so you don't need the dotnet tool run ... part.

@atomb atomb self-assigned this May 24, 2022
@atomb atomb marked this pull request as ready for review June 7, 2022 17:52
@atomb atomb changed the title [draft] Enable packaging Dafny as a .NET tool Enable packaging Dafny as a .NET tool Jun 7, 2022
Comment on lines 85 to 86
#- name: Upload package to NuGet
# run: dotnet nuget push "Binaries/Dafny*.nupkg" -k ${{ secrets.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#- name: Upload package to NuGet
# run: dotnet nuget push "Binaries/Dafny*.nupkg" -k ${{ secrets.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json
- name: Upload package to NuGet
run: dotnet nuget push "Binaries/Dafny*.nupkg" -k ${{ secrets.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json

Comment on lines 39 to 41
<EmbeddedResource Include="..\DafnyRuntime\DafnyRuntime.csproj">
<LogicalName>DafnyRuntime.csproj</LogicalName>
</EmbeddedResource>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this also work if done in DafnyRuntime?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The DafnyPipeline project doesn't depend on the DafnyRuntime project, so I don't think we'd be able to get the files into the executable assembly that way. The DafnyRuntime project is, as I understand it, something that generated C# code can depend on, but not something the compiler includes in its code base at all.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would adding that dependency be so bad?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like it would be sort of a strange thing to do, to me. The DafnyRuntime project is a C# library intended for use by generated C# code. It doesn't make sense to me for it include, for instance, DafnyRuntime.js as an embedded resource. Said differently, it makes more sense, in my view, for the compiler to bake in the code for the various runtime systems, rather than having the runtime system for one language include the runtime systems for other languages.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And why do you need the csproj file itself inside the DLL? What is reading that exactly?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should either get rid of the csproj entirely, or use it as much as possible.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another way to look at it: if we embed those files in DafnyRuntime.csproj, then the DafnyRuntime.dll file that users download from NuGet to link with their generated C# code will also include the text of the various other language runtimes. It wouldn't cause problems to do that, but it seems awkward to me.

Copy link
Member

@keyboardDrummer keyboardDrummer Jun 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should have separate projects for each back-end, and each of those should include their own runtime, and then , not DafnyPipeline but DafnyDriver.csproj should reference those. That way, consumers of Dafny as a library will consume DafnyPipeline but not necessarily all the back-ends and runtimes, while Dafny tool consumers do get all the back-ends. One example we have of a Dafny as a library consumer is DafnyLanguageServer. In the proposed setup, you can build DafnyLanguageServer without having Java instead because there's no dependency on DafnyRuntime.csproj.

Keeping DafnyRuntime.csproj as is, the above proposal turns into DafnyDriver.csproj referencing DafnyRuntime.csproj.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I like the main proposal you're making, @keyboardDrummer. Each back-end should be a separate project.

Given the current state of the code, though, the reason for embedding those resources in DafnyPipeline for the moment is so that the compilers can get a reference to them through GetExecutingAssembly. I originally put them in DafnyDriver. It would be possible to move them back into DafnyDriver and instead use GetEntryAssembly.

I had previously only seen methods in the Assembly class to load by type, and types in DafnyRuntime aren't available in the compilers, which are currently in DafnyPipeline, unless we make DafnyRuntime a dependency of DafnyPipeline, which is pretty much the opposite of what you're proposing. We could potentially use Load to load DafnyRuntime by name. I prefer something that doesn't require a name as a string, but not strongly.

Copy link
Member

@keyboardDrummer keyboardDrummer Jun 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, sorry I didn't see the GetExecutingAssembly call. In that case I think adding DafnyRuntime as a dependency to DafnyPipeline is OK for now.

fabiomadge
fabiomadge previously approved these changes Jun 7, 2022
Copy link
Collaborator

@fabiomadge fabiomadge left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How did we deal with z3?

@atomb
Copy link
Member Author

atomb commented Jun 7, 2022

How did we deal with z3?

The current plan is to have it be an implicit dependency that must be installed separately for the moment, but perhaps to add functionality to automatically download it at some point in the future.

Copy link
Member

@robin-aws robin-aws left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Today is a great day for Dafny. :)

Mostly just questions, but one other blocker: we need a copy of release-downloads.yml that installs the .NET tool version and runs the same smoke test. I don't see an easy way of triggering that automatically, so we should just make it a manual step of the release process for now.

RELEASE_NOTES.md Outdated
@@ -1,6 +1,7 @@
# Upcoming

- feat: The IDE will show verification errors for a method immediately after that method has been verified, instead of after all methods are verified.
- feat: Dafny is now available on NuGet, installable with `dotnet tool install Dafny`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- feat: Dafny is now available on NuGet, installable with `dotnet tool install Dafny`.
- feat: Dafny is now available on NuGet, installable with `dotnet tool install --global Dafny`.

Debatable perhaps, but it seems like the most common use case by far is putting it on your PATH so you can just run dafny on the command line.

I would also be more specific about the set of packages we're publishing than just "Dafny" :)

Comment on lines -74 to -75
var rt = wr.NewFile("dafny/dafny.go");
ReadRuntimeSystem("DafnyRuntime.go", rt);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did this have to move?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because it now depends on program which isn't in scope in EmitBuiltinDecls but is in EmitHeader. And changing the parameters of EmitBuiltinDecls would require changes to every compiler. The other compilers read the runtimes in EmitHeader, too, so it's more consistent that way.

You could argue that EmitBuiltinDecls is the right place for them all to emit the runtime system, though.

x.y.z.40112). Edit the internal version number in the following
places:

* `Source/version.cs`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd add a comment to this file noting that the other two instances have to be kept in sync, just in case.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good thought.

Comment on lines 39 to 41
<EmbeddedResource Include="..\DafnyRuntime\DafnyRuntime.csproj">
<LogicalName>DafnyRuntime.csproj</LogicalName>
</EmbeddedResource>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And why do you need the csproj file itself inside the DLL? What is reading that exactly?

@@ -8,6 +8,7 @@
<RootNamespace>Microsoft.Dafny.LanguageServer</RootNamespace>
<OutputPath>..\..\Binaries\</OutputPath>
<ValidateExecutableReferencesMatchSelfContained>false</ValidateExecutableReferencesMatchSelfContained>
<PackageVersion>3.6.0.40511</PackageVersion>
Copy link
Member

@keyboardDrummer keyboardDrummer Jun 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the use of publishing this as a package? I think it could be useful as a too, either a separate tool or part of the one dafny tool (preferred), so that the VSCode extension can install it using dotnet install tool dafny

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you know whether it's possible to set it up so that the language server gets distributed along with the main CLI? And, if so, how to do it? I think that'd be ideal, but wasn't sure if it would be possible, at least without significant refactoring.

Copy link
Member

@keyboardDrummer keyboardDrummer Jun 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sure it possible, and I think we should, but we should spend a little more time discussing whether we want it. For now, maybe just not publish DafnyLanguageServer, unless you see a reason why it would be useful to consume for other .NET projects.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds reasonable.

@@ -34,4 +34,19 @@
<ItemGroup>
Copy link
Member

@keyboardDrummer keyboardDrummer Jun 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't DafnyPipeline and DafnyRuntime be published as packages for Dafny as a library use-cases?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think so, but I was thinking that we'd make the modifications you mention here before doing that. If you think the library would be useful as-is then, yeah, let's include it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should publish them now. Customers can always copy things from DafnyDriver if they're missing something that's only there.

@@ -34,4 +34,19 @@
<ItemGroup>
<Content Include="DafnyPrelude.bpl" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>

<ItemGroup>
Copy link
Member

@keyboardDrummer keyboardDrummer Jun 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For reasons I outlined here, I don't think DafnyPipeline should reference DafnyRuntime in any way. Can we move these references to DafnyDriver?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think that's where they belong (and where I originally put them). Now that I understand how to make that work, I'm going to do it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed my mind on this yet again. The issue I came across is that the DafnyPipeline project, which we're making available as a library, includes the compilers. If you invoke those compilers and their assembly (or, as an option, possibly some other one that's available at runtime) doesn't contain the runtime files, they'll crash. So the runtime files can't reasonably go in DafnyDriver. They ultimately should go in language-specific compiler packages, as I think we all agree. But I still feel like it'd be messy to put them in the DafnyRuntime assembly (even though the source code is in that package). So that leaves DafnyPipeline, for now, until we split off the individual compilers.

@atomb
Copy link
Member Author

atomb commented Jun 9, 2022

This is currently failing due to a failed LSP test, EnsuresPriorityWorksEvenIfRemovingMethodsWhileTypo. We'll need to fix that separately before merging.

z3 -version
dafny -version
- name: Check
run: dafny/dafny /compileVerbose:0 /compile:0 a.dfy
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'l have to replace dafny/dafny with dafny everywhere, since dotnet tool install --global will put it directly on the PATH.

`RELEASE_NOTES.md`, and add a new "Upcoming" header above it.

Push and cut a PR, get it approved, and squash and merge those
changes to the `master` branch.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need a step after #7 to manually trigger the new release-downloads-nuget.yml workflow too.

lastdot = lines[2].rindex('.',qstart)
v1 = lines[2][qstart+1:lastdot]
v2 = lines[2][lastdot+1:qend]
verline = lines[5]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yuck - there has to be a better way of tracking the version number in the future :P

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I agree. :)

@@ -16,6 +16,7 @@
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Reflection;
using Microsoft.Win32.SafeHandles;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this used somehow? Hopefully not?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, IDEs! I was exploring various APIs to use to emit DafnyRuntime.jar, and my IDE must have inserted that while I had something temporary in place that I removed as soon as I read the docstring. :)

@@ -2195,6 +2196,19 @@ private class GenericArrayElementLvalue : ILvalue {
}
}

private void EmitRuntimeJar(string targetDirectory) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is definitely "weird", but not a lot weirder than the old way. :)

It's at least more consistent with the behavior of the other languages.

@@ -2207,8 +2221,12 @@ private class GenericArrayElementLvalue : ILvalue {
return false;
}
}

var targetDirectory = Path.GetDirectoryName(targetFilename);
EmitRuntimeJar(targetDirectory);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm...this should probably only happen if DafnyOptions.O.UseRuntimeLib is false. If it's true, the assumption is that you'll add DafnyRuntime.jar to the classpath yourself when you compile/run later. How hard is it to extract DafnyRuntime.jar from the nuget package after you've installed it?

I don't think this is blocking since existing codebases since we're still publishing the zip files as well, but we should probably make sure we start publishing DafnyRuntime.jar as well before we STOP publishing the zip files.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's an easy check to add, so I'll do it even if it's not immediately critical.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, and in response to your question, I don't know of any way to extract the embedded files from an assembly other than through the .NET API, but there probably is one.

@atomb atomb merged commit 51b6885 into dafny-lang:master Jun 10, 2022
@atomb atomb deleted the dotnet-tool branch June 10, 2022 21:23
cpitclaudel added a commit to dafny-lang/compiler-bootstrap that referenced this pull request Jun 30, 2022
cpitclaudel added a commit to dafny-lang/compiler-bootstrap that referenced this pull request Jul 12, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Package Dafny as a .NET Core CLI tool
5 participants