/// <summary> /// Helper function to build a simple project based on a particular change wave being set. /// Call SetChangeWave on your TestEnvironment before calling this function. /// </summary> /// <param name="testEnvironment">The TestEnvironment being used for this test.</param> /// <param name="versionToCheckAgainstCurrentChangeWave">The version to compare to the current set Change Wave.</param> /// <param name="currentChangeWaveShouldUltimatelyResolveTo">What the project property for the environment variable MSBuildDisableFeaturesFromVersion ultimately resolves to.</param> /// <param name="warningCodesLogShouldContain">An array of warning codes that should exist in the resulting log. Ex: "MSB4271".</param> private void buildSimpleProjectAndValidateChangeWave(TestEnvironment testEnvironment, Version versionToCheckAgainstCurrentChangeWave, Version currentChangeWaveShouldUltimatelyResolveTo, params string[] warningCodesLogShouldContain) { bool isThisWaveEnabled = versionToCheckAgainstCurrentChangeWave < currentChangeWaveShouldUltimatelyResolveTo || currentChangeWaveShouldUltimatelyResolveTo == ChangeWaves.EnableAllFeatures; ChangeWaves.AreFeaturesEnabled(versionToCheckAgainstCurrentChangeWave).ShouldBe(isThisWaveEnabled); string projectFile = $"" + $"<Project>" + $"<Target Name='HelloWorld' Condition=\"$([MSBuild]::AreFeaturesEnabled('{versionToCheckAgainstCurrentChangeWave}')) and '$(MSBUILDDISABLEFEATURESFROMVERSION)' == '{currentChangeWaveShouldUltimatelyResolveTo}'\">" + $"<Message Text='Hello World!'/>" + $"</Target>" + $"</Project>"; TransientTestFile file = testEnvironment.CreateFile("proj.csproj", projectFile); ProjectCollection collection = new ProjectCollection(); MockLogger log = new MockLogger(_output); collection.RegisterLogger(log); Project p = collection.LoadProject(file.Path); p.Build().ShouldBeTrue(); log.FullLog.Contains("Hello World!").ShouldBe(isThisWaveEnabled); if (warningCodesLogShouldContain != null) { log.WarningCount.ShouldBe(warningCodesLogShouldContain.Length); log.AssertLogContains(warningCodesLogShouldContain); } ChangeWaves.ResetStateForTests(); }
public void VersionTooHighClampsToHighestVersionInRotation(string disableFromWave) { using (TestEnvironment env = TestEnvironment.Create()) { env.SetChangeWave(disableFromWave); BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly(); // all waves but the highest should pass for (int i = 0; i < ChangeWaves.AllWaves.Length - 1; i++) { ChangeWaves.ResetStateForTests(); string projectFile = $"" + $"<Project>" + $"<Target Name='HelloWorld' Condition=\"'$(MSBUILDDISABLEFEATURESFROMVERSION)' == '{ChangeWaves.HighestWave}' and $([MSBuild]::AreFeaturesEnabled('{ChangeWaves.AllWaves[i]}'))\">" + $"<Message Text='Hello World!'/>" + $"</Target>" + $"</Project>"; TransientTestFile file = env.CreateFile("proj.csproj", projectFile); ProjectCollection collection = new ProjectCollection(); MockLogger log = new MockLogger(); collection.RegisterLogger(log); Project p = collection.LoadProject(file.Path); p.Build().ShouldBeTrue(); BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly(); log.WarningCount.ShouldBe(1); log.AssertLogContains("out of rotation"); log.AssertLogContains("Hello World!"); } } }
public void TargetPathAlreadySet_DisabledUnderChangeWave16_10(string targetPath) { using TestEnvironment env = TestEnvironment.Create(); string link = "c:/some/path"; ChangeWaves.ResetStateForTests(); env.SetEnvironmentVariable("MSBUILDDISABLEFEATURESFROMVERSION", ChangeWaves.Wave16_10.ToString()); BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly(); AssignTargetPath t = new AssignTargetPath(); t.BuildEngine = new MockEngine(); Dictionary <string, string> metaData = new Dictionary <string, string>(); metaData.Add("TargetPath", targetPath); metaData.Add("Link", link); t.Files = new ITaskItem[] { new TaskItem( itemSpec: NativeMethodsShared.IsWindows ? @"c:\f1\f2\file.txt" : "/f1/f2/file.txt", itemMetadata: metaData) }; t.RootFolder = NativeMethodsShared.IsWindows ? @"c:\f1\f2" : "/f1/f2"; t.Execute().ShouldBeTrue(); t.AssignedFiles.Length.ShouldBe(1); t.AssignedFiles[0].GetMetadata("TargetPath").ShouldBe(link); ChangeWaves.ResetStateForTests(); }
public void NoChangeWaveSetMeansAllChangeWavesAreEnabled(string featureWave) { using (TestEnvironment env = TestEnvironment.Create()) { ChangeWaves.ResetStateForTests(); ChangeWaves.AreFeaturesEnabled(featureWave).ShouldBe(true); string projectFile = $"" + $"<Project>" + $"<Target Name='HelloWorld' Condition=\"'$(MSBUILDDISABLEFEATURESFROMVERSION)' == '{ChangeWaves.EnableAllFeatures}' and $([MSBuild]::AreFeaturesEnabled('{featureWave}'))\">" + $"<Message Text='Hello World!'/>" + $"</Target>" + $"</Project>"; TransientTestFile file = env.CreateFile("proj.csproj", projectFile); ProjectCollection collection = new ProjectCollection(); MockLogger log = new MockLogger(); collection.RegisterLogger(log); collection.LoadProject(file.Path).Build().ShouldBeTrue(); log.AssertLogContains("Hello World!"); BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly(); } }
public void CorrectlyDetermineDisabledFeatures() { using (TestEnvironment env = TestEnvironment.Create()) { env.SetChangeWave(ChangeWaves.LowestWave); BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly(); foreach (string wave in ChangeWaves.AllWaves) { ChangeWaves.AreFeaturesEnabled(wave).ShouldBeFalse(); string projectFile = $"" + $"<Project>" + $"<Target Name='HelloWorld' Condition=\"$([MSBuild]::AreFeaturesEnabled('{wave}')) == false\">" + $"<Message Text='Hello World!'/>" + $"</Target>" + $"</Project>"; TransientTestFile file = env.CreateFile("proj.csproj", projectFile); ProjectCollection collection = new ProjectCollection(); MockLogger log = new MockLogger(); collection.RegisterLogger(log); Project p = collection.LoadProject(file.Path); p.Build().ShouldBeTrue(); log.AssertLogContains("Hello World!"); } BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly(); } }
internal void InitializeForTests(SdkResolverLoader resolverLoader = null, IList <SdkResolver> resolvers = null) { if (resolverLoader != null) { _sdkResolverLoader = resolverLoader; } _specificResolversManifestsRegistry = null; _generalResolversManifestsRegistry = null; _manifestToResolvers = null; _resolversList = null; if (resolvers != null) { if (ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_4)) { _specificResolversManifestsRegistry = new List <SdkResolverManifest>(); _generalResolversManifestsRegistry = new List <SdkResolverManifest>(); _manifestToResolvers = new Dictionary <SdkResolverManifest, IList <SdkResolver> >(); SdkResolverManifest sdkResolverManifest = new SdkResolverManifest(DisplayName: "TestResolversManifest", Path: null, ResolvableSdkRegex: null); _generalResolversManifestsRegistry.Add(sdkResolverManifest); _manifestToResolvers[sdkResolverManifest] = resolvers; } else { _resolversList = resolvers; } } }
public void WildcardDriveEnumerationTaskItemLogsError(string itemSpec) { using (var env = TestEnvironment.Create()) { Helpers.ResetStateForDriveEnumeratingWildcardTests(env, "1"); try { MockEngine engine = new MockEngine(); CreateItem t = new CreateItem() { BuildEngine = engine, Include = new ITaskItem[] { new TaskItem(itemSpec) }, }; t.Execute().ShouldBeFalse(); engine.Errors.ShouldBe(1); engine.AssertLogContains("MSB5029"); } finally { ChangeWaves.ResetStateForTests(); } } }
private static void VerifyDriveEnumerationWarningLoggedUponCreateItemExecution(string itemSpec) { using (var env = TestEnvironment.Create()) { Helpers.ResetStateForDriveEnumeratingWildcardTests(env, "0"); try { MockEngine engine = new MockEngine(); CreateItem t = new CreateItem() { BuildEngine = engine, Include = new ITaskItem[] { new TaskItem(itemSpec) }, }; t.Execute().ShouldBeTrue(); engine.Warnings.ShouldBe(1); engine.AssertLogContains("MSB5029"); } finally { ChangeWaves.ResetStateForTests(); } } }
public void InvalidCallerForIsFeatureEnabledThrows(string waveToCheck) { using (TestEnvironment env = TestEnvironment.Create()) { env.SetChangeWave("16.8"); BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly(); Shouldly.Should.Throw <InternalErrorException>(() => ChangeWaves.AreFeaturesEnabled(waveToCheck)); } }
/// <summary> /// Reads the application configuration file. /// NOTE: this is abstracted into a method to support unit testing GetToolsetDataFromConfiguration(). /// Unit tests wish to avoid reading (nunit.exe) application configuration file. /// </summary> private static Configuration ReadApplicationConfiguration() { if (ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_0)) { return(s_configurationCache.Value); } else { return(ReadOpenMappedExeConfiguration()); } }
public virtual SdkResult ResolveSdk(int submissionId, SdkReference sdk, LoggingContext loggingContext, ElementLocation sdkReferenceLocation, string solutionPath, string projectPath, bool interactive, bool isRunningInVisualStudio) { if (ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_4)) { return(ResolveSdkUsingResolversWithPatternsFirst(submissionId, sdk, loggingContext, sdkReferenceLocation, solutionPath, projectPath, interactive, isRunningInVisualStudio)); } else { return(ResolveSdkUsingAllResolvers(submissionId, sdk, loggingContext, sdkReferenceLocation, solutionPath, projectPath, interactive, isRunningInVisualStudio)); } }
public void EndToEndMultilineExec_EscapeSpecialCharacters_DisabledUnderChangeWave16_10() { using (var env = TestEnvironment.Create(_output)) { ChangeWaves.ResetStateForTests(); env.SetEnvironmentVariable("MSBUILDDISABLEFEATURESFROMVERSION", ChangeWaves.Wave16_10.ToString()); BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly(); var testProject = env.CreateTestProjectWithFiles(@"<Project> <Target Name=""ExecCommand""> <Exec Command=""echo Hello, World!"" /> </Target> </Project>"); // Ensure path has subfolders var newTempPath = env.CreateNewTempPathWithSubfolder("hello()wo(rld)").TempPath; string tempPath = Path.GetTempPath(); Assert.StartsWith(newTempPath, tempPath); using (var buildManager = new BuildManager()) { MockLogger logger = new MockLogger(_output, profileEvaluation: false, printEventsToStdout: false); var parameters = new BuildParameters() { Loggers = new[] { logger }, }; var collection = new ProjectCollection( new Dictionary <string, string>(), new[] { logger }, remoteLoggers: null, ToolsetDefinitionLocations.Default, maxNodeCount: 1, onlyLogCriticalEvents: false, loadProjectsReadOnly: true); var project = collection.LoadProject(testProject.ProjectFile).CreateProjectInstance(); var request = new BuildRequestData( project, targetsToBuild: new[] { "ExecCommand" }, hostServices: null); var result = buildManager.Build(parameters, request); logger.AssertLogContains("Hello, World!"); result.OverallResult.ShouldBe(BuildResultCode.Failure); } ChangeWaves.ResetStateForTests(); } }
private static XmlReader GetXmlReader(string file, StreamReader input, bool loadAsReadOnly, out Encoding encoding) { string uri = new UriBuilder(Uri.UriSchemeFile, string.Empty) { Path = file }.ToString(); XmlReader reader = null; if (ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave16_10) && loadAsReadOnly && !_disableReadOnlyLoad) { // Create an XML reader with IgnoreComments and IgnoreWhitespace set if we know that we won't be asked // to write the DOM back to a file. This is a performance optimization. XmlReaderSettings settings = new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore, IgnoreComments = true, IgnoreWhitespace = true, }; reader = XmlReader.Create(input, settings, uri); // Try to set Normalization to false. We do this to remain compatible with earlier versions of MSBuild // where we constructed the reader with 'new XmlTextReader()' which has normalization enabled by default. PropertyInfo normalizationPropertyInfo = GetNormalizationPropertyInfo(reader.GetType()); if (normalizationPropertyInfo != null) { normalizationPropertyInfo.SetValue(reader, false); } else { // Fall back to using XmlTextReader if the prop could not be bound. Debug.Fail("Could not set Normalization to false on the result of XmlReader.Create"); _disableReadOnlyLoad = true; reader.Dispose(); reader = null; } } if (reader == null) { reader = new XmlTextReader(uri, input) { DtdProcessing = DtdProcessing.Ignore }; } reader.Read(); encoding = input.CurrentEncoding; return(reader); }
protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) { if (ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_4)) { string?libraryPath = _resolver?.ResolveUnmanagedDllToPath(unmanagedDllName); if (libraryPath != null) { return(LoadUnmanagedDllFromPath(libraryPath)); } } return(base.LoadUnmanagedDll(unmanagedDllName)); }
internal static object GetRegistryValue(string keyName, string valueName, object defaultValue) { #if RUNTIME_TYPE_NETCORE // .NET Core MSBuild used to always return empty, so match that behavior // on non-Windows (no registry), and with a changewave (in case someone // had a registry property and it breaks when it lights up). if (!NativeMethodsShared.IsWindows || !ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_4)) { return(defaultValue); } #endif return(Registry.GetValue(keyName, valueName, defaultValue)); }
private void SetResolverState(int submissionId, SdkResolver resolver, object state) { // Do not set state for resolution requests that are not associated with a valid build submission ID if (submissionId != BuildEventContext.InvalidSubmissionId) { ConcurrentDictionary <SdkResolver, object> resolverState = _resolverStateBySubmission.GetOrAdd( submissionId, _ => new ConcurrentDictionary <SdkResolver, object>( NativeMethodsShared.GetLogicalCoreCount(), ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_4) ? _specificResolversManifestsRegistry.Count + _generalResolversManifestsRegistry.Count : _resolversList.Count)); resolverState.AddOrUpdate(resolver, state, (sdkResolver, obj) => state); } }
public void NoChangeWaveSetMeansAllChangeWavesAreEnabled(string featureVersion) { using (TestEnvironment env = TestEnvironment.Create()) { // Reset static ChangeWave SetChangeWave(string.Empty, env); Version featureAsVersion = Version.Parse(featureVersion); ChangeWaves.AreFeaturesEnabled(featureAsVersion).ShouldBe(true); buildSimpleProjectAndValidateChangeWave(testEnvironment: env, versionToCheckAgainstCurrentChangeWave: featureAsVersion, currentChangeWaveShouldUltimatelyResolveTo: ChangeWaves.EnableAllFeatures, warningCodesLogShouldContain: null); } }
/// <summary> /// Gets the location of the directory used for diagnostic log files. /// </summary> /// <returns></returns> private static string GetDebugDumpPath() { string debugPath = // Cannot access change wave logic from these assemblies (https://github.com/dotnet/msbuild/issues/6707) #if CLR2COMPATIBILITY || MICROSOFT_BUILD_ENGINE_OM_UNITTESTS Environment.GetEnvironmentVariable("MSBUILDDEBUGPATH"); #else ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_0) ? DebugUtils.DebugPath : Environment.GetEnvironmentVariable("MSBUILDDEBUGPATH"); #endif return(!string.IsNullOrEmpty(debugPath) ? debugPath : Path.GetTempPath()); }
public void SpacePropertyOptOutWave16_10() { using TestEnvironment env = TestEnvironment.Create(); ChangeWaves.ResetStateForTests(); env.SetEnvironmentVariable("MSBUILDDISABLEFEATURESFROMVERSION", ChangeWaves.Wave16_10.ToString()); BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly(); Scanner lexer = new Scanner("$(x )", ParserOptions.AllowProperties); AdvanceToScannerError(lexer); Assert.Null(lexer.UnexpectedlyFound); lexer = new Scanner("$( x)", ParserOptions.AllowProperties); AdvanceToScannerError(lexer); Assert.Null(lexer.UnexpectedlyFound); ChangeWaves.ResetStateForTests(); }
/// <summary> /// Scan for the end of the property expression /// </summary> /// <param name="expression">property expression to parse</param> /// <param name="index">current index to start from</param> /// <param name="indexResult">If successful, the index corresponds to the end of the property expression. /// In case of scan failure, it is the error position index.</param> /// <returns>result indicating whether or not the scan was successful.</returns> private static bool ScanForPropertyExpressionEnd(string expression, int index, out int indexResult) { int nestLevel = 0; bool whitespaceCheck = false; unsafe { fixed(char *pchar = expression) { while (index < expression.Length) { char character = pchar[index]; if (character == '(') { nestLevel++; whitespaceCheck = true; } else if (character == ')') { nestLevel--; whitespaceCheck = false; } else if (whitespaceCheck && char.IsWhiteSpace(character) && ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave16_10)) { indexResult = index; return(false); } // We have reached the end of the parenthesis nesting // this should be the end of the property expression // If it is not then the calling code will determine that if (nestLevel == 0) { indexResult = index; return(true); } else { index++; } } } } indexResult = index; return(true); }
internal static object GetRegistryValueFromView(string keyName, string valueName, object defaultValue, params object[] views) { #if RUNTIME_TYPE_NETCORE // .NET Core MSBuild used to always return empty, so match that behavior // on non-Windows (no registry), and with a changewave (in case someone // had a registry property and it breaks when it lights up). if (!NativeMethodsShared.IsWindows || !ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_4)) { return(defaultValue); } #endif if (views == null || views.Length == 0) { views = DefaultRegistryViews; } return(GetRegistryValueFromView(keyName, valueName, defaultValue, new ArraySegment <object>(views))); }
public void EscapeSpecifiedCharactersInPathToGeneratedBatchFile_DisabledUnderChangeWave16_10() { using (var testEnvironment = TestEnvironment.Create()) { ChangeWaves.ResetStateForTests(); testEnvironment.SetEnvironmentVariable("MSBUILDDISABLEFEATURESFROMVERSION", ChangeWaves.Wave16_10.ToString()); BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly(); var newTempPath = testEnvironment.CreateNewTempPathWithSubfolder("hello()w]o(rld)").TempPath; string tempPath = Path.GetTempPath(); Assert.StartsWith(newTempPath, tempPath); // Now run the Exec task on a simple command. Exec exec = PrepareExec("echo Hello World!"); exec.Execute().ShouldBeFalse(); ChangeWaves.ResetStateForTests(); } }
public void AssertFirstResolverWithPatternCantResolveChangeWave17_4() { using (TestEnvironment env = TestEnvironment.Create()) { ChangeWaves.ResetStateForTests(); env.SetEnvironmentVariable("MSBUILDDISABLEFEATURESFROMVERSION", ChangeWaves.Wave17_4.ToString()); BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly(); SdkResolverService.Instance.InitializeForTests(new MockLoaderStrategy(includeResolversWithPatterns: true)); SdkReference sdk = new SdkReference("1sdkName", "referencedVersion", "minimumVersion"); var result = SdkResolverService.Instance.ResolveSdk(BuildEventContext.InvalidSubmissionId, sdk, _loggingContext, new MockElementLocation("file"), "sln", "projectPath", interactive: false, isRunningInVisualStudio: false); result.Path.ShouldBe("resolverpath1"); _logger.BuildMessageEvents.Select(i => i.Message).ShouldContain("MockSdkResolver1 running"); _logger.BuildMessageEvents.Select(i => i.Message).ShouldNotContain("MockSdkResolverWithResolvableSdkPattern1 running"); ChangeWaves.ResetStateForTests(); } }
private static string GetLockedFileMessage(string file) { string message = string.Empty; try { if (NativeMethodsShared.IsWindows && ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_4)) { var processes = LockCheck.GetProcessesLockingFile(file); message = !string.IsNullOrEmpty(processes) ? ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("Copy.FileLocked", processes) : String.Empty; } } catch (Exception) { // Never throw if we can't get the processes locking the file. } return(message); }
/// <summary> /// Parse the given <paramref name="fileSpec" /> into a <see cref="MSBuildGlob" /> using a given /// <paramref name="globRoot" />. /// </summary> /// <param name="globRoot"> /// The root of the glob. /// The fixed directory part of the glob and the match arguments (<see cref="IsMatch" /> and <see cref="MatchInfo" />) /// will get normalized against this root. /// If empty, the current working directory is used. /// Cannot be null, and cannot contain invalid path arguments. /// </param> /// <param name="fileSpec">The string to parse</param> /// <returns></returns> public static MSBuildGlob Parse(string globRoot, string fileSpec) { ErrorUtilities.VerifyThrowArgumentNull(globRoot, nameof(globRoot)); ErrorUtilities.VerifyThrowArgumentNull(fileSpec, nameof(fileSpec)); ErrorUtilities.VerifyThrowArgumentInvalidPath(globRoot, nameof(globRoot)); if (string.IsNullOrEmpty(globRoot)) { globRoot = Directory.GetCurrentDirectory(); } globRoot = Strings.WeakIntern(FileUtilities.NormalizePath(globRoot).WithTrailingSlash()); var lazyState = new Lazy <GlobState>(() => { FileMatcher.Default.GetFileSpecInfo( fileSpec, out string fixedDirectoryPart, out string wildcardDirectoryPart, out string filenamePart, out bool needsRecursion, out bool isLegalFileSpec, (fixedDirPart, wildcardDirPart, filePart) => { var normalizedFixedPart = NormalizeTheFixedDirectoryPartAgainstTheGlobRoot(fixedDirPart, globRoot); return(normalizedFixedPart, wildcardDirPart, filePart); }); Regex regex = null; if (isLegalFileSpec) { string matchFileExpression = FileMatcher.RegularExpressionFromFileSpec(fixedDirectoryPart, wildcardDirectoryPart, filenamePart); lock (s_regexCache) { s_regexCache.TryGetValue(matchFileExpression, out regex); } if (regex == null) { RegexOptions regexOptions = FileMatcher.DefaultRegexOptions; // compile the regex since it's expected to be used multiple times // For the kind of regexes used here, compilation on .NET Framework tends to be expensive and not worth the small // run-time boost so it's enabled only on .NET Core by default. #if RUNTIME_TYPE_NETCORE bool compileRegex = true; #else bool compileRegex = !ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_0); #endif if (compileRegex) { regexOptions |= RegexOptions.Compiled; } Regex newRegex = new Regex(matchFileExpression, regexOptions); lock (s_regexCache) { if (!s_regexCache.TryGetValue(matchFileExpression, out regex)) { s_regexCache[matchFileExpression] = newRegex; } } regex ??= newRegex; } } return(new GlobState(globRoot, fileSpec, isLegalFileSpec, fixedDirectoryPart, wildcardDirectoryPart, filenamePart, needsRecursion, regex)); },
protected override ImmutableList <I> SelectItems(ImmutableList <ItemData> .Builder listBuilder, ImmutableHashSet <string> globsToIgnore) { var itemsToAdd = ImmutableList.CreateBuilder <I>(); Lazy <Func <string, bool> > excludeTester = null; ImmutableList <string> .Builder excludePatterns = ImmutableList.CreateBuilder <string>(); if (_excludes != null) { // STEP 4: Evaluate, split, expand and subtract any Exclude foreach (string exclude in _excludes) { string excludeExpanded = _expander.ExpandIntoStringLeaveEscaped(exclude, ExpanderOptions.ExpandPropertiesAndItems, _itemElement.ExcludeLocation); var excludeSplits = ExpressionShredder.SplitSemiColonSeparatedList(excludeExpanded); excludePatterns.AddRange(excludeSplits); } if (excludePatterns.Count > 0) { excludeTester = new Lazy <Func <string, bool> >(() => EngineFileUtilities.GetFileSpecMatchTester(excludePatterns, _rootDirectory)); } } ISet <string> excludePatternsForGlobs = null; foreach (var fragment in _itemSpec.Fragments) { if (fragment is ItemSpec <P, I> .ItemExpressionFragment itemReferenceFragment) { // STEP 3: If expression is "@(x)" copy specified list with its metadata, otherwise just treat as string var itemsFromExpression = _expander.ExpandExpressionCaptureIntoItems( itemReferenceFragment.Capture, _evaluatorData, _itemFactory, ExpanderOptions.ExpandItems, includeNullEntries: false, isTransformExpression: out _, elementLocation: _itemElement.IncludeLocation); itemsToAdd.AddRange( excludeTester != null ? itemsFromExpression.Where(item => !excludeTester.Value(item.EvaluatedInclude)) : itemsFromExpression); } else if (fragment is ValueFragment valueFragment) { string value = valueFragment.TextFragment; if (excludeTester?.Value(EscapingUtilities.UnescapeAll(value)) != true) { var item = _itemFactory.CreateItem(value, value, _itemElement.ContainingProject.FullPath); itemsToAdd.Add(item); } } else if (fragment is GlobFragment globFragment) { // If this item is behind a false condition and represents a full drive/filesystem scan, expanding it is // almost certainly undesired. It should be skipped to avoid evaluation taking an excessive amount of time. bool skipGlob = !_conditionResult && globFragment.IsFullFileSystemScan && !Traits.Instance.EscapeHatches.AlwaysEvaluateDangerousGlobs && ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave16_8); if (!skipGlob) { string glob = globFragment.TextFragment; if (excludePatternsForGlobs == null) { excludePatternsForGlobs = BuildExcludePatternsForGlobs(globsToIgnore, excludePatterns); } string[] includeSplitFilesEscaped; if (MSBuildEventSource.Log.IsEnabled()) { MSBuildEventSource.Log.ExpandGlobStart(_rootDirectory, glob, string.Join(", ", excludePatternsForGlobs)); } using (_lazyEvaluator._evaluationProfiler.TrackGlob(_rootDirectory, glob, excludePatternsForGlobs)) { includeSplitFilesEscaped = EngineFileUtilities.GetFileListEscaped( _rootDirectory, glob, excludePatternsForGlobs ); } if (MSBuildEventSource.Log.IsEnabled()) { MSBuildEventSource.Log.ExpandGlobStop(_rootDirectory, glob, string.Join(", ", excludePatternsForGlobs)); } foreach (string includeSplitFileEscaped in includeSplitFilesEscaped) { itemsToAdd.Add(_itemFactory.CreateItem(includeSplitFileEscaped, glob, _itemElement.ContainingProject.FullPath)); } } } else { throw new InvalidOperationException(fragment.GetType().ToString()); } } return(itemsToAdd.ToImmutable()); }
private static bool ScanForPropertyExpressionEnd(string expression, int index, out int indexResult) { int nestLevel = 0; bool whitespaceFound = false; bool nonIdentifierCharacterFound = false; indexResult = -1; unsafe { fixed(char *pchar = expression) { while (index < expression.Length) { char character = pchar[index]; if (character == '(') { nestLevel++; } else if (character == ')') { nestLevel--; } else if (char.IsWhiteSpace(character)) { whitespaceFound = true; indexResult = index; } else if (!XmlUtilities.IsValidSubsequentElementNameCharacter(character)) { nonIdentifierCharacterFound = true; } if (character == '$' && index < expression.Length - 1 && pchar[index + 1] == '(') { if (!ScanForPropertyExpressionEnd(expression, index + 1, out index)) { indexResult = index; return(false); } } // We have reached the end of the parenthesis nesting // this should be the end of the property expression // If it is not then the calling code will determine that if (nestLevel == 0) { if (whitespaceFound && !nonIdentifierCharacterFound && ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave16_10)) { return(false); } indexResult = index; return(true); } else { index++; } } } } indexResult = index; return(true); }
public void SetChangeWave(string wave) { ChangeWaves.ResetStateForTests(); SetEnvironmentVariable("MSBUILDDISABLEFEATURESFROMVERSION", wave); }
/// <summary> /// Execute the task. /// </summary> /// <returns></returns> public override bool Execute() { AssignedFiles = new ITaskItem[Files.Length]; if (Files.Length > 0) { // Compose a file in the root folder. // NOTE: at this point fullRootPath may or may not have a trailing // slash because Path.GetFullPath() does not add or remove it string fullRootPath = Path.GetFullPath(RootFolder); // Ensure trailing slash otherwise c:\bin appears to match part of c:\bin2\foo fullRootPath = FileUtilities.EnsureTrailingSlash(fullRootPath); string currentDirectory = Directory.GetCurrentDirectory(); // check if the root folder is the same as the current directory // NOTE: the path returned from Directory.GetCurrentDirectory() // does not have a trailing slash, but fullRootPath does bool isRootFolderSameAsCurrentDirectory = ((fullRootPath.Length - 1 /* exclude trailing slash */) == currentDirectory.Length) && (String.Compare( fullRootPath, 0, currentDirectory, 0, fullRootPath.Length - 1 /* don't compare trailing slash */, StringComparison.OrdinalIgnoreCase) == 0); for (int i = 0; i < Files.Length; ++i) { AssignedFiles[i] = new TaskItem(Files[i]); // If TargetPath is already set, it takes priority. // https://github.com/dotnet/msbuild/issues/2795 string targetPath = ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave16_10) ? Files[i].GetMetadata(ItemMetadataNames.targetPath) : null; // If TargetPath not already set, fall back to default behavior. if (string.IsNullOrEmpty(targetPath)) { targetPath = Files[i].GetMetadata(ItemMetadataNames.link); } if (string.IsNullOrEmpty(targetPath)) { if (// if the file path is relative !Path.IsPathRooted(Files[i].ItemSpec) && // if the file path doesn't contain any relative specifiers !Files[i].ItemSpec.Contains("." + Path.DirectorySeparatorChar) && // if the file path is already relative to the root folder isRootFolderSameAsCurrentDirectory) { // then just use the file path as-is // PERF NOTE: we do this to avoid calling Path.GetFullPath() below, // because that method consumes a lot of memory, esp. when we have // a lot of items coming through this task targetPath = Files[i].ItemSpec; } else { // PERF WARNING: Path.GetFullPath() is expensive in terms of memory; // we should avoid calling it whenever possible string itemSpecFullFileNamePath = Path.GetFullPath(Files[i].ItemSpec); if (String.Compare(fullRootPath, 0, itemSpecFullFileNamePath, 0, fullRootPath.Length, StringComparison.CurrentCultureIgnoreCase) == 0) { // The item spec file is in the "cone" of the RootFolder. Return the relative path from the cone root. targetPath = itemSpecFullFileNamePath.Substring(fullRootPath.Length); } else { // The item spec file is not in the "cone" of the RootFolder. Return the filename only. targetPath = Path.GetFileName(Files[i].ItemSpec); } } } AssignedFiles[i].SetMetadata(ItemMetadataNames.targetPath, EscapingUtilities.Escape(targetPath)); } } return(true); }
/// <summary> /// Performs necessary operations for setting the MSBuildDisableFeaturesFromVersion environment variable. /// This is required because Change Waves is static and stale values can be seen between tests in the same assembly. /// </summary> /// <param name="wave">The version to set as the current Change Wave.</param> private void SetChangeWave(string wave, TestEnvironment env) { ChangeWaves.ResetStateForTests(); env.SetEnvironmentVariable("MSBUILDDISABLEFEATURESFROMVERSION", wave); BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly(); }