From dfbbacc1b297739b8258f0148f555e555a5cf37a Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Sun, 2 Aug 2020 13:53:48 -0500 Subject: [PATCH 1/6] initial version --- src/fsharp/FSharp.Build/FSBuild.txt | 4 + src/fsharp/FSharp.Build/FSharp.Build.fsproj | 1 + src/fsharp/FSharp.Build/MapSourceRoots.fs | 188 ++++++++++++++++++ .../Microsoft.FSharp.NetSdk.targets | 67 +++++++ .../FSharp.Build/xlf/FSBuild.txt.cs.xlf | 20 ++ .../FSharp.Build/xlf/FSBuild.txt.de.xlf | 20 ++ .../FSharp.Build/xlf/FSBuild.txt.es.xlf | 20 ++ .../FSharp.Build/xlf/FSBuild.txt.fr.xlf | 20 ++ .../FSharp.Build/xlf/FSBuild.txt.it.xlf | 20 ++ .../FSharp.Build/xlf/FSBuild.txt.ja.xlf | 20 ++ .../FSharp.Build/xlf/FSBuild.txt.ko.xlf | 20 ++ .../FSharp.Build/xlf/FSBuild.txt.pl.xlf | 20 ++ .../FSharp.Build/xlf/FSBuild.txt.pt-BR.xlf | 20 ++ .../FSharp.Build/xlf/FSBuild.txt.ru.xlf | 20 ++ .../FSharp.Build/xlf/FSBuild.txt.tr.xlf | 20 ++ .../FSharp.Build/xlf/FSBuild.txt.zh-Hans.xlf | 20 ++ .../FSharp.Build/xlf/FSBuild.txt.zh-Hant.xlf | 20 ++ .../FSharp.Build.UnitTests.fsproj | 1 + .../MapSourceRootsTests.fs | 60 ++++++ 19 files changed, 581 insertions(+) create mode 100644 src/fsharp/FSharp.Build/MapSourceRoots.fs create mode 100644 tests/FSharp.Build.UnitTests/MapSourceRootsTests.fs diff --git a/src/fsharp/FSharp.Build/FSBuild.txt b/src/fsharp/FSharp.Build/FSBuild.txt index 99209176b85..bb94003e7cd 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 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..68f86b7157a --- /dev/null +++ b/src/fsharp/FSharp.Build/MapSourceRoots.fs @@ -0,0 +1,188 @@ +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) = + Path.EndsInDirectorySeparator path + + 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..30177da6117 100644 --- a/src/fsharp/FSharp.Build/Microsoft.FSharp.NetSdk.targets +++ b/src/fsharp/FSharp.Build/Microsoft.FSharp.NetSdk.targets @@ -90,7 +90,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..2e0ea664ec2 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 not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' + The value of SourceRoot.ContainingRoot 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..0121a1ab782 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 not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' + The value of SourceRoot.ContainingRoot 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..d33009b1cd2 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 not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' + The value of SourceRoot.ContainingRoot 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..40356751f32 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 not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' + The value of SourceRoot.ContainingRoot 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..6d9dc3cada9 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 not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' + The value of SourceRoot.ContainingRoot 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..4f81f5f8083 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 not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' + The value of SourceRoot.ContainingRoot 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..fd53c5c758b 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 not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' + The value of SourceRoot.ContainingRoot 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..115be428bb9 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 not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' + The value of SourceRoot.ContainingRoot 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..8be981a3fbd 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 not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' + The value of SourceRoot.ContainingRoot 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..8c5f73f8ba2 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 not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' + The value of SourceRoot.ContainingRoot 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..9194d12990c 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 not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' + The value of SourceRoot.ContainingRoot 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..440a1fe3aae 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 not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' + The value of SourceRoot.ContainingRoot 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..aaa6bcf52c6 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 not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' + The value of SourceRoot.ContainingRoot 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..a61d2e559f2 --- /dev/null +++ b/tests/FSharp.Build.UnitTests/MapSourceRootsTests.fs @@ -0,0 +1,60 @@ + +namespace FSharp.Build.UnitTests + +open Microsoft.Build.Framework +open Microsoft.Build.Utilities +open FSharp.Build +open NUnit.Framework +open System.Collections.Generic + +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 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) + + [] + 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/" + ]) + |] + let task = MapSourceRoots() + let outputs = MapSourceRoots.PerformMapping task.Log (items |> Array.map toTaskItem) true + + match outputs with + | None -> + Assert.Fail("Expected to get some mappings back from ths scenario") + | Some mappings -> + Array.zip items mappings + |> Array.iteri checkExpectations From 8ccf78e8648f5f24703eef1b1bf274178188bb7a Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Sun, 2 Aug 2020 16:26:13 -0500 Subject: [PATCH 2/6] finish porting tests, last test fails --- src/fsharp/FSharp.Build/FSBuild.txt | 2 +- .../FSharp.Build/xlf/FSBuild.txt.cs.xlf | 4 +- .../FSharp.Build/xlf/FSBuild.txt.de.xlf | 4 +- .../FSharp.Build/xlf/FSBuild.txt.es.xlf | 4 +- .../FSharp.Build/xlf/FSBuild.txt.fr.xlf | 4 +- .../FSharp.Build/xlf/FSBuild.txt.it.xlf | 4 +- .../FSharp.Build/xlf/FSBuild.txt.ja.xlf | 4 +- .../FSharp.Build/xlf/FSBuild.txt.ko.xlf | 4 +- .../FSharp.Build/xlf/FSBuild.txt.pl.xlf | 4 +- .../FSharp.Build/xlf/FSBuild.txt.pt-BR.xlf | 4 +- .../FSharp.Build/xlf/FSBuild.txt.ru.xlf | 4 +- .../FSharp.Build/xlf/FSBuild.txt.tr.xlf | 4 +- .../FSharp.Build/xlf/FSBuild.txt.zh-Hans.xlf | 4 +- .../FSharp.Build/xlf/FSBuild.txt.zh-Hant.xlf | 4 +- .../MapSourceRootsTests.fs | 387 +++++++++++++++++- 15 files changed, 400 insertions(+), 41 deletions(-) diff --git a/src/fsharp/FSharp.Build/FSBuild.txt b/src/fsharp/FSharp.Build/FSBuild.txt index bb94003e7cd..b429a789bdc 100644 --- a/src/fsharp/FSharp.Build/FSBuild.txt +++ b/src/fsharp/FSharp.Build/FSBuild.txt @@ -3,4 +3,4 @@ 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 not found in SourceRoot items, or the corresponding item is not a top-level source root: '%s'" \ No newline at end of file +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/xlf/FSBuild.txt.cs.xlf b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.cs.xlf index 2e0ea664ec2..bd142be8836 100644 --- a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.cs.xlf +++ b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.cs.xlf @@ -8,8 +8,8 @@ - The value of SourceRoot.ContainingRoot not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' - The value of SourceRoot.ContainingRoot 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}' + The value of SourceRoot.ContainingRoot was not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' diff --git a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.de.xlf b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.de.xlf index 0121a1ab782..88548526599 100644 --- a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.de.xlf +++ b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.de.xlf @@ -8,8 +8,8 @@ - The value of SourceRoot.ContainingRoot not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' - The value of SourceRoot.ContainingRoot 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}' + The value of SourceRoot.ContainingRoot was not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' diff --git a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.es.xlf b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.es.xlf index d33009b1cd2..180307863c5 100644 --- a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.es.xlf +++ b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.es.xlf @@ -8,8 +8,8 @@ - The value of SourceRoot.ContainingRoot not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' - The value of SourceRoot.ContainingRoot 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}' + The value of SourceRoot.ContainingRoot was not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' diff --git a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.fr.xlf b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.fr.xlf index 40356751f32..cd98018c017 100644 --- a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.fr.xlf +++ b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.fr.xlf @@ -8,8 +8,8 @@ - The value of SourceRoot.ContainingRoot not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' - The value of SourceRoot.ContainingRoot 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}' + The value of SourceRoot.ContainingRoot was not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' diff --git a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.it.xlf b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.it.xlf index 6d9dc3cada9..a694adff73b 100644 --- a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.it.xlf +++ b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.it.xlf @@ -8,8 +8,8 @@ - The value of SourceRoot.ContainingRoot not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' - The value of SourceRoot.ContainingRoot 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}' + The value of SourceRoot.ContainingRoot was not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' diff --git a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.ja.xlf b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.ja.xlf index 4f81f5f8083..c4638dc9e5b 100644 --- a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.ja.xlf +++ b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.ja.xlf @@ -8,8 +8,8 @@ - The value of SourceRoot.ContainingRoot not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' - The value of SourceRoot.ContainingRoot 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}' + The value of SourceRoot.ContainingRoot was not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' diff --git a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.ko.xlf b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.ko.xlf index fd53c5c758b..767de522e96 100644 --- a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.ko.xlf +++ b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.ko.xlf @@ -8,8 +8,8 @@ - The value of SourceRoot.ContainingRoot not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' - The value of SourceRoot.ContainingRoot 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}' + The value of SourceRoot.ContainingRoot was not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' diff --git a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.pl.xlf b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.pl.xlf index 115be428bb9..91acbeb6626 100644 --- a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.pl.xlf +++ b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.pl.xlf @@ -8,8 +8,8 @@ - The value of SourceRoot.ContainingRoot not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' - The value of SourceRoot.ContainingRoot 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}' + The value of SourceRoot.ContainingRoot was not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' 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 8be981a3fbd..2f4a683f5a2 100644 --- a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.pt-BR.xlf +++ b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.pt-BR.xlf @@ -8,8 +8,8 @@ - The value of SourceRoot.ContainingRoot not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' - The value of SourceRoot.ContainingRoot 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}' + The value of SourceRoot.ContainingRoot was not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' diff --git a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.ru.xlf b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.ru.xlf index 8c5f73f8ba2..e960f420a6c 100644 --- a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.ru.xlf +++ b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.ru.xlf @@ -8,8 +8,8 @@ - The value of SourceRoot.ContainingRoot not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' - The value of SourceRoot.ContainingRoot 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}' + The value of SourceRoot.ContainingRoot was not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' diff --git a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.tr.xlf b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.tr.xlf index 9194d12990c..3a8ad0841ed 100644 --- a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.tr.xlf +++ b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.tr.xlf @@ -8,8 +8,8 @@ - The value of SourceRoot.ContainingRoot not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' - The value of SourceRoot.ContainingRoot 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}' + The value of SourceRoot.ContainingRoot was not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' 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 440a1fe3aae..a4e15083f21 100644 --- a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.zh-Hans.xlf +++ b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.zh-Hans.xlf @@ -8,8 +8,8 @@ - The value of SourceRoot.ContainingRoot not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' - The value of SourceRoot.ContainingRoot 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}' + The value of SourceRoot.ContainingRoot was not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' 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 aaa6bcf52c6..6b18b193fe4 100644 --- a/src/fsharp/FSharp.Build/xlf/FSBuild.txt.zh-Hant.xlf +++ b/src/fsharp/FSharp.Build/xlf/FSBuild.txt.zh-Hant.xlf @@ -8,8 +8,8 @@ - The value of SourceRoot.ContainingRoot not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' - The value of SourceRoot.ContainingRoot 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}' + The value of SourceRoot.ContainingRoot was not found in SourceRoot items, or the corresponding item is not a top-level source root: '{0}' diff --git a/tests/FSharp.Build.UnitTests/MapSourceRootsTests.fs b/tests/FSharp.Build.UnitTests/MapSourceRootsTests.fs index a61d2e559f2..c06ab3e1dc1 100644 --- a/tests/FSharp.Build.UnitTests/MapSourceRootsTests.fs +++ b/tests/FSharp.Build.UnitTests/MapSourceRootsTests.fs @@ -7,30 +7,72 @@ open FSharp.Build open NUnit.Framework open System.Collections.Generic -type SourceRoot = - SourceRoot of - path: string * +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 toTaskItem (SourceRoot(path, props, _)) = + + 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) = + 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 + 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 = + member this.``basic deterministic scenarios`` () = + let items = [| SourceRoot(@"c:\packages\SourcePackage1\", [], ["MappedPath", @"/_1/"]) SourceRoot(@"/packages/SourcePackage2/", [], ["MappedPath", @"/_2/"]) @@ -49,12 +91,329 @@ type MapSourceRootsTests() = "MappedPath", @"/_/a/b/" ]) |] - let task = MapSourceRoots() + + 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 -> - Assert.Fail("Expected to get some mappings back from ths scenario") + | 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 + "NestedRooot", "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 + | None, false -> + Assert.Fail "Expected to fail when not deterministic" + | 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") + | Some mappings, false -> Array.zip items mappings |> Array.iteri checkExpectations From c13cb02bce5d2276f77fbd4c1d1857e3a4043c6a Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Sun, 2 Aug 2020 20:47:34 -0500 Subject: [PATCH 3/6] fix test --- src/fsharp/FSharp.Build/MapSourceRoots.fs | 1 - tests/FSharp.Build.UnitTests/MapSourceRootsTests.fs | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/fsharp/FSharp.Build/MapSourceRoots.fs b/src/fsharp/FSharp.Build/MapSourceRoots.fs index 68f86b7157a..97f7f3d6c03 100644 --- a/src/fsharp/FSharp.Build/MapSourceRoots.fs +++ b/src/fsharp/FSharp.Build/MapSourceRoots.fs @@ -148,7 +148,6 @@ type MapSourceRoots () = | NullOrEmpty -> log.LogError(FSBuild.SR.mapSourceRootsNoSuchTopLevelSourceRoot "") | NullOrEmpty -> () - else for root in mappedSourceRoots do root.SetMetadata(MappedPath, root.ItemSpec) diff --git a/tests/FSharp.Build.UnitTests/MapSourceRootsTests.fs b/tests/FSharp.Build.UnitTests/MapSourceRootsTests.fs index c06ab3e1dc1..a5d852b2625 100644 --- a/tests/FSharp.Build.UnitTests/MapSourceRootsTests.fs +++ b/tests/FSharp.Build.UnitTests/MapSourceRootsTests.fs @@ -390,7 +390,7 @@ type MapSourceRootsTests() = let items = [| SourceRoot(path1, [ "ContainingRoot", path1 - "NestedRooot", "a/b" + "NestedRoot", "a/b" ], [ "SourceControl", "" "RevisionId", "" @@ -405,8 +405,6 @@ type MapSourceRootsTests() = let outputs = MapSourceRoots.PerformMapping task.Log (items |> Array.map toTaskItem) deterministic match outputs, deterministic with - | None, false -> - Assert.Fail "Expected to fail when not deterministic" | Some _, true -> Assert.Fail "Expected to fail when deterministic" | None, true -> @@ -414,6 +412,8 @@ type MapSourceRootsTests() = 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 From ea921c14bae2427e3eb95148384bc4c6d08e20ac Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Tue, 4 Aug 2020 16:29:56 -0500 Subject: [PATCH 4/6] fix end tag --- src/fsharp/FSharp.Build/Microsoft.FSharp.NetSdk.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fsharp/FSharp.Build/Microsoft.FSharp.NetSdk.targets b/src/fsharp/FSharp.Build/Microsoft.FSharp.NetSdk.targets index 30177da6117..1b14e274fc1 100644 --- a/src/fsharp/FSharp.Build/Microsoft.FSharp.NetSdk.targets +++ b/src/fsharp/FSharp.Build/Microsoft.FSharp.NetSdk.targets @@ -138,7 +138,7 @@ WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and - + From 989cd617dff7950c72a885b8e3eb8e35564f5acf Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Tue, 4 Aug 2020 16:48:53 -0500 Subject: [PATCH 5/6] use cross-framework implementation of endsWithDirectorySeparator --- src/fsharp/FSharp.Build/MapSourceRoots.fs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/fsharp/FSharp.Build/MapSourceRoots.fs b/src/fsharp/FSharp.Build/MapSourceRoots.fs index 97f7f3d6c03..84310352b8d 100644 --- a/src/fsharp/FSharp.Build/MapSourceRoots.fs +++ b/src/fsharp/FSharp.Build/MapSourceRoots.fs @@ -62,7 +62,11 @@ type MapSourceRoots () = else path + "/" static let endsWithDirectorySeparator (path: string) = - Path.EndsInDirectorySeparator path + 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 From 6dada883c0af99dcc688237ecb73789dbba16808 Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Tue, 4 Aug 2020 17:02:46 -0500 Subject: [PATCH 6/6] UsingTask instead of fully-qualified names --- src/fsharp/FSharp.Build/Microsoft.FSharp.NetSdk.targets | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/fsharp/FSharp.Build/Microsoft.FSharp.NetSdk.targets b/src/fsharp/FSharp.Build/Microsoft.FSharp.NetSdk.targets index 1b14e274fc1..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) @@ -136,9 +137,9 @@ WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and <_MappedSourceRoot Remove="@(_MappedSourceRoot)" /> - + - +