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

Implement /WarnAsError to treat specified warnings as errors #1355

Merged
merged 1 commit into from
Nov 29, 2016

Conversation

jeffkl
Copy link
Contributor

@jeffkl jeffkl commented Nov 17, 2016

Specify just /WarnAsError to have all warnings logged as errors as well as have the build fail.
Specify a comma delimited list of warning codes to have just that set of warnings treated as errors as well as have the build fail.

Targets will still show as succeeded and the tasks will continue to execute but the overall build result will be a failure.

Related to #68 and will close in my next change to add /NoWarn.

Help text should be reviewed as well:

  /warnaserror[:code[;code2]]
                     List of warning codes to treats as errors.  Use a semicolon
                     or a comma to separate multiple warning codes. To treat all
                     warnings as errors use the switch with no values.
                     (Short form: /err[:c;[c2]])
                     Example:
                       /warnaserror:MSB4130

                     When a warning is treated as an error the target will
                     continue to execute as if it was a warning but the overall
                     build result will fail.

Copy link
Member

@rainersigwald rainersigwald left a comment

Choose a reason for hiding this comment

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

Biggest concern is the additive-vs-override nature of multiple arguments.

ILoggingService loggingService = ((IBuildComponentHost)this).LoggingService;

// Check if the user had specified /warnaserror and then override the result if there were any errors.
// This ensures the old behavior stays in tact where builds could succeed even if a failure was logged.
Copy link
Member

Choose a reason for hiding this comment

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

typo: no space in intact

@@ -621,6 +631,12 @@ public void EndBuild()

if (loggingService != null)
{
// Override the build success if the user specified /warnaserror and any errors were logged outside of a build submission.
if (_overallBuildSuccess && loggingService.WarningsAsErrors != null && loggingService.HasBuildSubmissionLoggedErrors(-1))
Copy link
Member

Choose a reason for hiding this comment

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

Is there a named constant somewhere for the magic -1?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I did a quick search and didn't find anything. I saw this line and figured it was okay to use -1. Should I declare a constant like NullSubmissionId ?

if (loggingService.WarningsAsErrors != null && loggingService.HasBuildSubmissionLoggedErrors(submission.SubmissionId))
{
result.SetOverallResult(overallResult: false);
}
Copy link
Member

Choose a reason for hiding this comment

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

Is there a good way to abstract this logic? It's pretty repetitive in here. Maybe EnsureFailureIfWarningsAsErrors(BuildResult, ILoggingService)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good idea, I can make a method that does this for two of the places.

@@ -320,6 +325,7 @@ private BuildParameters(BuildParameters other)
_disableInProcNode = other._disableInProcNode;
_logTaskInputs = other._logTaskInputs;
_logInitialPropertiesAndItems = other._logInitialPropertiesAndItems;
_warningsAsErrors = other._warningsAsErrors == null ? null : new HashSet<string>(other._warningsAsErrors, StringComparer.OrdinalIgnoreCase);
Copy link
Member

Choose a reason for hiding this comment

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

It's probably not worth changing this, but I would hazard a guess that for this use case (small sets of inputs), a SortedSet might actually be faster.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I did some limited research on what was faster and from what I could tell there was no difference. Do you know of any guidance on which is faster? I wanted the fastest possible lookup for sure.

Copy link
Member

Choose a reason for hiding this comment

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

AFAIK it's highly dependent on the individual use case, so we probably can't tell for this case without extensive benchmarking.

My understanding is based on Pike's third rule: Fancy algorithms are slow when n is small, and n is usually small. In this case, a hashtable is awesomely super fast, linear lookup . . . but requires hashing and indirection/pointer-following. A sorted set that is small, as I expect this thing to be, could easily use fewer instructions per lookup.

But we'd have to measure to know (Rule 2!) and I don't think this will be worth worrying about, so I'd say to leave it as-is.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I just did some benchmarks. If you have 10 warnings to set as errors and your build logs 10,000 warnings, the fastest is to use HashSet<T>(StringComparer.OrdinalIgnoreCase). I tried doing a case sensitive search but things placed in the set are upper case and the item being checked for is uppercased before the check but that was slower. The SortedSet<T> was just a little bit slower.

In my scenario, it took 1.04 milliseconds to check 10,000 times for the 10 items. I think the perf here is good enough but at least know we know. :emoji-for-the-'the-more-you-know'-public-service-annoncement:

Copy link
Member

Choose a reason for hiding this comment

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

Nice!

Copy link
Contributor

Choose a reason for hiding this comment

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

Alternatively, declare this as an ImmutableHashset and just copy the reference rather than cloning a new one. Theoretically they are slower, with logn lookup. Can you benchmark this one as well? :)


In reply to: 88770185 [](ancestors = 88770185)

@@ -584,6 +590,15 @@ public bool OnlyLogCriticalEvents
}

/// <summary>
/// A list of warnings to treat as errors. To treat all warnings as errors, set this to an empty <see cref="HashSet{String}"/>.
Copy link
Member

Choose a reason for hiding this comment

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

Hmm. The double-use here seems a bit mysterious, in a way it doesn't on the command line. But it's not wrong . . .

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was trying to avoid having another property like bool HasUserSpecifiedWarningsAsErrors so I am using the three states of a reference type; null, not null; or has a value. The usages of these are all internal so we can change it later. I made this decision for simplicity but am open to suggestions.

