Skip to content

Commit 6bdcf80

Browse files
authored
Merge pull request fsprojects#2428 from bhugot/PaketPackTargetFramework
Implementation for fsprojects#913 Paket.pack: add support for nuget dependencies conditional on target framework
2 parents b396a0a + b6a742b commit 6bdcf80

File tree

6 files changed

+248
-56
lines changed

6 files changed

+248
-56
lines changed

docs/content/template-files.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,17 @@ The `LOCKEDVERSION` placeholder allows to reference the currently used dependenc
179179
FSharp.Core >= 4.3.1
180180
Other.Dep ~> LOCKEDVERSION
181181

182+
It's possible to add a line to constrain the targetFramework:
183+
184+
dependencies
185+
framework: net45
186+
FSharp.Core 4.3.1
187+
My.OtherThing
188+
framework: netstandard11
189+
FSharp.Core 4.3.1
190+
191+
Like that the package is only going to be used by a project >= net45 and for >= netstandard11 it will not use My.OtherThing package.
192+
182193
In a project file, the following dependencies will be added:
183194

184195
* any Paket dependency with the range specified in the [`paket.dependencies` file](dependencies-file.html).

src/Paket.Core/Packaging/NupkgWriter.fs

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ open Paket.Xml
88
open System.Text
99
open System.Text.RegularExpressions
1010
open System.Xml
11+
open Paket.Requirements
1112

1213
module internal NupkgWriter =
1314

@@ -93,13 +94,34 @@ module internal NupkgWriter =
9394
dep.SetAttributeValue(XName.Get "version", version)
9495
dep
9596

97+
let buildGroupNode (framework:FrameworkIdentifier, add) =
98+
let g = XElement(ns + "group")
99+
g.SetAttributeValue(XName.Get "targetFramework", framework.ToString())
100+
add g
101+
g
102+
103+
104+
let buildDependencyNodes (excludedDependencies, add, dependencyList) =
105+
dependencyList
106+
|> List.filter (fun (a, _, _) -> Set.contains a excludedDependencies |> not)
107+
|> List.map (fun (a,b,_) -> a,b)
108+
|> List.iter (buildDependencyNode >> add)
109+
110+
let buildDependencyNodesByGroup excludedDependencies add dependencyList framework =
111+
let node = buildGroupNode(framework, add)
112+
buildDependencyNodes(excludedDependencies, node.Add, dependencyList)
113+
96114
let buildDependenciesNode excludedDependencies dependencyList =
97115
if List.isEmpty dependencyList then () else
98116
let d = XElement(ns + "dependencies")
99-
dependencyList
100-
|> List.filter (fun d -> Set.contains (fst d) excludedDependencies |> not)
101-
|> List.iter (buildDependencyNode >> d.Add)
102-
metadataNode.Add d
117+
let groups = List.groupBy thirdOf3 dependencyList
118+
match groups.Length, fst groups.Head with
119+
| (1, None) ->
120+
buildDependencyNodes(excludedDependencies, d.Add, dependencyList)
121+
| _ ->
122+
groups
123+
|> List.iter (fun a -> buildDependencyNodesByGroup excludedDependencies d.Add (snd a) (fst a).Value)
124+
metadataNode.Add d
103125

104126
let buildReferenceNode (fileName) =
105127
let dep = XElement(ns + "reference")
@@ -369,7 +391,7 @@ module NuspecExtensions =
369391
references.Groups |> Seq.collect (fun kvp ->
370392
kvp.Value.NugetPackages |> List.choose (fun pkg ->
371393
dependencies.TryGetPackage(kvp.Key,pkg.Name)
372-
|> Option.map (fun verreq -> pkg.Name,verreq.VersionRequirement)))
394+
|> Option.map (fun verreq -> pkg.Name,verreq.VersionRequirement, None)))
373395
|> List.ofSeq
374396
) |> Option.defaultValue []
375397
let projectInfo, optionalInfo = project.GetTemplateMetadata ()

