Compare commits

..

30 Commits
v0.14 ... v1.1

Author SHA1 Message Date
Benoit Dion
3523c6a934 Document additional action inputs
This helps avoid warnings when using previously undocumented inputs
2020-06-25 18:14:32 +02:00
Webber
bf702784d2 cleanup workflow 2020-06-25 00:57:16 +02:00
Benoit Dion
bdc3a88d22 Add input to set version code
Use action input `androidVersionCode` when provided. Generate the androidVersionCode from the version otherwise.
2020-06-25 00:33:25 +02:00
Benoit Dion
401ddcaae0 Add support for android builds
The emoji in the github action name exposed as a env variable through docker was causing a gradle crash 😢.
2020-06-20 10:08:08 +02:00
Benoit Dion
1245bfefc8 Fix fork PR builds
Remove hardcoded reference to the `origin` remote and instead implictly use the current commit or ref
2020-06-20 00:59:05 +02:00
Nathan Leiby
229b0d02f8 Adding logging before updating permissions 2020-06-13 22:48:49 +02:00
Nathan Leiby
c68bdf8177 make StandaloneOSX app runnable 2020-06-13 22:48:49 +02:00
Webber
938926799f Remove WebGL for faster CI 2020-06-13 17:06:03 +02:00
Webber
5efb4868ad Comment out activation 2020-06-13 17:06:03 +02:00
Webber
7749b8862d Add matrix support for multi license 2020-06-13 17:06:03 +02:00
Webber
98a56c4169 Add 2019.3.15 workflow 2020-06-13 17:06:03 +02:00
Webber
720ee0c896 Acquire activation files 2020-06-13 17:06:03 +02:00
Webber
d42c251af3 Snyk badge really almost never works, bye 2020-05-23 02:22:55 +02:00
Webber
bfe6be7ce2 Update readme for v1.0 🎉 2020-05-23 00:25:26 +02:00
Webber
f15f40d265 Use head for tags 2020-05-22 23:01:58 +02:00
Webber
866f364f64 Use ref instead of tag vs branch 2020-05-22 23:01:58 +02:00
Webber
a245f08e75 rename to throwContextualError 2020-05-22 23:01:58 +02:00
Webber
21c211bbdd rebase on master 2020-05-22 23:01:58 +02:00
Webber
3718e05961 Describe errors in System.run 2020-05-22 23:01:58 +02:00
Webber
0159028bb1 Fix missing await 2020-05-22 23:01:58 +02:00
Webber
054c6bfab3 Catch command for in-shell errors 2020-05-22 23:01:58 +02:00
Webber
8c9ff3249e More info if command gives no output, just the exit code. 2020-05-22 23:01:58 +02:00
Webber
7386c669ad Fix no output from errors 2020-05-22 23:01:58 +02:00
Webber
ce865270c4 Use commit-ish for git description 2020-05-22 23:01:58 +02:00
Webber
7e17091251 Split responsibilities between Input and BuildParameters models 2020-05-22 00:55:26 +02:00
Webber
02ff5bbef2 Add documentation and tests for allowDirtyBuild 2020-05-22 00:55:26 +02:00
Webber
8c177b1bad Add flag for allowing dirty branch 2020-05-22 00:55:26 +02:00
Webber
699621ed21 Run versioning commands in projectPath instead 2020-05-21 14:26:37 +02:00
Webber
44bde7feb9 Base number of commits off of the branch on origin 2020-05-02 16:37:24 +02:00
Webber
5328bda08e Base number of commits off of the branch 2020-05-02 16:37:24 +02:00
24 changed files with 622 additions and 212 deletions

View File

