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

Target framework sets #1668

Merged
merged 6 commits into from Oct 22, 2019

Conversation

thomaslevesque
Copy link
Member

Fixes #1662

To be merged after #1667

@thomaslevesque thomaslevesque force-pushed the target-framework-sets branch 2 times, most recently from 2a704a3 to fe3fcb0 Compare October 20, 2019 19:45
@thomaslevesque
Copy link
Member Author

This PR is on top of #1667 so that it builds successfully. The first 2 commits won't be part of it after #1667 is merged.

@thomaslevesque thomaslevesque changed the title [DO NOT MERGE] Target framework sets Target framework sets Oct 21, 2019
Copy link
Member

@blairconrad blairconrad left a comment

Choose a reason for hiding this comment

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

I'm having some difficulties with this. In particular, the netcore3.0 set doesn't work for me (I'm still on a preview at work, though). First, I hacked the build program to make it easier for me to work with:

diff --git a/tools/FakeItEasy.Build/Program.cs b/tools/FakeItEasy.Build/Program.cs
index dd9e571..b43bb81 100644
--- a/tools/FakeItEasy.Build/Program.cs
+++ b/tools/FakeItEasy.Build/Program.cs
@@ -56,11 +56,20 @@ namespace FakeItEasy.Build
 
         public static void Main(string[] args)
         {
+            var targetFrameworkSet = "full";
+            foreach (var arg in args)
+            {
+                if (arg.StartsWith("TargetFrameworkSet="))
+                {
+                    targetFrameworkSet = arg.Split('=')[1];
+                }
+            }
+
             Target("default", DependsOn("unit", "integ", "spec", "approve", "pack"));
 
             Target(
                 "build",
-                () => Run("dotnet", "build FakeItEasy.sln -c Release /maxcpucount /nr:false /verbosity:minimal /nologo /bl:artifacts/logs/build.binlog"));
+                () => Run("dotnet", $"build FakeItEasy.sln -c Release /maxcpucount /nr:false /verbosity:minimal /nologo /bl:artifacts/logs/build.binlog /p:TargetFrameworkSet={targetFrameworkSet}"));
 
             foreach (var testSuite in TestSuites)
             {
@@ -68,14 +77,14 @@ namespace FakeItEasy.Build
                     testSuite.Key,
                     DependsOn("build"),
                     forEach: testSuite.Value,
-                    action: testDirectory => Run("dotnet", "test --configuration Release --no-build -- RunConfiguration.NoAutoReporters=true", testDirectory));
+                    action: testDirectory => Run("dotnet", $"test --configuration Release --no-build /p:TargetFrameworkSet={targetFrameworkSet} -- RunConfiguration.NoAutoReporters=true", testDirectory));
             }
 
             Target(
                 "pack",
                 DependsOn("build", "pdbgit"),
                 forEach: ProjectsToPack,
-                action: project => Run("dotnet", $"pack {project.Path} --configuration Release --no-build --output {Path.GetFullPath("artifacts/output")}"));
+                action: project => Run("dotnet", $"pack {project.Path} --configuration Release --no-build --output {Path.GetFullPath("artifacts/output")} /p:TargetFrameworkSet={targetFrameworkSet}"));
 
             Target(
                 "pdbgit",
@@ -83,7 +92,7 @@ namespace FakeItEasy.Build
                 forEach: Pdbs.Where(pdb => File.Exists(pdb.Path)),
                 action: pdb => Run(ToolPaths.PdbGit, $"-u https://github.com/FakeItEasy/FakeItEasy -s {pdb.Path}"));
 
-            RunTargetsAndExit(args, messageOnly: ex => ex is NonZeroExitCodeException);
+            RunTargetsAndExit(args.Where(arg => !arg.Contains('=')), messageOnly: ex => ex is NonZeroExitCodeException);
         }
 
         private class Pdb

Then I ran .\build.cmd TargetFrameworkSet=netcore3.0 and got:

D:\Sandbox\FakeItEasy\tests\FakeItEasy.Tests.TestHelpers\FakeItEasy.Tests.TestHelpers.csproj :
error NU1201: Project FakeItEasy is not compatible with netstandard2.0 (.NETStandard,Version=v2.0).
Project FakeItEasy supports: netstandard2.1 (.NETStandard,Version=v2.1)
[D:\Sandbox\FakeItEasy\FakeItEasy.sln]

