Skip to content

Enable Change Waves#5710

Merged
benvillalobos merged 46 commits intodotnet:masterfrom
benvillalobos:change-waves
Sep 25, 2020
Merged

Enable Change Waves#5710
benvillalobos merged 46 commits intodotnet:masterfrom
benvillalobos:change-waves

Conversation

@benvillalobos
Copy link
Member

@benvillalobos benvillalobos commented Sep 4, 2020

Enable Change Waves

Provide customers with the ability to opt out of potentially breaking changes. This effectively warns them that there is a breaking change coming and gives them time to prepare for it.

To Do:

  • Reach out to Kathleen and get approval on using MSBuildDisableFeaturesFromVersion
  • Rename intrinsic function to AreFeaturesEnabled
  • If MSBuildDisableFeaturesFromVersion is set to a value higher than the current highest change wave, clamp it to the current highest change wave.
  • If MSBuildDisableFeaturesFromVersion is set to a value within the bounds of versions, but not set to a specific wave, set MSBuildDisableFeaturesFromVersion to the next highest wave.
  • Have the build manager throw warnings as necessary in BeginBuild [Warnings are logged but not visible in logs or output window?]

Copy link
Contributor

@cdmihai cdmihai Sep 4, 2020

Choose a reason for hiding this comment

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

Who sets this and what do they expect should happen? I guess some user facing documentation would be nice at some point :)

Copy link
Contributor

Choose a reason for hiding this comment

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

All this version parsing might get noticeable, especially if the under-the-wave features are on hotter paths. Consider storing the version constants directly as Version instead of strings.

Copy link
Contributor

Choose a reason for hiding this comment

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

Warning as in logger BuildWarningEventArgs? Throw a custom exception type, catch it in the nearest places that can log events, and convert it to a warning event there.

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 was hoping to avoid this, but it looks like that won't be possible. What was suggested in the PR review meeting was to return some enum ChangeWavesReturnType.InvalidFormat, then at each feature site we check what the return value was and log a warning based on that.

Copy link
Contributor

Choose a reason for hiding this comment

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

That wouldn't work for features implemented in places which don't have access to a LoggingService.

An alternative to bubbling up a special exception would be to push down the LoggingService everywhere (potentially guised as a more generic logging interface, maybe). Then you can change the method to public static bool IsChangeWaveEnabled(string wave, IWhateverLoggingInteface log)

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 got around this by throwing warnings in Evaluator.cs, as I'm now setting MSBUILDCHANGEWAVEVERSION as a default property. The version gets sanitized there and a warning is thrown for out-of-rotation waves and invalid formats.

@cdmihai
Copy link
Contributor

cdmihai commented Sep 4, 2020

What if a wave has multiple breaking changes and the user wants to turn a subset of them off, not the entire wave? Should developers check both the wave and a Trait.EscapeHatch? Or should we instead compose a wave out of multiple Trait.EscapeHatches?

@benvillalobos
Copy link
Member Author

I believe this would happen on a case by case basis. Which falls in line with the intention of change waves. Ideally we get feedback from customers that state they want to have a particular feature enabled while disabling all others.

Feature code would then look something like:

if(ChangeWaves.IsChangeWaveEnabled(ChangeWaves.Wave16_8) || Traits.Instance.SomeFeatureEscapeHatch)
{
    <feature>
}

The discussion around having subsets of change-waves indicated that we don't want to do this for every feature, as it would become very cumbersome over time.

@cdmihai
Copy link
Contributor

cdmihai commented Sep 4, 2020

I believe this would happen on a case by case basis. Which falls in line with the intention of change waves. Ideally we get feedback from customers that state they want to have a particular feature enabled while disabling all others.

But if you need customer feedback on which feature to disable, you need to do another release to add the escape hatch for it and the customer would have to wait for that release instead of being able to immediately opt out from a particular feature.

Alternative you can hide the change waive inside Traits to make the conditions easier to write. Have a lookup table in Traits that maps each escape hatch to a change wave number. Feature writers would then just need to check for if(Traits.Instance.SomeFeatureEscapeHatch), or Traits.Instance.IsActive(Traits.SomeFeatureEscapeHatch))

@benvillalobos benvillalobos removed the WIP label Sep 17, 2020
@benvillalobos
Copy link
Member Author

/azp run

@azure-pipelines
Copy link

Pull request contains merge conflicts.

@benvillalobos
Copy link
Member Author

