Merge branch 'main' into merge/vs17.9-to-main

This commit is contained in:
AR-May 2023-12-21 14:03:32 +01:00 коммит произвёл GitHub
Родитель e514b5973d 5cf78584f9
Коммит 5a3f2030de
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
42 изменённых файлов: 867 добавлений и 99 удалений

Просмотреть файл

@ -96,9 +96,8 @@ stages:
value: Products/$(System.TeamProject)/$(Build.Repository.Name)/$(Build.SourceBranchName)/$(Build.BuildNumber)
steps:
- task: NuGetToolInstaller@0
inputs:
versionSpec: '4.9.2'
- task: NuGetToolInstaller@1
displayName: 'Install NuGet.exe'
- task: NuGetCommand@2
displayName: Restore internal tools

Просмотреть файл

@ -30,6 +30,7 @@ A wave of features is set to "rotate out" (i.e. become standard functionality) t
- [Target parameters will be unquoted](https://github.com/dotnet/msbuild/pull/9452), meaning the ';' symbol in the parameter target name will always be treated as separator
- [Change Version switch output to finish with a newline](https://github.com/dotnet/msbuild/pull/9485)
- [Load Microsoft.DotNet.MSBuildSdkResolver into default load context (MSBuild.exe only)](https://github.com/dotnet/msbuild/pull/9439)
- [Load NuGet.Frameworks into secondary AppDomain (MSBuild.exe only)](https://github.com/dotnet/msbuild/pull/9446)
### 17.8
- [[RAR] Don't do I/O on SDK-provided references](https://github.com/dotnet/msbuild/pull/8688)

Просмотреть файл

@ -3,8 +3,6 @@
<UsageData>
<IgnorePatterns>
<UsagePattern IdentityGlob="Microsoft.SourceBuild.Intermediate.*/*" />
<!-- Baseline 7.0 dependencies until msbuild targets net8 and uses a net8 arcade, SBRP, etc.
These cannot be added to 7.0 SBRP, because they would are produced in the 7.0 build. -->
<UsagePattern IdentityGlob="System.Collections.Immutable/*8.0.0*" />

Просмотреть файл

@ -2,7 +2,7 @@
<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the MIT license. See License.txt in the project root for full license information. -->
<Project>
<PropertyGroup>
<VersionPrefix>17.9.0</VersionPrefix><DotNetFinalVersionKind>release</DotNetFinalVersionKind>
<VersionPrefix>17.10.0</VersionPrefix>
<PackageValidationBaselineVersion>17.8.3</PackageValidationBaselineVersion>
<AssemblyVersion>15.1.0.0</AssemblyVersion>
<PreReleaseVersionLabel>preview</PreReleaseVersionLabel>

Просмотреть файл

@ -557,6 +557,7 @@ namespace Microsoft.Build.Engine.UnitTests.ProjectCache
currentBuildEnvironment.Mode,
currentBuildEnvironment.CurrentMSBuildExePath,
currentBuildEnvironment.RunningTests,
currentBuildEnvironment.RunningInMSBuildExe,
runningInVisualStudio: true,
visualStudioPath: currentBuildEnvironment.VisualStudioInstallRootDirectory));
@ -674,6 +675,7 @@ namespace Microsoft.Build.Engine.UnitTests.ProjectCache
currentBuildEnvironment.Mode,
currentBuildEnvironment.CurrentMSBuildExePath,
currentBuildEnvironment.RunningTests,
currentBuildEnvironment.RunningInMSBuildExe,
runningInVisualStudio: true,
visualStudioPath: currentBuildEnvironment.VisualStudioInstallRootDirectory));
@ -1440,6 +1442,7 @@ namespace Microsoft.Build.Engine.UnitTests.ProjectCache
currentBuildEnvironment.Mode,
currentBuildEnvironment.CurrentMSBuildExePath,
currentBuildEnvironment.RunningTests,
currentBuildEnvironment.RunningInMSBuildExe,
runningInVisualStudio: true,
visualStudioPath: currentBuildEnvironment.VisualStudioInstallRootDirectory));

Просмотреть файл

@ -35,7 +35,7 @@ namespace Microsoft.Build.Evaluation
private static readonly Lazy<Regex> RegistrySdkRegex = new Lazy<Regex>(() => new Regex(@"^HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Microsoft SDKs\\Windows\\v(\d+\.\d+)$", RegexOptions.IgnoreCase));
private static readonly Lazy<NuGetFrameworkWrapper> NuGetFramework = new Lazy<NuGetFrameworkWrapper>(() => new NuGetFrameworkWrapper());
private static readonly Lazy<NuGetFrameworkWrapper> NuGetFramework = new Lazy<NuGetFrameworkWrapper>(() => NuGetFrameworkWrapper.CreateInstance());
/// <summary>
/// Add two doubles

Просмотреть файл

@ -670,4 +670,54 @@
<LogicalName>$(AssemblyName).Strings.shared.resources</LogicalName>
</EmbeddedResource>
</ItemGroup>
<PropertyGroup>
<NuGetFrameworkWrapperRedirects_FilePath>$(IntermediateOutputPath)NuGetFrameworkWrapper.redirects.cs</NuGetFrameworkWrapperRedirects_FilePath>
</PropertyGroup>
<!-- Extract binding redirects for Microsoft.Build from MSBuild.exe.config into a source file -->
<Target Name="GenerateAppDomainConfig"
Inputs="..\MSBuild\app.config;..\MSBuild\app.amd64.config"
Outputs="$(NuGetFrameworkWrapperRedirects_FilePath)"
BeforeTargets="CoreCompile"
Condition="'$(FeatureAppDomain)' == 'true'">
<PropertyGroup>
<BindingRedirectNamespace>&lt;Namespace Prefix='ns' Uri='urn:schemas-microsoft-com:asm.v1' /&gt;</BindingRedirectNamespace>
<BindingRedirectXPath>/configuration/runtime/ns:assemblyBinding/ns:dependentAssembly[ns:assemblyIdentity/@name='Microsoft.Build']</BindingRedirectXPath>
</PropertyGroup>
<XmlPeek XmlInputPath="..\MSBuild\app.config" Query="$(BindingRedirectXPath)" Namespaces="$(BindingRedirectNamespace)">
<Output TaskParameter="Result" ItemName="BindingRedirect32" />
</XmlPeek>
<XmlPeek XmlInputPath="..\MSBuild\app.amd64.config" Query="$(BindingRedirectXPath)" Namespaces="$(BindingRedirectNamespace)">
<Output TaskParameter="Result" ItemName="BindingRedirect64" />
</XmlPeek>
<PropertyGroup>
<NuGetFrameworkWrapperRedirects_Content><![CDATA[
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace Microsoft.Build.Evaluation%3B;
[System.CodeDom.Compiler.GeneratedCode("GenerateAppDomainConfig", "1.0")]
internal sealed partial class NuGetFrameworkWrapper
{
private const string _bindingRedirect32 = """;@(BindingRedirect32);"""%3B;
private const string _bindingRedirect64 = """;@(BindingRedirect64);"""%3B;
}
]]>
</NuGetFrameworkWrapperRedirects_Content>
</PropertyGroup>
<WriteLinesToFile File="$(NuGetFrameworkWrapperRedirects_FilePath)" Overwrite="true" WriteOnlyWhenDifferent="true" Lines="$(NuGetFrameworkWrapperRedirects_Content)" />
<ItemGroup>
<Compile Remove="$(NuGetFrameworkWrapperRedirects_FilePath)" />
<Compile Include="$(NuGetFrameworkWrapperRedirects_FilePath)">
<Link>Utilities\NuGetFrameworkWrapper.redirects.cs</Link>
</Compile>
<FileWrites Include="$(NuGetFrameworkWrapperRedirects_FilePath)" />
</ItemGroup>
</Target>
</Project>

Просмотреть файл

@ -1889,7 +1889,7 @@ Utilization: {0} Average Utilization: {1:###.0}</value>
<value>Property initial value: $({0})="{1}" Source: {2}</value>
</data>
<data name="NuGetAssemblyNotFound" xml:space="preserve">
<value>A required NuGet assembly was not found. Expected Path: {0}</value>
<value>A required NuGet assembly '{0}' could not be loaded.</value>
</data>
<data name="StaticGraphConstructionMetrics" xml:space="preserve">
<value>Static graph loaded in {0} seconds: {1} nodes, {2} edges</value>

4
src/Build/Resources/xlf/Strings.cs.xlf сгенерированный
Просмотреть файл

@ -222,8 +222,8 @@
<note />
</trans-unit>
<trans-unit id="NuGetAssemblyNotFound">
<source>A required NuGet assembly was not found. Expected Path: {0}</source>
<target state="translated">Požadované sestavení NuGet se nenašlo. Očekávaná cesta: {0}</target>
<source>A required NuGet assembly '{0}' could not be loaded.</source>
<target state="needs-review-translation">Požadované sestavení NuGet se nenašlo. Očekávaná cesta: {0}</target>
<note />
</trans-unit>
<trans-unit id="NullReferenceFromProjectInstanceFactory">

4
src/Build/Resources/xlf/Strings.de.xlf сгенерированный
Просмотреть файл

@ -222,8 +222,8 @@
<note />
</trans-unit>
<trans-unit id="NuGetAssemblyNotFound">
<source>A required NuGet assembly was not found. Expected Path: {0}</source>
<target state="translated">Eine erforderliche NuGet-Assembly wurde nicht gefunden. Erwarteter Pfad: {0}</target>
<source>A required NuGet assembly '{0}' could not be loaded.</source>
<target state="needs-review-translation">Eine erforderliche NuGet-Assembly wurde nicht gefunden. Erwarteter Pfad: {0}</target>
<note />
</trans-unit>
<trans-unit id="NullReferenceFromProjectInstanceFactory">

4
src/Build/Resources/xlf/Strings.es.xlf сгенерированный
Просмотреть файл

@ -222,8 +222,8 @@
<note />
</trans-unit>
<trans-unit id="NuGetAssemblyNotFound">
<source>A required NuGet assembly was not found. Expected Path: {0}</source>
<target state="translated">No se encontró un ensamblado de NuGet necesario. Ruta de acceso esperada: {0}</target>
<source>A required NuGet assembly '{0}' could not be loaded.</source>
<target state="needs-review-translation">No se encontró un ensamblado de NuGet necesario. Ruta de acceso esperada: {0}</target>
<note />
</trans-unit>
<trans-unit id="NullReferenceFromProjectInstanceFactory">

4
src/Build/Resources/xlf/Strings.fr.xlf сгенерированный
Просмотреть файл

@ -222,8 +222,8 @@
<note />
</trans-unit>
<trans-unit id="NuGetAssemblyNotFound">
<source>A required NuGet assembly was not found. Expected Path: {0}</source>
<target state="translated">Un assembly NuGet obligatoire est introuvable. Chemin attendu : {0}</target>
<source>A required NuGet assembly '{0}' could not be loaded.</source>
<target state="needs-review-translation">Un assembly NuGet obligatoire est introuvable. Chemin attendu : {0}</target>
<note />
</trans-unit>
<trans-unit id="NullReferenceFromProjectInstanceFactory">

4
src/Build/Resources/xlf/Strings.it.xlf сгенерированный
Просмотреть файл

@ -222,8 +222,8 @@
<note />
</trans-unit>
<trans-unit id="NuGetAssemblyNotFound">
<source>A required NuGet assembly was not found. Expected Path: {0}</source>
<target state="translated">Un assembly NuGet obbligatorio non è stato trovato. Percorso previsto: {0}</target>
<source>A required NuGet assembly '{0}' could not be loaded.</source>
<target state="needs-review-translation">Un assembly NuGet obbligatorio non è stato trovato. Percorso previsto: {0}</target>
<note />
</trans-unit>
<trans-unit id="NullReferenceFromProjectInstanceFactory">

4
src/Build/Resources/xlf/Strings.ja.xlf сгенерированный
Просмотреть файл

@ -222,8 +222,8 @@
<note />
</trans-unit>
<trans-unit id="NuGetAssemblyNotFound">
<source>A required NuGet assembly was not found. Expected Path: {0}</source>
<target state="translated">必要な NuGet アセンブリが見つかりませんでした。想定されるパス: {0}</target>
<source>A required NuGet assembly '{0}' could not be loaded.</source>
<target state="needs-review-translation">必要な NuGet アセンブリが見つかりませんでした。想定されるパス: {0}</target>
<note />
</trans-unit>
<trans-unit id="NullReferenceFromProjectInstanceFactory">

4
src/Build/Resources/xlf/Strings.ko.xlf сгенерированный
Просмотреть файл

@ -222,8 +222,8 @@
<note />
</trans-unit>
<trans-unit id="NuGetAssemblyNotFound">
<source>A required NuGet assembly was not found. Expected Path: {0}</source>
<target state="translated">필요한 NuGet 어셈블리를 찾을 수 없습니다. 예상 경로: {0}</target>
<source>A required NuGet assembly '{0}' could not be loaded.</source>
<target state="needs-review-translation">필요한 NuGet 어셈블리를 찾을 수 없습니다. 예상 경로: {0}</target>
<note />
</trans-unit>
<trans-unit id="NullReferenceFromProjectInstanceFactory">

4
src/Build/Resources/xlf/Strings.pl.xlf сгенерированный
Просмотреть файл

@ -222,8 +222,8 @@
<note />
</trans-unit>
<trans-unit id="NuGetAssemblyNotFound">
<source>A required NuGet assembly was not found. Expected Path: {0}</source>
<target state="translated">Nie znaleziono wymaganego zestawu NuGet. Oczekiwano ścieżki {0}.</target>
<source>A required NuGet assembly '{0}' could not be loaded.</source>
<target state="needs-review-translation">Nie znaleziono wymaganego zestawu NuGet. Oczekiwano ścieżki {0}.</target>
<note />
</trans-unit>
<trans-unit id="NullReferenceFromProjectInstanceFactory">

4
src/Build/Resources/xlf/Strings.pt-BR.xlf сгенерированный
Просмотреть файл

@ -222,8 +222,8 @@
<note />
</trans-unit>
<trans-unit id="NuGetAssemblyNotFound">
<source>A required NuGet assembly was not found. Expected Path: {0}</source>
<target state="translated">Um assembly NuGet necessário não foi encontrado. Caminho Esperado: {0}</target>
<source>A required NuGet assembly '{0}' could not be loaded.</source>
<target state="needs-review-translation">Um assembly NuGet necessário não foi encontrado. Caminho Esperado: {0}</target>
<note />
</trans-unit>
<trans-unit id="NullReferenceFromProjectInstanceFactory">

4
src/Build/Resources/xlf/Strings.ru.xlf сгенерированный
Просмотреть файл

@ -222,8 +222,8 @@
<note />
</trans-unit>
<trans-unit id="NuGetAssemblyNotFound">
<source>A required NuGet assembly was not found. Expected Path: {0}</source>
<target state="translated">Не найдена обязательная сборка NuGet. Ожидаемый путь: {0}</target>
<source>A required NuGet assembly '{0}' could not be loaded.</source>
<target state="needs-review-translation">Не найдена обязательная сборка NuGet. Ожидаемый путь: {0}</target>
<note />
</trans-unit>
<trans-unit id="NullReferenceFromProjectInstanceFactory">

4
src/Build/Resources/xlf/Strings.tr.xlf сгенерированный
Просмотреть файл

@ -222,8 +222,8 @@
<note />
</trans-unit>
<trans-unit id="NuGetAssemblyNotFound">
<source>A required NuGet assembly was not found. Expected Path: {0}</source>
<target state="translated">Gereken NuGet derlemesi bulunamadı. Beklenen Yol: {0}</target>
<source>A required NuGet assembly '{0}' could not be loaded.</source>
<target state="needs-review-translation">Gereken NuGet derlemesi bulunamadı. Beklenen Yol: {0}</target>
<note />
</trans-unit>
<trans-unit id="NullReferenceFromProjectInstanceFactory">

4
src/Build/Resources/xlf/Strings.zh-Hans.xlf сгенерированный
Просмотреть файл

@ -222,8 +222,8 @@
<note />
</trans-unit>
<trans-unit id="NuGetAssemblyNotFound">
<source>A required NuGet assembly was not found. Expected Path: {0}</source>
<target state="translated">未找到所需的 NuGet 程序集。所需路径: {0}</target>
<source>A required NuGet assembly '{0}' could not be loaded.</source>
<target state="needs-review-translation">未找到所需的 NuGet 程序集。所需路径: {0}</target>
<note />
</trans-unit>
<trans-unit id="NullReferenceFromProjectInstanceFactory">

4
src/Build/Resources/xlf/Strings.zh-Hant.xlf сгенерированный
Просмотреть файл

@ -222,8 +222,8 @@
<note />
</trans-unit>
<trans-unit id="NuGetAssemblyNotFound">
<source>A required NuGet assembly was not found. Expected Path: {0}</source>
<target state="translated">找不到必要的 NuGet 元件。預期的路徑: {0}</target>
<source>A required NuGet assembly '{0}' could not be loaded.</source>
<target state="needs-review-translation">找不到必要的 NuGet 元件。預期的路徑: {0}</target>
<note />
</trans-unit>
<trans-unit id="NullReferenceFromProjectInstanceFactory">

Просмотреть файл

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
@ -16,47 +17,63 @@ using Microsoft.Build.Shared;
namespace Microsoft.Build.Evaluation
{
/// <summary>
/// Wraps the NuGet.Frameworks assembly, which is referenced by reflection.
/// Wraps the NuGet.Frameworks assembly, which is referenced by reflection and optionally loaded into a separate AppDomain for performance.
/// </summary>
internal class NuGetFrameworkWrapper
internal sealed partial class NuGetFrameworkWrapper
#if FEATURE_APPDOMAIN
: MarshalByRefObject
#endif
{
/// <summary>
/// NuGet Types
/// </summary>
private static MethodInfo ParseMethod;
private static MethodInfo IsCompatibleMethod;
private static object DefaultCompatibilityProvider;
private static PropertyInfo FrameworkProperty;
private static PropertyInfo VersionProperty;
private static PropertyInfo PlatformProperty;
private static PropertyInfo PlatformVersionProperty;
private static PropertyInfo AllFrameworkVersionsProperty;
private const string NuGetFrameworksAssemblyName = "NuGet.Frameworks";
private const string NuGetFrameworksFileName = NuGetFrameworksAssemblyName + ".dll";
/// <summary>
/// Methods, properties, and objects used from the NuGet.Frameworks assembly.
/// </summary>
private MethodInfo ParseMethod;
private MethodInfo IsCompatibleMethod;
private object DefaultCompatibilityProvider;
private PropertyInfo FrameworkProperty;
private PropertyInfo VersionProperty;
private PropertyInfo PlatformProperty;
private PropertyInfo PlatformVersionProperty;
private PropertyInfo AllFrameworkVersionsProperty;
/// <summary>
/// Public constructor for cross-domain activation only. Use <see cref="CreateInstance"/> to instantiate.
/// </summary>
public NuGetFrameworkWrapper()
{ }
/// <summary>
/// Initialized this instance. May run in a separate AppDomain.
/// </summary>
/// <param name="assemblyName">The NuGet.Frameworks to be loaded or null to load by path.</param>
/// <param name="assemblyFilePath">The file path from which NuGet.Frameworks should be loaded of <paramref name="assemblyName"/> is null.</param>
public void Initialize(AssemblyName assemblyName, string assemblyFilePath)
{
// Resolve the location of the NuGet.Frameworks assembly
var assemblyDirectory = BuildEnvironmentHelper.Instance.Mode == BuildEnvironmentMode.VisualStudio ?
Path.Combine(BuildEnvironmentHelper.Instance.VisualStudioInstallRootDirectory, "Common7", "IDE", "CommonExtensions", "Microsoft", "NuGet") :
BuildEnvironmentHelper.Instance.CurrentMSBuildToolsDirectory;
try
Assembly NuGetAssembly;
if (assemblyName != null)
{
var NuGetAssembly = Assembly.LoadFile(Path.Combine(assemblyDirectory, "NuGet.Frameworks.dll"));
var NuGetFramework = NuGetAssembly.GetType("NuGet.Frameworks.NuGetFramework");
var NuGetFrameworkCompatibilityProvider = NuGetAssembly.GetType("NuGet.Frameworks.CompatibilityProvider");
var NuGetFrameworkDefaultCompatibilityProvider = NuGetAssembly.GetType("NuGet.Frameworks.DefaultCompatibilityProvider");
ParseMethod = NuGetFramework.GetMethod("Parse", new Type[] { typeof(string) });
IsCompatibleMethod = NuGetFrameworkCompatibilityProvider.GetMethod("IsCompatible");
DefaultCompatibilityProvider = NuGetFrameworkDefaultCompatibilityProvider.GetMethod("get_Instance").Invoke(null, Array.Empty<object>());
FrameworkProperty = NuGetFramework.GetProperty("Framework");
VersionProperty = NuGetFramework.GetProperty("Version");
PlatformProperty = NuGetFramework.GetProperty("Platform");
PlatformVersionProperty = NuGetFramework.GetProperty("PlatformVersion");
AllFrameworkVersionsProperty = NuGetFramework.GetProperty("AllFrameworkVersions");
// This will load the assembly into the default load context if possible, and fall back to LoadFrom context.
NuGetAssembly = Assembly.Load(assemblyName);
}
catch
else
{
throw new InternalErrorException(string.Format(AssemblyResources.GetString("NuGetAssemblyNotFound"), assemblyDirectory));
NuGetAssembly = Assembly.LoadFile(assemblyFilePath);
}
var NuGetFramework = NuGetAssembly.GetType("NuGet.Frameworks.NuGetFramework");
var NuGetFrameworkCompatibilityProvider = NuGetAssembly.GetType("NuGet.Frameworks.CompatibilityProvider");
var NuGetFrameworkDefaultCompatibilityProvider = NuGetAssembly.GetType("NuGet.Frameworks.DefaultCompatibilityProvider");
ParseMethod = NuGetFramework.GetMethod("Parse", new Type[] { typeof(string) });
IsCompatibleMethod = NuGetFrameworkCompatibilityProvider.GetMethod("IsCompatible");
DefaultCompatibilityProvider = NuGetFrameworkDefaultCompatibilityProvider.GetMethod("get_Instance").Invoke(null, Array.Empty<object>());
FrameworkProperty = NuGetFramework.GetProperty("Framework");
VersionProperty = NuGetFramework.GetProperty("Version");
PlatformProperty = NuGetFramework.GetProperty("Platform");
PlatformVersionProperty = NuGetFramework.GetProperty("PlatformVersion");
AllFrameworkVersionsProperty = NuGetFramework.GetProperty("AllFrameworkVersions");
}
private object Parse(string tfm)
@ -133,5 +150,99 @@ namespace Microsoft.Build.Evaluation
});
}
}
#if FEATURE_APPDOMAIN
/// <summary>
/// A null-returning InitializeLifetimeService to give the proxy an infinite lease time.
/// </summary>
public override object InitializeLifetimeService() => null;
private static AppDomainSetup CreateAppDomainSetup(AssemblyName assemblyName, string assemblyPath)
{
byte[] publicKeyToken = assemblyName.GetPublicKeyToken();
StringBuilder publicKeyTokenString = new(publicKeyToken.Length * 2);
for (int i = 0; i < publicKeyToken.Length; i++)
{
publicKeyTokenString.Append(publicKeyToken[i].ToString("x2", CultureInfo.InvariantCulture));
}
// Create an app.config for the AppDomain. We expect the AD to host the currently executing assembly Microsoft.Build,
// NuGet.Frameworks, and Framework assemblies. It is important to use the same binding redirects that were used when
// NGENing MSBuild for the native images to be used.
string configuration = $"""
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<DisableFXClosureWalk enabled="true" />
<DeferFXClosureWalk enabled="true" />
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
{(Environment.Is64BitProcess ? _bindingRedirect64 : _bindingRedirect32)}
<dependentAssembly>
<assemblyIdentity name="{NuGetFrameworksAssemblyName}" publicKeyToken="{publicKeyTokenString}" culture="{assemblyName.CultureName}" />
<codeBase version="{assemblyName.Version}" href="{assemblyPath}" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
""";
AppDomainSetup appDomainSetup = AppDomain.CurrentDomain.SetupInformation;
appDomainSetup.SetConfigurationBytes(Encoding.UTF8.GetBytes(configuration));
return appDomainSetup;
}
#endif
public static NuGetFrameworkWrapper CreateInstance()
{
// Resolve the location of the NuGet.Frameworks assembly
string assemblyDirectory = BuildEnvironmentHelper.Instance.Mode == BuildEnvironmentMode.VisualStudio ?
Path.Combine(BuildEnvironmentHelper.Instance.VisualStudioInstallRootDirectory, "Common7", "IDE", "CommonExtensions", "Microsoft", "NuGet") :
BuildEnvironmentHelper.Instance.CurrentMSBuildToolsDirectory;
string assemblyPath = Path.Combine(assemblyDirectory, NuGetFrameworksFileName);
NuGetFrameworkWrapper instance = null;
AssemblyName assemblyName = null;
#if FEATURE_APPDOMAIN
if (ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_10) &&
(BuildEnvironmentHelper.Instance.RunningInMSBuildExe || BuildEnvironmentHelper.Instance.RunningInVisualStudio))
{
// If we are running in MSBuild.exe or VS, we can load the assembly with Assembly.Load, which enables
// the runtime to bind to the native image, eliminating some non-trivial JITting cost. Devenv.exe knows how to
// load the assembly by name. In MSBuild.exe, however, we don't know the version of the assembly statically so
// we create a separate AppDomain with the right binding redirects.
try
{
assemblyName = AssemblyName.GetAssemblyName(assemblyPath);
if (assemblyName != null && BuildEnvironmentHelper.Instance.RunningInMSBuildExe)
{
AppDomainSetup appDomainSetup = CreateAppDomainSetup(assemblyName, assemblyPath);
if (appDomainSetup != null)
{
AppDomain appDomain = AppDomain.CreateDomain(nameof(NuGetFrameworkWrapper), null, appDomainSetup);
instance = (NuGetFrameworkWrapper)appDomain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, typeof(NuGetFrameworkWrapper).FullName);
}
}
}
catch
{
// If anything goes wrong just fall back to loading into current AD by path.
instance = null;
assemblyName = null;
}
}
#endif
try
{
instance ??= new NuGetFrameworkWrapper();
instance.Initialize(assemblyName, assemblyPath);
return instance;
}
catch (Exception ex)
{
throw new InternalErrorException(string.Format(AssemblyResources.GetString("NuGetAssemblyNotFound"), assemblyDirectory), ex);
}
}
}
}

