Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add build agent for BitBucket Pipelines #3069

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions docs/input/docs/reference/build-servers/bitbucket-pipelines.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
---
Order: 35
Title: BitBucket Pipelines
Description: Details on the Atlassian BitBucket Pipelines support in GitVersion
---

## Basic Usage

To use GitVersion with Atlassian BitBucket Pipelines, you will need to install and run the GitVersion CLI tool
in your build step.

## Executing GitVersion

### Using the GitVersion CLI tool

An example pipeline is shown below:

```yml
image: mcr.microsoft.com/dotnet/sdk:6.0

clone:
depth: full

pipelines:
default:
- step:
name: Version and build
script:
- export PATH="$PATH:/root/.dotnet/tools"
- dotnet tool install --global GitVersion.Tool --version 5.*
- dotnet-gitversion /buildserver
- source gitversion.properties
- echo Building with semver $GITVERSION_FULLSEMVER
- dotnet build
```

:::{.alert .alert-danger}
**Important**

You must set the `clone:depth` setting as shown above; without it, BitBucket Pipelines will perform a shallow clone, which will
cause GitVersion will display an error message.
davidkeaveny marked this conversation as resolved.
Show resolved Hide resolved
:::

When the action `dotnet-gitversion /buildserver` is executed, it will detect that it is running in BitBucket Pipelines by the presence of
the `BITBUCKET_WORKSPACE` environment variable, which is set by the BitBucket Pipelines engine. It will generate a text file named `gitversion.properties`
which contains all the output of the GitVersion tool, exported as individual environment variables prefixed with `GITVERSION_`.
These environment variables can then be imported back into the build step using the `source gitversion.properties` action.

If you want to share the text file across multiple build steps, then you will need to save it as an artifact. A more complex example pipeline
is shown below:

```yml
image: mcr.microsoft.com/dotnet/sdk:6.0

clone:
depth: full

pipelines:
default:
- step:
name: Version
script:
- export PATH="$PATH:/root/.dotnet/tools"
- dotnet tool install --global GitVersion.Tool --version 5.*
- dotnet-gitversion /buildserver
artifacts:
- gitversion.properties
- step:
name: Build
script:
- source gitversion.properties
- echo Building with semver $GITVERSION_FULLSEMVER
- dotnet build
```

