Commit ffc7a481 authored by L. Ellenbeck's avatar L. Ellenbeck

Build: updated pipeline

parent 8b325497
stages:
- build
- test
- docs
- update-assembly-info
- build-release
- semantic-release
- release
- pre_release
- releasetrigger
build:
stage: build
script:
- PowerShell .\build.ps1 -Target Build -Configuration Debug
variables:
GIT_STRATEGY: clone
except:
variables:
- $GITLAB_USER_ID == $GIT_BOT_USER_ID
test:
cake:Test:
stage: test
script:
- PowerShell .\build.ps1 -Target LinterAndTest -Configuration Debug
- PowerShell .\build.ps1 -Target Test -Configuration Debug
variables:
GIT_STRATEGY: none
dependencies:
- build
GIT_STRATEGY: clone
artifacts:
reports:
junit: "./Artifacts/TestResults.xml"
paths:
- "./Artifacts/*"
except:
variables:
- $GITLAB_USER_ID == $GIT_BOT_USER_ID
update-assembly-info:
stage: update-assembly-info
script:
- PowerShell .\build.ps1 -Target UpdateAssemblyInfo
variables:
GIT_STRATEGY: none
dependencies:
- test
only:
- master
except:
variables:
- $GITLAB_USER_ID == $GIT_BOT_USER_ID
build-release:
stage: build-release
script:
- PowerShell .\build.ps1 -Target Build -Configuration Release
- PowerShell .\build.ps1 -Configuration Release -Target NugetPack
variables:
GIT_STRATEGY: none
dependencies:
- update-assembly-info
only:
- master
except:
variables:
- $GITLAB_USER_ID == $GIT_BOT_USER_ID
- tags
docs:
stage: docs
script:
- .\publishDocs.ps1 $GITLAB_TOKEN
variables:
GIT_STRATEGY: none
dependencies:
- test
only:
- master
except:
variables:
- $GITLAB_USER_ID == $GIT_BOT_USER_ID
semantic-release:
stage: semantic-release
script:
- PowerShell .\build.ps1 -Target SemanticRelease
variables:
GIT_STRATEGY: none
dependencies:
- test
GIT_STRATEGY: clone
only:
- master
except:
variables:
- $GITLAB_USER_ID == $GIT_BOT_USER_ID
- tags
release:
before_script:
cake:Release:
stage: release
script:
- PowerShell .\build.ps1 -Target Build -Configuration Release
- PowerShell .\build.ps1 -Configuration Release -Target NugetPack
- PowerShell .\build.ps1 -Configuration Release -Target NugetPush --nugetApiKey="$NUGET_API_KEY"
- PowerShell .\build.ps1 -Target Release -Configuration Release --nugetApiKey="${NUGET_API_KEY}"
variables:
GIT_STRATEGY: clone
dependencies:
- cake:Test
artifacts:
paths:
- "./Artifacts/*"
only:
- tags
pre_release:
stage: pre_release
cake:Prerelease:
stage: release
script:
- PowerShell .\build.ps1 -Target Build -Configuration Release
- PowerShell .\build.ps1 -Target Prerelease -Configuration Release
variables:
GIT_STRATEGY: clone
dependencies:
- cake:Test
artifacts:
paths:
- "./Artifacts/*"
when: manual
except:
- tags
- master
\ No newline at end of file
- master
cake:GitlabRelease:
stage: releasetrigger
script:
- PowerShell .\build.ps1 -Target GitlabRelease --GitlabProjectPath="${CI_PROJECT_PATH}" --gitlabProjectId="${CI_PROJECT_ID}" --gitlabToken="${GITLAB_TOKEN}"
only:
- master
\ No newline at end of file
{
"plugins": [
["@semantic-release/commit-analyzer", {
"preset": "eslint"
}],
["@semantic-release/release-notes-generator", {
"preset": "eslint",
}],
["@semantic-release/gitlab", {
"preset": "eslint",
"gitlabUrl": "https://git.rwth-aachen.de"
}],
["@semantic-release/exec", {
"prepareCmd" : "echo ${nextRelease.version}",
"publishCmd" : "echo test"
}],
["@semantic-release/git", {
"preset": "eslint",
"assets": "src/**/Properties/AssemblyInfo.cs",
"message": "Chore: ${nextRelease.version}\n\n${nextRelease.notes}"
}]
]
}
mode: ContinuousDeployment
next-version: 1.0.0
major-version-bump-message: 'Breaking:'
minor-version-bump-message: '(Update|New):'
patch-version-bump-message: 'Fix:'
no-bump-message: '.*'
commit-message-incrementing: Enabled
\ No newline at end of file
#tool nuget:?package=NUnit.ConsoleRunner&version=3.9.0
#tool nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2018.3.4
#tool nuget:?package=vswhere&version=2.6.7
#addin nuget:https://api.nuget.org/v3/index.json?package=Cake.Npx&version=1.3.0
#addin nuget:https://api.nuget.org/v3/index.json?package=Cake.Issues&version=0.6.2
#addin nuget:https://api.nuget.org/v3/index.json?package=Cake.Issues.InspectCode&version=0.6.1
#addin nuget:https://api.nuget.org/v3/index.json?package=Cake.FileHelpers&version=3.1.0
//////////////////////////////////////////////////////////////////////
// ARGUMENTS
//////////////////////////////////////////////////////////////////////
#tool nuget:?package=NUnit.ConsoleRunner&version=3.10.0
#tool nuget:?package=vswhere&version=2.8.4
#tool nuget:?package=GitVersion.CommandLine&version=5.1.3
#addin nuget:https://api.nuget.org/v3/index.json?package=Cake.Json&version=4.0.0
#addin nuget:https://api.nuget.org/v3/index.json?package=Newtonsoft.Json&version=11.0.2
#addin nuget:https://api.nuget.org/v3/index.json?package=Cake.FileHelpers&version=3.2.1
using System.Net;
using System.Net.Http;
// Commandline arguments
var target = Argument("target", "Default");
var configuration = Argument("configuration", "Release");
var nugetApiKey = Argument<string>("nugetApiKey", null);
var version = Argument("nugetVersion", "");
var gitlabProjectPath = Argument("gitlabProjectPath", "");
var gitlabProjectId = Argument("gitlabProjectId", "");
var gitlabToken = Argument("gitlabToken", "");
//////////////////////////////////////////////////////////////////////
// PREPARATION
//////////////////////////////////////////////////////////////////////
// Define directories.
// Define directories
var projects = GetFiles("./**/*.csproj");
var artifactsDir = Directory("./Artifacts");
string nupkgDir;
var solutionFile = GetFiles("./**/*.sln").First();
var projectName = solutionFile.GetFilenameWithoutExtension().ToString();
var assemblyInfoSubPath = "Properties/AssemblyInfo.cs";
var nugetSource = "https://api.nuget.org/v3/index.json";
var localNugetFeed = new
{
Name = "LocalNuget",
Source = @"C:\LocalNuget"
};
var assemblyInfoSubPath = "Properties/AssemblyInfo.cs";
var semanticVersion = "";
string localNugetFeed;
// get latest MSBuild version
var vsLatest = VSWhereLatest();
var msBuildPathX64 = (vsLatest == null) ? null : vsLatest.CombineWithFilePath("./MSBuild/Current/Bin/MSBuild.exe");
// Error rules for resharper
// Example: {"InconsistentNaming", "RedundantUsingDirective"};
string[] resharperErrorRules = {};
// Paths to exclude from dupFinder
List<string> dupFinderExcludePatterns = projects.Select( x => $"{x.GetDirectory().ToString()}/{assemblyInfoSubPath}").ToList();
string[] dupFinderExcludeCodeRegionsByNameSubstring = { "DupFinder Exclusion" };
Action <NpxSettings> requiredSemanticVersionPackages = settings =>
settings.AddPackage("semantic-release")
.AddPackage("@semantic-release/commit-analyzer")
.AddPackage("@semantic-release/release-notes-generator")
.AddPackage("@semantic-release/gitlab")
.AddPackage("@semantic-release/git")
.AddPackage("@semantic-release/exec")
.AddPackage("conventional-changelog-eslint");
///////////////////////////////////////////////////////////////////////////////
// SETUP / TEARDOWN
///////////////////////////////////////////////////////////////////////////////
Setup(context =>{
nupkgDir = $"{artifactsDir.ToString()}/nupkg";
Information("Running tasks...");
var branch = GitVersion(new GitVersionSettings {
UpdateAssemblyInfo = false
}).BranchName.Replace("/", "-");
localNugetFeed = $"C:\\coscine\\LocalNugetFeeds\\{branch}";
Information("{0}", branch);
Information("Started at {0}", DateTime.Now);
});
Teardown(context =>{
Information("Finished running tasks.");
Information("Finished at {0}", DateTime.Now);
});
//////////////////////////////////////////////////////////////////////
// TASKS
//////////////////////////////////////////////////////////////////////
Task("Clean")
.Description("Cleans all build and artifacts directories")
.Does(() =>{
......@@ -72,74 +56,36 @@ Task("Clean")
Force = true
};
var directoriesToDelete = new List<DirectoryPath>();
var directoriesToClean = new List<DirectoryPath>();
foreach(var project in projects) {
directoriesToDelete.Add(Directory($"{project.GetDirectory()}/obj"));
directoriesToDelete.Add(Directory($"{project.GetDirectory()}/bin"));
directoriesToClean.Add(Directory($"{project.GetDirectory()}/obj"));
directoriesToClean.Add(Directory($"{project.GetDirectory()}/bin"));
}
directoriesToDelete.Add(artifactsDir);
directoriesToClean.Add(artifactsDir);
foreach(var dir in directoriesToDelete) {
foreach(var dir in directoriesToClean) {
Information("Cleaning {0}", dir.ToString());
if (DirectoryExists(dir)) {
Information($"Cleaning path {dir} ...");
DeleteDirectory(dir, settings);
CreateDirectory(dir);
} else {
CreateDirectory(dir);
}
}
});
Task("Restore")
.Does(() =>{
// Restore all NuGet packages.
Information($"Restoring {solutionFile}...");
if(!NuGetHasSource(localNugetFeed.Source))
{
NuGetAddSource(localNugetFeed.Name, localNugetFeed.Source);
}
NuGetRestore(solutionFile, new NuGetRestoreSettings {
NoCache = true
NoCache = true,
FallbackSource = new List<string>{ localNugetFeed },
});
});
Task("DupFinder")
.Description("Find duplicates in the code")
.Does(() =>{
var settings = new DupFinderSettings() {
ShowStats = true,
ShowText = true,
OutputFile = $"{artifactsDir}/dupfinder.xml",
ExcludeCodeRegionsByNameSubstring = dupFinderExcludeCodeRegionsByNameSubstring,
ExcludePattern = dupFinderExcludePatterns.ToArray(),
ThrowExceptionOnFindingDuplicates = false
};
DupFinder(solutionFile, settings);
});
Task("InspectCode")
.Description("Inspect the code using Resharper's rule set")
.Does(() =>{
var settings = new InspectCodeSettings() {
SolutionWideAnalysis = true,
OutputFile = $"{artifactsDir}/inspectcode.xml",
ThrowExceptionOnFindingViolations = false
};
InspectCode(solutionFile, settings);
var issues = ReadIssues(
InspectCodeIssuesFromFilePath($"{artifactsDir}/inspectcode.xml"), Context.Environment.WorkingDirectory);
Information("{0} issues are found.", issues.Count());
var errorIssues = issues.Where(issue =>resharperErrorRules.Any(issue.Rule.Contains)).ToList();
if (errorIssues.Any()) {
var errorMessage = errorIssues.Aggregate(new StringBuilder(), (stringBuilder, issue) =>stringBuilder.AppendFormat("FileName: {0} Line: {1} Message: {2}{3}", issue.AffectedFileRelativePath, issue.Line, issue.Message, Environment.NewLine));
throw new CakeException($"{errorIssues.Count} errors detected: {Environment.NewLine}{errorMessage}.");
}
});
Task("Test")
.IsDependentOn("Build")
.Does(() =>{
NUnit3($"./src/**/bin/{configuration}/*.Tests.dll", new NUnit3Settings {
// generate the xml file
......@@ -147,92 +93,167 @@ Task("Test")
Results = new NUnit3Result[] {
new NUnit3Result() {
FileName = $"{artifactsDir}/TestResults.xml",
Transform = Context.Environment.WorkingDirectory + "/nunit3-junit.xslt"
Transform = $"{Context.Environment.WorkingDirectory}/nunit3-junit.xslt"
}
}
});
});
Task("NugetPush")
.Does(() =>{
var nupkgs = GetFiles($"{nupkgDir}/*.nupkg");
Information("Need to push {0} packages", nupkgs.Count);
foreach(var nupkg in nupkgs) {
Information("Pushing {0}", nupkg);
NuGetPush(nupkg, new NuGetPushSettings {
Source = nugetSource,
ApiKey = nugetApiKey
});
Task("GitVersion")
.Does(() => {
if(string.IsNullOrWhiteSpace(version)) {
version = GitVersion(new GitVersionSettings {
UpdateAssemblyInfo = false
}).NuGetVersionV2;
}
var index = version.IndexOf("-");
semanticVersion = index > 0 ? version.Substring(0, index) : version;
Information("Version: {0}, SemanticVersion: {1}", version, semanticVersion);
});
Task("NugetPack")
Task("UpdateAssemblyInfo")
.Does(() =>{
var index = version.IndexOf("-");
var semanticVersion = index > 0 ? version.Substring(0, index) : version;
foreach(var project in projects) {
var nuspec = $"{project.GetDirectory()}/{project.GetFilenameWithoutExtension()}.nuspec";
if(!project.ToString().EndsWith(".Tests")
&& FileExists(nuspec))
{
Information("Packing {0}...", nuspec);
if(!DirectoryExists(nupkgDir)) {
CreateDirectory(nupkgDir);
}
NuGetPack(project.ToString() ,new NuGetPackSettings
{
OutputDirectory = nupkgDir,
Properties = new Dictionary<string, string> { {"Configuration", "Release"}}
});
}
}
CreateAssemblyInfo($"{project.GetDirectory()}/{assemblyInfoSubPath}", new AssemblyInfoSettings {
Product = project.GetFilenameWithoutExtension().ToString(),
Title = project.GetFilenameWithoutExtension().ToString(),
Company = "IT Center, RWTH Aachen University",
Version = semanticVersion,
FileVersion = semanticVersion,
InformationalVersion = version,
Copyright = $"{DateTime.Now.Year} IT Center, RWTH Aachen University",
Description = $"{project.GetFilenameWithoutExtension().ToString()} is a part of the CoScInE group."
});
}
});
Task("GitlabRelease")
.IsDependentOn("GitVersion")
.Does(() => {
var client = new HttpClient();
client.DefaultRequestHeaders.Add("PRIVATE-TOKEN", gitlabToken);
// get the latest tag
var result = client.GetAsync($"https://git.rwth-aachen.de/api/v4/projects/{gitlabProjectId}/repository/tags").Result;
if(!result.IsSuccessStatusCode) {
throw new Exception("Tag query failed.");
}
Task("UpdateAssemblyInfo")
.Does(() =>{
Information("Running semantic-release in dry run mode to extract next semantic version number");
string[] semanticReleaseOutput;
Npx("semantic-release", "--dry-run", requiredSemanticVersionPackages, out semanticReleaseOutput);
var tagList = result.Content.ReadAsStringAsync().Result;
var jArray = JArray.Parse(tagList);
// null if not tags exists yet
var lastTag = jArray.Select(x => x["name"]).FirstOrDefault();
Information(string.Join(Environment.NewLine, semanticReleaseOutput));
var url = $"https://git.rwth-aachen.de/{gitlabProjectPath}";
if(url.EndsWith(".git")) {
url = url.Substring(0, url.Length - ".git".Length);
}
var nextSemanticVersionNumber = ExtractNextSemanticVersionNumber(semanticReleaseOutput);
if(url.EndsWith("/")) {
url = url.Substring(0, url.Length - 1);
}
if (nextSemanticVersionNumber == null) {
Warning("There are no relevant changes. AssemblyInfo won't be updated!");
var description = "";
// First line of description
// Gitlab compare url, if something can be compared
if(lastTag == null) {
description = $"# {semanticVersion} ({DateTime.Now.Year}-{DateTime.Now.Month}-{DateTime.Now.Day})\n\n\n";
} else {
Information("Next semantic version number is {0}", nextSemanticVersionNumber);
var assemblyVersion = $"{nextSemanticVersionNumber}.0";
foreach(var project in projects) {
CreateAssemblyInfo($"{project.GetDirectory()}/{assemblyInfoSubPath}", new AssemblyInfoSettings {
Product = project.GetFilenameWithoutExtension().ToString(),
Title = project.GetFilenameWithoutExtension().ToString(),
Company = "IT Center, RWTH Aachen University",
Version = assemblyVersion,
FileVersion = assemblyVersion,
InformationalVersion = assemblyVersion,
Copyright = $"{DateTime.Now.Year} IT Center, RWTH Aachen University",
Description = $"{project.GetFilenameWithoutExtension().ToString()} is a part of the CoScInE group."
});
description = $"# [{semanticVersion}]({url}/compare/{lastTag}...v{semanticVersion}) ({DateTime.Now.Year}-{DateTime.Now.Month}-{DateTime.Now.Day})\n\n\n";
}
// From when will messages be parsed, null results in all messages
var logParam = "";
if(lastTag != null) {
logParam = $"{lastTag}..Head";
}
Information(lastTag);
IEnumerable<string> redirectedStandardOutput;
var exitCodeWithArgument =
StartProcess(
"git",
new ProcessSettings {
Arguments = $"log {logParam} --pretty=format:HASH%h:%B",
RedirectStandardOutput = true
},
out redirectedStandardOutput
);
var prefixList = new Dictionary<string, List<string>>{
{"Fix", new List<string>()},
{"Update", new List<string>()},
{"New", new List<string>()},
{"Breaking", new List<string>()},
{"Docs", new List<string>()},
{"Build", new List<string>()},
{"Upgrade", new List<string>()},
{"Chore", new List<string>()},
};
var currentHash = "";
// Output last line of process output.
foreach(var line in redirectedStandardOutput) {
var commitMessage = "";
if(line.StartsWith("HASH")) {
currentHash = line.Substring("HASH".Length);
currentHash = currentHash.Substring(0, currentHash.IndexOf(":"));
commitMessage = line.Substring(currentHash.Length + line.IndexOf(currentHash) + 1);
} else {
commitMessage = line;
}
foreach(var kv in prefixList) {
if(commitMessage.StartsWith($"{kv.Key}:")) {
kv.Value.Add($"* {commitMessage.Substring(kv.Key.Length + 1).Trim()} {currentHash}");
break;
}
};
}
foreach(var kv in prefixList) {
if(kv.Value.Any()) {
description += $" ### {kv.Key}\n\n";
foreach(var line in kv.Value) {
description += $"{line}\n";
}
description += "\n";
}
}
// correctly escape the json newlines
description = description.Replace("\n", "\\n");
Information("Description: {0}", description);
// create tag
result = client.PostAsync($"https://git.rwth-aachen.de/api/v4/projects/{gitlabProjectId}/repository/tags?tag_name=v{semanticVersion}&ref=master", null).Result;
Information("Create tag: {0}", result.Content.ReadAsStringAsync().Result);
if(!result.IsSuccessStatusCode) {
throw new Exception("Tag creation failed.");
}
// create release
var json = $"{{\"name\": \"v{semanticVersion}\", \"tag_name\": \"v{semanticVersion}\", \"description\": \"{description}\"}}";
var content = new StringContent(json, Encoding.UTF8, "application/json");
result = client.PostAsync($"https://git.rwth-aachen.de/api/v4/projects/{gitlabProjectId}/releases", content).Result;
Information("Create release: {0}", result.Content.ReadAsStringAsync().Result);
if(!result.IsSuccessStatusCode) {
throw new Exception("Release creation failed.");
}
});
Task("Build")
.IsDependentOn("Clean")
.IsDependentOn("GitVersion")
.IsDependentOn("UpdateAssemblyInfo")
.IsDependentOn("Restore")
.Does(() =>{
if (IsRunningOnWindows()) {
var frameworkSettingsWindows = new MSBuildSettings {
Configuration = configuration,
Restore = true
Configuration = configuration
};
frameworkSettingsWindows.ToolPath = msBuildPathX64;
......@@ -242,74 +263,89 @@ Task("Build")
frameworkSettingsWindows.WithProperty("DebugSymbols", "false");
frameworkSettingsWindows.WithProperty("DebugType", "None");
}
// Use MSBuild
Information($"Building {solutionFile}");
Information("Building {0}", solutionFile);
MSBuild(solutionFile, frameworkSettingsWindows);
}
else {
var frameworkSettingsUnix = new XBuildSettings {
Configuration = configuration
};
});
if (configuration.Equals("Release")) {
frameworkSettingsUnix.WithProperty("DebugSymbols", "false");
frameworkSettingsUnix.WithProperty("DebugType", "None");
}
// Use XBuild
Information($"Building {solutionFile}");
XBuild(solutionFile, frameworkSettingsUnix);
}
if (configuration.Equals("Release")) {
if(!DirectoryExists(artifactsDir)) {
CreateDirectory(artifactsDir);
}
foreach(var project in projects) {
if(!project.GetDirectory().ToString().EndsWith(".Tests")
&& !FileExists($"{project.GetDirectory()}/{project.GetFilenameWithoutExtension()}.nuspec"))
Task("NugetPack")
.IsDependentOn("Build")
.Does(() =>{
foreach(var project in projects) {
var nuspec = $"{project.GetDirectory()}/{project.GetFilenameWithoutExtension()}.nuspec";
if(!project.ToString().EndsWith(".Tests") && FileExists(nuspec))
{
Information($"Copying {project.GetDirectory()}/bin/{configuration}/* to {artifactsDir}");
CopyDirectory($"{project.GetDirectory()}/bin/{configuration}/", artifactsDir);
var settings = new NuGetPackSettings
{
OutputDirectory = nupkgDir,
Version = version,
Properties = new Dictionary<string, string>
{
{ "Configuration", configuration}
}
};
NuGetPack(project.ToString(), settings);
}
}
}
});
Task("NugetPush")
.IsDependentOn("NugetPack")
.Does(() =>{
var nupkgs = GetFiles($"{nupkgDir}/*.nupkg");
Information("Need to push {0} packages", nupkgs.Count);
if(!String.IsNullOrWhiteSpace(nugetApiKey)) {
foreach(var nupkg in nupkgs) {
Information("Pushing {0}", nupkg);
NuGetPush(nupkg, new NuGetPushSettings {
Source = nugetSource,
ApiKey = nugetApiKey
});
}
} else {
Information("NugetApiKey is not set. Can't push.");
throw new Exception("NugetApiKey is not set. Can't push.");
}
});
Task("SemanticRelease")
Task("CopyToArtifacts")
.Does(() =>{
Npx("semantic-release", requiredSemanticVersionPackages);
foreach(var project in projects) {
if(!project.GetDirectory().ToString().EndsWith(".Tests")
&& !FileExists($"{project.GetDirectory()}/{project.GetFilenameWithoutExtension()}.nuspec")
&& DirectoryExists(project.GetDirectory()))
{
Information("Copying {0}/* to {1}", $"{project.GetDirectory()}/bin/{configuration}", artifactsDir);
CopyDirectory($"{project.GetDirectory()}/bin/{configuration}/", artifactsDir);
}
}
});
Task("Linter")
.Description("Run DupFinder and InspectCode")
.IsDependentOn("DupFinder")
.IsDependentOn("InspectCode");
Task("NugetPushLocal")
.IsDependentOn("NugetPack")
.Does(() =>{
var nupkgs = GetFiles($"{nupkgDir}/*.nupkg");
foreach(var nupkg in nupkgs) {