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

chore: Factor out projects without runtimes #2568

Merged

Conversation

robin-aws
Copy link
Member

@robin-aws robin-aws commented Aug 9, 2022

Factors out two new projects that don't depend on the DafnyRuntime project or DafnyRuntime.* runtime artifacts, and removes the DLL-level dependencies on DafnyRuntime.dll. It also has the happy side-effect of making directory, csproj, and assembly names consistent.

The four main changes are:

  • Rename Dafny folder/DafnyPipeline project to DafnyCore, remove reference to DafnyRuntime and DafnyRuntime.* embedded resources
  • Create new DafnyPipeline folder/project, referencing DafnyCore and adding DafnyRuntime.* embedded resources
    • Note DafnyPipeline.dll does NOT depend on DafnyRuntime.dll, however! It has a project-level dependency to ensure DafnyRuntime is built first, but only embeds the runtime artifacts.
  • Relabel DafnyDriver as DafnyDriver.dll instead of Dafny.dll, remove reference to DafnyRuntime
  • Create new Dafny folder/project, referencing DafnyDriver and DafnyPipeline (and hence pulling in embedded runtimes)

Note that we are still only publishing Dafny.dll and DafnyPipeline.dll as Nuget packages, and they still transitively contain the same content. We could publish DafnyDriver and/or DafnyCore in the future as well.

Removing DafnyRuntime as a DLL dependency from all of these projects initially caused the Test/dafny0/DafnyLibClient.dfy test to break. This is because the driver was attempting to Assembly.Load each DLL passed to it, in order to read any DafnySourceAttribute value out of it if present. This has never been a good idea, since an arbitrary DLL will have dependencies not satisfied by the invoking context, and not finding DafnyRuntime.dll was just one potential error this could cause. I had previously used Cecil to read DLL files without loading them, but this was lost when we moved to .NET Core v2 since that library was not supported. This time I've used the built-in System.Reflection.Metadata.MetadataReader and related types to decode enough of the DLL to read this attribute.

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

* Rename Dafny folder/DafnyPipeline project to DafnyCore, remove reference to DafnyRuntime and DafnyRuntime.* embedded resources
* Create new DafnyPipeline folder/project, referencing DafnyCore and adding DafnyRuntime.* embedded resources
* Relabel DafnyDriver as DafnyDriver.dll instead of Dafny.dll, remove reference to DafnyRuntime
* Create new Dafny folder/project, referencing DafnyDriver and DafnyPipeline (and hence pulling in embedded runtimes)
Copy link
Member

@atomb atomb left a comment

Choose a reason for hiding this comment

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

This is a very nice improvement. :) I noticed a few tiny things that could be fixed (which should fix at least one of the test failures) but it otherwise seems great.

Makefile Outdated
@@ -29,12 +29,12 @@ z3-ubuntu:
mv z3-4.8.5-x64-ubuntu-16.04 ${DIR}/Binaries/z3

format:
dotnet tool run dotnet-format -w -s error Source/Dafny.sln --exclude Dafny/Scanner.cs --exclude Dafny/Parser.cs
dotnet tool run dotnet-format -w -s error Source/Dafny.sln --exclude DafnyCore/Scanner.cs --exclude DafnyCore/Parser.cs

clean:
(cd ${DIR}; cd Source; rm -rf Dafny/bin Dafny/obj DafnyDriver/bin DafnyDriver/obj DafnyRuntime/obj DafnyRuntime/bin DafnyServer/bin DafnyServer/obj DafnyPipeline/obj DafnyPipeline/bin )
Copy link
Member

Choose a reason for hiding this comment

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

This line should perhaps be updated to include DafnyCore, too?

Makefile Outdated

