diff --git a/src/fsharp/FSharp.Build/FSBuild.txt b/src/fsharp/FSharp.Build/FSBuild.txt index 99209176b85..b429a789bdc 100644 --- a/src/fsharp/FSharp.Build/FSBuild.txt +++ b/src/fsharp/FSharp.Build/FSBuild.txt @@ -1,2 +1,6 @@ # FSharp.Build resource strings toolpathUnknown,"ToolPath is unknown; specify the path to the tool." +mapSourceRootsContainsDuplicate,"SourceRoot contains duplicate items '%s' with conflicting metadata '%s': '%s' and '%s'" +mapSourceRootsPathMustEndWithSlashOrBackslash,"SourceRoot paths are required to end with a slash or backslash: '%s'" +mapSourceRootsNoTopLevelSourceRoot,"SourceRoot items must include at least one top-level (not nested) item when DeterministicSourcePaths is true" +mapSourceRootsNoSuchTopLevelSourceRoot,"The value of SourceRoot.ContainingRoot was not found in SourceRoot items, or the corresponding item is not a top-level source root: '%s'" \ No newline at end of file diff --git a/src/fsharp/FSharp.Build/FSharp.Build.fsproj b/src/fsharp/FSharp.Build/FSharp.Build.fsproj index 77e910e4db8..c62b2c2d93e 100644 --- a/src/fsharp/FSharp.Build/FSharp.Build.fsproj +++ b/src/fsharp/FSharp.Build/FSharp.Build.fsproj @@ -28,6 +28,7 @@ + diff --git a/src/fsharp/FSharp.Build/MapSourceRoots.fs b/src/fsharp/FSharp.Build/MapSourceRoots.fs new file mode 100644 index 00000000000..84310352b8d --- /dev/null +++ b/src/fsharp/FSharp.Build/MapSourceRoots.fs @@ -0,0 +1,191 @@ +namespace FSharp.Build + +open System +open System.IO +open Microsoft.Build.Framework +open Microsoft.Build.Utilities +open System.Collections.Generic + +(* + This type is a translation of the matching MapSourceRoots task in Roslyn, + which is planned to move to a shared location at some point in the future. + + Until then, this version will be used. The exact source used is: + https://github.com/dotnet/roslyn/blob/69d3fb733e6c74a41c118bf905739163cf5aef2a/src/Compilers/Core/MSBuildTask/MapSourceRoots.cs, + with matching targets usage at: + https://github.com/dotnet/roslyn/blob/69d3fb733e6c74a41c118bf905739163cf5aef2a/src/Compilers/Core/MSBuildTask/Microsoft.Managed.Core.targets#L79-L127 + +*) + +module Utilities = + /// + /// Copied from msbuild. ItemSpecs are normalized using this method. + /// + let FixFilePath (path: string) = + if String.IsNullOrEmpty(path) || Path.DirectorySeparatorChar = '\\' + then path + else path.Replace('\\', '/'); + +/// +/// Given a list of SourceRoot items produces a list of the same items with added MappedPath metadata that +/// contains calculated deterministic source path for each SourceRoot. +/// +/// +/// Does not perform any path validation. +/// +/// The MappedPath is either the path (ItemSpec) itself, when is false, +/// or a calculated deterministic source path (starting with prefix '/_/', '/_1/', etc.), otherwise. +/// +type MapSourceRoots () = + inherit Task () + + static let MappedPath = "MappedPath" + static let SourceControl = "SourceControl" + static let NestedRoot = "NestedRoot" + static let ContainingRoot = "ContainingRoot" + static let RevisionId = "RevisionId" + static let SourceLinkUrl = "SourceLinkUrl" + static let knownMetadataNames = + [ + SourceControl + RevisionId + NestedRoot + ContainingRoot + MappedPath + SourceLinkUrl + ] + + static let (|NullOrEmpty|HasValue|) (s: string) = if String.IsNullOrEmpty s then NullOrEmpty else HasValue s + static let ensureEndsWithSlash (path: string) = + if path.EndsWith "/" + then path + else path + "/" + + static let endsWithDirectorySeparator (path: string) = + if path.Length = 0 + then false + else + let endChar = path.[path.Length - 1] + endChar = Path.DirectorySeparatorChar || endChar = Path.AltDirectorySeparatorChar + + static let reportConflictingWellKnownMetadata (log: TaskLoggingHelper) (l: ITaskItem) (r: ITaskItem) = + for name in knownMetadataNames do + match l.GetMetadata name, r.GetMetadata name with + | HasValue lValue, HasValue rValue when lValue <> rValue -> + log.LogWarning(FSBuild.SR.mapSourceRootsContainsDuplicate(r.ItemSpec, name, lValue, rValue)) + | _, _ -> () + + + static member PerformMapping (log: TaskLoggingHelper) (sourceRoots: ITaskItem []) deterministic = + let mappedSourceRoots = ResizeArray<_>() + let rootByItemSpec = Dictionary(); + + for sourceRoot in sourceRoots do + // 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 not (endsWithDirectorySeparator sourceRoot.ItemSpec) + then + log.LogError(FSBuild.SR.mapSourceRootsPathMustEndWithSlashOrBackslash sourceRoot.ItemSpec) + + match rootByItemSpec.TryGetValue sourceRoot.ItemSpec with + | true, existingRoot -> + reportConflictingWellKnownMetadata log existingRoot sourceRoot + sourceRoot.CopyMetadataTo existingRoot + | false, _ -> + rootByItemSpec.[sourceRoot.ItemSpec] <- sourceRoot + mappedSourceRoots.Add sourceRoot + + if log.HasLoggedErrors + then None + else + if deterministic + then + let topLevelMappedPaths = Dictionary<_,_>() + let setTopLevelMappedPaths isSourceControlled = + + let mapNestedRootIfEmpty (root: ITaskItem) = + let localPath = root.ItemSpec + match root.GetMetadata NestedRoot with + | NullOrEmpty -> + // root isn't nested + if topLevelMappedPaths.ContainsKey(localPath) + then + log.LogError(FSBuild.SR.mapSourceRootsContainsDuplicate(localPath, NestedRoot, "", "")); + else + let index = topLevelMappedPaths.Count; + let mappedPath = "/_" + (if index = 0 then "" else string index) + "/" + topLevelMappedPaths.[localPath] <- mappedPath + root.SetMetadata(MappedPath, mappedPath) + | HasValue _ -> () + + for root in mappedSourceRoots do + match root.GetMetadata SourceControl with + | HasValue v when isSourceControlled -> mapNestedRootIfEmpty root + | NullOrEmpty when not isSourceControlled -> mapNestedRootIfEmpty root + | _ -> () + + // assign mapped paths to process source-controlled top-level roots first: + setTopLevelMappedPaths true + + // then assign mapped paths to other source-controlled top-level roots: + setTopLevelMappedPaths false + + if topLevelMappedPaths.Count = 0 + then + log.LogError(FSBuild.SR.mapSourceRootsNoTopLevelSourceRoot ()) + else + // finally, calculate mapped paths of nested roots: + for root in mappedSourceRoots do + match root.GetMetadata NestedRoot with + | HasValue nestedRoot -> + match root.GetMetadata ContainingRoot with + | HasValue 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. + match topLevelMappedPaths.TryGetValue(Utilities.FixFilePath(containingRoot)) with + | true, mappedTopLevelPath -> + root.SetMetadata(MappedPath, mappedTopLevelPath + ensureEndsWithSlash(nestedRoot.Replace('\\', '/'))); + | false, _ -> + log.LogError(FSBuild.SR.mapSourceRootsNoSuchTopLevelSourceRoot containingRoot) + | NullOrEmpty -> + log.LogError(FSBuild.SR.mapSourceRootsNoSuchTopLevelSourceRoot "") + | NullOrEmpty -> () + else + for root in mappedSourceRoots do + root.SetMetadata(MappedPath, root.ItemSpec) + + if log.HasLoggedErrors then None else Some (mappedSourceRoots.ToArray()) + + + /// + /// SourceRoot items with the following optional well-known metadata: + /// + /// SourceControlIndicates name of the source control system the source root is tracked by (e.g. Git, TFVC, etc.), if any. + /// NestedRootIf 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. + /// ContainingRootIdentifies another source root item that this source root is nested under. + /// + /// + [] + member val SourceRoots: ITaskItem[] = null with get, set + + /// + /// True if the mapped paths should be deterministic. + /// + member val Deterministic = false with get, set + + /// + /// SourceRoot items with MappedPath metadata set. + /// Items listed in that have the same ItemSpec will be merged into a single item in this list. + /// + [] + member val MappedSourceRoots: ITaskItem[] = null with get, set + + override this.Execute() = + match MapSourceRoots.PerformMapping this.Log this.SourceRoots this.Deterministic with + | None -> + false + | Some mappings -> + this.MappedSourceRoots <- mappings + true \ No newline at end of file diff --git a/src/fsharp/FSharp.Build/Microsoft.FSharp.NetSdk.targets b/src/fsharp/FSharp.Build/Microsoft.FSharp.NetSdk.targets index 7fa9d899362..a04f7854de5 100644 --- a/src/fsharp/FSharp.Build/Microsoft.FSharp.NetSdk.targets +++ b/src/fsharp/FSharp.Build/Microsoft.FSharp.NetSdk.targets @@ -12,7 +12,8 @@ WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and --> - + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) @@ -90,7 +91,74 @@ WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and $(FSharpToolsDirectory)/$(FSharpDesignTimeProtocol)/%(_ResolvedOutputFiles.NearestTargetFramework)/%(_ResolvedOutputFiles.FileName)%(_ResolvedOutputFiles.Extension) + + + + + + + + true + + + + + + + <_MappedSourceRoot Remove="@(_MappedSourceRoot)" /> + + + + + + + + + + + + + true + + + + diff --git a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.cs.xlf b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.cs.xlf index 374f1770bbb..bd142be8836 100644 --- a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.cs.xlf +++ b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.cs.xlf @@ -2,6 +2,26 @@ + + SourceRoot contains duplicate items '{0}' with conflicting metadata '{1}': '{2}' and '{3}' + SourceRoot contains duplicate items '{0}' with conflicting metadata '{1}': '{2}' and '{3}' + + + + The value of SourceRoot.ContainingRoot was not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' + The value of SourceRoot.ContainingRoot was not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' + + + + SourceRoot items must include at least one top-level (not nested) item when DeterministicSourcePaths is true + SourceRoot items must include at least one top-level (not nested) item when DeterministicSourcePaths is true + + + + SourceRoot paths are required to end with a slash or backslash: '{0}' + SourceRoot paths are required to end with a slash or backslash: '{0}' + + ToolPath is unknown; specify the path to the tool. Parametr ToolPath není známý. Zadejte cestu k nástroji. diff --git a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.de.xlf b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.de.xlf index c5a6cb42c9f..88548526599 100644 --- a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.de.xlf +++ b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.de.xlf @@ -2,6 +2,26 @@ + + SourceRoot contains duplicate items '{0}' with conflicting metadata '{1}': '{2}' and '{3}' + SourceRoot contains duplicate items '{0}' with conflicting metadata '{1}': '{2}' and '{3}' + + + + The value of SourceRoot.ContainingRoot was not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' + The value of SourceRoot.ContainingRoot was not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' + + + + SourceRoot items must include at least one top-level (not nested) item when DeterministicSourcePaths is true + SourceRoot items must include at least one top-level (not nested) item when DeterministicSourcePaths is true + + + + SourceRoot paths are required to end with a slash or backslash: '{0}' + SourceRoot paths are required to end with a slash or backslash: '{0}' + + ToolPath is unknown; specify the path to the tool. ToolPath unbekannt. Geben Sie den Pfad zum Tool an. diff --git a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.es.xlf b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.es.xlf index 4791cd16b2c..180307863c5 100644 --- a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.es.xlf +++ b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.es.xlf @@ -2,6 +2,26 @@ + + SourceRoot contains duplicate items '{0}' with conflicting metadata '{1}': '{2}' and '{3}' + SourceRoot contains duplicate items '{0}' with conflicting metadata '{1}': '{2}' and '{3}' + + + + The value of SourceRoot.ContainingRoot was not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' + The value of SourceRoot.ContainingRoot was not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' + + + + SourceRoot items must include at least one top-level (not nested) item when DeterministicSourcePaths is true + SourceRoot items must include at least one top-level (not nested) item when DeterministicSourcePaths is true + + + + SourceRoot paths are required to end with a slash or backslash: '{0}' + SourceRoot paths are required to end with a slash or backslash: '{0}' + + ToolPath is unknown; specify the path to the tool. ToolPath se desconoce; especifique la ruta de acceso a la herramienta. diff --git a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.fr.xlf b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.fr.xlf index 79ddfd7ab24..cd98018c017 100644 --- a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.fr.xlf +++ b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.fr.xlf @@ -2,6 +2,26 @@ + + SourceRoot contains duplicate items '{0}' with conflicting metadata '{1}': '{2}' and '{3}' + SourceRoot contains duplicate items '{0}' with conflicting metadata '{1}': '{2}' and '{3}' + + + + The value of SourceRoot.ContainingRoot was not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' + The value of SourceRoot.ContainingRoot was not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' + + + + SourceRoot items must include at least one top-level (not nested) item when DeterministicSourcePaths is true + SourceRoot items must include at least one top-level (not nested) item when DeterministicSourcePaths is true + + + + SourceRoot paths are required to end with a slash or backslash: '{0}' + SourceRoot paths are required to end with a slash or backslash: '{0}' + + ToolPath is unknown; specify the path to the tool. ToolPath est inconnu, spécifiez le chemin de l'outil. diff --git a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.it.xlf b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.it.xlf index a7dd116bb44..a694adff73b 100644 --- a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.it.xlf +++ b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.it.xlf @@ -2,6 +2,26 @@ + + SourceRoot contains duplicate items '{0}' with conflicting metadata '{1}': '{2}' and '{3}' + SourceRoot contains duplicate items '{0}' with conflicting metadata '{1}': '{2}' and '{3}' + + + + The value of SourceRoot.ContainingRoot was not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' + The value of SourceRoot.ContainingRoot was not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' + + + + SourceRoot items must include at least one top-level (not nested) item when DeterministicSourcePaths is true + SourceRoot items must include at least one top-level (not nested) item when DeterministicSourcePaths is true + + + + SourceRoot paths are required to end with a slash or backslash: '{0}' + SourceRoot paths are required to end with a slash or backslash: '{0}' + + ToolPath is unknown; specify the path to the tool. Il valore di ToolPath è sconosciuto. Specificare il percorso dello strumento. diff --git a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.ja.xlf b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.ja.xlf index e26503cb96f..c4638dc9e5b 100644 --- a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.ja.xlf +++ b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.ja.xlf @@ -2,6 +2,26 @@ + + SourceRoot contains duplicate items '{0}' with conflicting metadata '{1}': '{2}' and '{3}' + SourceRoot contains duplicate items '{0}' with conflicting metadata '{1}': '{2}' and '{3}' + + + + The value of SourceRoot.ContainingRoot was not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' + The value of SourceRoot.ContainingRoot was not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' + + + + SourceRoot items must include at least one top-level (not nested) item when DeterministicSourcePaths is true + SourceRoot items must include at least one top-level (not nested) item when DeterministicSourcePaths is true + + + + SourceRoot paths are required to end with a slash or backslash: '{0}' + SourceRoot paths are required to end with a slash or backslash: '{0}' + + ToolPath is unknown; specify the path to the tool. ToolPath が不明です。ツールンパスを指定します。 diff --git a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.ko.xlf b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.ko.xlf index 4c141658672..767de522e96 100644 --- a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.ko.xlf +++ b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.ko.xlf @@ -2,6 +2,26 @@ + + SourceRoot contains duplicate items '{0}' with conflicting metadata '{1}': '{2}' and '{3}' + SourceRoot contains duplicate items '{0}' with conflicting metadata '{1}': '{2}' and '{3}' + + + + The value of SourceRoot.ContainingRoot was not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' + The value of SourceRoot.ContainingRoot was not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' + + + + SourceRoot items must include at least one top-level (not nested) item when DeterministicSourcePaths is true + SourceRoot items must include at least one top-level (not nested) item when DeterministicSourcePaths is true + + + + SourceRoot paths are required to end with a slash or backslash: '{0}' + SourceRoot paths are required to end with a slash or backslash: '{0}' + + ToolPath is unknown; specify the path to the tool. ToolPath를 알 수 없습니다. 도구 경로를 지정하세요. diff --git a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.pl.xlf b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.pl.xlf index b7734b58a75..91acbeb6626 100644 --- a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.pl.xlf +++ b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.pl.xlf @@ -2,6 +2,26 @@ + + SourceRoot contains duplicate items '{0}' with conflicting metadata '{1}': '{2}' and '{3}' + SourceRoot contains duplicate items '{0}' with conflicting metadata '{1}': '{2}' and '{3}' + + + + The value of SourceRoot.ContainingRoot was not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' + The value of SourceRoot.ContainingRoot was not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' + + + + SourceRoot items must include at least one top-level (not nested) item when DeterministicSourcePaths is true + SourceRoot items must include at least one top-level (not nested) item when DeterministicSourcePaths is true + + + + SourceRoot paths are required to end with a slash or backslash: '{0}' + SourceRoot paths are required to end with a slash or backslash: '{0}' + + ToolPath is unknown; specify the path to the tool. Właściwość ToolPath jest nieznana. Określ ścieżkę do narzędzia. diff --git a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.pt-BR.xlf b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.pt-BR.xlf index 865c593fd07..2f4a683f5a2 100644 --- a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.pt-BR.xlf +++ b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.pt-BR.xlf @@ -2,6 +2,26 @@ + + SourceRoot contains duplicate items '{0}' with conflicting metadata '{1}': '{2}' and '{3}' + SourceRoot contains duplicate items '{0}' with conflicting metadata '{1}': '{2}' and '{3}' + + + + The value of SourceRoot.ContainingRoot was not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' + The value of SourceRoot.ContainingRoot was not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' + + + + SourceRoot items must include at least one top-level (not nested) item when DeterministicSourcePaths is true + SourceRoot items must include at least one top-level (not nested) item when DeterministicSourcePaths is true + + + + SourceRoot paths are required to end with a slash or backslash: '{0}' + SourceRoot paths are required to end with a slash or backslash: '{0}' + + ToolPath is unknown; specify the path to the tool. ToolPath desconhecido. Especifique o caminho para a ferramenta. diff --git a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.ru.xlf b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.ru.xlf index b1fef639ee0..e960f420a6c 100644 --- a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.ru.xlf +++ b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.ru.xlf @@ -2,6 +2,26 @@ + + SourceRoot contains duplicate items '{0}' with conflicting metadata '{1}': '{2}' and '{3}' + SourceRoot contains duplicate items '{0}' with conflicting metadata '{1}': '{2}' and '{3}' + + + + The value of SourceRoot.ContainingRoot was not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' + The value of SourceRoot.ContainingRoot was not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' + + + + SourceRoot items must include at least one top-level (not nested) item when DeterministicSourcePaths is true + SourceRoot items must include at least one top-level (not nested) item when DeterministicSourcePaths is true + + + + SourceRoot paths are required to end with a slash or backslash: '{0}' + SourceRoot paths are required to end with a slash or backslash: '{0}' + + ToolPath is unknown; specify the path to the tool. Путь к инструменту (ToolPath) неизвестен, укажите путь к инструменту. diff --git a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.tr.xlf b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.tr.xlf index 4dd43b35531..3a8ad0841ed 100644 --- a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.tr.xlf +++ b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.tr.xlf @@ -2,6 +2,26 @@ + + SourceRoot contains duplicate items '{0}' with conflicting metadata '{1}': '{2}' and '{3}' + SourceRoot contains duplicate items '{0}' with conflicting metadata '{1}': '{2}' and '{3}' + + + + The value of SourceRoot.ContainingRoot was not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' + The value of SourceRoot.ContainingRoot was not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' + + + + SourceRoot items must include at least one top-level (not nested) item when DeterministicSourcePaths is true + SourceRoot items must include at least one top-level (not nested) item when DeterministicSourcePaths is true + + + + SourceRoot paths are required to end with a slash or backslash: '{0}' + SourceRoot paths are required to end with a slash or backslash: '{0}' + + ToolPath is unknown; specify the path to the tool. ToolPath bilinmiyor; aracın yolunu belirtin. diff --git a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.zh-Hans.xlf b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.zh-Hans.xlf index bf5016a3685..a4e15083f21 100644 --- a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.zh-Hans.xlf +++ b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.zh-Hans.xlf @@ -2,6 +2,26 @@ + + SourceRoot contains duplicate items '{0}' with conflicting metadata '{1}': '{2}' and '{3}' + SourceRoot contains duplicate items '{0}' with conflicting metadata '{1}': '{2}' and '{3}' + + + + The value of SourceRoot.ContainingRoot was not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' + The value of SourceRoot.ContainingRoot was not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' + + + + SourceRoot items must include at least one top-level (not nested) item when DeterministicSourcePaths is true + SourceRoot items must include at least one top-level (not nested) item when DeterministicSourcePaths is true + + + + SourceRoot paths are required to end with a slash or backslash: '{0}' + SourceRoot paths are required to end with a slash or backslash: '{0}' + + ToolPath is unknown; specify the path to the tool. ToolPath 未知;请指定工具的路径。 diff --git a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.zh-Hant.xlf b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.zh-Hant.xlf index 0e30a5cfeca..6b18b193fe4 100644 --- a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.zh-Hant.xlf +++ b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.zh-Hant.xlf @@ -2,6 +2,26 @@ + + SourceRoot contains duplicate items '{0}' with conflicting metadata '{1}': '{2}' and '{3}' + SourceRoot contains duplicate items '{0}' with conflicting metadata '{1}': '{2}' and '{3}' + + + + The value of SourceRoot.ContainingRoot was not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' + The value of SourceRoot.ContainingRoot was not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' + + + + SourceRoot items must include at least one top-level (not nested) item when DeterministicSourcePaths is true + SourceRoot items must include at least one top-level (not nested) item when DeterministicSourcePaths is true + + + + SourceRoot paths are required to end with a slash or backslash: '{0}' + SourceRoot paths are required to end with a slash or backslash: '{0}' + + ToolPath is unknown; specify the path to the tool. ToolPath 未知; 請指定工具的路徑。 diff --git a/tests/FSharp.Build.UnitTests/FSharp.Build.UnitTests.fsproj b/tests/FSharp.Build.UnitTests/FSharp.Build.UnitTests.fsproj index 0a91d3d70c1..ffc6aa91e4b 100644 --- a/tests/FSharp.Build.UnitTests/FSharp.Build.UnitTests.fsproj +++ b/tests/FSharp.Build.UnitTests/FSharp.Build.UnitTests.fsproj @@ -12,6 +12,7 @@ + diff --git a/tests/FSharp.Build.UnitTests/MapSourceRootsTests.fs b/tests/FSharp.Build.UnitTests/MapSourceRootsTests.fs new file mode 100644 index 00000000000..a5d852b2625 --- /dev/null +++ b/tests/FSharp.Build.UnitTests/MapSourceRootsTests.fs @@ -0,0 +1,419 @@ + +namespace FSharp.Build.UnitTests + +open Microsoft.Build.Framework +open Microsoft.Build.Utilities +open FSharp.Build +open NUnit.Framework +open System.Collections.Generic + +type MockEngine() = + member val Errors = ResizeArray() with get + member val Warnings = ResizeArray() with get + member val Custom = ResizeArray() with get + member val Messages = ResizeArray() with get + + interface IBuildEngine with + member this.BuildProjectFile(projectFileName: string, targetNames: string [], globalProperties: System.Collections.IDictionary, targetOutputs: System.Collections.IDictionary): bool = + failwith "Not Implemented" + member this.ColumnNumberOfTaskNode: int = 0 + member this.ContinueOnError: bool = true + member this.LineNumberOfTaskNode: int = 0 + member this.LogCustomEvent(e: CustomBuildEventArgs): unit = + this.Custom.Add e + failwith "Not Implemented" + member this.LogErrorEvent(e: BuildErrorEventArgs): unit = + this.Errors.Add e + member this.LogMessageEvent(e: BuildMessageEventArgs): unit = + this.Messages.Add e + member this.LogWarningEvent(e: BuildWarningEventArgs): unit = + this.Warnings.Add e + member this.ProjectFileOfTaskNode: string = "" + +type SourceRoot = + SourceRoot of + path: string * + props: list * + expectedProps: list + + +/// these tests are ported from https://github.com/dotnet/roslyn/blob/093ea477717001c58be6231cf2a793f4245cbf72/src/Compilers/Core/MSBuildTaskTests/MapSourceRootTests.cs +/// Same scenarios, slightly different setup/teardown +[] +type MapSourceRootsTests() = + + let assertNoErrors (t: MapSourceRoots) = + let engine = t.BuildEngine :?> MockEngine + let errors = engine.Errors + Assert.AreEqual(0, errors.Count, sprintf "Expected no errors, but found the following: %A" errors) + let newTask () = + MapSourceRoots(BuildEngine = MockEngine()) + let toTaskItem (SourceRoot(path, props, _)) = + let dict = Dictionary() + for (k, v) in props do dict.Add(k, v) + TaskItem(path, dict) :> ITaskItem + let checkExpectations position (SourceRoot(path, _, expectedProps), mapping: ITaskItem) = + Assert.AreEqual(Utilities.FixFilePath path, mapping.ItemSpec, sprintf "expected paths to be the same while checking position %d" position) + for (key, value) in expectedProps do + Assert.AreEqual(value, mapping.GetMetadata(key), sprintf "expected values for metadata key %s to be the same while checking position %d" key position) + + let successfulTest items = + let task = newTask() + let outputs = MapSourceRoots.PerformMapping task.Log (items |> Array.map toTaskItem) true + + assertNoErrors task + + match outputs with + | None -> + Assert.Fail("Expected to get some mappings back from this scenario") + | Some mappings -> + Array.zip items mappings + |> Array.iteri checkExpectations + + [] + member this.``basic deterministic scenarios`` () = + let items = + [| + SourceRoot(@"c:\packages\SourcePackage1\", [], ["MappedPath", @"/_1/"]) + SourceRoot(@"/packages/SourcePackage2/", [], ["MappedPath", @"/_2/"]) + SourceRoot(@"c:\MyProjects\MyProject\", ["SourceControl", "Git"], [ + "SourceControl", "Git" + "MappedPath", @"/_/" + ]) + SourceRoot(@"c:\MyProjects\MyProject\a\b\", [ + "SourceControl", "Git" + "NestedRoot", "a/b" + "ContainingRoot", @"c:\MyProjects\MyProject\" + "some metadata", "some value" + ], [ + "SourceControl", "Git" + "some metadata", "some value" + "MappedPath", @"/_/a/b/" + ]) + |] + + successfulTest items + + + [] + member this.``invalid chars`` () = + let items = + [| + SourceRoot(@"!@#:;$%^&*()_+|{}\", [], ["MappedPath", @"/_1/"]) + SourceRoot(@"****/", ["SourceControl", "Git"], [ + "MappedPath", @"/_/" + "SourceControl", "Git" + ]) + SourceRoot(@"****\|||:;\", [ + "SourceControl", "Git" + "NestedRoot","|||:;" + "ContainingRoot", @"****/" + ], [ + "MappedPath", @"/_/|||:;/" + "SourceControl", "Git" + ]) + |] + successfulTest items + + [] + member this.``input paths must end with separator`` () = + let items = + [| + SourceRoot(@"C:\", [], []) + SourceRoot(@"C:/", [], []) + SourceRoot(@"C:", [], []) + SourceRoot(@"C", [], []) + |] + let task = newTask() + let outputs = MapSourceRoots.PerformMapping task.Log (items |> Array.map toTaskItem) true + + match outputs with + | None -> + let errors = (task.BuildEngine :?> MockEngine).Errors + Assert.AreEqual(2, errors.Count, "Should have had some errors with path mappings") + let expectedErrors = ["'C:'"; "'C'"] + let errorMessages = errors |> Seq.map (fun e -> e.Message) + + Assert.IsTrue(errorMessages |> Seq.forall (fun error -> error.Contains("end with a slash or backslash"))) + + expectedErrors + |> Seq.iter (fun expectedErrorPath -> + Assert.IsTrue(errorMessages |> Seq.exists (fun err -> err.EndsWith expectedErrorPath), + sprintf "expected an error to end with '%s', none did.\nMessages were:\n%A" expectedErrorPath errorMessages) + ) + | Some mappings -> + Assert.Fail("Expected to fail on the inputs") + + [] + member this.``nested roots separators`` () = + let items = + [| + SourceRoot(@"c:\MyProjects\MyProject\", [], [ + "MappedPath", @"/_/" + ]) + SourceRoot(@"c:\MyProjects\MyProject\a\a\", [ + "NestedRoot", @"a/a/" + "ContainingRoot", @"c:\MyProjects\MyProject\" + ], [ + "MappedPath", @"/_/a/a/" + ]) + SourceRoot(@"c:\MyProjects\MyProject\a\b\", [ + "NestedRoot", @"a/b\" + "ContainingRoot", @"c:\MyProjects\MyProject\" + ],[ + "MappedPath", @"/_/a/b/" + ]) + SourceRoot(@"c:\MyProjects\MyProject\a\c\", [ + "NestedRoot", @"a\c" + "ContainingRoot", @"c:\MyProjects\MyProject\" + ], [ + "MappedPath", @"/_/a/c/" + ]) + |] + + successfulTest items + + [] + member this.``sourceroot case sensitivity``() = + let items = [| + SourceRoot(@"c:\packages\SourcePackage1\", [], ["MappedPath", @"/_/"]) + SourceRoot(@"C:\packages\SourcePackage1\", [], ["MappedPath", @"/_1/"]) + SourceRoot(@"c:\packages\SourcePackage2\", [], ["MappedPath", @"/_2/"]) + |] + + successfulTest items + + [] + member this.``recursion error`` () = + let path1 = Utilities.FixFilePath @"c:\MyProjects\MyProject\a\1\" + let path2 = Utilities.FixFilePath @"c:\MyProjects\MyProject\a\2\" + let path3 = Utilities.FixFilePath @"c:\MyProjects\MyProject\" + let items = + [| + SourceRoot(path1, [ + "ContainingRoot", path2 + "NestedRoot", "a/1" + ], []) + SourceRoot(path2, [ + "ContainingRoot", path1 + "NestedRoot", "a/2" + ], []) + SourceRoot(path3, [], []) + |] + + let task = newTask() + let outputs = MapSourceRoots.PerformMapping task.Log (items |> Array.map toTaskItem) true + + match outputs with + | None -> + let errors = (task.BuildEngine :?> MockEngine).Errors + Assert.AreEqual(2, errors.Count, "Should have had some errors with path mappings") + let expectedErrors = [path2; path1] |> List.map (sprintf "'%s'") + let errorMessages = errors |> Seq.map (fun e -> e.Message) + + Assert.IsTrue(errorMessages |> Seq.forall (fun error -> error.Contains("ContainingRoot was not found in SourceRoot items")), + sprintf "Expected to have the same type of errors but had %A" errorMessages + ) + + expectedErrors + |> Seq.iter (fun expectedErrorPath -> + Assert.IsTrue(errorMessages |> Seq.exists (fun err -> err.EndsWith expectedErrorPath), sprintf "expected an error to end with '%s', none did.\nMessages were:\n%A" expectedErrorPath errorMessages) + ) + | Some mappings -> + Assert.Fail("Expected to fail on the inputs") + + [] + [] + [] + member this.``metadata merge 1`` (deterministic: bool) = + let path1 = Utilities.FixFilePath @"c:\packages\SourcePackage1\" + let path2 = Utilities.FixFilePath @"c:\packages\SourcePackage2\" + let path3 = Utilities.FixFilePath @"c:\packages\SourcePackage3\" + + let items = [| + SourceRoot(path1, [ + "NestedRoot", @"NR1A" + "ContainingRoot", path3 + "RevisionId", "RevId1" + "SourceControl", "git" + "MappedPath", "MP1" + "SourceLinkUrl", "URL1" + ], []) + SourceRoot(path1, [ + "NestedRoot", @"NR1B" + "ContainingRoot", @"CR" + "RevisionId", "RevId2" + "SourceControl", "tfvc" + "MappedPath", "MP2" + "SourceLinkUrl", "URL2" + ], []) + SourceRoot(path2, [ + "NestedRoot", @"NR2" + "SourceControl", "git" + ], []) + SourceRoot(path2, [ + "ContainingRoot", path3 + "SourceControl", "git" + ], []) + SourceRoot(path3, [], []) + |] + + /// because this test isn't one to one we have to put the expecations in another structure + let actualExpectations = [| + SourceRoot(path1, [], [ + "SourceControl", "git" + "RevisionId", "RevId1" + "NestedRoot", "NR1A" + "ContainingRoot", path3 + "MappedPath", if deterministic then "/_/NR1A/" else path1 + "SourceLinkUrl", "URL1" + ]) + SourceRoot(path2, [], [ + "SourceControl", "git" + "RevisionId", "" + "NestedRoot", "NR2" + "ContainingRoot", path3 + "MappedPath", if deterministic then "/_/NR2/" else path2 + "SourceLinkUrl", "" + ]) + SourceRoot(path3, [], [ + "SourceControl", "" + "RevisionId", "" + "NestedRoot", "" + "ContainingRoot", "" + "MappedPath", if deterministic then "/_/" else path3 + "SourceLinkUrl", "" + ]) + |] + + let task = newTask() + let outputs = MapSourceRoots.PerformMapping task.Log (items |> Array.map toTaskItem) deterministic + + assertNoErrors task + + match outputs with + | None -> + Assert.Fail("Expected to get some mappings back from this scenario") + | Some mappings -> + let warnings = (task.BuildEngine :?> MockEngine).Warnings |> Seq.map (fun w -> w.Message) + + Assert.AreEqual(6, Seq.length warnings) + Assert.IsTrue(warnings |> Seq.forall (fun w -> w.Contains "duplicate items")) + + [ + "SourceControl", "git", "tfvc" + "RevisionId", "RevId1", "RevId2" + "NestedRoot", "NR1A", "NR1B" + "ContainingRoot", path3, "CR" + "MappedPath", "MP1", "MP2" + "SourceLinkUrl", "URL1", "URL2" + ] + |> List.iter (fun (key, lval, rval) -> + Assert.IsTrue( + (warnings |> Seq.exists (fun warn -> warn.Contains(sprintf "SourceRoot contains duplicate items '%s' with conflicting metadata '%s': '%s' and '%s'" path1 key lval rval))), + sprintf "Expected to find an error message for %s comparing %s and %s, but got %A" key lval rval warnings + ) + ) + + Array.zip actualExpectations mappings + |> Array.iteri checkExpectations + + [] + member this.``missing containing root`` () = + let items = [| + SourceRoot(@"c:\MyProjects\MYPROJECT\", [], []) + SourceRoot(@"c:\MyProjects\MyProject\a\b\", [ + "SourceControl", "Git" + "NestedRoot", "a/b" + "ContainingRoot", @"c:\MyProjects\MyProject\" + ], [] + ) + |] + + let task = newTask() + let outputs = MapSourceRoots.PerformMapping task.Log (items |> Array.map toTaskItem) true + + match outputs with + | None -> + let errors = (task.BuildEngine :?> MockEngine).Errors + Assert.AreEqual(1, errors.Count, "Should have had some errors with path mappings") + let expectedErrors = [@"c:\MyProjects\MyProject\"] |> List.map (sprintf "'%s'") + let errorMessages = errors |> Seq.map (fun e -> e.Message) + + Assert.IsTrue(errorMessages |> Seq.forall (fun error -> error.Contains("corresponding item is not a top-level source root")), + sprintf "Expected to have the same type of errors but had %A" errorMessages + ) + + expectedErrors + |> Seq.iter (fun expectedErrorPath -> + Assert.IsTrue(errorMessages |> Seq.exists (fun err -> err.EndsWith expectedErrorPath), sprintf "expected an error to end with '%s', none did.\nMessages were:\n%A" expectedErrorPath errorMessages) + ) + | Some mappings -> + Assert.Fail("Expected to fail on the inputs") + + [] + member this.``no containing root`` () = + let items = [| + SourceRoot(@"c:\MyProjects\MyProject\", [], []) + SourceRoot(@"c:\MyProjects\MyProject\a\b\", [ + "SourceControl", "Git" + "NestedRoot", "a/b" + ], []) + |] + + let task = newTask() + let outputs = MapSourceRoots.PerformMapping task.Log (items |> Array.map toTaskItem) true + + match outputs with + | None -> + let errors = (task.BuildEngine :?> MockEngine).Errors + Assert.AreEqual(1, errors.Count, "Should have had some errors with path mappings") + let expectedErrors = [@""] |> List.map (sprintf "'%s'") + let errorMessages = errors |> Seq.map (fun e -> e.Message) + + Assert.IsTrue(errorMessages |> Seq.forall (fun error -> error.Contains("corresponding item is not a top-level source root")), + sprintf "Expected to have the same type of errors but had %A" errorMessages + ) + + expectedErrors + |> Seq.iter (fun expectedErrorPath -> + Assert.IsTrue(errorMessages |> Seq.exists (fun err -> err.EndsWith expectedErrorPath), sprintf "expected an error to end with '%s', none did.\nMessages were:\n%A" expectedErrorPath errorMessages) + ) + | Some mappings -> + Assert.Fail("Expected to fail on the inputs") + + [] + [] + [] + member this.``no top level source root`` (deterministic: bool) = + let path1 = Utilities.FixFilePath @"c:\MyProjects\MyProject\a\b\" + let items = [| + SourceRoot(path1, [ + "ContainingRoot", path1 + "NestedRoot", "a/b" + ], [ + "SourceControl", "" + "RevisionId", "" + "NestedRoot", "a/b" + "ContainingRoot", path1 + "MappedPath", path1 + "SourceLinkUrl", "" + ]) + |] + + let task = newTask() + let outputs = MapSourceRoots.PerformMapping task.Log (items |> Array.map toTaskItem) deterministic + + match outputs, deterministic with + | Some _, true -> + Assert.Fail "Expected to fail when deterministic" + | None, true -> + let errors = (task.BuildEngine :?> MockEngine).Errors + Assert.AreEqual(1, errors.Count, "Should have had some errors with path mappings") + let error = errors.[0].Message + Assert.IsTrue(error.Contains "when DeterministicSourcePaths is true") + | None, false -> + Assert.Fail (sprintf "Expected to succeed when not deterministic") + | Some mappings, false -> + Array.zip items mappings + |> Array.iteri checkExpectations