@@ -1,11 +1,10 @@
name: Actions 😎 name: Actions
on: on:
pull_request: {} pull_request: {}
push: { branches: [master] } push: { branches: [master] }
env: env:
UNITY_LICENSE: "<?xml version=\"1.0\" encoding=\"UTF-8\"?><root>\n <License id=\"Terms\">\n <MachineBindings>\n <Binding Key=\"1\" Value=\"d39b8e2f4d364b2e98b06afa0c6e08c5\"/>\n <Binding Key=\"2\" Value=\"d39b8e2f4d364b2e98b06afa0c6e08c5\"/>\n </MachineBindings>\n <MachineID Value=\"Xxo1ZKbdPu/IATrc0mPBYANJFF0=\"/>\n <SerialHash Value=\"1efd68fa935192b6090ac03c77d289a9f588c55a\"/>\n <Features>\n <Feature Value=\"33\"/>\n <Feature Value=\"1\"/>\n <Feature Value=\"12\"/>\n <Feature Value=\"2\"/>\n <Feature Value=\"24\"/>\n <Feature Value=\"3\"/>\n <Feature Value=\"36\"/>\n <Feature Value=\"17\"/>\n <Feature Value=\"19\"/>\n <Feature Value=\"62\"/>\n </Features>\n <DeveloperData Value=\"AQAAAEY0LUg2WFMtUE00NS1SM0M4LUUyWlotWkdWOA==\"/>\n <SerialMasked Value=\"F4-H6XS-PM45-R3C8-E2ZZ-XXXX\"/>\n <StartDate Value=\"2018-05-02T00:00:00\"/>\n <UpdateDate Value=\"2019-11-25T18:23:38\"/>\n <InitialActivationDate Value=\"2018-05-02T14:21:28\"/>\n <LicenseVersion Value=\"6.x\"/>\n <ClientProvidedVersion Value=\"2019.2.11f1\"/>\n <AlwaysOnline Value=\"false\"/>\n <Entitlements>\n <Entitlement Ns=\"unity_editor\" Tag=\"UnityPersonal\" Type=\"EDITOR\" ValidTo=\"9999-12-31T00:00:00\"/>\n </Entitlements>\n </License>\n<Signature xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><SignedInfo><CanonicalizationMethod Algorithm=\"http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments\"/><SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#rsa-sha1\"/><Reference URI=\"#Terms\"><Transforms><Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"/></Transforms><DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/><DigestValue>JHdOBFmBNq2H8BrGFzir/StLoYo=</DigestValue></Reference></SignedInfo><SignatureValue>aENLHd37a51RtP2/g7YU0Pexf5mx0/ENXYGtrPzqwZ8NQt2AsSdxGnl0CUB45/GuNXfJVDt2HWot\ncNYZB2OylVBn1WHQbKZlPmm8gEAMz0MYbr4Isb5i5buryBrZlmbEOjnRI+pEg1CBwlgMo6xdtjjE\n/d7cC293QIUO91kdzRXftYou1dNaUyuPL9ZH65vdB2pDXGRNxgUVD+GnnqZA7b5L2HXqNQclcWAK\n5Yd1BeF3VzR1iLw9G/SmH5oOhnpXSmqbL4qk7LVP2/mgXpFk5kP4X8VC3z47obNhBIGq40dwWyEe\nUYk5/nRAOkZawDT+tcu96e06gPC9Cxk5PdbRbA==</SignatureValue></Signature></root>"
CODECOV_TOKEN: '2f2eb890-30e2-4724-83eb-7633832cf0de' CODECOV_TOKEN: '2f2eb890-30e2-4724-83eb-7633832cf0de'
jobs: jobs:
@@ -34,15 +33,23 @@ jobs:
- test-project - test-project
unityVersion: unityVersion:
- 2019.2.11f1 - 2019.2.11f1
# - 2019.3.0f1 # requires different license file/method - 2019.3.15f1
include:
# Please be polite; don't copy my personal licenses.
# These are here because they are needed to allowing pull requests from forks to unity-builder.
# You should be using ${{ secrets.UNITY_LICENSE_2019_3_15 }} here.
- unityVersion: 2019.2.11f1
license: "<?xml version=\"1.0\" encoding=\"UTF-8\"?><root>\n <License id=\"Terms\">\n <MachineBindings>\n <Binding Key=\"1\" Value=\"d39b8e2f4d364b2e98b06afa0c6e08c5\"/>\n <Binding Key=\"2\" Value=\"d39b8e2f4d364b2e98b06afa0c6e08c5\"/>\n </MachineBindings>\n <MachineID Value=\"Xxo1ZKbdPu/IATrc0mPBYANJFF0=\"/>\n <SerialHash Value=\"1efd68fa935192b6090ac03c77d289a9f588c55a\"/>\n <Features>\n <Feature Value=\"33\"/>\n <Feature Value=\"1\"/>\n <Feature Value=\"12\"/>\n <Feature Value=\"2\"/>\n <Feature Value=\"24\"/>\n <Feature Value=\"3\"/>\n <Feature Value=\"36\"/>\n <Feature Value=\"17\"/>\n <Feature Value=\"19\"/>\n <Feature Value=\"62\"/>\n </Features>\n <DeveloperData Value=\"AQAAAEY0LUg2WFMtUE00NS1SM0M4LUUyWlotWkdWOA==\"/>\n <SerialMasked Value=\"F4-H6XS-PM45-R3C8-E2ZZ-XXXX\"/>\n <StartDate Value=\"2018-05-02T00:00:00\"/>\n <UpdateDate Value=\"2019-11-25T18:23:38\"/>\n <InitialActivationDate Value=\"2018-05-02T14:21:28\"/>\n <LicenseVersion Value=\"6.x\"/>\n <ClientProvidedVersion Value=\"2019.2.11f1\"/>\n <AlwaysOnline Value=\"false\"/>\n <Entitlements>\n <Entitlement Ns=\"unity_editor\" Tag=\"UnityPersonal\" Type=\"EDITOR\" ValidTo=\"9999-12-31T00:00:00\"/>\n </Entitlements>\n </License>\n<Signature xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><SignedInfo><CanonicalizationMethod Algorithm=\"http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments\"/><SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#rsa-sha1\"/><Reference URI=\"#Terms\"><Transforms><Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"/></Transforms><DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/><DigestValue>JHdOBFmBNq2H8BrGFzir/StLoYo=</DigestValue></Reference></SignedInfo><SignatureValue>aENLHd37a51RtP2/g7YU0Pexf5mx0/ENXYGtrPzqwZ8NQt2AsSdxGnl0CUB45/GuNXfJVDt2HWot\ncNYZB2OylVBn1WHQbKZlPmm8gEAMz0MYbr4Isb5i5buryBrZlmbEOjnRI+pEg1CBwlgMo6xdtjjE\n/d7cC293QIUO91kdzRXftYou1dNaUyuPL9ZH65vdB2pDXGRNxgUVD+GnnqZA7b5L2HXqNQclcWAK\n5Yd1BeF3VzR1iLw9G/SmH5oOhnpXSmqbL4qk7LVP2/mgXpFk5kP4X8VC3z47obNhBIGq40dwWyEe\nUYk5/nRAOkZawDT+tcu96e06gPC9Cxk5PdbRbA==</SignatureValue></Signature></root>"
- unityVersion: 2019.3.15f1
license: "<?xml version=\"1.0\" encoding=\"UTF-8\"?><root>\n <License id=\"Terms\">\n <MachineBindings>\n <Binding Key=\"1\" Value=\"33bf639e81e54693a8f9bf57c8900e5a\"/>\n <Binding Key=\"2\" Value=\"33bf639e81e54693a8f9bf57c8900e5a\"/>\n </MachineBindings>\n <MachineID Value=\"xWka2iXdDJejhZdi/zU2RUeXUi4=\"/>\n <SerialHash Value=\"1efd68fa935192b6090ac03c77d289a9f588c55a\"/>\n <Features>\n <Feature Value=\"33\"/>\n <Feature Value=\"1\"/>\n <Feature Value=\"12\"/>\n <Feature Value=\"2\"/>\n <Feature Value=\"24\"/>\n <Feature Value=\"3\"/>\n <Feature Value=\"36\"/>\n <Feature Value=\"17\"/>\n <Feature Value=\"19\"/>\n <Feature Value=\"62\"/>\n </Features>\n <DeveloperData Value=\"AQAAAEY0LUg2WFMtUE00NS1SM0M4LUUyWlotWkdWOA==\"/>\n <SerialMasked Value=\"F4-H6XS-PM45-R3C8-E2ZZ-XXXX\"/>\n <StartDate Value=\"2018-05-02T00:00:00\"/>\n <UpdateDate Value=\"2020-06-14T13:49:47\"/>\n <InitialActivationDate Value=\"2018-05-02T14:21:28\"/>\n <LicenseVersion Value=\"6.x\"/>\n <ClientProvidedVersion Value=\"2019.3.15f1\"/>\n <AlwaysOnline Value=\"false\"/>\n <Entitlements>\n <Entitlement Ns=\"unity_editor\" Tag=\"UnityPersonal\" Type=\"EDITOR\" ValidTo=\"9999-12-31T00:00:00\"/>\n </Entitlements>\n </License>\n<Signature xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><SignedInfo><CanonicalizationMethod Algorithm=\"http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments\"/><SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#rsa-sha1\"/><Reference URI=\"#Terms\"><Transforms><Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"/></Transforms><DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/><DigestValue>bpzWx3PZ0lqWDo1m9aLQuZ4cweo=</DigestValue></Reference></SignedInfo><SignatureValue>QcDm4/qAXZuUMQbUVk63vO6u66Bp8PnqqWQcZZOcym/rGUZLj1sr66EquF3X3w1L7aqiwMGtbY2b\nkPttcalFeaBkc5NsJMrexWjuBCxQvhbmVFQnTjvC6vNS+k1wrkz7If1oPkz/XaDtCfUs8oxc9iPe\nPzzUJIVYLZoDtpPq2XbgVn9/TiVb3Zu6ldKgvtNRYUjrB3KywtvL9OcIFll3htRcBZPG43kxryJc\nDD2TL5Nw1JuX6MejBBuYTZsZNpGX9Pjop9+uFUZ4GI9h8a5g6wJUfXzsGw7j4gkvDkC9MvyWiksi\n2hNXw1QNeB6JfQsd4sAuhYh/CqTm2gCz9i9ZpA==</SignatureValue></Signature></root>"
targetPlatform: targetPlatform:
- StandaloneOSX # Build a macOS standalone (Intel 64-bit). - StandaloneOSX # Build a macOS standalone (Intel 64-bit).
- StandaloneWindows # Build a Windows standalone.
- StandaloneWindows64 # Build a Windows 64-bit standalone. - StandaloneWindows64 # Build a Windows 64-bit standalone.
- StandaloneLinux64 # Build a Linux 64-bit standalone. - StandaloneLinux64 # Build a Linux 64-bit standalone.
- iOS # Build an iOS player. - iOS # Build an iOS player.
# - Android # Build an Android .apk standalone app. - Android # Build an Android .apk.
- WebGL # WebGL. # - StandaloneWindows # Build a Windows standalone.
# - WebGL # WebGL.
# - WSAPlayer # Build an Windows Store Apps player. # - WSAPlayer # Build an Windows Store Apps player.
# - PS4 # Build a PS4 Standalone. # - PS4 # Build a PS4 Standalone.
# - XboxOne # Build a Xbox One Standalone. # - XboxOne # Build a Xbox One Standalone.
@@ -61,12 +68,39 @@ jobs:
Library-${{ matrix.projectPath }}- Library-${{ matrix.projectPath }}-
Library- Library-
- uses: ./ - uses: ./
env:
UNITY_LICENSE: ${{ matrix.license }}
with: with:
projectPath: ${{ matrix.projectPath }} projectPath: ${{ matrix.projectPath }}
unityVersion: ${{ matrix.unityVersion }} unityVersion: ${{ matrix.unityVersion }}
targetPlatform: ${{ matrix.targetPlatform }} targetPlatform: ${{ matrix.targetPlatform }}
androidVersionCode: ${{ github.run_number }}
customParameters: -profile SomeProfile -someBoolean -someValue exampleValue customParameters: -profile SomeProfile -someBoolean -someValue exampleValue
- uses: actions/upload-artifact@v1 - uses: actions/upload-artifact@v1
with: with:
name: Build name: Build (${{ matrix.unityVersion }})
path: build path: build
# activation:
# name: Request manual activation file (${{ matrix.unityVersion }}) 🔑
# runs-on: ubuntu-latest
# strategy:
# fail-fast: false
# matrix:
# unityVersion:
# - 2019.2.11f1
# - 2019.3.15f1
#
# steps:
# # Request manual activation file
# - name: Request manual activation file
# id: getManualLicenseFile
# uses: webbertakken/unity-request-manual-activation-file@v1.1
# with:
# unityVersion: ${{ matrix.unityVersion }}
#
# # Upload artifact (Unity_v20XX.X.XXXX.alf)
# - name: Expose as artifact
# uses: actions/upload-artifact@v1
# with:
# name: ${{ steps.getManualLicenseFile.outputs.filePath }}
# path: ${{ steps.getManualLicenseFile.outputs.filePath }}

