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

(GH-43) Add Support for running Cake Frosting Projects #46

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
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
36 changes: 36 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,39 @@ jobs:
string-parameter: 'value'
numeric-parameter: 3
boolean-parameter: true
test-with-frosting:
name: Test with Cake Frosting
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [windows-latest, ubuntu-latest]
env:
csproj-directory: integrationtests/frosting
steps:
- name: Get the sources
uses: actions/checkout@v1
- name: Install Node 20
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install the .NET 8 SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: Run a specific Cake Frosting project
uses: ./
with:
csproj-path: ${{ env.csproj-directory}}/Build.csproj
- name: Run a specific Cake Frosting task
uses: ./
with:
csproj-path: ${{ env.csproj-directory}}/Build.csproj
target: Successful-Task
- name: Run with a specific verbosity level
uses: ./
env:
EXPECTED_VERBOSITY: Diagnostic
with:
verbosity: Diagnostic
csproj-path: ${{ env.csproj-directory}}/Build.csproj
target: Test-Verbosity
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,9 @@ typings/

# Secret files
.secrets

# bin folder
bin/

# obj folder
obj/
30 changes: 25 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[![GitHub Marketplace](https://img.shields.io/github/v/release/cake-build/cake-action?label=Marketplace&sort=semver)](https://github.com/marketplace/actions/cake-action) [![GitHub Actions Build](https://github.com/cake-build/cake-action/workflows/Build/badge.svg)](https://github.com/cake-build/cake-action/actions?workflow=Build) [![GitHub Actions Tests](https://github.com/cake-build/cake-action/workflows/Tests/badge.svg)](https://github.com/cake-build/cake-action/actions?workflow=Tests) [![Coveralls](https://coveralls.io/repos/github/cake-build/cake-action/badge.svg?branch=master)](https://coveralls.io/github/cake-build/cake-action?branch=master)

This action allows you to run a Cake script from your GitHub Actions workflow without having to use a [bootstrapper](https://github.com/cake-build/resources).
This action allows you to run a Cake script or Cake Frosting project from your GitHub Actions workflow without having to use a [bootstrapper](https://github.com/cake-build/resources).

## Usage

Expand All @@ -14,7 +14,9 @@ steps:
uses: cake-build/cake-action@v2
```

The Cake action will look for a script named `build.cake` in your repository's root directory and run it for you using the [Cake Tool](https://www.nuget.org/packages/Cake.Tool/). All output from the Cake script will be automatically redirected to the build log for inspection.
The Cake action will look for a script named `build.cake` in your repository's root directory and run it for you using the [Cake Tool](https://www.nuget.org/packages/Cake.Tool/). If you are using a [Cake Frosting](https://cakebuild.net/docs/running-builds/runners/cake-frosting) project, the `csproj-path` parameter must be specified and the `script-path` is ignored.

All output from the Cake script or Cake Frosting project will be automatically redirected to the build log for inspection.

## Inputs

Expand All @@ -30,9 +32,21 @@ steps:
script-path: path/to/script.cake
```

### `csproj-path`

If you are using [Cake Frosting](https://cakebuild.net/docs/running-builds/runners/cake-frosting), you can specify the path with the `csproj-path` [input parameter](https://help.github.com/en/github/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idstepswith):

```yml
steps:
- name: Run Cake Frosting
uses: cake-build/cake-action@v2
with:
csproj-path: ./path/to/build.csproj
```

### `target`

You'll likely want to specify which task to run out of the ones defined in the Cake script. For that, you can use the `target` parameter:
You'll likely want to specify which task to run out of the ones defined in the Cake script or Cake Frosting project. For that, you can use the `target` parameter:

```yml
steps:
Expand Down Expand Up @@ -88,7 +102,11 @@ The arguments are defined in a [multi-line string literal](https://yaml.org/spec

### `cake-version`

By default, the Cake action will run your script using the latest _stable_ version of the [Cake .NET Core Global tool](https://www.nuget.org/packages/Cake.Tool/). However, if for some reason you want to [use a specific version of Cake](https://cakebuild.net/docs/tutorials/pinning-cake-version) (for compatibility with older third-party addins, for example), you can do so by specifying the version number in the `cake-version` parameter:
By default, the Cake action will run your script using the latest _stable_ version of the [Cake .NET Core Global tool](https://www.nuget.org/packages/Cake.Tool/).

_Ignored_ for Cake Frosting Project.

If for some reason you want to [use a specific version of Cake](https://cakebuild.net/docs/tutorials/pinning-cake-version) (for compatibility with older third-party addins, for example), you can do so by specifying the version number in the `cake-version` parameter.:

```yml
steps:
Expand All @@ -112,6 +130,8 @@ steps:

As of [Cake 1.0.0](https://github.com/cake-build/cake/releases/tag/v1.0.0), any [custom modules](https://cakebuild.net/docs/fundamentals/modules) that you reference in your script are [bootstrapped automatically](https://github.com/cake-build/cake/issues/2833) upon running it.

_Ignored_ for Cake Frosting Project.

If you're using an older version of Cake, however, you need to explicitly [bootstrap](https://cakebuild.net/docs/fundamentals/preprocessor-directives#module-directive) them before running the script. The Cake action can take care of this extra step for you by setting the `cake-bootstrap` parameter to `explicit`:

```yml
Expand All @@ -137,7 +157,7 @@ The default value is `auto`, which means that the modules will be automatically

## Cross-platform

Since the [Cake Tool](https://www.nuget.org/packages/Cake.Tool/) is built on .NET Core, the Cake action will run on any of the [virtual environments](https://help.github.com/en/github/automating-your-workflow-with-github-actions/software-in-virtual-environments-for-github-actions) supported by GitHub Actions, namely Linux, Windows and macOS.
Since the [Cake Tool](https://www.nuget.org/packages/Cake.Tool/) and [Cake Frosting](https://www.nuget.org/packages/Cake.Frosting) are built on .NET Core, the Cake action will run on any of the [virtual environments](https://help.github.com/en/github/automating-your-workflow-with-github-actions/software-in-virtual-environments-for-github-actions) supported by GitHub Actions, namely Linux, Windows and macOS.

This allows you to define your build step exactly _once_ and run it on multiple operating systems _in parallel_ by defining a [build matrix](https://help.github.com/en/github/automating-your-workflow-with-github-actions/configuring-a-workflow#configuring-a-build-matrix):

Expand Down
59 changes: 59 additions & 0 deletions __tests__/cake.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import { CakeArgument, CakeSwitch } from '../src/cakeParameter';

const pathToLocalToolsDirectory = path.join('path', 'to', 'tool');
const pathToLocalTool = path.join(pathToLocalToolsDirectory, 'dotnet-cake');
const pathToCsprojFile = path.join('build', 'Build.csproj');
louisfischer marked this conversation as resolved.
Show resolved Hide resolved
const dotnetManifestCake = 'dotnet tool run dotnet-cake';
const dotnetRun = 'dotnet run';

jest.mock('@actions/exec');
jest.mock('@actions/io');
Expand Down Expand Up @@ -190,3 +192,60 @@ describe('When running a script successfully using the tool manifest', () => {
['script.cake', '--switch']);
});
});

describe('When running a Cake Frosting project successfully', () => {
const fakeExec = exec as jest.MockedFunction<typeof exec>;
const fakeToolsDirectory = new ToolsDirectory();

beforeAll(async () => {
fakeExec.mockReturnValue(Promise.resolve(0));
});

test('it should run with the default non-required parameters', async () => {
await cake.runProject(pathToCsprojFile, fakeToolsDirectory);

expect(fakeExec).toHaveBeenCalledWith(
dotnetRun,
[
'--project', pathToCsprojFile,
'--no-launch-profile',
'--verbosity', 'minimal',
'--configuration', 'Release',
'--',
`--paths_tools="${fakeToolsDirectory.path}"`
]);
});

test('it should run with the specified parameters', async () => {
await cake.runProject(
pathToCsprojFile,
fakeToolsDirectory,
new CakeArgument('param', 'arg'),
new CakeSwitch('switch'));

expect(fakeExec).toHaveBeenCalledWith(
dotnetRun,
[
'--project', pathToCsprojFile,
'--no-launch-profile',
'--verbosity', 'minimal',
'--configuration', 'Release',
'--',
`--paths_tools="${fakeToolsDirectory.path}"`,
'--param=arg',
'--switch'
]);
});
});

describe('When failing to run a Cake Frosting Project', () => {
const fakeExec = exec as jest.MockedFunction<typeof exec>;

beforeAll(() => {
fakeExec.mockReturnValue(Promise.resolve(-21));
});

test('it should throw an error containing the exit code', async () => {
await expect(cake.runProject('', new ToolsDirectory())).rejects.toThrow('-21');
});
});
22 changes: 22 additions & 0 deletions __tests__/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,28 @@ describe('When running the action with the script path input argument', () => {
});
});

describe('When running the action with the csproj path input argument', () => {
const fakeGetInputs = action.getInputs as jest.MockedFunction<typeof action.getInputs>;
const fakeRunProject = cake.runProject as jest.MockedFunction<typeof cake.runProject>;

beforeAll(() => {
fakeGetInputs.mockReturnValue({
csprojPath: './path/to/build.csproj',
scriptArguments: []
});
});

test('it should run the Cake Frosting Project', async () => {
await run();
expect(cake.runProject).toHaveBeenCalled();
});

test('it should run the specified Cake Frosting project', async () => {
await run();
expect(fakeRunProject.mock.calls[0][0]).toBe('./path/to/build.csproj');
});
});

describe('When running the action with tool-manifest as the Cake version input argument', () => {
const fakeGetInputs = action.getInputs as jest.MockedFunction<typeof action.getInputs>;
const fakeInstallCakeTool = cakeTool.install as jest.MockedFunction<typeof cakeTool.install>;
Expand Down
5 changes: 4 additions & 1 deletion action.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: 'Cake Action'
description: 'Run a Cake script as part of your build.'
description: 'Run a Cake script or Cake Frosting project as part of your build.'
author: 'Enrico Campidoglio'
branding:
icon: 'box'
Expand All @@ -9,6 +9,9 @@ inputs:
description: 'The path of the Cake script to run.'
required: false
default: 'build.cake'
csproj-path:
description: 'The path of the Cake Frosting Project to run. Takes precedence over script-path'
required: false
target:
description: 'The name of the task to execute. Note that this argument must be supported by the script.'
required: false
Expand Down
36 changes: 31 additions & 5 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3991,6 +3991,7 @@ const input = __importStar(__nccwpck_require__(6747));
function getInputs() {
return {
scriptPath: core.getInput('script-path'),
csprojPath: core.getInput('csproj-path'),
cakeVersion: getCakeVersionInput(),
cakeBootstrap: getCakeBootstrapInput(),
scriptArguments: getScriptInputs()
Expand Down Expand Up @@ -4070,11 +4071,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
});
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.bootstrapScript = exports.runScript = void 0;
exports.bootstrapScript = exports.runProject = exports.runScript = void 0;
const exec_1 = __nccwpck_require__(1514);
const io_1 = __nccwpck_require__(7436);
const dotnetCake = 'dotnet-cake';
const dotnetLocalToolCake = 'dotnet tool run dotnet-cake';
const dotnetRun = 'dotnet run';
function runScript(scriptPath = 'build.cake', cakeToolSettings, ...params) {
return __awaiter(this, void 0, void 0, function* () {
const cakeToolPath = yield resolveCakeToolPath(cakeToolSettings);
Expand All @@ -4086,6 +4088,24 @@ function runScript(scriptPath = 'build.cake', cakeToolSettings, ...params) {
});
}
exports.runScript = runScript;
function runProject(csprojPath, toolsDir, ...params) {
return __awaiter(this, void 0, void 0, function* () {
const cakeParams = formatParameters(params);
const exitCode = yield (0, exec_1.exec)(dotnetRun, [
'--project', csprojPath,
'--no-launch-profile',
'--verbosity', 'minimal',
'--configuration', 'Release',
'--',
`--paths_tools="${toolsDir}"`,
...cakeParams
]);
if (exitCode != 0) {
throw new Error(`Failed to run the csproj. Exit code: ${exitCode}`);
}
});
}
exports.runProject = runProject;
function bootstrapScript(scriptPath = 'build.cake', cakeToolSettings) {
return __awaiter(this, void 0, void 0, function* () {
const cakeToolPath = yield resolveCakeToolPath(cakeToolSettings);
Expand Down Expand Up @@ -4447,18 +4467,24 @@ function run() {
try {
const inputs = action.getInputs();
const scriptPath = inputs.scriptPath;
const csprojPath = inputs.csprojPath;
const version = inputs.cakeVersion;
const bootstrap = inputs.cakeBootstrap;
const toolsDir = new toolsDirectory_1.ToolsDirectory();
toolsDir.create();
const cakeToolSettings = new cakeToolSettings_1.CakeToolSettings(toolsDir, (version === null || version === void 0 ? void 0 : version.version) === 'tool-manifest');
dotnet.disableTelemetry();
dotnet.disableWelcomeMessage();
yield cakeTool.install(toolsDir, version);
if (bootstrap === 'explicit') {
yield cake.bootstrapScript(scriptPath, cakeToolSettings);
if (csprojPath) {
yield cake.runProject(csprojPath, toolsDir, ...inputs.scriptArguments);
}
else {
yield cakeTool.install(toolsDir, version);
if (bootstrap === 'explicit') {
yield cake.bootstrapScript(scriptPath, cakeToolSettings);
}
yield cake.runScript(scriptPath, cakeToolSettings, ...inputs.scriptArguments);
}
yield cake.runScript(scriptPath, cakeToolSettings, ...inputs.scriptArguments);
}
catch (error) {
if ((0, guards_1.isError)(error)) {
Expand Down
10 changes: 10 additions & 0 deletions integrationtests/frosting/Build.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Cake.Frosting" Version="4.0.0" />
</ItemGroup>
</Project>
63 changes: 63 additions & 0 deletions integrationtests/frosting/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using System;
using Cake.Common;
using Cake.Core;
using Cake.Core.Diagnostics;
using Cake.Frosting;

public static class Program
{
public static int Main(string[] args)
{
return new CakeHost()
.UseContext<BuildContext>()
.Run(args);
}
}

public class BuildContext : FrostingContext
{
public BuildContext(ICakeContext context)
: base(context)
{
}
}

[TaskName("Successful-Task")]
public sealed class SuccessfulTask : FrostingTask<BuildContext>
{
public override void Run(BuildContext context)
{
context.Log.Information("✓ Passed");
}
}

[TaskName("Test-Verbosity")]
public sealed class TestVerbosity : FrostingTask<BuildContext>
{
public override void Run(BuildContext context)
{
var hasExpectedVerbosity = Enum.TryParse(
context.EnvironmentVariable("EXPECTED_VERBOSITY"),
ignoreCase: true,
out Verbosity expectedVerbosity);

if (!hasExpectedVerbosity)
{
throw new Exception(
"✕ The EXPECTED_VERBOSITY environment variable is not set or it doesn't contain a verbosity level");
}

var actualVerbosity = context.Log.Verbosity;

if (expectedVerbosity != actualVerbosity)
{
throw new Exception($"✕ Expected verbosity {expectedVerbosity} but got {actualVerbosity}");
}

context.Log.Information("✓ Passed");
}
}

[TaskName("Default")]
[IsDependentOn(typeof(SuccessfulTask))]
public class DefaultTask : FrostingTask;
Loading
Loading