clean:
(cd ${DIR}; cd Source; rm -rf Dafny/bin Dafny/obj DafnyDriver/bin DafnyDriver/obj DafnyRuntime/obj DafnyRuntime/bin DafnyServer/bin DafnyServer/obj DafnyPipeline/obj DafnyPipeline/bin )
(cd ${DIR} ; dotnet build Source/Dafny.sln -v:q --nologo -target:clean )
make -C ${DIR}/Source/Dafny -f Makefile clean
make -C ${DIR}/Source/DafnyCore -f Makefile clean
(cd ${DIR}/Source/DafnyRuntime/DafnyRuntimeJava; ./gradlew clean)
make -C ${DIR}/docs/DafnyRef clean
(cd ${DIR}; cd Source; rm -rf Dafny/bin Dafny/obj DafnyDriver/bin DafnyDriver/obj DafnyRuntime/obj DafnyRuntime/bin DafnyServer/bin DafnyServer/obj DafnyPipeline/obj DafnyPipeline/bin )
Copy link
Member

Choose a reason for hiding this comment

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

Same as for the previous rm line.

}
var fullJarName = $"{targetDirectory}/{jarName}";
FileStream outStream = new FileStream(fullJarName, FileMode.Create, FileAccess.Write);
stream!.CopyTo(outStream);
Copy link
Member

Choose a reason for hiding this comment

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

This change has the side effect of creating an empty JavaRuntime.jar if it's not found in the assembly, as opposed to doing nothing, which is perhaps better behavior? It might be even nicer to emit an error message in that case, but I didn't do that in the original code, either. :)

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 my intention was just to cleanly make this an exception rather than a silent failure, but I can throw right away without that messy side effect.

Note that I'm going to make it an exception rather than an error message as SinglePassCompiler.ReadRuntimeSystem does (and I intend to make that throw as well in the next PR). At least right now, the publicly released tools should always have the runtimes attached, so I'd rather crash loudly if we've made a publishing mistake.

Copy link
Member Author

Choose a reason for hiding this comment

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

In fact I'm touching SinglePassCompiler.ReadRuntimeSystem already anyway, I'm going to make it throw in this PR after all - no time like the present!

@@ -1,18 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<AssemblyName>Dafny</AssemblyName>
<OutputType>Library</OutputType>
Copy link
Member

Choose a reason for hiding this comment

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

One nice impact of this is that a client can now get all of Dafny as a library (though only conveniently if we publish this library to NuGet).

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, and I think I'm going to have to define an additional "DafnyWithoutRuntimes" Exe project that depends on this in order to run Dafny on my DafnyRuntime.dfy code in the next PR. My intent remains to keep this and that project internal-only for now though.

@@ -21,8 +21,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\DafnyDriver\DafnyDriver.csproj" />
<ProjectReference Include="..\DafnyRuntime\DafnyRuntime.csproj" />
Copy link
Member

Choose a reason for hiding this comment

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

Based on the test output, it seems that this project still depends dynamically on 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.

Specific tests are depending on linking to DafnyRuntime.dll, yup. Unfortunately one of them reveals that DafnyMain calls Assembly.LoadFile on any dll file passed to the CLI, in order to read the contents of any DafnySourceAttribute: https://github.com/dafny-lang/dafny/blob/master/Source/Dafny/DafnyMain.cs#L67. This will fail if its dependencies are not available (DafnyRuntime.dll or any other dependency!).

I originally fixed this by loading the assembly reflectively using Cecil, but unfortunately we lost that when switching to .NET Core: https://github.com/dafny-lang/dafny/pull/794/files#diff-0ba2e4842e187b0efcf6a6b5fd6fca73b546ece27e54074265eab734f826240dL45-R47. Even better, there's an Assembly.ReflectionOnlyLoad method, but it turns out it always did a regular load instead, and now always throws an exception instead (which is definitely better, but still feels like a bait-and-switch :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.

Eventually figured out the supported replacement for Assembly.ReflectionOnlyLoad - see updated PR description and release note.

@@ -30,36 +29,8 @@
<PackageReference Include="Boogie.ExecutionEngine" Version="2.15.7" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\DafnyRuntime\DafnyRuntime.csproj" />
Copy link
Member

Choose a reason for hiding this comment

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

