Skip to content

Commit 9a92d6e

Browse files
committed
1 parent ce6793b commit 9a92d6e

File tree

9 files changed

+342
-336
lines changed

9 files changed

+342
-336
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ bld/
2626
[Bb]in/
2727
[Oo]bj/
2828
[Ll]og/
29+
buildOutput
2930

3031
# VS Code directory
3132
.vscode/

build/ExtractDocs.fs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
module ExtractDocs
2+
3+
open System
4+
open System.IO
5+
open System.Text.RegularExpressions
6+
7+
let LiquidTagRegex = @"```(?<tag>\w+)" + // Tag start with argument. e.g. "```csharp"
8+
@"(?<contents>(?s:.*?))" + // Tag contents
9+
@"```?" // Tag end
10+
let TypeOrTestDeclRegex = @"(\[Test\]|(public |private |protected )?(class |interface )\w+\s*\{)"
11+
12+
type LiquidTag = LiquidTag of name : string * contents : string
13+
type CodeBlock = Declaration of string | Snippet of string
14+
15+
let tags s : LiquidTag seq =
16+
Regex.Matches(s, LiquidTagRegex)
17+
|> Seq.cast<Match>
18+
|> Seq.map (fun m -> LiquidTag (m.Groups.[1].Value, m.Groups.[2].Value))
19+
20+
let toCodeBlock (LiquidTag (name, c)) =
21+
let isTypeOrTestDecl s = Regex.IsMatch(s, TypeOrTestDeclRegex)
22+
match name with
23+
| "csharp" -> if isTypeOrTestDecl c then Some (Declaration c) else Some (Snippet c)
24+
| "requiredcode" -> Some (Declaration c)
25+
| _ -> None
26+
27+
let toCodeBlocks : string -> CodeBlock seq =
28+
Seq.choose toCodeBlock << tags
29+
30+
let toFixture name content =
31+
let escapeName (s:string) = Regex.Replace(s, "\W", "_")
32+
sprintf """
33+
using System;
34+
using NUnit.Framework;
35+
using System.Linq;
36+
using System.Collections.Generic;
37+
using System.ComponentModel;
38+
using NSubstitute.Extensions;
39+
using NSubstitute.Compatibility;
40+
41+
namespace NSubstitute.Samples {
42+
public class Tests_%s {
43+
%s
44+
}
45+
}""" (escapeName name) content
46+
47+
let toTest = sprintf """[Test] public void Test_%d() {
48+
%s
49+
}"""
50+
51+
let appendCodeBlock (code, testNum) (cb:CodeBlock) =
52+
match cb with
53+
| Declaration d -> (code + Environment.NewLine + d, testNum)
54+
| Snippet s ->
55+
let test = toTest testNum s
56+
(code + Environment.NewLine + test, testNum + 1)
57+
58+
let strToFixture fixtureName s : string =
59+
s
60+
|> toCodeBlocks
61+
|> Seq.fold appendCodeBlock ("", 0)
62+
|> fst
63+
|> toFixture fixtureName

build/ExtractDocs.fsx

Lines changed: 0 additions & 62 deletions
This file was deleted.

build/build.cmd

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,4 @@ cls
33
set encoding=utf-8
44

55
SET SCRIPT_DIR=%~dp0
6-
SET TOOL_PATH=%SCRIPT_DIR%\.fake
7-
8-
IF NOT EXIST "%TOOL_PATH%\fake.exe" (
9-
dotnet tool install fake-cli --tool-path %TOOL_PATH%
10-
)
11-
12-
"%TOOL_PATH%\fake.exe" --silent run %SCRIPT_DIR%\build.fsx %*
6+
dotnet run --project "%SCRIPT_DIR%/build.fsproj" -- %*

build/build.fs

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
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

Comments
 (0)