diff --git a/.config/git-merge-flow-config.jsonc b/.config/git-merge-flow-config.jsonc index 13efaeba878..1733ebd1d16 100644 --- a/.config/git-merge-flow-config.jsonc +++ b/.config/git-merge-flow-config.jsonc @@ -30,8 +30,12 @@ "vs18.0": { "MergeToBranch": "vs18.3" }, - // Automate opening PRs to merge msbuild's vs18.3 (SDK 10.0.2xx) into main + // Automate opening PRs to merge msbuild's vs18.3 (SDK 10.0.2xx) into vs18.4 (VS) "vs18.3": { + "MergeToBranch": "vs18.4" + }, + // Automate opening PRs to merge msbuild's vs18.4 (VS) into main (VS canary, SDK main & next-feature-band) + "vs18.4": { "MergeToBranch": "main" } } diff --git a/.github/skills/multithreaded-task-migration/SKILL.md b/.github/skills/multithreaded-task-migration/SKILL.md new file mode 100644 index 00000000000..3f97e8adf8f --- /dev/null +++ b/.github/skills/multithreaded-task-migration/SKILL.md @@ -0,0 +1,244 @@ +--- +name: multithreaded-task-migration +description: Guide for migrating MSBuild tasks to the multithreaded mode support. Use this when asked to convert tasks to thread-safe versions, implement IMultiThreadableTask, or add TaskEnvironment support to tasks. +--- + +# Migrating MSBuild Tasks to Multithreaded API + +This skill guides you through migrating MSBuild tasks to support multithreaded execution by implementing `IMultiThreadableTask` and using `TaskEnvironment`. + +## Overview + +MSBuild's multithreaded execution model requires tasks to avoid global process state (working directory, environment variables). Thread-safe tasks declare this capability by annotating with `MSBuildMultiThreadableTask` and use `TaskEnvironment` provided by `IMultiThreadableTask` for safe alternatives. + +## Migration Steps + +### Step 1: Update Task Class Declaration + +a. add the attribute +b. AND implement the interface if it's necessary to use TaskEnvironment APIs. + +```csharp +[MSBuildMultiThreadableTask] +public class MyTask : Task, IMultiThreadableTask +{ + public TaskEnvironment TaskEnvironment { get; set; } + ... +} +``` + +### Step 2: Absolutize Paths Before File Operations + +**Critical**: All path strings must be absolutized with `TaskEnvironment.GetAbsolutePath()` before use in file system APIs. This ensures paths resolve relative to the project directory, not the process working directory. + +```csharp +// BEFORE - File.Exists uses process working directory for relative paths (UNSAFE) +if (File.Exists(inputPath)) +{ + string content = File.ReadAllText(inputPath); +} + +// AFTER - Absolutize first, then use in file operations (SAFE) +AbsolutePath absolutePath = TaskEnvironment.GetAbsolutePath(inputPath); +if (File.Exists(absolutePath)) +{ + string content = File.ReadAllText(absolutePath); +} +``` + +`GetAbsolutePath()` throws for null/empty inputs. See [Exception Handling in Batch Operations](#exception-handling-in-batch-operations) for handling strategies. + +The [`AbsolutePath`](https://github.com/dotnet/msbuild/blob/main/src/Framework/PathHelpers/AbsolutePath.cs) struct: +- Has `Value` property returning the absolute path string +- Has `OriginalValue` property preserving the input path +- Is implicitly convertible to `string` for File/Directory API compatibility + +**CAUTION**: `FileInfo` can be created from relative paths - only use `FileInfo.FullName` if constructed with an absolute path. + +#### Note: +If code previously used `Path.GetFullPath()` for canonicalization (resolving `..` segments, normalizing separators), call `AbsolutePath.GetCanonicalForm()` after absolutization to preserve that behavior. Do not simply replace `Path.GetFullPath` with `GetAbsolutePath` if canonicalization was the intent. You can replace `Path.GetFullPath` behavior by combining both: + +```csharp +AbsolutePath absolutePath = TaskEnvironment.GetAbsolutePath(inputPath).GetCanonicalForm(); +``` +The goal is MAXIMUM compatibility so think about these edge cases so it behaves the same as before. + +### Step 3: Replace Environment Variable APIs + +```csharp +// BEFORE (UNSAFE) +string value = Environment.GetEnvironmentVariable("VAR"); +Environment.SetEnvironmentVariable("VAR", "value"); + +// AFTER (SAFE) +string value = TaskEnvironment.GetEnvironmentVariable("VAR"); +TaskEnvironment.SetEnvironmentVariable("VAR", "value"); +``` + +### Step 4: Replace Process Start APIs + +```csharp +// BEFORE (UNSAFE - inherits process state) +var psi = new ProcessStartInfo("tool.exe"); + +// AFTER (SAFE - uses task's isolated environment) +var psi = TaskEnvironment.GetProcessStartInfo(); +psi.FileName = "tool.exe"; +``` + +## Updating Unit Tests + +**Every test creating a task instance must set TaskEnvironment.** Use `TaskEnvironmentHelper.CreateForTest()`: + +```csharp +// BEFORE +var task = new Copy +{ + BuildEngine = new MockEngine(true), + SourceFiles = sourceFiles, + DestinationFolder = new TaskItem(destFolder), +}; + +// AFTER +var task = new Copy +{ + TaskEnvironment = TaskEnvironmentHelper.CreateForTest(), + BuildEngine = new MockEngine(true), + SourceFiles = sourceFiles, + DestinationFolder = new TaskItem(destFolder), +}; +``` + +### Testing Exception Cases + +Tasks must handle null/empty path inputs properly. + +```csharp +[Fact] +public void Task_WithNullPath_Throws() +{ + var task = CreateTask(); + + Should.Throw(() => task.ProcessPath(null!)); +} +``` + +## APIs to Avoid + +### Critical Errors (No Alternative) +- `Environment.Exit()`, `Environment.FailFast()` - Return false or throw instead +- `Process.GetCurrentProcess().Kill()` - Never terminate process +- `ThreadPool.SetMinThreads/MaxThreads` - Process-wide settings +- `CultureInfo.DefaultThreadCurrentCulture` (setter) - Affects all threads +- `Console.*` - Interferes with logging + +### Requires TaskEnvironment +- `Environment.CurrentDirectory` → `TaskEnvironment.ProjectDirectory` +- `Environment.GetEnvironmentVariable` → `TaskEnvironment.GetEnvironmentVariable` +- `Environment.SetEnvironmentVariable` → `TaskEnvironment.SetEnvironmentVariable` +- `Path.GetFullPath` → `TaskEnvironment.GetAbsolutePath` +- `Process.Start`, `ProcessStartInfo` → `TaskEnvironment.GetProcessStartInfo` + +### File APIs Need Absolute Paths +- `File.*`, `Directory.*`, `FileInfo`, `DirectoryInfo`, `FileStream`, `StreamReader`, `StreamWriter` +- All path parameters must be absolute + +### Potential Issues (Review Required) +- `Assembly.Load*`, `LoadFrom`, `LoadFile` - Version conflicts +- `Activator.CreateInstance*` - Version conflicts + +## Practical Notes + +### CRITICAL: Trace All Path String Usage + +**You MUST trace every path string variable through the entire codebase** to find all places where it flows into file system operations - including helper methods, utility classes, and third-party code that may internally use File APIs. + +Steps: +1. Find every path string (e.g., `item.ItemSpec`, function parameters) +2. **Trace downstream**: Follow the variable through all method calls and assignments +3. Absolutize BEFORE any code path that touches the file system +4. Use `OriginalValue` for user-facing output (logs, errors) + +```csharp +// WRONG - LockCheck internally uses File APIs with non-absolutized path +string sourceSpec = item.ItemSpec; // sourceSpec is string +string lockedMsg = LockCheck.GetLockedFileMessage(sourceSpec); // BUG! Trace the call! + +// CORRECT - absolutized path passed to helper +AbsolutePath sourceFile = TaskEnvironment.GetAbsolutePath(item.ItemSpec); +string lockedMsg = LockCheck.GetLockedFileMessage(sourceFile); + +// For error messages, preserve original user input +Log.LogError("...", sourceFile.OriginalValue, ...); +``` + +### Exception Handling in Batch Operations + +**Important**: `GetAbsolutePath()` throws on null/empty inputs. In batch processing scenarios (e.g., iterating over multiple files), an unhandled exception will abort the entire batch. Tasks must catch and handle these exceptions appropriately to avoid cutting short processing of valid items: + +```csharp +// WRONG - one bad path aborts entire batch +foreach (ITaskItem item in SourceFiles) +{ + AbsolutePath path = TaskEnvironment.GetAbsolutePath(item.ItemSpec); // throws, batch stops! + ProcessFile(path); +} + +// CORRECT - handle exceptions, continue processing valid items +bool success = true; +foreach (ITaskItem item in SourceFiles) +{ + try + { + AbsolutePath path = TaskEnvironment.GetAbsolutePath(item.ItemSpec); + ProcessFile(path); + } + catch (ArgumentException ex) + { + Log.LogError($"Invalid path '{item.ItemSpec}': {ex.Message}"); + success = false; + // Continue processing remaining items + } +} +return success; +``` + +Consider the task's error semantics: should one invalid path fail the entire task immediately, or should all items be processed with errors collected? Match the original task's behavior. + +### Prefer AbsolutePath Over String + +When working with paths, stay in the `AbsolutePath` world as much as possible rather than converting back and forth to `string`. This reduces unnecessary conversions and maintains type safety: + +```csharp +// AVOID - unnecessary conversions +string path = TaskEnvironment.GetAbsolutePath(input).Value; +AbsolutePath again = TaskEnvironment.GetAbsolutePath(path); // redundant! + +// PREFER - stay in AbsolutePath +AbsolutePath path = TaskEnvironment.GetAbsolutePath(input); +// Use path directly - it's implicitly convertible to string where needed +File.ReadAllText(path); +``` + +### TaskEnvironment is Not Thread-Safe + +If your task spawns multiple threads internally, you must synchronize access to `TaskEnvironment`. However, each task instance gets its own environment, so no synchronization with other tasks is needed. + +## Checklist + +- [ ] Task is annotated with `MSBuildMultiThreadableTask` attribute and implements `IMultiThreadableTask` if TaskEnvironment APIs are required +- [ ] All environment variable access uses `TaskEnvironment` APIs +- [ ] All process spawning uses `TaskEnvironment.GetProcessStartInfo()` +- [ ] All file system APIs receive absolute paths +- [ ] All helper methods receiving path strings are traced to verify they don't internally use File APIs with non-absolutized paths +- [ ] No use of `Environment.CurrentDirectory` +- [ ] All tests set `TaskEnvironment = TaskEnvironmentHelper.CreateForTest()` +- [ ] Tests verify exception behavior for null/empty paths +- [ ] No use of forbidden APIs (Environment.Exit, etc.) + +## References + +- [Thread-Safe Tasks Spec](https://github.com/dotnet/msbuild/blob/main/documentation/specs/multithreading/thread-safe-tasks.md) - Full specification for multithreaded task support +- [`AbsolutePath`](https://github.com/dotnet/msbuild/blob/main/src/Framework/PathHelpers/AbsolutePath.cs) - Struct for representing absolute paths +- [`TaskEnvironment`](https://github.com/dotnet/msbuild/blob/main/src/Framework/TaskEnvironment.cs) - Thread-safe environment APIs for tasks +- [`IMultiThreadableTask`](https://github.com/dotnet/msbuild/blob/main/src/Framework/IMultiThreadableTask.cs) - Interface for multithreaded task support diff --git a/MSBuild.Minimal.slnf b/MSBuild.Minimal.slnf new file mode 100644 index 00000000000..39c6ed08343 --- /dev/null +++ b/MSBuild.Minimal.slnf @@ -0,0 +1,14 @@ +{ + "solution": { + "path": "MSBuild.sln", + "projects": [ + "src\\Build\\Microsoft.Build.csproj", + "src\\Framework\\Microsoft.Build.Framework.csproj", + "src\\MSBuild\\MSBuild.csproj", + "src\\StringTools\\StringTools.csproj", + "src\\Tasks\\Microsoft.Build.Tasks.csproj", + "src\\Utilities\\Microsoft.Build.Utilities.csproj", + "src\\MSBuild.Bootstrap\\MSBuild.Bootstrap.csproj" + ] + } +} diff --git a/azure-pipelines/vs-insertion-experimental.yml b/azure-pipelines/vs-insertion-experimental.yml index 5b05db87d14..c28ba2e8035 100644 --- a/azure-pipelines/vs-insertion-experimental.yml +++ b/azure-pipelines/vs-insertion-experimental.yml @@ -24,6 +24,7 @@ parameters: displayName: 'Insertion Target Branch (select for manual insertion)' values: - main + - rel/d18.5 - rel/d18.4 - rel/d18.3 - rel/d18.0 diff --git a/azure-pipelines/vs-insertion.yml b/azure-pipelines/vs-insertion.yml index 76f7ff3a5d1..e60fae499b9 100644 --- a/azure-pipelines/vs-insertion.yml +++ b/azure-pipelines/vs-insertion.yml @@ -44,6 +44,7 @@ parameters: values: - auto - main + - rel/d18.5 - rel/d18.4 - rel/d18.3 - rel/d18.0 @@ -68,7 +69,9 @@ parameters: variables: # `auto` should work every time and selecting a branch in parameters is likely to fail due to incompatible versions in MSBuild and VS - name: AutoInsertTargetBranch - ${{ if eq(variables['Build.SourceBranchName'], 'vs18.4') }}: + ${{ if eq(variables['Build.SourceBranchName'], 'vs18.5') }}: + value: 'rel/d18.5' + ${{ elseif eq(variables['Build.SourceBranchName'], 'vs18.4') }}: value: 'rel/d18.4' ${{ elseif eq(variables['Build.SourceBranchName'], 'vs18.3') }}: value: 'rel/d18.3' diff --git a/benchmark-build.cmd b/benchmark-build.cmd new file mode 100644 index 00000000000..8ab38ebc28e --- /dev/null +++ b/benchmark-build.cmd @@ -0,0 +1,151 @@ +@echo off +setlocal EnableDelayedExpansion + +REM ============================================================================ +REM benchmark-build.cmd - Benchmark script for comparing build times +REM +REM Runs various build scenarios and reports timing for each. +REM Use this to objectively measure build performance improvements. +REM ============================================================================ + +set "RepoRoot=%~dp0" +set "ResultsFile=%RepoRoot%build-benchmark-results.txt" + +echo. +echo ============================================================================ +echo MSBuild Build Performance Benchmark +echo ============================================================================ +echo. +echo This script will run multiple build scenarios and measure their times. +echo Results will be saved to: %ResultsFile% +echo. +echo Press Ctrl+C to cancel, or any key to continue... +pause > nul +echo. + +REM Initialize results file +echo MSBuild Build Performance Benchmark > "%ResultsFile%" +echo ================================== >> "%ResultsFile%" +echo. >> "%ResultsFile%" +echo Date: %date% %time% >> "%ResultsFile%" +echo Machine: %COMPUTERNAME% >> "%ResultsFile%" +echo. >> "%ResultsFile%" + +REM Clean first +echo [1/8] Cleaning build artifacts... +call "%RepoRoot%build.cmd" -clean > nul 2>&1 + +REM ============================================================================ +REM Benchmark 1: Full build (cold) +REM ============================================================================ +echo [2/8] Running: Full build (cold)... +set "StartTime=%time%" +powershell -NoLogo -NoProfile -ExecutionPolicy ByPass -Command "& """%RepoRoot%eng\common\build.ps1""" -restore -build -configuration Debug -v quiet" > nul 2>&1 +set "EndTime=%time%" +call :CalcDuration "%StartTime%" "%EndTime%" Duration +echo Full build (cold): %Duration% +echo Full build (cold): %Duration% >> "%ResultsFile%" + +REM ============================================================================ +REM Benchmark 2: Full build (incremental - no changes) +REM ============================================================================ +echo [3/8] Running: Full build (incremental)... +set "StartTime=%time%" +powershell -NoLogo -NoProfile -ExecutionPolicy ByPass -Command "& """%RepoRoot%eng\common\build.ps1""" -restore -build -configuration Debug -v quiet" > nul 2>&1 +set "EndTime=%time%" +call :CalcDuration "%StartTime%" "%EndTime%" Duration +echo Full build (incremental): %Duration% +echo Full build (incremental): %Duration% >> "%ResultsFile%" + +REM Clean for minimal builds +echo [4/8] Cleaning for minimal build tests... +call "%RepoRoot%build.cmd" -clean > nul 2>&1 + +REM ============================================================================ +REM Benchmark 3: Minimal build with bootstrap (cold) +REM ============================================================================ +echo [5/8] Running: Minimal build with bootstrap (cold)... +set "StartTime=%time%" +powershell -NoLogo -NoProfile -ExecutionPolicy ByPass -Command "& """%RepoRoot%eng\common\build.ps1""" -restore -build -configuration Debug -v quiet /p:CreateBootstrap=true /p:Projects=%RepoRoot%MSBuild.Minimal.slnf" > nul 2>&1 +set "EndTime=%time%" +call :CalcDuration "%StartTime%" "%EndTime%" Duration +echo Minimal build with bootstrap (cold): %Duration% +echo Minimal build with bootstrap (cold): %Duration% >> "%ResultsFile%" + +REM ============================================================================ +REM Benchmark 4: Minimal build with bootstrap (incremental) +REM ============================================================================ +echo [6/8] Running: Minimal build with bootstrap (incremental)... +set "StartTime=%time%" +powershell -NoLogo -NoProfile -ExecutionPolicy ByPass -Command "& """%RepoRoot%eng\common\build.ps1""" -restore -build -configuration Debug -v quiet /p:CreateBootstrap=true /p:Projects=%RepoRoot%MSBuild.Minimal.slnf" > nul 2>&1 +set "EndTime=%time%" +call :CalcDuration "%StartTime%" "%EndTime%" Duration +echo Minimal build with bootstrap (incremental): %Duration% +echo Minimal build with bootstrap (incremental): %Duration% >> "%ResultsFile%" + +REM ============================================================================ +REM Benchmark 5: Minimal build without bootstrap (incremental) +REM ============================================================================ +echo [7/8] Running: Minimal build without bootstrap (incremental)... +set "StartTime=%time%" +powershell -NoLogo -NoProfile -ExecutionPolicy ByPass -Command "& """%RepoRoot%eng\common\build.ps1""" -restore -build -configuration Debug -v quiet /p:CreateBootstrap=false /p:Projects=%RepoRoot%MSBuild.Minimal.slnf" > nul 2>&1 +set "EndTime=%time%" +call :CalcDuration "%StartTime%" "%EndTime%" Duration +echo Minimal build without bootstrap (incremental): %Duration% +echo Minimal build without bootstrap (incremental): %Duration% >> "%ResultsFile%" + +REM ============================================================================ +REM Benchmark 6: Minimal build .NET Core only (incremental) +REM ============================================================================ +echo [8/8] Running: Minimal build .NET Core only (incremental)... +set "StartTime=%time%" +powershell -NoLogo -NoProfile -ExecutionPolicy ByPass -Command "& """%RepoRoot%eng\common\build.ps1""" -restore -build -configuration Debug -v quiet /p:CreateBootstrap=false /p:Projects=%RepoRoot%MSBuild.Minimal.slnf /p:TargetFrameworks=net10.0" > nul 2>&1 +set "EndTime=%time%" +call :CalcDuration "%StartTime%" "%EndTime%" Duration +echo Minimal build .NET Core only (incremental): %Duration% +echo Minimal build .NET Core only (incremental): %Duration% >> "%ResultsFile%" + +echo. >> "%ResultsFile%" +echo ============================================================================ >> "%ResultsFile%" + +echo. +echo ============================================================================ +echo Benchmark Complete! +echo ============================================================================ +echo. +echo Results saved to: %ResultsFile% +echo. +type "%ResultsFile%" +echo. + +exit /b 0 + +:CalcDuration +REM Calculate duration between two times +REM Usage: call :CalcDuration "StartTime" "EndTime" ResultVar +setlocal EnableDelayedExpansion + +set "Start=%~1" +set "End=%~2" + +REM Parse start time +for /f "tokens=1-4 delims=:., " %%a in ("%Start%") do ( + set /a "StartSec=(((%%a*60)+%%b)*60+%%c)" +) + +REM Parse end time +for /f "tokens=1-4 delims=:., " %%a in ("%End%") do ( + set /a "EndSec=(((%%a*60)+%%b)*60+%%c)" +) + +set /a "DiffSec=EndSec-StartSec" +if !DiffSec! lss 0 set /a "DiffSec+=86400" + +set /a "Minutes=DiffSec/60" +set /a "Seconds=DiffSec%%60" + +if !Minutes! lss 10 set "Minutes=0!Minutes!" +if !Seconds! lss 10 set "Seconds=0!Seconds!" + +endlocal & set "%~3=%Minutes%:%Seconds%" +goto :eof diff --git a/build-minimal.cmd b/build-minimal.cmd new file mode 100644 index 00000000000..c61d90848f2 --- /dev/null +++ b/build-minimal.cmd @@ -0,0 +1,258 @@ +@echo off +setlocal EnableDelayedExpansion + +REM ============================================================================ +REM build-minimal.cmd - Fast build script for minimal MSBuild assemblies only +REM +REM This script builds only the essential MSBuild runtime without tests, samples, +REM or package projects. It is significantly faster than a full build. +REM +REM Usage: +REM build-minimal.cmd - Build with bootstrap (default) +REM build-minimal.cmd -nobootstrap - Build without bootstrap (fastest) +REM build-minimal.cmd -release - Build release configuration +REM build-minimal.cmd -rebuild - Force rebuild +REM build-minimal.cmd -help - Show help +REM ============================================================================ + +set "RepoRoot=%~dp0" +set "Configuration=Debug" +set "CreateBootstrap=true" +set "Rebuild=" +set "Build=true" +set "Verbosity=minimal" +set "ExtraArgs=" +set "SingleTFM=" + +:parse_args +if "%~1"=="" goto :end_parse +if /i "%~1"=="-help" goto :show_help +if /i "%~1"=="/help" goto :show_help +if /i "%~1"=="/?" goto :show_help +if /i "%~1"=="--help" goto :show_help +if /i "%~1"=="-nobootstrap" ( + set "CreateBootstrap=false" + shift + goto :parse_args +) +if /i "%~1"=="--nobootstrap" ( + set "CreateBootstrap=false" + shift + goto :parse_args +) +if /i "%~1"=="-release" ( + set "Configuration=Release" + shift + goto :parse_args +) +if /i "%~1"=="--release" ( + set "Configuration=Release" + shift + goto :parse_args +) +if /i "%~1"=="-debug" ( + set "Configuration=Debug" + shift + goto :parse_args +) +if /i "%~1"=="--debug" ( + set "Configuration=Debug" + shift + goto :parse_args +) +if /i "%~1"=="-rebuild" ( + set "Rebuild=-rebuild" + shift + goto :parse_args +) +if /i "%~1"=="--rebuild" ( + set "Rebuild=-rebuild" + shift + goto :parse_args +) +if /i "%~1"=="-core" ( + set "SingleTFM=net10.0" + shift + goto :parse_args +) +if /i "%~1"=="--core" ( + set "SingleTFM=net10.0" + shift + goto :parse_args +) +if /i "%~1"=="-netfx" ( + set "SingleTFM=net472" + shift + goto :parse_args +) +if /i "%~1"=="--netfx" ( + set "SingleTFM=net472" + shift + goto :parse_args +) +if /i "%~1"=="-v" ( + set "Verbosity=%~2" + shift + shift + goto :parse_args +) +if /i "%~1"=="--verbosity" ( + set "Verbosity=%~2" + shift + shift + goto :parse_args +) + +REM Pass through any other arguments +set "ExtraArgs=%ExtraArgs% %~1" +shift +goto :parse_args + +:end_parse + +REM If single TFM requested, use direct MSBuild.exe build (faster) +if defined SingleTFM goto :tfm_build + +REM Build arguments for Arcade build +set "BuildArgs=-restore -configuration %Configuration% -v %Verbosity%" +if defined Build set "BuildArgs=%BuildArgs% -build" +if defined Rebuild set "BuildArgs=%BuildArgs% %Rebuild%" +set "BuildArgs=%BuildArgs% /p:CreateBootstrap=%CreateBootstrap%" + +REM Use solution filter for minimal projects only +set "BuildArgs=%BuildArgs% /p:Projects=%RepoRoot%MSBuild.Minimal.slnf" + +REM Disable IBC optimization for minimal builds (requires VSSetup which we don't build) +set "BuildArgs=%BuildArgs% /p:UsingToolIbcOptimization=false /p:UsingToolVisualStudioIbcTraining=false" + +echo. +echo ============================================================ +echo MSBuild Minimal Build +echo ============================================================ +echo Configuration: %Configuration% +echo Create Bootstrap: %CreateBootstrap% +echo Verbosity: %Verbosity% +echo ============================================================ +echo. + +REM Run the build using PowerShell +powershell -NoLogo -NoProfile -ExecutionPolicy ByPass -Command "& """%RepoRoot%eng\common\build.ps1""" %BuildArgs% %ExtraArgs%" +set "ExitCode=%ERRORLEVEL%" +goto :build_done + +:tfm_build +REM Single TFM build using Visual Studio MSBuild.exe directly +echo. +echo ============================================================ +echo MSBuild Minimal Build - Single TFM +echo ============================================================ +echo Configuration: %Configuration% +echo Target Framework: %SingleTFM% +echo Verbosity: %Verbosity% +echo ============================================================ +echo. + +REM Check if restore has been done +if not exist "%RepoRoot%artifacts\obj\Microsoft.Build" ( + echo ERROR: No prior restore found. Please run one of these first: + echo - build-minimal.cmd ^(builds and restores all TFMs^) + echo - build.cmd -restore ^(restores only^) + echo. + echo Then run with -core or -netfx for fast single-TFM builds. + exit /b 1 +) + +REM Find MSBuild.exe from Visual Studio +set "MSBuildExe=" +for /f "delims=" %%i in ('where /r "C:\Program Files\Microsoft Visual Studio" MSBuild.exe 2^>nul ^| findstr /i "Current\\Bin\\MSBuild.exe"') do ( + if not defined MSBuildExe set "MSBuildExe=%%i" +) +if not defined MSBuildExe ( + echo ERROR: Visual Studio MSBuild.exe not found. -core and -netfx require Visual Studio. + exit /b 1 +) + +REM Map verbosity +set "MSBuildVerbosity=minimal" +if /i "%Verbosity%"=="q" set "MSBuildVerbosity=quiet" +if /i "%Verbosity%"=="quiet" set "MSBuildVerbosity=quiet" +if /i "%Verbosity%"=="m" set "MSBuildVerbosity=minimal" +if /i "%Verbosity%"=="n" set "MSBuildVerbosity=normal" +if /i "%Verbosity%"=="d" set "MSBuildVerbosity=detailed" + +REM Build MSBuild.csproj with single TFM +set "MSBuildArgs=%RepoRoot%src\MSBuild\MSBuild.csproj /p:Configuration=%Configuration%" +set "MSBuildArgs=%MSBuildArgs% /p:TargetFramework=%SingleTFM%" +set "MSBuildArgs=%MSBuildArgs% /restore:false /v:%MSBuildVerbosity%" +if defined Rebuild set "MSBuildArgs=%MSBuildArgs% /t:Rebuild" + +"%MSBuildExe%" %MSBuildArgs% +set "ExitCode=%ERRORLEVEL%" + +REM Also build Bootstrap +if %ExitCode%==0 ( + set "BootstrapArgs=%RepoRoot%src\MSBuild.Bootstrap\MSBuild.Bootstrap.csproj /p:Configuration=%Configuration%" + set "BootstrapArgs=!BootstrapArgs! /p:TargetFramework=%SingleTFM%" + set "BootstrapArgs=!BootstrapArgs! /restore:false /v:%MSBuildVerbosity%" + if defined Rebuild set "BootstrapArgs=!BootstrapArgs! /t:Rebuild" + + "%MSBuildExe%" !BootstrapArgs! + set "ExitCode=!ERRORLEVEL!" +) + +:build_done + +if %ExitCode%==0 ( + echo. + echo ============================================================ + echo Build succeeded! + if defined SingleTFM ( + echo. + if "%SingleTFM%"=="net10.0" ( + echo Bootstrap: artifacts\bin\bootstrap\core\dotnet.exe build + ) else ( + echo Bootstrap: artifacts\bin\bootstrap\net472\MSBuild\Current\Bin\MSBuild.exe + ) + ) else if "%CreateBootstrap%"=="true" ( + echo. + echo To use the bootstrapped MSBuild, run: + echo artifacts\msbuild-build-env.bat + echo. + echo Then use 'dotnet build' with your locally-built MSBuild. + ) + echo ============================================================ +) else ( + echo. + echo Build failed with exit code %ExitCode%. Check errors above. +) + +exit /b %ExitCode% + +:show_help +echo. +echo MSBuild Minimal Build Script - Fast build for development +echo. +echo Usage: build-minimal.cmd [options] +echo. +echo Options: +echo -nobootstrap Skip creating the bootstrap folder (fastest builds) +echo -core Build only .NET Core (net10.0) - requires prior restore +echo -netfx Build only .NET Framework (net472) - requires prior restore +echo -release Build in Release configuration (default: Debug) +echo -debug Build in Debug configuration +echo -rebuild Force a rebuild (clean + build) +echo -v ^ Verbosity: q[uiet], m[inimal], n[ormal], d[etailed] +echo -help Show this help +echo. +echo Examples: +echo build-minimal.cmd Minimal build with bootstrap +echo build-minimal.cmd -nobootstrap Fast incremental build (no bootstrap) +echo build-minimal.cmd -core Fast .NET Core only build +echo build-minimal.cmd -netfx Fast .NET Framework only build +echo build-minimal.cmd -release Release build +echo. +echo Note: -core and -netfx require a prior restore (run build-minimal.cmd once first) +echo. +echo For full builds including tests, use: build.cmd +echo. +exit /b 0 diff --git a/build-minimal.sh b/build-minimal.sh new file mode 100644 index 00000000000..842740873cd --- /dev/null +++ b/build-minimal.sh @@ -0,0 +1,134 @@ +#!/usr/bin/env bash + +# ============================================================================ +# build-minimal.sh - Fast build script for minimal MSBuild assemblies only +# +# This script builds only the essential MSBuild runtime without tests, samples, +# or package projects. It is significantly faster than a full build. +# +# Usage: +# ./build-minimal.sh - Build with bootstrap (default) +# ./build-minimal.sh --nobootstrap - Build without bootstrap (fastest) +# ./build-minimal.sh --release - Build release configuration +# ./build-minimal.sh --rebuild - Force rebuild +# ./build-minimal.sh --help - Show help +# ============================================================================ + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$SCRIPT_DIR" + +# Default values +CONFIGURATION="Debug" +CREATE_BOOTSTRAP="true" +REBUILD="" +BUILD="true" +VERBOSITY="minimal" +EXTRA_ARGS="" + +show_help() { + echo "" + echo "MSBuild Minimal Build Script - Fast build for development" + echo "" + echo "Usage: ./build-minimal.sh [options]" + echo "" + echo "Options:" + echo " --nobootstrap Skip creating the bootstrap folder (fastest builds)" + echo " --release Build in Release configuration (default: Debug)" + echo " --debug Build in Debug configuration" + echo " --rebuild Force a rebuild (clean + build)" + echo " -v Verbosity: q[uiet], m[inimal], n[ormal], d[etailed]" + echo " --help Show this help" + echo "" + echo "Examples:" + echo " ./build-minimal.sh Minimal build with bootstrap" + echo " ./build-minimal.sh --nobootstrap Fast incremental build" + echo " ./build-minimal.sh --release Release build" + echo "" + echo "For full builds including tests, use: ./build.sh" + echo "" + exit 0 +} + +# Parse arguments +while [[ $# -gt 0 ]]; do + case "$1" in + --help|-h) + show_help + ;; + --nobootstrap|-nobootstrap) + CREATE_BOOTSTRAP="false" + shift + ;; + --release|-release) + CONFIGURATION="Release" + shift + ;; + --debug|-debug) + CONFIGURATION="Debug" + shift + ;; + --rebuild|-rebuild) + REBUILD="--rebuild" + shift + ;; + -v|--verbosity) + VERBOSITY="$2" + shift 2 + ;; + *) + EXTRA_ARGS="$EXTRA_ARGS $1" + shift + ;; + esac +done + +# Build arguments +BUILD_ARGS="--restore --configuration $CONFIGURATION -v $VERBOSITY" +if [[ -n "$BUILD" ]]; then + BUILD_ARGS="$BUILD_ARGS --build" +fi +if [[ -n "$REBUILD" ]]; then + BUILD_ARGS="$BUILD_ARGS $REBUILD" +fi +BUILD_ARGS="$BUILD_ARGS /p:CreateBootstrap=$CREATE_BOOTSTRAP" + +# Use solution filter for minimal projects only +BUILD_ARGS="$BUILD_ARGS /p:Projects=$REPO_ROOT/MSBuild.Minimal.slnf" + +# Disable IBC optimization for minimal builds (requires VSSetup which we don't build) +BUILD_ARGS="$BUILD_ARGS /p:UsingToolIbcOptimization=false /p:UsingToolVisualStudioIbcTraining=false" + +echo "" +echo "============================================================" +echo " MSBuild Minimal Build" +echo "============================================================" +echo " Configuration: $CONFIGURATION" +echo " Create Bootstrap: $CREATE_BOOTSTRAP" +echo " Verbosity: $VERBOSITY" +echo "============================================================" +echo "" + +# Run the build +"$REPO_ROOT/eng/common/build.sh" $BUILD_ARGS $EXTRA_ARGS +EXIT_CODE=$? + +if [[ $EXIT_CODE -eq 0 ]]; then + echo "" + echo "============================================================" + echo " Build succeeded!" + if [[ "$CREATE_BOOTSTRAP" == "true" ]]; then + echo "" + echo " To use the bootstrapped MSBuild, run:" + echo " source artifacts/sdk-build-env.sh" + echo "" + echo " Then use 'dotnet build' with your locally-built MSBuild." + fi + echo "============================================================" +else + echo "" + echo "Build failed with exit code $EXIT_CODE. Check errors above." +fi + +exit $EXIT_CODE diff --git a/documentation/wiki/Fast-Builds.md b/documentation/wiki/Fast-Builds.md new file mode 100644 index 00000000000..409b5ebfc61 --- /dev/null +++ b/documentation/wiki/Fast-Builds.md @@ -0,0 +1,136 @@ +# Fast Builds for MSBuild Development + +This document describes optimized build workflows for faster development iteration. + +## Quick Start + +For the fastest possible development workflow: + +```bash +# First time: Minimal build with bootstrap (creates usable MSBuild) +.\build-minimal.cmd + +# After that: Fast incremental rebuilds (no bootstrap, ~10 seconds) +.\build-minimal.cmd -nobootstrap +``` + +## Build Scripts + +### `build-minimal.cmd` / `build-minimal.sh` + +A specialized script that builds only the minimal MSBuild assemblies without tests, samples, or package projects. This is significantly faster than a full build. + +| Scenario | Approximate Time | +|----------|------------------| +| Full build (`build.cmd`) | 2-3 minutes | +| Minimal build with bootstrap (cold) | ~1 minute | +| Minimal build with bootstrap (incremental) | ~15 seconds | +| Minimal build without bootstrap (incremental) | ~10 seconds | + +#### Options + +``` +.\build-minimal.cmd [options] + +Options: + -nobootstrap Skip creating the bootstrap folder (fastest builds) + -release Build in Release configuration (default: Debug) + -debug Build in Debug configuration + -rebuild Force a rebuild (clean + build) + -v Verbosity: q[uiet], m[inimal], n[ormal], d[etailed] +``` + +#### Examples + +```bash +# Standard minimal build with bootstrap +.\build-minimal.cmd + +# Fast incremental build (when bootstrap already exists) +.\build-minimal.cmd -nobootstrap + +# Release build +.\build-minimal.cmd -release + +# Force clean rebuild +.\build-minimal.cmd -rebuild +``` + +### Unix/macOS + +```bash +./build-minimal.sh --nobootstrap +./build-minimal.sh --release +``` + +## Solution Filters + +The repository includes solution filters for different development scenarios: + +| Filter | Description | +|--------|-------------| +| `MSBuild.Minimal.slnf` | Minimal runtime projects only (no tests, no samples) | +| `MSBuild.Dev.slnf` | Core + test projects | +| `MSBuild.sln` | Full solution | + +For day-to-day development, open `MSBuild.Minimal.slnf` or `MSBuild.Dev.slnf` in Visual Studio for faster IDE operations. + +## Benchmarking + +Use `benchmark-build.cmd` to objectively measure build times on your machine: + +```bash +.\benchmark-build.cmd +``` + +This runs multiple build scenarios and reports timing for each, saving results to `build-benchmark-results.txt`. + +## Optimizing Bootstrap + +The bootstrap process creates a usable copy of MSBuild from your local build. Tips: + +1. **Skip when not needed**: Use `-nobootstrap` when you're just iterating on code and don't need to run the built MSBuild. + +2. **Bootstrap is incremental**: The bootstrap now uses `SkipUnchangedFiles="true"`, so subsequent builds with bootstrap are much faster. + +3. **The SDK is cached**: The .NET SDK for bootstrap is downloaded once and reused. You won't re-download it on every build. + +## Best Practices for Fast Iteration + +1. **Initial Setup**: Run `.\build-minimal.cmd` once to create the bootstrap environment. + +2. **Iterating on Code**: Use `.\build-minimal.cmd -nobootstrap` for fast rebuilds. + +3. **Need to Test Changes**: Run `.\build-minimal.cmd` (with bootstrap) to update your local MSBuild. + +4. **Running Tests**: After building minimal, you can run individual test projects: + ```bash + .\artifacts\msbuild-build-env.bat + dotnet test src\Build.UnitTests\Microsoft.Build.Engine.UnitTests.csproj --filter "FullyQualifiedName~YourTest" + ``` + +5. **Visual Studio**: Use `MSBuild.Minimal.slnf` for faster solution load and build times within VS. + +## What's in the Minimal Build? + +The `MSBuild.Minimal.slnf` includes only the essential runtime assemblies: + +- `Microsoft.Build.Framework` - Core interfaces and types +- `Microsoft.Build` - Main build engine +- `Microsoft.Build.Tasks` - Built-in tasks +- `Microsoft.Build.Utilities` - Utilities for task authors +- `StringTools` - String handling utilities +- `MSBuild.exe` - Command-line entry point +- `MSBuild.Bootstrap` - Bootstrap environment setup + +## Comparison with Full Build + +| What's Built | `build.cmd` | `build-minimal.cmd` | +|--------------|-------------|---------------------| +| Core assemblies | ✓ | ✓ | +| Test projects | ✓ | ✗ | +| Sample projects | ✓ | ✗ | +| Package projects | ✓ | ✗ | +| Bootstrap | ✓ | ✓ (optional) | + +Use `build-minimal.cmd` during development, and `build.cmd -test` before submitting PRs. diff --git a/eng/BootStrapMsBuild.targets b/eng/BootStrapMsBuild.targets index b52010c3ca4..539c0a13822 100644 --- a/eng/BootStrapMsBuild.targets +++ b/eng/BootStrapMsBuild.targets @@ -256,7 +256,8 @@ SkipUnchangedFiles="true" /> + DestinationFiles="@(FreshlyBuiltNetBinaries->'$(InstallDir)sdk\$(BootstrapSdkVersion)\%(RecursiveDir)%(Filename)%(Extension)')" + SkipUnchangedFiles="true" /> diff --git a/eng/Versions.props b/eng/Versions.props index b13f1b3c952..48a73f73a64 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -3,7 +3,7 @@ - 18.4.0 + 18.5.0 preview 18.0.2 15.1.0.0 @@ -102,3 +102,4 @@ + diff --git a/src/Build.UnitTests/BackEnd/AssemblyLoadContextTestTasks.cs b/src/Build.UnitTests/BackEnd/AssemblyLoadContextTestTasks.cs index b11055c16c5..be9dcb0b600 100644 --- a/src/Build.UnitTests/BackEnd/AssemblyLoadContextTestTasks.cs +++ b/src/Build.UnitTests/BackEnd/AssemblyLoadContextTestTasks.cs @@ -3,11 +3,69 @@ using Microsoft.Build.Framework; using Microsoft.Build.Utilities; +using System; +using System.Reflection; #nullable disable namespace AssemblyLoadContextTest { + /// + /// Task that validates assembly version roll-forward behavior. + /// Tests that MSBuildLoadContext accepts newer assembly versions when older versions are requested. + /// + public class ValidateAssemblyVersionRollForward : Task + { + /// + /// The name of the assembly to check (e.g., "System.Collections.Immutable") + /// + [Required] + public string AssemblyName { get; set; } + + /// + /// The minimum expected version (e.g., "1.0.0.0") + /// + [Required] + public string MinimumVersion { get; set; } + + public override bool Execute() + { + try + { + // Try to load the assembly by name with minimum version + var minimumVersion = Version.Parse(MinimumVersion); + var assemblyName = new AssemblyName(AssemblyName) + { + Version = minimumVersion + }; + + // This will trigger MSBuildLoadContext.Load which should accept newer versions + var assembly = Assembly.Load(assemblyName); + var loadedVersion = assembly.GetName().Version; + + Log.LogMessage(MessageImportance.High, + $"Requested {AssemblyName} version {minimumVersion}, loaded version {loadedVersion}"); + + // Verify that we got a version >= minimum + if (loadedVersion < minimumVersion) + { + Log.LogError( + $"Assembly version roll-forward failed: requested {minimumVersion}, but loaded {loadedVersion} which is older"); + return false; + } + + Log.LogMessage(MessageImportance.High, + $"Assembly version roll-forward succeeded: loaded version {loadedVersion} >= requested {minimumVersion}"); + return true; + } + catch (Exception ex) + { + Log.LogErrorFromException(ex, showStackTrace: true); + return false; + } + } + } + public class RegisterObject : Task { internal const string CacheKey = "RegressionForMSBuild#5080"; diff --git a/src/Build.UnitTests/BackEnd/TaskBuilder_Tests.cs b/src/Build.UnitTests/BackEnd/TaskBuilder_Tests.cs index 9bec89727fe..7d80283306d 100644 --- a/src/Build.UnitTests/BackEnd/TaskBuilder_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TaskBuilder_Tests.cs @@ -662,6 +662,33 @@ public void SameAssemblyFromDifferentRelativePathsSharesAssemblyLoadContext() logger.AssertLogDoesntContain("MSB4018"); } +#if FEATURE_ASSEMBLYLOADCONTEXT + /// + /// Regression test for https://github.com/dotnet/msbuild/issues/12370 + /// Verifies that MSBuildLoadContext accepts newer assembly versions when older versions are requested (version roll-forward). + /// + [Fact] + public void MSBuildLoadContext_AcceptsNewerAssemblyVersions() + { + string realTaskPath = Assembly.GetExecutingAssembly().Location; + + // Use System.Collections.Immutable as test assembly - it's available in modern .NET runtime + // Request an older version (1.0.0.0) which should roll forward to whatever version is available + string projectContents = @" + + + + + +"; + + MockLogger logger = ObjectModelHelpers.BuildProjectExpectSuccess(projectContents, _testOutput); + + // Verify that the task logged success message + logger.AssertLogContains("Assembly version roll-forward succeeded"); + } +#endif + #if FEATURE_CODETASKFACTORY /// diff --git a/src/Build.UnitTests/BackEnd/TaskEnvironment_Tests.cs b/src/Build.UnitTests/BackEnd/TaskEnvironment_Tests.cs index acb1bb6e832..4a9d6126ced 100644 --- a/src/Build.UnitTests/BackEnd/TaskEnvironment_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TaskEnvironment_Tests.cs @@ -384,54 +384,5 @@ public void TaskEnvironment_GetAbsolutePath_WithInvalidPathChars_ShouldNotThrow( DisposeTaskEnvironment(taskEnvironment); } } - - [Theory] - [MemberData(nameof(EnvironmentTypes))] - public void TaskEnvironment_GetAbsolutePath_WithEmptyPath_ReturnsProjectDirectory(string environmentType) - { - var taskEnvironment = CreateTaskEnvironment(environmentType); - - // Empty path should absolutize to project directory (Path.Combine behavior) - var absolutePath = taskEnvironment.GetAbsolutePath(string.Empty); - - absolutePath.Value.ShouldBe(taskEnvironment.ProjectDirectory.Value); - absolutePath.OriginalValue.ShouldBe(string.Empty); - } - - [Theory] - [MemberData(nameof(EnvironmentTypes))] - public void TaskEnvironment_GetAbsolutePath_WithNullPath_WhenWave18_4Disabled_ReturnsNullPath(string environmentType) - { - using TestEnvironment testEnv = TestEnvironment.Create(); - ChangeWaves.ResetStateForTests(); - testEnv.SetEnvironmentVariable("MSBUILDDISABLEFEATURESFROMVERSION", ChangeWaves.Wave18_4.ToString()); - BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly(); - - var taskEnvironment = CreateTaskEnvironment(environmentType); - - // When Wave18_4 is disabled, null path returns as-is - var absolutePath = taskEnvironment.GetAbsolutePath(null!); - - absolutePath.Value.ShouldBeNull(); - absolutePath.OriginalValue.ShouldBeNull(); - - ChangeWaves.ResetStateForTests(); - } - - [Theory] - [MemberData(nameof(EnvironmentTypes))] - public void TaskEnvironment_GetAbsolutePath_WithNullPath_WhenWave18_4Enabled_Throws(string environmentType) - { - using TestEnvironment testEnv = TestEnvironment.Create(); - ChangeWaves.ResetStateForTests(); - BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly(); - - var taskEnvironment = CreateTaskEnvironment(environmentType); - - // When Wave18_4 is enabled, null path should throw - Should.Throw(() => taskEnvironment.GetAbsolutePath(null!)); - - ChangeWaves.ResetStateForTests(); - } } } diff --git a/src/Build.UnitTests/Resources_Tests.cs b/src/Build.UnitTests/Resources_Tests.cs new file mode 100644 index 00000000000..34d3892a78f --- /dev/null +++ b/src/Build.UnitTests/Resources_Tests.cs @@ -0,0 +1,254 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Xml.Linq; +using Shouldly; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.Build.UnitTests +{ + /// + /// Tests to verify that all resource strings used in code exist in .resx files. + /// + /// This test suite helps prevent runtime errors where code references resource strings + /// that don't exist in the corresponding .resx files. These issues typically manifest as: + /// 1. Missing resource exceptions at runtime + /// 2. Resources accessible from multiple code paths but only tested in one + /// 3. Resources referenced in conditional compilation code that aren't in the main .resx + /// + /// If these tests fail, it means: + /// - New code is referencing a resource that doesn't exist - add the resource to the .resx file + /// - A resource was deleted but code still references it - update the code + /// - A resource is in the wrong .resx file - move it to the correct assembly's resources + /// + /// Related issues: + /// - https://github.com/dotnet/msbuild/issues/12334 + /// - https://github.com/dotnet/msbuild/issues/11515 + /// - https://github.com/dotnet/msbuild/issues/7218 + /// - https://github.com/dotnet/msbuild/issues/2997 + /// - https://github.com/dotnet/msbuild/issues/9150 + /// + public class Resources_Tests + { + private readonly ITestOutputHelper _output; + + public Resources_Tests(ITestOutputHelper output) + { + _output = output; + } + + /// + /// Verifies that all resource strings referenced in Microsoft.Build assembly exist in the corresponding .resx files + /// + [Fact] + public void AllReferencedResourcesExistInBuildAssembly() + { + VerifyResourcesForAssembly( + "Microsoft.Build", + Path.Combine(GetRepoRoot(), "src", "Build"), + new[] { "Resources/Strings.resx" }, + new[] { "../Shared/Resources/Strings.shared.resx" }); + } + + /// + /// Verifies that all resource strings referenced in Microsoft.Build.Tasks.Core assembly exist in the corresponding .resx files + /// + [Fact] + public void AllReferencedResourcesExistInTasksAssembly() + { + VerifyResourcesForAssembly( + "Microsoft.Build.Tasks.Core", + Path.Combine(GetRepoRoot(), "src", "Tasks"), + new[] { "Resources/Strings.resx" }, + new[] { "../Shared/Resources/Strings.shared.resx" }); + } + + /// + /// Verifies that all resource strings referenced in Microsoft.Build.Utilities.Core assembly exist in the corresponding .resx files + /// + [Fact] + public void AllReferencedResourcesExistInUtilitiesAssembly() + { + VerifyResourcesForAssembly( + "Microsoft.Build.Utilities.Core", + Path.Combine(GetRepoRoot(), "src", "Utilities"), + new[] { "Resources/Strings.resx" }, + new[] { "../Shared/Resources/Strings.shared.resx" }); + } + + /// + /// Verifies that all resource strings referenced in MSBuild assembly exist in the corresponding .resx files + /// + [Fact] + public void AllReferencedResourcesExistInMSBuildAssembly() + { + VerifyResourcesForAssembly( + "MSBuild", + Path.Combine(GetRepoRoot(), "src", "MSBuild"), + new[] { "Resources/Strings.resx" }, + new[] { "../Shared/Resources/Strings.shared.resx" }); + } + + // NOTE: To add verification for additional assemblies, follow this pattern: + // [Fact] + // public void AllReferencedResourcesExistInYourAssembly() + // { + // VerifyResourcesForAssembly( + // "Your.Assembly.Name", + // Path.Combine(GetRepoRoot(), "src", "YourAssemblyFolder"), + // new[] { "Resources/Strings.resx" }, // Primary resources for this assembly + // new[] { "../Shared/Resources/Strings.shared.resx" }); // Shared resources + // } + + private void VerifyResourcesForAssembly( + string assemblyName, + string sourceDirectory, + string[] primaryResxPaths, + string[] sharedResxPaths) + { + _output.WriteLine($"Verifying resources for {assemblyName}"); + _output.WriteLine($"Source directory: {sourceDirectory}"); + + // Load all resource strings from .resx files + var availableResources = new HashSet(StringComparer.OrdinalIgnoreCase); + + foreach (var resxPath in primaryResxPaths) + { + var fullPath = Path.Combine(sourceDirectory, resxPath); + _output.WriteLine($"Loading primary resources from: {fullPath}"); + LoadResourcesFromResx(fullPath, availableResources); + } + + foreach (var resxPath in sharedResxPaths) + { + var fullPath = Path.Combine(sourceDirectory, resxPath); + _output.WriteLine($"Loading shared resources from: {fullPath}"); + LoadResourcesFromResx(fullPath, availableResources); + } + + _output.WriteLine($"Total available resources: {availableResources.Count}"); + + // Find all resource string references in source code + var referencedResources = new HashSet(StringComparer.OrdinalIgnoreCase); + var sourceFiles = Directory.GetFiles(sourceDirectory, "*.cs", SearchOption.AllDirectories) + .Where(f => !f.Contains("/obj/") && !f.Contains("\\obj\\")) + .Where(f => !f.Contains("/bin/") && !f.Contains("\\bin\\")) + .ToList(); + + _output.WriteLine($"Scanning {sourceFiles.Count} source files for resource references"); + + foreach (var sourceFile in sourceFiles) + { + ExtractResourceReferences(sourceFile, referencedResources); + } + + _output.WriteLine($"Total referenced resources: {referencedResources.Count}"); + + // Find missing resources + var missingResources = referencedResources.Except(availableResources, StringComparer.OrdinalIgnoreCase).ToList(); + + if (missingResources.Any()) + { + _output.WriteLine($"Missing resources ({missingResources.Count}):"); + foreach (var missing in missingResources.OrderBy(x => x)) + { + _output.WriteLine($" - {missing}"); + } + } + + // Assert that all referenced resources exist + missingResources.ShouldBeEmpty($"The following resources are referenced in code but missing from .resx files in {assemblyName}: {string.Join(", ", missingResources)}"); + } + + private void LoadResourcesFromResx(string resxPath, HashSet resources) + { + if (!File.Exists(resxPath)) + { + _output.WriteLine($"WARNING: Resource file not found: {resxPath}"); + return; + } + + var doc = XDocument.Load(resxPath); + foreach (var dataElement in doc.Descendants("data")) + { + var name = dataElement.Attribute("name")?.Value; + if (!string.IsNullOrEmpty(name)) + { + resources.Add(name!); + } + } + } + + private void ExtractResourceReferences(string sourceFile, HashSet resources) + { + var content = File.ReadAllText(sourceFile); + + // Skip files that are conditional compilation only (e.g., XamlTaskFactory which is .NETFramework-only) + // These might reference resources that are intentionally not included in all builds + // TODO: Consider handling this more elegantly by checking project file conditionals + + // Patterns to match resource method calls with string literal arguments + var patterns = new[] + { + // ResourceUtilities.FormatResourceString*("ResourceName", ...) + @"ResourceUtilities\.FormatResourceString[A-Za-z]*\s*\(\s*""([A-Z][^""]+)""\s*[,\)]", + + // ResourceUtilities.GetResourceString("ResourceName") + @"ResourceUtilities\.GetResourceString\s*\(\s*""([A-Z][^""]+)""\s*\)", + + // Log.LogErrorWithCodeFromResources("ResourceName", ...) + @"\.LogErrorWithCodeFromResources\s*\(\s*""([A-Z][^""]+)""\s*[,\)]", + + // Log.LogWarningWithCodeFromResources("ResourceName", ...) + @"\.LogWarningWithCodeFromResources\s*\(\s*""([A-Z][^""]+)""\s*[,\)]", + + // ProjectErrorUtilities.ThrowInvalidProject(*location, "ResourceName", ...) + @"ProjectErrorUtilities\.ThrowInvalid[A-Za-z]*\s*\([^,""]*,\s*""([A-Z][^""]+)""\s*[,\)]", + + // ProjectErrorUtilities.VerifyThrowInvalidProject(*location, "ResourceName", ...) + @"ProjectErrorUtilities\.VerifyThrowInvalid[A-Za-z]*\s*\([^,""]*,\s*""([A-Z][^""]+)""\s*[,\)]", + + // AssemblyResources.GetString("ResourceName") - case where the resource name starts with uppercase + @"AssemblyResources\.GetString\s*\(\s*""([A-Z][^""]+)""\s*\)", + }; + + foreach (var pattern in patterns) + { + var matches = Regex.Matches(content, pattern, RegexOptions.Multiline); + foreach (Match match in matches) + { + if (match.Groups.Count > 1) + { + var resourceName = match.Groups[1].Value; + // Resource names typically start with uppercase and don't contain braces or dollar signs + if (!resourceName.Contains("{") && + !resourceName.Contains("$") && + !resourceName.Contains(" ") && + char.IsUpper(resourceName[0])) + { + resources.Add(resourceName); + } + } + } + } + } + + private string GetRepoRoot() + { + // Start from the current directory and walk up until we find the repo root + var currentDir = Directory.GetCurrentDirectory(); + while (currentDir != null && !File.Exists(Path.Combine(currentDir, "MSBuild.sln"))) + { + currentDir = Directory.GetParent(currentDir)?.FullName; + } + + return currentDir ?? throw new InvalidOperationException("Could not find repository root"); + } + } +} diff --git a/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs b/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs index 2d40523d648..edbf03967b2 100644 --- a/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs +++ b/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs @@ -301,7 +301,7 @@ public bool IsTraversal { if (!_isTraversalProject.HasValue) { -#if NET471_OR_GREATER +#if FEATURE_MSIOREDIST if (MemoryExtensions.Equals(Microsoft.IO.Path.GetFileName(ProjectFullPath.AsSpan()), "dirs.proj".AsSpan(), StringComparison.OrdinalIgnoreCase)) #else if (MemoryExtensions.Equals(Path.GetFileName(ProjectFullPath.AsSpan()), "dirs.proj", StringComparison.OrdinalIgnoreCase)) diff --git a/src/Build/BackEnd/TaskExecutionHost/MultiProcessTaskEnvironmentDriver.cs b/src/Build/BackEnd/TaskExecutionHost/MultiProcessTaskEnvironmentDriver.cs index bec24b034f7..490ed0ca9bc 100644 --- a/src/Build/BackEnd/TaskExecutionHost/MultiProcessTaskEnvironmentDriver.cs +++ b/src/Build/BackEnd/TaskExecutionHost/MultiProcessTaskEnvironmentDriver.cs @@ -40,13 +40,7 @@ public AbsolutePath ProjectDirectory /// public AbsolutePath GetAbsolutePath(string path) { - // Opt-out for null path when Wave18_4 is disabled - return null as-is. - if (!ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave18_4) && path is null) - { - return new AbsolutePath(path!, path!, ignoreRootedCheck: true); - } - - return new AbsolutePath(path, basePath: ProjectDirectory); + return new AbsolutePath(path, ProjectDirectory); } /// diff --git a/src/Build/BackEnd/TaskExecutionHost/MultiThreadedTaskEnvironmentDriver.cs b/src/Build/BackEnd/TaskExecutionHost/MultiThreadedTaskEnvironmentDriver.cs index 1bf9009054a..cf54c430d0d 100644 --- a/src/Build/BackEnd/TaskExecutionHost/MultiThreadedTaskEnvironmentDriver.cs +++ b/src/Build/BackEnd/TaskExecutionHost/MultiThreadedTaskEnvironmentDriver.cs @@ -71,12 +71,6 @@ public AbsolutePath ProjectDirectory /// public AbsolutePath GetAbsolutePath(string path) { - // Opt-out for null path when Wave18_4 is disabled - return null as-is. - if (!ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave18_4) && path is null) - { - return new AbsolutePath(path!, path!, ignoreRootedCheck: true); - } - return new AbsolutePath(path, ProjectDirectory); } diff --git a/src/Build/BuildCheck/Checks/DoubleWritesCheck.cs b/src/Build/BuildCheck/Checks/DoubleWritesCheck.cs index d2320616e71..59eedb88ac4 100644 --- a/src/Build/BuildCheck/Checks/DoubleWritesCheck.cs +++ b/src/Build/BuildCheck/Checks/DoubleWritesCheck.cs @@ -3,15 +3,14 @@ using System; using System.Collections.Generic; -#if !FEATURE_MSIOREDIST -using System.IO; -#endif using System.Linq; using Microsoft.Build.Shared; using static Microsoft.Build.Experimental.BuildCheck.TaskInvocationCheckData; #if FEATURE_MSIOREDIST using Path = Microsoft.IO.Path; +#else +using System.IO; #endif namespace Microsoft.Build.Experimental.BuildCheck.Checks; diff --git a/src/Build/BuildCheck/Checks/ExecCliBuildCheck.cs b/src/Build/BuildCheck/Checks/ExecCliBuildCheck.cs index 252159162b2..d81ed36aaac 100644 --- a/src/Build/BuildCheck/Checks/ExecCliBuildCheck.cs +++ b/src/Build/BuildCheck/Checks/ExecCliBuildCheck.cs @@ -3,13 +3,12 @@ using System; using System.Collections.Generic; -#if !FEATURE_MSIOREDIST -using System.IO; -#endif using Microsoft.Build.Shared; #if FEATURE_MSIOREDIST using Path = Microsoft.IO.Path; +#else +using System.IO; #endif namespace Microsoft.Build.Experimental.BuildCheck.Checks; diff --git a/src/Build/Evaluation/Expander.cs b/src/Build/Evaluation/Expander.cs index 3cb76b64630..9912532a3bb 100644 --- a/src/Build/Evaluation/Expander.cs +++ b/src/Build/Evaluation/Expander.cs @@ -7,11 +7,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; -#if NET using System.IO; -#else -using Microsoft.IO; -#endif using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; @@ -34,6 +30,11 @@ using TaskItem = Microsoft.Build.Execution.ProjectItemInstance.TaskItem; using TaskItemFactory = Microsoft.Build.Execution.ProjectItemInstance.TaskItem.TaskItemFactory; +#if FEATURE_MSIOREDIST +using Directory = Microsoft.IO.Directory; +using Path = Microsoft.IO.Path; +#endif + #nullable disable namespace Microsoft.Build.Evaluation diff --git a/src/Build/Evaluation/Expander/ArgumentParser.cs b/src/Build/Evaluation/Expander/ArgumentParser.cs index 2d9fa7954a0..a6b8d1f8bd5 100644 --- a/src/Build/Evaluation/Expander/ArgumentParser.cs +++ b/src/Build/Evaluation/Expander/ArgumentParser.cs @@ -4,7 +4,7 @@ using System; using System.Globalization; -#if NETFRAMEWORK +#if FEATURE_MSIOREDIST using Microsoft.IO; #endif diff --git a/src/Build/Instance/ProjectItemInstance.cs b/src/Build/Instance/ProjectItemInstance.cs index 2b14dc6b998..352e148e892 100644 --- a/src/Build/Instance/ProjectItemInstance.cs +++ b/src/Build/Instance/ProjectItemInstance.cs @@ -743,7 +743,8 @@ private void CommonConstructor( if (itemDefinitions == null || !useItemDefinitionsWithoutModification) { // TaskItems don't have an item type. So for their benefit, we have to lookup and add the regular item definition. - inheritedItemDefinitions = (itemDefinitions == null) ? null : new List(itemDefinitions); + inheritedItemDefinitions = (itemDefinitions == null) ? null : new List(itemDefinitions.Count + 1); + ((List)inheritedItemDefinitions)?.AddRange(itemDefinitions); ProjectItemDefinitionInstance itemDefinition; if (projectToUse.ItemDefinitions.TryGetValue(itemTypeToUse, out itemDefinition)) diff --git a/src/Build/Instance/TaskFactories/AssemblyTaskFactory.cs b/src/Build/Instance/TaskFactories/AssemblyTaskFactory.cs index bef70623b73..5c4478317d0 100644 --- a/src/Build/Instance/TaskFactories/AssemblyTaskFactory.cs +++ b/src/Build/Instance/TaskFactories/AssemblyTaskFactory.cs @@ -11,8 +11,9 @@ using Microsoft.Build.BackEnd.Components.RequestBuilder; using Microsoft.Build.Framework; using Microsoft.Build.Shared; -#if NETFRAMEWORK -using Microsoft.IO; + +#if FEATURE_MSIOREDIST +using Path = Microsoft.IO.Path; #else using System.IO; #endif diff --git a/src/Build/Logging/TerminalLogger/TerminalLogger.cs b/src/Build/Logging/TerminalLogger/TerminalLogger.cs index 8965696dfa8..be8330da86c 100644 --- a/src/Build/Logging/TerminalLogger/TerminalLogger.cs +++ b/src/Build/Logging/TerminalLogger/TerminalLogger.cs @@ -16,8 +16,8 @@ using System.Buffers; #endif -#if NETFRAMEWORK -using Microsoft.IO; +#if FEATURE_MSIOREDIST +using Path = Microsoft.IO.Path; #else using System.IO; #endif diff --git a/src/Build/Utilities/Utilities.cs b/src/Build/Utilities/Utilities.cs index b3936b737e2..d3221ab6d3d 100644 --- a/src/Build/Utilities/Utilities.cs +++ b/src/Build/Utilities/Utilities.cs @@ -5,11 +5,6 @@ using System.Collections; using System.Collections.Generic; using System.Diagnostics; -#if NET -using System.IO; -#else -using Microsoft.IO; -#endif using System.Linq; using System.Text; using System.Text.RegularExpressions; @@ -22,6 +17,12 @@ using Toolset = Microsoft.Build.Evaluation.Toolset; using XmlElementWithLocation = Microsoft.Build.Construction.XmlElementWithLocation; +#if FEATURE_MSIOREDIST +using Path = Microsoft.IO.Path; +#else +using System.IO; +#endif + #nullable disable namespace Microsoft.Build.Internal diff --git a/src/Framework.UnitTests/AbsolutePath_Tests.cs b/src/Framework.UnitTests/AbsolutePath_Tests.cs index 3a099ef36c5..40a93551385 100644 --- a/src/Framework.UnitTests/AbsolutePath_Tests.cs +++ b/src/Framework.UnitTests/AbsolutePath_Tests.cs @@ -1,15 +1,24 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.IO; using Microsoft.Build.Framework; +using Microsoft.Build.Shared; using Shouldly; using Xunit; +using Xunit.NetCore.Extensions; namespace Microsoft.Build.UnitTests { public class AbsolutePath_Tests { + private static AbsolutePath GetTestBasePath() + { + string baseDirectory = Path.Combine(Path.GetTempPath(), "abspath_test_base"); + return new AbsolutePath(baseDirectory, ignoreRootedCheck: false); + } + private static void ValidatePathAcceptance(string path, bool shouldBeAccepted) { if (shouldBeAccepted) @@ -36,11 +45,31 @@ public void AbsolutePath_FromAbsolutePath_ShouldPreservePath() Path.IsPathRooted(absolutePath.Value).ShouldBeTrue(); } + [Theory] + [InlineData(null)] + [InlineData("")] + [UseInvariantCulture] + public void AbsolutePath_NullOrEmpty_ShouldThrow(string? path) + { + var exception = Should.Throw(() => new AbsolutePath(path!)); + exception.Message.ShouldContain("Path must not be null or empty"); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [UseInvariantCulture] + public void AbsolutePath_NullOrEmptyWithBasePath_ShouldThrow(string? path) + { + var basePath = GetTestBasePath(); + var exception = Should.Throw(() => new AbsolutePath(path!, basePath)); + exception.Message.ShouldContain("Path must not be null or empty"); + } + [Theory] [InlineData("subfolder")] [InlineData("deep/nested/path")] [InlineData(".")] - [InlineData("")] [InlineData("..")] public void AbsolutePath_FromRelativePath_ShouldResolveAgainstBase(string relativePath) { @@ -189,5 +218,13 @@ public void AbsolutePath_UnixPathValidation_ShouldAcceptOnlyTrueAbsolutePaths(st { ValidatePathAcceptance(path, shouldBeAccepted); } + + [WindowsOnlyFact] + [UseInvariantCulture] + public void AbsolutePath_NotRooted_ShouldThrowWithLocalizedMessage() + { + var exception = Should.Throw(() => new AbsolutePath("relative/path")); + exception.Message.ShouldContain("Path must be rooted"); + } } } diff --git a/src/Framework/Microsoft.Build.Framework.csproj b/src/Framework/Microsoft.Build.Framework.csproj index b87b9db871e..b644c5ca208 100644 --- a/src/Framework/Microsoft.Build.Framework.csproj +++ b/src/Framework/Microsoft.Build.Framework.csproj @@ -73,4 +73,11 @@ + + + + $(AssemblyName).Strings.resources + Designer + + diff --git a/src/Framework/PathHelpers/AbsolutePath.cs b/src/Framework/PathHelpers/AbsolutePath.cs index 7fb31c57232..a3433c46b46 100644 --- a/src/Framework/PathHelpers/AbsolutePath.cs +++ b/src/Framework/PathHelpers/AbsolutePath.cs @@ -2,8 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -#if NETFRAMEWORK -using Microsoft.IO; + +#if FEATURE_MSIOREDIST +using Path = Microsoft.IO.Path; #else using System.IO; #endif @@ -42,6 +43,7 @@ namespace Microsoft.Build.Framework /// Initializes a new instance of the struct. /// /// The absolute path string. + /// Thrown if is null, empty, or not a rooted path. public AbsolutePath(string path) { ValidatePath(path); @@ -85,7 +87,7 @@ private static void ValidatePath(string path) { if (string.IsNullOrEmpty(path)) { - throw new ArgumentException("Path must not be null or empty.", nameof(path)); + throw new ArgumentException(FrameworkResources.GetString("PathMustNotBeNullOrEmpty"), nameof(path)); } // Path.IsPathFullyQualified is not available in .NET Standard 2.0 @@ -93,7 +95,7 @@ private static void ValidatePath(string path) #if NETFRAMEWORK || NET if (!Path.IsPathFullyQualified(path)) { - throw new ArgumentException("Path must be rooted.", nameof(path)); + throw new ArgumentException(FrameworkResources.GetString("PathMustBeRooted"), nameof(path)); } #endif } @@ -103,14 +105,20 @@ private static void ValidatePath(string path) /// /// The path to combine with the base path. /// The base path to combine with. + /// Thrown if is null or empty. public AbsolutePath(string path, AbsolutePath basePath) { + if (string.IsNullOrEmpty(path)) + { + throw new ArgumentException(FrameworkResources.GetString("PathMustNotBeNullOrEmpty"), nameof(path)); + } + // This function should not throw when path has illegal characters. // For .NET Framework, Microsoft.IO.Path.Combine should be used instead of System.IO.Path.Combine to achieve it. // For .NET Core, System.IO.Path.Combine already does not throw in this case. Value = Path.Combine(basePath.Value, path); OriginalValue = path; - } + } /// /// Implicitly converts an AbsolutePath to a string. diff --git a/src/Framework/Resources/AssemblyResources.cs b/src/Framework/Resources/AssemblyResources.cs new file mode 100644 index 00000000000..f30e00fce30 --- /dev/null +++ b/src/Framework/Resources/AssemblyResources.cs @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Globalization; +using System.Reflection; +using System.Resources; + +namespace Microsoft.Build.Framework +{ + /// + /// This class provides access to the Framework assembly's resources. + /// + /// + /// Named FrameworkResources (not AssemblyResources) to avoid conflicts with + /// Microsoft.Build.Shared.AssemblyResources which is visible via InternalsVisibleTo. + /// + internal static class FrameworkResources + { + /// + /// The assembly's primary resources. + /// + private static readonly ResourceManager s_resources = new ResourceManager("Microsoft.Build.Framework.Strings", typeof(FrameworkResources).GetTypeInfo().Assembly); + + /// + /// Loads the specified resource string. + /// + /// This method is thread-safe. + /// The name of the string resource to load. + /// The resource string. + internal static string GetString(string name) + { + // NOTE: the ResourceManager.GetString() method is thread-safe + string? resource = s_resources.GetString(name, CultureInfo.CurrentUICulture); + + FrameworkErrorUtilities.VerifyThrow(resource != null, $"Missing resource '{name}'"); + + return resource!; + } + } +} diff --git a/src/Framework/Resources/Strings.resx b/src/Framework/Resources/Strings.resx new file mode 100644 index 00000000000..9c21872c323 --- /dev/null +++ b/src/Framework/Resources/Strings.resx @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Path must not be null or empty. + Error thrown when a null or empty path is passed to AbsolutePath constructor. + + + Path must be rooted. + Error thrown when a relative (non-absolute) path is passed to AbsolutePath constructor. + + diff --git a/src/Framework/Resources/xlf/Strings.cs.xlf b/src/Framework/Resources/xlf/Strings.cs.xlf new file mode 100644 index 00000000000..1636fbd163e --- /dev/null +++ b/src/Framework/Resources/xlf/Strings.cs.xlf @@ -0,0 +1,17 @@ + + + + + + Path must be rooted. + Path must be rooted. + Error thrown when a relative (non-absolute) path is passed to AbsolutePath constructor. + + + Path must not be null or empty. + Path must not be null or empty. + Error thrown when a null or empty path is passed to AbsolutePath constructor. + + + + \ No newline at end of file diff --git a/src/Framework/Resources/xlf/Strings.de.xlf b/src/Framework/Resources/xlf/Strings.de.xlf new file mode 100644 index 00000000000..11ebbc2cd71 --- /dev/null +++ b/src/Framework/Resources/xlf/Strings.de.xlf @@ -0,0 +1,17 @@ + + + + + + Path must be rooted. + Path must be rooted. + Error thrown when a relative (non-absolute) path is passed to AbsolutePath constructor. + + + Path must not be null or empty. + Path must not be null or empty. + Error thrown when a null or empty path is passed to AbsolutePath constructor. + + + + \ No newline at end of file diff --git a/src/Framework/Resources/xlf/Strings.en.xlf b/src/Framework/Resources/xlf/Strings.en.xlf new file mode 100644 index 00000000000..1ca805d7ff0 --- /dev/null +++ b/src/Framework/Resources/xlf/Strings.en.xlf @@ -0,0 +1,17 @@ + + + + + + Path must be rooted. + Path must be rooted. + Error thrown when a relative (non-absolute) path is passed to AbsolutePath constructor. + + + Path must not be null or empty. + Path must not be null or empty. + Error thrown when a null or empty path is passed to AbsolutePath constructor. + + + + diff --git a/src/Framework/Resources/xlf/Strings.es.xlf b/src/Framework/Resources/xlf/Strings.es.xlf new file mode 100644 index 00000000000..d585e2b55d1 --- /dev/null +++ b/src/Framework/Resources/xlf/Strings.es.xlf @@ -0,0 +1,17 @@ + + + + + + Path must be rooted. + Path must be rooted. + Error thrown when a relative (non-absolute) path is passed to AbsolutePath constructor. + + + Path must not be null or empty. + Path must not be null or empty. + Error thrown when a null or empty path is passed to AbsolutePath constructor. + + + + \ No newline at end of file diff --git a/src/Framework/Resources/xlf/Strings.fr.xlf b/src/Framework/Resources/xlf/Strings.fr.xlf new file mode 100644 index 00000000000..ae73a006114 --- /dev/null +++ b/src/Framework/Resources/xlf/Strings.fr.xlf @@ -0,0 +1,17 @@ + + + + + + Path must be rooted. + Path must be rooted. + Error thrown when a relative (non-absolute) path is passed to AbsolutePath constructor. + + + Path must not be null or empty. + Path must not be null or empty. + Error thrown when a null or empty path is passed to AbsolutePath constructor. + + + + \ No newline at end of file diff --git a/src/Framework/Resources/xlf/Strings.it.xlf b/src/Framework/Resources/xlf/Strings.it.xlf new file mode 100644 index 00000000000..2f15144e36a --- /dev/null +++ b/src/Framework/Resources/xlf/Strings.it.xlf @@ -0,0 +1,17 @@ + + + + + + Path must be rooted. + Path must be rooted. + Error thrown when a relative (non-absolute) path is passed to AbsolutePath constructor. + + + Path must not be null or empty. + Path must not be null or empty. + Error thrown when a null or empty path is passed to AbsolutePath constructor. + + + + \ No newline at end of file diff --git a/src/Framework/Resources/xlf/Strings.ja.xlf b/src/Framework/Resources/xlf/Strings.ja.xlf new file mode 100644 index 00000000000..c5bfff5c34e --- /dev/null +++ b/src/Framework/Resources/xlf/Strings.ja.xlf @@ -0,0 +1,17 @@ + + + + + + Path must be rooted. + Path must be rooted. + Error thrown when a relative (non-absolute) path is passed to AbsolutePath constructor. + + + Path must not be null or empty. + Path must not be null or empty. + Error thrown when a null or empty path is passed to AbsolutePath constructor. + + + + \ No newline at end of file diff --git a/src/Framework/Resources/xlf/Strings.ko.xlf b/src/Framework/Resources/xlf/Strings.ko.xlf new file mode 100644 index 00000000000..91f0bc8d505 --- /dev/null +++ b/src/Framework/Resources/xlf/Strings.ko.xlf @@ -0,0 +1,17 @@ + + + + + + Path must be rooted. + Path must be rooted. + Error thrown when a relative (non-absolute) path is passed to AbsolutePath constructor. + + + Path must not be null or empty. + Path must not be null or empty. + Error thrown when a null or empty path is passed to AbsolutePath constructor. + + + + \ No newline at end of file diff --git a/src/Framework/Resources/xlf/Strings.pl.xlf b/src/Framework/Resources/xlf/Strings.pl.xlf new file mode 100644 index 00000000000..bfef59da0d4 --- /dev/null +++ b/src/Framework/Resources/xlf/Strings.pl.xlf @@ -0,0 +1,17 @@ + + + + + + Path must be rooted. + Path must be rooted. + Error thrown when a relative (non-absolute) path is passed to AbsolutePath constructor. + + + Path must not be null or empty. + Path must not be null or empty. + Error thrown when a null or empty path is passed to AbsolutePath constructor. + + + + \ No newline at end of file diff --git a/src/Framework/Resources/xlf/Strings.pt-BR.xlf b/src/Framework/Resources/xlf/Strings.pt-BR.xlf new file mode 100644 index 00000000000..1251f7b11ab --- /dev/null +++ b/src/Framework/Resources/xlf/Strings.pt-BR.xlf @@ -0,0 +1,17 @@ + + + + + + Path must be rooted. + Path must be rooted. + Error thrown when a relative (non-absolute) path is passed to AbsolutePath constructor. + + + Path must not be null or empty. + Path must not be null or empty. + Error thrown when a null or empty path is passed to AbsolutePath constructor. + + + + \ No newline at end of file diff --git a/src/Framework/Resources/xlf/Strings.ru.xlf b/src/Framework/Resources/xlf/Strings.ru.xlf new file mode 100644 index 00000000000..446dbd40a54 --- /dev/null +++ b/src/Framework/Resources/xlf/Strings.ru.xlf @@ -0,0 +1,17 @@ + + + + + + Path must be rooted. + Path must be rooted. + Error thrown when a relative (non-absolute) path is passed to AbsolutePath constructor. + + + Path must not be null or empty. + Path must not be null or empty. + Error thrown when a null or empty path is passed to AbsolutePath constructor. + + + + \ No newline at end of file diff --git a/src/Framework/Resources/xlf/Strings.tr.xlf b/src/Framework/Resources/xlf/Strings.tr.xlf new file mode 100644 index 00000000000..fb278031528 --- /dev/null +++ b/src/Framework/Resources/xlf/Strings.tr.xlf @@ -0,0 +1,17 @@ + + + + + + Path must be rooted. + Path must be rooted. + Error thrown when a relative (non-absolute) path is passed to AbsolutePath constructor. + + + Path must not be null or empty. + Path must not be null or empty. + Error thrown when a null or empty path is passed to AbsolutePath constructor. + + + + \ No newline at end of file diff --git a/src/Framework/Resources/xlf/Strings.xlf b/src/Framework/Resources/xlf/Strings.xlf new file mode 100644 index 00000000000..2b84b4f9f6b --- /dev/null +++ b/src/Framework/Resources/xlf/Strings.xlf @@ -0,0 +1,16 @@ + + + + + + + Path must be rooted. + Error thrown when a relative (non-absolute) path is passed to AbsolutePath constructor. + + + Path must not be null or empty. + Error thrown when a null or empty path is passed to AbsolutePath constructor. + + + + diff --git a/src/Framework/Resources/xlf/Strings.zh-Hans.xlf b/src/Framework/Resources/xlf/Strings.zh-Hans.xlf new file mode 100644 index 00000000000..b5b2b7a856a --- /dev/null +++ b/src/Framework/Resources/xlf/Strings.zh-Hans.xlf @@ -0,0 +1,17 @@ + + + + + + Path must be rooted. + Path must be rooted. + Error thrown when a relative (non-absolute) path is passed to AbsolutePath constructor. + + + Path must not be null or empty. + Path must not be null or empty. + Error thrown when a null or empty path is passed to AbsolutePath constructor. + + + + \ No newline at end of file diff --git a/src/Framework/Resources/xlf/Strings.zh-Hant.xlf b/src/Framework/Resources/xlf/Strings.zh-Hant.xlf new file mode 100644 index 00000000000..7452d1b5a8b --- /dev/null +++ b/src/Framework/Resources/xlf/Strings.zh-Hant.xlf @@ -0,0 +1,17 @@ + + + + + + Path must be rooted. + Path must be rooted. + Error thrown when a relative (non-absolute) path is passed to AbsolutePath constructor. + + + Path must not be null or empty. + Path must not be null or empty. + Error thrown when a null or empty path is passed to AbsolutePath constructor. + + + + \ No newline at end of file diff --git a/src/MSBuild.UnitTests/MSBuildServer_Tests.cs b/src/MSBuild.UnitTests/MSBuildServer_Tests.cs index a820d138bf8..68f1ae0a765 100644 --- a/src/MSBuild.UnitTests/MSBuildServer_Tests.cs +++ b/src/MSBuild.UnitTests/MSBuildServer_Tests.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics; +using System.IO; using System.Reflection; using System.Text.RegularExpressions; using System.Threading; @@ -13,15 +14,9 @@ using Microsoft.Build.Shared; using Microsoft.Build.UnitTests; using Microsoft.Build.UnitTests.Shared; -#if NETFRAMEWORK -using Microsoft.IO; -#else -using System.IO; -#endif using Shouldly; using Xunit; using Xunit.Abstractions; -using Path = System.IO.Path; namespace Microsoft.Build.Engine.UnitTests { diff --git a/src/MSBuild/ValidateMSBuildPackageDependencyVersions.cs b/src/MSBuild/ValidateMSBuildPackageDependencyVersions.cs index af57996ef8d..770dcb4fae8 100644 --- a/src/MSBuild/ValidateMSBuildPackageDependencyVersions.cs +++ b/src/MSBuild/ValidateMSBuildPackageDependencyVersions.cs @@ -87,38 +87,21 @@ public override bool Execute() string assemblyVersion = AssemblyName.GetAssemblyName(path).Version.ToString(); if (!version.Equals(assemblyVersion)) { - // Ensure that the binding redirect is to the GAC version, but - // we still ship the version we explicitly reference to let - // API consumers bind to it at runtime. - // See https://github.com/dotnet/msbuild/issues/6976. - if (String.Equals(name, "System.ValueTuple", StringComparison.OrdinalIgnoreCase) && - String.Equals(version, "4.0.0.0") && String.Equals(assemblyVersion, "4.0.3.0")) - { - // foundSystemValueTuple = true; - } - else - { - Log.LogError( - subcategory: null, - errorCode: null, - helpKeyword: null, - file: appConfigPath, - lineNumber: bindingRedirectLineNumber, - columnNumber: 0, - endLineNumber: 0, - endColumnNumber: 0, - message: $"Binding redirect for '{name}' redirects to a different version ({version}) than MSBuild ships ({assemblyVersion})."); - } + Log.LogError( + subcategory: null, + errorCode: null, + helpKeyword: null, + file: appConfigPath, + lineNumber: bindingRedirectLineNumber, + columnNumber: 0, + endLineNumber: 0, + endColumnNumber: 0, + message: $"Binding redirect for '{name}' redirects to a different version ({version}) than MSBuild ships ({assemblyVersion})."); + } } } } - - // if (!foundSystemValueTuple) - // { - // Log.LogError("Binding redirect for 'System.ValueTuple' missing."); - // } - return !Log.HasLoggedErrors; } } diff --git a/src/MSBuild/XMake.cs b/src/MSBuild/XMake.cs index 113f7f0602c..64ed54db5de 100644 --- a/src/MSBuild/XMake.cs +++ b/src/MSBuild/XMake.cs @@ -45,11 +45,10 @@ using SimpleErrorLogger = Microsoft.Build.Logging.SimpleErrorLogger.SimpleErrorLogger; using TerminalLogger = Microsoft.Build.Logging.TerminalLogger; -#if NETFRAMEWORK +#if FEATURE_MSIOREDIST // Use I/O operations from Microsoft.IO.Redist which is generally higher perf // and also works around https://github.com/dotnet/msbuild/issues/10540. // Unnecessary on .NET 6+ because the perf improvements are in-box there. -using Microsoft.IO; using Directory = Microsoft.IO.Directory; using File = Microsoft.IO.File; using FileInfo = Microsoft.IO.FileInfo; diff --git a/src/Shared/FileMatcher.cs b/src/Shared/FileMatcher.cs index d08e6549791..168df31476a 100644 --- a/src/Shared/FileMatcher.cs +++ b/src/Shared/FileMatcher.cs @@ -6,12 +6,13 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; -#if !NETFRAMEWORK -using System.IO; +using System.Linq; + +#if FEATURE_MSIOREDIST +using Path = Microsoft.IO.Path; #else -using Microsoft.IO; +using System.IO; #endif -using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; diff --git a/src/Shared/FileSystem/WindowsFileSystem.cs b/src/Shared/FileSystem/WindowsFileSystem.cs index a83d932c00d..c704360c777 100644 --- a/src/Shared/FileSystem/WindowsFileSystem.cs +++ b/src/Shared/FileSystem/WindowsFileSystem.cs @@ -66,7 +66,7 @@ public override bool DirectoryExists(string path) public override bool FileExists(string path) { -#if NETFRAMEWORK +#if FEATURE_MSIOREDIST return Microsoft.IO.File.Exists(path); #else return File.Exists(path); diff --git a/src/Shared/MSBuildLoadContext.cs b/src/Shared/MSBuildLoadContext.cs index 6e322309312..3e797529682 100644 --- a/src/Shared/MSBuildLoadContext.cs +++ b/src/Shared/MSBuildLoadContext.cs @@ -83,7 +83,7 @@ public MSBuildLoadContext(string assemblyPath) } AssemblyName candidateAssemblyName = AssemblyLoadContext.GetAssemblyName(candidatePath); - if (candidateAssemblyName.Version != assemblyName.Version) + if (candidateAssemblyName.Version < assemblyName.Version) { continue; } diff --git a/src/Shared/Resources/Strings.shared.resx b/src/Shared/Resources/Strings.shared.resx index b008a9a313f..6e29c8e68af 100644 --- a/src/Shared/Resources/Strings.shared.resx +++ b/src/Shared/Resources/Strings.shared.resx @@ -367,4 +367,13 @@ The number of elements in the collection is greater than the available space in the destination array (when starting at the specified index). + + MSB3511: "{0}" is an invalid value for the "Importance" parameter. Valid values are: High, Normal and Low. + {StrBegin="MSB3511: "}UE: This message is shown when a user specifies a value for the importance attribute of Message which is not valid. + The importance enumeration is: High, Normal and Low. Specifying any other importance will result in this message being shown + LOCALIZATION: "Importance" should not be localized. + High should not be localized. + Normal should not be localized. + Low should not be localized. + diff --git a/src/Shared/Resources/xlf/Strings.shared.cs.xlf b/src/Shared/Resources/xlf/Strings.shared.cs.xlf index 16622d9e922..efc33efebe0 100644 --- a/src/Shared/Resources/xlf/Strings.shared.cs.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.cs.xlf @@ -47,6 +47,16 @@ MSB6005: Úloha se pokusila přihlásit před tím, než byla inicializována. Zpráva: {0} {StrBegin="MSB6005: "}UE: This occurs if the task attempts to log something in its own constructor. + + MSB3511: "{0}" is an invalid value for the "Importance" parameter. Valid values are: High, Normal and Low. + MSB3511: {0} je neplatná hodnota parametru Importance. Platné hodnoty: High, Normal a Low. + {StrBegin="MSB3511: "}UE: This message is shown when a user specifies a value for the importance attribute of Message which is not valid. + The importance enumeration is: High, Normal and Low. Specifying any other importance will result in this message being shown + LOCALIZATION: "Importance" should not be localized. + High should not be localized. + Normal should not be localized. + Low should not be localized. + Making the following modifications to the environment received from the parent node before applying it to the task host: Předtím, než bude pro hostitele úlohy použito prostředí přijaté z nadřazeného uzlu, budou provedeny jeho následující úpravy: diff --git a/src/Shared/Resources/xlf/Strings.shared.de.xlf b/src/Shared/Resources/xlf/Strings.shared.de.xlf index 5efa84ae78d..a63a5c4e29d 100644 --- a/src/Shared/Resources/xlf/Strings.shared.de.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.de.xlf @@ -47,6 +47,16 @@ MSB6005: Die Aufgabe hat versucht, eine Protokollierung durchzuführen, bevor sie initialisiert wurde. Meldung: {0} {StrBegin="MSB6005: "}UE: This occurs if the task attempts to log something in its own constructor. + + MSB3511: "{0}" is an invalid value for the "Importance" parameter. Valid values are: High, Normal and Low. + MSB3511: "{0}" ist ein ungültiger Wert für den "Importance"-Parameter. Gültige Werte sind: High, Normal und Low. + {StrBegin="MSB3511: "}UE: This message is shown when a user specifies a value for the importance attribute of Message which is not valid. + The importance enumeration is: High, Normal and Low. Specifying any other importance will result in this message being shown + LOCALIZATION: "Importance" should not be localized. + High should not be localized. + Normal should not be localized. + Low should not be localized. + Making the following modifications to the environment received from the parent node before applying it to the task host: Es werden folgende vom übergeordneten Knoten empfangene Änderungen an der Umgebung vorgenommen, bevor sie auf den Aufgabenhost angewendet wird: diff --git a/src/Shared/Resources/xlf/Strings.shared.es.xlf b/src/Shared/Resources/xlf/Strings.shared.es.xlf index 17dff3bef29..b7936f94976 100644 --- a/src/Shared/Resources/xlf/Strings.shared.es.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.es.xlf @@ -47,6 +47,16 @@ MSB6005: La tarea intentó registrarse antes de inicializarse. El mensaje era: {0} {StrBegin="MSB6005: "}UE: This occurs if the task attempts to log something in its own constructor. + + MSB3511: "{0}" is an invalid value for the "Importance" parameter. Valid values are: High, Normal and Low. + MSB3511: "{0}" no es un valor válido para el parámetro "Importance". Los valores válidos son: High, Normal y Low. + {StrBegin="MSB3511: "}UE: This message is shown when a user specifies a value for the importance attribute of Message which is not valid. + The importance enumeration is: High, Normal and Low. Specifying any other importance will result in this message being shown + LOCALIZATION: "Importance" should not be localized. + High should not be localized. + Normal should not be localized. + Low should not be localized. + Making the following modifications to the environment received from the parent node before applying it to the task host: Se están realizando las siguientes modificaciones en el entorno recibido del nodo primario antes de aplicarlo al host de tareas: diff --git a/src/Shared/Resources/xlf/Strings.shared.fr.xlf b/src/Shared/Resources/xlf/Strings.shared.fr.xlf index 062e03202b0..abbc14e5158 100644 --- a/src/Shared/Resources/xlf/Strings.shared.fr.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.fr.xlf @@ -47,6 +47,16 @@ MSB6005: La tâche a tenté d'ouvrir une session avant d'être initialisée. Le message était le suivant : {0} {StrBegin="MSB6005: "}UE: This occurs if the task attempts to log something in its own constructor. + + MSB3511: "{0}" is an invalid value for the "Importance" parameter. Valid values are: High, Normal and Low. + MSB3511: "{0}" n'est pas une valeur valide pour le paramètre "Importance". Les valeurs valides sont : High, Normal et Low. + {StrBegin="MSB3511: "}UE: This message is shown when a user specifies a value for the importance attribute of Message which is not valid. + The importance enumeration is: High, Normal and Low. Specifying any other importance will result in this message being shown + LOCALIZATION: "Importance" should not be localized. + High should not be localized. + Normal should not be localized. + Low should not be localized. + Making the following modifications to the environment received from the parent node before applying it to the task host: Modifications suivantes en cours sur l'environnement reçu du nœud parent avant son application à l'hôte de tâche : diff --git a/src/Shared/Resources/xlf/Strings.shared.it.xlf b/src/Shared/Resources/xlf/Strings.shared.it.xlf index ff1a89aad44..082c3faab51 100644 --- a/src/Shared/Resources/xlf/Strings.shared.it.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.it.xlf @@ -47,6 +47,16 @@ MSB6005: tentativo di registrazione prima dell'inizializzazione dell'attività. Messaggio: {0} {StrBegin="MSB6005: "}UE: This occurs if the task attempts to log something in its own constructor. + + MSB3511: "{0}" is an invalid value for the "Importance" parameter. Valid values are: High, Normal and Low. + MSB3511: "{0}" non è un valore valido per il parametro "Importance". I valori validi sono: High, Normal e Low. + {StrBegin="MSB3511: "}UE: This message is shown when a user specifies a value for the importance attribute of Message which is not valid. + The importance enumeration is: High, Normal and Low. Specifying any other importance will result in this message being shown + LOCALIZATION: "Importance" should not be localized. + High should not be localized. + Normal should not be localized. + Low should not be localized. + Making the following modifications to the environment received from the parent node before applying it to the task host: Le modifiche seguenti verranno apportate all'ambiente ricevuto dal nodo padre prima dell'applicazione all'host attività: diff --git a/src/Shared/Resources/xlf/Strings.shared.ja.xlf b/src/Shared/Resources/xlf/Strings.shared.ja.xlf index 1811273670a..8e33727f2fb 100644 --- a/src/Shared/Resources/xlf/Strings.shared.ja.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.ja.xlf @@ -47,6 +47,16 @@ MSB6005: タスクは、初期化される前にログを記録しようとしました。メッセージ: {0} {StrBegin="MSB6005: "}UE: This occurs if the task attempts to log something in its own constructor. + + MSB3511: "{0}" is an invalid value for the "Importance" parameter. Valid values are: High, Normal and Low. + MSB3511: "{0}" は、"Importance" パラメーターに対して無効な値です。有効な値は High、Normal および Low です。 + {StrBegin="MSB3511: "}UE: This message is shown when a user specifies a value for the importance attribute of Message which is not valid. + The importance enumeration is: High, Normal and Low. Specifying any other importance will result in this message being shown + LOCALIZATION: "Importance" should not be localized. + High should not be localized. + Normal should not be localized. + Low should not be localized. + Making the following modifications to the environment received from the parent node before applying it to the task host: 親ノードから受け取った環境をタスク ホストに適用する前に、次の変更を行っています: diff --git a/src/Shared/Resources/xlf/Strings.shared.ko.xlf b/src/Shared/Resources/xlf/Strings.shared.ko.xlf index 3d2df7b612e..102c6ecfb97 100644 --- a/src/Shared/Resources/xlf/Strings.shared.ko.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.ko.xlf @@ -47,6 +47,16 @@ MSB6005: 작업을 초기화하기 전에 로깅하려고 했습니다. 메시지: {0} {StrBegin="MSB6005: "}UE: This occurs if the task attempts to log something in its own constructor. + + MSB3511: "{0}" is an invalid value for the "Importance" parameter. Valid values are: High, Normal and Low. + MSB3511: "{0}"은(는) "Importance" 매개 변수에 사용할 수 없는 값입니다. 유효한 값은 High, Normal 및 Low입니다. + {StrBegin="MSB3511: "}UE: This message is shown when a user specifies a value for the importance attribute of Message which is not valid. + The importance enumeration is: High, Normal and Low. Specifying any other importance will result in this message being shown + LOCALIZATION: "Importance" should not be localized. + High should not be localized. + Normal should not be localized. + Low should not be localized. + Making the following modifications to the environment received from the parent node before applying it to the task host: 작업 호스트에 적용하기 전에 부모 노드로부터 받은 환경을 다음과 같이 수정하고 있습니다. diff --git a/src/Shared/Resources/xlf/Strings.shared.pl.xlf b/src/Shared/Resources/xlf/Strings.shared.pl.xlf index 86dadeb1928..f5c531296d1 100644 --- a/src/Shared/Resources/xlf/Strings.shared.pl.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.pl.xlf @@ -47,6 +47,16 @@ MSB6005: Zadanie podjęło próbę zarejestrowania przed zainicjowaniem. Pojawił się komunikat: {0} {StrBegin="MSB6005: "}UE: This occurs if the task attempts to log something in its own constructor. + + MSB3511: "{0}" is an invalid value for the "Importance" parameter. Valid values are: High, Normal and Low. + MSB3511: „{0}” jest nieprawidłową wartością parametru „Importance”. Prawidłowe wartości: High, Normal i Low. + {StrBegin="MSB3511: "}UE: This message is shown when a user specifies a value for the importance attribute of Message which is not valid. + The importance enumeration is: High, Normal and Low. Specifying any other importance will result in this message being shown + LOCALIZATION: "Importance" should not be localized. + High should not be localized. + Normal should not be localized. + Low should not be localized. + Making the following modifications to the environment received from the parent node before applying it to the task host: Wymienione zmiany otrzymane z węzła nadrzędnego zostaną wprowadzone w środowisku, a po sprawdzeniu działania zastosowane do hosta zadań: diff --git a/src/Shared/Resources/xlf/Strings.shared.pt-BR.xlf b/src/Shared/Resources/xlf/Strings.shared.pt-BR.xlf index a1d367b1034..cf78ee34200 100644 --- a/src/Shared/Resources/xlf/Strings.shared.pt-BR.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.pt-BR.xlf @@ -47,6 +47,16 @@ MSB6005: A tarefa tentou fazer o registro antes de ser inicializada. A mensagem era: {0} {StrBegin="MSB6005: "}UE: This occurs if the task attempts to log something in its own constructor. + + MSB3511: "{0}" is an invalid value for the "Importance" parameter. Valid values are: High, Normal and Low. + MSB3511: "{0}" é um valor inválido para o parâmetro "Importance". Os valores válidos são: High, Normal e Low. + {StrBegin="MSB3511: "}UE: This message is shown when a user specifies a value for the importance attribute of Message which is not valid. + The importance enumeration is: High, Normal and Low. Specifying any other importance will result in this message being shown + LOCALIZATION: "Importance" should not be localized. + High should not be localized. + Normal should not be localized. + Low should not be localized. + Making the following modifications to the environment received from the parent node before applying it to the task host: Fazendo as seguintes modificações no ambiente recebido do nó pai antes de aplicá-lo ao host de tarefas: diff --git a/src/Shared/Resources/xlf/Strings.shared.ru.xlf b/src/Shared/Resources/xlf/Strings.shared.ru.xlf index ef150da8523..a40cb28ad14 100644 --- a/src/Shared/Resources/xlf/Strings.shared.ru.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.ru.xlf @@ -47,6 +47,16 @@ MSB6005: задачей предпринята попытка вести журнал до своей инициализации. Сообщение: {0} {StrBegin="MSB6005: "}UE: This occurs if the task attempts to log something in its own constructor. + + MSB3511: "{0}" is an invalid value for the "Importance" parameter. Valid values are: High, Normal and Low. + MSB3511: "{0}" — недопустимое значение для параметра Importance (Важность). Допустимые значения: High (высокая), Normal (средняя) и Low (низкая). + {StrBegin="MSB3511: "}UE: This message is shown when a user specifies a value for the importance attribute of Message which is not valid. + The importance enumeration is: High, Normal and Low. Specifying any other importance will result in this message being shown + LOCALIZATION: "Importance" should not be localized. + High should not be localized. + Normal should not be localized. + Low should not be localized. + Making the following modifications to the environment received from the parent node before applying it to the task host: Перед применением окружения, полученного от родительского узла, к серверу задач в нем выполняются следующие изменения: diff --git a/src/Shared/Resources/xlf/Strings.shared.tr.xlf b/src/Shared/Resources/xlf/Strings.shared.tr.xlf index 258ccd203da..e5e76c65345 100644 --- a/src/Shared/Resources/xlf/Strings.shared.tr.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.tr.xlf @@ -47,6 +47,16 @@ MSB6005: Görev başlatılmadan önce günlüğe yazmaya çalıştı. İleti: {0} {StrBegin="MSB6005: "}UE: This occurs if the task attempts to log something in its own constructor. + + MSB3511: "{0}" is an invalid value for the "Importance" parameter. Valid values are: High, Normal and Low. + MSB3511: "{0}", "Importance" parametresi için geçersiz bir değer. Geçerli değerler şunlardır: High, Normal ve Low. + {StrBegin="MSB3511: "}UE: This message is shown when a user specifies a value for the importance attribute of Message which is not valid. + The importance enumeration is: High, Normal and Low. Specifying any other importance will result in this message being shown + LOCALIZATION: "Importance" should not be localized. + High should not be localized. + Normal should not be localized. + Low should not be localized. + Making the following modifications to the environment received from the parent node before applying it to the task host: Üst düğümden alınan ortam görev ana bilgisayarına uygulanmadan önce ortamda aşağıdaki değişiklikler yapılıyor: diff --git a/src/Shared/Resources/xlf/Strings.shared.zh-Hans.xlf b/src/Shared/Resources/xlf/Strings.shared.zh-Hans.xlf index f601e00a74b..f0540b9f5e0 100644 --- a/src/Shared/Resources/xlf/Strings.shared.zh-Hans.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.zh-Hans.xlf @@ -47,6 +47,16 @@ MSB6005: 任务尚未初始化就尝试进行日志记录。消息为: {0} {StrBegin="MSB6005: "}UE: This occurs if the task attempts to log something in its own constructor. + + MSB3511: "{0}" is an invalid value for the "Importance" parameter. Valid values are: High, Normal and Low. + MSB3511: “{0}”是无效的“Importance”参数值。有效值包括: High、Normal 和 Low。 + {StrBegin="MSB3511: "}UE: This message is shown when a user specifies a value for the importance attribute of Message which is not valid. + The importance enumeration is: High, Normal and Low. Specifying any other importance will result in this message being shown + LOCALIZATION: "Importance" should not be localized. + High should not be localized. + Normal should not be localized. + Low should not be localized. + Making the following modifications to the environment received from the parent node before applying it to the task host: 先对从父节点收到的环境进行以下修改,然后再将其应用于任务宿主: diff --git a/src/Shared/Resources/xlf/Strings.shared.zh-Hant.xlf b/src/Shared/Resources/xlf/Strings.shared.zh-Hant.xlf index 610cf607d85..a7e8187bddb 100644 --- a/src/Shared/Resources/xlf/Strings.shared.zh-Hant.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.zh-Hant.xlf @@ -47,6 +47,16 @@ MSB6005: 工作在初始化之前就嘗試記錄。訊息為: {0} {StrBegin="MSB6005: "}UE: This occurs if the task attempts to log something in its own constructor. + + MSB3511: "{0}" is an invalid value for the "Importance" parameter. Valid values are: High, Normal and Low. + MSB3511: "{0}" 對 "Importance" 參數而言為無效值。有效值為: High、Normal 和 Low。 + {StrBegin="MSB3511: "}UE: This message is shown when a user specifies a value for the importance attribute of Message which is not valid. + The importance enumeration is: High, Normal and Low. Specifying any other importance will result in this message being shown + LOCALIZATION: "Importance" should not be localized. + High should not be localized. + Normal should not be localized. + Low should not be localized. + Making the following modifications to the environment received from the parent node before applying it to the task host: 在套用到工作主機之前,對從父節點接收的環境進行下列修改: diff --git a/src/Shared/TaskFactoryUtilities.cs b/src/Shared/TaskFactoryUtilities.cs index 4d918f92bb3..67568c05c32 100644 --- a/src/Shared/TaskFactoryUtilities.cs +++ b/src/Shared/TaskFactoryUtilities.cs @@ -6,8 +6,10 @@ using System.Reflection; using Microsoft.Build.Shared.FileSystem; using Microsoft.Build.Framework; -#if NETFRAMEWORK -using Microsoft.IO; + +#if FEATURE_MSIOREDIST +using File = Microsoft.IO.File; +using Path = Microsoft.IO.Path; #else using System.IO; #endif diff --git a/src/Tasks.UnitTests/RoslynCodeTaskFactory_Tests.cs b/src/Tasks.UnitTests/RoslynCodeTaskFactory_Tests.cs index 0253b720504..d18a9effcf2 100644 --- a/src/Tasks.UnitTests/RoslynCodeTaskFactory_Tests.cs +++ b/src/Tasks.UnitTests/RoslynCodeTaskFactory_Tests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Reflection; using Microsoft.Build.Framework; @@ -10,10 +11,6 @@ using Microsoft.Build.UnitTests; using Microsoft.Build.UnitTests.Shared; using Microsoft.Build.Utilities; -using System.IO; -#if NETFRAMEWORK -using MicrosoftIO = Microsoft.IO; -#endif using Shouldly; using VerifyTests; using VerifyXunit; diff --git a/src/Tasks/Copy.cs b/src/Tasks/Copy.cs index a3adcf74260..c3899515a90 100644 --- a/src/Tasks/Copy.cs +++ b/src/Tasks/Copy.cs @@ -311,7 +311,7 @@ private void LogAlwaysRetryDiagnosticFromResources(string messageResourceName, p if (FailIfNotIncremental) { - // Before the introduction of AbsolutePath, this logged full paths, so preserve that behavior + // Before the introduction of AbsolutePath, this logged full paths, so preserve that behavior Log.LogError(FileComment, sourceFileState.Path, destinationFileState.Path); return false; } @@ -508,8 +508,19 @@ private bool CopySingleThreaded( string destSpec = DestinationFiles[i].ItemSpec; // Compute absolute paths once - reused for ETW, deduplication dictionary, and FileState - AbsolutePath sourceAbsolutePath = TaskEnvironment.GetAbsolutePath(sourceSpec); - AbsolutePath destAbsolutePath = TaskEnvironment.GetAbsolutePath(destSpec); + AbsolutePath sourceAbsolutePath; + AbsolutePath destAbsolutePath; + try + { + sourceAbsolutePath = TaskEnvironment.GetAbsolutePath(sourceSpec); + destAbsolutePath = TaskEnvironment.GetAbsolutePath(destSpec); + } + catch (ArgumentException ex) + { + Log.LogErrorWithCodeFromResources("Copy.Error", sourceSpec, destSpec, ex.Message); + success = false; + continue; + } MSBuildEventSource.Log.CopyUpToDateStart(destAbsolutePath); if (filesActuallyCopied.TryGetValue(destAbsolutePath, out string originalSource)) @@ -663,9 +674,20 @@ void ProcessPartition() string destSpec = destItem.ItemSpec; // Compute absolute paths once - reused for ETW, deduplication check, and FileState - AbsolutePath sourceAbsolutePath = TaskEnvironment.GetAbsolutePath(sourceSpec); - AbsolutePath destAbsolutePath = TaskEnvironment.GetAbsolutePath(destSpec); - + AbsolutePath sourceAbsolutePath; + AbsolutePath destAbsolutePath; + try + { + sourceAbsolutePath = TaskEnvironment.GetAbsolutePath(sourceSpec); + destAbsolutePath = TaskEnvironment.GetAbsolutePath(destSpec); + } + catch (ArgumentException ex) + { + Log.LogErrorWithCodeFromResources("Copy.Error", sourceSpec, destSpec, ex.Message); + success = false; + continue; + } + // Check if we just copied from this location to the destination, don't copy again. MSBuildEventSource.Log.CopyUpToDateStart(destAbsolutePath); bool copyComplete = false; diff --git a/src/Tasks/Resources/Strings.resx b/src/Tasks/Resources/Strings.resx index 6f7e57ec2e6..83cd450921b 100644 --- a/src/Tasks/Resources/Strings.resx +++ b/src/Tasks/Resources/Strings.resx @@ -1221,15 +1221,7 @@ If this bucket overflows, pls. contact 'vsppbdev'. --> - - MSB3511: "{0}" is an invalid value for the "Importance" parameter. Valid values are: High, Normal and Low. - {StrBegin="MSB3511: "}UE: This message is shown when a user specifies a value for the importance attribute of Message which is not valid. - The importance enumeration is: High, Normal and Low. Specifying any other importance will result in this message being shown - LOCALIZATION: "Importance" should not be localized. - High should not be localized. - Normal should not be localized. - Low should not be localized. - +