src/Paket.Core/Packaging/PackageMetaData.fs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -107,11 +107,14 @@ let (|Valid|Invalid|) md =
107107
Symbols = s }
108108
| _ -> Invalid
109109

110-
let addDependency (templateFile : TemplateFile) (dependency : PackageName * VersionRequirement) =
110+
111+
112+
let addDependency (templateFile : TemplateFile) (dependency : PackageName * VersionRequirement * FrameworkIdentifier option) =
111113
match templateFile with
112114
| CompleteTemplate(core, opt) ->
115+
let packageName = dependency |> (fun (n,_,_) -> n)
113116
let newDeps =
114-
match opt.Dependencies |> List.tryFind (fun (n,_) -> n = fst dependency) with
117+
match opt.Dependencies |> List.tryFind (fun (n,_,_) -> n = packageName) with
115118
| None -> dependency :: opt.Dependencies
116119
| _ -> opt.Dependencies
117120
{ FileName = templateFile.FileName
@@ -253,7 +256,7 @@ let findDependencies (dependenciesFile : DependenciesFile) config platform (temp
253256
match core.Version with
254257
| Some v ->
255258
let versionConstraint = if lockDependencies || pinProjectReferences then Specific v else Minimum v
256-
PackageName core.Id, VersionRequirement(versionConstraint, getPreReleaseStatus v)
259+
PackageName core.Id, VersionRequirement(versionConstraint, getPreReleaseStatus v), None
257260
| None -> failwithf "There was no version given for %s." templateFile.FileName
258261
| IncompleteTemplate ->
259262
failwithf "You cannot create a dependency on a template file (%s) with incomplete metadata." templateFile.FileName)
@@ -357,7 +360,7 @@ let findDependencies (dependenciesFile : DependenciesFile) config platform (temp
357360
| None ->
358361
match version with
359362
| Some v ->
360-
np.Name,VersionRequirement.Parse (v.ToString())
363+
np.Name,VersionRequirement.Parse (v.ToString()) , None
361364
| None ->
362365
if minimumFromLockFile then
363366
let groupName =
@@ -368,7 +371,7 @@ let findDependencies (dependenciesFile : DependenciesFile) config platform (temp
368371
|> Option.map fst
369372

370373
match groupName with
371-
| None -> np.Name,specificVersionRequirement
374+
| None -> np.Name,specificVersionRequirement, None
372375
| Some groupName ->
373376
let group = lockFile.GetGroup groupName
374377

@@ -377,9 +380,9 @@ let findDependencies (dependenciesFile : DependenciesFile) config platform (temp
377380
| Some resolvedPackage -> VersionRequirement(GreaterThan resolvedPackage.Version, getPreReleaseStatus resolvedPackage.Version)
378381
| None -> specificVersionRequirement
379382

380-
np.Name,lockedVersion
383+
np.Name,lockedVersion, None
381384
else
382-
np.Name,specificVersionRequirement
385+
np.Name,specificVersionRequirement, None
383386
| Some groupName ->
384387
let dependencyVersionRequirement =
385388
if not lockDependencies then
@@ -437,7 +440,8 @@ let findDependencies (dependenciesFile : DependenciesFile) config platform (temp
437440
match dependencyVersionRequirement with
438441
| Some installed -> installed
439442
| None -> failwithf "No package with id '%O' installed in group %O." np.Name groupName
440-
np.Name, dep)
443+
444+
np.Name, dep, None)
441445

442446
deps
443447
|> List.fold addDependency withDepsAndIncluded

src/Paket.Core/PaketConfigFiles/TemplateFile.fs

Lines changed: 72 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ open System.IO
66
open System.Text.RegularExpressions
77
open Chessie.ErrorHandling
88
open Paket.Domain
9+
open Paket.Requirements
910

1011
module private TemplateParser =
1112
type private ParserState =
@@ -141,7 +142,7 @@ type OptionalPackagingInfo =
141142
RequireLicenseAcceptance : bool
142143
Tags : string list
143144
DevelopmentDependency : bool
144-
Dependencies : (PackageName * VersionRequirement) list
145+
Dependencies : (PackageName * VersionRequirement * FrameworkIdentifier option) list
145146
ExcludedDependencies : Set<PackageName>
146147
ExcludedGroups : Set<GroupName>
147148
References : string list
@@ -261,45 +262,80 @@ module internal TemplateFile =
261262
| Some m -> ok m
262263
| None -> failP file "No description line in paket.template file."
263264

265+
let private (|Framework|_|) (line:string) =
266+
match line.Trim() with
267+
| String.RemovePrefix "framework:" _ as trimmed -> Some (FrameworkDetection.Extract(trimmed.Replace("framework: ","")))
268+
| _ -> None
269+
270+
let private (|Empty|_|) (line:string) =
271+
match line.Trim() with
272+
| _ when String.IsNullOrWhiteSpace line -> Some (Empty line)
273+
| String.RemovePrefix "//" _ -> Some (Empty line)
274+
| String.RemovePrefix "#" _ -> Some (Empty line)
275+
| _ -> None
276+
type TargetFrameworkGroup =
277+
{
278+
Framework : FrameworkIdentifier option
279+
Dependencies : (PackageName * VersionRequirement * FrameworkIdentifier option) List}
280+
static member ForNone = { Framework = None ; Dependencies = [] }
281+
static member ForFramework framework = { Framework = framework ; Dependencies = [] }
282+
283+
let private getDependencyByLine (fileName, lockFile:LockFile,currentVersion:SemVerInfo option, specificVersions:Map<string, SemVerInfo>, line:string, framework:FrameworkIdentifier option)=
284+
let reg = Regex(@"(?<id>\S+)(?<version>.*)").Match line
285+
let name = PackageName reg.Groups.["id"].Value
286+
let versionRequirement =
287+
let versionString =
288+
let s = reg.Groups.["version"].Value.Trim()
289+
if s.Contains "CURRENTVERSION" then
290+
match specificVersions.TryFind (string name) with
291+
| Some v -> s.Replace("CURRENTVERSION", string v)
292+
| None ->
293+
match currentVersion with
294+
| Some v -> s.Replace("CURRENTVERSION", string v)
295+
| None -> failwithf "The template file %s contains the placeholder CURRENTVERSION, but no version was given." fileName
296+
297+
elif s.Contains "LOCKEDVERSION" then
298+
match lockFile.Groups.[Constants.MainDependencyGroup].Resolution |> Map.tryFind name with
299+
| Some p -> s.Replace("LOCKEDVERSION", string p.Version)
300+
| None ->
301+
let packages =
302+
lockFile.GetGroupedResolution()
303+
|> Seq.filter (fun kv -> snd kv.Key = name)
304+
|> Seq.toList
305+
306+
match packages with
307+
| [] -> failwithf "The template file %s contains the placeholder LOCKEDVERSION, but no version was given for package %O in paket.lock." fileName name
308+
| [kv] -> s.Replace("LOCKEDVERSION", string kv.Value.Version)
309+
| _ -> failwithf "The template file %s contains the placeholder LOCKEDVERSION, but more than one group contains package %O in paket.lock." fileName name
310+
311+
else s
312+
313+
DependenciesFileParser.parseVersionRequirement versionString
314+
name, versionRequirement, framework
315+
316+
let private getDependenciesByTargetFramework fileName lockFile currentVersion specificVersions (lineNo, state: TargetFrameworkGroup list) line=
317+
match state with
318+
| current::other ->
319+
let lineNo = lineNo + 1
320+
match line with
321+
| Framework framework ->
322+
let group = TargetFrameworkGroup.ForFramework framework::current::other
323+
lineNo, group
324+
| Empty _ -> lineNo, current::other
325+
| _ ->
326+
let dependency = getDependencyByLine(fileName, lockFile, currentVersion, specificVersions, line, current.Framework)
327+
lineNo ,{ current with Dependencies = current.Dependencies @ [dependency] }::other
328+
| [] -> failwithf "Error in paket.dependencies line %d" lineNo
329+
264330
let private getDependencies (fileName, lockFile:LockFile, info : Map<string, string>,currentVersion:SemVerInfo option, specificVersions:Map<string, SemVerInfo>) =
265331
match Map.tryFind "dependencies" info with
266332
| None -> []
267333
| Some d ->
268-
d.Split '\n'
269-
|> Array.map (fun d ->
270-
let reg = Regex(@"(?<id>\S+)(?<version>.*)").Match d
271-
let name = PackageName reg.Groups.["id"].Value
272-
let versionRequirement =
273-
let versionString =
274-
let s = reg.Groups.["version"].Value.Trim()
275-
if s.Contains "CURRENTVERSION" then
276-
match specificVersions.TryFind (string name) with
277-
| Some v -> s.Replace("CURRENTVERSION", string v)
278-
| None ->
279-
match currentVersion with
280-
| Some v -> s.Replace("CURRENTVERSION", string v)
281-
| None -> failwithf "The template file %s contains the placeholder CURRENTVERSION, but no version was given." fileName
282-
283-
elif s.Contains "LOCKEDVERSION" then
284-
match lockFile.Groups.[Constants.MainDependencyGroup].Resolution |> Map.tryFind name with
285-
| Some p -> s.Replace("LOCKEDVERSION", string p.Version)
286-
| None ->
287-
let packages =
288-
lockFile.GetGroupedResolution()
289-
|> Seq.filter (fun kv -> snd kv.Key = name)
290-
|> Seq.toList
291-
292-
match packages with
293-
| [] -> failwithf "The template file %s contains the placeholder LOCKEDVERSION, but no version was given for package %O in paket.lock." fileName name
294-
| [kv] -> s.Replace("LOCKEDVERSION", string kv.Value.Version)
295-
| _ -> failwithf "The template file %s contains the placeholder LOCKEDVERSION, but more than one group contains package %O in paket.lock." fileName name
296-
297-
else s
298-
299-
DependenciesFileParser.parseVersionRequirement versionString
300-
301-
name, versionRequirement)
302-
|> Array.toList
334+
d.Split '\n'
335+
|> Array.fold (getDependenciesByTargetFramework fileName lockFile currentVersion specificVersions) (0, [TargetFrameworkGroup.ForNone])
336+
|> snd
337+
|> List.rev
338+
|> List.collect (fun a -> a.Dependencies)
303339

304340

305341
let private getExcludedDependencies (info : Map<string, string>) =

tests/Paket.Tests/Packaging/NuspecWriterSpecs.fs

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ open FsUnit
77
open NUnit.Framework
88
open TestHelpers
99
open Paket.Domain
10+
open Paket.Requirements
1011

1112
[<Test>]
1213
let ``should serialize core info``() =
@@ -58,8 +59,89 @@ let ``should serialize dependencies``() =
5859
{ OptionalPackagingInfo.Epmty with
5960
Tags = [ "f#"; "rules" ]
6061
Dependencies =
61-
[ PackageName "Paket.Core", VersionRequirement.Parse "[3.1]"
62-
PackageName "xUnit", VersionRequirement.Parse "2.0" ] }
62+
[ PackageName "Paket.Core", VersionRequirement.Parse "[3.1]", None
63+
PackageName "xUnit", VersionRequirement.Parse "2.0", None ] }
64+
65+
let doc = NupkgWriter.nuspecDoc (core, optional)
66+
doc.ToString()
67+
|> normalizeLineEndings
68+
|> shouldEqual (normalizeLineEndings result)
69+
70+
71+
[<Test>]
72+
let ``#913 should serialize dependencies by group``() =
73+
let result = """<package xmlns="http://schemas.microsoft.com/packaging/2011/10/nuspec.xsd">
74+
<metadata>
75+
<id>Paket.Tests</id>
76+
<version>1.0.0.0</version>
77+
<authors>Two, Authors</authors>
78+
<description>A description</description>
79+
<tags>f# rules</tags>
80+
<dependencies>
81+
<group targetFramework="net35">
82+
<dependency id="Paket.Core" version="[3.1.0]" />
83+
<dependency id="xUnit" version="2.0.0" />
84+
</group>
85+
</dependencies>
86+
</metadata>
87+
</package>"""
88+
89+
let core : CompleteCoreInfo =
90+
{ Id = "Paket.Tests"
91+
Version = SemVer.Parse "1.0.0.0" |> Some
92+
Authors = [ "Two"; "Authors" ]
93+
Description = "A description"
94+
Symbols = false }
95+
96+
let optional =
97+
{ OptionalPackagingInfo.Epmty with
98+
Tags = [ "f#"; "rules" ]
99+
Dependencies =
100+
[ PackageName "Paket.Core", VersionRequirement.Parse "[3.1]", Some(FrameworkIdentifier.DotNetFramework(FrameworkVersion.V3_5))
101+
PackageName "xUnit", VersionRequirement.Parse "2.0", Some(FrameworkIdentifier.DotNetFramework(FrameworkVersion.V3_5)) ] }
102+
103+
let doc = NupkgWriter.nuspecDoc (core, optional)
104+
doc.ToString()
105+
|> normalizeLineEndings
106+
|> shouldEqual (normalizeLineEndings result)
107+
108+
[<Test>]
109+
let ``#913 should serialize dependencies by group with 2 group``() =
110+
let result = """<package xmlns="http://schemas.microsoft.com/packaging/2011/10/nuspec.xsd">
111+
<metadata>
112+
<id>Paket.Tests</id>
113+
<version>1.0.0.0</version>
114+
<authors>Two, Authors</authors>
115+
<description>A description</description>
116+
<tags>f# rules</tags>
117+
<dependencies>
118+
<group targetFramework="net35">
119+
<dependency id="Paket.Core" version="[3.1.0]" />
120+
<dependency id="xUnit" version="2.0.0" />
121+
</group>
122+
<group targetFramework="netstandard13">
123+
<dependency id="Paket.Core" version="[3.1.0]" />
124+
<dependency id="xUnit" version="2.0.0" />
125+
</group>
126+
</dependencies>
127+
</metadata>
128+
</package>"""
129+
130+
let core : CompleteCoreInfo =
131+
{ Id = "Paket.Tests"
132+
Version = SemVer.Parse "1.0.0.0" |> Some
133+
Authors = [ "Two"; "Authors" ]
134+
Description = "A description"
135+
Symbols = false }
136+
137+
let optional =
138+
{ OptionalPackagingInfo.Epmty with
139+
Tags = [ "f#"; "rules" ]
140+
Dependencies =
141+
[ PackageName "Paket.Core", VersionRequirement.Parse "[3.1]", Some(FrameworkIdentifier.DotNetFramework(FrameworkVersion.V3_5))
142+
PackageName "xUnit", VersionRequirement.Parse "2.0", Some(FrameworkIdentifier.DotNetFramework(FrameworkVersion.V3_5))
143+
PackageName "Paket.Core", VersionRequirement.Parse "[3.1]", Some(FrameworkIdentifier.DotNetStandard(DotNetStandardVersion.V1_3))
144+
PackageName "xUnit", VersionRequirement.Parse "2.0", Some(FrameworkIdentifier.DotNetStandard(DotNetStandardVersion.V1_3)) ] }
63145

64146
let doc = NupkgWriter.nuspecDoc (core, optional)
65147
doc.ToString()

0 commit comments

Comments
 (0)