Skip to content

Commit

Permalink
Adds publishing check and makes Sharpliner self-hosted (#29)
Browse files Browse the repository at this point in the history
**Makes the project use the pipeline defined using Sharpliner inside the `Sharpliner.CI` project!**

Adds an AzDO task that will verify whether you have published and commited all changes into YAML files. (resolves #3)

The many empty arrays in the YAML will be gone once we check in aaubry/YamlDotNet#608
  • Loading branch information
premun committed May 17, 2021
1 parent f3bbfd2 commit 19b1d03
Show file tree
Hide file tree
Showing 9 changed files with 159 additions and 35 deletions.
25 changes: 20 additions & 5 deletions azure-pipelines.yml
Expand Up @@ -9,20 +9,35 @@ pr:
include:
- main

jobs:
stages:
- stage: Build
displayName: Build
jobs:
- job: Build
displayName: Build and test
pool:
name: Azure Pipelines
vmImage: windows-latest

name: Azure Pipelines
steps:
# dotnet build fails with .NET 5 SDK and the new() statements

- task: UseDotNet@2
displayName: Install .NET 6 preview 3
inputs:
packageType: sdk
version: 6.0.100-preview.3.21202.5
- powershell: >-
dotnet build "eng/Sharpliner.CI/Sharpliner.CI.csproj"
$results = Invoke-Expression "git status --porcelain" | Select-String -Pattern "\.ya?ml$"
if (!$results) {
Write-Host 'Check succeeded - no YAML changes needed'
exit 0
} else {
Write-Error 'Please rebuild eng/Sharpliner.CI/Sharpliner.CI.csproj locally and commit the YAML changes'
exit 1
}
displayName: Validate YAML is published
- task: DotNetCoreCLI@2
displayName: dotnet build
Expand All @@ -31,7 +46,6 @@ jobs:
includeNuGetOrg: true
projects: Sharpliner.sln

# dotnet test somehow doesn't work with .NET 6 SDK
- task: UseDotNet@2
displayName: Install .NET 5
inputs:
Expand All @@ -42,4 +56,5 @@ jobs:
displayName: dotnet test
inputs:
command: test
includeNuGetOrg: true
projects: Sharpliner.sln
26 changes: 18 additions & 8 deletions eng/Sharpliner.CI/SharplinerPipeline.cs
Expand Up @@ -11,16 +11,12 @@ internal class SharplinerPipeline : AzureDevOpsPipelineDefinition

public override AzureDevOpsPipeline Pipeline => new()
{
Trigger = new DetailedTrigger
Trigger = new Trigger("main")
{
Batch = true,
Branches = new()
{
Include = { "main" }
}
},

Pr = new BranchPrTrigger("main"),
Pr = new PrTrigger("main"),

Stages =
{
Expand All @@ -33,14 +29,18 @@ internal class SharplinerPipeline : AzureDevOpsPipelineDefinition
Pool = new HostedPool("Azure Pipelines", "windows-latest"),
Steps =
{
new AzureDevOpsTask("UseDotNet@2", "Use .NET 5")
// dotnet build fails with .NET 5 SDK and the new() statements
new AzureDevOpsTask("UseDotNet@2", "Install .NET 6 preview 3")
{
Inputs = new TaskInputs
{
{ "packageType", "sdk" },
{ "version", "5.0.102" },
{ "version", "6.0.100-preview.3.21202.5" },
}
},

// Validate we published the YAML
new SharplinerValidateTask("eng/Sharpliner.CI/Sharpliner.CI.csproj", false),

new AzureDevOpsTask("DotNetCoreCLI@2", "dotnet build")
{
Expand All @@ -51,6 +51,16 @@ internal class SharplinerPipeline : AzureDevOpsPipelineDefinition
{ "projects", "Sharpliner.sln" },
}
},

// dotnet test somehow doesn't work with .NET 6 SDK
new AzureDevOpsTask("UseDotNet@2", "Install .NET 5")
{
Inputs = new TaskInputs
{
{ "packageType", "sdk" },
{ "version", "5.0.202" },
}
},

new AzureDevOpsTask("DotNetCoreCLI@2", "dotnet test")
{
Expand Down
14 changes: 13 additions & 1 deletion src/Sharpliner/AzureDevOps/AzureDevOpsPipelineDefinition.cs
@@ -1,3 +1,5 @@
using System;
using System.Text.RegularExpressions;
using Sharpliner.Definition;

namespace Sharpliner.AzureDevOps
Expand All @@ -21,6 +23,16 @@ protected static ConditionedDefinition<T> Template<T>(string path, TemplateParam
protected static ConditionBuilder<VariableBase> If => new();
protected static ConditionBuilder<T> If_<T>() => new();

public override string Serialize() => SharplinerSerializer.Serialize(Pipeline);
public override string Serialize()
{
var yaml = SharplinerSerializer.Serialize(Pipeline);

// Add empty new lines to make text more readable
yaml = Regex.Replace(yaml, "((\r?\n)[a-zA-Z]+:)", Environment.NewLine + "$1");
yaml = Regex.Replace(yaml, "((\r?\n) - task: )", Environment.NewLine + "$1");
yaml = Regex.Replace(yaml, "((\r?\n) {0,8}- \\${{ if [^\n](\r?\n) - task: )", Environment.NewLine + "$1");

return yaml;
}
}
}
21 changes: 14 additions & 7 deletions src/Sharpliner/AzureDevOps/PrTrigger.cs
@@ -1,15 +1,10 @@
using System.ComponentModel;
using System.Linq;

namespace Sharpliner.AzureDevOps
{
// https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=azure-devops&tabs=schema%2Cparameter-schema#pr-trigger
public abstract record PrTrigger;

public record NoPrTrigger : PrTrigger;

public record BranchPrTrigger(params string[] Branches) : PrTrigger;

public record DetailedPrTrigger : PrTrigger
public record PrTrigger
{
/// <summary>
/// Indicates whether additional pushes to a PR should cancel in-progress runs for the same PR.
Expand All @@ -28,5 +23,17 @@ public record DetailedPrTrigger : PrTrigger
public InclusionRule? Branches { get; init; } = null;

public InclusionRule? Paths { get; init; } = null;

public PrTrigger()
{
}

public PrTrigger(params string[] branches)
{
Branches = new InclusionRule
{
Include = branches.ToList()
};
}
}
}
4 changes: 2 additions & 2 deletions src/Sharpliner/AzureDevOps/Tasks/PowerShellTask.cs
Expand Up @@ -62,10 +62,10 @@ public record InlinePowerShellTask : PowerShellTask
[YamlMember(Order = 2)]
public string TargetType => "filepath";

public InlinePowerShellTask(string displayName, string contents)
public InlinePowerShellTask(string displayName, params string[] scriptLines)
: base(displayName)
{
Contents = contents ?? throw new ArgumentNullException(nameof(contents));
Contents = string.Join("\n", scriptLines);
}
}

Expand Down
72 changes: 72 additions & 0 deletions src/Sharpliner/AzureDevOps/Tasks/SharplinerValidateTask.cs
@@ -0,0 +1,72 @@
using System;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;

namespace Sharpliner.AzureDevOps.Tasks
{
/// <summary>
/// This task verifies that you didn't forget to check in your YAML pipeline changes.
/// </summary>
public record SharplinerValidateTask : Step, IYamlConvertible
{
private readonly string _pipelineProject;
private readonly bool _isPosix;

/// <summary>
/// This task verifies that you didn't forget to check in your YAML pipeline changes.
/// </summary>
/// <param name="pipelineProject">Path to the .csproj where pipelines are defined</param>
/// <param name="isPosix">True for bash, false for PowerShell (based on OS)</param>
public SharplinerValidateTask(string pipelineProject, bool isPosix, string displayName = "Validate YAML is published")
: base(displayName)
{
if (string.IsNullOrEmpty(pipelineProject))
{
throw new ArgumentException($"'{nameof(pipelineProject)}' cannot be null or empty.", nameof(pipelineProject));
}

_pipelineProject = pipelineProject;
_isPosix = isPosix;
}

private string GetValidationScript() => string.Join(
"\n",
_isPosix
? new[]
{
$"dotnet build \"{_pipelineProject}\"",
"if [[ `git status --porcelain | grep -i '.ya\\?ml$'` ]]; then",
$" echo 'Please rebuild {_pipelineProject} locally and commit the YAML changes' 1>&2",
" exit 1",
"else",
" echo 'Check succeeded - no YAML changes needed'",
" exit 0",
"fi",
}
: new[]
{
$"dotnet build \"{_pipelineProject}\"",
"$results = Invoke-Expression \"git status --porcelain\" | Select-String -Pattern \"\\.ya?ml$\"",
"if (!$results) {",
" Write-Host 'Check succeeded - no YAML changes needed'",
" exit 0",
"} else {",
$" Write-Error 'Please rebuild {_pipelineProject} locally and commit the YAML changes'",
" exit 1",
"}",
});

public void Read(IParser parser, Type expectedType, ObjectDeserializer nestedObjectDeserializer) => throw new NotImplementedException();

public void Write(IEmitter emitter, ObjectSerializer nestedObjectSerializer)
{
emitter.Emit(new MappingStart());
emitter.Emit(new Scalar(_isPosix ? "bash" : "powershell"));
emitter.Emit(new Scalar(GetValidationScript()));
emitter.Emit(new Scalar("displayName"));
emitter.Emit(new Scalar(DisplayName));
emitter.Emit(new MappingEnd());
}
}
}
24 changes: 16 additions & 8 deletions src/Sharpliner/AzureDevOps/Trigger.cs
@@ -1,13 +1,9 @@
namespace Sharpliner.AzureDevOps
using System.Linq;

namespace Sharpliner.AzureDevOps
{
// https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=azure-devops&tabs=schema%2Cparameter-schema#triggers
public abstract record Trigger;

public record NoTrigger : Trigger;

public record BranchTrigger(params string[] BranchNames) : Trigger;

public record DetailedTrigger : Trigger
public record Trigger
{
/// <summary>
/// Batch changes if true; start a new build for every push if false (default).
Expand All @@ -19,5 +15,17 @@ public record DetailedTrigger : Trigger
public InclusionRule? Tags { get; init; } = null;

public InclusionRule? Paths { get; init; } = null;

public Trigger()
{
}

public Trigger(params string[] branches)
{
Branches = new InclusionRule
{
Include = branches.ToList()
};
}
}
}
4 changes: 2 additions & 2 deletions tests/Sharpliner.Tests/AzureDevOps/MockPipeline.cs
Expand Up @@ -13,7 +13,7 @@ internal class MockPipeline : AzureDevOpsPipelineDefinition
{
Name = "$(Date:yyyMMdd).$(Rev:rr)",

Trigger = new DetailedTrigger
Trigger = new Trigger
{
Batch = false,
Branches = new()
Expand All @@ -26,7 +26,7 @@ internal class MockPipeline : AzureDevOpsPipelineDefinition
}
},

Pr = new BranchPrTrigger("main", "release/*"),
Pr = new PrTrigger("main", "release/*"),

Variables =
{
Expand Down
4 changes: 2 additions & 2 deletions tests/Sharpliner.Tests/AzureDevOps/XHarnessPipeline.cs
Expand Up @@ -20,7 +20,7 @@ internal class XHarnessPipeline : AzureDevOpsPipelineDefinition
Variable("Build.Repository.Clean", true),
},

Trigger = new DetailedTrigger
Trigger = new Trigger
{
Batch = true,
Branches = new()
Expand All @@ -29,7 +29,7 @@ internal class XHarnessPipeline : AzureDevOpsPipelineDefinition
}
},

Pr = new DetailedPrTrigger()
Pr = new PrTrigger()
{
Branches = new()
{
Expand Down

0 comments on commit 19b1d03

Please sign in to comment.