[Variables and Secrets](https://support.atlassian.com/bitbucket-cloud/docs/variables-and-secrets/)
[Clone Options](https://bitbucket.org/blog/support-for-more-clone-options-at-the-step-level)
13 changes: 13 additions & 0 deletions src/GitVersion.App.Tests/PullRequestInBuildAgentTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,19 @@ public async Task VerifyTravisCIPullRequest(string pullRequestRef)
await VerifyPullRequestVersionIsCalculatedProperly(pullRequestRef, env);
}


[TestCaseSource(nameof(PrMergeRefs))]
public async Task VerifyBitBucketPipelinesPullRequest(string pullRequestRef)
{

var env = new Dictionary<string, string>
{
{ BitBucketPipelines.EnvironmentVariableName, "MyWorkspace" },
{ BitBucketPipelines.PullRequestEnvironmentVariableName, pullRequestRef }
};
await VerifyPullRequestVersionIsCalculatedProperly(pullRequestRef, env);
}

private static async Task VerifyPullRequestVersionIsCalculatedProperly(string pullRequestRef, Dictionary<string, string> env)
{
using var fixture = new EmptyRepositoryFixture();
Expand Down
173 changes: 173 additions & 0 deletions src/GitVersion.Core.Tests/BuildAgents/BitBucketPipelinesTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
using GitVersion.BuildAgents;
using GitVersion.Core.Tests.Helpers;
using GitVersion.Helpers;
using GitVersion.VersionCalculation;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using Shouldly;

namespace GitVersion.Core.Tests.BuildAgents;

[TestFixture]
public class BitBucketPipelinesTests : TestBase
{
private IEnvironment environment;
private BitBucketPipelines buildServer;
private IServiceProvider sp;

[SetUp]
public void SetEnvironmentVariableForTest()
{
this.sp = ConfigureServices(services => services.AddSingleton<BitBucketPipelines>());
this.environment = sp.GetRequiredService<IEnvironment>();
this.buildServer = sp.GetRequiredService<BitBucketPipelines>();

this.environment.SetEnvironmentVariable(BitBucketPipelines.EnvironmentVariableName, "MyWorkspace");
}


[Test]
public void CanNotApplyToCurrentContextWhenEnvironmentVariableNotSet()
{
// Arrange
this.environment.SetEnvironmentVariable(BitBucketPipelines.EnvironmentVariableName, "");

// Act
var result = this.buildServer.CanApplyToCurrentContext();

// Assert
result.ShouldBeFalse();
}

[Test]
public void CalculateVersionOnMainBranch()
{
// Arrange
this.environment.SetEnvironmentVariable(BitBucketPipelines.BranchEnvironmentVariableName, "refs/heads/main");

var vars = new TestableVersionVariables(fullSemVer: "1.2.3");
var vsVersion = this.buildServer.GenerateSetVersionMessage(vars);

vsVersion.ShouldBe("1.2.3");
}

[Test]
public void CalculateVersionOnDevelopBranch()
{
// Arrange
this.environment.SetEnvironmentVariable(BitBucketPipelines.BranchEnvironmentVariableName, "refs/heads/develop");

var vars = new TestableVersionVariables(fullSemVer: "1.2.3-unstable.4");
var vsVersion = this.buildServer.GenerateSetVersionMessage(vars);

vsVersion.ShouldBe("1.2.3-unstable.4");
}

[Test]
public void CalculateVersionOnFeatureBranch()
{
// Arrange
this.environment.SetEnvironmentVariable(BitBucketPipelines.BranchEnvironmentVariableName, "refs/heads/feature/my-work");

var vars = new TestableVersionVariables(fullSemVer: "1.2.3-beta.4");
var vsVersion = this.buildServer.GenerateSetVersionMessage(vars);

vsVersion.ShouldBe("1.2.3-beta.4");
}

[Test]
public void GetCurrentBranchShouldHandleBranches()
{
// Arrange
this.environment.SetEnvironmentVariable(BitBucketPipelines.BranchEnvironmentVariableName, "refs/heads/feature/my-work");

// Act
var result = this.buildServer.GetCurrentBranch(false);

// Assert
result.ShouldBe($"refs/heads/feature/my-work");
}

[Test]
public void GetCurrentBranchShouldHandleTags()
{
// Arrange
this.environment.SetEnvironmentVariable(BitBucketPipelines.BranchEnvironmentVariableName, null);
this.environment.SetEnvironmentVariable(BitBucketPipelines.TagEnvironmentVariableName, "refs/heads/tags/1.2.3");

// Act
var result = this.buildServer.GetCurrentBranch(false);

// Assert
result.ShouldBeNull();
}

[Test]
public void GetCurrentBranchShouldHandlePullRequests()
{
// Arrange
this.environment.SetEnvironmentVariable(BitBucketPipelines.BranchEnvironmentVariableName, null);
this.environment.SetEnvironmentVariable(BitBucketPipelines.TagEnvironmentVariableName, null);
this.environment.SetEnvironmentVariable(BitBucketPipelines.PullRequestEnvironmentVariableName, "refs/pull/1/merge");

// Act
var result = this.buildServer.GetCurrentBranch(false);

// Assert
result.ShouldBeNull();
}


[Test]
public void WriteAllVariablesToTheTextWriter()
{
var assemblyLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
assemblyLocation.ShouldNotBeNull();
var f = PathHelper.Combine(assemblyLocation, "gitversion.properties");

try
{
AssertVariablesAreWrittenToFile(f);
}
finally
{
File.Delete(f);
}
}

private void AssertVariablesAreWrittenToFile(string file)
{
var writes = new List<string?>();
var semanticVersion = new SemanticVersion
{
Major = 1,
Minor = 2,
Patch = 3,
PreReleaseTag = "beta1",
BuildMetaData = "5"
};

semanticVersion.BuildMetaData.CommitDate = new DateTimeOffset(2022, 4, 6, 16, 10, 59, TimeSpan.FromHours(10));
semanticVersion.BuildMetaData.Sha = "f28807e615e9f06aec8a33c87780374e0c1f6fb8";

var config = new TestEffectiveConfiguration();
var variableProvider = this.sp.GetRequiredService<IVariableProvider>();

var variables = variableProvider.GetVariablesFor(semanticVersion, config, false);

this.buildServer.WithPropertyFile(file);

this.buildServer.WriteIntegration(writes.Add, variables);

writes[1].ShouldBe("1.2.3-beta.1+5");

File.Exists(file).ShouldBe(true);

var props = File.ReadAllText(file);

props.ShouldContain("export GITVERSION_MAJOR=1");
props.ShouldContain("export GITVERSION_MINOR=2");
props.ShouldContain("export GITVERSION_SHA=f28807e615e9f06aec8a33c87780374e0c1f6fb8");
props.ShouldContain("export GITVERSION_COMMITDATE=2022-04-06");
}
}
65 changes: 65 additions & 0 deletions src/GitVersion.Core/BuildAgents/BitBucketPipelines.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using GitVersion.Logging;
using GitVersion.OutputVariables;

namespace GitVersion.BuildAgents;

public class BitBucketPipelines : BuildAgentBase
{
public const string EnvironmentVariableName = "BITBUCKET_WORKSPACE";
public const string BranchEnvironmentVariableName = "BITBUCKET_BRANCH";
public const string TagEnvironmentVariableName = "BITBUCKET_TAG";
public const string PullRequestEnvironmentVariableName = "BITBUCKET_PR_ID";
private string? file;

public BitBucketPipelines(IEnvironment environment, ILog log) : base(environment, log) => WithPropertyFile("gitversion.properties");

protected override string EnvironmentVariable => EnvironmentVariableName;

public override string? GenerateSetVersionMessage(VersionVariables variables) => variables.FullSemVer;

public void WithPropertyFile(string propertiesFileName) => this.file = propertiesFileName;

public override string[] GenerateSetParameterMessage(string name, string value) => new[]
{
$"GITVERSION_{name.ToUpperInvariant()}={value}"
};

public override void WriteIntegration(Action<string?> writer, VersionVariables variables, bool updateBuildNumber = true)
{
if (this.file is null)
return;

base.WriteIntegration(writer, variables, updateBuildNumber);
writer($"Outputting variables to '{this.file}' ... ");
writer("To import the file into your build environment, add the following line to your build step:");
writer($" - source {this.file}");
writer("");
writer("To reuse the file across build steps, add the file as a build artifact:");
writer(" artifacts:");
writer($" - {this.file}");

var exports = variables
.Select(variable => $"export GITVERSION_{variable.Key.ToUpperInvariant()}={variable.Value}")
.ToList();

File.WriteAllLines(this.file, exports);
}

public override string? GetCurrentBranch(bool usingDynamicRepos)
{
var branchName = EvaluateEnvironmentVariable(BranchEnvironmentVariableName);
if (branchName != null && branchName.StartsWith("refs/heads/"))
{
return branchName;
}

return null;
}

private string? EvaluateEnvironmentVariable(string variableName)
{
var branchName = Environment.GetEnvironmentVariable(variableName);
Log.Info("Evaluating environment variable {0} : {1}", variableName, branchName!);
return branchName;
}
}