Alternative you can hide the change waive inside Traits to make the conditions easier to write. Have a lookup table in Traits that maps each escape hatch to a change wave number. Feature writers would then just need to check for if(Traits.Instance.SomeFeatureEscapeHatch), or Traits.Instance.IsActive(Traits.SomeFeatureEscapeHatch))

Discussion for our PR meeting: I find that this wouldn't add much value over just adding escape hatches for features we feel would need them. I may be misunderstanding though.

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
Sometimes we want to make a breaking change _and_ give customers a heads up as to what's breaking. So we develop the change and provide an opt-out while acknowledging that this feature will become a standard functionality down the line.
Sometimes we want to make a breaking change _and_ give customers a heads up as to what's breaking. So we develop the change and provide an opt-out while acknowledging that this feature will become standard functionality down the line.

Copy link
Member

Choose a reason for hiding this comment

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

Should this doc also say "don't use change waves if you're not part of the core MSBuild/SDK/Microsoft set of stuff?"

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'll add a note at the top in the dev docs.

Copy link
Member

Choose a reason for hiding this comment

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

Why is this public? Isn't this an internal implementation detail?

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 switched it over to internal, and now I think it needs to be back at public. The latest addition to TestEnvironment (Making a SetChangeWave function) means that it needs to set ChangeWaves.DisabledWave to null, which it can't do if the class is anything less than public.

Copy link
Contributor

Choose a reason for hiding this comment

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

What if you did that in ResetInstance_ForUnitTestsOnly instead? I would expect that method to reset DisabledWave anyway, and they're both in Microsoft.Build.Framework.UnitTests.csproj. It would also clean up the tests somewhat.

Copy link
Member

Choose a reason for hiding this comment

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

Copyright header

Copy link
Contributor

Choose a reason for hiding this comment

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

Did you mean to do this?

Copy link
Contributor

Choose a reason for hiding this comment

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

Bump

Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need this if it's the default?

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's just a nice-to-have for easy comparison in my out-of-rotation-check function

@benvillalobos
Copy link
Member Author

Interesting failure.

Log was expected to contain 'out of rotation', but did not. Full log:\n=======\nEvaluation started ("C:\Users\VssAdministrator\AppData\Local\Temp\3hwntpzc.jcx\Temporary80f421333ba042c887a872ec6a412cf7\proj.csproj")\r\nEvaluation finished ("C:\Users\VssAdministrator\AppData\Local\Temp\3hwntpzc.jcx\Temporary80f421333ba042c887a872ec6a412cf7\proj.csproj")\r\nBuild started.\r\nProject "proj.csproj" (default targets):\r\nBuilding with tools version "Current".\r\nTarget "HelloWorld" skipped, due to false condition; ('$(MSBUILDDISABLEFEATURESFROMVERSION)' == '16.8' and $([MSBuild]::IsChangeWaveEnabled('16.8')) == false) was evaluated as ('999.999' == '16.8' and True == false).\r\nDone building project "proj.csproj".\r\nBuild succeeded.\r\n\n=======\r\nExpected: True\r\nActual:   False

It's looking like the property is being set incorrectly? This doesn't fail locally.

@Forgind
Copy link
Contributor

Forgind commented Sep 20, 2020

Interesting failure.

Log was expected to contain 'out of rotation', but did not. Full log:\n=======\nEvaluation started ("C:\Users\VssAdministrator\AppData\Local\Temp\3hwntpzc.jcx\Temporary80f421333ba042c887a872ec6a412cf7\proj.csproj")\r\nEvaluation finished ("C:\Users\VssAdministrator\AppData\Local\Temp\3hwntpzc.jcx\Temporary80f421333ba042c887a872ec6a412cf7\proj.csproj")\r\nBuild started.\r\nProject "proj.csproj" (default targets):\r\nBuilding with tools version "Current".\r\nTarget "HelloWorld" skipped, due to false condition; ('$(MSBUILDDISABLEFEATURESFROMVERSION)' == '16.8' and $([MSBuild]::IsChangeWaveEnabled('16.8')) == false) was evaluated as ('999.999' == '16.8' and True == false).\r\nDone building project "proj.csproj".\r\nBuild succeeded.\r\n\n=======\r\nExpected: True\r\nActual:   False

It's looking like the property is being set incorrectly? This doesn't fail locally.

Try adding BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly.

@benvillalobos
Copy link
Member Author

@Forgind I did, and didn't notice any differences in the tests locally (examing the TestEnvironment that was created). Could be worth a shot.

@Forgind
Copy link
Contributor

Forgind commented Sep 20, 2020