Probably because we didn't build FIE 2.0 in this run.
Indeed, changing TestHelpersTargetFrameworks to netstandard2.1 for this TargetFrameworkSet resolved the problem…

Or maybe I'm doing things wrong, since the "full" build works fine.

</PropertyGroup>

<PropertyGroup Condition="'$(TargetFramework)' == 'netstandard1.6'">
<DefineConstants>$(DefineConstants);FEATURE_NETCORE_REFLECTION;USE_RUNTIMELOADER;FEATURE_EXCEPTION_DISPATCH_INFO;FEATURE_ARRAY_EMPTY;FEATURE_PARAMETERINFO_CUSTOMATTRIBUTES_PROPERTY</DefineConstants>
Copy link
Member

Choose a reason for hiding this comment

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

I am surprised some of these constants (e.g. FEATURE_ARRAY_EMPTY) don't need to be set for netstandard2.0. Or at least can't be set…

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. I must have copied this from FakeItEasy.csproj... We don't use FEATURE_ARRAY_EMPTY in the tests anyway. I'll fix this and leave only the necessary ones.

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 both forgot about this comment as well.

Copy link
Member Author

Choose a reason for hiding this comment

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

Oops! Sorry

@@ -17,7 +17,10 @@ public static class ObjectAssertionsExtensions
/// <param name="assertion">A FluentAssertions assertion that has been initiated on a subject.</param>
public static void BeAFake(this ReferenceTypeAssertions<object, ObjectAssertions> assertion)
{
Guard.AgainstNull(assertion, nameof(assertion));
if (assertion == null)
Copy link
Member

Choose a reason for hiding this comment

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

rather than make FakeItEasy's internals visible to this project, I assume?

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. At this point. If you prefer, I can add a InternalsVisibleTo attribute to FIE

Copy link
Member

Choose a reason for hiding this comment

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

I don't mind either way. What you have is fine. Thanks for asking.


<!-- .NET Framework only -->
<PropertyGroup Condition=" '$(TargetFrameworkSet)' == 'netfx' ">
<FakeItEasyTargetFrameworks>net45</FakeItEasyTargetFrameworks>
Copy link
Member

Choose a reason for hiding this comment

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

and net40 as well?

Copy link
Member Author

Choose a reason for hiding this comment

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

Since the goal was to make the build faster, I thought it would make sense to include as few TFMs as possible. Is there a scenario where we would want to build all netfx targets, and nothing else?
But the target sets I defined here are mostly examples anyway. The idea is that we can add new ones as needed.

Copy link
Member

Choose a reason for hiding this comment

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

Ah. I was taking "netcoreall" for comparison, without paying attention to the "all" on the end of that.
I don't mind excluding net40 from the set, but maybe in this case, rename to net45, since we're closer to a "netcore2.1" situation than a "netcoreall" situation?

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 point. I don't think a netfxall is very useful. I intended netcoreall to be the default for non-Windows build (could be set automatically based on the OS)

<ApprovalTestsTargetFrameworks>net461</ApprovalTestsTargetFrameworks>
</PropertyGroup>

<!-- .NET Core 3 / .NET Standard 2.0 -->
Copy link
Member

Choose a reason for hiding this comment

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

.NET Standard 2.1?

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! Forgot to update after I rebased

<IntegrationTestsTargetFrameworks>netcoreapp3.0</IntegrationTestsTargetFrameworks>
<UnitTestsTargetFrameworks>netcoreapp3.0</UnitTestsTargetFrameworks>
<AnalyzerTestsTargetFrameworks>netcoreapp3.0</AnalyzerTestsTargetFrameworks>
<TestHelpersTargetFrameworks>netstandard2.0</TestHelpersTargetFrameworks>
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 not sure about this. See my summary.

@thomaslevesque
Copy link
Member Author

In particular, the netcore3.0 set doesn't work for me

Oops. Seems broken for me too. Must have broke when I rebased. I'll look into it.

@thomaslevesque
Copy link
Member Author

In general, what do you think about this approach?
I like your idea to add a parameter to the build script.
We could also enable a user-defined props file that would be included in Directory.Build.props if it exists, and ignored by Git. e.g.

    <Import Project="$(MSBuildThisFileDirectory)/FakeItEasy.user.props" Condition="Exists('$(MSBuildThisFileDirectory)/FakeItEasy.user.props')" />

This way, there would be no need to temporarily edit Directory.Build.props

@thomaslevesque
Copy link
Member Author

As a possible improvement, I could move the "target framework set definitions" to separate files, which we import based on the value of TargetFrameworkSet property:

<Import Project="targets/$(TargetFrameworkSet).props" />

This would be mostly to make the Directory.Build.props file look cleaner

Also, I'm not sure about the name of the concept. "Target framework set" is what I came up with, but let me know if you have a better idea. "Build profile", maybe?

@blairconrad
Copy link
Member

I think the approach is good. I'm not sure I'll use the feature that often, but who knows? I use -s more than I expected.

I liked adding a parameter to the build script as well. The problem is that it's not discoverable as is, and as you and M. Ralph noted, adding parameters that aren't handled by Bullseye can be problematic. I toyed with the idea of adding targets to set the targetframeworkset (e.g. build netcoreall default, but the downside is that as soon as you include the target, you lose the defaultiness of the default target. Maybe that's an acceptable downside? If just running tests it isn't apparent: build netcoreall specs. And honestly, most of the time that I'll be trying to speed up the build, I will for sure be cutting out the approval and packing targets!

We could also enable a user-defined props file that would be included in Directory.Build.props if it exists, and ignored by Git.

Yes, please. Better than temporarily editing anything. Until we edit that file and forget to unedit! 😆

could move the "target framework set definitions" to separate files

I'm ambivalent. Cleaner main file, but more files to look at. Do whichever you think is best.

I'm not sure about the name of the concept. "Target framework set" is what I came up with, but let me know if you have a better idea. "Build profile", maybe?

I found "target framework set" to be comprehensible, so I was content. But since you put "build profile" in front of my face, I find I like it more…

@thomaslevesque
Copy link
Member Author

I'm not sure I'll use the feature that often, but who knows?

I think I would. In fact, I think I will almost always set the target framework set to netcore3.0, to have a fast feedback loop while coding, and only switch back to full before committing.

I liked adding a parameter to the build script as well. The problem is that it's not discoverable as is, and as you and M. Ralph noted, adding parameters that aren't handled by Bullseye can be problematic.

Look here for a workaround. Not ideal, but it gets the job done.

I toyed with the idea of adding targets to set the targetframeworkset (e.g. build netcoreall default, but the downside is that as soon as you include the target, you lose the defaultiness of the default target. Maybe that's an acceptable downside? If just running tests it isn't apparent: build netcoreall specs.

It's a good option too. But I think I like something like build.cmd --targets=netcoreall better than build.cmd netcoreall default

Yes, please. Better than temporarily editing anything. Until we edit that file and forget to unedit! 😆

Will do 👍

I'm ambivalent. Cleaner main file, but more files to look at. Do whichever you think is best.

I'll try it and we'll see how it goes.

I found "target framework set" to be comprehensible, so I was content. But since you put "build profile" in front of my face, I find I like it more…

Build profile it is, then!

@thomaslevesque
Copy link
Member Author

I pushed new commits for the changes we discussed, let me know what you think.

@thomaslevesque
Copy link
Member Author

(I didn't implement the changes in the build script yet)

Copy link
Member

@blairconrad blairconrad left a comment

Choose a reason for hiding this comment

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

Looking good, @thomaslevesque. One minor comment, which I don't care that much if you change anything for. I'm content for you to add docs and squash!

<PropertyGroup>
<FakeItEasyTargetFrameworks>netstandard1.6;netstandard2.0;netstandard2.1</FakeItEasyTargetFrameworks>
<ValueTaskExtensionsTargetFrameworks>netstandard1.6;netstandard2.0;netstandard2.1</ValueTaskExtensionsTargetFrameworks>
<ExampleTargetFrameworks>netcoreapp2.1</ExampleTargetFrameworks>
Copy link
Member

Choose a reason for hiding this comment

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

I expected 2.1 and 3.0.
If the thinking was that they're just examples, so we probably don't need to build both, maybe default to the newer framework? I can live with it as is, though.

(Actually, this brings up a similar question for the full profile, and which frameworks we should target for the examples and the approval tests - if we're just going to target one in each build profile, should it be the latest/most portable? Or maybe it's all fine.)

Copy link
Member Author

Choose a reason for hiding this comment

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

If the thinking was that they're just examples, so we probably don't need to build both

Yes, that's what I had in mind

maybe default to the newer framework?

👍

(Actually, this brings up a similar question for the full profile, and which frameworks we should target for the examples and the approval tests - if we're just going to target one in each build profile, should it be the latest/most portable? Or maybe it's all fine.)

The latest for each profile is a good choice, I think.

@thomaslevesque
Copy link
Member Author

I changed the TFM for the example projects, squashed fixup commits, and updated the docs

Copy link
Member

@blairconrad blairconrad left a comment

Choose a reason for hiding this comment

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

Sorry, @thomaslevesque. One question and one minor grammatical nit.
Otherwise, great. I enjoyed the docs. Very explainy!

<IntegrationTestsTargetFrameworks>netcoreapp1.0;netcoreapp2.1;netcoreapp3.0</IntegrationTestsTargetFrameworks>
<UnitTestsTargetFrameworks>netcoreapp1.0;netcoreapp2.1;netcoreapp3.0</UnitTestsTargetFrameworks>
<AnalyzerTestsTargetFrameworks>netcoreapp2.1;netcoreapp3.0</AnalyzerTestsTargetFrameworks>
<TestHelpersTargetFrameworks>netstandard1.6;netstandard2.0;netstandard2.1</TestHelpersTargetFrameworks>
Copy link
Member

Choose a reason for hiding this comment

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

Why a netstandard2.1 here but not in full?

Copy link
Member Author

Choose a reason for hiding this comment

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

A mistake. TestHelpers doesn't need a netstandard2.1 target

Copy link
Member Author

Choose a reason for hiding this comment

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

And it also shouldn't be netstandard2.1 for the netcore3.0 profile

how_to_build.md Outdated
FakeItEasy targets multiple versions of .NET (.NET Framework 4.0 and 4.5, .NET
Standard 1.6, 2.0 and 2.1), and the tests also run on multiple frameworks (.NET
Framework 4.6.1 and several versions of .NET Core). A consequence is that a full
build can take a significant time. When working on the code, you might want a
Copy link
Member

Choose a reason for hiding this comment

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

"take a significant time" hits my ear funny. I'd've written "take a significant amount of time". Or perhaps "take significant time".

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, thanks. Not being an native English speaker, it can sometimes be hard to tell what will sound funny to a native speaker...

@thomaslevesque
Copy link
Member Author

Fixed and squashed

Copy link
Member

@blairconrad blairconrad left a comment

Choose a reason for hiding this comment

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

It's the review that never ends. Sorry.

<IntegrationTestsTargetFrameworks>netcoreapp3.0</IntegrationTestsTargetFrameworks>
<UnitTestsTargetFrameworks>netcoreapp3.0</UnitTestsTargetFrameworks>
<AnalyzerTestsTargetFrameworks>netcoreapp3.0</AnalyzerTestsTargetFrameworks>
<TestHelpersTargetFrameworks>netstandard2.0</TestHelpersTargetFrameworks>
Copy link
Member

Choose a reason for hiding this comment

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

When I use the FakeItEasy.user.props file to set the build profile to netcore3.0 (which was fun), and run build integ, I again get

D:\Sandbox\FakeItEasy\tests\FakeItEasy.Tests.TestHelpers\FakeItEasy.Tests.TestHelpers.csproj :
error NU1201: Project FakeItEasy is not compatible with netstandard2.0 (.NETStandard,Version=v2.0).
Project FakeItEasy supports: netstandard2.1 (.NETStandard,Version=v2.1) [D:\Sandbox\FakeItEasy\FakeItEasy.sln]

Copy link
Member Author

Choose a reason for hiding this comment

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

Argh... OK, I'll really review and test those TFMs before I push a new fix.

</PropertyGroup>

<PropertyGroup Condition="'$(TargetFramework)' == 'netstandard1.6'">
<DefineConstants>$(DefineConstants);FEATURE_NETCORE_REFLECTION;USE_RUNTIMELOADER;FEATURE_EXCEPTION_DISPATCH_INFO;FEATURE_ARRAY_EMPTY;FEATURE_PARAMETERINFO_CUSTOMATTRIBUTES_PROPERTY</DefineConstants>
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 both forgot about this comment as well.

@thomaslevesque
Copy link
Member Author

It's the review that never ends. Sorry.

My fault... It should be fixed now
Thanks for the careful review!

@blairconrad
Copy link
Member

blairconrad commented Oct 22, 2019

Hey. Packing fails in a clean sandbox with a non-full build. e.g. when I build netcore3.0. Perhaps we should take a page out of the main package's book and use something like

<None Include="$(OutputPath)/**/$(AssemblyName).pdb" Pack="true" PackagePath="lib" Visible="false" />

in FakeItEasy.Extensions.ValueTask.csproj

@thomaslevesque
Copy link
Member Author

Packing fails in a clean sandbox with a non-full build. e.g. when I build netcore3.0.

Oh :(
I had tried all profiles, but only with the build target.

Perhaps we should take a page out of the main package's book and use something like

Yes, I think I'll do that. Although I intended to drop that at some point and use AllowedOutputExtensionsInPackageBuildOutputFolder instead, as mentioned here.

@thomaslevesque
Copy link
Member Author

Perhaps we should take a page out of the main package's book and use something like

In fact, it doesn't work, because it also takes netstandard2.1, which should be excluded...

@blairconrad
Copy link
Member

blairconrad commented Oct 22, 2019

<None Include="$(OutputPath)/net45/$(AssemblyName).pdb" Pack="true" PackagePath="lib/net45" Visible="false" Condition="Exists('$(OutputPath)/net45/$(AssemblyName).pdb')" />
<None Include="$(OutputPath)/netstandard1.6/$(AssemblyName).pdb" Pack="true" PackagePath="lib/netstandard1.6" Visible="false" Condition="Exists('$(OutputPath)/netstandard1.6/$(AssemblyName).pdb')" />
<None Include="$(OutputPath)/netstandard2.0/$(AssemblyName).pdb" Pack="true" PackagePath="lib/netstandard2.0" Visible="false" Condition="Exists('$(OutputPath)/netstandard2.0/$(AssemblyName).pdb')" />

?

@thomaslevesque
Copy link
Member Author

It's an option. But if the PDBs are there from a previous build, it would include them even when they're not in the selected TFMs. I have another option in mind, let me try...

@thomaslevesque
Copy link
Member Author

I have another option in mind, let me try...

Well, my thing didn't work. I found another way, which isn't great, but the end result is slightly cleaner than before

But if the PDBs are there from a previous build, it would include them even when they're not in the selected TFMs

Actually, it doesn't really matter, because we will only ever publish packages produced by the full build anyway.

<SignAssembly>true</SignAssembly>
<IncludeBuildOutput>false</IncludeBuildOutput>
Copy link
Member Author

Choose a reason for hiding this comment

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

This causes the Pack target to not include .dll and .xml files from the build output. I include them manually, which gives me control over what goes into the package.

<SignAssembly>true</SignAssembly>
<IncludeBuildOutput>false</IncludeBuildOutput>
<BeforePack>AddNetStd21Placeholder;$(BeforePackOn)</BeforePack>
Copy link
Member Author

Choose a reason for hiding this comment

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

Cleaner than depending on _WalkEachTargetPerFramework, which is a private target

<None Include="$(OutputPath)/**/$(AssemblyName).dll" Pack="true" PackagePath="lib" Visible="false" />
<None Include="$(OutputPath)/**/$(AssemblyName).pdb" Pack="true" PackagePath="lib" Visible="false" />
<None Include="$(OutputPath)/**/$(AssemblyName).xml" Pack="true" PackagePath="lib" Visible="false" />
<None Remove="$(OutputPath)/netstandard2.1/*" />
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 include everything, then exclude what I don't want

@blairconrad blairconrad merged commit c2c47b2 into FakeItEasy:master Oct 22, 2019
@blairconrad
Copy link
Member

Nice job, @thomaslevesque! Thanks for your work.

@thomaslevesque thomaslevesque deleted the target-framework-sets branch October 23, 2019 07:40
@thomaslevesque
Copy link
Member Author

Thanks for the careful review!

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.

Support building only part of the target frameworks
2 participants