/
Paket.fs
396 lines (316 loc) · 15 KB
/
Paket.fs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
namespace Fake.DotNet
/// <summary>
/// Contains helper functions and task which allow to inspect, create and publish
/// <a href="https://www.nuget.org/">NuGet</a> packages with
/// <a href="http://fsprojects.github.io/Paket/index.html">Paket</a>.
/// </summary>
[<RequireQualifiedAccess>]
module Paket =
open System
open System.IO
open System.Xml.Linq
open System.Text.RegularExpressions
open Fake.Core
open Fake.IO
open Fake.IO.FileSystemOperators
open Fake.IO.Globbing.Operators
/// <summary>
/// Paket pack parameter type
/// </summary>
type PaketPackParams =
{
/// paket tool path
ToolPath: string
/// The kind of application ToolPath references
ToolType: ToolType
/// The timeout for the toll commands execution
TimeOut: TimeSpan
/// The packaging version
Version: string
/// version number to use for package ID; may be repeated
SpecificVersions: (string * string) list
/// use version constraints from paket.lock instead of paket.dependencies
LockDependencies: bool
/// the release notes to include in output NuGet package
ReleaseNotes: string
/// build configuration that should be packaged (default: Release)
BuildConfig: string
/// build platform that should be packaged (default: check all known platform targets)
BuildPlatform: string
/// pack a single paket.template file
TemplateFile: string
/// exclude paket.template file by package ID; may be repeated
ExcludedTemplates: string list
/// the directory to execute command in
WorkingDir: string
/// output directory for <c>.nupkg</c> files
OutputPath: string
/// homepage URL for the package
ProjectUrl: string
/// create symbol and source packages in addition to library and content packages
Symbols: bool
/// include symbols and source from referenced projects
IncludeReferencedProjects: bool
/// use version constraints from paket.lock instead of paket.dependencies and add them as a minimum
/// version; --lock-dependencies overrides this option
MinimumFromLockFile: bool
/// pin dependencies generated from project references to exact versions (<c>=</c>)
/// instead of using minimum versions (<c>>=</c>); with <c>--lock-dependencies</c> project
/// references will be pinned even if this option is not specified
PinProjectReferences: bool
}
let internal findPaketExecutable baseDir =
ProcessUtils.findLocalTool "PAKET" "paket" [ Path.Combine(baseDir, ".paket") ]
/// Paket pack default parameters
let PaketPackDefaults () : PaketPackParams =
{ ToolPath = findPaketExecutable ""
ToolType = ToolType.Create()
TimeOut = TimeSpan.FromMinutes 5.
Version = null
SpecificVersions = []
LockDependencies = false
ReleaseNotes = null
BuildConfig = null
BuildPlatform = null
TemplateFile = null
ProjectUrl = null
ExcludedTemplates = []
WorkingDir = "."
OutputPath = "./temp"
Symbols = false
IncludeReferencedProjects = false
MinimumFromLockFile = false
PinProjectReferences = false }
/// <summary>
/// Paket push parameter type
/// </summary>
type PaketPushParams =
{
/// paket tool path
ToolPath: string
/// The kind of application ToolPath references
ToolType: ToolType
/// The timeout for the toll commands execution
TimeOut: TimeSpan
/// URL of the NuGet feed
PublishUrl: string
/// API endpoint to push to (default: /api/v2/package)
EndPoint: string
/// the directory to execute command in
WorkingDir: string
/// the number of parallel processes to use
DegreeOfParallelism: int
/// API key for the URL (default: value of the NUGET_KEY environment variable)
ApiKey: string
/// Ignore any HTTP409 (Conflict) errors and treat as success (default: false)
IgnoreConflicts: bool
}
/// Paket push default parameters
let PaketPushDefaults () : PaketPushParams =
{ ToolPath = findPaketExecutable ""
ToolType = ToolType.Create()
TimeOut = TimeSpan.MaxValue
PublishUrl = null
EndPoint = null
WorkingDir = "./temp"
DegreeOfParallelism = 8
ApiKey = null
IgnoreConflicts = false }
/// <summary>
/// Paket restore packages type
/// </summary>
type PaketRestoreParams =
{
/// paket tool path
ToolPath: string
/// The kind of application ToolPath references
ToolType: ToolType
/// The timeout for the toll commands execution
TimeOut: TimeSpan
/// the directory to execute command in
WorkingDir: string
/// force download and re-installation of all dependencies
ForceDownloadOfPackages: bool
/// only restore packages that are referenced by paket.references files
OnlyReferencedFiles: bool
/// restore dependencies of a single group
Group: string
/// restore packages from a paket.references file; may be repeated
ReferenceFiles: string list
}
/// Paket restore default parameters
let PaketRestoreDefaults () : PaketRestoreParams =
{ ToolPath = findPaketExecutable ""
ToolType = ToolType.Create()
TimeOut = TimeSpan.MaxValue
WorkingDir = "."
ForceDownloadOfPackages = false
OnlyReferencedFiles = false
ReferenceFiles = []
Group = "" }
let private startPaket (toolType: ToolType) toolPath workDir timeout args =
CreateProcess.fromCommand (RawCommand(toolPath, args))
|> CreateProcess.withToolType toolType
|> CreateProcess.withWorkingDirectory workDir
|> CreateProcess.withTimeout timeout
let private start (c: CreateProcess<ProcessResult<_>>) = c |> Proc.run |> (fun r -> r.ExitCode)
type internal StartType =
| PushFile of parameters: PaketPushParams * files: string
| Pack of parameters: PaketPackParams
| Restore of parameters: PaketRestoreParams
let internal createProcess (runType: StartType) =
match runType with
| PushFile (parameters, file) ->
Arguments.OfArgs [ "push" ]
|> Arguments.appendNotEmpty "--url" parameters.PublishUrl
|> Arguments.appendNotEmpty "--endpoint" parameters.EndPoint
|> Arguments.appendNotEmpty "--api-key" parameters.ApiKey
|> Arguments.appendIf parameters.IgnoreConflicts "--ignoreConflicts"
|> Arguments.append [ file ]
|> startPaket parameters.ToolType parameters.ToolPath parameters.WorkingDir parameters.TimeOut
| Pack parameters ->
let xmlEncode (notEncodedText: string) =
if String.IsNullOrWhiteSpace notEncodedText then
""
else
XText(notEncodedText).ToString().Replace("ß", "ß")
Arguments.OfArgs [ "pack" ]
|> Arguments.appendNotEmpty "--version" parameters.Version
|> Arguments.appendNotEmpty "--build-config" parameters.BuildConfig
|> Arguments.appendNotEmpty "--build-platform" parameters.BuildPlatform
|> Arguments.appendNotEmpty "--template" parameters.TemplateFile
|> Arguments.appendNotEmpty "--release-notes" (xmlEncode parameters.ReleaseNotes)
|> Arguments.appendNotEmpty "--project-url" parameters.ProjectUrl
|> Arguments.appendIf parameters.LockDependencies "--lock-dependencies"
|> Arguments.appendIf parameters.MinimumFromLockFile "--minimum-from-lock-file"
|> Arguments.appendIf parameters.PinProjectReferences "--pin-project-references"
|> Arguments.appendIf parameters.Symbols "--symbols"
|> Arguments.appendIf parameters.IncludeReferencedProjects "--include-referenced-projects"
|> List.foldBack (fun t -> Arguments.append [ "--exclude"; t ]) parameters.ExcludedTemplates
|> List.foldBack
(fun (id, v) -> Arguments.append [ "--specific-version"; id; v ])
parameters.SpecificVersions
|> Arguments.append [ parameters.OutputPath ]
|> startPaket parameters.ToolType parameters.ToolPath parameters.WorkingDir parameters.TimeOut
| Restore parameters ->
Arguments.OfArgs [ "restore" ]
|> Arguments.appendNotEmpty "--group" parameters.Group
|> Arguments.appendIf parameters.ForceDownloadOfPackages "--force"
|> Arguments.appendIf parameters.OnlyReferencedFiles "--only-referenced"
|> List.foldBack (fun ref -> Arguments.append [ "--references-file"; ref ]) parameters.ReferenceFiles
|> startPaket parameters.ToolType parameters.ToolPath parameters.WorkingDir parameters.TimeOut
/// <summary>
/// Creates a new NuGet package by using Paket pack on all paket.template files in the working directory.
/// </summary>
///
/// <param name="setParams">Function used to manipulate the default parameters.</param>
let pack setParams =
let parameters: PaketPackParams = PaketPackDefaults() |> setParams
use __ = Trace.traceTask "PaketPack" parameters.WorkingDir
let packResult = createProcess (Pack parameters) |> start
if packResult <> 0 then
failwithf "Error during packing %s." parameters.WorkingDir
__.MarkSuccess()
/// <summary>
/// Pushes the given NuGet packages to the server by using Paket push.
/// </summary>
///
/// <param name="setParams">Function used to manipulate the default parameters.</param>
/// <param name="files">The files to be pushed to the server.</param>
let pushFiles setParams files =
let parameters: PaketPushParams = PaketPushDefaults() |> setParams
TraceSecrets.register "<PaketApiKey>" parameters.ApiKey
match Environment.environVarOrNone "nugetkey" with
| Some k -> TraceSecrets.register "<PaketApiKey>" k
| None -> ()
match Environment.environVarOrNone "nuget-key" with
| Some k -> TraceSecrets.register "<PaketApiKey>" k
| None -> ()
let packages = Seq.toList files
use __ = Trace.traceTask "PaketPush" (String.separated ", " packages)
if parameters.DegreeOfParallelism > 0 then
/// Returns a sequence that yields chunks of length n.
/// Each chunk is returned as a list.
let split length (xs: seq<'T>) =
let rec loop xs =
[ yield Seq.truncate length xs |> Seq.toList
match Seq.length xs <= length with
| false -> yield! loop (Seq.skip length xs)
| true -> () ]
loop xs
for chunk in split parameters.DegreeOfParallelism packages do
let tasks =
chunk
|> Seq.toArray
|> Array.map (fun package ->
async {
let pushResult = createProcess (PushFile(parameters, package)) |> start
if pushResult <> 0 then
failwithf "Error during pushing %s." package
})
Async.Parallel tasks |> Async.RunSynchronously |> ignore
else
for package in packages do
let pushResult = createProcess (PushFile(parameters, package)) |> start
if pushResult <> 0 then
failwithf "Error during pushing %s." package
__.MarkSuccess()
/// <summary>
/// Pushes all NuGet packages in the working dir to the server by using Paket push.
/// </summary>
///
/// <param name="setParams">Function used to manipulate the default parameters.</param>
let push setParams =
let parameters: PaketPushParams = PaketPushDefaults() |> setParams
!!(parameters.WorkingDir @@ "/**/*.nupkg") |> pushFiles (fun _ -> parameters)
/// <summary>
/// Returns the dependencies from specified paket.references file
/// </summary>
///
/// <param name="referencesFile">Paket reference file to use</param>
/// - `referencesFile` -
let getDependenciesForReferencesFile (referencesFile: string) =
let getReferenceFilePackages =
let isSingleFile (line: string) = line.StartsWith "File:"
let isGroupLine (line: string) = line.StartsWith "group "
let notEmpty (line: string) = not <| String.IsNullOrWhiteSpace line
let parsePackageName (line: string) =
let parts = line.Split(' ')
parts[0]
File.ReadAllLines
>> Array.filter notEmpty
>> Array.map (fun s -> s.Trim())
>> Array.filter (isSingleFile >> not)
>> Array.filter (isGroupLine >> not)
>> Array.map parsePackageName
let getLockFilePackages =
let getPaketLockFile referencesFile =
let rec find dir =
let fi = FileInfo(dir </> "paket.lock")
if fi.Exists then
fi.FullName
else
find fi.Directory.Parent.FullName
find <| FileInfo(referencesFile).Directory.FullName
let breakInParts (line: string) =
match Regex.Match(line, "^[ ]{4}([^ ].+) \((.+)\)") with
| m when m.Success && m.Groups.Count = 3 -> Some(m.Groups[1].Value, m.Groups[2].Value)
| _ -> None
getPaketLockFile >> File.ReadAllLines >> Array.choose breakInParts
let refLines = getReferenceFilePackages referencesFile
getLockFilePackages referencesFile
|> Array.filter (fun (n, _) ->
refLines
|> Array.exists (fun pn -> pn.Equals(n, StringComparison.OrdinalIgnoreCase)))
/// <summary>
/// Restores all packages referenced in either a paket.dependencies or a paket.references file using Paket
/// </summary>
///
/// <param name="setParams">Function used to manipulate the default parameters.</param>
let restore setParams =
let parameters: PaketRestoreParams = PaketRestoreDefaults() |> setParams
use __ = Trace.traceTask "PaketRestore" parameters.WorkingDir
let restoreResult = createProcess (Restore parameters) |> start
if restoreResult <> 0 then
failwithf "Error during restore %s." parameters.WorkingDir
__.MarkSuccess()