View File

@@ -1,7 +1,6 @@
# Unity - Builder # Unity - Builder
[![Actions status](https://github.com/webbertakken/unity-builder/workflows/Actions%20%F0%9F%98%8E/badge.svg?event=push&branch=master)](https://github.com/webbertakken/unity-builder/actions?query=branch%3Amaster+event%3Apush+workflow%3A%22Actions+%F0%9F%98%8E%22) [![Actions status](https://github.com/webbertakken/unity-builder/workflows/Actions/badge.svg?event=push&branch=master)](https://github.com/webbertakken/unity-builder/actions?query=branch%3Amaster+event%3Apush+workflow%3A%22Actions)
[![snyk - known vulnerabilities](https://snyk.io/test/github/webbertakken/unity-builder/badge.svg)](https://snyk.io/test/github/webbertakken/unity-builder)
[![lgtm - code quality](https://img.shields.io/lgtm/grade/javascript/g/webbertakken/unity-builder.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/webbertakken/unity-builder/context:javascript) [![lgtm - code quality](https://img.shields.io/lgtm/grade/javascript/g/webbertakken/unity-builder.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/webbertakken/unity-builder/context:javascript)
[![codecov - test coverage](https://codecov.io/gh/webbertakken/unity-builder/branch/master/graph/badge.svg)](https://codecov.io/gh/webbertakken/unity-builder) [![codecov - test coverage](https://codecov.io/gh/webbertakken/unity-builder/branch/master/graph/badge.svg)](https://codecov.io/gh/webbertakken/unity-builder)
@@ -51,7 +50,7 @@ your license file and add it as a secret.
Then, define the build step as follows: Then, define the build step as follows:
```yaml ```yaml
- uses: webbertakken/unity-builder@v0.11 - uses: webbertakken/unity-builder@<version>
env: env:
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }} UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
with: with:
@@ -73,7 +72,7 @@ Instead, three variables will need to be set.
Define the build step as follows: Define the build step as follows:
```yaml ```yaml
- uses: webbertakken/unity-builder@v0.11 - uses: webbertakken/unity-builder@<version>
env: env:
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
@@ -177,7 +176,7 @@ jobs:
restore-keys: | restore-keys: |
Library-${{ matrix.projectPath }}- Library-${{ matrix.projectPath }}-
Library- Library-
- uses: webbertakken/unity-builder@v0.11 - uses: webbertakken/unity-builder@<version>
with: with:
projectPath: ${{ matrix.projectPath }} projectPath: ${{ matrix.projectPath }}
unityVersion: ${{ matrix.unityVersion }} unityVersion: ${{ matrix.unityVersion }}
@@ -255,33 +254,7 @@ _**default:** Built-in script that will run a build out of the box._
#### versioning #### versioning
The versioning strategy to use. Configure a specific versioning strategy
Strategies only work when no custom buildMethod is specified.
_**required:** `false`_
_**default:** `Auto`_
#### _These are the available strategies:_
##### None
No version will be set by Builder.
```yaml
- uses: webbertakken/unity-builder@<version>
with:
versioning: None
```
Note that the version set in the project will be used instead.
##### Semantic (default)
Builder automatically generates a version based on [semantic versioning](https://semver.org/) out of the box.
The version works as follows: `<major>.<minor>.<patch>` for example `0.1.2`.
The latest tag dictates `<major>.<minor>` and the number of commits since that tag is used in `<patch>`.
```yaml ```yaml
- uses: webbertakken/unity-builder@<version> - uses: webbertakken/unity-builder@<version>
@@ -289,40 +262,58 @@ The latest tag dictates `<major>.<minor>` and the number of commits since that t
versioning: Semantic versioning: Semantic
``` ```
This strategy works well for the following reasons: Find the available strategies below:
- All builds have their unique version ##### Semantic
- No version related commits are created
- No knowledge of git or versioning is required
- Developer keeps control over `major` and `minor` versions using tags.
- Zero configuration; It works out of the box
##### Tag Versioning out of the box! **(recommended)**
Uses the tag that points at `HEAD` as the version. > Compatible with **all platforms**.
> Does **not** modify your repository.
> Requires **zero configuration**.
```yaml How it works:
- uses: webbertakken/unity-builder@<version>
with:
versioning: Tag
```
This strategy works well when using a pipeline that specifically runs for tags. > Generates a version based on [semantic versioning](https://semver.org/).
> Follows `<major>.<minor>.<patch>` for example `0.17.2`.
> The latest tag dictates `<major>.<minor>` (defaults to 0.0 for no tag).
> The number of commits (since the last tag, if any) is used for `<patch>`.
The tag must be a version tag. No configuration required.
##### Custom ##### Custom
Allows specifying a custom version in the `version` field. Allows specifying a custom version in the `version` field. **(advanced users)**
> This strategy is useful when your project or pipeline has some kind of orchestration
> that determines the versions.
##### None
No version will be set by Builder. **(not recommended)**
> Not recommended unless you generate a new version in a pre-commit hook. Manually
> setting versions is error-prone.
#### androidVersionCode
Configure the android `versionCode`.
When not specified, the version code is generated from the version using the `major * 1000000 + minor * 1000 + patch` scheme;
#### allowDirtyBuild
Allows the branch of the build to be dirty, and still generate the build.
```yaml ```yaml
- uses: webbertakken/unity-builder@<version> - uses: webbertakken/unity-builder@<version>
with: with:
versioning: Custom allowDirtyBuild: true
version: <some_version>
``` ```
If there is a use case missing from Builder, feel free to create a feature request. Note that it is generally bad practice to modify your branch
in a CI Pipeline. However there are exceptions where this might
be needed. (use with care).
#### customParameters #### customParameters

View File

@@ -26,6 +26,26 @@ inputs:
required: false required: false
default: '' default: ''
description: 'Path to a Namespace.Class.StaticMethod to run to perform the build.' description: 'Path to a Namespace.Class.StaticMethod to run to perform the build.'
versioning:
required: false
default: 'Semantic'
description: 'The versioning scheme to use when building the project'
version:
required: false
default: ''
description: 'The version, when used with the "Custom" versioning scheme'
androidVersionCode:
required: false
default: ''
description: 'The android versionCode'
customParameters:
required: false
default: ''
description: >
Custom parameters to configure the build.
Parameters must start with a hyphen (-) and may be followed by a value (without hyphen).
Parameters without a value will be considered booleans (with a value of true).
outputs: {} outputs: {}
branding: branding:
icon: 'box' icon: 'box'

View File

@@ -28,6 +28,7 @@ namespace UnityBuilderAction
// Set version for this build // Set version for this build
VersionApplicator.SetVersion(options["version"]); VersionApplicator.SetVersion(options["version"]);
VersionApplicator.SetAndroidVersionCode(options["androidVersionCode"]);
// Perform build // Perform build
BuildReport buildReport = BuildPipeline.BuildPlayer(buildOptions); BuildReport buildReport = BuildPipeline.BuildPlayer(buildOptions);

View File

@@ -1,5 +1,4 @@
using System; using System;
using JetBrains.Annotations;
using UnityEditor; using UnityEditor;
namespace UnityBuilderAction.Versioning namespace UnityBuilderAction.Versioning
@@ -11,10 +10,14 @@ namespace UnityBuilderAction.Versioning
if (version == "none") { if (version == "none") {
return; return;
} }
Apply(version); Apply(version);
} }
public static void SetAndroidVersionCode(string androidVersionCode) {
PlayerSettings.Android.bundleVersionCode = Int32.Parse(androidVersionCode);
}
static void Apply(string version) static void Apply(string version)
{ {
PlayerSettings.bundleVersion = version; PlayerSettings.bundleVersion = version;

File diff suppressed because one or more lines are too long

View File

@@ -109,8 +109,8 @@ xvfb-run --auto-servernum --server-args='-screen 0 640x480x24' \
-customBuildTarget "$BUILD_TARGET" \ -customBuildTarget "$BUILD_TARGET" \
-customBuildPath "$CUSTOM_BUILD_PATH" \ -customBuildPath "$CUSTOM_BUILD_PATH" \
-executeMethod "$BUILD_METHOD" \ -executeMethod "$BUILD_METHOD" \
-versioning "$VERSIONING" \
-version "$VERSION" \ -version "$VERSION" \
-androidVersionCode "$ANDROID_VERSION_CODE" \
$CUSTOM_PARAMETERS $CUSTOM_PARAMETERS
# Catch exit code # Catch exit code
@@ -123,6 +123,13 @@ else
echo "Build failed, with exit code $BUILD_EXIT_CODE"; echo "Build failed, with exit code $BUILD_EXIT_CODE";
fi fi
# Add permissions to make app runnable
if [[ "$BUILD_TARGET" == "StandaloneOSX" ]]; then
ADD_PERMISSIONS_PATH=$BUILD_PATH_FULL/StandaloneOSX.app/Contents/MacOS/*
echo "Making the following path executable: $ADD_PERMISSIONS_PATH"
chmod +x $ADD_PERMISSIONS_PATH
fi
# #
# Results # Results
# #

View File

@@ -10,12 +10,14 @@
"prebuild": "yarn", "prebuild": "yarn",
"build": "ncc build src --out action --minify", "build": "ncc build src --out action --minify",
"lint": "prettier --check \"src/**/*.js\" && eslint src", "lint": "prettier --check \"src/**/*.js\" && eslint src",
"format": "prettier --write \"src/**/*.js\"",
"test": "jest" "test": "jest"
}, },
"dependencies": { "dependencies": {
"@actions/core": "^1.2.4", "@actions/core": "^1.2.4",
"@actions/exec": "1.0.4", "@actions/exec": "1.0.4",
"@actions/github": "^2.1.1" "@actions/github": "^2.1.1",
"semver": "^7.3.2"
}, },
"devDependencies": { "devDependencies": {
"@babel/cli": "7.8.4", "@babel/cli": "7.8.4",

View File

@@ -1,4 +1,4 @@
import { Action, BuildParameters, Cache, Docker, Input, ImageTag } from './model'; import { Action, BuildParameters, Cache, Docker, ImageTag } from './model';
const core = require('@actions/core'); const core = require('@actions/core');
@@ -7,7 +7,8 @@ async function action() {
Cache.verify(); Cache.verify();
const { dockerfile, workspace, actionFolder } = Action; const { dockerfile, workspace, actionFolder } = Action;
const buildParameters = BuildParameters.create(await Input.getFromUser());
const buildParameters = await BuildParameters.create();
const baseImage = new ImageTag(buildParameters); const baseImage = new ImageTag(buildParameters);
// Build docker image // Build docker image

View File

@@ -0,0 +1,38 @@
/* eslint-disable unicorn/prevent-abbreviations */
// Import these named export into your test file:
export const mockProjectPath = jest.fn().mockResolvedValue('mockProjectPath');
export const mockIsDirtyAllowed = jest.fn().mockResolvedValue(false);
export const mockBranch = jest.fn().mockResolvedValue('mockBranch');
export const mockHeadRef = jest.fn().mockResolvedValue('mockHeadRef');
export const mockRef = jest.fn().mockResolvedValue('mockRef');
export const mockDetermineVersion = jest.fn().mockResolvedValue('1.2.3');
export const mockGenerateSemanticVersion = jest.fn().mockResolvedValue('2.3.4');
export const mockGenerateTagVersion = jest.fn().mockResolvedValue('1.0');
export const mockParseSemanticVersion = jest.fn().mockResolvedValue({});
export const mockFetch = jest.fn().mockImplementation(() => {});
export const mockGetVersionDescription = jest.fn().mockResolvedValue('1.2-3-g12345678-dirty');
export const mockIsDirty = jest.fn().mockResolvedValue(false);
export const mockGetTag = jest.fn().mockResolvedValue('v1.0');
export const mockHasAnyVersionTags = jest.fn().mockResolvedValue(true);
export const mockGetTotalNumberOfCommits = jest.fn().mockResolvedValue(3);
export const mockGit = jest.fn().mockImplementation(() => {});
export default {
projectPath: mockProjectPath,
isDirtyAllowed: mockIsDirtyAllowed,
branch: mockBranch,
headRef: mockHeadRef,
ref: mockRef,
determineVersion: mockDetermineVersion,
generateSemanticVersion: mockGenerateSemanticVersion,
generateTagVersion: mockGenerateTagVersion,
parseSemanticVersion: mockParseSemanticVersion,
fetch: mockFetch,
getVersionDescription: mockGetVersionDescription,
isDirty: mockIsDirty,
getTag: mockGetTag,
hasAnyVersionTags: mockHasAnyVersionTags,
getTotalNumberOfCommits: mockGetTotalNumberOfCommits,
git: mockGit,
};

View File

@@ -0,0 +1,33 @@
import * as core from '@actions/core';
import * as semver from 'semver';
export default class AndroidVersioning {
static determineVersionCode(version, inputVersionCode) {
if (!inputVersionCode) {
return AndroidVersioning.versionToVersionCode(version);
}
return inputVersionCode;
}
static versionToVersionCode(version) {
const parsedVersion = semver.parse(version);
if (!parsedVersion) {
core.warning(`Could not parse "${version}" to semver, defaulting android version code to 1`);
return 1;
}
// The greatest value Google Plays allows is 2100000000.
// Allow for 3 patch digits, 3 minor digits and 3 major digits.
const versionCode =
parsedVersion.major * 1000000 + parsedVersion.minor * 1000 + parsedVersion.patch;
if (versionCode >= 1000000000) {
throw new Error(
`Generated versionCode ${versionCode} is dangerously close to the maximum allowed number 2100000000. Consider a different versioning scheme to be able to continue updating your application.`,
);
}
core.info(`Using android versionCode ${versionCode}`);
return versionCode;
}
}

View File

@@ -0,0 +1,27 @@
import AndroidVersioning from './android-versioning';
describe('Android Versioning', () => {
describe('versionToVersionCode', () => {
it('defaults to 1 when version is not a valid semver', () => {
expect(AndroidVersioning.versionToVersionCode('abcd')).toBe(1);
});
it('returns a number', () => {
expect(AndroidVersioning.versionToVersionCode('123.456.789')).toBe(123456789);
});
it('throw when generated version code is too large', () => {
expect(() => AndroidVersioning.versionToVersionCode('1234.0.0')).toThrow();
});
});
describe('determineVersionCode', () => {
it('defaults to parsed version', () => {
expect(AndroidVersioning.determineVersionCode('1.2.3', '')).toBe(1002003);
});
it('use specified code', () => {
expect(AndroidVersioning.determineVersionCode('1.2.3', 2)).toBe(2);
});
});
});

View File

@@ -1,28 +1,32 @@
import AndroidVersioning from './android-versioning';
import Input from './input';
import Platform from './platform'; import Platform from './platform';
import Versioning from './versioning';
class BuildParameters { class BuildParameters {
static create(parameters) { static async create() {
const { const buildFile = this.parseBuildFile(Input.buildName, Input.targetPlatform);
version, const buildVersion = await Versioning.determineVersion(
targetPlatform, Input.versioningStrategy,
projectPath, Input.specifiedVersion,
buildName, );
buildsPath,
buildMethod, const androidVersionCode = AndroidVersioning.determineVersionCode(
buildVersion, buildVersion,
customParameters, Input.androidVersionCode,
} = parameters; );
return { return {
version, version: Input.unityVersion,
platform: targetPlatform, platform: Input.targetPlatform,
projectPath, projectPath: Input.projectPath,
buildName, buildName: Input.buildName,
buildPath: `${buildsPath}/${targetPlatform}`, buildPath: `${Input.buildsPath}/${Input.targetPlatform}`,
buildFile: this.parseBuildFile(buildName, targetPlatform), buildFile,
buildMethod, buildMethod: Input.buildMethod,
buildVersion, buildVersion,
customParameters, androidVersionCode,
customParameters: Input.customParameters,
}; };
} }

View File

@@ -1,82 +1,126 @@
import Versioning from './versioning';
import BuildParameters from './build-parameters'; import BuildParameters from './build-parameters';
import Input from './input';
import Platform from './platform'; import Platform from './platform';
const determineVersion = jest
.spyOn(Versioning, 'determineVersion')
.mockImplementation(() => '1.3.37');
afterEach(() => {
jest.clearAllMocks();
});
describe('BuildParameters', () => { describe('BuildParameters', () => {
describe('create', () => { describe('create', () => {
const someParameters = { it('does not throw', async () => {
version: 'someVersion', await expect(BuildParameters.create()).resolves.not.toThrow();
targetPlatform: 'somePlatform',
projectPath: 'path/to/project',
buildName: 'someBuildName',
buildsPath: 'someBuildsPath',
buildMethod: 'Namespace.Class.Method',
customParameters: '-someParam someValue',
};
it('does not throw', () => {
expect(() => BuildParameters.create(someParameters)).not.toThrow();
}); });
it('returns the version', () => { it('determines the version only once', async () => {
expect(BuildParameters.create(someParameters).version).toStrictEqual(someParameters.version); await BuildParameters.create();
expect(determineVersion).toHaveBeenCalledTimes(1);
}); });
it('returns the platform', () => { it('returns the version', async () => {
expect(BuildParameters.create(someParameters).platform).toStrictEqual( const mockValue = 'someVersion';
someParameters.targetPlatform, jest.spyOn(Input, 'unityVersion', 'get').mockReturnValue(mockValue);
await expect(BuildParameters.create()).resolves.toEqual(
expect.objectContaining({ version: mockValue }),
); );
}); });
it('returns the project path', () => { it('returns the android version code with provided input', async () => {
expect(BuildParameters.create(someParameters).projectPath).toStrictEqual( const mockValue = '42';
someParameters.projectPath, jest.spyOn(Input, 'androidVersionCode', 'get').mockReturnValue(mockValue);
await expect(BuildParameters.create()).resolves.toEqual(
expect.objectContaining({ androidVersionCode: mockValue }),
); );
}); });
it('returns the build name', () => { it('returns the android version code from version by default', async () => {
expect(BuildParameters.create(someParameters).buildName).toStrictEqual( const mockValue = '';
someParameters.buildName, jest.spyOn(Input, 'androidVersionCode', 'get').mockReturnValue(mockValue);
await expect(BuildParameters.create()).resolves.toEqual(
expect.objectContaining({ androidVersionCode: 1003037 }),
); );
}); });
it('returns the build path', () => { it('returns the platform', async () => {
expect(BuildParameters.create(someParameters).buildPath).toStrictEqual( const mockValue = 'somePlatform';
`${someParameters.buildsPath}/${someParameters.targetPlatform}`, jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(mockValue);
await expect(BuildParameters.create()).resolves.toEqual(
expect.objectContaining({ platform: mockValue }),
); );
}); });
describe('build file', () => { it('returns the project path', async () => {
it('returns the build file', () => { const mockValue = 'path/to/project';
expect(BuildParameters.create(someParameters).buildFile).toStrictEqual( jest.spyOn(Input, 'projectPath', 'get').mockReturnValue(mockValue);
someParameters.buildName, await expect(BuildParameters.create()).resolves.toEqual(
expect.objectContaining({ projectPath: mockValue }),
);
});
it('returns the build name', async () => {
const mockValue = 'someBuildName';
jest.spyOn(Input, 'buildName', 'get').mockReturnValue(mockValue);
await expect(BuildParameters.create()).resolves.toEqual(
expect.objectContaining({ buildName: mockValue }),
);
});
it('returns the build path', async () => {
const mockPath = 'somePath';
const mockPlatform = 'somePlatform';
const expectedBuildPath = `${mockPath}/${mockPlatform}`;
jest.spyOn(Input, 'buildsPath', 'get').mockReturnValue(mockPath);
jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(mockPlatform);
await expect(BuildParameters.create()).resolves.toEqual(
expect.objectContaining({ buildPath: expectedBuildPath }),
);
});
it('returns the build file', async () => {
const mockValue = 'someBuildName';
jest.spyOn(Input, 'buildName', 'get').mockReturnValue(mockValue);
await expect(BuildParameters.create()).resolves.toEqual(
expect.objectContaining({ buildFile: mockValue }),
);
});
test.each([Platform.types.StandaloneWindows, Platform.types.StandaloneWindows64])(
'appends exe for %s',
async (targetPlatform) => {
jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(targetPlatform);
jest.spyOn(Input, 'buildName', 'get').mockReturnValue(targetPlatform);
await expect(BuildParameters.create()).resolves.toEqual(
expect.objectContaining({ buildFile: `${targetPlatform}.exe` }),
); );
}); },
);
test.each([Platform.types.StandaloneWindows, Platform.types.StandaloneWindows64])( test.each([Platform.types.Android])('appends apk for %s', async (targetPlatform) => {
'appends exe for %s', jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(targetPlatform);
(targetPlatform) => { jest.spyOn(Input, 'buildName', 'get').mockReturnValue(targetPlatform);
expect( await expect(BuildParameters.create()).resolves.toEqual(
BuildParameters.create({ ...someParameters, targetPlatform }).buildFile, expect.objectContaining({ buildFile: `${targetPlatform}.apk` }),
).toStrictEqual(`${someParameters.buildName}.exe`);
},
);
test.each([Platform.types.Android])('appends apk for %s', (targetPlatform) => {
expect(
BuildParameters.create({ ...someParameters, targetPlatform }).buildFile,
).toStrictEqual(`${someParameters.buildName}.apk`);
});
});
it('returns the build method', () => {
expect(BuildParameters.create(someParameters).buildMethod).toStrictEqual(
someParameters.buildMethod,
); );
}); });
it('returns the custom parameters', () => { it('returns the build method', async () => {
expect(BuildParameters.create(someParameters).customParameters).toStrictEqual( const mockValue = 'Namespace.ClassName.BuildMethod';
someParameters.customParameters, jest.spyOn(Input, 'buildMethod', 'get').mockReturnValue(mockValue);
await expect(BuildParameters.create()).resolves.toEqual(
expect.objectContaining({ buildMethod: mockValue }),
);
});
it('returns the custom parameters', async () => {
const mockValue = '-profile SomeProfile -someBoolean -someValue exampleValue';
jest.spyOn(Input, 'customParameters', 'get').mockReturnValue(mockValue);
await expect(BuildParameters.create()).resolves.toEqual(
expect.objectContaining({ customParameters: mockValue }),
); );
}); });
}); });

View File

@@ -1,3 +1,4 @@
import * as core from '@actions/core';
import fs from 'fs'; import fs from 'fs';
import Action from './action'; import Action from './action';
import Project from './project'; import Project from './project';
@@ -14,11 +15,11 @@ class Cache {
return; return;
} }
// eslint-disable-next-line no-console core.warning(`
console.log(`
Library folder does not exist. Library folder does not exist.
Consider setting up caching to speed up your workflow Consider setting up caching to speed up your workflow,
If this is not your first build.`); if this is not your first build.
`);
} }
} }

View File

@@ -29,6 +29,7 @@ class Docker {
buildMethod, buildMethod,
buildVersion, buildVersion,
customParameters, customParameters,
androidVersionCode,
} = parameters; } = parameters;
const command = `docker run \ const command = `docker run \
@@ -47,6 +48,7 @@ class Docker {
--env BUILD_FILE="${buildFile}" \ --env BUILD_FILE="${buildFile}" \
--env BUILD_METHOD="${buildMethod}" \ --env BUILD_METHOD="${buildMethod}" \
--env VERSION="${buildVersion}" \ --env VERSION="${buildVersion}" \
--env ANDROID_VERSION_CODE="${androidVersionCode}" \
--env CUSTOM_PARAMETERS="${customParameters}" \ --env CUSTOM_PARAMETERS="${customParameters}" \
--env HOME=/github/home \ --env HOME=/github/home \
--env GITHUB_REF \ --env GITHUB_REF \

View File

@@ -25,7 +25,7 @@ describe('Docker', () => {
const parameters = { const parameters = {
workspace: Action.rootFolder, workspace: Action.rootFolder,
projectPath: `${Action.rootFolder}/test-project`, projectPath: `${Action.rootFolder}/test-project`,
buildName: 'someBulidName', buildName: 'someBuildName',
buildsPath: 'build', buildsPath: 'build',
method: '', method: '',
}; };

View File

@@ -1,38 +1,58 @@
import Platform from './platform'; import Platform from './platform';
import Versioning from './versioning';
const core = require('@actions/core'); const core = require('@actions/core');
/**
* Input variables specified in workflows using "with" prop.
*
* Note that input is always passed as a string, even booleans.
*/
class Input { class Input {
static async getFromUser() { static get unityVersion() {
// Input variables specified in workflows using "with" prop. return core.getInput('unityVersion');
const version = core.getInput('unityVersion'); }
const targetPlatform = core.getInput('targetPlatform') || Platform.default;
static get targetPlatform() {
return core.getInput('targetPlatform') || Platform.default;
}
static get projectPath() {
const rawProjectPath = core.getInput('projectPath') || '.'; const rawProjectPath = core.getInput('projectPath') || '.';
const buildName = core.getInput('buildName') || targetPlatform; return rawProjectPath.replace(/\/$/, '');
const buildsPath = core.getInput('buildsPath') || 'build'; }
const buildMethod = core.getInput('buildMethod'); // processed in docker file
const versioningStrategy = core.getInput('versioning') || 'Semantic';
const specifiedVersion = core.getInput('version') || '';
const customParameters = core.getInput('customParameters') || '';
// Sanitise input static get buildName() {
const projectPath = rawProjectPath.replace(/\/$/, ''); return core.getInput('buildName') || this.targetPlatform;
}
// Parse input static get buildsPath() {
const buildVersion = await Versioning.determineVersion(versioningStrategy, specifiedVersion); return core.getInput('buildsPath') || 'build';
}
// Return validated input static get buildMethod() {
return { return core.getInput('buildMethod'); // processed in docker file
version, }
targetPlatform,
projectPath, static get versioningStrategy() {
buildName, return core.getInput('versioning') || 'Semantic';
buildsPath, }
buildMethod,
buildVersion, static get specifiedVersion() {
customParameters, return core.getInput('version') || '';
}; }
static get androidVersionCode() {
return core.getInput('androidVersionCode');
}
static get allowDirtyBuild() {
const input = core.getInput('allowDirtyBuild') || 'false';
return input === 'true' ? 'true' : 'false';
}
static get customParameters() {
return core.getInput('customParameters') || '';
} }
} }

View File

@@ -1,27 +1,164 @@
import Input from './input'; import * as core from '@actions/core';
import Versioning from './versioning';
const determineVersion = jest import Input from './input';
.spyOn(Versioning, 'determineVersion') import Platform from './platform';
.mockImplementation(() => '1.3.37');
afterEach(() => { afterEach(() => {
jest.clearAllMocks(); jest.restoreAllMocks();
}); });
describe('Input', () => { describe('Input', () => {
describe('getFromUser', () => { describe('unityVersion', () => {
it('does not throw', async () => { it('returns the default value', () => {
await expect(Input.getFromUser()).resolves.not.toBeNull(); expect(Input.unityVersion).toStrictEqual('');
}); });
it('returns an object', async () => { it('takes input from the users workflow', () => {
await expect(typeof (await Input.getFromUser())).toStrictEqual('object'); const mockValue = '2020.4.99f9';
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
expect(Input.unityVersion).toStrictEqual(mockValue);
expect(spy).toHaveBeenCalledTimes(1);
});
});
describe('targetPlatform', () => {
it('returns the default value', () => {
expect(Input.targetPlatform).toStrictEqual(Platform.default);
}); });
it('calls version generator once', async () => { it('takes input from the users workflow', () => {
await Input.getFromUser(); const mockValue = 'Android';
expect(determineVersion).toHaveBeenCalledTimes(1); const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
expect(Input.targetPlatform).toStrictEqual(mockValue);
expect(spy).toHaveBeenCalledTimes(1);
});
});
describe('projectPath', () => {
it('returns the default value', () => {
expect(Input.projectPath).toStrictEqual('.');
});
it('takes input from the users workflow', () => {
const mockValue = 'customProjectPath';
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
expect(Input.projectPath).toStrictEqual(mockValue);
expect(spy).toHaveBeenCalledTimes(1);
});
});
describe('buildName', () => {
it('returns the default value', () => {
expect(Input.buildName).toStrictEqual(Input.targetPlatform);
});
it('takes input from the users workflow', () => {
const mockValue = 'Build';
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
expect(Input.buildName).toStrictEqual(mockValue);
expect(spy).toHaveBeenCalledTimes(1);
});
it('takes special characters as input', () => {
const mockValue = '1ßúëld2';
jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
expect(Input.buildName).toStrictEqual(mockValue);
});
});
describe('buildsPath', () => {
it('returns the default value', () => {
expect(Input.buildsPath).toStrictEqual('build');
});
it('takes input from the users workflow', () => {
const mockValue = 'customBuildsPath';
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
expect(Input.buildsPath).toStrictEqual(mockValue);
expect(spy).toHaveBeenCalledTimes(1);
});
});
describe('buildMethod', () => {
it('returns the default value', () => {
expect(Input.buildMethod).toStrictEqual('');
});
it('takes input from the users workflow', () => {
const mockValue = 'Namespace.ClassName.Method';
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
expect(Input.buildMethod).toStrictEqual(mockValue);
expect(spy).toHaveBeenCalledTimes(1);
});
});
describe('versioningStrategy', () => {
it('returns the default value', () => {
expect(Input.versioningStrategy).toStrictEqual('Semantic');
});
it('takes input from the users workflow', () => {
const mockValue = 'Anything';
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
expect(Input.versioningStrategy).toStrictEqual(mockValue);
expect(spy).toHaveBeenCalledTimes(1);
});
});
describe('specifiedVersion', () => {
it('returns the default value', () => {
expect(Input.specifiedVersion).toStrictEqual('');
});
it('takes input from the users workflow', () => {
const mockValue = '1.33.7';
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
expect(Input.specifiedVersion).toStrictEqual(mockValue);
expect(spy).toHaveBeenCalledTimes(1);
});
});
describe('androidVersionCode', () => {
it('defaults to null', () => {
expect(Input.androidVersionCode).toBeFalsy();
});
it('takes input from the users workflow', () => {
const mockValue = '42';
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
expect(Input.androidVersionCode).toStrictEqual(mockValue);
expect(spy).toHaveBeenCalledTimes(1);
});
});
describe('allowDirtyBuild', () => {
it('returns the default value', () => {
expect(Input.allowDirtyBuild).toStrictEqual('false');
});
it('returns true when string true is passed', () => {
const spy = jest.spyOn(core, 'getInput').mockReturnValue('true');
expect(Input.allowDirtyBuild).toStrictEqual('true');
expect(spy).toHaveBeenCalledTimes(1);
});
it('returns false when string false is passed', () => {
const spy = jest.spyOn(core, 'getInput').mockReturnValue('false');
expect(Input.allowDirtyBuild).toStrictEqual('false');
expect(spy).toHaveBeenCalledTimes(1);
});
});
describe('customParameters', () => {
it('returns the default value', () => {
expect(Input.customParameters).toStrictEqual('');
});
it('takes input from the users workflow', () => {
const mockValue = '-imAFlag';
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
expect(Input.customParameters).toStrictEqual(mockValue);
expect(spy).toHaveBeenCalledTimes(1);
}); });
}); });
}); });

View File

@@ -1,11 +1,10 @@
import * as core from '@actions/core'; import Input from './input';
import Unity from './unity'; import Unity from './unity';
import Action from './action'; import Action from './action';
class Project { class Project {
static get relativePath() { static get relativePath() {
// Todo - properly use Input for this. const { projectPath } = Input;
const projectPath = core.getInput('projectPath') || '.';
return `${projectPath}`; return `${projectPath}`;
} }

View File

@@ -19,22 +19,40 @@ class System {
}, },
}; };
const exitCode = await exec(command, arguments_, { silent: true, listeners, ...options }); const showOutput = () => {
if (debug !== '') {
core.debug(debug);
}
if (debug !== '') { if (result !== '') {
core.debug(debug); core.info(result);
} }
if (result !== '') { if (error !== '') {
core.info(result); core.warning(error);
} }
};
if (exitCode !== 0) { const throwContextualError = (message) => {
throw new Error(error); let commandAsString = command;
} if (Array.isArray(arguments_)) {
commandAsString += ` ${arguments_.join(' ')}`;
} else if (typeof arguments_ === 'string') {
commandAsString += ` ${arguments_}`;
}
if (error !== '') { throw new Error(`Failed to run "${commandAsString}".\n ${message}`);
core.warning(error); };
try {
const exitCode = await exec(command, arguments_, { silent: true, listeners, ...options });
showOutput();
if (exitCode !== 0) {
throwContextualError(`Command returned non-zero exit code.\nError: ${error}`);
}
} catch (inCommandError) {
showOutput();
throwContextualError(`In-command error caught: ${inCommandError}`);
} }
return result; return result;

View File

@@ -1,9 +1,18 @@
import * as core from '@actions/core'; import * as core from '@actions/core';
import NotImplementedException from './error/not-implemented-exception'; import NotImplementedException from './error/not-implemented-exception';
import ValidationError from './error/validation-error'; import ValidationError from './error/validation-error';
import Input from './input';
import System from './system'; import System from './system';
export default class Versioning { export default class Versioning {
static get projectPath() {
return Input.projectPath;
}
static get isDirtyAllowed() {
return Input.allowDirtyBuild === 'true';
}
static get strategies() { static get strategies() {
return { None: 'None', Semantic: 'Semantic', Tag: 'Tag', Custom: 'Custom' }; return { None: 'None', Semantic: 'Semantic', Tag: 'Tag', Custom: 'Custom' };
} }
@@ -30,6 +39,13 @@ export default class Versioning {
return process.env.GITHUB_REF; return process.env.GITHUB_REF;
} }
/**
* The commit SHA that triggered the workflow run.
*/
static get sha() {
return process.env.GITHUB_SHA;
}
/** /**
* Regex to parse version description into separate fields * Regex to parse version description into separate fields
*/ */
@@ -79,7 +95,7 @@ export default class Versioning {
static async generateSemanticVersion() { static async generateSemanticVersion() {
await this.fetch(); await this.fetch();
if (await this.isDirty()) { if ((await this.isDirty()) && !this.isDirtyAllowed) {
throw new Error('Branch is dirty. Refusing to base semantic version on uncommitted changes'); throw new Error('Branch is dirty. Refusing to base semantic version on uncommitted changes');
} }
@@ -137,10 +153,10 @@ export default class Versioning {
*/ */
static async fetch() { static async fetch() {
try { try {
await System.run('git', ['fetch', '--unshallow']); await this.git(['fetch', '--unshallow']);
} catch (error) { } catch (error) {
core.warning(error); core.warning(`Fetch --unshallow caught: ${error}`);
await System.run('git', ['fetch']); await this.git(['fetch']);
} }
} }
@@ -153,21 +169,14 @@ export default class Versioning {
* identifies the current commit. * identifies the current commit.
*/ */
static async getVersionDescription() { static async getVersionDescription() {
return System.run('git', [ return this.git(['describe', '--long', '--tags', '--always', '--debug', this.sha]);
'describe',
'--long',
'--tags',
'--always',
'--debug',
`origin/${this.branch}`,
]);
} }
/** /**
* Returns whether there are uncommitted changes that are not ignored. * Returns whether there are uncommitted changes that are not ignored.
*/ */
static async isDirty() { static async isDirty() {
const output = await System.run('git', ['status', '--porcelain']); const output = await this.git(['status', '--porcelain']);
return output !== ''; return output !== '';
} }
@@ -176,7 +185,7 @@ export default class Versioning {
* Get the tag if there is one pointing at HEAD * Get the tag if there is one pointing at HEAD
*/ */
static async getTag() { static async getTag() {
return System.run('git', ['tag', '--points-at', 'HEAD']); return this.git(['tag', '--points-at', 'HEAD']);
} }
/** /**
@@ -195,10 +204,19 @@ export default class Versioning {
/** /**
* Get the total number of commits on head. * Get the total number of commits on head.
*
* Note: HEAD should not be used, as it may be detached, resulting in an additional count.
*/ */
static async getTotalNumberOfCommits() { static async getTotalNumberOfCommits() {
const numberOfCommitsAsString = await System.run('git', ['rev-list', '--count', 'HEAD']); const numberOfCommitsAsString = await this.git(['rev-list', '--count', this.sha]);
return Number.parseInt(numberOfCommitsAsString, 10); return Number.parseInt(numberOfCommitsAsString, 10);
} }
/**
* Run git in the specified project path
*/
static async git(arguments_, options = {}) {
return System.run('git', arguments_, { cwd: this.projectPath, ...options });
}
} }

View File

@@ -90,6 +90,16 @@ describe('Versioning', () => {
}); });
}); });
describe('isDirtyAllowed', () => {
it('does not throw', () => {
expect(() => Versioning.isDirtyAllowed).not.toThrow();
});
it('returns false by default', () => {
expect(Versioning.isDirtyAllowed).toStrictEqual(false);
});
});
describe('descriptionRegex', () => { describe('descriptionRegex', () => {
it('is a valid regex', () => { it('is a valid regex', () => {
expect(Versioning.descriptionRegex).toBeInstanceOf(RegExp); expect(Versioning.descriptionRegex).toBeInstanceOf(RegExp);

View File

@@ -5045,7 +5045,7 @@ semver@^6.0.0, semver@^6.1.2, semver@^6.3.0:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
semver@^7.1.3: semver@^7.1.3, semver@^7.3.2:
version "7.3.2" version "7.3.2"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938"
integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==