@Forgind Nathan Mytelka FTE I did, and didn't notice any differences in the tests locally (examing the TestEnvironment that was created). Could be worth a shot.

You said it didn't fail locally, and when running the tests locally, they each had their own test environment. Running them all together in CI requires the test environment to be reset.

@Forgind
Copy link
Contributor

Forgind commented Sep 22, 2020

Any chance you could get it to only print once?

Evaluate runs even when not doing a full build, so this could turn into a lot of messages.

@benvillalobos
Copy link
Member Author

benvillalobos commented Sep 23, 2020

Ideally yes. Some ideas:

  1. Static boolean inside of ChangeWaves, HasLoggedError. The warnings in evaluator would then check that.
  2. Condition it based on if (ChangeWaves.DisabledWave != null), it's guaranteed to be null the first time.

Though both of these solutions only work for the first build. Beyond that it wouldn't throw a warning on the same node.

  1. A flag within evaluator that checks this?

I could theoretically call SetChangeWave at any point, so long as I have some logging context to throw a warning with.

@marcpopMSFT marcpopMSFT added this to the MSBuild 16.8 milestone Sep 23, 2020
@benvillalobos
Copy link
Member Author

Documenting that I got sign off on the environment variable MSBuildDisableFeaturesFromVersion from @KathleenDollard via teams.

case ChangeWaveConversionState.OutOfRotation:
log.LogWarning(BuildEventContext.Invalid, "", new BuildEventFileInfo(""), "ChangeWave_OutOfRotation", ChangeWaves.AllWaves[0], Traits.Instance.MSBuildDisableFeaturesFromVersion);
break;
case ChangeWaveConversionState.InvalidVersion:
Copy link
Contributor

Choose a reason for hiding this comment

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

Did we agree to a warning here? It feels far too strong to me, since people might do this intentionally

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 realize now that we did lean toward not having a warning here. Thanks for the catch

@Forgind
Copy link
Contributor

Forgind commented Sep 23, 2020

Would you mind making a checklist of what you've done and what you still have to do?

@benvillalobos
Copy link
Member Author

Current status:
Hitting issues with a locally-deployed msbuild.

  1. The warnings don't show up in the build logs (command line or in VS)
  2. The warnings aren't output in the output window (VS)

Command line experience is fine, minus the build logs not containing the warning.

It seems like I just need to find the correct way to log warnings in within the BuildManager

TransientTestFile file = env.CreateFile("proj.csproj", projectFile);

ProjectCollection collection = new ProjectCollection();
MockLogger log = new MockLogger();
Copy link
Contributor

Choose a reason for hiding this comment

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

Also check whether it's correctly set for calling your API from C# (for this and the next test)

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 not sure what there is to check? Something like ChangeWaves.DisabledVersion.ShouldBe(blah)?

Copy link
Contributor

Choose a reason for hiding this comment

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

Bump

Version changeWave;

// If unset, enable all features.
if (DisabledWave.Length == 0)
Copy link
Contributor

Choose a reason for hiding this comment

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

nit:
convert to switch statement and reorder according to which you think will be used most often. (So EnableAllFeaturesAsVersion at top, for instance.

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 switch statement based on what? I do agree this can be implemented better.

// Ensure it's set to an existing version within the current rotation
if (!AllWavesAsVersion.Contains(changeWave))
{
foreach (Version wave in AllWavesAsVersion)
Copy link
Contributor

Choose a reason for hiding this comment

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

This can be part of the if/else chain or switch statement, right? Also, why do you have to loop through them? Can't you just set it to whatever it is, since < (AreFeaturesEnabled) works the same either way?

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 loop is to find the next available version, because we only get here if:

  1. It is a valid version between the bounds
  2. it isn't already in the array
    So we need to find the next highest one to clamp it to.

@benvillalobos benvillalobos merged commit cda0060 into dotnet:master Sep 25, 2020
Forgind pushed a commit to Forgind/msbuild that referenced this pull request Sep 25, 2020
Enable change waves.

Added static ChangeWaves class that contains AreFeaturesEnabled and ApplyChangeWave. Documentation is in the wiki.
@benvillalobos benvillalobos mentioned this pull request Sep 25, 2020
10 tasks
Forgind added a commit that referenced this pull request Sep 25, 2020
Makes three PRs (#5669, #5625, and #5653) use Change Waves (#5710) to opt out as a unit. Also added a test to ensure that change waves worked properly for one of the three PRs and corrected a minor issue with tests for changes under a change wave that assume it is enabled when there exist tests that disable the relevant change wave without resetting it to empty at the end.
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.

5 participants