I'm fine removing the reference to the DafnyRuntime, however, in one of my PRs, I am making use of the DafnyRuntime.cs which should then become a dependency of DafnyCore in your setting. Can we just add an external dependency to this file so that it's included at compile time?
That was we don't need a dependency of all the possible runtimes as you suggest but I have still it working for us.

Copy link
Member Author

Choose a reason for hiding this comment

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

Your PR will actually work just fine after this change. Since you're not passing /useRuntimeLib when compiling your Dafny code through dafny.msbuild, the compiled output will have the embedded DafnyRuntime.cs file prepended, and you won't need to depend on DafnyRuntime.dll.

That means that the version of the runtime you build your part of DafnyCore with doesn't affect the version that a dafny end user will end up depending on in their compiled code. It's admittedly a little weird and confusing to have the C# runtime code embedded like that though. :)

Copy link
Member

Choose a reason for hiding this comment

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

Oh I see, it's in the generated code. That's fine I think ^^

@@ -1,18 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
Copy link
Member

Choose a reason for hiding this comment

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

From the PR description:

Relabel DafnyDriver as DafnyDriver.dll instead of Dafny.dll, remove reference to DafnyRuntime
I'll be able to use this entry point to build Dafny source code into the runtimes when implementing #2390

Why do you need this project? Can't you use DafnyCore instead? Are you planning on setting the output type here to Exe again so you can run 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'm planning on adding an empty Exe project that references DafnyDriver, something like DafnyWithoutRuntimes, so I can easily invoke the Dafny compiler from something like a Makefile. I don't know of any way to invoke logic in DafnyCore directly, but I'd be happier if there was!

…projects-without-runtimes

# Conflicts:
#	RELEASE_NOTES.md
#	Source/DafnyCore/DafnyCore.csproj
#	Source/DafnyDriver/DafnyDriver.csproj
#	Source/DafnyLanguageServer.Test/Various/PluginsTestBase.cs
#	Source/version.cs
@robin-aws robin-aws marked this pull request as ready for review September 8, 2022 19:37
Copy link
Member

@atomb atomb left a comment

Choose a reason for hiding this comment

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

This looks good! I had a couple of questions that could lead to changes, but I'm approving it to avoid delay in the (likely) case where they don't require any.


<ItemGroup>
<ProjectReference Include="..\DafnyDriver\DafnyDriver.csproj" />
<ProjectReference Include="..\DafnyPipeline\DafnyPipeline.csproj" />
Copy link
Member

Choose a reason for hiding this comment

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

Does this need DafnyPipeline?

Copy link
Member Author

Choose a reason for hiding this comment

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

Absolutely, since that's how the runtime files are included. I understand why you ask, because the minimal contents of Dafny should just be DafnyDriver + runtimes, but DafnyPipeline is exactly the assembly that embeds the runtimes. Most importantly the compilers load them from the DafnyPipeline.dll assembly explicitly rather than whatever the currently executing assembly is.

Copy link
Member

Choose a reason for hiding this comment

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

Right, of course!

}

private static string GetDafnySourceAttributeText(string dllPath) {
using var dllFs = new FileStream(dllPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
Copy link
Member

Choose a reason for hiding this comment

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

Does this result in a reasonable error if dllPath is a non-existent file? Or does the earlier code in DafnyFile ensure that it exists?

Copy link
Member Author

@robin-aws robin-aws Sep 9, 2022

Choose a reason for hiding this comment

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

Good callout! It's no worse than the old version that tried to Assembly.LoadFile it: you still get a System.IO.FileNotFoundException, just with a slightly different stack trace.

It's worth making that cleaner since it's a user error that shouldn't lead to a crash, but it may be non-trivial to fix. Throwing IllegalDafnyFile will probably lead to a different less helpful error so it may need some refactoring. I cut #2719 as a follow up.

@robin-aws robin-aws merged commit 7fc07d3 into dafny-lang:master Sep 9, 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.

4 participants