Assert.Equal(expectedBuildEvent.LineNumber, actualBuildEvent.LineNumber);
Assert.Equal(expectedBuildEvent.BuildEventContext, actualBuildEvent.BuildEventContext);
Assert.Equal(expectedBuildEvent.ThreadId, actualBuildEvent.ThreadId);
Assert.Equal(expectedBuildEvent.Timestamp, actualBuildEvent.Timestamp);
Copy link
Member

Choose a reason for hiding this comment

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

Yikes! But I guess it's not worth implementing IEquatable just for this test.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah I thought about making a Clone() method as well but I didn't want to go there... This unit test made sure I was properly copying every single property when I mutate the warning into an error (and I did miss one 😄)


When a warning is treated as an error the target will
continue to execute as if it was a warning but the overall
build result will show as failed.
Copy link
Member

Choose a reason for hiding this comment

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

"but the overall build will fail." seems a bit clearer.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Haha that's what I had originally and then I second guessed myself :sad:


Assert.NotNull(actualWarningsAsErrors);

Assert.Equal(expectedWarningsAsErors, actualWarningsAsErrors, StringComparer.OrdinalIgnoreCase);
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 test implying that the switches are additive? I find that a bit surprising. And definitely I find it surprising that if the last one is a no-code-specified-turn-everything-on setting, that doesn't win.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was imaging a scenario where the list of warning codes was specified in one or more response files, an environment variable and the command-line. I figured I would just conglomerate them into one set. If this is the case, then specifying the switch by itself doesn't add any more codes and some codes already exist so the functionality is to only treat the specified codes as errors.

This is sort of how /p:foo=bar works minus the ability to clear them all by specifying just /p. Let's discuss this tomorrow.

Copy link
Member

Choose a reason for hiding this comment

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

For posterity: we discussed, and decided that the additive model makes sense, but a new no-code-specified setting should override previous specific ones. If you need to clear the setting you can do /warnaserror /warnaserror:X to get back to a "just X" state.

Assert.Equal(0, actualWarningsAsErrors.Count);
}

#if !RUNTIME_TYPE_NETCORE
Copy link
Member

Choose a reason for hiding this comment

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

Why?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ResourceManager.GetResourceSet() doesn't exist on .NET core. This test is just making sure I typed up the new command-line argument usage correctly and so it technically doesn't matter if it isn't running on .NET Core since it's testing all of the switches. The RESX resources are not #ifdef'd out, only the display of them.

Should I put a comment?

@@ -1966,7 +1975,7 @@ bool recursing
}

ErrorUtilities.VerifyThrow(!invokeBuild || !String.IsNullOrEmpty(projectFile), "We should have a project file if we're going to build.");

Copy link
Member

Choose a reason for hiding this comment

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

👻 trailing whitespace

Copy link
Contributor Author

Choose a reason for hiding this comment

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

grr

@@ -36,8 +38,9 @@ public void BogusSwitchIdentificationTests()
bool multipleParametersAllowed;
string missingParametersErrorMessage;
bool unquoteParameters;
bool allowEmptyParmaters;
Copy link
Member

Choose a reason for hiding this comment

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

typo: Parmaters

_parameterizedSwitches[(int)parameterizedSwitch].parameters.AddRange(QuotingUtilities.SplitUnquoted(switchParameters, int.MaxValue, false /* discard empty parameters */, unquoteParameters, out emptyParameters, s_parameterSeparators));
if (String.Empty.Equals(switchParameters) && emptyParametersAllowed)
{
// Store an null parameter if its allowed
Copy link
Member

Choose a reason for hiding this comment

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

typo: an -> a

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This originally said "an empty string", thanks for catching it


Assert.NotNull(actualWarningsAsErrors);

Assert.Equal(expectedWarningsAsErors, actualWarningsAsErrors, StringComparer.OrdinalIgnoreCase);
Copy link
Member

Choose a reason for hiding this comment

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

For posterity: we discussed, and decided that the additive model makes sense, but a new no-code-specified setting should override previous specific ones. If you need to clear the setting you can do /warnaserror /warnaserror:X to get back to a "just X" state.

@@ -2029,6 +2040,37 @@ internal static TextWriter ProcessPreprocessSwitch(string[] parameters)
return writer;
}

internal static ISet<string> ProcessWarnAsErrorSwitch(CommandLineSwitches commandLineSwitches)
{
// TODO: Parse an environment variable as well?
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 feel strongly about this. Doesn't seem worth it.

@jeffkl
Copy link
Contributor Author

jeffkl commented Nov 19, 2016

I'm going to squash this and re-push. When we're ready I'd like to do a merge commit so I can start on the /NoWarn switch and not have any merge conflicts.

Specify just /WarnAsError to have all warnings logged as errors as well as have the build fail.
Specify a list of warning codes to have just that set of warnings treated as errors as well as have the build fail.

Targets will still show as succeeded and the tasks will continue to execute but the overall build result will be a failure.

Related to dotnet#68 and will close in my next change to add /NoWarn.

* Feature switch FEATURE_RESOURCEMANAGER_GETRESOURCESET
* Support for command-line arguments having empty values
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants