Last active
November 28, 2016 15:27
-
-
Save mavnn/4944580 to your computer and use it in GitHub Desktop.
Literate build.fsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(** | |
## References | |
As they aren't part of a project, fsx files need | |
to reference all of their dependencies within the | |
file. | |
You'll always want to reference FakeLib. VersionUpdater | |
is an inhouse tool we use for handling feature branch | |
support within TeamCity. | |
*) | |
#r @"tools\FAKE\tools\FakeLib.dll" | |
#r @"tools\15below.VersionUpdater.dll" | |
#r "System.Xml.Linq" | |
open Fake | |
open System | |
open System.IO | |
open System.Text.RegularExpressions | |
open System.Xml.Linq | |
open FifteenBelow.VersionUpdater | |
(*** | |
## Set up | |
A set of environment variables we'd expect to be supplied | |
by the build server if we're on TeamCity. If not, default | |
them. | |
*) | |
let configuration = "Release" | |
let core_version_number = environVarOrDefault "core_version_number" "0.0.0" | |
let build_number = environVarOrDefault "build_number" "0" | |
let teamcity_build_branch = environVarOrDefault "teamcity_build_branch" (sprintf "%s/release" core_version_number) | |
let vcsroot_branch = environVarOrDefault "vcsroot_branch" "development" | |
let nugetPath = currentDirectory @@ "tools" @@ "nuget" @@ "NuGet.exe" | |
(*** | |
A helper method which sets up the build parameters we use on most of our projects. | |
The only real thing of note here is the `FscTools` property that will allow us | |
to use the version of the F# compiler in the repository. | |
You might want to change any or all of the rest depending on taste: we've | |
gone for very minimal output during the build, but writing both errors | |
and warnings to file for future reference if needed. | |
As you can see, things like the Verbosity of the build are strongly typed | |
in FAKE, removing a lot of the need to check the underlying tools manual | |
every two lines of code. | |
FAKE in general makes heavy use of records to set parameters for tools. | |
This allows for a default set of parameters to be provided, and then you | |
can override only the values you need to change with the `{ record with ... }` | |
syntax. | |
*) | |
let buildSolution sln targets = | |
let setParams defaults = | |
{ defaults with | |
Verbosity = Some(Quiet) | |
Targets = [targets] | |
Properties = [ | |
"DeployTarget","Package" | |
"DeployOnBuild","True" | |
"CreatePackageOnPublish","True" | |
"Optimize", "True" | |
"DebugSymbols", "True" | |
"Platform", "Any CPU" | |
"Configuration",configuration | |
"FscTools", currentDirectory @@ "tools" @@ "FSharp\\"] | |
FileLoggers = Some | |
[{ Number = 8 | |
Filename = Some "errors.log" | |
Verbosity = Some Normal | |
Parameters = Some [ErrorsOnly] | |
}; | |
{ | |
Number = 9 | |
Filename = Some "warnings.log" | |
Verbosity = Some Normal | |
Parameters = Some [WarningsOnly;Append] | |
}] | |
} | |
build setParams sln | |
(*** | |
## Targets | |
Our first `Target`! Targets in FAKE are like build targets in most | |
build systems. They allow us to group functionality into steps that | |
we can then define dependencies between. | |
In FAKE we create the targets first and then set up the dependency | |
tree at the end. | |
This first target uses our inhouse VersionUpdater to update any | |
AssemblyInfo files with the current version from TeamCity | |
(including semantic versions for feature branches). If this build | |
was creating nuget packages, we'd also update the nuspec files | |
here with relevant version information. | |
It then reports back to TeamCity what it has set these values to. | |
*) | |
Target "UpdateVersions" (fun _ -> | |
let assemblyInfos = !! (sprintf "%s/**/AssemblyInfo.*" currentDirectory) | |
|> Seq.map (fun name -> new FileInfo(name)) | |
let returns = Update.Do(teamcity_build_branch, vcsroot_branch, core_version_number, build_number, true, assemblyInfos) | |
setEnvironVar "AssemblyVersion" returns.AssemblyVersion | |
setEnvironVar "NugetVersion" returns.NugetVersion | |
setEnvironVar "SafeBranchName" returns.SafeBranchName | |
SetTeamCityParameter "safe_branch" returns.SafeBranchName | |
SetTeamCityParameter "nuget_version" returns.NugetVersion | |
) | |
(** | |
By default, Visual Studio 2012 creates project files that try and use | |
the version of Microsoft.FSharp.targets that was installed with it. Makes | |
sense, and we don't want to change them as this breaks tools like NCrunch. | |
Unfortunately, this version of the targets does not support the `FscTools` | |
property correctly. So when we're building with FAKE we first mangle the | |
fsproj files to use the .targets file from within the repository. | |
At the end of the Target we use the "ActivateFinalTarget" function to | |
make sure that these fsproj files will be restored, even if another later | |
build step fails. | |
*) | |
Target "UpdateFSharpTargets" (fun _ -> | |
let targetFullPath = FileInfo(currentDirectory @@ "tools" @@ "FSharp" @@ "Microsoft.FSharp.targets").FullName | |
trace ("FullPath: " + targetFullPath) | |
let alterProj (projXml : XDocument) = | |
let name = XNamespace.Get "http://schemas.microsoft.com/developer/msbuild/2003" | |
projXml.Element(name + "Project").Elements(name + "Import") | |
|> Seq.toList | |
|> List.iter (fun elem -> elem.Remove()) | |
let importOverride = | |
new XElement(name + "Import") | |
importOverride.Add(XAttribute(XName.Get "Project", targetFullPath)) | |
projXml.Element(name + "Project").Add(importOverride) | |
projXml | |
!! (sprintf "%s/**/*.fsproj" currentDirectory) | |
|> Seq.map (fun file -> FileInfo(file).CopyTo(file + ".orig", true) |> ignore; file) | |
|> Seq.map (fun file -> XDocument.Load(file), file) | |
|> Seq.map (fun (xDoc, file) -> alterProj xDoc, file) | |
|> Seq.iter (fun (xDoc, file) -> xDoc.Save(file)) | |
ActivateFinalTarget "RestoreFSharpTargets" | |
) | |
(** | |
This is the implementation of the final target mentioned above. Final targets always get | |
run, even in the event of a build failure. Normal targets are aborted if a previous target | |
has thrown an exception. | |
*) | |
FinalTarget "RestoreFSharpTargets" (fun _ -> | |
!! (sprintf "%s/**/*.fsproj.orig" currentDirectory) | |
|> Seq.iter (fun file -> FileInfo(file).CopyTo(file.[0..(String.length file - 5)], true) |> ignore) | |
) | |
(** | |
This target simply runs nuget.exe to restore all nuget references within the repository. | |
There is now a built in function in FAKE called RestorePackages that does the same thing, | |
but we'd written this already! | |
*) | |
Target "InstallPackages" (fun _ -> | |
let updatePackages packagesConfig = | |
let timeOut = TimeSpan.FromMinutes 5. | |
let args = sprintf @"install ""%s"" -OutputDirectory ""packages""" packagesConfig | |
let result = ExecProcess (fun info -> | |
info.FileName <- nugetPath | |
info.WorkingDirectory <- currentDirectory | |
info.Arguments <- args) timeOut | |
if result <> 0 then failwithf "Error during Nuget update. %s %s" nugetPath args | |
!! @"**\packages.config" | |
|> Seq.iter updatePackages | |
) | |
(** | |
As above but updates rather than restoring packages. | |
This is never called as part of the build process, but | |
can be run as a target in it's own right if you want to | |
update all nuget packages in the repository. | |
*) | |
Target "UpdatePackages" (fun _ -> | |
let updatePackages packagesConfig = | |
let timeOut = TimeSpan.FromMinutes 5. | |
let args = sprintf @"update ""%s""" packagesConfig | |
let result = ExecProcess (fun info -> | |
info.FileName <- nugetPath | |
info.WorkingDirectory <- currentDirectory | |
info.Arguments <- args) timeOut | |
if result <> 0 then failwithf "Error during Nuget update. %s %s" nugetPath args | |
!! @"**\packages.config" | |
|> Seq.iter updatePackages | |
) | |
(** | |
This target searches for and then runs NUnit test dlls. | |
NUnit.Runners has been set up as a nuget dependency and | |
we use one of FAKE's NuGet helper methods to get the | |
current version of NUnit.Runners installed to determine | |
it's location. | |
After that, we use the NUnit helper method to actually | |
run the tests. This includes functionality to inform | |
TeamCity where the test results are, so we do not need | |
to configure that within the TeamCity settings. | |
If (and only if) all of the tests pass, we activate the | |
PushPackages target, but only if we are building in CI. | |
*) | |
Target "RunTests" (fun _ -> | |
let nunitVersion = GetPackageVersion "packages" "NUnit.Runners" | |
let nunitPath = sprintf @"packages/NUnit.Runners.%s/tools/" nunitVersion | |
!! (sprintf @"*.Tests\**\bin\%s\*.Tests.dll" configuration) | |
|> NUnit (fun p -> { p with ToolPath = nunitPath; DisableShadowCopy = true; OutputFile = @"TestResults.xml"; Framework = "4.0" }) | |
if not isLocalBuild then | |
ActivateFinalTarget "PushPackages" | |
) | |
(** | |
These are the two projects that we will want the build results packaged | |
from when a build is carried out on the build server. | |
Another FAKE helper method allows us to quickly and easily build the | |
zip files we want to publish as build artifacts, and the `PublishArticfact` (sic) | |
method then notifies TeamCity where the files are. | |
*) | |
FinalTarget "PushPackages" (fun _ -> | |
let ProjectNames = | |
[ | |
"BuildOctopusProject" | |
"ProcessCustomerQuestionaire" | |
] | |
ProjectNames | |
|> List.map (fun proj -> | |
let zipFile = currentDirectory @@ proj + ".zip" | |
let projDir = currentDirectory @@ proj @@ "bin" @@ configuration | |
Zip projDir zipFile <| Directory.EnumerateFiles(projDir) | |
zipFile) | |
|> List.iter (fun proj -> PublishArticfact proj) | |
) | |
(** | |
A simple Target that just calls our buildSolution helper from the top of the | |
file with a "Clean" target. | |
*) | |
Target "Clean" (fun _ -> | |
buildSolution "PackagedProductTools.sln" "Clean" | |
) | |
(** | |
And, at last, the target that actually builds the solution. | |
Before it starts, it deletes the "warnings.log" from previous | |
builds. In case you're wondering why we're appending to the log | |
file, it's because that way we could build multiple solutions | |
in this target and aggregate the warnings from all of them. | |
*) | |
Target "Build" (fun _ -> | |
DeleteFile "warnings.log" | |
buildSolution "PackagedProductTools.sln" "Build" | |
) | |
(** | |
To finish things off, we have a no-op "Default" target, | |
that we will use for defining a dependency tree to give | |
the default behaviour of the script if it's run without | |
a named target. | |
*) | |
Target "Default" DoNothing | |
(** | |
FAKE has a few custom operators defined or creating dependency trees. | |
They operate on strings, which are the target names defined above. | |
The `==>` operator implies that the target on the left is a requirement | |
for the target on the right. | |
The `<=>` operator implies that the two targets on each side do not depend | |
on each other, but they share the same dependency chain up to that point. | |
The `=?>` operator (not used below) allows you to set up conditional dependencies | |
based on a predicate. As the predicate is just a standard F# statement | |
it can use any information available (build target requested, whether the build | |
is on a build server, etc) to decide if a particular dependency exists for | |
this build. | |
So here, we're saying that both clean and build rely on "UpdateFSharpTargets". | |
We're then going on to say that: | |
* Default depends on "RunTests" | |
* ...that depends on "Build" | |
* ...etc | |
*) | |
"UpdateFSharpTargets" | |
==> "Clean" <=> "Build" | |
"InstallPackages" | |
==> "Clean" <=> "UpdateVersions" | |
==> "Build" | |
==> "RunTests" | |
==> "Default" | |
(** | |
Having set everything up above, it's time to actually start the build process | |
going. This little piece of code means that you can either run the build file | |
with FAKE.exe and no arguments: | |
`.\tools\FAKE\FAKE.exe build.fsx` | |
or with a target specified: | |
`.\tools\FAKE\FAKE.exe build.fsx target="Build"` | |
(Which given our dependency tree would build the solution but not run the tests.) | |
*) | |
// start build | |
RunParameterTargetOrDefault "target" "Default" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment