1
+ open System
2
+ open System.Diagnostics
3
+ open System.IO
4
+ open System.Text .RegularExpressions
5
+
6
+ open Fake.Core
7
+ open Fake.Core .TargetOperators
8
+ open Fake.DotNet
9
+ open Fake.IO
10
+ open Fake.IO .Globbing .Operators
11
+ open Fake.IO .FileSystemOperators
12
+ open Fake.Tools
13
+
14
+ open ExtractDocs
15
+
16
+ let target = Target.create
17
+ let description = Target.description
18
+
19
+ module FileReaderWriter =
20
+ let Read file = File.ReadAllText( file)
21
+ let Write file text = File.WriteAllText( file, text)
22
+ let TransformFile file target ( f : string -> string ) =
23
+ Read file
24
+ |> f
25
+ |> Write target
26
+
27
+ module ExamplesToCode =
28
+ open FileReaderWriter
29
+
30
+ let ConvertFile ( file : string ) targetDir =
31
+ let fileName = Path.GetFileNameWithoutExtension( file)
32
+ let target = targetDir @@ fileName + " .cs"
33
+ Trace.log <| sprintf " Converting %s to %s " file target
34
+ TransformFile file target ( ExtractDocs.strToFixture fileName)
35
+
36
+ let Convert paths targetDir =
37
+ let paths = paths |> Seq.toList
38
+ for p in paths do
39
+ Trace.trace <| sprintf " Convert from %s to %s " p targetDir
40
+ let files = !! " *.markdown" ++ " *.html" ++ " *.md" |> GlobbingPattern.setBaseDir p
41
+ for file in files do
42
+ ConvertFile file targetDir
43
+
44
+ type BuildVersion = { assembly: string ; file: string ; info: string ; package: string }
45
+ let getVersion () =
46
+ // The --first-parent flag is needed to make our walk linear from current commit and top.
47
+ // This way also merge commit is counted as "1".
48
+ let desc = Git.CommandHelper.runSimpleGitCommand " " " describe --tags --long --abbrev=40 --first-parent --match=v*"
49
+ let result = Regex.Match( desc,
50
+ @" ^v(?<maj>\d+)\.(?<min>\d+)\.(?<rev>\d+)(?<pre>-\w+\d*)?-(?<num>\d+)-g(?<sha>[a-z0-9]+)$" ,
51
+ RegexOptions.IgnoreCase)
52
+ .Groups
53
+ let getMatch ( name : string ) = result.[ name]. Value
54
+
55
+ let ( major , minor , revision , preReleaseSuffix , commitsNum , commitSha ) =
56
+ ( getMatch " maj" |> int, getMatch " min" |> int, getMatch " rev" |> int, getMatch " pre" , getMatch " num" |> int, getMatch " sha" )
57
+
58
+ // Assembly version should contain major and minor only, as no breaking changes are expected in bug fix releases.
59
+ let assemblyVersion = sprintf " %d .%d .0.0" major minor
60
+ let fileVersion = sprintf " %d .%d .%d .%d " major minor revision commitsNum
61
+
62
+ // If number of commits since last tag is greater than zero, we append another identifier with number of commits.
63
+ // The produced version is larger than the last tag version.
64
+ // If we are on a tag, we use version without modification.
65
+ // Examples of output: 3.50.2.1, 3.50.2.215, 3.50.1-rc1.3, 3.50.1-rc3.35
66
+ let packageVersion = match commitsNum with
67
+ | 0 -> sprintf " %d .%d .%d%s " major minor revision preReleaseSuffix
68
+ | _ -> sprintf " %d .%d .%d%s .%d " major minor revision preReleaseSuffix commitsNum
69
+
70
+ let infoVersion = match commitsNum with
71
+ | 0 -> packageVersion
72
+ | _ -> sprintf " %s -%s " packageVersion commitSha
73
+
74
+ { assembly = assemblyVersion; file = fileVersion; info = infoVersion; package = packageVersion }
75
+
76
+ let root = __ SOURCE_ DIRECTORY__ </> " .." |> Path.getFullName
77
+
78
+ let configuration = Environment.environVarOrDefault " configuration" " Debug"
79
+ let version = getVersion ()
80
+
81
+ let additionalArgs = [
82
+ " AssemblyVersion" , version.assembly
83
+ " FileVersion" , version.file
84
+ " InformationalVersion" , version.info
85
+ " PackageVersion" , version.package
86
+ ]
87
+
88
+ let output = root </> " bin" </> configuration
89
+ let solution = ( root </> " NSubstitute.sln" )
90
+
91
+ let initTargets () =
92
+ Target.create " Default" ignore
93
+ Target.create " All" ignore
94
+
95
+ Target.description( " Clean compilation artifacts and remove output bin directory" )
96
+ Target.create " Clean" ( fun _ ->
97
+ DotNet.exec ( fun p -> { p with WorkingDirectory = root }) " clean"
98
+ ( sprintf " --configuration %s --verbosity minimal" configuration)
99
+ |> ignore
100
+ Shell.cleanDirs [ output ]
101
+ )
102
+
103
+ Target.description( " Restore dependencies" )
104
+ Target.create " Restore" ( fun _ ->
105
+ DotNet.restore ( fun p -> p) solution
106
+ )
107
+
108
+ Target.description( " Compile all projects" )
109
+ Target.create " Build" ( fun _ ->
110
+ DotNet.build ( fun p ->
111
+ { p with Configuration = DotNet.BuildConfiguration.fromString configuration
112
+ MSBuildParams = { p.MSBuildParams with Properties = additionalArgs }
113
+ }) solution
114
+ )
115
+
116
+ Target.description( " Run tests" )
117
+ Target.create " Test" ( fun _ ->
118
+ DotNet.test ( fun p ->
119
+ { p with Configuration = DotNet.BuildConfiguration.fromString configuration
120
+ MSBuildParams = { p.MSBuildParams with Properties = additionalArgs }
121
+ }) ( root </> " tests/NSubstitute.Acceptance.Specs/NSubstitute.Acceptance.Specs.csproj" )
122
+ )
123
+
124
+ Target.description( " Generate Nuget package" )
125
+ Target.create " Package" ( fun _ ->
126
+ DotNet.pack ( fun p ->
127
+ { p with Configuration = DotNet.BuildConfiguration.fromString configuration
128
+ MSBuildParams = { p.MSBuildParams with Properties = additionalArgs }
129
+ }) ( root </> " src/NSubstitute/NSubstitute.csproj" )
130
+ )
131
+
132
+ Target.description( " Run all benchmarks. Must be run with configuration=Release." )
133
+ Target.create " Benchmarks" ( fun _ ->
134
+ if configuration <> " Release" then
135
+ failwith " Benchmarks can only be run in Release mode. Please re-run the build in Release configuration."
136
+
137
+ let benchmarkCsproj = root </> " tests/NSubstitute.Benchmarks/NSubstitute.Benchmarks.csproj" |> Path.getFullName
138
+ let benchmarkToRun = Environment.environVarOrDefault " benchmark" " *" // Defaults to "*" (all)
139
+ [ " netcoreapp2.1" ]
140
+ |> List.iter ( fun framework ->
141
+ Trace.traceImportant ( " Benchmarking " + framework)
142
+ let work = output </> " benchmark-" + framework
143
+ Directory.ensure work
144
+ DotNet.exec ( fun p -> { p with WorkingDirectory = work }) " run"
145
+ ( " --framework " + framework + " --project " + benchmarkCsproj + " -- " + benchmarkToRun)
146
+ |> ignore
147
+ )
148
+ )
149
+
150
+ Target.description( " Extract, build and test code from documentation." )
151
+ Target.create " TestCodeFromDocs" <| fun _ ->
152
+ let outputCodePath = output </> " CodeFromDocs"
153
+ Directory.create outputCodePath
154
+ // generate samples from docs
155
+ ExamplesToCode.Convert [ root </> " docs/" ; root </> " docs/help/_posts/" ; root ] outputCodePath
156
+ // compile code samples
157
+ let csproj = """
158
+ <Project Sdk="Microsoft.NET.Sdk">
159
+ <PropertyGroup>
160
+ <TargetFrameworks>net6.0;net462</TargetFrameworks>
161
+ <LangVersion>latest</LangVersion>
162
+ </PropertyGroup>
163
+ <ItemGroup>
164
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
165
+ <PackageReference Include="NUnit" Version="3.13.3" />
166
+ <PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
167
+ </ItemGroup>
168
+ <ItemGroup>
169
+ <ProjectReference Include="..\..\..\src\NSubstitute\NSubstitute.csproj" />
170
+ </ItemGroup>
171
+ </Project>
172
+ """
173
+ let projPath = outputCodePath </> " Docs.csproj"
174
+ FileReaderWriter.Write projPath csproj
175
+ DotNet.restore ( fun p -> p) projPath
176
+ DotNet.build ( fun p -> p) projPath
177
+ DotNet.test ( fun p -> p) projPath
178
+
179
+ let tryFindFileOnPath ( file : string ) : string option =
180
+ Environment.GetEnvironmentVariable( " PATH" ) .Split([| Path.PathSeparator |])
181
+ |> Seq.append [ " ." ]
182
+ |> fun path -> ProcessUtils.tryFindFile path file
183
+
184
+ Target.description( " Build documentation website. Requires Ruby, bundler and jekyll." )
185
+ Target.create " Documentation" <| fun _ ->
186
+ Trace.log " Building site..."
187
+ let exe = [ " bundle.bat" ; " bundle" ]
188
+ |> Seq.map tryFindFileOnPath
189
+ |> Seq.collect ( Option.toList)
190
+ |> Seq.tryFind ( fun _ -> true )
191
+ |> function | Some x -> Trace.log ( " using " + x); x
192
+ | None -> Trace.log ( " count not find exe" ); " bundle"
193
+
194
+ let workingDir = root </> " docs/"
195
+ let docOutputRelativeToWorkingDir = " .." </> output </> " nsubstitute.github.com"
196
+
197
+ // TODO migrate the following to FAKE API: CreateProcess.ofStartInfo(p)
198
+ // https://fake.build/apidocs/v5/fake-core-createprocess.html
199
+ // that doesn't work for some reason
200
+ let p = ProcessStartInfo(
201
+ UseShellExecute = false ,
202
+ CreateNoWindow = true ,
203
+ FileName = exe,
204
+ WorkingDirectory = workingDir,
205
+ Arguments = " exec jekyll build -d \" " + docOutputRelativeToWorkingDir + " \" " )
206
+ let proc = Process.Start( p)
207
+ proc.WaitForExit()
208
+ let result = proc.ExitCode
209
+ if result = 0 then
210
+ " Site built in " + docOutputRelativeToWorkingDir |> Trace.log
211
+ else
212
+ " failed to build site" |> failwith
213
+
214
+ Target.description( " List targets, similar to `rake -T`. For more details, run `--listTargets` instead." )
215
+ Target.create " -T" <| fun _ ->
216
+ printfn " Optional config options:"
217
+ printfn " configuration=Debug|Release"
218
+ printfn " benchmark=*|<benchmark name> (only for Benchmarks target in Release mode)"
219
+ printfn " "
220
+ Target.listAvailable()
221
+
222
+ " Clean" ?=> " Build" |> ignore
223
+ " Clean" ?=> " Test" |> ignore
224
+ " Clean" ?=> " Restore" |> ignore
225
+ " Clean" ?=> " Documentation" |> ignore
226
+ " Clean" ?=> " TestCodeFromDocs" |> ignore
227
+ " Clean" ?=> " Package" |> ignore
228
+ " Clean" ?=> " Default" |> ignore
229
+
230
+ " Build" <== [ " Restore" ]
231
+ " Test" <== [ " Build" ]
232
+ " Documentation" <== [ " TestCodeFromDocs" ]
233
+ " Benchmarks" <== [ " Build" ]
234
+ // For packaging, use a clean build and make sure all tests (inc. docs) pass.
235
+ " Package" <== [ " Clean" ; " Build" ; " Test" ; " TestCodeFromDocs" ]
236
+
237
+ " Default" <== [ " Restore" ; " Build" ; " Test" ]
238
+ " All" <== [ " Clean" ; " Default" ; " Documentation" ; " Package" ]
239
+
240
+ [<EntryPoint>]
241
+ let main argv =
242
+ argv
243
+ |> Array.toList
244
+ |> Context.FakeExecutionContext.Create false " build.fsx"
245
+ |> Context.RuntimeContext.Fake
246
+ |> Context.setExecutionContext
247
+ initTargets()
248
+ Target.runOrDefaultWithArguments " Default"
249
+ 0
0 commit comments