diff --git a/integrationtests/Paket.IntegrationTests/NuGetV3Specs.fs b/integrationtests/Paket.IntegrationTests/NuGetV3Specs.fs index 0a982f023f..0f20cd1160 100644 --- a/integrationtests/Paket.IntegrationTests/NuGetV3Specs.fs +++ b/integrationtests/Paket.IntegrationTests/NuGetV3Specs.fs @@ -15,4 +15,20 @@ let ``#1387 update package in v3``() = update "i001387-nugetv3" |> ignore let lockFile = LockFile.LoadFrom(Path.Combine(scenarioTempPath "i001387-nugetv3","paket.lock")) lockFile.Groups.[Constants.MainDependencyGroup].Resolution.[PackageName "Bender"].Version - |> shouldEqual (SemVer.Parse "3.0.29.0") \ No newline at end of file + |> shouldEqual (SemVer.Parse "3.0.29.0") + +[] +let ``#2700-1 v3 works properly``() = + paketEx true "update" "i002700-1" |> ignore + let lockFile = LockFile.LoadFrom(Path.Combine(scenarioTempPath "i002700-1","paket.lock")) + let mainGroup = lockFile.Groups.[Constants.MainDependencyGroup] + mainGroup.Resolution.[PackageName "Microsoft.CSharp"].Source.Url + |> shouldEqual "https://www.myget.org/F/dotnet-core-svc/api/v3/index.json" + +[] +let ``#2700-2 v2 is not upgraded to v3``() = + updateEx true "i002700-2" |> ignore + let lockFile = LockFile.LoadFrom(Path.Combine(scenarioTempPath "i002700-2","paket.lock")) + let mainGroup = lockFile.Groups.[Constants.MainDependencyGroup] + mainGroup.Resolution.[PackageName "Microsoft.CSharp"].Source.Url + |> shouldEqual "https://www.myget.org/F/dotnet-core-svc" \ No newline at end of file diff --git a/integrationtests/Paket.IntegrationTests/TestHelper.fs b/integrationtests/Paket.IntegrationTests/TestHelper.fs index 77ba4eee5d..4f960eca52 100644 --- a/integrationtests/Paket.IntegrationTests/TestHelper.fs +++ b/integrationtests/Paket.IntegrationTests/TestHelper.fs @@ -70,7 +70,12 @@ let prepare scenario = for file in Directory.GetFiles(scenarioPath, (sprintf "*.%stemplate" ext), SearchOption.AllDirectories) do File.Move(file, Path.ChangeExtension(file, ext)) -let directPaketInPath command scenarioPath = +type PaketMsg = + { IsError : bool; Message : string } + static member isError ({ IsError = e}:PaketMsg) = e + static member getMessage ({ Message = msg }:PaketMsg) = msg + +let directPaketInPathEx command scenarioPath = #if INTERACTIVE let result = ExecProcessWithLambdas (fun info -> @@ -81,12 +86,14 @@ let directPaketInPath command scenarioPath = false (printfn "%s") (printfn "%s") - string result + let res = new ResizeArray() + res.Add (string result) + res #else Environment.SetEnvironmentVariable("PAKET_DETAILED_ERRORS", "true") printfn "%s> paket %s" scenarioPath command let perfMessages = ResizeArray() - let msgs = ResizeArray() + let msgs = ResizeArray() let mutable perfMessagesStarted = false let addAndPrint isError msg = if not isError then @@ -95,7 +102,7 @@ let directPaketInPath command scenarioPath = elif perfMessagesStarted then perfMessages.Add(msg) - msgs.Add((isError, msg)) + msgs.Add({ IsError = isError; Message = msg}) let result = try @@ -114,12 +121,12 @@ let directPaketInPath command scenarioPath = else printfn "ExecProcessWithLambdas failed. Output was: " - for isError, msg in msgs do + for { IsError = isError; Message = msg } in msgs do printfn "%s%s" (if isError then "ERR: " else "") msg reraise() // Only throw after the result <> 0 check because the current test might check the argument parsing // this is the only case where no performance is printed - let isUsageError = result <> 0 && msgs |> Seq.filter fst |> Seq.map snd |> Seq.exists (fun msg -> msg.Contains "USAGE:") + let isUsageError = result <> 0 && msgs |> Seq.filter PaketMsg.isError |> Seq.map PaketMsg.getMessage |> Seq.exists (fun msg -> msg.Contains "USAGE:") if not isUsageError then if perfMessages.Count = 0 then failwith "No Performance messages recieved in test!" @@ -128,47 +135,66 @@ let directPaketInPath command scenarioPath = printfn "%s" msg // always print stderr - for isError, msg in msgs do - if isError then - printfn "ERR: %s" msg + for msg in msgs do + if msg.IsError then + printfn "ERR: %s" msg.Message if result <> 0 then - let errors = String.Join(Environment.NewLine,msgs |> Seq.filter fst |> Seq.map snd) + let errors = String.Join(Environment.NewLine,msgs |> Seq.filter PaketMsg.isError |> Seq.map PaketMsg.getMessage) if String.IsNullOrWhiteSpace errors then failwithf "The process exited with code %i" result else failwith errors - - String.Join(Environment.NewLine,msgs |> Seq.map snd) + msgs #endif +let private fromMessages msgs = + String.Join(Environment.NewLine,msgs |> Seq.map PaketMsg.getMessage) + +let directPaketInPath command scenarioPath = directPaketInPathEx command scenarioPath |> fromMessages -let directPaket command scenario = +let directPaketEx command scenario = partitionForTravis scenario - directPaketInPath command (scenarioTempPath scenario) + directPaketInPathEx command (scenarioTempPath scenario) -let paket command scenario = +let directPaket command scenario = directPaketEx command scenario |> fromMessages + +let paketEx checkZeroWarn command scenario = prepare scenario - directPaket command scenario + let msgs = directPaketEx command scenario + if checkZeroWarn then + msgs + |> Seq.filter PaketMsg.isError + |> Seq.toList + |> shouldEqual [] + msgs -let update scenario = +let paket command scenario = + paketEx false command scenario |> fromMessages + +let updateEx checkZeroWarn scenario = #if INTERACTIVE paket "update --verbose" scenario |> printfn "%s" #else - paket "update" scenario |> ignore + paketEx checkZeroWarn "update" scenario |> ignore #endif LockFile.LoadFrom(Path.Combine(scenarioTempPath scenario,"paket.lock")) -let install scenario = +let update scenario = + updateEx false scenario + +let installEx checkZeroWarn scenario = #if INTERACTIVE paket "install --verbose" scenario |> printfn "%s" #else - paket "install" scenario |> ignore + paketEx checkZeroWarn "install" scenario |> ignore #endif LockFile.LoadFrom(Path.Combine(scenarioTempPath scenario,"paket.lock")) -let restore scenario = paket "restore" scenario |> ignore +let install scenario = installEx false scenario + +let restore scenario = paketEx false "restore" scenario |> ignore let updateShouldFindPackageConflict packageName scenario = try diff --git a/integrationtests/scenarios/i002700-1/before/paket.dependencies b/integrationtests/scenarios/i002700-1/before/paket.dependencies new file mode 100644 index 0000000000..04c578eba1 --- /dev/null +++ b/integrationtests/scenarios/i002700-1/before/paket.dependencies @@ -0,0 +1,3 @@ +source https://www.myget.org/F/dotnet-core-svc/api/v3/index.json + +nuget Microsoft.CSharp prerelease \ No newline at end of file diff --git a/integrationtests/scenarios/i002700-2/before/paket.dependencies b/integrationtests/scenarios/i002700-2/before/paket.dependencies new file mode 100644 index 0000000000..00e376709a --- /dev/null +++ b/integrationtests/scenarios/i002700-2/before/paket.dependencies @@ -0,0 +1,3 @@ +source https://www.myget.org/F/dotnet-core-svc + +nuget Microsoft.CSharp prerelease \ No newline at end of file diff --git a/src/Paket.Core/Dependencies/NuGet.fs b/src/Paket.Core/Dependencies/NuGet.fs index 07130593b4..a2796737a4 100644 --- a/src/Paket.Core/Dependencies/NuGet.fs +++ b/src/Paket.Core/Dependencies/NuGet.fs @@ -448,7 +448,7 @@ let GetVersions force alternativeProjectRoot root (sources, packageName:PackageN return v2Feeds | NuGetV3 source -> - let! versionsAPI = PackageSources.getNuGetV3Resource source AllVersionsAPI + let! versionsAPI = NuGetV3.getNuGetV3Resource source NuGetV3.AllVersionsAPI let auth = source.Authentication |> Option.map toCredentials return [ getVersionsCached "V3" tryNuGetV3 (nugetSource, auth, versionsAPI, packageName) ] | LocalNuGet(path,Some _) -> diff --git a/src/Paket.Core/Dependencies/NuGetV3.fs b/src/Paket.Core/Dependencies/NuGetV3.fs index a8eee85c78..913dd55082 100644 --- a/src/Paket.Core/Dependencies/NuGetV3.fs +++ b/src/Paket.Core/Dependencies/NuGetV3.fs @@ -5,6 +5,8 @@ open Newtonsoft.Json open System.IO open System.Collections.Generic +open System +open System.Threading.Tasks open Paket.Domain open Paket.NuGetCache open Paket.Utils @@ -14,18 +16,97 @@ open Paket.Requirements open Paket.Logging open Paket.PlatformMatching + +type NugetV3SourceResourceJSON = + { [] + Type : string + [] + ID : string } + +type NugetV3SourceRootJSON = + { [] + Resources : NugetV3SourceResourceJSON [] } + +//type NugetV3Source = +// { Url : string +// Authentication : NugetSourceAuthentication option } + +type NugetV3ResourceType = + | AutoComplete + | AllVersionsAPI + //| Registration + | PackageIndex + + member this.AsString = + match this with + | AutoComplete -> "SearchAutoCompleteService" + //| Registration -> "RegistrationsBaseUrl" + | AllVersionsAPI -> "PackageBaseAddress/3.0.0" + | PackageIndex -> "PackageDisplayMetadataUriTemplate" + +// Cache for nuget indices of sources +type ResourceIndex = Map +let private nugetV3Resources = System.Collections.Concurrent.ConcurrentDictionary>() + +let getNuGetV3Resource (source : NugetV3Source) (resourceType : NugetV3ResourceType) : Async = + let key = source + let getResourcesRaw () = + async { + let basicAuth = source.Authentication |> Option.map toCredentials + let! rawData = safeGetFromUrl(basicAuth, source.Url, acceptJson) + let rawData = + match rawData with + | NotFound -> + raise <| new Exception(sprintf "Could not load resources (404) from '%s'" source.Url) + | UnknownError e -> + raise <| new Exception(sprintf "Could not load resources from '%s'" source.Url, e.SourceException) + | SuccessResponse x -> x + + let json = JsonConvert.DeserializeObject(rawData) + let resources = + json.Resources + |> Seq.distinctBy(fun x -> x.Type.ToLower()) + |> Seq.map(fun x -> x.Type.ToLower(), x.ID) + let map = + resources + |> Seq.choose (fun (res, value) -> + let resType = + match res.ToLower() with + | "searchautocompleteservice" -> Some AutoComplete + //| "registrationsbaseurl" -> Some Registration + | s when s.StartsWith "packagedisplaymetadatauritemplate" -> Some PackageIndex + | "packagebaseaddress/3.0.0" -> Some AllVersionsAPI + | _ -> None + match resType with + | None -> None + | Some k -> + Some (k, value)) + |> Seq.distinctBy fst + |> Map.ofSeq + return map + } |> Async.StartAsTask + + async { + let t = nugetV3Resources.GetOrAdd(key, (fun _ -> getResourcesRaw())) + let! res = t |> Async.AwaitTask + return + match res.TryFind resourceType with + | Some s -> s + | None -> failwithf "could not find an %s endpoint for %s" (resourceType.ToString()) source.Url + } + /// [omit] -type JSONResource = +type JSONResource = { Type : string; ID: string } /// [omit] -type JSONVersionData = - { Data : string [] +type JSONVersionData = + { Data : string [] Versions : string [] } /// [omit] -type JSONRootData = +type JSONRootData = { Resources : JSONResource [] } /// [omit] @@ -35,7 +116,7 @@ let private searchDict = new System.Collections.Concurrent.ConcurrentDictionary< let private allVersionsDict = new System.Collections.Concurrent.ConcurrentDictionary<_,System.Threading.Tasks.Task<_>>() /// Calculates the NuGet v3 URL from a NuGet v2 URL. -let calculateNuGet3Path(nugetUrl:string) = +let calculateNuGet3Path(nugetUrl:string) = match nugetUrl.TrimEnd([|'/'|]) with | "http://nuget.org/api/v2" -> Some "http://api.nuget.org/v3/index.json" | "https://nuget.org/api/v2" -> Some "https://api.nuget.org/v3/index.json" @@ -49,7 +130,7 @@ let calculateNuGet3Path(nugetUrl:string) = | _ -> None /// Calculates the NuGet v3 URL from a NuGet v2 URL. -let calculateNuGet2Path(nugetUrl:string) = +let calculateNuGet2Path(nugetUrl:string) = match nugetUrl.TrimEnd([|'/'|]) with | "http://api.nuget.org/v3/index.json" -> Some "http://nuget.org/api/v2" | "https://api.nuget.org/v3/index.json" -> Some "https://nuget.org/api/v2" @@ -63,35 +144,35 @@ let calculateNuGet2Path(nugetUrl:string) = /// [omit] -let getSearchAPI(auth,nugetUrl) = +let getSearchAPI(auth,nugetUrl) = searchDict.GetOrAdd(nugetUrl, fun nugetUrl -> async { match calculateNuGet3Path nugetUrl with | None -> return None - | Some v3Path -> + | Some v3Path -> let source = { Url = v3Path; Authentication = auth } - let! v3res = PackageSources.getNuGetV3Resource source AutoComplete |> Async.Catch - return + let! v3res = getNuGetV3Resource source AutoComplete |> Async.Catch + return match v3res with | Choice1Of2 s -> Some s - | Choice2Of2 ex -> + | Choice2Of2 ex -> if verbose then traceWarnfn "getSearchAPI: %s" (ex.ToString()) None } |> Async.StartAsTask) /// [omit] -let getAllVersionsAPI(auth,nugetUrl) = +let getAllVersionsAPI(auth,nugetUrl) = allVersionsDict.GetOrAdd(nugetUrl, fun nugetUrl -> async { match calculateNuGet3Path nugetUrl with | None -> return None | Some v3Path -> let source = { Url = v3Path; Authentication = auth } - let! v3res = PackageSources.getNuGetV3Resource source AllVersionsAPI |> Async.Catch + let! v3res = getNuGetV3Resource source AllVersionsAPI |> Async.Catch return match v3res with | Choice1Of2 s -> Some s - | Choice2Of2 ex -> + | Choice2Of2 ex -> if verbose then traceWarnfn "getAllVersionsAPI: %s" (ex.ToString()) None } |> Async.StartAsTask) @@ -159,13 +240,13 @@ let extractPackages(response:string) = let private getPackages(auth, nugetURL, packageNamePrefix, maxResults) = async { let! apiRes = getSearchAPI(auth,nugetURL) |> Async.AwaitTask match apiRes with - | Some url -> + | Some url -> let query = sprintf "%s?q=%s&take=%d" url packageNamePrefix maxResults let! response = safeGetFromUrl(auth |> Option.map toCredentials,query,acceptJson) match SafeWebResult.asResult response with | Result.Ok text -> return Result.Ok (extractPackages text) | Result.Error err -> return Result.Error err - | None -> + | None -> if verbose then tracefn "Could not calculate search api from %s" nugetURL return Result.Ok [||] } @@ -176,68 +257,132 @@ let FindPackages(auth, nugetURL, packageNamePrefix, maxResults) = return! getPackages(auth, nugetURL, packageNamePrefix, maxResults) } -type Registration = - { [] - CatalogEntry : string - - [] - PackageContent : string } -type CatalogDependency = +type CatalogDependency = { [] - Id : string - + Id : string [] Range : string } -type CatalogDependencyGroup = +type CatalogDependencyGroup = { [] TargetFramework : string - [] Dependencies : CatalogDependency [] } -type Catalog = +type Catalog = { [] LicenseUrl : string - [] Listed : System.Nullable - + [] + Version : string [] DependencyGroups : CatalogDependencyGroup [] } -let getRegistration (source : NugetV3Source) (packageName:PackageName) (version:SemVerInfo) = + +type PackageIndexPackage = + { [] + Type: string + [] + DownloadLink: string + [] + PackageDetails: Catalog } + +type PackageIndexPage = + { [] + Id: string + [] + Type: string + [] + Packages: PackageIndexPackage [] + [] + Count: int + [] + Lower: string + [] + Upper: string } + +type PackageIndex = + { [] + Id: string + [] + Pages: PackageIndexPage [] + [] + Count : int } + +let private getPackageIndexRaw (source : NugetV3Source) (packageName:PackageName) = async { - let! registrationUrl = PackageSources.getNuGetV3Resource source Registration - let url = sprintf "%s%s/%s.json" registrationUrl (packageName.ToString().ToLower()) (version.Normalize()) + let! registrationUrl = getNuGetV3Resource source PackageIndex + let url = registrationUrl.Replace("{id-lower}", packageName.ToString().ToLower()) // sprintf "%s%s/%s.json" registrationUrl (packageName.ToString().ToLower()) (version.Normalize()) let! rawData = safeGetFromUrl (source.Authentication |> Option.map toCredentials, url, acceptJson) return match rawData with | NotFound -> None //raise <| System.Exception(sprintf "could not get registration data (404) from '%s'" url) | UnknownError err -> raise <| System.Exception(sprintf "could not get registration data from %s" url, err.SourceException) - | SuccessResponse x -> Some (JsonConvert.DeserializeObject(x)) + | SuccessResponse x -> Some (JsonConvert.DeserializeObject(x)) } -let getCatalog url auth = +let private getPackageIndexMemoized = + memoizeAsync (fun (source, packageName) -> getPackageIndexRaw source packageName) +let getPackageIndex source packageName = getPackageIndexMemoized (source, packageName) + + +let private getPackageIndexPageRaw (source:NugetV3Source) (url:string) = async { - let! rawData = safeGetFromUrl (auth, url, acceptJson) + let! rawData = safeGetFromUrl (source.Authentication |> Option.map toCredentials, url, acceptJson) return match rawData with - | NotFound -> - raise <| System.Exception(sprintf "could not get catalog data (404) from '%s'" url) + | NotFound -> raise <| System.Exception(sprintf "could not get registration data (404) from '%s'" url) | UnknownError err -> - raise <| System.Exception(sprintf "could not get catalog data from %s" url, err.SourceException) - | SuccessResponse x -> JsonConvert.DeserializeObject(x) + raise <| System.Exception(sprintf "could not get registration data from %s" url, err.SourceException) + | SuccessResponse x -> JsonConvert.DeserializeObject(x) + } + +let private getPackageIndexPageMemoized = + memoizeAsync (fun (source, url) -> getPackageIndexPageRaw source url) +let getPackageIndexPage source (page:PackageIndexPage) = getPackageIndexPageMemoized (source, page.Id) + + +let getRelevantPage (source:NugetV3Source) (index:PackageIndex) (version:SemVerInfo) = + async { + let pages = + index.Pages + |> Seq.filter (fun p -> SemVer.Parse p.Lower <= version && version <= SemVer.Parse p.Upper) + |> Seq.toList + + match pages with + | [ page ] -> + let! resolvedPage = async { + if page.Count > 0 && (isNull page.Packages || page.Packages.Length = 0) then + return! getPackageIndexPage source page + else return page } + if page.Count > 0 && (isNull page.Packages || page.Packages.Length = 0) then + failwithf "Page should contain packages!" + + let packages = + resolvedPage.Packages + |> Seq.filter (fun p -> SemVer.Parse p.PackageDetails.Version = version) + |> Seq.toList + match packages with + | [ package ] -> return Some package + | _ -> return failwithf "Version '%O' should be part of part of page '%s' but wasn't." version page.Id + | [] -> + return None + | _ :: _ -> + return failwithf "Mulitple pages of V3 index '%s' match with version '%O'" index.Id version } let getPackageDetails (source:NugetV3Source) (packageName:PackageName) (version:SemVerInfo) : Async = async { - let! registrationData = getRegistration source packageName version - match registrationData with + let! pageIndex = getPackageIndex source packageName// version + match pageIndex with | None -> return EmptyResult - | Some registrationData -> - let! catalogData = getCatalog registrationData.CatalogEntry (source.Authentication |> Option.map toCredentials) - + | Some pageIndex -> + let! relevantPage = getRelevantPage source pageIndex version + match relevantPage with + | None -> return EmptyResult + | Some relevantPage -> + let catalogData = relevantPage.PackageDetails let dependencyGroups, dependencies = if catalogData.DependencyGroups = null then [], [] @@ -281,7 +426,7 @@ let getPackageDetails (source:NugetV3Source) (packageName:PackageName) (version: PackageName = packageName.ToString() SourceUrl = source.Url Unlisted = unlisted - DownloadUrl = registrationData.PackageContent + DownloadUrl = relevantPage.DownloadLink LicenseUrl = catalogData.LicenseUrl Version = version.Normalize() CacheVersion = NuGetPackageCache.CurrentCacheVersion } diff --git a/src/Paket.Core/Versioning/PackageSources.fs b/src/Paket.Core/Versioning/PackageSources.fs index c800a980e4..99d95a4207 100644 --- a/src/Paket.Core/Versioning/PackageSources.fs +++ b/src/Paket.Core/Versioning/PackageSources.fs @@ -81,81 +81,8 @@ type NugetSource = member x.BasicAuth = x.Authentication |> Option.map toCredentials -type NugetV3SourceResourceJSON = - { [] - Type : string - [] - ID : string } - -type NugetV3SourceRootJSON = - { [] - Resources : NugetV3SourceResourceJSON [] } - type NugetV3Source = NugetSource -//type NugetV3Source = -// { Url : string -// Authentication : NugetSourceAuthentication option } -type NugetV3ResourceType = - | AutoComplete - | AllVersionsAPI - | Registration - - member this.AsString = - match this with - | AutoComplete -> "SearchAutoCompleteService" - | Registration -> "RegistrationsBaseUrl" - | AllVersionsAPI -> "PackageBaseAddress/3.0.0" - -// Cache for nuget indices of sources -type ResourceIndex = Map -let private nugetV3Resources = System.Collections.Concurrent.ConcurrentDictionary>() - -let getNuGetV3Resource (source : NugetV3Source) (resourceType : NugetV3ResourceType) : Async = - let key = source - let getResourcesRaw () = - async { - let basicAuth = source.Authentication |> Option.map toCredentials - let! rawData = safeGetFromUrl(basicAuth, source.Url, acceptJson) - let rawData = - match rawData with - | NotFound -> - raise <| new Exception(sprintf "Could not load resources (404) from '%s'" source.Url) - | UnknownError e -> - raise <| new Exception(sprintf "Could not load resources from '%s'" source.Url, e.SourceException) - | SuccessResponse x -> x - - let json = JsonConvert.DeserializeObject(rawData) - let resources = - json.Resources - |> Seq.distinctBy(fun x -> x.Type.ToLower()) - |> Seq.map(fun x -> x.Type.ToLower(), x.ID) - let map = - resources - |> Seq.choose (fun (res, value) -> - let resType = - match res.ToLower() with - | "searchautocompleteservice" -> Some AutoComplete - | "registrationsbaseurl" -> Some Registration - | "packagebaseaddress/3.0.0" -> Some AllVersionsAPI - | _ -> None - match resType with - | None -> None - | Some k -> - Some (k, value)) - |> Seq.distinctBy fst - |> Map.ofSeq - return map - } |> Async.StartAsTask - - async { - let t = nugetV3Resources.GetOrAdd(key, (fun _ -> getResourcesRaw())) - let! res = t |> Async.AwaitTask - return - match res.TryFind resourceType with - | Some s -> s - | None -> failwithf "could not find an %s endpoint for %s" (resourceType.ToString()) source.Url - } let userNameRegex = Regex("username[:][ ]*[\"]([^\"]*)[\"]", RegexOptions.IgnoreCase ||| RegexOptions.Compiled) let passwordRegex = Regex("password[:][ ]*[\"]([^\"]*)[\"]", RegexOptions.IgnoreCase ||| RegexOptions.Compiled) let authTypeRegex = Regex("authtype[:][ ]*[\"]([^\"]*)[\"]", RegexOptions.IgnoreCase ||| RegexOptions.Compiled) diff --git a/src/Paket.Core/Versioning/SemVer.fs b/src/Paket.Core/Versioning/SemVer.fs index 85951fa5aa..231c2d63ae 100644 --- a/src/Paket.Core/Versioning/SemVer.fs +++ b/src/Paket.Core/Versioning/SemVer.fs @@ -156,6 +156,7 @@ type SemVerInfo = | _ -> invalidArg "yobj" "cannot compare values of different types" + /// Parser which allows to deal with [Semantic Versioning](http://semver.org/) (SemVer). module SemVer = /// Parses the given version string into a SemVerInfo which can be printed using ToString() or compared diff --git a/src/Paket/Paket.fsproj b/src/Paket/Paket.fsproj index 6675ccc49d..12806e85d3 100644 --- a/src/Paket/Paket.fsproj +++ b/src/Paket/Paket.fsproj @@ -31,8 +31,8 @@ Project paket.exe Project - install - C:\proj\testing\VS2017PerfIssue\ + update + C:\proj\testing\ true @@ -42,8 +42,8 @@ 3 - install - C:\proj\testing\VS2017PerfIssue\ + update + C:\proj\testing\ 14.0