-
-
Notifications
You must be signed in to change notification settings - Fork 379
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
Add support for deterministic builds #883
Conversation
0aa752d
to
30be3e9
Compare
30be3e9
to
591fb21
Compare
src/Shouldly/Shouldly.csproj
Outdated
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" /> | ||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" Condition="$(Configuration) == 'Release'" /> | ||
<None Include="..\..\assets\logo_128x128.png" Pack="true" PackagePath="assets" /> | ||
</ItemGroup> | ||
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' " > |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an unrelated change to this PR, but it was a blocker to using Rider, so have bundled up this fix.
/// Shouldly can enhance assertion failure messages if it can find the source code at the call site. | ||
/// When using deterministic builds, set this property to explicitly tell Shouldly the root path which symbol paths will be relative to. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The wording here is pretty awkward, suggestions welcome!
Another thing to note is that this all goes away if and when we use |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @slang25,
for me removing build.props
would definitly solve my build problems. I am not shure what happend if the other properties are not set properly.
<DebugSymbols>true</DebugSymbols>
<Optimize>false</Optimize>
<DebugType>embedded</DebugType>
But this solution looks very promising.
if (sourceRoot != null) | ||
{ | ||
fileName = fileName.Replace("/_/", sourceRoot + Path.PathSeparator); | ||
return fileName.Replace("/_/", sourceRoot + Path.PathSeparator); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am working on a windows system. The files are processed with this inputs and outputs:
Non deterministic test project build:
Input: E:\Temp\shouldly\src\DeterministicTests\Tests.cs
Output: E:\Temp\shouldly\src\DeterministicTests\Tests.cs
This is fine.
Deterministic test project build:
Input: /_/src/DeterministicTests/Tests.cs
Output: E:\Temp\shouldly;src/DeterministicTests/Tests.cs
Path.PathSeperator
should be Path.DirectorySeperator
and the slashes must be converted.
return fileName.Replace("/_/", sourceRoot + Path.DirectorySeparatorChar)
.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great spot, thanks
@@ -57,15 +66,14 @@ private void ParseStackTrace(StackTrace? stackTrace) | |||
TryFindGitRepoRoot(assemblyDirectory!, out sourceRoot); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe searching for the SLN could be an alternative to the directory .git
. It would remove the dependency to Git if you start a project and add Git afterwards.
private static bool TryFindProjectFolder(string startDirectory, [NotNullWhen(true)] out string? projectFolder)
{
try
{
var currentDirectory = new DirectoryInfo(startDirectory);
while (currentDirectory != null)
{
if (IsGitParentDirectory(currentDirectory)
|| IsSlnParentDirectory(currentDirectory))
{
projectFolder = currentDirectory.FullName;
return true;
}
currentDirectory = currentDirectory.Parent;
}
}
catch { }
projectFolder = null;
return false;
}
private static bool IsGitParentDirectory(DirectoryInfo currentDirectory)
{
var gitDirectory = Path.Combine(currentDirectory.FullName, ".git");
return Directory.Exists(gitDirectory);
}
private static bool IsSlnParentDirectory(DirectoryInfo currentDirectory)
{
var foundFiles = Directory.EnumerateFiles(currentDirectory.FullName, "*.sln");
return foundFiles.Any();
}
Further checks could be implemented as needed. I think the best solution would be to look for the CSPROJ of the test projects and parse for the referenced projects to test. But there could be multiple. It would add some complexity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we'd get too many false positives, I just checked a handful of open source .NET projects and about half had the .sln
file one level in (Newtonsoft.Json
, Spectre.Console
etc...).
The git approach is quite crude, but might cover 90% of scenarios, for everything else we can tell users to pass a SourceRoot
.
For deterministic builds, there an MSBuild ItemGroup
item named SourceRoot
which ideally we'd want to reuse, but the only way to get it would be some sort of source code generation.
@slang25 whats stopping us from using |
We could add it but there's a few trade offs:
The combination of the first two points will make it a bit of a messy change, but the more I think about it the more I think we should do it. |
Expect a separate PR shortly, I think it won't take too long |
this supports CallerArgumentExpression back to net461 https://github.com/SimonCropp/Polyfill#callerargumentexpressionattribute |
That's only if the consumer of the package has a compiler version that supports it, we can't guarantee the version of tooling or build environment consumers of this would be using. So for example your Polyfill library would work for users using .NET SDK 6 and up targeting the |
yeah thats true |
I think this is good to merge @SimonCropp, let me know if there are any objections |
did u consider using msbuild to embed the SolutionDir as an attribute in the consuming assembly, and then reading that at runtime? |
I had considered it, but then I realised that it defeats the purpose of deterministic builds, because now the binaries will differ depending on the machine and location they were built on. A reasonable solution would be to pass it using an environment variable by the build runner. The |
Actually, let's do that. I'll remove the config value and use an env var, and have a target that will set it if it detects the right conditions. |
@SimonCropp I've pushed a new approach, not fully tested yet outside of the test project, I'm trying to figure out if This approach uses the This approach currently only works when you do |
@SimonCropp any feedback? |
@slang25 @SimonCropp after upgrading to v4.2 one of our CI workflows in GraphQL.NET parser failed with message
graphql-dotnet/parser#299 |
Thanks for reporting @sungam3r What we were doing in Shouldly The current support added in this PR only works when the |
OK. Our workflows intentionally use |
New error detected in Xabaril/AspNetCore.Diagnostics.HealthChecks#1801:
|
This adds support for deterministic builds by detecting the MSBuild
PathMap
property during the build target, and then setting an environment variable which Shouldly can use at runtime to replace deterministic path roots (/_/
,/_1/
etc...) with the actual local paths.This currently wouldn't work if you have a separate build and test step, but we could add support for this later by outputting our own file to the Output directory (
bin
) with thePathMap
value, and reading from it during test.With this addition, I've removed the intrusive build props inclusion, which was tripping people up and hard to discover.