An approach to building .NET Core applications with Cake, XUnit and synchronous `project.json` versionining for both local and on Bamboo
#addin "Cake.Powershell" | |
#r "tools/AddIns/Cake.DotNetCoreVersion/Cake.DotNetCoreVersion.dll" | |
////////////////////////////////////////////////////////////////////// | |
// ARGUMENTS | |
////////////////////////////////////////////////////////////////////// | |
var target = Argument("target", "Default"); | |
var configuration = Argument("configuration", "Release"); | |
string version = null; | |
string semanticVersion = null; | |
string prerelease = null; | |
////////////////////////////////////////////////////////////////////// | |
// TASKS | |
////////////////////////////////////////////////////////////////////// | |
Task("Clean") | |
.Does(() => | |
{ | |
CleanDirectory("./artifacts/"); | |
CleanDirectory("./test-results/"); | |
}); | |
Task("Restore") | |
.Does(() => | |
{ | |
DotNetCoreRestore(); | |
}); | |
Task("Version") | |
.Does(() => | |
{ | |
if (Bamboo.IsRunningOnBamboo) | |
{ | |
// MA - We are running a CI build - so need make sure we execute the script with -local = $false | |
StartPowershellFile("./version.ps1", args => | |
{ | |
args.Append("local", "$false"); | |
args.Append("branch", EnvironmentVariable("bamboo_planRepository_branchName")); | |
}); | |
} | |
else | |
{ | |
StartPowershellFile("./version.ps1", args => args.Append("local", "$true")); | |
} | |
string[] lines = System.IO.File.ReadAllLines("./version.props"); | |
foreach (string line in lines) | |
{ | |
if (line.StartsWith("version")) | |
{ | |
version = line.Substring("version=".Length).Trim(); | |
} | |
else if (line.StartsWith("semanticVersion")) | |
{ | |
semanticVersion = line.Substring("semanticVersion=".Length).Trim(); | |
} | |
else if (line.StartsWith("prerelease")) | |
{ | |
prerelease = line.Substring("prerelease=".Length).Trim(); | |
} | |
} | |
Console.WriteLine("Version: {0}", version); | |
Console.WriteLine("SemanticVersion: {0}", semanticVersion); | |
Console.WriteLine("PreRelease: {0}", prerelease); | |
DotNetCoreVersion(new DotNetCoreVersionSettings | |
{ | |
Files = GetFiles("**/project.json"), | |
Version = semanticVersion | |
}); | |
}); | |
Task("Build") | |
.Does(() => | |
{ | |
// MA - Build the libraries | |
DotNetCoreBuild("./src/**/project.json", new DotNetCoreBuildSettings | |
{ | |
Configuration = configuration | |
}); | |
// MA - Build the test libraries | |
DotNetCoreBuild("./tests/**/project.json", new DotNetCoreBuildSettings | |
{ | |
Configuration = configuration | |
}); | |
}); | |
Task("Test") | |
.WithCriteria(() => HasArgument("test")) | |
.Does(() => | |
{ | |
var tests = GetFiles("./tests/**/project.json"); | |
foreach (var test in tests) | |
{ | |
string projectFolder = System.IO.Path.GetDirectoryName(test.FullPath); | |
string projectName = projectFolder.Substring(projectFolder.LastIndexOf('\\') + 1); | |
string resultsFile = "./test-results/" + projectName + ".xml"; | |
DotNetCoreTest(test.FullPath, new DotNetCoreTestSettings | |
{ | |
ArgumentCustomization = args => args.Append("-xml " + resultsFile) | |
}); | |
// MA - Transform the result XML into NUnit-compatible XML for the build server. | |
XmlTransform("./tools/NUnitXml.xslt", "./test-results/" + projectName + ".xml", "./test-results/NUnit." + projectName + ".xml"); | |
} | |
}); | |
Task("Pack") | |
.WithCriteria(() => HasArgument("pack")) | |
.Does(() => | |
{ | |
var projects = GetFiles("./src/**/project.json"); | |
foreach (var project in projects) | |
{ | |
// MA - Pack the libraries | |
DotNetCorePack(project.FullPath, new DotNetCorePackSettings | |
{ | |
Configuration = configuration, | |
OutputDirectory = "./artifacts/" | |
}); | |
} | |
}); | |
////////////////////////////////////////////////////////////////////// | |
// TASK TARGETS | |
////////////////////////////////////////////////////////////////////// | |
Task("Default") | |
.IsDependentOn("Version") | |
.IsDependentOn("Clean") | |
.IsDependentOn("Restore") | |
.IsDependentOn("Build") | |
.IsDependentOn("Test") | |
.IsDependentOn("Pack"); | |
////////////////////////////////////////////////////////////////////// | |
// EXECUTION | |
////////////////////////////////////////////////////////////////////// | |
RunTarget(target); |
versionMajor=1 | |
versionMinor=0 | |
versionRev=0 |
namespace Cake.DotNetVersion | |
{ | |
using System; | |
using System.Collections.Generic; | |
using Cake.Core; | |
using Cake.Core.Annotations; | |
using Cake.Core.IO; | |
/// <summary> | |
/// Contains functionality required to updating project file versions. | |
/// </summary> | |
[CakeAliasCategory("DotNetCoreVersion")] | |
public static class DotNetCoreVersionExtensions | |
{ | |
[CakeMethodAlias] | |
public static void DotNetCoreVersion(this ICakeContext context, string version) | |
{ | |
var settings = new DotNetCoreVersionSettings | |
{ | |
Files = context.Globber.GetFiles("/**/project.json"), | |
Version = version | |
}; | |
DotNetCoreVersion(context, settings); | |
} | |
[CakeMethodAlias] | |
public static void DotNetCoreVersion(this ICakeContext context, FilePath file, string version) | |
{ | |
var settings = new DotNetCoreVersionSettings | |
{ | |
File = file, | |
Version = version | |
}; | |
DotNetCoreVersion(context, settings); | |
} | |
[CakeMethodAlias] | |
public static void DotNetCoreVersion(this ICakeContext context, IEnumerable<FilePath> files, string version) | |
{ | |
var settings = new DotNetCoreVersionSettings | |
{ | |
Files = files, | |
Version = version | |
}; | |
DotNetCoreVersion(context, settings); | |
} | |
[CakeMethodAlias] | |
public static void DotNetCoreVersion(this ICakeContext context, DotNetCoreVersionSettings settings) | |
{ | |
if (context == null) | |
{ | |
throw new ArgumentNullException(nameof(context)); | |
} | |
if (settings == null) | |
{ | |
throw new ArgumentNullException(nameof(settings)); | |
} | |
if (settings.File == null && settings.Files == null) | |
{ | |
throw new ArgumentException("You must provide a file or set of file paths to process."); | |
} | |
if (string.IsNullOrEmpty(settings.Version)) | |
{ | |
throw new ArgumentException("You must provide a version to set."); | |
} | |
var runner = new DotNetCoreVersionRunner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); | |
runner.Run(settings); | |
} | |
} | |
} |
namespace Cake.DotNetVersion | |
{ | |
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Text; | |
using Cake.Core; | |
using Cake.Core.IO; | |
using Cake.Core.Tooling; | |
using Newtonsoft.Json; | |
using Newtonsoft.Json.Linq; | |
/// <summary> | |
/// Applies a version to a set of project.json files. | |
/// </summary> | |
public class DotNetCoreVersionRunner : Tool<DotNetCoreVersionSettings> | |
{ | |
/// <summary> | |
/// Initialises a new instance of <see cref="DotNetCoreVersionRunner"/> | |
/// </summary> | |
/// <param name="fileSystem">The Cake file system.</param> | |
public DotNetCoreVersionRunner(IFileSystem fileSystem, ICakeEnvironment environment, IProcessRunner processRunner, IToolLocator toolLocator) | |
: base(fileSystem, environment, processRunner, toolLocator) | |
{ | |
} | |
public void Run(DotNetCoreVersionSettings settings) | |
{ | |
if (settings == null) | |
{ | |
throw new ArgumentNullException(nameof(settings)); | |
} | |
if (settings.Files != null) | |
{ | |
foreach (var file in settings.Files) | |
{ | |
RunCore(file, settings.Version); | |
} | |
} | |
else if (settings.File != null) | |
{ | |
RunCore(settings.File, settings.Version); | |
} | |
} | |
protected override IEnumerable<string> GetToolExecutableNames() | |
{ | |
return new string[0]; | |
} | |
protected override string GetToolName() | |
{ | |
return "DotNetCoreVersion"; | |
} | |
private void RunCore(FilePath filePath, string version) | |
{ | |
JObject project; | |
using (var file = new FileStream(filePath.FullPath, FileMode.Open)) | |
using (var stream = new StreamReader(file)) | |
using (var json = new JsonTextReader(stream)) | |
{ | |
project = JObject.Load(json); | |
} | |
var versionAttr = project.Property("version"); | |
if (versionAttr == null) | |
{ | |
project.Add("version", new JValue(version)); | |
} | |
else | |
{ | |
versionAttr.Value = version; | |
} | |
File.WriteAllText(filePath.FullPath, project.ToString(), Encoding.UTF8); | |
} | |
} | |
} |
namespace Cake.DotNetVersion | |
{ | |
using System.Collections.Generic; | |
using Cake.Core.IO; | |
using Cake.Core.Tooling; | |
/// <summary> | |
/// Contains settings used by the version tool. | |
/// </summary> | |
public class DotNetCoreVersionSettings : ToolSettings | |
{ | |
/// <summary> | |
/// Gets or sets the collection of project files to update. | |
/// </summary> | |
public IEnumerable<FilePath> Files { get; set; } | |
/// <summary> | |
/// Gets or sets the single file path to update. | |
/// </summary> | |
public FilePath File { get; set; } | |
/// <summary> | |
/// Gets or sets the version to set. | |
/// </summary> | |
public string Version { get; set; } | |
} | |
} |
[CmdletBinding()] | |
Param( | |
[bool] $local = $true, | |
[string] $branch = $null | |
) | |
function ReadBuildProps | |
{ | |
$buildProps = ConvertFrom-StringData (Get-Content ./build.props -raw) | |
$buildObject = New-Object PSObject -Property $buildProps | Select-Object versionMajor, versionMinor, versionRev | |
return $buildObject | |
} | |
function ReadBranchName | |
{ | |
$branchName = (& git rev-parse --abbrev-ref HEAD).Trim() | |
return $branchName | |
} | |
function ReadCommitCount | |
{ | |
$count = (& git rev-list --all --count).Trim() | |
return $count | |
} | |
function CreateVersion | |
( | |
[string] $major, | |
[string] $minor, | |
[string] $rev, | |
[string] $branch, | |
[int] $commits, | |
[bool] $local = $true | |
) | |
{ | |
$version = [string]::Concat($major, ".", $minor, ".", $rev) | |
$prerelease = $null | |
$branch = $branch.Replace('/', '-') | |
if ($branch -ne "release") | |
{ | |
if ($local) | |
{ | |
$prerelease = $branch | |
} | |
else | |
{ | |
$prerelease = "$($branch)-$($commits)" | |
} | |
} | |
if ($prerelease -ne $null) | |
{ | |
$semVer = [string]::Concat($version, "-", $prerelease) | |
} | |
else | |
{ | |
$semVer = $version | |
} | |
return @{ | |
Version = $version | |
SemanticVersion = $semVer | |
PreRelease = $prerelease | |
} | |
} | |
function WriteVersion | |
( | |
[string] $version, | |
[string] $semanticVersion, | |
[string] $prerelease | |
) | |
{ | |
Set-Content ./version.props "version=$($version)`nsemanticVersion=$($semanticVersion)`nprerelease=$($prerelease)" | |
} | |
function ResolveVersion | |
( | |
[bool] $local = $true | |
) | |
{ | |
$parts = ReadBuildProps | |
if (!$branch) | |
{ | |
$branch = ReadBranchName | |
} | |
$count = ReadCommitCount | |
$version = CreateVersion -major $parts.versionMajor -minor $parts.versionMinor -rev $parts.versionRev -branch $branch -commits $count -local $local | |
WriteVersion -version $version.Version -semanticVersion $version.SemanticVersion -prerelease $version.PreRelease | |
} | |
ResolveVersion -local $local |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment