未验证 提交 1ad342d6 编写于 作者: T Tomáš Matoušek 提交者: GitHub

Implements targets that calculate PathMap from SourceRoots (#25325)

Implements targets that calculate PathMap from SourceRoots and provide extension point for Source Link generator
上级 c76f5902
......@@ -470,6 +470,9 @@ function Test-XUnitCoreClr() {
# Core function for running our unit / integration tests tests
function Test-XUnit() {
# Used by tests to locate dotnet CLI
$env:DOTNET_INSTALL_DIR = Split-Path $dotnet -Parent
if ($testCoreClr) {
Test-XUnitCoreClr
return
......
......@@ -223,6 +223,42 @@ internal class ErrorString {
}
}
/// <summary>
/// Looks up a localized string similar to {0} contains duplicate items &apos;{1}&apos; with conflicting metadata &apos;{2}&apos;: &apos;{3}&apos; and &apos;{4}&apos;.
/// </summary>
internal static string MapSourceRoots_ContainsDuplicate {
get {
return ResourceManager.GetString("MapSourceRoots.ContainsDuplicate", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The value of {0} not found in {1} items, or the corresponding item is not a top-level source root: &apos;{2}&apos;.
/// </summary>
internal static string MapSourceRoots_NoSuchTopLevelSourceRoot {
get {
return ResourceManager.GetString("MapSourceRoots.NoSuchTopLevelSourceRoot", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} items must include at least one top-level (not nested) item when {1} is true.
/// </summary>
internal static string MapSourceRoots_NoTopLevelSourceRoot {
get {
return ResourceManager.GetString("MapSourceRoots.NoTopLevelSourceRoot", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} paths are required to end with a slash or backslash: &apos;{1}&apos;.
/// </summary>
internal static string MapSourceRoots_PathMustEndWithSlashOrBackslash {
get {
return ResourceManager.GetString("MapSourceRoots.PathMustEndWithSlashOrBackslash", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Shared compilation failed; falling back to tool: {0}.
/// </summary>
......
......@@ -199,4 +199,16 @@
<value>MSB3402: There was an error creating the pdb file "{0}". {1}</value>
<comment>{StrBegin="MSB3402: "}</comment>
</data>
<data name="MapSourceRoots.ContainsDuplicate">
<value>{0} contains duplicate items '{1}' with conflicting metadata '{2}': '{3}' and '{4}'</value>
</data>
<data name="MapSourceRoots.NoSuchTopLevelSourceRoot">
<value>The value of {0} not found in {1} items, or the corresponding item is not a top-level source root: '{2}'</value>
</data>
<data name="MapSourceRoots.PathMustEndWithSlashOrBackslash">
<value>{0} paths are required to end with a slash or backslash: '{1}'</value>
</data>
<data name="MapSourceRoots.NoTopLevelSourceRoot">
<value>{0} items must include at least one top-level (not nested) item when {1} is true</value>
</data>
</root>
\ No newline at end of file
......@@ -72,5 +72,8 @@
<PackageReference Include="System.IO.Pipes.AccessControl" Version="$(SystemIOPipesAccessControlVersion)" />
<PackageReference Include="System.Security.AccessControl" Version="$(SystemSecurityAccessControlVersion)" />
</ItemGroup>
<ItemGroup>
<InternalsVisibleToTest Include="Microsoft.Build.Tasks.CodeAnalysis.UnitTests" />
</ItemGroup>
<Import Project="..\CommandLine\CommandLine.projitems" Label="Shared" />
</Project>
\ No newline at end of file
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using Microsoft.Build.Framework;
using Microsoft.Build.Tasks;
using Microsoft.Build.Utilities;
namespace Microsoft.CodeAnalysis.BuildTasks
{
/// <summary>
/// Given a list of SourceRoot items produces a list of the same items with added <c>MappedPath</c> metadata that
/// contains calculated deterministic source path for each SourceRoot.
/// </summary>
/// <remarks>
/// Does not perform any path validation.
///
/// The <c>MappedPath</c> is either the path (ItemSpec) itself, when <see cref="Deterministic"/> is false,
/// or a calculated deterministic source path (starting with prefix '/_/', '/_1/', etc.), otherwise.
/// </remarks>
public sealed class MapSourceRoots : Task
{
public MapSourceRoots()
{
TaskResources = ErrorString.ResourceManager;
}
/// <summary>
/// SourceRoot items with the following optional well-known metadata:
/// <list type="bullet">
/// <term>SourceControl</term><description>Indicates name of the source control system the source root is tracked by (e.g. Git, TFVC, etc.), if any.</description>
/// <term>NestedRoot</term><description>If a value is specified the source root is nested (e.g. git submodule). The value is a path to this root relative to the containing root.</description>
/// <term>ContainingRoot</term><description>Identifies another source root item that this source root is nested under.</description>
/// </list>
/// </summary>
[Required]
public ITaskItem[] SourceRoots { get; set; }
/// <summary>
/// True if the mapped paths should be deterministic.
/// </summary>
public bool Deterministic { get; set; }
/// <summary>
/// SourceRoot items with <term>MappedPath</term> metadata set.
/// Items listed in <see cref="SourceRoots"/> that have the same ItemSpec will be merged into a single item in this list.
/// </summary>
[Output]
public ITaskItem[] MappedSourceRoots { get; private set; }
private static class Names
{
public const string SourceRoot = nameof(SourceRoot);
public const string DeterministicSourcePaths = nameof(DeterministicSourcePaths);
// Names of well-known SourceRoot metadata items:
public const string SourceControl = nameof(SourceControl);
public const string RevisionId = nameof(RevisionId);
public const string NestedRoot = nameof(NestedRoot);
public const string ContainingRoot = nameof(ContainingRoot);
public const string MappedPath = nameof(MappedPath);
public const string SourceLinkUrl = nameof(SourceLinkUrl);
public static readonly string[] SourceRootMetadataNames = new[] { SourceControl, RevisionId, NestedRoot, ContainingRoot, MappedPath, SourceLinkUrl };
}
private static string EnsureEndsWithSlash(string path)
=> (path[path.Length - 1] == '/') ? path : path + '/';
private static bool EndsWithDirectorySeparator(string path)
{
if (path.Length == 0)
{
return false;
}
char c = path[path.Length - 1];
return c == Path.DirectorySeparatorChar || c == Path.AltDirectorySeparatorChar;
}
private void MergeSourceRootMetadata(ITaskItem left, ITaskItem right)
{
foreach (string metadataName in right.MetadataNames)
{
var leftValue = left.GetMetadata(metadataName);
var rightValue = right.GetMetadata(metadataName);
if (!string.IsNullOrEmpty(leftValue) && !string.IsNullOrEmpty(rightValue))
{
Log.LogErrorFromResources("MapSourceRoots.ContainsDuplicate", Names.SourceRoot, right.ItemSpec, metadataName, leftValue, rightValue);
}
else if (string.IsNullOrEmpty(leftValue) && !string.IsNullOrEmpty(rightValue))
{
left.SetMetadata(metadataName, rightValue);
}
}
}
public override bool Execute()
{
// Merge metadata of SourceRoot items with the same identity.
var mappedSourceRoots = new List<ITaskItem>();
var rootByItemSpec = new Dictionary<string, ITaskItem>();
foreach (var sourceRoot in SourceRoots)
{
// The SourceRoot is required to have a trailing directory separator.
// We do not append one implicitly as we do not know which separator to append on Windows.
// The usage of SourceRoot might be sensitive to what kind of separator is used (e.g. in SourceLink where it needs
// to match the corresponding separators used in paths given to the compiler).
if (!EndsWithDirectorySeparator(sourceRoot.ItemSpec))
{
Log.LogErrorFromResources("MapSourceRoots.PathMustEndWithSlashOrBackslash", Names.SourceRoot, sourceRoot.ItemSpec);
}
if (rootByItemSpec.TryGetValue(sourceRoot.ItemSpec, out var existingRoot))
{
ReportConflictingWellKnownMetadata(existingRoot, sourceRoot);
sourceRoot.CopyMetadataTo(existingRoot);
}
else
{
rootByItemSpec.Add(sourceRoot.ItemSpec, sourceRoot);
mappedSourceRoots.Add(sourceRoot);
}
}
if (Log.HasLoggedErrors)
{
return false;
}
if (Deterministic)
{
var topLevelMappedPaths = new Dictionary<string, string>();
void setTopLevelMappedPaths(bool sourceControlled)
{
foreach (var root in mappedSourceRoots)
{
if (!string.IsNullOrEmpty(root.GetMetadata(Names.SourceControl)) == sourceControlled)
{
string localPath = root.ItemSpec;
string nestedRoot = root.GetMetadata(Names.NestedRoot);
if (string.IsNullOrEmpty(nestedRoot))
{
// root isn't nested
if (topLevelMappedPaths.ContainsKey(localPath))
{
Log.LogErrorFromResources("MapSourceRoots.ContainsDuplicate", Names.SourceRoot, localPath);
}
else
{
int index = topLevelMappedPaths.Count;
var mappedPath = "/_" + (index == 0 ? "" : index.ToString()) + "/";
topLevelMappedPaths.Add(localPath, mappedPath);
root.SetMetadata(Names.MappedPath, mappedPath);
}
}
}
}
}
// assign mapped paths to process source-controlled top-level roots first:
setTopLevelMappedPaths(sourceControlled: true);
// then assign mapped paths to other source-controlled top-level roots:
setTopLevelMappedPaths(sourceControlled: false);
if (topLevelMappedPaths.Count == 0)
{
Log.LogErrorFromResources("MapSourceRoots.NoTopLevelSourceRoot", Names.SourceRoot, Names.DeterministicSourcePaths);
return false;
}
// finally, calculate mapped paths of nested roots:
foreach (var root in mappedSourceRoots)
{
string nestedRoot = root.GetMetadata(Names.NestedRoot);
if (!string.IsNullOrEmpty(nestedRoot))
{
string containingRoot = root.GetMetadata(Names.ContainingRoot);
// The value of ContainingRoot metadata is a file path that is compared with ItemSpec values of SourceRoot items.
// Since the paths in ItemSpec have backslashes replaced with slashes on non-Windows platforms we need to do the same for ContainingRoot.
if (containingRoot != null && topLevelMappedPaths.TryGetValue(Utilities.FixFilePath(containingRoot), out var mappedTopLevelPath))
{
Debug.Assert(mappedTopLevelPath.EndsWith("/", StringComparison.Ordinal));
root.SetMetadata(Names.MappedPath, mappedTopLevelPath + EnsureEndsWithSlash(nestedRoot.Replace('\\', '/')));
}
else
{
Log.LogErrorFromResources("MapSourceRoots.NoSuchTopLevelSourceRoot", Names.SourceRoot + "." + Names.ContainingRoot, Names.SourceRoot, containingRoot);
}
}
}
}
else
{
foreach (var root in mappedSourceRoots)
{
root.SetMetadata(Names.MappedPath, root.ItemSpec);
}
}
if (!Log.HasLoggedErrors)
{
MappedSourceRoots = mappedSourceRoots.ToArray();
}
return !Log.HasLoggedErrors;
}
/// <summary>
/// Checks that when merging metadata of two SourceRoot items we don't have any conflicting well-known metadata values.
/// </summary>
private void ReportConflictingWellKnownMetadata(ITaskItem left, ITaskItem right)
{
foreach (var metadataName in Names.SourceRootMetadataNames)
{
var leftValue = left.GetMetadata(metadataName);
var rightValue = right.GetMetadata(metadataName);
if (!string.IsNullOrEmpty(leftValue) && !string.IsNullOrEmpty(rightValue) && leftValue != rightValue)
{
Log.LogWarningFromResources("MapSourceRoots.ContainsDuplicate", Names.SourceRoot, left.ItemSpec, metadataName, leftValue, rightValue);
}
}
}
}
}
......@@ -4,6 +4,7 @@
<!--
Common targets for managed compilers.
-->
<UsingTask TaskName="Microsoft.CodeAnalysis.BuildTasks.MapSourceRoots" AssemblyFile="$(RoslynTargetsPath)\Microsoft.Build.Tasks.CodeAnalysis.dll" Condition="'$(MSBuildAssemblyVersion)' != ''" />
<Target Name="ShimReferencePathsWhenCommonTargetsDoesNotUnderstandReferenceAssemblies"
BeforeTargets="CoreCompile"
......@@ -40,4 +41,100 @@
<UseSharedCompilation>true</UseSharedCompilation>
</PropertyGroup>
</Target>
<!--
========================
DeterministicSourcePaths
========================
Unless specified otherwise enable deterministic source root (PathMap) when building deterministically on CI server, but not for local builds.
In order for the debugger to find source files when debugging a locally built binary the PDB must contain original, unmapped local paths.
-->
<PropertyGroup>
<DeterministicSourcePaths Condition="'$(DeterministicSourcePaths)' == '' and '$(Deterministic)' == 'true' and '$(ContinuousIntegrationBuild)' == 'true'">true</DeterministicSourcePaths>
</PropertyGroup>
<!--
==========
SourceRoot
==========
All source files of the project are expected to be located under one of the directories specified by SourceRoot item group.
This target collects all SourceRoots from various sources.
This target calculates final local path for each SourceRoot and sets SourceRoot.MappedPath metadata accordingly.
The final path is a path with deterministic prefix when DeterministicSourcePaths is true, and the original path otherwise.
In addition, the target validates and deduplicates the SourceRoot items.
InitializeSourceControlInformation is an msbuild target that ensures the SourceRoot items are populated from source control.
The target is available only if SourceControlInformationFeatureSupported is true.
A consumer of SourceRoot.MappedPath metadata, such as Source Link generator, shall depend on this target.
-->
<Target Name="InitializeSourceRootMappedPaths"
DependsOnTargets="_InitializeSourceRootMappedPathsFromSourceControl">
<ItemGroup Condition="'@(_MappedSourceRoot)' != ''">
<_MappedSourceRoot Remove="@(_MappedSourceRoot)" />
</ItemGroup>
<Microsoft.CodeAnalysis.BuildTasks.MapSourceRoots SourceRoots="@(SourceRoot)" Deterministic="$(DeterministicSourcePaths)">
<Output TaskParameter="MappedSourceRoots" ItemName="_MappedSourceRoot" />
</Microsoft.CodeAnalysis.BuildTasks.MapSourceRoots>
<ItemGroup>
<SourceRoot Remove="@(SourceRoot)" />
<SourceRoot Include="@(_MappedSourceRoot)" />
</ItemGroup>
</Target>
<!--
If InitializeSourceControlInformation target isn't supported, we just continue without invoking that synchronization target.
We'll proceed with SourceRoot (and other source control properties) provided by the user (or blank).
-->
<Target Name="_InitializeSourceRootMappedPathsFromSourceControl"
DependsOnTargets="InitializeSourceControlInformation"
Condition="'$(SourceControlInformationFeatureSupported)' == 'true'" />
<!--
=======
PathMap
=======
If DeterministicSourcePaths is true sets PathMap based on SourceRoot.MappedPaths.
This target requires SourceRoot to be initialized in order to calculate the PathMap.
If SourceRoot doesn't contain any top-level roots an error is reported.
-->
<Target Name="_SetPathMapFromSourceRoots"
DependsOnTargets="InitializeSourceRootMappedPaths"
BeforeTargets="CoreCompile"
Condition="'$(DeterministicSourcePaths)' == 'true'">
<ItemGroup>
<_TopLevelSourceRoot Include="@(SourceRoot)" Condition="'%(SourceRoot.NestedRoot)' == ''"/>
</ItemGroup>
<PropertyGroup Condition="'@(_TopLevelSourceRoot)' != ''">
<!-- TODO: Report error/warning if /pathmap doesn't cover all emitted source paths: https://github.com/dotnet/roslyn/issues/23969 -->
<!-- TODO: PathMap should accept and ignore empty mapping: https://github.com/dotnet/roslyn/issues/23523 -->
<PathMap Condition="'$(PathMap)' != ''">,$(PathMap)</PathMap>
<!--
Prepend the SourceRoot.MappedPath values to PathMap, if it already has a value.
For each emitted source path the compiler applies the first mapping that matches the path.
PathMap values set previously will thus only be applied if the mapping provided by
SourceRoot.MappedPath doesn't match. Since SourceRoot.MappedPath is also used by SourceLink
preferring it over manually set PathMap ensures that PathMap is consistent with SourceLink.
TODO: quote the paths to avoid misinterpreting ',' and '=' in them as separators,
but quoting doesn't currently work (see https://github.com/dotnet/roslyn/issues/22835).
-->
<PathMap>@(_TopLevelSourceRoot->'%(Identity)=%(MappedPath)', ',')$(PathMap)</PathMap>
</PropertyGroup>
</Target>
</Project>
\ No newline at end of file
......@@ -18,6 +18,12 @@ internal static class Utilities
{
private const string MSBuildRoslynFolderName = "Roslyn";
/// <summary>
/// Copied from msbuild. ItemSpecs are normalized using this method.
/// </summary>
public static string FixFilePath(string path)
=> string.IsNullOrEmpty(path) || Path.DirectorySeparatorChar == '\\' ? path : path.Replace('\\', '/');
/// <summary>
/// Convert a task item metadata to bool. Throw an exception if the string is badly formed and can't
/// be converted.
......
......@@ -119,6 +119,26 @@
<target state="translated">MSB3402: Při vytváření souboru PDB {0} došlo k chybě. {1}</target>
<note>{StrBegin="MSB3402: "}</note>
</trans-unit>
<trans-unit id="MapSourceRoots.ContainsDuplicate">
<source>{0} contains duplicate items '{1}' with conflicting metadata '{2}': '{3}' and '{4}'</source>
<target state="new">{0} contains duplicate items '{1}' with conflicting metadata '{2}': '{3}' and '{4}'</target>
<note />
</trans-unit>
<trans-unit id="MapSourceRoots.PathMustEndWithSlashOrBackslash">
<source>{0} paths are required to end with a slash or backslash: '{1}'</source>
<target state="new">{0} paths are required to end with a slash or backslash: '{1}'</target>
<note />
</trans-unit>
<trans-unit id="MapSourceRoots.NoTopLevelSourceRoot">
<source>{0} items must include at least one top-level (not nested) item when {1} is true</source>
<target state="new">{0} items must include at least one top-level (not nested) item when {1} is true</target>
<note />
</trans-unit>
<trans-unit id="MapSourceRoots.NoSuchTopLevelSourceRoot">
<source>The value of {0} not found in {1} items, or the corresponding item is not a top-level source root: '{2}'</source>
<target state="new">The value of {0} not found in {1} items, or the corresponding item is not a top-level source root: '{2}'</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -119,6 +119,26 @@
<target state="translated">MSB3402: Fehler beim Erstellen der PDB-Datei "{0}". {1}</target>
<note>{StrBegin="MSB3402: "}</note>
</trans-unit>
<trans-unit id="MapSourceRoots.ContainsDuplicate">
<source>{0} contains duplicate items '{1}' with conflicting metadata '{2}': '{3}' and '{4}'</source>
<target state="new">{0} contains duplicate items '{1}' with conflicting metadata '{2}': '{3}' and '{4}'</target>
<note />
</trans-unit>
<trans-unit id="MapSourceRoots.PathMustEndWithSlashOrBackslash">
<source>{0} paths are required to end with a slash or backslash: '{1}'</source>
<target state="new">{0} paths are required to end with a slash or backslash: '{1}'</target>
<note />
</trans-unit>
<trans-unit id="MapSourceRoots.NoTopLevelSourceRoot">
<source>{0} items must include at least one top-level (not nested) item when {1} is true</source>
<target state="new">{0} items must include at least one top-level (not nested) item when {1} is true</target>
<note />
</trans-unit>
<trans-unit id="MapSourceRoots.NoSuchTopLevelSourceRoot">
<source>The value of {0} not found in {1} items, or the corresponding item is not a top-level source root: '{2}'</source>
<target state="new">The value of {0} not found in {1} items, or the corresponding item is not a top-level source root: '{2}'</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -119,6 +119,26 @@
<target state="translated">MSB3402: Error al crear el archivo pdb "{0}". {1}</target>
<note>{StrBegin="MSB3402: "}</note>
</trans-unit>
<trans-unit id="MapSourceRoots.ContainsDuplicate">
<source>{0} contains duplicate items '{1}' with conflicting metadata '{2}': '{3}' and '{4}'</source>
<target state="new">{0} contains duplicate items '{1}' with conflicting metadata '{2}': '{3}' and '{4}'</target>
<note />
</trans-unit>
<trans-unit id="MapSourceRoots.PathMustEndWithSlashOrBackslash">
<source>{0} paths are required to end with a slash or backslash: '{1}'</source>
<target state="new">{0} paths are required to end with a slash or backslash: '{1}'</target>
<note />
</trans-unit>
<trans-unit id="MapSourceRoots.NoTopLevelSourceRoot">
<source>{0} items must include at least one top-level (not nested) item when {1} is true</source>
<target state="new">{0} items must include at least one top-level (not nested) item when {1} is true</target>
<note />
</trans-unit>
<trans-unit id="MapSourceRoots.NoSuchTopLevelSourceRoot">
<source>The value of {0} not found in {1} items, or the corresponding item is not a top-level source root: '{2}'</source>
<target state="new">The value of {0} not found in {1} items, or the corresponding item is not a top-level source root: '{2}'</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -119,6 +119,26 @@
<target state="translated">MSB3402: Une erreur s'est produite durant la création du fichier pdb "{0}". {1}</target>
<note>{StrBegin="MSB3402: "}</note>
</trans-unit>
<trans-unit id="MapSourceRoots.ContainsDuplicate">
<source>{0} contains duplicate items '{1}' with conflicting metadata '{2}': '{3}' and '{4}'</source>
<target state="new">{0} contains duplicate items '{1}' with conflicting metadata '{2}': '{3}' and '{4}'</target>
<note />
</trans-unit>
<trans-unit id="MapSourceRoots.PathMustEndWithSlashOrBackslash">
<source>{0} paths are required to end with a slash or backslash: '{1}'</source>
<target state="new">{0} paths are required to end with a slash or backslash: '{1}'</target>
<note />
</trans-unit>
<trans-unit id="MapSourceRoots.NoTopLevelSourceRoot">
<source>{0} items must include at least one top-level (not nested) item when {1} is true</source>
<target state="new">{0} items must include at least one top-level (not nested) item when {1} is true</target>
<note />
</trans-unit>
<trans-unit id="MapSourceRoots.NoSuchTopLevelSourceRoot">
<source>The value of {0} not found in {1} items, or the corresponding item is not a top-level source root: '{2}'</source>
<target state="new">The value of {0} not found in {1} items, or the corresponding item is not a top-level source root: '{2}'</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -119,6 +119,26 @@
<target state="translated">MSB3402: si è verificato un errore durante la creazione del file PDB "{0}". {1}</target>
<note>{StrBegin="MSB3402: "}</note>
</trans-unit>
<trans-unit id="MapSourceRoots.ContainsDuplicate">
<source>{0} contains duplicate items '{1}' with conflicting metadata '{2}': '{3}' and '{4}'</source>
<target state="new">{0} contains duplicate items '{1}' with conflicting metadata '{2}': '{3}' and '{4}'</target>
<note />
</trans-unit>
<trans-unit id="MapSourceRoots.PathMustEndWithSlashOrBackslash">
<source>{0} paths are required to end with a slash or backslash: '{1}'</source>
<target state="new">{0} paths are required to end with a slash or backslash: '{1}'</target>
<note />
</trans-unit>
<trans-unit id="MapSourceRoots.NoTopLevelSourceRoot">
<source>{0} items must include at least one top-level (not nested) item when {1} is true</source>
<target state="new">{0} items must include at least one top-level (not nested) item when {1} is true</target>
<note />
</trans-unit>
<trans-unit id="MapSourceRoots.NoSuchTopLevelSourceRoot">
<source>The value of {0} not found in {1} items, or the corresponding item is not a top-level source root: '{2}'</source>
<target state="new">The value of {0} not found in {1} items, or the corresponding item is not a top-level source root: '{2}'</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -119,6 +119,26 @@
<target state="translated">MSB3402: pdb ファイル "{0}" の作成中にエラーが発生しました。{1}</target>
<note>{StrBegin="MSB3402: "}</note>
</trans-unit>
<trans-unit id="MapSourceRoots.ContainsDuplicate">
<source>{0} contains duplicate items '{1}' with conflicting metadata '{2}': '{3}' and '{4}'</source>
<target state="new">{0} contains duplicate items '{1}' with conflicting metadata '{2}': '{3}' and '{4}'</target>
<note />
</trans-unit>
<trans-unit id="MapSourceRoots.PathMustEndWithSlashOrBackslash">
<source>{0} paths are required to end with a slash or backslash: '{1}'</source>
<target state="new">{0} paths are required to end with a slash or backslash: '{1}'</target>
<note />
</trans-unit>
<trans-unit id="MapSourceRoots.NoTopLevelSourceRoot">
<source>{0} items must include at least one top-level (not nested) item when {1} is true</source>
<target state="new">{0} items must include at least one top-level (not nested) item when {1} is true</target>
<note />
</trans-unit>
<trans-unit id="MapSourceRoots.NoSuchTopLevelSourceRoot">
<source>The value of {0} not found in {1} items, or the corresponding item is not a top-level source root: '{2}'</source>
<target state="new">The value of {0} not found in {1} items, or the corresponding item is not a top-level source root: '{2}'</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -119,6 +119,26 @@
<target state="translated">MSB3402: pdb 파일 "{0}"을(를) 만드는 동안 오류가 발생했습니다. {1}</target>
<note>{StrBegin="MSB3402: "}</note>
</trans-unit>
<trans-unit id="MapSourceRoots.ContainsDuplicate">
<source>{0} contains duplicate items '{1}' with conflicting metadata '{2}': '{3}' and '{4}'</source>
<target state="new">{0} contains duplicate items '{1}' with conflicting metadata '{2}': '{3}' and '{4}'</target>
<note />
</trans-unit>
<trans-unit id="MapSourceRoots.PathMustEndWithSlashOrBackslash">
<source>{0} paths are required to end with a slash or backslash: '{1}'</source>
<target state="new">{0} paths are required to end with a slash or backslash: '{1}'</target>
<note />
</trans-unit>
<trans-unit id="MapSourceRoots.NoTopLevelSourceRoot">
<source>{0} items must include at least one top-level (not nested) item when {1} is true</source>
<target state="new">{0} items must include at least one top-level (not nested) item when {1} is true</target>
<note />
</trans-unit>
<trans-unit id="MapSourceRoots.NoSuchTopLevelSourceRoot">
<source>The value of {0} not found in {1} items, or the corresponding item is not a top-level source root: '{2}'</source>
<target state="new">The value of {0} not found in {1} items, or the corresponding item is not a top-level source root: '{2}'</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -119,6 +119,26 @@
<target state="translated">MSB3402: Wystąpił błąd podczas tworzenia pliku pdb „{0}”. {1}</target>
<note>{StrBegin="MSB3402: "}</note>
</trans-unit>
<trans-unit id="MapSourceRoots.ContainsDuplicate">
<source>{0} contains duplicate items '{1}' with conflicting metadata '{2}': '{3}' and '{4}'</source>
<target state="new">{0} contains duplicate items '{1}' with conflicting metadata '{2}': '{3}' and '{4}'</target>
<note />
</trans-unit>
<trans-unit id="MapSourceRoots.PathMustEndWithSlashOrBackslash">
<source>{0} paths are required to end with a slash or backslash: '{1}'</source>
<target state="new">{0} paths are required to end with a slash or backslash: '{1}'</target>
<note />
</trans-unit>
<trans-unit id="MapSourceRoots.NoTopLevelSourceRoot">
<source>{0} items must include at least one top-level (not nested) item when {1} is true</source>
<target state="new">{0} items must include at least one top-level (not nested) item when {1} is true</target>
<note />
</trans-unit>
<trans-unit id="MapSourceRoots.NoSuchTopLevelSourceRoot">
<source>The value of {0} not found in {1} items, or the corresponding item is not a top-level source root: '{2}'</source>
<target state="new">The value of {0} not found in {1} items, or the corresponding item is not a top-level source root: '{2}'</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -119,6 +119,26 @@
<target state="translated">MSB3402: houve um erro na criação do arquivo pdb "{0}". {1}</target>
<note>{StrBegin="MSB3402: "}</note>
</trans-unit>
<trans-unit id="MapSourceRoots.ContainsDuplicate">
<source>{0} contains duplicate items '{1}' with conflicting metadata '{2}': '{3}' and '{4}'</source>
<target state="new">{0} contains duplicate items '{1}' with conflicting metadata '{2}': '{3}' and '{4}'</target>
<note />
</trans-unit>
<trans-unit id="MapSourceRoots.PathMustEndWithSlashOrBackslash">
<source>{0} paths are required to end with a slash or backslash: '{1}'</source>
<target state="new">{0} paths are required to end with a slash or backslash: '{1}'</target>
<note />
</trans-unit>
<trans-unit id="MapSourceRoots.NoTopLevelSourceRoot">
<source>{0} items must include at least one top-level (not nested) item when {1} is true</source>
<target state="new">{0} items must include at least one top-level (not nested) item when {1} is true</target>
<note />
</trans-unit>
<trans-unit id="MapSourceRoots.NoSuchTopLevelSourceRoot">
<source>The value of {0} not found in {1} items, or the corresponding item is not a top-level source root: '{2}'</source>
<target state="new">The value of {0} not found in {1} items, or the corresponding item is not a top-level source root: '{2}'</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -119,6 +119,26 @@
<target state="translated">MSB3402: ошибка создания PDB-файла "{0}". {1}</target>
<note>{StrBegin="MSB3402: "}</note>
</trans-unit>
<trans-unit id="MapSourceRoots.ContainsDuplicate">
<source>{0} contains duplicate items '{1}' with conflicting metadata '{2}': '{3}' and '{4}'</source>
<target state="new">{0} contains duplicate items '{1}' with conflicting metadata '{2}': '{3}' and '{4}'</target>
<note />
</trans-unit>
<trans-unit id="MapSourceRoots.PathMustEndWithSlashOrBackslash">
<source>{0} paths are required to end with a slash or backslash: '{1}'</source>
<target state="new">{0} paths are required to end with a slash or backslash: '{1}'</target>
<note />
</trans-unit>
<trans-unit id="MapSourceRoots.NoTopLevelSourceRoot">
<source>{0} items must include at least one top-level (not nested) item when {1} is true</source>
<target state="new">{0} items must include at least one top-level (not nested) item when {1} is true</target>
<note />
</trans-unit>
<trans-unit id="MapSourceRoots.NoSuchTopLevelSourceRoot">
<source>The value of {0} not found in {1} items, or the corresponding item is not a top-level source root: '{2}'</source>
<target state="new">The value of {0} not found in {1} items, or the corresponding item is not a top-level source root: '{2}'</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -119,6 +119,26 @@
<target state="translated">MSB3402: "{0}" pdb dosyası oluşturulurken hata oluştu. {1}</target>
<note>{StrBegin="MSB3402: "}</note>
</trans-unit>
<trans-unit id="MapSourceRoots.ContainsDuplicate">
<source>{0} contains duplicate items '{1}' with conflicting metadata '{2}': '{3}' and '{4}'</source>
<target state="new">{0} contains duplicate items '{1}' with conflicting metadata '{2}': '{3}' and '{4}'</target>
<note />
</trans-unit>
<trans-unit id="MapSourceRoots.PathMustEndWithSlashOrBackslash">
<source>{0} paths are required to end with a slash or backslash: '{1}'</source>
<target state="new">{0} paths are required to end with a slash or backslash: '{1}'</target>
<note />
</trans-unit>
<trans-unit id="MapSourceRoots.NoTopLevelSourceRoot">
<source>{0} items must include at least one top-level (not nested) item when {1} is true</source>
<target state="new">{0} items must include at least one top-level (not nested) item when {1} is true</target>
<note />
</trans-unit>
<trans-unit id="MapSourceRoots.NoSuchTopLevelSourceRoot">
<source>The value of {0} not found in {1} items, or the corresponding item is not a top-level source root: '{2}'</source>
<target state="new">The value of {0} not found in {1} items, or the corresponding item is not a top-level source root: '{2}'</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -119,6 +119,26 @@
<target state="translated">MSB3402: 创建 pdb 文件“{0}”时出现错误。{1}</target>
<note>{StrBegin="MSB3402: "}</note>
</trans-unit>
<trans-unit id="MapSourceRoots.ContainsDuplicate">
<source>{0} contains duplicate items '{1}' with conflicting metadata '{2}': '{3}' and '{4}'</source>
<target state="new">{0} contains duplicate items '{1}' with conflicting metadata '{2}': '{3}' and '{4}'</target>
<note />
</trans-unit>
<trans-unit id="MapSourceRoots.PathMustEndWithSlashOrBackslash">
<source>{0} paths are required to end with a slash or backslash: '{1}'</source>
<target state="new">{0} paths are required to end with a slash or backslash: '{1}'</target>
<note />
</trans-unit>
<trans-unit id="MapSourceRoots.NoTopLevelSourceRoot">
<source>{0} items must include at least one top-level (not nested) item when {1} is true</source>
<target state="new">{0} items must include at least one top-level (not nested) item when {1} is true</target>
<note />
</trans-unit>
<trans-unit id="MapSourceRoots.NoSuchTopLevelSourceRoot">
<source>The value of {0} not found in {1} items, or the corresponding item is not a top-level source root: '{2}'</source>
<target state="new">The value of {0} not found in {1} items, or the corresponding item is not a top-level source root: '{2}'</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -119,6 +119,26 @@
<target state="translated">MSB3402: 建立 PDB 檔案 "{0}" 時發生錯誤。{1}</target>
<note>{StrBegin="MSB3402: "}</note>
</trans-unit>
<trans-unit id="MapSourceRoots.ContainsDuplicate">
<source>{0} contains duplicate items '{1}' with conflicting metadata '{2}': '{3}' and '{4}'</source>
<target state="new">{0} contains duplicate items '{1}' with conflicting metadata '{2}': '{3}' and '{4}'</target>
<note />
</trans-unit>
<trans-unit id="MapSourceRoots.PathMustEndWithSlashOrBackslash">
<source>{0} paths are required to end with a slash or backslash: '{1}'</source>
<target state="new">{0} paths are required to end with a slash or backslash: '{1}'</target>
<note />
</trans-unit>
<trans-unit id="MapSourceRoots.NoTopLevelSourceRoot">
<source>{0} items must include at least one top-level (not nested) item when {1} is true</source>
<target state="new">{0} items must include at least one top-level (not nested) item when {1} is true</target>
<note />
</trans-unit>
<trans-unit id="MapSourceRoots.NoSuchTopLevelSourceRoot">
<source>The value of {0} not found in {1} items, or the corresponding item is not a top-level source root: '{2}'</source>
<target state="new">The value of {0} not found in {1} items, or the corresponding item is not a top-level source root: '{2}'</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.IO;
using Roslyn.Test.Utilities;
namespace Microsoft.CodeAnalysis.BuildTasks.UnitTests
{
public class DotNetSdkTests : DotNetSdkTestBase
{
[ConditionalFact(typeof(DotNetSdkAvailable))]
public void TestSourceLink()
{
var sourcePackageDir = Temp.CreateDirectory();
// TODO: test escaping (https://github.com/dotnet/roslyn/issues/22835): .CreateDirectory("a=b, c");
var libFile = sourcePackageDir.CreateFile("lib.cs").WriteAllText("class Lib { public void M() { } }");
var root1 = Path.GetFullPath(ProjectDir.Path + "\\");
var root2 = Path.GetFullPath(sourcePackageDir.Path + "\\");
var sourceLinkJsonPath = Path.Combine(ObjDir.Path, ProjectName + ".sourcelink.json");
var sourcePackageTargets = $@"
<ItemGroup>
<Compile Include=""{libFile.Path}"" Link=""Lib.cs"" />
<SourceRoot Include=""{root2}"" SourceLinkUrl=""https://raw.githubusercontent.com/Source/Package/*""/>
</ItemGroup>
";
var sourceLinkPackageTargets = $@"
<PropertyGroup>
<SourceLink>{sourceLinkJsonPath}</SourceLink>
</PropertyGroup>
<Target Name=""_InitializeSourceControlProperties"" BeforeTargets=""InitializeSourceControlInformation"">
<ItemGroup>
<SourceRoot Include=""{root1}"" SourceControl=""git"" SourceLinkUrl=""https://raw.githubusercontent.com/R1/*""/>
<SourceRoot Include=""{root1}sub1\"" SourceControl=""git"" NestedRoot=""sub1"" ContainingRoot=""{root1}"" SourceLinkUrl=""https://raw.githubusercontent.com/M1/*""/>
<SourceRoot Include=""{root1}sub2\"" SourceControl=""git"" NestedRoot=""sub2"" ContainingRoot=""{root1}"" SourceLinkUrl=""https://raw.githubusercontent.com/M2/*""/>
</ItemGroup>
</Target>
<Target Name=""_GenerateSourceLinkFile""
DependsOnTargets=""InitializeSourceRootMappedPaths""
BeforeTargets=""CoreCompile""
Outputs=""$(SourceLink)"">
<WriteLinesToFile File=""$(SourceLink)""
Lines=""@(SourceRoot->'[%(MappedPath)]=[%(SourceLinkUrl)]', ',')""
Overwrite=""true""
Encoding=""UTF-8"" />
<ItemGroup>
<FileWrites Include=""$(SourceLink)"" />
</ItemGroup>
</Target>
";
// deterministic CI build:
VerifyValues(
props: $@"
<Project>
<PropertyGroup>
<SourceControlInformationFeatureSupported>true</SourceControlInformationFeatureSupported>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
<PathMap>PreviousPathMap</PathMap>
</PropertyGroup>
{sourcePackageTargets}
{sourceLinkPackageTargets}
</Project>",
targets: new[]
{
"CoreCompile"
},
expressions: new[]
{
"@(SourceRoot->'%(Identity): %(MappedPath)')",
"$(DeterministicSourcePaths)",
"$(PathMap)"
},
expectedResults: new[]
{
$@"{root2}: /_1/",
$@"{root1}: /_/",
$@"{root1}sub1\: /_/sub1/",
$@"{root1}sub2\: /_/sub2/",
@"true",
$@"{root2}=/_1/,{root1}=/_/,PreviousPathMap"
});
AssertEx.AssertEqualToleratingWhitespaceDifferences(
"[/_1/]=[https://raw.githubusercontent.com/Source/Package/*]," +
"[/_/]=[https://raw.githubusercontent.com/R1/*]," +
"[/_/sub1/]=[https://raw.githubusercontent.com/M1/*]," +
"[/_/sub2/]=[https://raw.githubusercontent.com/M2/*]",
File.ReadAllText(sourceLinkJsonPath));
// non-deterministic CI build:
VerifyValues(
props: $@"
<Project>
<PropertyGroup>
<SourceControlInformationFeatureSupported>true</SourceControlInformationFeatureSupported>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
<Deterministic>false</Deterministic>
</PropertyGroup>
{sourcePackageTargets}
{sourceLinkPackageTargets}
</Project>",
targets: new[]
{
"CoreCompile"
},
expressions: new[]
{
"@(SourceRoot->'%(Identity): %(MappedPath)')",
"$(DeterministicSourcePaths)",
"$(PathMap)"
},
expectedResults: new[]
{
$@"{root2}: {root2}",
$@"{root1}: {root1}",
$@"{root1}sub1\: {root1}sub1\",
$@"{root1}sub2\: {root1}sub2\",
@"",
$@""
});
AssertEx.AssertEqualToleratingWhitespaceDifferences(
$@"[{root2}]=[https://raw.githubusercontent.com/Source/Package/*]," +
$@"[{root1}]=[https://raw.githubusercontent.com/R1/*]," +
$@"[{root1}sub1\]=[https://raw.githubusercontent.com/M1/*]," +
$@"[{root1}sub2\]=[https://raw.githubusercontent.com/M2/*]",
File.ReadAllText(sourceLinkJsonPath));
// deterministic local build:
VerifyValues(
props: $@"
<Project>
<PropertyGroup>
<SourceControlInformationFeatureSupported>true</SourceControlInformationFeatureSupported>
<ContinuousIntegrationBuild>false</ContinuousIntegrationBuild>
</PropertyGroup>
{sourcePackageTargets}
{sourceLinkPackageTargets}
</Project>",
targets: new[]
{
"CoreCompile"
},
expressions: new[]
{
"@(SourceRoot->'%(Identity): %(MappedPath)')",
"$(DeterministicSourcePaths)",
"$(PathMap)"
},
expectedResults: new[]
{
$@"{root2}: {root2}",
$@"{root1}: {root1}",
$@"{root1}sub1\: {root1}sub1\",
$@"{root1}sub2\: {root1}sub2\",
@"",
$@""
});
AssertEx.AssertEqualToleratingWhitespaceDifferences(
$@"[{root2}]=[https://raw.githubusercontent.com/Source/Package/*]," +
$@"[{root1}]=[https://raw.githubusercontent.com/R1/*]," +
$@"[{root1}sub1\]=[https://raw.githubusercontent.com/M1/*]," +
$@"[{root1}sub2\]=[https://raw.githubusercontent.com/M2/*]",
File.ReadAllText(sourceLinkJsonPath));
// DeterministicSourcePaths override:
VerifyValues(
props: $@"
<Project>
<PropertyGroup>
<SourceControlInformationFeatureSupported>true</SourceControlInformationFeatureSupported>
<DeterministicSourcePaths>false</DeterministicSourcePaths>
</PropertyGroup>
{sourcePackageTargets}
{sourceLinkPackageTargets}
</Project>",
targets: new[]
{
"CoreCompile"
},
expressions: new[]
{
"@(SourceRoot->'%(Identity): %(MappedPath)')",
"$(DeterministicSourcePaths)",
"$(PathMap)"
},
expectedResults: new[]
{
$@"{root2}: {root2}",
$@"{root1}: {root1}",
$@"{root1}sub1\: {root1}sub1\",
$@"{root1}sub2\: {root1}sub2\",
@"false",
$@""
});
AssertEx.AssertEqualToleratingWhitespaceDifferences(
$@"[{root2}]=[https://raw.githubusercontent.com/Source/Package/*]," +
$@"[{root1}]=[https://raw.githubusercontent.com/R1/*]," +
$@"[{root1}sub1\]=[https://raw.githubusercontent.com/M1/*]," +
$@"[{root1}sub2\]=[https://raw.githubusercontent.com/M2/*]",
File.ReadAllText(sourceLinkJsonPath));
// SourceControlInformationFeatureSupported = false:
VerifyValues(
props: $@"
<Project>
<PropertyGroup>
<SourceControlInformationFeatureSupported>false</SourceControlInformationFeatureSupported>
<DeterministicSourcePaths>true</DeterministicSourcePaths>
</PropertyGroup>
<ItemGroup>
<SourceRoot Include=""{root1}"" SourceLinkUrl=""https://raw.githubusercontent.com/R1/*"" />
</ItemGroup>
{sourcePackageTargets}
{sourceLinkPackageTargets}
</Project>",
targets: new[]
{
"CoreCompile"
},
expressions: new[]
{
"@(SourceRoot->'%(Identity): %(MappedPath)')",
"$(DeterministicSourcePaths)",
"$(PathMap)"
},
expectedResults: new[]
{
$@"{root1}: /_/",
$@"{root2}: /_1/",
@"true",
$@"{root1}=/_/,{root2}=/_1/"
});
AssertEx.AssertEqualToleratingWhitespaceDifferences(
$@"[/_/]=[https://raw.githubusercontent.com/R1/*]," +
$@"[/_1/]=[https://raw.githubusercontent.com/Source/Package/*]",
File.ReadAllText(sourceLinkJsonPath));
// No SourceLink package:
VerifyValues(
props: $@"
<Project>
<PropertyGroup>
<SourceControlInformationFeatureSupported>true</SourceControlInformationFeatureSupported>
<DeterministicSourcePaths>true</DeterministicSourcePaths>
</PropertyGroup>
<ItemGroup>
<SourceRoot Include=""{root1}"" SourceLinkUrl=""https://raw.githubusercontent.com/R1/*"" />
</ItemGroup>
{sourcePackageTargets}
</Project>",
targets: new[]
{
"CoreCompile"
},
expressions: new[]
{
"@(SourceRoot->'%(Identity): %(MappedPath)')",
"$(DeterministicSourcePaths)",
"$(PathMap)"
},
expectedResults: new[]
{
$@"{root1}: /_/",
$@"{root2}: /_1/",
@"true",
$@"{root1}=/_/,{root2}=/_1/"
});
AssertEx.AssertEqualToleratingWhitespaceDifferences(
$@"[/_/]=[https://raw.githubusercontent.com/R1/*]," +
$@"[/_1/]=[https://raw.githubusercontent.com/Source/Package/*]",
File.ReadAllText(sourceLinkJsonPath));
}
}
}
......@@ -25,6 +25,7 @@
<ProjectReference Include="..\..\VisualBasic\Portable\BasicCodeAnalysis.vbproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="Microsoft.Build.Engine" />
<Reference Include="System" />
<Reference Include="System.Xml" />
<PackageReference Include="xunit" Version="$(xunitVersion)" />
......@@ -38,4 +39,25 @@
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<ItemGroup>
<Content Include="TestResources\**\*.*" />
<EmbeddedResource Include="TestResources\**\*.*" />
</ItemGroup>
<PropertyGroup>
<_DotNetSdkVersionFile>$(IntermediateOutputPath)\DotNetSdkVersion.g.cs</_DotNetSdkVersionFile>
</PropertyGroup>
<Target Name="_GenerateSdkVersionAttribute" BeforeTargets="CoreCompile" Outputs="$(_DotNetSdkVersionFile)">
<ItemGroup>
<_Attribute Include="Microsoft.CodeAnalysis.BuildTasks.UnitTests.DotNetSdkVersionAttribute">
<_Parameter1>$(NETCoreSdkVersion)</_Parameter1>
</_Attribute>
</ItemGroup>
<WriteCodeFragment AssemblyAttributes="@(_Attribute)" Language="$(Language)" OutputFile="$(_DotNetSdkVersionFile)">
<Output TaskParameter="OutputFile" ItemName="Compile" />
<Output TaskParameter="OutputFile" ItemName="FileWrites" />
</WriteCodeFragment>
</Target>
</Project>
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Roslyn.Test.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.BuildTasks.UnitTests
{
public sealed class MapSourceRootsTests
{
private string InspectSourceRoot(ITaskItem sourceRoot)
=> $"'{sourceRoot.ItemSpec}'" +
$" SourceControl='{sourceRoot.GetMetadata("SourceControl")}'" +
$" RevisionId='{sourceRoot.GetMetadata("RevisionId")}'" +
$" NestedRoot='{sourceRoot.GetMetadata("NestedRoot")}'" +
$" ContainingRoot='{sourceRoot.GetMetadata("ContainingRoot")}'" +
$" MappedPath='{sourceRoot.GetMetadata("MappedPath")}'" +
$" SourceLinkUrl='{sourceRoot.GetMetadata("SourceLinkUrl")}'";
[Fact]
public void BasicMapping()
{
var engine = new MockEngine();
var task = new MapSourceRoots
{
BuildEngine = engine,
SourceRoots = new[]
{
new TaskItem(@"c:\packages\SourcePackage1\"),
new TaskItem(@"/packages/SourcePackage2/"),
new TaskItem(@"c:\MyProjects\MyProject\", new Dictionary<string, string>
{
{ "SourceControl", "Git" },
}),
new TaskItem(@"c:\MyProjects\MyProject\a\b\", new Dictionary<string, string>
{
{ "SourceControl", "Git" },
{ "NestedRoot", "a/b" },
{ "ContainingRoot", @"c:\MyProjects\MyProject\" },
{ "some metadata", "some value" },
}),
},
Deterministic = true
};
bool result = task.Execute();
AssertEx.AssertEqualToleratingWhitespaceDifferences("", engine.Log);
Assert.Equal(4, task.MappedSourceRoots.Length);
Assert.Equal(Utilities.FixFilePath(@"c:\packages\SourcePackage1\"), task.MappedSourceRoots[0].ItemSpec);
Assert.Equal(@"/_1/", task.MappedSourceRoots[0].GetMetadata("MappedPath"));
Assert.Equal(Utilities.FixFilePath(@"/packages/SourcePackage2/"), task.MappedSourceRoots[1].ItemSpec);
Assert.Equal(@"/_2/", task.MappedSourceRoots[1].GetMetadata("MappedPath"));
Assert.Equal(Utilities.FixFilePath(@"c:\MyProjects\MyProject\"), task.MappedSourceRoots[2].ItemSpec);
Assert.Equal(@"/_/", task.MappedSourceRoots[2].GetMetadata("MappedPath"));
Assert.Equal(@"Git", task.MappedSourceRoots[2].GetMetadata("SourceControl"));
Assert.Equal(Utilities.FixFilePath(@"c:\MyProjects\MyProject\a\b\"), task.MappedSourceRoots[3].ItemSpec);
Assert.Equal(@"/_/a/b/", task.MappedSourceRoots[3].GetMetadata("MappedPath"));
Assert.Equal(@"Git", task.MappedSourceRoots[3].GetMetadata("SourceControl"));
Assert.Equal(@"some value", task.MappedSourceRoots[3].GetMetadata("some metadata"));
Assert.True(result);
}
[Fact]
public void InvalidChars()
{
var engine = new MockEngine();
var task = new MapSourceRoots
{
BuildEngine = engine,
SourceRoots = new[]
{
new TaskItem(@"!@#:;$%^&*()_+|{}\"),
new TaskItem(@"****/", new Dictionary<string, string>
{
{ "SourceControl", "Git" },
}),
new TaskItem(@"****\|||:;\", new Dictionary<string, string>
{
{ "SourceControl", "Git" },
{ "NestedRoot", "|||:;" },
{ "ContainingRoot", @"****/" },
}),
},
Deterministic = true
};
bool result = task.Execute();
AssertEx.AssertEqualToleratingWhitespaceDifferences("", engine.Log);
Assert.Equal(3, task.MappedSourceRoots.Length);
Assert.Equal(Utilities.FixFilePath(@"!@#:;$%^&*()_+|{}\"), task.MappedSourceRoots[0].ItemSpec);
Assert.Equal(@"/_1/", task.MappedSourceRoots[0].GetMetadata("MappedPath"));
Assert.Equal(Utilities.FixFilePath("****/"), task.MappedSourceRoots[1].ItemSpec);
Assert.Equal(@"/_/", task.MappedSourceRoots[1].GetMetadata("MappedPath"));
Assert.Equal(@"Git", task.MappedSourceRoots[1].GetMetadata("SourceControl"));
Assert.Equal(Utilities.FixFilePath(@"****\|||:;\"), task.MappedSourceRoots[2].ItemSpec);
Assert.Equal(@"/_/|||:;/", task.MappedSourceRoots[2].GetMetadata("MappedPath"));
Assert.Equal(@"Git", task.MappedSourceRoots[2].GetMetadata("SourceControl"));
Assert.True(result);
}
[Fact]
public void SourceRootPaths_EndWithSeparator()
{
var engine = new MockEngine();
var task = new MapSourceRoots
{
BuildEngine = engine,
SourceRoots = new[]
{
new TaskItem(@"C:\"),
new TaskItem(@"C:/"),
new TaskItem(@"C:"),
new TaskItem(@"C"),
},
Deterministic = true
};
bool result = task.Execute();
AssertEx.AssertEqualToleratingWhitespaceDifferences(@"
ERROR : SourceRoot paths are required to end with a slash or backslash: 'C:'
ERROR : SourceRoot paths are required to end with a slash or backslash: 'C'
", engine.Log);
Assert.False(result);
}
[Fact]
public void NestedRoots_Separators()
{
var engine = new MockEngine();
var task = new MapSourceRoots
{
BuildEngine = engine,
SourceRoots = new[]
{
new TaskItem(@"c:\MyProjects\MyProject\"),
new TaskItem(@"c:\MyProjects\MyProject\a\a\", new Dictionary<string, string>
{
{ "NestedRoot", @"a/a/" },
{ "ContainingRoot", @"c:\MyProjects\MyProject\" },
}),
new TaskItem(@"c:\MyProjects\MyProject\a\b\", new Dictionary<string, string>
{
{ "NestedRoot", @"a/b\" },
{ "ContainingRoot", @"c:\MyProjects\MyProject\" },
}),
new TaskItem(@"c:\MyProjects\MyProject\a\c\", new Dictionary<string, string>
{
{ "NestedRoot", @"a\c" },
{ "ContainingRoot", @"c:\MyProjects\MyProject\" },
}),
},
Deterministic = true
};
bool result = task.Execute();
AssertEx.AssertEqualToleratingWhitespaceDifferences("", engine.Log);
Assert.Equal(4, task.MappedSourceRoots.Length);
Assert.Equal(Utilities.FixFilePath(@"c:\MyProjects\MyProject\"), task.MappedSourceRoots[0].ItemSpec);
Assert.Equal(@"/_/", task.MappedSourceRoots[0].GetMetadata("MappedPath"));
Assert.Equal(Utilities.FixFilePath(@"c:\MyProjects\MyProject\a\a\"), task.MappedSourceRoots[1].ItemSpec);
Assert.Equal(@"/_/a/a/", task.MappedSourceRoots[1].GetMetadata("MappedPath"));
Assert.Equal(Utilities.FixFilePath(@"c:\MyProjects\MyProject\a\b\"), task.MappedSourceRoots[2].ItemSpec);
Assert.Equal(@"/_/a/b/", task.MappedSourceRoots[2].GetMetadata("MappedPath"));
Assert.Equal(Utilities.FixFilePath(@"c:\MyProjects\MyProject\a\c\"), task.MappedSourceRoots[3].ItemSpec);
Assert.Equal(@"/_/a/c/", task.MappedSourceRoots[3].GetMetadata("MappedPath"));
Assert.True(result);
}
[Fact]
public void SourceRootCaseSensitive()
{
var engine = new MockEngine();
var task = new MapSourceRoots
{
BuildEngine = engine,
SourceRoots = new[]
{
new TaskItem(@"c:\packages\SourcePackage1\"),
new TaskItem(@"C:\packages\SourcePackage1\"),
new TaskItem(@"c:\packages\SourcePackage2\"),
},
Deterministic = true
};
bool result = task.Execute();
AssertEx.AssertEqualToleratingWhitespaceDifferences("", engine.Log);
Assert.Equal(3, task.MappedSourceRoots.Length);
Assert.Equal(Utilities.FixFilePath(@"c:\packages\SourcePackage1\"), task.MappedSourceRoots[0].ItemSpec);
Assert.Equal(@"/_/", task.MappedSourceRoots[0].GetMetadata("MappedPath"));
Assert.Equal(Utilities.FixFilePath(@"C:\packages\SourcePackage1\"), task.MappedSourceRoots[1].ItemSpec);
Assert.Equal(@"/_1/", task.MappedSourceRoots[1].GetMetadata("MappedPath"));
Assert.Equal(Utilities.FixFilePath(@"c:\packages\SourcePackage2\"), task.MappedSourceRoots[2].ItemSpec);
Assert.Equal(@"/_2/", task.MappedSourceRoots[2].GetMetadata("MappedPath"));
Assert.True(result);
}
[Fact]
public void Error_Recursion()
{
var engine = new MockEngine();
var path1 = Utilities.FixFilePath(@"c:\MyProjects\MyProject\a\1\");
var path2 = Utilities.FixFilePath(@"c:\MyProjects\MyProject\a\2\");
var path3 = Utilities.FixFilePath(@"c:\MyProjects\MyProject\");
var task = new MapSourceRoots
{
BuildEngine = engine,
SourceRoots = new[]
{
new TaskItem(path1, new Dictionary<string, string>
{
{ "ContainingRoot", path2 },
{ "NestedRoot", "a/1" },
}),
new TaskItem(path2, new Dictionary<string, string>
{
{ "ContainingRoot", path1 },
{ "NestedRoot", "a/2" },
}),
new TaskItem(path3),
},
Deterministic = true
};
bool result = task.Execute();
AssertEx.AssertEqualToleratingWhitespaceDifferences(
"ERROR : " + string.Format(task.Log.FormatResourceString(
"MapSourceRoots.NoSuchTopLevelSourceRoot", "SourceRoot.ContainingRoot", "SourceRoot", path2)) + Environment.NewLine +
"ERROR : " + string.Format(task.Log.FormatResourceString(
"MapSourceRoots.NoSuchTopLevelSourceRoot", "SourceRoot.ContainingRoot", "SourceRoot", path1)) + Environment.NewLine, engine.Log);
Assert.Null(task.MappedSourceRoots);
Assert.False(result);
}
[Theory]
[InlineData(new object[] { true })]
[InlineData(new object[] { false })]
public void MetadataMerge1(bool deterministic)
{
var engine = new MockEngine();
var path1 = Utilities.FixFilePath(@"c:\packages\SourcePackage1\");
var path2 = Utilities.FixFilePath(@"c:\packages\SourcePackage2\");
var path3 = Utilities.FixFilePath(@"c:\packages\SourcePackage3\");
var task = new MapSourceRoots
{
BuildEngine = engine,
SourceRoots = new[]
{
new TaskItem(path1, new Dictionary<string, string>
{
{ "NestedRoot", @"NR1A" },
{ "ContainingRoot", path3 },
{ "RevisionId", "RevId1" },
{ "SourceControl", "git" },
{ "MappedPath", "MP1" },
{ "SourceLinkUrl", "URL1" },
}),
new TaskItem(path1, new Dictionary<string, string>
{
{ "NestedRoot", @"NR1B" },
{ "ContainingRoot", @"CR" },
{ "RevisionId", "RevId2" },
{ "SourceControl", "tfvc" },
{ "MappedPath", "MP2" },
{ "SourceLinkUrl", "URL2" },
}),
new TaskItem(path2, new Dictionary<string, string>
{
{ "NestedRoot", @"NR2" },
{ "SourceControl", "git" },
}),
new TaskItem(path2, new Dictionary<string, string>
{
{ "ContainingRoot", path3 },
{ "SourceControl", "git" },
}),
new TaskItem(path3),
},
Deterministic = deterministic
};
bool result = task.Execute();
AssertEx.AssertEqualToleratingWhitespaceDifferences(
"WARNING : " + string.Format(task.Log.FormatResourceString(
"MapSourceRoots.ContainsDuplicate", "SourceRoot", path1, "SourceControl", "git", "tfvc")) + Environment.NewLine +
"WARNING : " + string.Format(task.Log.FormatResourceString(
"MapSourceRoots.ContainsDuplicate", "SourceRoot", path1, "RevisionId", "RevId1", "RevId2")) + Environment.NewLine +
"WARNING : " + string.Format(task.Log.FormatResourceString(
"MapSourceRoots.ContainsDuplicate", "SourceRoot", path1, "NestedRoot", "NR1A", "NR1B")) + Environment.NewLine +
"WARNING : " + string.Format(task.Log.FormatResourceString(
"MapSourceRoots.ContainsDuplicate", "SourceRoot", path1, "ContainingRoot", path3, "CR")) + Environment.NewLine +
"WARNING : " + string.Format(task.Log.FormatResourceString(
"MapSourceRoots.ContainsDuplicate", "SourceRoot", path1, "MappedPath", "MP1", "MP2")) + Environment.NewLine +
"WARNING : " + string.Format(task.Log.FormatResourceString(
"MapSourceRoots.ContainsDuplicate", "SourceRoot", path1, "SourceLinkUrl", "URL1", "URL2")) + Environment.NewLine,
engine.Log);
AssertEx.Equal(new[]
{
$"'{path1}' SourceControl='git' RevisionId='RevId1' NestedRoot='NR1A' ContainingRoot='{path3}' MappedPath='{(deterministic ? "/_/NR1A/" : path1)}' SourceLinkUrl='URL1'",
$"'{path2}' SourceControl='git' RevisionId='' NestedRoot='NR2' ContainingRoot='{path3}' MappedPath='{(deterministic ? "/_/NR2/" : path2)}' SourceLinkUrl=''",
$"'{path3}' SourceControl='' RevisionId='' NestedRoot='' ContainingRoot='' MappedPath='{(deterministic ? "/_/" : path3)}' SourceLinkUrl=''",
}, task.MappedSourceRoots.Select(InspectSourceRoot));
Assert.True(result);
}
[Fact]
public void Error_MissingContainingRoot()
{
var engine = new MockEngine();
var task = new MapSourceRoots
{
BuildEngine = engine,
SourceRoots = new[]
{
new TaskItem(@"c:\MyProjects\MYPROJECT\"),
new TaskItem(@"c:\MyProjects\MyProject\a\b\", new Dictionary<string, string>
{
{ "SourceControl", "Git" },
{ "NestedRoot", "a/b" },
{ "ContainingRoot", @"c:\MyProjects\MyProject\" },
}),
},
Deterministic = true
};
bool result = task.Execute();
AssertEx.AssertEqualToleratingWhitespaceDifferences("ERROR : " + string.Format(task.Log.FormatResourceString(
"MapSourceRoots.NoSuchTopLevelSourceRoot", "SourceRoot.ContainingRoot", "SourceRoot", @"c:\MyProjects\MyProject\")) + Environment.NewLine, engine.Log);
Assert.Null(task.MappedSourceRoots);
Assert.False(result);
}
[Fact]
public void Error_NoContainingRootSpecified()
{
var engine = new MockEngine();
var task = new MapSourceRoots
{
BuildEngine = engine,
SourceRoots = new[]
{
new TaskItem(@"c:\MyProjects\MyProject\"),
new TaskItem(@"c:\MyProjects\MyProject\a\b\", new Dictionary<string, string>
{
{ "SourceControl", "Git" },
{ "NestedRoot", "a/b" },
}),
},
Deterministic = true
};
bool result = task.Execute();
AssertEx.AssertEqualToleratingWhitespaceDifferences("ERROR : " + string.Format(task.Log.FormatResourceString(
"MapSourceRoots.NoSuchTopLevelSourceRoot", "SourceRoot.ContainingRoot", "SourceRoot", @"")) + Environment.NewLine, engine.Log);
Assert.Null(task.MappedSourceRoots);
Assert.False(result);
}
[Theory]
[InlineData(new object[] { true })]
[InlineData(new object[] { false })]
public void Error_NoTopLevelSourceRoot(bool deterministic)
{
var engine = new MockEngine();
var path1 = Utilities.FixFilePath(@"c:\MyProjects\MyProject\a\b\");
var task = new MapSourceRoots
{
BuildEngine = engine,
SourceRoots = new[]
{
new TaskItem(path1, new Dictionary<string, string>
{
{ "ContainingRoot", path1 },
{ "NestedRoot", "a/b" },
}),
},
Deterministic = deterministic
};
bool result = task.Execute();
if (deterministic)
{
AssertEx.AssertEqualToleratingWhitespaceDifferences("ERROR : " + string.Format(task.Log.FormatResourceString(
"MapSourceRoots.NoTopLevelSourceRoot", "SourceRoot", "DeterministicSourcePaths")) + Environment.NewLine, engine.Log);
Assert.Null(task.MappedSourceRoots);
Assert.False(result);
}
else
{
AssertEx.Equal(new[]
{
$"'{path1}' SourceControl='' RevisionId='' NestedRoot='a/b' ContainingRoot='{path1}' MappedPath='{path1}' SourceLinkUrl=''",
}, task.MappedSourceRoots.Select(InspectSourceRoot));
Assert.True(result);
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.BuildTasks.UnitTests
{
public abstract partial class DotNetSdkTestBase : TestBase
{
public sealed class DotNetSdkAvailable : ExecutionCondition
{
public override bool ShouldSkip => s_dotnetSdkPath == null;
public override string SkipReason => "The location of dotnet SDK can't be determined (DOTNET_INSTALL_DIR environment variable is unset)";
}
private static readonly string s_dotnetExeName;
private static readonly string s_dotnetInstallDir;
private static readonly string s_dotnetSdkVersion;
private static readonly string s_dotnetSdkPath;
private static string s_projectSource =
@"<Project Sdk='Microsoft.NET.Sdk'>
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
</Project>
";
private static string s_classSource =
@"using System;
public class TestClass
{
public void F()
{
Console.WriteLine(123);
}
}
";
protected readonly TempDirectory ProjectDir;
protected readonly TempDirectory ObjDir;
protected readonly TempDirectory OutDir;
protected readonly TempFile Project;
protected readonly string ProjectName;
protected readonly string ProjectFileName;
protected readonly string Configuration;
protected readonly string TargetFramework;
protected readonly string DotNetPath;
protected readonly IReadOnlyDictionary<string, string> EnvironmentVariables;
private int _logIndex;
private static string GetSdkPath(string dotnetInstallDir, string version)
=> Path.Combine(dotnetInstallDir, "sdk", version);
static DotNetSdkTestBase()
{
s_dotnetExeName = "dotnet" + (Path.DirectorySeparatorChar == '/' ? "" : ".exe");
s_dotnetSdkVersion = typeof(DotNetSdkTests).Assembly.GetCustomAttribute<DotNetSdkVersionAttribute>().Version;
bool isMatchingDotNetInstance(string dotnetDir)
=> dotnetDir != null && File.Exists(Path.Combine(dotnetDir, s_dotnetExeName)) && Directory.Exists(GetSdkPath(dotnetDir, s_dotnetSdkVersion));
var dotnetInstallDir = Environment.GetEnvironmentVariable("DOTNET_INSTALL_DIR");
if (!isMatchingDotNetInstance(dotnetInstallDir))
{
dotnetInstallDir = Environment.GetEnvironmentVariable("PATH").Split(Path.PathSeparator).FirstOrDefault(isMatchingDotNetInstance);
}
if (dotnetInstallDir != null)
{
s_dotnetInstallDir = dotnetInstallDir;
s_dotnetSdkPath = GetSdkPath(dotnetInstallDir, s_dotnetSdkVersion);
}
}
private const string EmptyValueMarker = "--{empty}--";
private static void EmitTestHelperProps(
string objDirectory,
string projectFileName,
string projectXml)
{
// Common.props automatically import {project-name}.*.props files from MSBuildProjectExtensionsPath directory,
// which is by default set to the IntermediateOutputPath:
File.WriteAllText(Path.Combine(objDirectory, projectFileName + ".TestHelpers.g.props"), projectXml);
}
private static string EmitTestHelperTargets(
string objDirectory,
string outDirectory,
string projectFileName,
IEnumerable<string> expressions)
{
var outputFile = Path.Combine(outDirectory, "EvaluationResult.txt");
// Common.targets automatically import {project-name}.*.targets files from MSBuildProjectExtensionsPath directory,
// which is by defautl set to the IntermediateOutputPath:
File.WriteAllText(Path.Combine(objDirectory, projectFileName + ".TestHelpers.g.targets"),
$@"<Project>
<Target Name=""Test_EvaluateExpressions"">
<PropertyGroup>
{string.Join(Environment.NewLine + " ", expressions.SelectWithIndex((e, i) => $@"<_Value{i}>{e}</_Value{i}><_Value{i} Condition=""'$(_Value{i})' == ''"">{EmptyValueMarker}</_Value{i}>"))}
</PropertyGroup>
<ItemGroup>
<LinesToWrite Include=""{string.Join(";", expressions.SelectWithIndex((e, i) => $"$(_Value{i})"))}""/>
</ItemGroup>
<MakeDir Directories=""{outDirectory}"" />
<WriteLinesToFile File=""{outputFile}""
Lines=""@(LinesToWrite)""
Overwrite=""true""
Encoding=""UTF-8"" />
</Target>
<!-- Overwrite CoreCompile target to avoid triggering the compiler -->
<Target Name=""CoreCompile""
DependsOnTargets=""$(CoreCompileDependsOn);_BeforeVBCSCoreCompile"">
</Target>
<Target Name=""InitializeSourceControlInformation""/>
</Project>");
return outputFile;
}
public DotNetSdkTestBase()
{
Assert.True(s_dotnetInstallDir != null, $"SDK not found. Use {nameof(ConditionalFactAttribute)}(typeof({nameof(DotNetSdkAvailable)})) to skip the test if the SDK is not found.");
DotNetPath = Path.Combine(s_dotnetInstallDir, s_dotnetExeName);
var testBinDirectory = Path.GetDirectoryName(typeof(DotNetSdkTests).Assembly.Location);
var sdksDir = Path.Combine(s_dotnetSdkPath, "Sdks");
ProjectName = "test";
ProjectFileName = ProjectName + ".csproj";
Configuration = "Debug";
TargetFramework = "netstandard2.0";
ProjectDir = Temp.CreateDirectory();
ObjDir = ProjectDir.CreateDirectory("obj");
OutDir = ProjectDir.CreateDirectory("bin").CreateDirectory(Configuration).CreateDirectory(TargetFramework);
Project = ProjectDir.CreateFile(ProjectFileName).WriteAllText(s_projectSource);
ProjectDir.CreateFile("TestClass.cs").WriteAllText(s_classSource);
// avoid accidental dependency on files outside of the project directory:
ProjectDir.CreateFile("Directory.Build.props").WriteAllText("<Project/>");
ProjectDir.CreateFile("Directory.Build.targets").WriteAllText("<Project/>");
ProjectDir.CreateFile(".editorconfig").WriteAllText("root = true");
Assert.True(File.Exists(Path.Combine(testBinDirectory, "Microsoft.CSharp.Core.targets")));
Assert.True(File.Exists(Path.Combine(testBinDirectory, "Microsoft.VisualBasic.Core.targets")));
EnvironmentVariables = new Dictionary<string, string>()
{
{ "RoslynTargetsPath", testBinDirectory },
{ "MSBuildSDKsPath", sdksDir },
{ "DOTNET_MSBUILD_SDK_RESOLVER_SDKS_DIR", sdksDir }
};
var restoreResult = ProcessUtilities.Run(DotNetPath, $@"msbuild ""{Project.Path}"" /t:restore /bl:{Path.Combine(ProjectDir.Path, "restore.binlog")}",
additionalEnvironmentVars: EnvironmentVariables);
Assert.True(restoreResult.ExitCode == 0, $"Failed with exit code {restoreResult.ExitCode}: {restoreResult.Output}");
Assert.True(File.Exists(Path.Combine(ObjDir.Path, "project.assets.json")));
Assert.True(File.Exists(Path.Combine(ObjDir.Path, ProjectFileName + ".nuget.g.props")));
Assert.True(File.Exists(Path.Combine(ObjDir.Path, ProjectFileName + ".nuget.g.targets")));
}
protected void VerifyValues(string props, string[] targets, string[] expressions, string[] expectedResults)
{
if (!string.IsNullOrEmpty(props))
{
EmitTestHelperProps(ObjDir.Path, ProjectFileName, props);
}
var evaluationResultsFile = EmitTestHelperTargets(ObjDir.Path, OutDir.Path, ProjectFileName, expressions);
var targetsArg = string.Join(";", targets.Concat(new[] { "Test_EvaluateExpressions" }));
var testBinDirectory = Path.GetDirectoryName(typeof(DotNetSdkTests).Assembly.Location);
var binLog = Path.Combine(ProjectDir.Path, $"build{_logIndex++}.binlog");
var buildResult = ProcessUtilities.Run(DotNetPath, $@"msbuild ""{Project.Path}"" /t:{targetsArg} /p:Configuration={Configuration} /p:RoslynTargetsPath=""{testBinDirectory}"" /bl:""{binLog}""",
additionalEnvironmentVars: EnvironmentVariables);
Assert.True(buildResult.ExitCode == 0, $"Failed with exit code {buildResult.ExitCode}: {buildResult.Output}");
var evaluationResult = File.ReadAllLines(evaluationResultsFile).Select(l => (l != EmptyValueMarker) ? l : "");
AssertEx.Equal(expectedResults, evaluationResult);
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.CodeAnalysis.BuildTasks.UnitTests
{
/// <summary>
/// Attribute added to the test assembly during build.
/// Captures the version of dotnet SDK the build is targeting.
/// </summary>
[AttributeUsage(AttributeTargets.Assembly)]
public sealed class DotNetSdkVersionAttribute : Attribute
{
public string Version { get; }
public DotNetSdkVersionAttribute(string version)
{
Version = version;
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
namespace Microsoft.CodeAnalysis.BuildTasks.UnitTests
{
public static class EnumerableExtensions
{
public static IEnumerable<S> SelectWithIndex<T, S>(this IEnumerable<T> items, Func<T, int, S> selector)
{
int i = 0;
foreach (var item in items)
{
yield return selector(item, i++);
}
}
}
}
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections;
using System.Text;
using Microsoft.Build.Framework;
namespace Microsoft.CodeAnalysis.BuildTasks.UnitTests
{
internal sealed class MockEngine : IBuildEngine
{
private StringBuilder _log = new StringBuilder();
public MessageImportance MinimumMessageImportance = MessageImportance.Low;
internal string Log
{
set { _log = new StringBuilder(value); }
get { return _log.ToString(); }
}
public void LogErrorEvent(BuildErrorEventArgs eventArgs)
{
_log.Append("ERROR ");
_log.Append(eventArgs.Code);
_log.Append(": ");
_log.Append(eventArgs.Message);
_log.AppendLine();
}
public void LogWarningEvent(BuildWarningEventArgs eventArgs)
{
_log.Append("WARNING ");
_log.Append(eventArgs.Code);
_log.Append(": ");
_log.Append(eventArgs.Message);
_log.AppendLine();
}
public void LogCustomEvent(CustomBuildEventArgs eventArgs)
{
_log.AppendLine(eventArgs.Message);
}
public void LogMessageEvent(BuildMessageEventArgs eventArgs)
{
_log.AppendLine(eventArgs.Message);
}
public string ProjectFileOfTaskNode => "";
public int ColumnNumberOfTaskNode => 0;
public int LineNumberOfTaskNode => 0;
public bool ContinueOnError => true;
public bool BuildProjectFile(string projectFileName, string[] targetNames, IDictionary globalProperties, IDictionary targetOutputs)
=> throw new NotImplementedException();
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册