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