Просмотреть файл

@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Diagnostics;
using System.Linq;
#nullable disable
@ -22,7 +23,7 @@ namespace Microsoft.Build.Framework
/// </summary>
/// See docs here: https://github.com/dotnet/msbuild/blob/main/documentation/wiki/ChangeWaves.md
/// For dev docs: https://github.com/dotnet/msbuild/blob/main/documentation/wiki/ChangeWaves-Dev.md
internal class ChangeWaves
internal static class ChangeWaves
{
internal static readonly Version Wave17_4 = new Version(17, 4);
internal static readonly Version Wave17_6 = new Version(17, 6);
@ -35,6 +36,13 @@ namespace Microsoft.Build.Framework
/// </summary>
internal static readonly Version EnableAllFeatures = new Version(999, 999);
#if DEBUG
/// <summary>
/// True if <see cref="ResetStateForTests"/> has been called.
/// </summary>
private static bool _runningTests = false;
#endif
/// <summary>
/// The lowest wave in the current rotation of Change Waves.
/// </summary>
@ -162,6 +170,10 @@ namespace Microsoft.Build.Framework
{
ApplyChangeWave();
#if DEBUG
Debug.Assert(_runningTests || AllWaves.Contains(wave), $"Change wave version {wave} is invalid");
#endif
return wave < _cachedWave;
}
@ -171,6 +183,9 @@ namespace Microsoft.Build.Framework
/// </summary>
internal static void ResetStateForTests()
{
#if DEBUG
_runningTests = true;
#endif
_cachedWave = null;
_state = ChangeWaveConversionState.NotConvertedYet;
}

Просмотреть файл

@ -211,9 +211,15 @@ namespace Microsoft.Build.CommandLine
/// MSBuild no longer runs any arbitrary code (tasks or loggers) on the main thread, so it never needs the
/// main thread to be in an STA. Accordingly, to avoid ambiguity, we explicitly use the [MTAThread] attribute.
/// This doesn't actually do any work unless COM interop occurs for some reason.
/// We use the MultiDomainHost loader policy because we may create secondary AppDomains and need NGEN images
/// for Framework / GACed assemblies to be loaded domain neutral so their native images can be used.
/// See <see cref="NuGetFrameworkWrapper"/>.
/// </remarks>
/// <returns>0 on success, 1 on failure</returns>
[MTAThread]
#if FEATURE_APPDOMAIN
[LoaderOptimization(LoaderOptimization.MultiDomainHost)]
#endif
#pragma warning disable SA1111, SA1009 // Closing parenthesis should be on line of last parameter
public static int Main(
#if !FEATURE_GET_COMMANDLINE

Просмотреть файл

@ -120,6 +120,7 @@ namespace Microsoft.Build.Shared
BuildEnvironmentMode.None,
msbuildExePath,
runningTests: s_runningTests(),
runningInMSBuildExe: false,
runningInVisualStudio: false,
visualStudioPath: null);
}
@ -153,6 +154,7 @@ namespace Microsoft.Build.Shared
BuildEnvironmentMode.VisualStudio,
msBuildExe,
runningTests: false,
runningInMSBuildExe: false,
runningInVisualStudio: true,
visualStudioPath: vsRoot);
}
@ -173,6 +175,7 @@ namespace Microsoft.Build.Shared
BuildEnvironmentMode.VisualStudio,
msBuildExe,
runningTests: false,
runningInMSBuildExe: true,
runningInVisualStudio: false,
visualStudioPath: GetVsRootFromMSBuildAssembly(msBuildExe));
}
@ -182,6 +185,7 @@ namespace Microsoft.Build.Shared
BuildEnvironmentMode.Standalone,
msBuildExe,
runningTests: false,
runningInMSBuildExe: true,
runningInVisualStudio: false,
visualStudioPath: null);
}
@ -223,6 +227,7 @@ namespace Microsoft.Build.Shared
BuildEnvironmentMode.Standalone,
msBuildPath,
runningTests: s_runningTests(),
runningInMSBuildExe: false,
runningInVisualStudio: false,
visualStudioPath: null);
}
@ -244,6 +249,7 @@ namespace Microsoft.Build.Shared
BuildEnvironmentMode.VisualStudio,
GetMSBuildExeFromVsRoot(visualStudioRoot),
runningTests: s_runningTests(),
runningInMSBuildExe: false,
runningInVisualStudio: false,
visualStudioPath: visualStudioRoot);
}
@ -274,6 +280,7 @@ namespace Microsoft.Build.Shared
BuildEnvironmentMode.VisualStudio,
GetMSBuildExeFromVsRoot(vsInstallDir),
runningTests: false,
runningInMSBuildExe: false,
runningInVisualStudio: false,
visualStudioPath: vsInstallDir);
}
@ -306,6 +313,7 @@ namespace Microsoft.Build.Shared
BuildEnvironmentMode.VisualStudio,
GetMSBuildExeFromVsRoot(instances[0].Path),
runningTests: false,
runningInMSBuildExe: false,
runningInVisualStudio: false,
visualStudioPath: instances[0].Path);
}
@ -338,6 +346,7 @@ namespace Microsoft.Build.Shared
BuildEnvironmentMode.Standalone,
msBuildExePath,
runningTests: s_runningTests(),
runningInMSBuildExe: false,
runningInVisualStudio: false,
visualStudioPath: null);
}
@ -527,13 +536,15 @@ namespace Microsoft.Build.Shared
/// </summary>
internal sealed class BuildEnvironment
{
public BuildEnvironment(BuildEnvironmentMode mode, string currentMSBuildExePath, bool runningTests, bool runningInVisualStudio, string visualStudioPath)
public BuildEnvironment(BuildEnvironmentMode mode, string currentMSBuildExePath, bool runningTests, bool runningInMSBuildExe, bool runningInVisualStudio,
string visualStudioPath)
{
FileInfo currentMSBuildExeFile = null;
DirectoryInfo currentToolsDirectory = null;
Mode = mode;
RunningTests = runningTests;
RunningInMSBuildExe = runningInMSBuildExe;
RunningInVisualStudio = runningInVisualStudio;
CurrentMSBuildExePath = currentMSBuildExePath;
VisualStudioInstallRootDirectory = visualStudioPath;
@ -620,6 +631,11 @@ namespace Microsoft.Build.Shared
/// </summary>
internal bool RunningTests { get; }
/// <summary>
/// Returns true when the entry point application is MSBuild.exe.
/// </summary>
internal bool RunningInMSBuildExe { get; }
/// <summary>
/// Returns true when the entry point application is Visual Studio.
/// </summary>

Просмотреть файл

@ -16,10 +16,11 @@ using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
using Microsoft.Build.Tasks;
using Microsoft.Build.Utilities;
using Shouldly;
using Xunit;
using Xunit.Abstractions;
using Xunit.NetCore.Extensions;
#nullable disable
@ -27,6 +28,20 @@ namespace Microsoft.Build.UnitTests
{
public class Copy_Tests : IDisposable
{
public static IEnumerable<object[]> GetDestinationExists() =>
new List<object[]>
{
new object[] { true },
new object[] { false },
};
public static IEnumerable<object[]> GetNullAndEmptyArrays() =>
new List<object[]>
{
new object[] { null },
new object[] { Array.Empty<ITaskItem>() },
};
/// <summary>
/// Gets data for testing with combinations of isUseHardLinks and isUseSymbolicLinks.
/// Index 0 is the value for isUseHardLinks.
@ -112,6 +127,356 @@ namespace Microsoft.Build.UnitTests
Copy.RefreshInternalEnvironmentValues();
}
[Fact]
public void CopyWithNoInput()
{
var task = new Copy { BuildEngine = new MockEngine(true), };
task.Execute().ShouldBeTrue();
(task.CopiedFiles == null || task.CopiedFiles.Length == 0).ShouldBeTrue();
(task.DestinationFiles == null || task.DestinationFiles.Length == 0).ShouldBeTrue();
task.WroteAtLeastOneFile.ShouldBeFalse();
}
[Fact]
public void CopyWithMatchingSourceFilesToDestinationFiles()
{
using (var env = TestEnvironment.Create())
{
var sourceFile = env.CreateFile("source.txt");
var task = new Copy
{
BuildEngine = new MockEngine(true),
SourceFiles = new ITaskItem[] { new TaskItem(sourceFile.Path) },
DestinationFiles = new ITaskItem[] { new TaskItem("destination.txt") },
RetryDelayMilliseconds = 1,
};
task.Execute().ShouldBeTrue();
task.CopiedFiles.ShouldNotBeNull();
task.CopiedFiles.Length.ShouldBe(1);
task.DestinationFiles.ShouldNotBeNull();
task.DestinationFiles.Length.ShouldBe(1);
task.WroteAtLeastOneFile.ShouldBeTrue();
}
}
[Theory]
[MemberData(nameof(GetDestinationExists))]
public void CopyWithSourceFilesToDestinationFolder(bool isDestinationExists)
{
using (var env = TestEnvironment.Create())
{
var sourceFile = env.CreateFile("source.txt");
var destinationFolder = env.CreateFolder(isDestinationExists);
var task = new Copy
{
BuildEngine = new MockEngine(true),
SourceFiles = new ITaskItem[] { new TaskItem(sourceFile.Path) },
DestinationFolder = new TaskItem(destinationFolder.Path),
RetryDelayMilliseconds = 1,
};
task.Execute().ShouldBeTrue();
task.CopiedFiles.ShouldNotBeNull();
task.CopiedFiles.Length.ShouldBe(1);
task.DestinationFiles.ShouldNotBeNull();
task.DestinationFiles.Length.ShouldBe(1);
task.WroteAtLeastOneFile.ShouldBeTrue();
}
}
[Theory]
[MemberData(nameof(GetDestinationExists))]
public void CopyWithSourceFoldersToDestinationFolder(bool isDestinationExists)
{
using (var env = TestEnvironment.Create())
{
var s0Folder = env.DefaultTestDirectory.CreateDirectory("source0");
s0Folder.CreateFile("00.txt");
s0Folder.CreateFile("01.txt");
var s0AFolder = s0Folder.CreateDirectory("a");
s0AFolder.CreateFile("a0.txt");
s0AFolder.CreateFile("a1.txt");
_ = s0Folder.CreateDirectory("b");
var s0CFolder = s0Folder.CreateDirectory("c");
s0CFolder.CreateFile("c0.txt");
var s1Folder = env.DefaultTestDirectory.CreateDirectory("source1");
s1Folder.CreateFile("10.txt");
s1Folder.CreateFile("11.txt");
var s1AFolder = s1Folder.CreateDirectory("a");
s1AFolder.CreateFile("a0.txt");
s1AFolder.CreateFile("a1.txt");
var s1BFolder = s1Folder.CreateDirectory("b");
s1BFolder.CreateFile("b0.txt");
var destinationFolder = env.CreateFolder(isDestinationExists);
var task = new Copy
{
BuildEngine = new MockEngine(true),
SourceFolders = new ITaskItem[] { new TaskItem(s0Folder.Path), new TaskItem(s1Folder.Path) },
DestinationFolder = new TaskItem(destinationFolder.Path),
RetryDelayMilliseconds = 1,
};
task.Execute().ShouldBeTrue();
task.CopiedFiles.ShouldNotBeNull();
task.CopiedFiles.Length.ShouldBe(10);
task.DestinationFiles.ShouldNotBeNull();
task.DestinationFiles.Length.ShouldBe(10);
task.WroteAtLeastOneFile.ShouldBeTrue();
Directory.Exists(Path.Combine(destinationFolder.Path, "source0")).ShouldBeTrue();
Directory.Exists(Path.Combine(destinationFolder.Path, "source1")).ShouldBeTrue();
}
}
[Fact]
public void CopyWithNoSource()
{
using (var env = TestEnvironment.Create())
{
var engine = new MockEngine(true);
var destinationFolder = env.CreateFolder(true);
var task = new Copy
{
BuildEngine = engine,
DestinationFolder = new TaskItem(destinationFolder.Path),
};
task.Execute().ShouldBeTrue();
task.CopiedFiles.ShouldNotBeNull();
task.CopiedFiles.Length.ShouldBe(0);
task.DestinationFiles.ShouldNotBeNull();
task.DestinationFiles.Length.ShouldBe(0);
task.WroteAtLeastOneFile.ShouldBeFalse();
}
}
[Theory]
[MemberData(nameof(GetDestinationExists))]
public void CopyWithMultipleSourceTypes(bool isDestinationExists)
{
using (var env = TestEnvironment.Create())
{
var engine = new MockEngine(true);
var sourceFile = env.CreateFile("source.txt");
var sourceFolder = env.DefaultTestDirectory.CreateDirectory("source");
sourceFolder.CreateFile("source.txt");
var aDirectory = sourceFolder.CreateDirectory("a");
aDirectory.CreateFile("a.txt");
sourceFolder.CreateDirectory("b");
var destinationFolder = env.CreateFolder(isDestinationExists);
var task = new Copy
{
BuildEngine = engine,
SourceFiles = new ITaskItem[] { new TaskItem(sourceFile.Path) },
SourceFolders = new ITaskItem[] { new TaskItem(sourceFolder.Path) },
DestinationFolder = new TaskItem(destinationFolder.Path),
};
task.Execute().ShouldBeTrue();
task.CopiedFiles.ShouldNotBeNull();
task.CopiedFiles.Length.ShouldBe(3);
task.DestinationFiles.ShouldNotBeNull();
task.DestinationFiles.Length.ShouldBe(3);
task.WroteAtLeastOneFile.ShouldBeTrue();
}
}
[Theory]
[MemberData(nameof(GetNullAndEmptyArrays))]
public void CopyWithEmptySourceFiles(ITaskItem[] sourceFiles)
{
using (var env = TestEnvironment.Create())
{
var engine = new MockEngine(true);
var destinationFolder = env.CreateFolder(true);
var task = new Copy
{
BuildEngine = engine,
SourceFiles = sourceFiles,
DestinationFolder = new TaskItem(destinationFolder.Path),
};
task.Execute().ShouldBeTrue();
task.CopiedFiles.ShouldNotBeNull();
task.CopiedFiles.Length.ShouldBe(0);
task.DestinationFiles.ShouldNotBeNull();
task.DestinationFiles.Length.ShouldBe(0);
task.WroteAtLeastOneFile.ShouldBeFalse();
}
}
[Theory]
[MemberData(nameof(GetNullAndEmptyArrays))]
public void CopyWithEmptySourceFolders(ITaskItem[] sourceFolders)
{
using (var env = TestEnvironment.Create())
{
var engine = new MockEngine(true);
var destinationFolder = env.CreateFolder(true);
var task = new Copy
{
BuildEngine = engine,
SourceFolders = sourceFolders,
DestinationFolder = new TaskItem(destinationFolder.Path),
};
task.Execute().ShouldBeTrue();
task.CopiedFiles.ShouldNotBeNull();
task.CopiedFiles.Length.ShouldBe(0);
task.DestinationFiles.ShouldNotBeNull();
task.DestinationFiles.Length.ShouldBe(0);
task.WroteAtLeastOneFile.ShouldBeFalse();
}
}
[Theory]
[MemberData(nameof(GetNullAndEmptyArrays))]
public void CopyWithNoDestination(ITaskItem[] destinationFiles)
{
using (var env = TestEnvironment.Create())
{
var engine = new MockEngine(true);
var sourceFile = env.CreateFile("source.txt");
var task = new Copy
{
BuildEngine = engine,
SourceFiles = new ITaskItem[] { new TaskItem(sourceFile.Path) },
DestinationFiles = destinationFiles,
};
task.Execute().ShouldBeFalse();
// Copy.NeedsDestination (MSB3023) or General.TwoVectorsMustHaveSameLength (MSB3094)
engine.AssertLogContains(destinationFiles == null ? "MSB3023" : "MSB3094");
task.CopiedFiles.ShouldBeNull();
(task.DestinationFiles == null || task.DestinationFiles.Length == 0).ShouldBeTrue();
task.WroteAtLeastOneFile.ShouldBeFalse();
}
}
[Fact]
public void CopyWithMultipleDestinationTypes()
{
using (var env = TestEnvironment.Create())
{
var engine = new MockEngine(true);
var sourceFile = env.CreateFile("source.txt");
var destinationFolder = env.CreateFolder(true);
var task = new Copy
{
BuildEngine = engine,
SourceFiles = new ITaskItem[] { new TaskItem(sourceFile.Path) },
DestinationFiles = new ITaskItem[] { new TaskItem("destination.txt") },
DestinationFolder = new TaskItem(destinationFolder.Path),
};
task.Execute().ShouldBeFalse();
engine.AssertLogContains("MSB3022"); // Copy.ExactlyOneTypeOfDestination
task.CopiedFiles.ShouldBeNull();
task.DestinationFiles.ShouldNotBeNull();
task.WroteAtLeastOneFile.ShouldBeFalse();
}
}
[Fact]
public void CopyWithSourceFoldersAndDestinationFiles()
{
using (var env = TestEnvironment.Create())
{
var engine = new MockEngine(true);
var sourceFile = env.CreateFile("source.txt");
var sourceFolder = env.CreateFolder(true);
var task = new Copy
{
BuildEngine = engine,
SourceFiles = new ITaskItem[] { new TaskItem(sourceFile.Path) },
SourceFolders = new ITaskItem[] { new TaskItem(sourceFolder.Path) },
DestinationFiles = new ITaskItem[] { new TaskItem("destination0.txt"), new TaskItem("destination1.txt") },
};
task.Execute().ShouldBeFalse();
engine.AssertLogContains("MSB3896"); // Copy.IncompatibleParameters
task.CopiedFiles.ShouldBeNull();
task.DestinationFiles.ShouldNotBeNull();
task.WroteAtLeastOneFile.ShouldBeFalse();
}
}
[Fact]
public void CopyWithDifferentLengthSourceFilesToDestinationFiles()
{
using (var env = TestEnvironment.Create())
{
var engine = new MockEngine(true);
var sourceFile = env.CreateFile("source.txt");
var task = new Copy
{
BuildEngine = engine,
SourceFiles = new ITaskItem[] { new TaskItem(sourceFile.Path) },
DestinationFiles = new ITaskItem[] { new TaskItem("destination0.txt"), new TaskItem("destination1.txt") },
};
task.Execute().ShouldBeFalse();
engine.AssertLogContains("MSB3094"); // General.TwoVectorsMustHaveSameLength
task.CopiedFiles.ShouldBeNull();
task.DestinationFiles.ShouldNotBeNull();
task.WroteAtLeastOneFile.ShouldBeFalse();
}
}
/// <summary>
/// Verifies that we error for retries less than 0
/// </summary>
[Fact]
public void CopyWithInvalidRetryCount()
{
using (var env = TestEnvironment.Create())
{
var engine = new MockEngine(true);
var sourceFile = env.CreateFile("source.txt");
var task = new Copy
{
BuildEngine = engine,
SourceFiles = new ITaskItem[] { new TaskItem(sourceFile.Path) },
DestinationFiles = new ITaskItem[] { new TaskItem("destination.txt") },
Retries = -1,
};
task.Execute().ShouldBeFalse();
engine.AssertLogContains("MSB3028"); // Copy.InvalidRetryCount
task.CopiedFiles.ShouldBeNull();
task.DestinationFiles.ShouldNotBeNull();
task.WroteAtLeastOneFile.ShouldBeFalse();
}
}
/// <summary>
/// Verifies that we error for retry delay less than 0
/// </summary>
[Fact]
public void CopyWithInvalidRetryDelay()
{
using (var env = TestEnvironment.Create())
{
var engine = new MockEngine(true);
var sourceFile = env.CreateFile("source.txt");
var task = new Copy
{
BuildEngine = engine,
SourceFiles = new ITaskItem[] { new TaskItem(sourceFile.Path) },
DestinationFiles = new ITaskItem[] { new TaskItem("destination.txt") },
RetryDelayMilliseconds = -1,
};
task.Execute().ShouldBeFalse();
engine.AssertLogContains("MSB3029"); // Copy.InvalidRetryDelay
task.CopiedFiles.ShouldBeNull();
task.DestinationFiles.ShouldNotBeNull();
task.WroteAtLeastOneFile.ShouldBeFalse();
}
}
/// <summary>
/// If OnlyCopyIfDifferent is set to "true" then we shouldn't copy over files that
/// have the same date and time.
@ -1898,6 +2263,30 @@ namespace Microsoft.Build.UnitTests
((MockEngine)t.BuildEngine).AssertLogDoesntContain("MSB3026"); // Didn't do retries
}
/// <summary>
/// If the DestinationFolder parameter is given invalid path characters, make sure the task exits gracefully.
/// </summary>
[Theory]
[MemberData(nameof(GetHardLinksSymLinks))]
public void ExitGracefullyOnInvalidPathCharactersInDestinationFolder(bool isUseHardLinks, bool isUseSymbolicLinks)
{
var t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
BuildEngine = new MockEngine(_testOutputHelper),
SourceFiles = new ITaskItem[] { new TaskItem("foo") },
DestinationFolder = new TaskItem("here | there"),
UseHardlinksIfPossible = isUseHardLinks,
UseSymboliclinksIfPossible = isUseSymbolicLinks,
};
bool result = t.Execute();
// Expect for there to have been no copies.
Assert.False(result);
((MockEngine)t.BuildEngine).AssertLogDoesntContain("MSB3026"); // Didn't do retries
}
/// <summary>
/// Verifies that we error for retries less than 0
/// </summary>

Просмотреть файл

@ -8,6 +8,7 @@ using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks.Dataflow;
using Microsoft.Build.Eventing;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
@ -99,9 +100,10 @@ namespace Microsoft.Build.Tasks
/// </summary>
private const int RetryDelayMillisecondsDefault = 1000;
[Required]
public ITaskItem[] SourceFiles { get; set; }
public ITaskItem[] SourceFolders { get; set; }
public ITaskItem DestinationFolder { get; set; }
/// <summary>
@ -402,7 +404,7 @@ namespace Microsoft.Build.Tasks
int parallelism)
{
// If there are no source files then just return success.
if (SourceFiles == null || SourceFiles.Length == 0)
if (IsSourceSetEmpty())
{
DestinationFiles = Array.Empty<ITaskItem>();
CopiedFiles = Array.Empty<ITaskItem>();
@ -643,6 +645,11 @@ namespace Microsoft.Build.Tasks
return success;
}
private bool IsSourceSetEmpty()
{
return (SourceFiles == null || SourceFiles.Length == 0) && (SourceFolders == null || SourceFolders.Length == 0);
}
/// <summary>
/// Verify that the inputs are correct.
/// </summary>
@ -661,7 +668,7 @@ namespace Microsoft.Build.Tasks
return false;
}
// There must be a destinationFolder (either files or directory).
// There must be a destination (either files or directory).
if (DestinationFiles == null && DestinationFolder == null)
{
Log.LogErrorWithCodeFromResources("Copy.NeedsDestination", "DestinationFiles", "DestinationFolder");
@ -675,6 +682,13 @@ namespace Microsoft.Build.Tasks
return false;
}
// SourceFolders and DestinationFiles can't be used together.
if (SourceFolders != null && DestinationFiles != null)
{
Log.LogErrorWithCodeFromResources("Copy.IncompatibleParameters", "SourceFolders", "DestinationFiles");
return false;
}
// If the caller passed in DestinationFiles, then its length must match SourceFiles.
if (DestinationFiles != null && DestinationFiles.Length != SourceFiles.Length)
{
@ -682,7 +696,6 @@ namespace Microsoft.Build.Tasks
return false;
}
if (ErrorIfLinkFails && !UseHardlinksIfPossible && !UseSymboliclinksIfPossible)
{
Log.LogErrorWithCodeFromResources("Copy.ErrorIfLinkFailsSetWithoutLinkOption");
@ -694,39 +707,125 @@ namespace Microsoft.Build.Tasks
/// <summary>
/// Set up our list of destination files.
/// For SourceFiles: Apply DestinationFolder to each SourceFiles item to create a DestinationFiles item.
/// For SourceFolders: With each SourceFolders item, get the files in the represented directory. Create both SourceFiles and DestinationFiles items.
/// </summary>
/// <returns>False if an error occurred, implying aborting the overall copy operation.</returns>
private bool InitializeDestinationFiles()
{
if (DestinationFiles == null)
bool isSuccess = true;
try
{
// If the caller passed in DestinationFolder, convert it to DestinationFiles
DestinationFiles = new ITaskItem[SourceFiles.Length];
for (int i = 0; i < SourceFiles.Length; ++i)
if (DestinationFiles == null && SourceFiles != null)
{
// Build the correct path.
string destinationFile;
try
{
destinationFile = Path.Combine(DestinationFolder.ItemSpec, Path.GetFileName(SourceFiles[i].ItemSpec));
}
catch (ArgumentException e)
{
Log.LogErrorWithCodeFromResources("Copy.Error", SourceFiles[i].ItemSpec, DestinationFolder.ItemSpec, e.Message);
// Clear the outputs.
DestinationFiles = Array.Empty<ITaskItem>();
return false;
}
DestinationFiles = new ITaskItem[SourceFiles.Length];
// Initialize the destinationFolder item.
// ItemSpec is unescaped, and the TaskItem constructor expects an escaped input, so we need to
// make sure to re-escape it here.
DestinationFiles[i] = new TaskItem(EscapingUtilities.Escape(destinationFile));
for (int i = 0; i < SourceFiles.Length; ++i)
{
// Build the correct path.
if (!TryPathOperation(
() => Path.Combine(DestinationFolder.ItemSpec, Path.GetFileName(SourceFiles[i].ItemSpec)),
SourceFiles[i].ItemSpec,
DestinationFolder.ItemSpec,
out string destinationFile))
{
isSuccess = false;
break;
}
// Copy meta-data from source to destinationFolder.
SourceFiles[i].CopyMetadataTo(DestinationFiles[i]);
// Initialize the destinationFolder item.
// ItemSpec is unescaped, and the TaskItem constructor expects an escaped input, so we need to
// make sure to re-escape it here.
DestinationFiles[i] = new TaskItem(EscapingUtilities.Escape(destinationFile));
// Copy meta-data from source to destinationFolder.
SourceFiles[i].CopyMetadataTo(DestinationFiles[i]);
}
}
if (isSuccess && SourceFolders != null && SourceFolders.Length > 0)
{
var sourceFiles = SourceFiles != null ? new List<ITaskItem>(SourceFiles) : new List<ITaskItem>();
var destinationFiles = DestinationFiles != null ? new List<ITaskItem>(DestinationFiles) : new List<ITaskItem>();
foreach (ITaskItem sourceFolder in SourceFolders)
{
string src = FileUtilities.NormalizePath(sourceFolder.ItemSpec);
string srcName = Path.GetFileName(src);
(string[] filesInFolder, _, _) = FileMatcher.Default.GetFiles(src, "**");
foreach (string file in filesInFolder)
{
if (!TryPathOperation(
() => Path.Combine(src, file),
sourceFolder.ItemSpec,
DestinationFolder.ItemSpec,
out string sourceFile))
{
isSuccess = false;
break;
}
if (!TryPathOperation(
() => Path.Combine(DestinationFolder.ItemSpec, srcName, file),
sourceFolder.ItemSpec,
DestinationFolder.ItemSpec,
out string destinationFile))
{
isSuccess = false;
break;
}
var item = new TaskItem(EscapingUtilities.Escape(sourceFile));
sourceFolder.CopyMetadataTo(item);
sourceFiles.Add(item);
item = new TaskItem(EscapingUtilities.Escape(destinationFile));
sourceFolder.CopyMetadataTo(item);
destinationFiles.Add(item);
}
}
SourceFiles = sourceFiles.ToArray();
DestinationFiles = destinationFiles.ToArray();
}
}
finally
{
if (!isSuccess)
{
// Clear the outputs.
DestinationFiles = Array.Empty<ITaskItem>();
}
}
return isSuccess;
}
/// <summary>
/// Tries the path operation. Logs a 'Copy.Error' if an exception is thrown.
/// </summary>
/// <param name="operation">The operation.</param>
/// <param name="src">The source to use for the log message.</param>
/// <param name="dest">The destination to use for the log message.</param>
/// <param name="resultPathOperation">The result of the path operation.</param>
/// <returns></returns>
private bool TryPathOperation(Func<string> operation, string src, string dest, out string resultPathOperation)
{
resultPathOperation = string.Empty;
try
{
resultPathOperation = operation();
}
catch (ArgumentException e)
{
Log.LogErrorWithCodeFromResources("Copy.Error", src, dest, e.Message);
return false;
}
return true;

Просмотреть файл

@ -323,7 +323,7 @@ Copyright (C) Microsoft Corporation. All rights reserved.
<!-- Example, C:\MyProjects\MyProject\bin\Debug\MyAssembly.dll -->
<TargetPath Condition=" '$(TargetPath)' == '' ">$(TargetDir)$(TargetFileName)</TargetPath>
<TargetRefPath Condition=" '$(TargetRefPath)' == '' and '$(ProduceReferenceAssembly)' == 'true' and ('$(ProduceReferenceAssemblyInOutDir)' == 'true' or '$([MSBuild]::AreFeaturesEnabled(17.0))' != 'true' ) ">$([MSBuild]::NormalizePath($(TargetDir), 'ref', $(TargetFileName)))</TargetRefPath>
<TargetRefPath Condition=" '$(TargetRefPath)' == '' and '$(ProduceReferenceAssembly)' == 'true' and '$(ProduceReferenceAssemblyInOutDir)' == 'true' ">$([MSBuild]::NormalizePath($(TargetDir), 'ref', $(TargetFileName)))</TargetRefPath>
<TargetRefPath Condition=" '$(TargetRefPath)' == '' and '$(ProduceReferenceAssembly)' == 'true' ">$([MSBuild]::NormalizePath($(MSBuildProjectDirectory), $(IntermediateOutputPath), 'ref', $(TargetFileName)))</TargetRefPath>
<!-- Example, C:\MyProjects\MyProject\ -->
@ -1374,6 +1374,18 @@ Copyright (C) Microsoft Corporation. All rights reserved.
<_Temp Remove="@(_Temp)" />
</ItemGroup>
<!-- RESOURCE ITEMS -->
<AssignLinkMetadata Items="@(Resource)"
Condition="'@(Resource)' != '' and '%(Resource.DefiningProjectFullPath)' != '$(MSBuildProjectFullPath)' and $([MSBuild]::AreFeaturesEnabled('17.10'))">
<Output TaskParameter="OutputItems" ItemName="_Temp" />
</AssignLinkMetadata>
<ItemGroup Condition="$([MSBuild]::AreFeaturesEnabled('17.10'))">
<Resource Remove="@(_Temp)" />
<Resource Include="@(_Temp)" />
<_Temp Remove="@(_Temp)" />
</ItemGroup>
</Target>
<!--
@ -5012,7 +5024,7 @@ Copyright (C) Microsoft Corporation. All rights reserved.
This target enforces the dependency.
-->
<MSBuildCopyContentTransitively Condition=" '$(MSBuildCopyContentTransitively)' == '' and $([MSBuild]::AreFeaturesEnabled('17.0'))">true</MSBuildCopyContentTransitively>
<MSBuildCopyContentTransitively Condition=" '$(MSBuildCopyContentTransitively)' == ''">true</MSBuildCopyContentTransitively>
<_TargetsThatPrepareProjectReferences Condition=" '$(MSBuildCopyContentTransitively)' == 'true' ">
AssignProjectConfiguration;

Просмотреть файл

@ -2793,6 +2793,10 @@
<value>MSB3895: Retrying on ERROR_ACCESS_DENIED because environment variable MSBUILDALWAYSRETRY=1</value>
<comment>{StrBegin="MSB3895: "} LOCALIZATION: Do NOT translate MSBUILDALWAYSRETRY)</comment>
</data>
<data name="Copy.IncompatibleParameters">
<value>MSB3896: Both "{0}" and "{1}" were specified as parameters in the project file but cannot be used together. Please choose one or the other.</value>
<comment>{StrBegin="MSB3896: "}</comment>
</data>
<!--
MSB3901 - MSB3910 Task: Telemetry

7
src/Tasks/Resources/xlf/Strings.cs.xlf сгенерированный
Просмотреть файл

@ -196,6 +196,11 @@
<target state="translated">MSB3894: Při kopírování „{1}“ do „{2}“ došlo k výjimce {0} a HR je {3}</target>
<note>{StrBegin="MSB3894: "} LOCALIZATION: {0} is exception.ToString(), {1} and {2} are paths, {3} is a number")</note>
</trans-unit>
<trans-unit id="Copy.IncompatibleParameters">
<source>MSB3896: Both "{0}" and "{1}" were specified as parameters in the project file but cannot be used together. Please choose one or the other.</source>
<target state="new">MSB3896: Both "{0}" and "{1}" were specified as parameters in the project file but cannot be used together. Please choose one or the other.</target>
<note>{StrBegin="MSB3896: "}</note>
</trans-unit>
<trans-unit id="Copy.LinkFailed">
<source>MSB3893: Could not use a link to copy "{0}" to "{1}".</source>
<target state="translated">MSB3893: Nedá se použít odkaz pro kopírování {0} do {1}.</target>
@ -1067,7 +1072,7 @@
<trans-unit id="GenerateResource.BinaryFormatterUse">
<source>MSB3825: Resource "{0}" of type "{1}" is deserialized via BinaryFormatter at runtime. BinaryFormatter is deprecated due to possible security risks and will be removed with .NET 9. If you wish to continue using it, set property "GenerateResourceWarnOnBinaryFormatterUse" to false.
More information: https://aka.ms/msbuild/net8-binaryformatter</source>
<target state="translated">MSB3825: Prostředek „{0}“ typu „{1}“ je deserializován prostřednictvím BinaryFormatter za běhu. BinaryFormatter je zastaralý kvůli možným bezpečnostním rizikům a odebere se s .NET 9. Pokud ho chcete používat dál, nastavte vlastnost GenerateResourceWarnOnBinaryFormatterUse na false.
<target state="translated">MSB3825: Prostředek „{0}“ typu „{1}“ je deserializován prostřednictvím BinaryFormatter za běhu. BinaryFormatter je zastaralý kvůli možným bezpečnostním rizikům a odebere se s .NET 9. Pokud ho chcete používat dál, nastavte vlastnost GenerateResourceWarnOnBinaryFormatterUse na false.
Další informace: https://aka.ms/msbuild/net8-binaryformatter</target>
<note>{StrBegin="MSB3825: "}</note>
</trans-unit>

5
src/Tasks/Resources/xlf/Strings.de.xlf сгенерированный
Просмотреть файл

@ -196,6 +196,11 @@
<target state="translated">MSB3894: Erhalt von {0} Kopieren von "{1}" zu "{2}" und HR ist {3}</target>
<note>{StrBegin="MSB3894: "} LOCALIZATION: {0} is exception.ToString(), {1} and {2} are paths, {3} is a number")</note>
</trans-unit>
<trans-unit id="Copy.IncompatibleParameters">
<source>MSB3896: Both "{0}" and "{1}" were specified as parameters in the project file but cannot be used together. Please choose one or the other.</source>
<target state="new">MSB3896: Both "{0}" and "{1}" were specified as parameters in the project file but cannot be used together. Please choose one or the other.</target>
<note>{StrBegin="MSB3896: "}</note>
</trans-unit>
<trans-unit id="Copy.LinkFailed">
<source>MSB3893: Could not use a link to copy "{0}" to "{1}".</source>
<target state="translated">MSB3893: Es konnte kein Link verwendet werden, um "{0}" in "{1}" zu kopieren.</target>

5
src/Tasks/Resources/xlf/Strings.es.xlf сгенерированный
Просмотреть файл

@ -196,6 +196,11 @@
<target state="translated">MSB3894: Se obtuvo {0} al copiar "{1}" en "{2}" y HR es {3}</target>
<note>{StrBegin="MSB3894: "} LOCALIZATION: {0} is exception.ToString(), {1} and {2} are paths, {3} is a number")</note>
</trans-unit>
<trans-unit id="Copy.IncompatibleParameters">
<source>MSB3896: Both "{0}" and "{1}" were specified as parameters in the project file but cannot be used together. Please choose one or the other.</source>
<target state="new">MSB3896: Both "{0}" and "{1}" were specified as parameters in the project file but cannot be used together. Please choose one or the other.</target>
<note>{StrBegin="MSB3896: "}</note>
</trans-unit>
<trans-unit id="Copy.LinkFailed">
<source>MSB3893: Could not use a link to copy "{0}" to "{1}".</source>
<target state="translated">MSB3893: No se puede usar un vínculo para copiar "{0}" en "{1}".</target>

5
src/Tasks/Resources/xlf/Strings.fr.xlf сгенерированный
Просмотреть файл

@ -196,6 +196,11 @@
<target state="translated">MSB3894: Nous avons copié {0} «{1}» vers «{2}» et les ressources humaines sont {3}</target>
<note>{StrBegin="MSB3894: "} LOCALIZATION: {0} is exception.ToString(), {1} and {2} are paths, {3} is a number")</note>
</trans-unit>
<trans-unit id="Copy.IncompatibleParameters">
<source>MSB3896: Both "{0}" and "{1}" were specified as parameters in the project file but cannot be used together. Please choose one or the other.</source>
<target state="new">MSB3896: Both "{0}" and "{1}" were specified as parameters in the project file but cannot be used together. Please choose one or the other.</target>
<note>{StrBegin="MSB3896: "}</note>
</trans-unit>
<trans-unit id="Copy.LinkFailed">
<source>MSB3893: Could not use a link to copy "{0}" to "{1}".</source>
<target state="translated">MSB3893: impossible d'utiliser un lien pour copier "{0}" vers "{1}".</target>

5
src/Tasks/Resources/xlf/Strings.it.xlf сгенерированный
Просмотреть файл

@ -196,6 +196,11 @@
<target state="translated">MSB3894: Ottenuto {0} copiando "{1}" in "{2}" e HR è {3}"</target>
<note>{StrBegin="MSB3894: "} LOCALIZATION: {0} is exception.ToString(), {1} and {2} are paths, {3} is a number")</note>
</trans-unit>
<trans-unit id="Copy.IncompatibleParameters">
<source>MSB3896: Both "{0}" and "{1}" were specified as parameters in the project file but cannot be used together. Please choose one or the other.</source>
<target state="new">MSB3896: Both "{0}" and "{1}" were specified as parameters in the project file but cannot be used together. Please choose one or the other.</target>
<note>{StrBegin="MSB3896: "}</note>
</trans-unit>
<trans-unit id="Copy.LinkFailed">
<source>MSB3893: Could not use a link to copy "{0}" to "{1}".</source>
<target state="translated">MSB3893: non è stato possibile usare un collegamento per copiare "{0}" in "{1}".</target>

5
src/Tasks/Resources/xlf/Strings.ja.xlf сгенерированный
Просмотреть файл

@ -196,6 +196,11 @@
<target state="translated">MSB3894: "{1}" を "{2}" にコピー中に {0}が発生しました。HR は {3} です</target>
<note>{StrBegin="MSB3894: "} LOCALIZATION: {0} is exception.ToString(), {1} and {2} are paths, {3} is a number")</note>
</trans-unit>
<trans-unit id="Copy.IncompatibleParameters">
<source>MSB3896: Both "{0}" and "{1}" were specified as parameters in the project file but cannot be used together. Please choose one or the other.</source>
<target state="new">MSB3896: Both "{0}" and "{1}" were specified as parameters in the project file but cannot be used together. Please choose one or the other.</target>
<note>{StrBegin="MSB3896: "}</note>
</trans-unit>
<trans-unit id="Copy.LinkFailed">
<source>MSB3893: Could not use a link to copy "{0}" to "{1}".</source>
<target state="translated">MSB3893: リンクを使用して "{0}" を "{1}" にコピーできませんでした。</target>

5
src/Tasks/Resources/xlf/Strings.ko.xlf сгенерированный
Просмотреть файл

@ -196,6 +196,11 @@
<target state="translated">MSB3894: {0}을(를) “{1}”(으)로 복사하는 “{2}”이(가) 있고 HR은 {3}입니다.</target>
<note>{StrBegin="MSB3894: "} LOCALIZATION: {0} is exception.ToString(), {1} and {2} are paths, {3} is a number")</note>
</trans-unit>
<trans-unit id="Copy.IncompatibleParameters">
<source>MSB3896: Both "{0}" and "{1}" were specified as parameters in the project file but cannot be used together. Please choose one or the other.</source>
<target state="new">MSB3896: Both "{0}" and "{1}" were specified as parameters in the project file but cannot be used together. Please choose one or the other.</target>
<note>{StrBegin="MSB3896: "}</note>
</trans-unit>
<trans-unit id="Copy.LinkFailed">
<source>MSB3893: Could not use a link to copy "{0}" to "{1}".</source>
<target state="translated">MSB3893: 링크를 사용하여 "{0}"을(를) "{1}"에 복사할 수 없습니다.</target>

5
src/Tasks/Resources/xlf/Strings.pl.xlf сгенерированный
Просмотреть файл

@ -196,6 +196,11 @@
<target state="translated">MSB3894: „Mam {0} kopiowane z „{1}” do „{2}”, a HR to {3}”</target>
<note>{StrBegin="MSB3894: "} LOCALIZATION: {0} is exception.ToString(), {1} and {2} are paths, {3} is a number")</note>
</trans-unit>
<trans-unit id="Copy.IncompatibleParameters">
<source>MSB3896: Both "{0}" and "{1}" were specified as parameters in the project file but cannot be used together. Please choose one or the other.</source>
<target state="new">MSB3896: Both "{0}" and "{1}" were specified as parameters in the project file but cannot be used together. Please choose one or the other.</target>
<note>{StrBegin="MSB3896: "}</note>
</trans-unit>
<trans-unit id="Copy.LinkFailed">
<source>MSB3893: Could not use a link to copy "{0}" to "{1}".</source>
<target state="translated">MSB3893: Nie można użyć linku w celu skopiowania ścieżki „{0}” do ścieżki „{1}”.</target>

5
src/Tasks/Resources/xlf/Strings.pt-BR.xlf сгенерированный
Просмотреть файл

@ -196,6 +196,11 @@
<target state="translated">MSB3894: Recebi {0} copiando "{1}" para {2} e o RH é {3}</target>
<note>{StrBegin="MSB3894: "} LOCALIZATION: {0} is exception.ToString(), {1} and {2} are paths, {3} is a number")</note>
</trans-unit>
<trans-unit id="Copy.IncompatibleParameters">
<source>MSB3896: Both "{0}" and "{1}" were specified as parameters in the project file but cannot be used together. Please choose one or the other.</source>
<target state="new">MSB3896: Both "{0}" and "{1}" were specified as parameters in the project file but cannot be used together. Please choose one or the other.</target>
<note>{StrBegin="MSB3896: "}</note>
</trans-unit>
<trans-unit id="Copy.LinkFailed">
<source>MSB3893: Could not use a link to copy "{0}" to "{1}".</source>
<target state="translated">MSB3893: Não foi possível usar um link para copiar "{0}" para "{1}".</target>

5
src/Tasks/Resources/xlf/Strings.ru.xlf сгенерированный
Просмотреть файл

@ -196,6 +196,11 @@
<target state="translated">MSB3894: Выполнено копирование {0} "{1}" в "{2}" и HR — {3}</target>
<note>{StrBegin="MSB3894: "} LOCALIZATION: {0} is exception.ToString(), {1} and {2} are paths, {3} is a number")</note>
</trans-unit>
<trans-unit id="Copy.IncompatibleParameters">
<source>MSB3896: Both "{0}" and "{1}" were specified as parameters in the project file but cannot be used together. Please choose one or the other.</source>
<target state="new">MSB3896: Both "{0}" and "{1}" were specified as parameters in the project file but cannot be used together. Please choose one or the other.</target>
<note>{StrBegin="MSB3896: "}</note>
</trans-unit>
<trans-unit id="Copy.LinkFailed">
<source>MSB3893: Could not use a link to copy "{0}" to "{1}".</source>
<target state="translated">MSB3893: не удалось использовать связь для копирования "{0}" в "{1}".</target>

5
src/Tasks/Resources/xlf/Strings.tr.xlf сгенерированный
Просмотреть файл

@ -196,6 +196,11 @@
<target state="translated">MSB3894: {0} için {1} yolu {2} yoluna kopyalandı ve HR: {3}</target>
<note>{StrBegin="MSB3894: "} LOCALIZATION: {0} is exception.ToString(), {1} and {2} are paths, {3} is a number")</note>
</trans-unit>
<trans-unit id="Copy.IncompatibleParameters">
<source>MSB3896: Both "{0}" and "{1}" were specified as parameters in the project file but cannot be used together. Please choose one or the other.</source>
<target state="new">MSB3896: Both "{0}" and "{1}" were specified as parameters in the project file but cannot be used together. Please choose one or the other.</target>
<note>{StrBegin="MSB3896: "}</note>
</trans-unit>
<trans-unit id="Copy.LinkFailed">
<source>MSB3893: Could not use a link to copy "{0}" to "{1}".</source>
<target state="translated">MSB3893: "{0}" dosyasını "{1}" yoluna kopyalama bağlantısı kullanılamadı.</target>

5
src/Tasks/Resources/xlf/Strings.zh-Hans.xlf сгенерированный
Просмотреть файл

@ -196,6 +196,11 @@
<target state="translated">MSB3894: 将 "{1}" 复制到 "{2}" 时出现 {0}HR 为 {3}</target>
<note>{StrBegin="MSB3894: "} LOCALIZATION: {0} is exception.ToString(), {1} and {2} are paths, {3} is a number")</note>
</trans-unit>
<trans-unit id="Copy.IncompatibleParameters">
<source>MSB3896: Both "{0}" and "{1}" were specified as parameters in the project file but cannot be used together. Please choose one or the other.</source>
<target state="new">MSB3896: Both "{0}" and "{1}" were specified as parameters in the project file but cannot be used together. Please choose one or the other.</target>
<note>{StrBegin="MSB3896: "}</note>
</trans-unit>
<trans-unit id="Copy.LinkFailed">
<source>MSB3893: Could not use a link to copy "{0}" to "{1}".</source>
<target state="translated">MSB3893: 无法使用链接将“{0}”复制到“{1}”。</target>

5
src/Tasks/Resources/xlf/Strings.zh-Hant.xlf сгенерированный
Просмотреть файл

@ -196,6 +196,11 @@
<target state="translated">MSB3894: 擁有 {0} 將 "{1}" 複製至 "{2}",且 HR 為 {3}</target>
<note>{StrBegin="MSB3894: "} LOCALIZATION: {0} is exception.ToString(), {1} and {2} are paths, {3} is a number")</note>
</trans-unit>
<trans-unit id="Copy.IncompatibleParameters">
<source>MSB3896: Both "{0}" and "{1}" were specified as parameters in the project file but cannot be used together. Please choose one or the other.</source>
<target state="new">MSB3896: Both "{0}" and "{1}" were specified as parameters in the project file but cannot be used together. Please choose one or the other.</target>
<note>{StrBegin="MSB3896: "}</note>
</trans-unit>
<trans-unit id="Copy.LinkFailed">
<source>MSB3893: Could not use a link to copy "{0}" to "{1}".</source>
<target state="translated">MSB3893: 無法使用連結將 "{0}" 複製到 "{1}"。</target>