mirror of
https://github.com/game-ci/unity-builder.git
synced 2026-06-01 22:36:15 -07:00
* Enable noImplicitAny Add types to all implicit any variables Bump target to ES2020 for recent language features (optional chaining) Code cleanup Add debug configuration for vscode Remove autorun flag from jest to remove warning Bump packages to fix dependency version mismatch warning Changed @arkweid/lefthook to @evilmartians/lefthook as @arkweid/lefthook has been deprecated in favor of @evilmartians/lefthook Added concurrency groups to integrity check and build workflows. New commits to branches will cancel superseded runs on the same branch/pr Update imports to not use require syntax Use node packages (ie node:fs rather than fs) AndroidVersionCode is now a string rather than a number as it gets converted to a string when passed out of the system Reduce timeout for windows builds Remove 2020.1.17f1 from windows builds due to repeated license activation errors Update naming scheme of workflows for consistency Update build names so target platform and unity version aren't cut off by github actions UI * Add exclude to test matrix for 2022.2 on android until Unity bug is fixed --------- Co-authored-by: AndrewKahr <AndrewKahr@users.noreply.github.com>
277 lines
7.4 KiB
TypeScript
277 lines
7.4 KiB
TypeScript
import * as core from '@actions/core';
|
|
import NotImplementedException from './error/not-implemented-exception';
|
|
import ValidationError from './error/validation-error';
|
|
import Input from './input';
|
|
import System from './system';
|
|
|
|
export default class Versioning {
|
|
static get strategies() {
|
|
return { None: 'None', Semantic: 'Semantic', Tag: 'Tag', Custom: 'Custom' };
|
|
}
|
|
|
|
static get grepCompatibleInputVersionRegex() {
|
|
return '^v?([0-9]+\\.)*[0-9]+.*';
|
|
}
|
|
|
|
/**
|
|
* Get the branch name of the (related) branch
|
|
*/
|
|
static get branch() {
|
|
return this.headRef || this.ref?.slice(11);
|
|
}
|
|
|
|
/**
|
|
* For pull requests we can reliably use GITHUB_HEAD_REF
|
|
*/
|
|
static get headRef() {
|
|
return process.env.GITHUB_HEAD_REF;
|
|
}
|
|
|
|
/**
|
|
* For branches GITHUB_REF will have format `refs/heads/feature-branch-1`
|
|
*/
|
|
static get ref() {
|
|
return process.env.GITHUB_REF;
|
|
}
|
|
|
|
/**
|
|
* The commit SHA that triggered the workflow run.
|
|
*/
|
|
static get sha() {
|
|
return process.env.GITHUB_SHA;
|
|
}
|
|
|
|
/**
|
|
* Maximum number of lines to print when logging the git diff
|
|
*/
|
|
static get maxDiffLines() {
|
|
return 60;
|
|
}
|
|
|
|
/**
|
|
* Log up to maxDiffLines of the git diff.
|
|
*/
|
|
static async logDiff() {
|
|
const diffCommand = `git --no-pager diff | head -n ${this.maxDiffLines.toString()}`;
|
|
await System.run(
|
|
'sh',
|
|
undefined,
|
|
{
|
|
input: Buffer.from(diffCommand),
|
|
silent: true,
|
|
},
|
|
false,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Regex to parse version description into separate fields
|
|
*/
|
|
static get descriptionRegexes(): RegExp[] {
|
|
return [
|
|
/^v?([\d.]+)-(\d+)-g(\w+)-?(\w+)*/g,
|
|
/^v?([\d.]+-\w+)-(\d+)-g(\w+)-?(\w+)*/g,
|
|
/^v?([\d.]+-\w+\.\d+)-(\d+)-g(\w+)-?(\w+)*/g,
|
|
];
|
|
}
|
|
|
|
static async determineBuildVersion(strategy: string, inputVersion: string): Promise<string> {
|
|
// Validate input
|
|
if (!Object.hasOwnProperty.call(this.strategies, strategy)) {
|
|
throw new ValidationError(`Versioning strategy should be one of ${Object.values(this.strategies).join(', ')}.`);
|
|
}
|
|
|
|
switch (strategy) {
|
|
case this.strategies.None:
|
|
return 'none';
|
|
case this.strategies.Custom:
|
|
return inputVersion;
|
|
case this.strategies.Semantic:
|
|
return await this.generateSemanticVersion();
|
|
case this.strategies.Tag:
|
|
return await this.generateTagVersion();
|
|
default:
|
|
throw new NotImplementedException(`Strategy ${strategy} is not implemented.`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Automatically generates a version based on SemVer out of the box.
|
|
*
|
|
* The version works as follows: `<major>.<minor>.<patch>` for example `0.1.2`.
|
|
*
|
|
* The latest tag dictates `<major>.<minor>`
|
|
* The number of commits since that tag dictates`<patch>`.
|
|
*
|
|
* @See: https://semver.org/
|
|
*/
|
|
static async generateSemanticVersion() {
|
|
if (await this.isShallow()) {
|
|
await this.fetch();
|
|
}
|
|
|
|
await this.logDiff();
|
|
|
|
if ((await this.isDirty()) && !Input.allowDirtyBuild) {
|
|
throw new Error('Branch is dirty. Refusing to base semantic version on uncommitted changes');
|
|
}
|
|
|
|
if (!(await this.hasAnyVersionTags())) {
|
|
const version = `0.0.${await this.getTotalNumberOfCommits()}`;
|
|
core.info(`Generated version ${version} (no version tags found).`);
|
|
|
|
return version;
|
|
}
|
|
|
|
const versionDescriptor = await this.parseSemanticVersion();
|
|
if (versionDescriptor) {
|
|
const { tag, commits, hash } = versionDescriptor;
|
|
|
|
// Ensure 3 digits (commits should always be patch level)
|
|
const [major, minor, patch] = `${tag}.${commits}`.split('.');
|
|
const threeDigitVersion = /^\d+$/.test(patch) ? `${major}.${minor}.${patch}` : `${major}.0.${minor}`;
|
|
|
|
core.info(`Found semantic version ${threeDigitVersion} for ${this.branch}@${hash}`);
|
|
|
|
return `${threeDigitVersion}`;
|
|
}
|
|
|
|
const version = `0.0.${await this.getTotalNumberOfCommits()}`;
|
|
core.info(`Generated version ${version} (semantic version couldn't be determined).`);
|
|
|
|
return version;
|
|
}
|
|
|
|
/**
|
|
* Generate the proper version for unity based on an existing tag.
|
|
*/
|
|
static async generateTagVersion() {
|
|
let tag = await this.getTag();
|
|
|
|
if (tag.charAt(0) === 'v') {
|
|
tag = tag.slice(1);
|
|
}
|
|
|
|
return tag;
|
|
}
|
|
|
|
/**
|
|
* Parses the versionDescription into their named parts.
|
|
*/
|
|
static async parseSemanticVersion() {
|
|
const description = await this.getVersionDescription();
|
|
for (const descriptionRegex of Versioning.descriptionRegexes) {
|
|
try {
|
|
const [match, tag, commits, hash] = descriptionRegex.exec(description) as RegExpExecArray;
|
|
|
|
return {
|
|
match,
|
|
tag,
|
|
commits,
|
|
hash,
|
|
};
|
|
} catch {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
core.warning(`Failed to parse git describe output or version can not be determined through: "${description}".`);
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Returns whether the repository is shallow.
|
|
*/
|
|
static async isShallow() {
|
|
const output = await this.git(['rev-parse', '--is-shallow-repository']);
|
|
|
|
return output !== 'false\n';
|
|
}
|
|
|
|
/**
|
|
* Retrieves refs from the configured remote.
|
|
*
|
|
* Fetch unshallow for incomplete repository, but fall back to normal fetch.
|
|
*
|
|
* Note: `--all` should not be used, and would break fetching for push event.
|
|
*/
|
|
static async fetch() {
|
|
try {
|
|
await this.git(['fetch', '--unshallow']);
|
|
} catch (error) {
|
|
core.warning(`Fetch --unshallow caught: ${error}`);
|
|
await this.git(['fetch']);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieves information about the branch.
|
|
*
|
|
* Format: `v0.12-24-gd2198ab`
|
|
*
|
|
* In this format v0.12 is the latest tag, 24 are the number of commits since, and gd2198ab
|
|
* identifies the current commit.
|
|
*/
|
|
static async getVersionDescription() {
|
|
return this.git(['describe', '--long', '--tags', '--always', this.sha!]);
|
|
}
|
|
|
|
/**
|
|
* Returns whether there are uncommitted changes that are not ignored.
|
|
*/
|
|
static async isDirty() {
|
|
const output = await this.git(['status', '--porcelain']);
|
|
const isDirty = output !== '';
|
|
|
|
if (isDirty) {
|
|
core.warning('Changes were made to the following files and folders:\n');
|
|
core.warning(output);
|
|
}
|
|
|
|
return isDirty;
|
|
}
|
|
|
|
/**
|
|
* Get the tag if there is one pointing at HEAD
|
|
*/
|
|
static async getTag() {
|
|
return (await this.git(['tag', '--points-at', 'HEAD'])).trim();
|
|
}
|
|
|
|
/**
|
|
* Whether the current tree has any version tags yet.
|
|
*
|
|
* Note: Currently this is run in all OSes, so the syntax must be cross-platform.
|
|
*/
|
|
static async hasAnyVersionTags() {
|
|
const numberOfTagsAsString = await System.run('sh', undefined, {
|
|
input: Buffer.from(`git tag --list --merged HEAD | grep -E '${this.grepCompatibleInputVersionRegex}' | wc -l`),
|
|
cwd: Input.projectPath,
|
|
silent: false,
|
|
});
|
|
|
|
const numberOfTags = Number.parseInt(numberOfTagsAsString, 10);
|
|
|
|
return numberOfTags !== 0;
|
|
}
|
|
|
|
/**
|
|
* 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() {
|
|
const numberOfCommitsAsString = await this.git(['rev-list', '--count', this.sha!]);
|
|
|
|
return Number.parseInt(numberOfCommitsAsString, 10);
|
|
}
|
|
|
|
/**
|
|
* Run git in the specified project path
|
|
*/
|
|
static async git(arguments_: string[], options = {}) {
|
|
return System.run('git', arguments_, { cwd: Input.projectPath, ...options }, false);
|
|
}
|
|
}
|