diff --git a/src/Paket.Core/BindingRedirects.fs b/src/Paket.Core/BindingRedirects.fs index 5974bac921..5cdb1087af 100644 --- a/src/Paket.Core/BindingRedirects.fs +++ b/src/Paket.Core/BindingRedirects.fs @@ -3,20 +3,28 @@ open System open System.Xml.Linq open System.IO +open System.Reflection [] -module private Helpers = +module private XmlLinq = let asOption = function | null -> None | x -> Some x - let tryGetElement name (xe:XContainer) = xe.Element(XName.Get name) |> asOption - let getElements name (xe:XContainer) = xe.Elements(XName.Get name) - let tryGetAttribute name (xe:XElement) = xe.Attribute(XName.Get name) |> asOption - let createElement name attributes = XElement(XName.Get name, attributes |> Seq.map(fun (name,value) -> XAttribute(XName.Get name, value))) + let private xname ns name = XName.Get(name, defaultArg ns "") + let tryGetElement ns name (xe:XContainer) = + let XName = xname ns name + xe.Element XName |> asOption + let getElements ns name (xe:XContainer) = xname ns name |> xe.Elements + let tryGetAttribute name (xe:XElement) = xe.Attribute(xname None name) |> asOption + let createElement ns name attributes = XElement(xname ns name, attributes |> Seq.map(fun (name,value) -> XAttribute(xname None name, value))) let ensurePathExists (xpath:string) (item:XContainer) = (item, xpath.Split([|'/'|], StringSplitOptions.RemoveEmptyEntries)) ||> Seq.fold(fun parent node -> - match parent |> tryGetElement node with + let node, ns = + match node.Split '!' with + | [| node; ns |] -> node, Some ns + | _ -> node, None + match parent |> tryGetElement ns node with | None -> - let node = XElement(XName.Get node) + let node = XElement(XName.Get(node, defaultArg ns "")) parent.Add node node :> XContainer | Some existingNode -> existingNode :> XContainer) @@ -25,46 +33,62 @@ module private Helpers = type BindingRedirect = { AssemblyName : string Version : string - PublicKeyToken : string option + PublicKeyToken : string Culture : string option } /// Updates the supplied MSBuild document with the supplied binding redirect. -let setRedirect (doc:XDocument) bindingRedirect = - let assemblyBinding = doc |> ensurePathExists "/configuration/runtime/assemblyBinding" +let internal setRedirect (doc:XDocument) bindingRedirect = + let bindingNs = "urn:schemas-microsoft-com:asm.v1" + let createElementWithNs = createElement (Some bindingNs) + let tryGetElementWithNs = tryGetElement (Some bindingNs) + let getElementsWithNs = getElements (Some bindingNs) + + let assemblyBinding = doc |> ensurePathExists ("/configuration/runtime/assemblyBinding!" + bindingNs) let dependentAssembly = - assemblyBinding |> getElements "dependentAssembly" + assemblyBinding + |> getElementsWithNs "dependentAssembly" |> Seq.tryFind(fun dependentAssembly -> defaultArg (dependentAssembly - |> tryGetElement "assemblyIdentity" + |> tryGetElementWithNs "assemblyIdentity" |> Option.bind(tryGetAttribute "name") |> Option.map(fun attribute -> attribute.Value = bindingRedirect.AssemblyName)) false) |> function | Some dependentAssembly -> dependentAssembly | None -> - let dependentAssembly = createElement "dependentAssembly" [] - dependentAssembly.Add(createElement "assemblyIdentity" ([ "name", Some bindingRedirect.AssemblyName - "publicKeyToken", bindingRedirect.PublicKeyToken - "culture", bindingRedirect.Culture ] - |> Seq.choose(fun (key,value) -> match value with Some v -> Some(key, v) | None -> None))) + let dependentAssembly = createElementWithNs "dependentAssembly" [] + dependentAssembly.Add(createElementWithNs "assemblyIdentity" ([ "name", bindingRedirect.AssemblyName + "publicKeyToken", bindingRedirect.PublicKeyToken + "culture", defaultArg bindingRedirect.Culture "neutral" ])) assemblyBinding.Add(dependentAssembly) dependentAssembly - let newRedirect = createElement "bindingRedirect" [ "oldVersion", sprintf "0.0.0.0-%s" bindingRedirect.Version - "newVersion", bindingRedirect.Version ] - match dependentAssembly |> tryGetElement "bindingRedirect" with + let newRedirect = createElementWithNs "bindingRedirect" [ "oldVersion", sprintf "0.0.0.0-%s" bindingRedirect.Version + "newVersion", bindingRedirect.Version ] + match dependentAssembly |> tryGetElementWithNs "bindingRedirect" with | Some redirect -> redirect.ReplaceWith(newRedirect) | None -> dependentAssembly.Add(newRedirect) doc /// Applies a set of binding redirects to a single configuration file. -let applyBindingRedirects bindingRedirects (configFilePath:string) = +let private applyBindingRedirects bindingRedirects (configFilePath:string) = let config = XDocument.Load configFilePath let config = Seq.fold setRedirect config bindingRedirects config.Save configFilePath /// Applies a set of binding redirects to all .config files in a specific folder. -let applyBindingRedirectsToFolder bindingRedirects rootPath = - Directory.GetFiles(rootPath, "*.config", SearchOption.AllDirectories) - |> Seq.iter (applyBindingRedirects bindingRedirects) \ No newline at end of file +let applyBindingRedirectsToFolder rootPath bindingRedirects = + let getFiles searchPattern = Directory.GetFiles(rootPath, searchPattern, SearchOption.AllDirectories) |> List.ofArray + + getFiles "web.config" @ + getFiles "app.config" + |> Seq.iter (applyBindingRedirects bindingRedirects) + +/// Calculates the short form of the public key token for use with binding redirects, if it exists. +let getPublicKeyToken (assembly:Assembly) = + ("", assembly.GetName().GetPublicKeyToken()) + ||> Array.fold(fun state b -> state + b.ToString("X2")) + |> function + | "" -> None + | token -> Some <| token.ToLower() diff --git a/src/Paket.Core/CustomAssemblyInfo.fs b/src/Paket.Core/CustomAssemblyInfo.fs new file mode 100644 index 0000000000..a404811616 --- /dev/null +++ b/src/Paket.Core/CustomAssemblyInfo.fs @@ -0,0 +1,5 @@ +namespace System +open System.Runtime.CompilerServices + +[] +do () \ No newline at end of file diff --git a/src/Paket.Core/InstallModel.fs b/src/Paket.Core/InstallModel.fs index 1031b9ba18..e3f5e0ee65 100644 --- a/src/Paket.Core/InstallModel.fs +++ b/src/Paket.Core/InstallModel.fs @@ -31,6 +31,10 @@ type Reference = let fi = new FileInfo(normalizePath lib) fi.Name.Replace(fi.Extension, "") + member this.Path = + match this with + | Library path -> path + | FrameworkAssemblyReference path -> path type InstallFiles = { References : Reference Set diff --git a/src/Paket.Core/InstallProcess.fs b/src/Paket.Core/InstallProcess.fs index 4df1fba9d8..62d8312cf9 100644 --- a/src/Paket.Core/InstallProcess.fs +++ b/src/Paket.Core/InstallProcess.fs @@ -4,11 +4,13 @@ module Paket.InstallProcess open Paket open Paket.Domain open Paket.Logging +open Paket.BindingRedirects open Paket.ModuleResolver open Paket.PackageResolver open System.IO open System.Collections.Generic open FSharp.Polyfill +open System.Reflection let private findPackagesWithContent (root,usedPackages:Dictionary<_,_>) = usedPackages @@ -93,6 +95,24 @@ let createModel(root, sources,force, lockFile:LockFile) = extractedPackages +/// Applies binding redirects for all strong-named default fallback references to all app. and web. config files. +let private applyBindingRedirects root extractedPackages = + extractedPackages + |> Seq.map(fun (package, model) -> model.DefaultFallback.References) + |> Seq.reduce (+) + |> Set.toSeq + |> Seq.map(fun ref -> Assembly.LoadFrom ref.Path) + |> Seq.choose(fun assembly -> + assembly + |> BindingRedirects.getPublicKeyToken + |> Option.map(fun token -> assembly, token)) + |> Seq.map(fun (assembly, token) -> + { BindingRedirect.AssemblyName = assembly.GetName().Name + Version = assembly.GetName().Version.ToString() + PublicKeyToken = token + Culture = None }) + |> applyBindingRedirectsToFolder root + /// Installs the given all packages from the lock file. let Install(sources,force, hard, lockFile:LockFile) = let root = FileInfo(lockFile.FileName).Directory.FullName @@ -109,7 +129,7 @@ let Install(sources,force, hard, lockFile:LockFile) = |> Array.choose (fun p -> ProjectFile.FindReferencesFile(FileInfo(p.FileName)) |> Option.map (fun r -> p, ReferencesFile.FromFile(r))) - for project,referenceFile in applicableProjects do + for project, referenceFile in applicableProjects do verbosefn "Installing to %s" project.FileName let usedPackages = lockFile.GetPackageHull(referenceFile) @@ -142,3 +162,5 @@ let Install(sources,force, hard, lockFile:LockFile) = project.UpdateFileItems(gitRemoteItems @ nuGetFileItems, hard) project.Save() + + extractedPackages |> applyBindingRedirects root \ No newline at end of file diff --git a/src/Paket.Core/Paket.Core.fsproj b/src/Paket.Core/Paket.Core.fsproj index b6d6c87c85..73a9eabeda 100644 --- a/src/Paket.Core/Paket.Core.fsproj +++ b/src/Paket.Core/Paket.Core.fsproj @@ -436,7 +436,6 @@ True Streams.fs - FSharp.Core.optdata Always @@ -471,6 +470,7 @@ + @@ -479,10 +479,11 @@ - + + diff --git a/src/Paket/App.config b/src/Paket/App.config index 67daae8f55..56f1efd1aa 100644 --- a/src/Paket/App.config +++ b/src/Paket/App.config @@ -1,14 +1,11 @@  - - - - - - - - + + + + + + + + \ No newline at end of file diff --git a/tests/Paket.Tests/BindingRedirect.fs b/tests/Paket.Tests/BindingRedirect.fs index 02d11f2d8b..11e77a3f0b 100644 --- a/tests/Paket.Tests/BindingRedirect.fs +++ b/tests/Paket.Tests/BindingRedirect.fs @@ -9,18 +9,20 @@ open FsUnit let defaultRedirect = { AssemblyName = "Assembly" Version = "1.0.0" - PublicKeyToken = None + PublicKeyToken = "PUBLIC_KEY" Culture = None } let sampleDoc() = """ """ |> XDocument.Parse -let private containsDescendents count elementName (doc:XDocument) = - Assert.AreEqual(count, doc.Descendants(XName.Get elementName) |> Seq.length) -let private containsSingleDescendent = containsDescendents 1 -let private createSimpleBindingRedirectXml assembly version = sprintf "\r\n \r\n \r\n" assembly version version -let private createFullBindingRedirectXml assembly version culture publicKey = sprintf "\r\n \r\n \r\n" assembly publicKey culture version version +let private bindingNs = "urn:schemas-microsoft-com:asm.v1" +let private containsDescendents count ns elementName (doc:XDocument) = + Assert.AreEqual(count, doc.Descendants(XName.Get(elementName, ns)) |> Seq.length) +let private containsSingleDescendent = containsDescendents 1 "" +let private containsSingleDescendentWithNs = containsDescendents 1 bindingNs +let private createBindingRedirectXml culture assembly version publicKey = sprintf "\r\n \r\n \r\n" assembly publicKey culture version version +let private xNameForNs name = XName.Get(name, bindingNs) [] let ``add missing elements to configuration file``() = @@ -31,7 +33,7 @@ let ``add missing elements to configuration file``() = // Assert doc |> containsSingleDescendent "runtime" - doc |> containsSingleDescendent "assemblyBinding" + doc |> containsSingleDescendentWithNs "assemblyBinding" [] let ``add new binding redirect to configuration file``() = @@ -41,29 +43,30 @@ let ``add new binding redirect to configuration file``() = setRedirect doc defaultRedirect |> ignore // Assert - doc |> containsSingleDescendent "dependentAssembly" + doc |> containsSingleDescendentWithNs "dependentAssembly" + [] -let ``correctly creates a simple binding redirect``() = +let ``correctly creates a binding redirect``() = let doc = sampleDoc() - setRedirect doc defaultRedirect |> ignore + setRedirect doc { defaultRedirect with Culture = Some "en-gb"; PublicKeyToken = "123456" } |> ignore // Act - let dependency = doc.Descendants(XName.Get "dependentAssembly") |> Seq.head + let dependency = doc.Descendants(xNameForNs "dependentAssembly") |> Seq.head // Assert - dependency.ToString() |> shouldEqual (createSimpleBindingRedirectXml "Assembly" "1.0.0") + dependency.ToString() |> shouldEqual (createBindingRedirectXml "en-gb" "Assembly" "1.0.0" "123456") [] -let ``correctly creates a full binding redirect``() = +let ``correctly creates a binding redirect with default culture``() = let doc = sampleDoc() - setRedirect doc { defaultRedirect with Culture = Some "en-gb"; PublicKeyToken = Some "123456" } |> ignore + setRedirect doc defaultRedirect |> ignore // Act - let dependency = doc.Descendants(XName.Get "dependentAssembly") |> Seq.head + let dependency = doc.Descendants(xNameForNs "dependentAssembly") |> Seq.head // Assert - dependency.ToString() |> shouldEqual (createFullBindingRedirectXml "Assembly" "1.0.0" "en-gb" "123456") + dependency.ToString() |> shouldEqual (createBindingRedirectXml "neutral" "Assembly" "1.0.0" "PUBLIC_KEY") [] let ``does not overwrite existing binding redirects for a different assembly``() = @@ -74,7 +77,7 @@ let ``does not overwrite existing binding redirects for a different assembly``() setRedirect doc { defaultRedirect with AssemblyName = "OtherAssembly" } |> ignore // Assert - doc |> containsDescendents 2 "dependentAssembly" + doc |> containsDescendents 2 bindingNs "dependentAssembly" [] let ``does not add a new binding redirect if one already exists for the assembly``() = @@ -85,7 +88,7 @@ let ``does not add a new binding redirect if one already exists for the assembly setRedirect doc { defaultRedirect with Version = "2.0.0" } |> ignore // Assert - doc |> containsSingleDescendent "dependentAssembly" + doc |> containsSingleDescendentWithNs "dependentAssembly" [] let ``correctly updates an existing binding redirect``() = @@ -96,6 +99,6 @@ let ``correctly updates an existing binding redirect``() = setRedirect doc { defaultRedirect with Version = "2.0.0" } |> ignore // Assert - let dependency = doc.Descendants(XName.Get "dependentAssembly") |> Seq.head - dependency.ToString() |> shouldEqual (createSimpleBindingRedirectXml "Assembly" "2.0.0") + let dependency = doc.Descendants(xNameForNs "dependentAssembly") |> Seq.head + dependency.ToString() |> shouldEqual (createBindingRedirectXml "neutral" "Assembly" "2.0.0" "PUBLIC_KEY")