diff --git a/.gitignore b/.gitignore index 7c2965eda294e35b644059bf493b44aa290aebb6..363c7b2ec0283c1fb1b2d1d239fe573caffc5663 100644 --- a/.gitignore +++ b/.gitignore @@ -262,5 +262,6 @@ __pycache__/ *.pyc #cake -tools/ -dist/ \ No newline at end of file +tools/* +!tools/packages.config +dist/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a60ae9cac3363615b4f1ae284ba69ccae900c97e..58b4648128ed7fb6a416446cf5200f8e05e6e42a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,5 +1,3 @@ - - stages: - build - test @@ -8,30 +6,30 @@ stages: - build-release - semantic-release - release - + build: - before_script: - - PowerShell .\build.ps1 -Target Clean - - PowerShell .\build.ps1 -Target Restore-NuGet-Packages stage: build script: - - PowerShell .\build.ps1 -Target Build + - PowerShell .\build.ps1 -Target Build -Configuration Debug + variables: + GIT_STRATEGY: clone except: variables: - $GITLAB_USER_ID == $GIT_BOT_USER_ID - + test: stage: test script: - - PowerShell .\build.ps1 -Target Resharper - - PowerShell .\build.ps1 -Target Run-Unit-Tests + - PowerShell .\build.ps1 -Target LinterAndTest -Configuration Debug variables: GIT_STRATEGY: none dependencies: - build artifacts: reports: - junit: TestResult.xml + junit: "./Artifacts/TestResults.xml" + paths: + - "./Artifacts/*" except: variables: - $GITLAB_USER_ID == $GIT_BOT_USER_ID @@ -39,7 +37,7 @@ test: update-assembly-info: stage: update-assembly-info script: - - PowerShell .\build.ps1 -Target Update-Assembly-Info + - PowerShell .\build.ps1 -Target UpdateAssemblyInfo variables: GIT_STRATEGY: none dependencies: @@ -51,12 +49,10 @@ update-assembly-info: - $GITLAB_USER_ID == $GIT_BOT_USER_ID build-release: - before_script: - - PowerShell .\build.ps1 -Target Clean - - PowerShell .\build.ps1 -Target Restore-NuGet-Packages stage: build-release script: - - PowerShell .\build.ps1 -Target Build-Release + - PowerShell .\build.ps1 -Target Build -Configuration Release + - PowerShell .\build.ps1 -Configuration Release -Target NugetPack variables: GIT_STRATEGY: none dependencies: @@ -80,11 +76,11 @@ docs: except: variables: - $GITLAB_USER_ID == $GIT_BOT_USER_ID - + semantic-release: stage: semantic-release script: - - PowerShell .\build.ps1 -Target Semantic-Release + - PowerShell .\build.ps1 -Target SemanticRelease variables: GIT_STRATEGY: none dependencies: @@ -94,15 +90,18 @@ semantic-release: except: variables: - $GITLAB_USER_ID == $GIT_BOT_USER_ID - + release: + before_script: stage: release script: - - PowerShell .\build.ps1 -Target Build-Release + - 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" variables: - GIT_STRATEGY: none + GIT_STRATEGY: clone artifacts: paths: - - dist + - "./Artifacts/*" only: - tags diff --git a/.releaserc b/.releaserc index 576a3f725c1310a69365c11aae327a9f61e5dc47..72ff2701baf27f6738b07f990506607bcc8dd562 100644 --- a/.releaserc +++ b/.releaserc @@ -20,4 +20,4 @@ "message": "Chore: ${nextRelease.version}\n\n${nextRelease.notes}" }] ] -} \ No newline at end of file +} diff --git a/build.cake b/build.cake index 9167f136cd91b4c3805451f5c14b6009130cae7c..cfb6a113c06df741f752ffe39de23f5c190ffe09 100644 --- a/build.cake +++ b/build.cake @@ -1,241 +1,307 @@ #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 ////////////////////////////////////////////////////////////////////// - var target = Argument("target", "Default"); var configuration = Argument("configuration", "Release"); +var nugetApiKey = Argument<string>("nugetApiKey", null); ////////////////////////////////////////////////////////////////////// // PREPARATION ////////////////////////////////////////////////////////////////////// - // Define directories. -string projectName; -string projectPath; -DirectoryPath buildDir; -FilePath solutionFile; +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"; + +// 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 = {}; +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..."); +}); -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"); +Teardown(context =>{ + Information("Finished running tasks."); +}); ////////////////////////////////////////////////////////////////////// // TASKS ////////////////////////////////////////////////////////////////////// - -Task("Get-Project-Name") - .Does(() => -{ - var solutions = GetFiles("./**/*.sln"); - projectName = solutions.First().GetFilenameWithoutExtension().ToString(); - Information("Project Name: {0}", projectName); +Task("Clean") +.Description("Cleans all build and artifacts directories") +.Does(() =>{ + var settings = new DeleteDirectorySettings { + Recursive = true, + Force = true + }; - solutionFile = solutions.First().ToString(); - Information("Solution File: {0}", solutionFile.ToString()); + var directoriesToDelete = new List<DirectoryPath>(); - projectPath = Context.Environment.WorkingDirectory.ToString().ToString() + "/src"; - Information("Project Directory: {0}", projectPath); + foreach(var project in projects) { + directoriesToDelete.Add(Directory($"{project.GetDirectory()}/obj")); + directoriesToDelete.Add(Directory($"{project.GetDirectory()}/bin")); + } - buildDir = Directory(projectPath + "/" + projectName + "/bin") + Directory(configuration); - Information("Build Directory: {0}", buildDir.ToString()); + directoriesToDelete.Add(artifactsDir); + + foreach(var dir in directoriesToDelete) { + if (DirectoryExists(dir)) { + Information($"Cleaning path {dir} ..."); + DeleteDirectory(dir, settings); + } + } }); -Task("Clean") - .IsDependentOn("Get-Project-Name") - .Does(() => -{ - CleanDirectory(buildDir); - CleanDirectory("./dist"); +Task("Restore") +.Does(() =>{ + // Restore all NuGet packages. + Information($"Restoring {solutionFile}..."); + NuGetRestore(solutionFile, new NuGetRestoreSettings { + NoCache = true + }); +}); + +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 = true + }; + 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("Restore-NuGet-Packages") - .IsDependentOn("Get-Project-Name") - .Does(() => -{ - NuGetRestore(solutionFile); +Task("Test") +.Does(() =>{ + NUnit3($"./src/**/bin/{configuration}/*.Tests.dll", new NUnit3Settings { + // generate the xml file + NoResults = false, + Results = new NUnit3Result[] { + new NUnit3Result() { + FileName = $"{artifactsDir}/TestResults.xml", + Transform = Context.Environment.WorkingDirectory + "/nunit3-junit.xslt" + } + } + }); }); -Task("Resharper") - .IsDependentOn("Get-Project-Name") - .Does(() => -{ - FilePath dupLog = Context.Environment.WorkingDirectory + "/Resharper/dupfinder.xml"; - FilePath inspectLog = Context.Environment.WorkingDirectory + "/Resharper/inspectcode.xml"; - - DupFinder(solutionFile, new DupFinderSettings() { - OutputFile = dupLog.ToString() - }); - - Information("DupFinder Log:{0}{1}", Environment.NewLine, FileReadText(dupLog)); - - InspectCode(solutionFile, new InspectCodeSettings() { - OutputFile = inspectLog.ToString() - }); - - var issues = ReadIssues( - InspectCodeIssuesFromFilePath(inspectLog.ToString()), - Context.Environment.WorkingDirectory); - - Information("{0} issues are found.", issues.Count()); - - Information("InspectCode Log:{0}{1}", Environment.NewLine, FileReadText(inspectLog)); - - 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("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("Update-Assembly-Info") - .IsDependentOn("Get-Project-Name") - .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); - - Information(string.Join(Environment.NewLine, semanticReleaseOutput)); - - var nextSemanticVersionNumber = ExtractNextSemanticVersionNumber(semanticReleaseOutput); - - if (nextSemanticVersionNumber == null) { - Warning("There are no relevant changes. AssemblyInfo won't be updated!"); - } else { - Information("Next semantic version number is {0}", nextSemanticVersionNumber); - - var assemblyVersion = $"{nextSemanticVersionNumber}.0"; - - CreateAssemblyInfo(projectPath + "/" + projectName + "/Properties/AssemblyInfo.cs", new AssemblyInfoSettings{ - Product = projectName, - Title = projectName, - Company = "RWTH Aachen University IT Center", - Version = assemblyVersion, - FileVersion = assemblyVersion, - InformationalVersion = assemblyVersion, - Copyright = "RWTH Aachen University IT Center " + DateTime.Now.Year - }); - } + +Task("NugetPack") +.Does(() =>{ + 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"}} + }); + } + } }); - -Task("Build-Release") - .IsDependentOn("Get-Project-Name") - .Does(() => -{ - if(IsRunningOnWindows()) - { - // Use MSBuild - MSBuild(solutionFile, settings => - { - settings.SetConfiguration(configuration); - settings.WithProperty("DebugSymbols", "false"); - settings.WithProperty("DebugType", "None"); - }); - } - else - { - // Use XBuild - XBuild(solutionFile, settings => - { - settings.SetConfiguration(configuration); - settings.WithProperty("DebugSymbols", "false"); - settings.WithProperty("DebugType", "None"); - }); - } - CopyDirectory(buildDir, "./dist"); + + +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); + + Information(string.Join(Environment.NewLine, semanticReleaseOutput)); + + var nextSemanticVersionNumber = ExtractNextSemanticVersionNumber(semanticReleaseOutput); + + if (nextSemanticVersionNumber == null) { + Warning("There are no relevant changes. AssemblyInfo won't be updated!"); + } 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." + }); + } + } }); Task("Build") - .IsDependentOn("Get-Project-Name") - .Does(() => -{ - if(IsRunningOnWindows()) - { - // Use MSBuild - MSBuild(solutionFile, settings => - { - settings.SetConfiguration(configuration); - settings.WithProperty("RunCodeAnalysis", "true"); - }); - } - else - { - // Use XBuild - XBuild(solutionFile, settings => - { - settings.SetConfiguration(configuration); - settings.WithProperty("RunCodeAnalysis", "true"); - }); - } -}); +.IsDependentOn("Clean") +.IsDependentOn("Restore") +.Does(() =>{ + if (IsRunningOnWindows()) { + var frameworkSettingsWindows = new MSBuildSettings { + Configuration = configuration, + Restore = true + }; + + frameworkSettingsWindows.ToolPath = msBuildPathX64; + frameworkSettingsWindows.WorkingDirectory = Context.Environment.WorkingDirectory; + + if (configuration.Equals("Release")) { + frameworkSettingsWindows.WithProperty("DebugSymbols", "false"); + frameworkSettingsWindows.WithProperty("DebugType", "None"); + } + // Use MSBuild + Information($"Building {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")) + { + Information($"Copying {project.GetDirectory()}/bin/{configuration}/* to {artifactsDir}"); + CopyDirectory($"{project.GetDirectory()}/bin/{configuration}/", artifactsDir); + } + } + } -Task("Run-Unit-Tests") - .Does(() => -{ - NUnit3("./src/**/bin/" + configuration + "/*.Tests.dll", new NUnit3Settings { - // generate the xml file - NoResults = false, - Results = new NUnit3Result[]{new NUnit3Result(){ - FileName = Context.Environment.WorkingDirectory + "/TestResult.xml", - Transform = Context.Environment.WorkingDirectory + "/nunit3-junit.xslt" - }} - }); }); -Task("Semantic-Release") - .Does(() => -{ - Npx("semantic-release", requiredSemanticVersionPackages); +Task("SemanticRelease") +.Does(() =>{ + Npx("semantic-release", requiredSemanticVersionPackages); }); -////////////////////////////////////////////////////////////////////// -// TASK TARGETS -////////////////////////////////////////////////////////////////////// +Task("Linter") +.Description("Run DupFinder and InspectCode") +.IsDependentOn("DupFinder") +.IsDependentOn("InspectCode"); + +Task("LinterAndTest") +.Description("Run Linter and Tests") +.IsDependentOn("Linter") +.IsDependentOn("Test"); Task("Default") - .IsDependentOn("Clean") - .IsDependentOn("Restore-NuGet-Packages") - .IsDependentOn("Resharper") - .IsDependentOn("Build") - .IsDependentOn("Run-Unit-Tests"); +.IsDependentOn("Clean") +.IsDependentOn("Restore") +.IsDependentOn("DupFinder") +.IsDependentOn("InspectCode") +.IsDependentOn("Build") +.IsDependentOn("Test"); ////////////////////////////////////////////////////////////////////// // EXECUTION ////////////////////////////////////////////////////////////////////// - RunTarget(target); /////////////////////////////////////////////////////////////////////////////// // Helpers /////////////////////////////////////////////////////////////////////////////// +string ExtractNextSemanticVersionNumber(string[] semanticReleaseOutput) { + var extractRegEx = new System.Text.RegularExpressions.Regex("^.+next release version is (?<SemanticVersionNumber>.*)$"); -string ExtractNextSemanticVersionNumber(string[] semanticReleaseOutput) -{ - var extractRegEx = new System.Text.RegularExpressions.Regex("^.+next release version is (?<SemanticVersionNumber>.*)$"); - - return semanticReleaseOutput - .Select(line => extractRegEx.Match(line).Groups["SemanticVersionNumber"].Value) - .Where(line => !string.IsNullOrWhiteSpace(line)) - .SingleOrDefault(); -} \ No newline at end of file + return semanticReleaseOutput.Select(line =>extractRegEx.Match(line).Groups["SemanticVersionNumber"].Value).Where(line =>!string.IsNullOrWhiteSpace(line)).SingleOrDefault(); +} diff --git a/src/Configuration/Configuration.nuspec b/src/Configuration/Configuration.nuspec new file mode 100644 index 0000000000000000000000000000000000000000..60611db69b82e686c8b730926481cacd0a509297 --- /dev/null +++ b/src/Configuration/Configuration.nuspec @@ -0,0 +1,15 @@ +<?xml version="1.0"?> +<package > + <metadata> + <id>$id$</id> + <version>$version$</version> + <title>$title$</title> + <authors>rwth-aachen</authors> + <owners>rwth-aachen</owners> + <license type="expression">MIT</license> + <projectUrl>https://git.rwth-aachen.de/coscine/cs/configuration</projectUrl> + <requireLicenseAcceptance>true</requireLicenseAcceptance> + <description>$description$</description> + <copyright>$copyright$</copyright> + </metadata> +</package> \ No newline at end of file diff --git a/tools/packages.config b/tools/packages.config new file mode 100644 index 0000000000000000000000000000000000000000..3c65df896fad902af9e0d5bc8388adf80f57944c --- /dev/null +++ b/tools/packages.config @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="Cake" version="0.28.0" /> +</packages>