diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..3baeedef --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,20 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Dotnet.ProjInfo.Workspace.Tests", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/test/Dotnet.ProjInfo.Workspace.Tests/bin/Debug/netcoreapp2.1/Dotnet.ProjInfo.Workspace.Tests.dll", + "args": [], + "cwd": "${workspaceFolder}", + "stopAtEntry": false, + "console": "internalConsole" + } + + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 19051d8a..029de1ac 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -8,7 +8,7 @@ "tasks": [ { "taskName": "build", - "args": [ "src/dotnet-proj" ], + "args": [ "src/dotnet-proj.sln" ], "isBuildCommand": true, "showOutput": "silent", "problemMatcher": "$msCompile" diff --git a/Directory.Build.props b/Directory.Build.props index 98260a09..4b8f0a48 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,7 +1,7 @@ - 0.33.0$(VersionSuffix) + 0.34.0$(VersionSuffix) diff --git a/src/Dotnet.ProjInfo.Workspace/Library.fs b/src/Dotnet.ProjInfo.Workspace/Library.fs index bd9b1161..085b06fa 100644 --- a/src/Dotnet.ProjInfo.Workspace/Library.fs +++ b/src/Dotnet.ProjInfo.Workspace/Library.fs @@ -86,10 +86,7 @@ type Loader private (msbuildPath, msbuildNetSdkPath) = Error (GetProjectOptionsErrors.GenericError(proj, "not found")) match loader notify cache project with - | Ok (po, sources, props, additionalProjs) -> - // TODO sources and props are wrong, because not project specific. but of root proj - let loaded po = WorkspaceProjectState.Loaded (po, sources, props) - + | Ok (po, props, additionalProjs) -> let rec visit (p: ProjectOptions) = seq { yield p for p2pRef in p.ReferencedProjects do @@ -102,7 +99,8 @@ type Loader private (msbuildPath, msbuildNetSdkPath) = parsedProjects.AddOrUpdate(getKey proj, proj, fun _ _ -> proj) |> ignore for proj in visit po do - notify (loaded proj) + WorkspaceProjectState.Loaded (proj, props) + |> notify | Error e -> let failed = WorkspaceProjectState.Failed (project, e) @@ -163,3 +161,39 @@ type NetFWInfo private (msbuildPath) = static member Create(config: NetFWInfoConfig) = NetFWInfo(config.MSBuildHost) + +type ProjectViewerTree = + { Name: string; + Items: ProjectViewerItem list } +and [] ProjectViewerItem = + | Compile of string + +type ProjectViewer () = + + member __.Render(proj: ProjectOptions) = + + let compileFiles = + let sources = proj.SourceFiles + match proj.ExtraProjectInfo.ProjectSdkType with + | ProjectSdkType.Verbose _ -> + //compatibility with old behaviour (projectcracker), so test output is exactly the same + //the temp source files (like generated assemblyinfo.fs) are not added to sources + let isTempFile (name: string) = + let tempPath = Path.GetTempPath() + let s = name.ToLower() + s.StartsWith(tempPath.ToLower()) + sources + |> List.filter (fun p -> not(isTempFile p)) + | ProjectSdkType.DotnetSdk _ -> + //the generated assemblyinfo.fs are not shown as sources + let isGeneratedAssemblyinfo (name: string) = + let projName = proj.ProjectFileName |> Path.GetFileNameWithoutExtension + //TODO check is in `obj` dir for the tfm + //TODO better, get the name from fsproj + //TODO cs too + name.EndsWith(sprintf "%s.AssemblyInfo.fs" projName) + sources + |> List.filter (fun p -> not(isGeneratedAssemblyinfo p)) + + { ProjectViewerTree.Name = proj.ProjectFileName |> Path.GetFileNameWithoutExtension + Items = compileFiles |> List.map ProjectViewerItem.Compile } diff --git a/src/Dotnet.ProjInfo.Workspace/ProjectCrackerDotnetSdk.fs b/src/Dotnet.ProjInfo.Workspace/ProjectCrackerDotnetSdk.fs index 607065f0..f0948bb0 100644 --- a/src/Dotnet.ProjInfo.Workspace/ProjectCrackerDotnetSdk.fs +++ b/src/Dotnet.ProjInfo.Workspace/ProjectCrackerDotnetSdk.fs @@ -229,7 +229,7 @@ module ProjectCrackerDotnetSdk = let isSourceFile : (string -> bool) = if Path.GetExtension(file) = ".fsproj" then - (fun n -> n.EndsWith ".fs" || n.EndsWith ".fsx" || n.EndsWith ".fsi") + FscArguments.isCompileFile else (fun n -> n.EndsWith ".cs") @@ -288,25 +288,7 @@ module ProjectCrackerDotnetSdk = try let po, log, additionalProjs = getProjectOptionsFromProjectFile msbuildPath notifyState cache parseAsSdk file - let compileFiles = - let sources = FscArguments.compileFiles po.OtherOptions - match po with - | ProjectExtraInfoBySdk extraInfo -> - match extraInfo.ProjectSdkType with - | ProjectSdkType.Verbose _ -> - //compatibility with old behaviour (projectcracker), so test output is exactly the same - //the temp source files (like generated assemblyinfo.fs) are not added to sources - let isTempFile (name: string) = - let tempPath = Path.GetTempPath() - let s = name.ToLower() - s.StartsWith(tempPath.ToLower()) - sources - |> List.filter (not << isTempFile) - | ProjectSdkType.DotnetSdk _ -> - sources - | _ -> sources - - Ok (po, Seq.toList compileFiles, (log |> Map.ofList), additionalProjs) + Ok (po, (log |> Map.ofList), additionalProjs) with | ProjectInspectException d -> Error d | e -> Error (GenericError(file, e.Message)) diff --git a/src/Dotnet.ProjInfo.Workspace/ProjectCrackerTypes.fs b/src/Dotnet.ProjInfo.Workspace/ProjectCrackerTypes.fs index 15e8b30c..32d8d281 100644 --- a/src/Dotnet.ProjInfo.Workspace/ProjectCrackerTypes.fs +++ b/src/Dotnet.ProjInfo.Workspace/ProjectCrackerTypes.fs @@ -76,7 +76,7 @@ and ProjectReference = type [] WorkspaceProjectState = | Loading of string * ((string * string) list) - | Loaded of ProjectOptions * string list * Map + | Loaded of ProjectOptions * Map | Failed of string * GetProjectOptionsErrors module ProjectRecognizer = @@ -128,11 +128,8 @@ module FscArguments = |> Option.map (makeAbs projDir) let isCompileFile (s:string) = - s.EndsWith(".fs") || s.EndsWith (".fsi") - - let compileFiles = - //TODO filter the one without initial - - List.filter isCompileFile + //TODO check if is not an option, check prefix `-` ? + s.EndsWith(".fs") || s.EndsWith (".fsi") || s.EndsWith (".fsx") let references = //TODO valid also --reference: diff --git a/test/Dotnet.ProjInfo.Workspace.Tests/Tests.fs b/test/Dotnet.ProjInfo.Workspace.Tests/Tests.fs index 592160ed..97c6c74f 100644 --- a/test/Dotnet.ProjInfo.Workspace.Tests/Tests.fs +++ b/test/Dotnet.ProjInfo.Workspace.Tests/Tests.fs @@ -101,7 +101,7 @@ module ExpectNotification = let loaded (name: string) = let isLoaded n = match n with - | WorkspaceProjectState.Loaded (po, _, _) when po.ProjectFileName.EndsWith(name) -> true + | WorkspaceProjectState.Loaded (po, _) when po.ProjectFileName.EndsWith(name) -> true | _ -> false sprintf "loaded %s" name, isLoaded @@ -122,7 +122,7 @@ module ExpectNotification = let minimal_info = match n with | WorkspaceProjectState.Loading (path, _) -> sprintf "loading %s " path - | WorkspaceProjectState.Loaded (po, _, _) -> sprintf "loaded %s" po.ProjectFileName + | WorkspaceProjectState.Loaded (po, _) -> sprintf "loaded %s" po.ProjectFileName | WorkspaceProjectState.Failed (path, _) -> sprintf "failed %s" path Expect.isTrue (f n) (sprintf "expected %s but was %s" name minimal_info) ) @@ -243,6 +243,8 @@ let tests (suiteConfig: TestSuiteConfig) = [ loading "l1.fsproj"; loaded "l1.fsproj" ] |> expectNotifications (watcher.Notifications) + let [_; WorkspaceProjectState.Loaded(l1Loaded,_)] = watcher.Notifications + let parsed = loader.Projects Expect.equal parsed.Length 1 "lib" @@ -257,6 +259,8 @@ let tests (suiteConfig: TestSuiteConfig) = (Path.GetTempPath()) / ".NETFramework,Version=v4.6.1.AssemblyAttributes.fs" ] Expect.equal l1Parsed.SourceFiles expectedSources "check sources" + + Expect.equal l1Parsed l1Loaded "notificaton and parsed should be the same" ) testCase |> withLog "can load sample2" (fun logger fs -> @@ -278,6 +282,8 @@ let tests (suiteConfig: TestSuiteConfig) = [ loading "n1.fsproj"; loaded "n1.fsproj" ] |> expectNotifications (watcher.Notifications) + let [_; WorkspaceProjectState.Loaded(n1Loaded,_)] = watcher.Notifications + let parsed = loader.Projects Expect.equal parsed.Length 1 "console and lib" @@ -292,6 +298,8 @@ let tests (suiteConfig: TestSuiteConfig) = |> List.map Path.GetFullPath Expect.equal n1Parsed.SourceFiles expectedSources "check sources" + + Expect.equal n1Parsed n1Loaded "notificaton and parsed should be the same" ) testCase |> withLog ("can load sample3" |> knownFailure) (fun logger fs -> @@ -319,6 +327,8 @@ let tests (suiteConfig: TestSuiteConfig) = [ loading "c1.fsproj"; loading "l1.csproj"; loading "l2.fsproj"; loaded "c1.fsproj"; loaded "l1.csproj"; loaded "l2.fsproj"; ] |> expectNotifications (watcher.Notifications) + let [_; _; _; WorkspaceProjectState.Loaded(c1Loaded,_); WorkspaceProjectState.Loaded(l1Loaded,_); WorkspaceProjectState.Loaded(l2Loaded,_)] = watcher.Notifications + let parsed = loader.Projects Expect.equal parsed.Length 3 (sprintf "console (F#) and lib (F#) and lib (C#), but was %A" (parsed |> Array.map (fun x -> x.Key))) @@ -329,7 +339,7 @@ let tests (suiteConfig: TestSuiteConfig) = let l1ExpectedSources = [ l1Dir / "obj/Debug/netstandard2.0/l1.AssemblyInfo.cs" - l1Dir / "Class1.fs" ] + l1Dir / "Class1.cs" ] |> List.map Path.GetFullPath // TODO C# doesnt have OtherOptions or SourceFiles atm. it should @@ -373,6 +383,9 @@ let tests (suiteConfig: TestSuiteConfig) = Expect.equal c1Parsed.SourceFiles c1ExpectedSources "check sources" + Expect.equal l1Parsed l1Loaded "l1 notificaton and parsed should be the same" + Expect.equal l2Parsed l2Loaded "l2 notificaton and parsed should be the same" + Expect.equal c1Parsed c1Loaded "c1 notificaton and parsed should be the same" ) testCase |> withLog "can load sample4" (fun logger fs -> @@ -398,6 +411,8 @@ let tests (suiteConfig: TestSuiteConfig) = [ loading "m1.fsproj"; loading "m1.fsproj"; loaded "m1.fsproj" ] |> expectNotifications (watcher.Notifications) + let [_; _; WorkspaceProjectState.Loaded(m1Loaded,_)] = watcher.Notifications + let parsed = loader.Projects Expect.equal parsed.Length 1 (sprintf "multi-tfm lib (F#), but was %A" (parsed |> Array.map (fun x -> x.Key))) @@ -412,6 +427,8 @@ let tests (suiteConfig: TestSuiteConfig) = |> List.map Path.GetFullPath Expect.equal m1Parsed.SourceFiles m1ExpectedSources "check sources" + + Expect.equal m1Parsed m1Loaded "m1 notificaton and parsed should be the same" ) testCase |> withLog "can load sample5" (fun logger fs -> @@ -433,6 +450,8 @@ let tests (suiteConfig: TestSuiteConfig) = [ loading "l2.csproj"; loaded "l2.csproj" ] |> expectNotifications (watcher.Notifications) + let [_; WorkspaceProjectState.Loaded(l2Loaded,_)] = watcher.Notifications + let parsed = loader.Projects Expect.equal parsed.Length 1 "lib" @@ -441,7 +460,7 @@ let tests (suiteConfig: TestSuiteConfig) = parsed |> expectFind projPath { ProjectKey.ProjectPath = projPath; TargetFramework = "netstandard2.0" } "a C# lib" - let l2ExpectedSources = + let _l2ExpectedSources = [ projDir / "obj/Debug/netstandard2.0/l2.AssemblyInfo.cs" projDir / "Class1.cs" ] |> List.map Path.GetFullPath @@ -449,6 +468,8 @@ let tests (suiteConfig: TestSuiteConfig) = // TODO C# doesnt have OtherOptions or SourceFiles atm. it should // Expect.equal l2Parsed.SourceFiles l2ExpectedSources "check sources" Expect.equal l2Parsed.SourceFiles [] "check sources" + + Expect.equal l2Parsed l2Loaded "l2 notificaton and parsed should be the same" ) testCase |> withLog "can load sln" (fun logger fs -> @@ -661,6 +682,226 @@ let tests (suiteConfig: TestSuiteConfig) = ) ] + let view = + + let createLoader logger = + let msbuildLocator = MSBuildLocator() + let config = LoaderConfig.Default msbuildLocator + logConfig logger config + let loader = Loader.Create(config) + loader + + let renderOf sampleProj sources = + { ProjectViewerTree.Name = sampleProj.ProjectFile |> Path.GetFileNameWithoutExtension + Items = sources |> List.map ProjectViewerItem.Compile } + + testList "view" [ + + testCase |> withLog "can render sample1" (fun logger fs -> + let testDir = inDir fs "render_sample1" + let sampleProj = ``sample1 OldSdk library`` + copyDirFromAssets fs sampleProj.ProjDir testDir + + let projPath = testDir/ (sampleProj.ProjectFile) + let projDir = Path.GetDirectoryName projPath + + fs.cd projDir + nuget fs ["restore"; "-PackagesDirectory"; "packages"] + |> checkExitCodeZero + + fs.cd testDir + + // msbuild fs [projPath; "/t:Build"] + // |> checkExitCodeZero + + let loader = createLoader logger + + loader.LoadProjects [projPath] + + let parsed = loader.Projects + + let l1Parsed = + parsed + |> expectFind projPath { ProjectKey.ProjectPath = projPath; TargetFramework = "net461" } "a lib" + + let viewer = ProjectViewer () + + let rendered = viewer.Render l1Parsed + + let expectedSources = + [ projDir / "AssemblyInfo.fs" + projDir / "Library.fs" ] + + Expect.equal rendered (renderOf sampleProj expectedSources) "check rendered project" + ) + + testCase |> withLog "can render sample2" (fun logger fs -> + let testDir = inDir fs "render_sample2" + let sampleProj = ``sample2 NetSdk library`` + copyDirFromAssets fs sampleProj.ProjDir testDir + + let projPath = testDir/ (sampleProj.ProjectFile) + let projDir = Path.GetDirectoryName projPath + + dotnet fs ["restore"; projPath] + |> checkExitCodeZero + + let loader = createLoader logger + + loader.LoadProjects [projPath] + + let parsed = loader.Projects + + let n1Parsed = + parsed + |> expectFind projPath { ProjectKey.ProjectPath = projPath; TargetFramework = "netstandard2.0" } "first is a lib" + + let viewer = ProjectViewer () + + let rendered = viewer.Render n1Parsed + + let expectedSources = + [ projDir / "Library.fs" ] + |> List.map Path.GetFullPath + + Expect.equal rendered (renderOf sampleProj expectedSources) "check rendered project" + ) + + testCase |> withLog ("can render sample3" |> knownFailure) (fun logger fs -> + let testDir = inDir fs "render_sample3" + let c1Proj = ``sample3 Netsdk projs`` + copyDirFromAssets fs c1Proj.ProjDir testDir + + let projPath = testDir/ (c1Proj.ProjectFile) + let projDir = Path.GetDirectoryName projPath + + let (l1Proj, l1, l1Dir) :: (l2Proj, l2, l2Dir) :: [] = + c1Proj.ProjectReferences + |> List.map (fun p2p -> p2p, Path.GetFullPath (testDir/ p2p.ProjectFile) ) + |> List.map (fun (p2p, path) -> p2p, path, Path.GetDirectoryName(path)) + + dotnet fs ["build"; projPath] + |> checkExitCodeZero + + let loader = createLoader logger + + loader.LoadProjects [projPath] + + let parsed = loader.Projects + + let l1Parsed = + parsed + |> expectFind l1 { ProjectKey.ProjectPath = l1; TargetFramework = "netstandard2.0" } "the C# lib" + + let l2Parsed = + parsed + |> expectFind l2 { ProjectKey.ProjectPath = l2; TargetFramework = "netstandard2.0" } "the F# lib" + + let c1Parsed = + parsed + |> expectFind projPath { ProjectKey.ProjectPath = projPath; TargetFramework = "netcoreapp2.1" } "the F# console" + + let viewer = ProjectViewer () + + let _l1ExpectedSources = + [ l1Dir / "Class1.fs" ] + |> List.map Path.GetFullPath + + // TODO C# doesnt have OtherOptions or SourceFiles atm. it should + Expect.equal (viewer.Render l1Parsed) (renderOf l1Proj []) "check rendered l1" + + let l2ExpectedSources = + [ l2Dir / "Library.fs" ] + |> List.map Path.GetFullPath + + Expect.equal (viewer.Render l2Parsed) (renderOf l2Proj l2ExpectedSources) "check rendered l2" + + if (isOSX () && suiteConfig.SkipKnownFailure) then + let errorOnOsx = + """ + check sources. + expected: + ["/Users/travis/build/enricosada/dotnet-proj-info/test/testrun_ws/render_sample3/c1/Program.fs"] + actual: + [] + + The OtherOptions is empty. + """.Trim() + Tests.skiptest (sprintf "Known failure on OSX travis. error is %s" errorOnOsx) + //TODO check failure on osx + + let c1ExpectedSources = + [ projDir / "Program.fs" ] + |> List.map Path.GetFullPath + + Expect.equal (viewer.Render c1Parsed) (renderOf c1Proj c1ExpectedSources) "check rendered c1" + ) + + testCase |> withLog "can render sample4" (fun logger fs -> + let testDir = inDir fs "render_sample4" + let m1Proj = ``sample4 NetSdk multi tfm`` + copyDirFromAssets fs m1Proj.ProjDir testDir + + let projPath = testDir/ (m1Proj.ProjectFile) + let projDir = Path.GetDirectoryName projPath + + dotnet fs ["restore"; projPath] + |> checkExitCodeZero + + for (tfm, _) in m1Proj.TargetFrameworks |> Map.toList do + printfn "tfm: %s" tfm + + let loader = createLoader logger + + loader.LoadProjects [projPath] + + let parsed = loader.Projects + + let m1Parsed = + parsed + |> expectFind projPath { ProjectKey.ProjectPath = projPath; TargetFramework = "netstandard2.0" } "the F# console" + + let viewer = ProjectViewer () + + let m1ExpectedSources = + [ projDir / "LibraryA.fs" ] + |> List.map Path.GetFullPath + + Expect.equal (viewer.Render m1Parsed) (renderOf m1Proj m1ExpectedSources) "check rendered m1" + ) + + testCase |> withLog "can render sample5" (fun logger fs -> + let testDir = inDir fs "render_sample5" + let l2Proj = ``sample5 NetSdk CSharp library`` + copyDirFromAssets fs l2Proj.ProjDir testDir + + let projPath = testDir/ (l2Proj.ProjectFile) + let projDir = Path.GetDirectoryName projPath + + dotnet fs ["restore"; projPath] + |> checkExitCodeZero + + let loader = createLoader logger + + loader.LoadProjects [projPath] + + let parsed = loader.Projects + + let l2Parsed = + parsed + |> expectFind projPath { ProjectKey.ProjectPath = projPath; TargetFramework = "netstandard2.0" } "a C# lib" + + let viewer = ProjectViewer () + + let l2ExpectedSources = + [ projDir / "Class1.cs" ] + |> List.map Path.GetFullPath + + // TODO C# doesnt have OtherOptions or SourceFiles atm. it should + Expect.equal (viewer.Render l2Parsed) (renderOf l2Proj []) "check rendered l2" + ) + ] + let msbuild = testList "msbuild" [ @@ -694,6 +935,6 @@ let tests (suiteConfig: TestSuiteConfig) = ) ] - [ valid; invalid; fsx; netfw; msbuild ] + [ valid; invalid; fsx; netfw; msbuild; view ] |> testList "workspace" |> testSequenced