Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
fb8cbcd
unity-cli v4: wrap the official unity cli
StephenHodgson Oct 24, 2026
2d572e6
harden installer, license and ci paths flagged by code scanning
StephenHodgson Oct 24, 2026
f21f7aa
npm audit fix
StephenHodgson Oct 24, 2026
ed59733
override brace-expansion to a patched release
StephenHodgson Oct 24, 2026
2f98167
patch bee_backend without a check-then-use race
StephenHodgson Oct 24, 2026
0d14c2c
patch the single-file licensing client for 4.x and 5.x editors
StephenHodgson Oct 24, 2026
6b61f82
do not forward -quit to unity run, and launch directly when it reject…
StephenHodgson Oct 24, 2026
9a49c9c
patch the licensing client wherever its license version lives
StephenHodgson Oct 24, 2026
0968462
treat already installed modules as success
StephenHodgson Oct 24, 2026
8377433
patch the macos licensing client bundle
StephenHodgson Oct 24, 2026
1615eb0
time out the licensing client and stop background instances before th…
StephenHodgson Oct 25, 2026
2400845
include the macos licensing client executable in the patch scan
StephenHodgson Oct 25, 2026
3c89ac8
settle licensing client commands on exit, not on pipe close
StephenHodgson Oct 25, 2026
3112cb3
redact credentials from licensing client and unity run output
StephenHodgson Oct 25, 2026
e6b9194
patch the macos licensing client's utf-8 license version constant
StephenHodgson Oct 25, 2026
0e01ac0
retry the android sdk install and log licensing client problems on ti…
StephenHodgson Oct 25, 2026
ce56335
download installers to random temp names
StephenHodgson Oct 25, 2026
cf78b43
address copilot review: license status, redaction, latest, -quit, arc…
StephenHodgson Oct 25, 2026
a768490
debug macos activation and print the redacted client log on timeout
StephenHodgson Oct 25, 2026
e125fa0
create the macos license folder up front instead of letting the clien…
StephenHodgson Oct 25, 2026
a0e0c51
link the legacy license file to wherever the client wrote it
StephenHodgson Oct 25, 2026
8f1604c
use the hub-bundled licensing client for 4.x and 5.x editors on macos
StephenHodgson Oct 25, 2026
96a09e6
pick the project template from the editor's template list
StephenHodgson Oct 25, 2026
dc4ac4c
cap job times and settle unity run on exit
StephenHodgson Oct 25, 2026
887306d
sanitize client log lines without a polynomial regex
StephenHodgson Oct 25, 2026
780e79e
clean up editor helpers and background licensing processes the tool s…
StephenHodgson Oct 25, 2026
d6ff8f8
leave the licensing client running between runs, and launch android b…
StephenHodgson Oct 25, 2026
d82778d
list the editor log's error lines after a failed run, outside the log…
StephenHodgson Oct 25, 2026
368d14e
match unity stack frames without a polynomial regex
StephenHodgson Oct 25, 2026
4c634cf
match filename trailers without a backtracking regex
StephenHodgson Oct 25, 2026
a0ef457
return the license on windows rows only, and accept a cached license …
StephenHodgson Oct 25, 2026
e87c057
cache the license file per os for macos and linux rows
StephenHodgson Oct 25, 2026
9eaa534
keep the macos and linux license in repo secrets instead of the actio…
StephenHodgson Oct 25, 2026
da8d53d
fix the security audit findings for v4 and pass the .app bundle to un…
StephenHodgson Oct 26, 2026
b87f316
read the patched client marker without a separate exists check
StephenHodgson Oct 26, 2026
a593471
add the newest unity 6 streams and the unity 7 alpha to the matrix an…
StephenHodgson Oct 26, 2026
0d4a8ee
stop setting UNITY_THISISABUILDMACHINE, which points gradle at unity'…
StephenHodgson Oct 26, 2026
e21abfb
install the latest official cli on first use, restore the upm command…
StephenHodgson Oct 26, 2026
d7c14d5
bound the linux apt dependency install with timeouts and stream its o…
StephenHodgson Oct 26, 2026
c1d67b2
install modules from the project's build profiles, and keep the relea…
StephenHodgson Oct 26, 2026
91e64bb
install the linux editor's required packages before the optional ffmp…
StephenHodgson Oct 26, 2026
0dba5bf
match create-project templates without compiling a regex, and read bu…
StephenHodgson Oct 26, 2026
7a4238f
keep pulseaudio in the required linux packages so fmod finds an audio…
StephenHodgson Oct 26, 2026
20b74fc
give the linux apt install room for a slow mirror
StephenHodgson Oct 26, 2026
c9bd850
fall back to the account when a cached license file resolves no entit…
StephenHodgson Oct 28, 2026
d641cf1
temporarily test only 6000.5 through 7000.0 while macos licensing is …
StephenHodgson Oct 28, 2026
30e6137
skip a cached license file issued to a different machine identity
StephenHodgson Oct 28, 2026
767965f
run unity rows grouped by os with a windows seat cap, cache linux edi…
StephenHodgson Oct 28, 2026
0c7aa28
mark license-exhausted and transient failures for reruns, and make ff…
StephenHodgson Oct 28, 2026
021c8c9
skip returning the license on github-hosted macos and linux runners b…
StephenHodgson Oct 28, 2026
90cb2fb
test unity 6 and newer on prs and 4.7.2 through 2022.3 monthly
StephenHodgson Oct 29, 2026
77424ca
give android rows a 60 minute job timeout
StephenHodgson Oct 29, 2026
3945212
disable webgl compression in test projects so builds skip brotli
StephenHodgson Oct 29, 2026
2f31b3f
mark a download cut off mid-stream as a transient failure
StephenHodgson Oct 30, 2026
6d93d65
group unity rows by editor version again, with every group running at…
StephenHodgson Oct 30, 2026
9042e1f
stop only the processes this job started, never another job's on a sh…
StephenHodgson Oct 30, 2026
e7151e0
address the remaining review findings
StephenHodgson Oct 30, 2026
7a8c3f9
mark unity id sign-in throttling as retryable and add a license-refre…
StephenHodgson Oct 30, 2026
d5c059b
activate one license per os per run and pass it to the build rows enc…
StephenHodgson Oct 30, 2026
febe631
key the run license with ci_token instead of the unity password
StephenHodgson Oct 30, 2026
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
11 changes: 8 additions & 3 deletions .github/actions/run-unity-test-batch/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,14 @@ runs:
- name: Prepare test list and install packages
shell: bash
working-directory: ${{ inputs.unity-project-path }}
# inputs reach the script through the environment, never by expression interpolation
env:
TESTS_INPUT_OVERRIDE: ${{ inputs.tests-input }}
TEST_PROFILE: ${{ inputs.test-profile }}
run: |
set -euo pipefail
tests_input="${{ inputs.tests-input }}"
test_profile="${{ inputs.test-profile }}"
tests_input="${TESTS_INPUT_OVERRIDE}"
test_profile="${TEST_PROFILE}"

if [ -z "$tests_input" ]; then
case "$test_profile" in
Expand Down Expand Up @@ -60,7 +64,8 @@ runs:
needs_test_framework=true
fi

npm install -g openupm-cli
# the CLI is pinned; the packages it adds stay on their latest releases on purpose (see docs/unity-version-support.md)
npm install -g openupm-cli@4.5.2
openupm add com.utilities.buildpipeline
if [ "$needs_test_framework" = true ]; then
openupm add com.unity.test-framework
Expand Down
150 changes: 30 additions & 120 deletions .github/scripts/unity-release-discovery.cjs
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
/**
* Weekly Unity release discovery: compare Releases API tips to CI matrix + canary pin.
* Weekly Unity release discovery: compare Releases API tips to the CI matrix.
* Usage: node .github/scripts/unity-release-discovery.cjs
* Env: GITHUB_STEP_SUMMARY (optional), OPEN_ISSUE=1 + GH_TOKEN/GITHUB_TOKEN to open/update drift issue.
* Env: GITHUB_STEP_SUMMARY (optional): the report is appended to the run summary, and drift is a warning annotation.
*/
const fs = require('fs');
const path = require('path');
const { execFileSync } = require('child_process');

const repoRoot = path.join(__dirname, '..', '..');
const apiBase = 'https://services.api.unity.com/unity/editor/release/v1/releases';
const minors = ['6000.0', '6000.1', '6000.2', '6000.3', '6000.4', '6000.5', '6000.6', '6000.7'];
const ISSUE_TITLE = 'unity-release-drift: matrix / canary vs Unity Releases API';
const ISSUE_LABEL = 'unity-release-drift';
const minors = ['6000.0', '6000.1', '6000.2', '6000.3', '6000.4', '6000.5', '6000.6', '6000.7', '6000.8', '7000.0', '7000.1'];

async function fetchLatest(versionPrefix) {
const url = `${apiBase}?version=${encodeURIComponent(versionPrefix)}&limit=10`;
Expand All @@ -21,12 +18,24 @@ async function fetchLatest(versionPrefix) {
}
const data = await res.json();
const results = Array.isArray(data.results) ? data.results : [];
return results.map(r => ({
version: r.version,
stream: r.stream,
shortRevision: r.shortRevision,
releaseDate: r.releaseDate,
}));
// the report is markdown in the run summary, so only fields rebuilt from validated parts reach it
return results.map(normalizeRelease).filter(Boolean);
}

const STREAMS = { LTS: 'LTS', SUPPORTED: 'SUPPORTED', TECH: 'TECH', BETA: 'BETA', ALPHA: 'ALPHA' };
const CHANNELS = { a: 'a', b: 'b', c: 'c', f: 'f', p: 'p', x: 'x' };

function normalizeRelease(r) {
const version = /^(\d{1,4})\.(\d{1,3})\.(\d{1,3})([abcfpx])(\d{1,3})$/.exec(String(r?.version ?? ''));
const revision = /^[0-9a-f]{12}$/.exec(String(r?.shortRevision ?? ''));
const stream = STREAMS[String(r?.stream ?? '').toUpperCase()] ?? 'OTHER';
if (!version) return null;
const [, major, minor, patch, channel, build] = version;
return {
version: `${Number(major)}.${Number(minor)}.${Number(patch)}${CHANNELS[channel]}${Number(build)}`,
stream,
shortRevision: revision ? revision[0] : '',
};
}

function loadMatrixVersions() {
Expand All @@ -36,17 +45,6 @@ function loadMatrixVersions() {
return buildOptions['unity-version'] || [];
}

function loadCanaryPin() {
const yml = fs.readFileSync(
path.join(repoRoot, '.github', 'workflows', 'unity-preview-canary.yml'),
'utf8'
);
const version = (yml.match(/"unity-version":\s*"([^"]+)"/) || [])[1];
const changeset = (yml.match(/"changeset":\s*"([^"]+)"/) || [])[1];
const channel = (yml.match(/"channel":\s*"([^"]+)"/) || [])[1];
return { version, changeset, channel };
}

function pickTip(rows, preferStreams) {
for (const stream of preferStreams) {
const hit = rows.find(r => String(r.stream).toUpperCase() === stream);
Expand All @@ -57,12 +55,12 @@ function pickTip(rows, preferStreams) {

async function main() {
const matrixVersions = loadMatrixVersions();
const canary = loadCanaryPin();
const tips = [];

for (const minor of minors) {
try {
const rows = await fetchLatest(minor);
if (rows.length === 0) continue; // the stream has no release yet
const tip = pickTip(rows, ['LTS', 'SUPPORTED', 'BETA', 'ALPHA']);
tips.push({ minor, tip, count: rows.length });
} catch (err) {
Expand All @@ -79,19 +77,13 @@ async function main() {
lines.push(`- \`${v}\``);
}
lines.push('');
lines.push('### Preview canary pin');
lines.push('');
lines.push(`- version: \`${canary.version || 'n/a'}\``);
lines.push(`- changeset: \`${canary.changeset || 'n/a'}\``);
lines.push(`- channel: \`${canary.channel || 'n/a'}\``);
lines.push('');
lines.push('### API tips by minor');
lines.push('');
lines.push('| Minor | Tip version | Stream | Changeset |');
lines.push('|-------|-------------|--------|-----------|');
for (const row of tips) {
if (!row.tip) {
lines.push(`| ${row.minor} | _(error)_ | | ${row.error || ''} |`);
lines.push(`| ${row.minor} | _(error)_ | | |`);
continue;
}
lines.push(
Expand All @@ -101,16 +93,10 @@ async function main() {
lines.push('');

const drifts = [];
const tip60006 = tips.find(t => t.minor === '6000.6')?.tip;
if (tip60006 && canary.version && tip60006.version !== canary.version) {
drifts.push(
`Canary pin ${canary.version} is behind API tip ${tip60006.version} (${tip60006.stream}, ${tip60006.shortRevision}).`
);
}
// every stream is tracked, alphas and betas included: the matrix resolves each row to the newest build of its stream
for (const row of tips) {
if (!row.tip) continue;
const stream = String(row.tip.stream || '').toUpperCase();
if (stream !== 'LTS' && stream !== 'SUPPORTED') continue;
const minorKey = row.minor; // e.g. 6000.5
const covered = matrixVersions.some(v => {
if (v === minorKey) return true;
Expand All @@ -130,7 +116,7 @@ async function main() {
if (drifts.length === 0) {
lines.push('### Drift');
lines.push('');
lines.push('No matrix/canary drift detected against API tips.');
lines.push('No matrix drift detected against API tips.');
} else {
lines.push('### Drift');
lines.push('');
Expand All @@ -142,89 +128,13 @@ async function main() {
const body = lines.join('\n');
console.log(body);

// Step summary: local matrix/canary + drift count only (no HTTP-tainted API tip fields).
if (process.env.GITHUB_STEP_SUMMARY) {
const summaryPath = path.resolve(process.env.GITHUB_STEP_SUMMARY);
const runnerTemp = process.env.RUNNER_TEMP ? path.resolve(process.env.RUNNER_TEMP) : '';
const underTemp =
runnerTemp &&
(summaryPath === runnerTemp || summaryPath.startsWith(runnerTemp + path.sep));
if (underTemp || process.env.GITHUB_ACTIONS === 'true') {
const localSummary = [
'## Unity release discovery',
'',
'### Blocking matrix (`build-options.json`)',
'',
...matrixVersions.map(v => `- \`${v}\``),
'',
'### Preview canary pin',
'',
`- version: \`${canary.version || 'n/a'}\``,
`- changeset: \`${canary.changeset || 'n/a'}\``,
`- channel: \`${canary.channel || 'n/a'}\``,
'',
'### Drift',
'',
drifts.length === 0
? 'No matrix/canary drift detected against API tips.'
: `${drifts.length} drift item(s) detected. See the job log for API tip details.`,
'',
].join('\n');
fs.appendFileSync(summaryPath, localSummary);
}
// drift shows as a warning on the run; the report itself only goes to the run summary
for (const d of drifts) {
console.log(`::warning title=Unity release drift::${d}`);
}

if (process.env.OPEN_ISSUE === '1' && drifts.length > 0) {
const token = process.env.GH_TOKEN || process.env.GITHUB_TOKEN;
if (!token) {
console.error('OPEN_ISSUE=1 but no GH_TOKEN/GITHUB_TOKEN');
process.exitCode = 1;
return;
}
try {
const existing = execFileSync(
'gh',
['issue', 'list', '--label', ISSUE_LABEL, '--state', 'open', '--json', 'number,title', '--limit', '5'],
{ encoding: 'utf8' }
);
const issues = JSON.parse(existing || '[]');
const hit = issues.find(i => i.title === ISSUE_TITLE);
const issueBody = [
'Automated drift report from `unity-release-discovery`.',
'',
...drifts.map(d => `- ${d}`),
'',
body,
].join('\n');
try {
execFileSync('gh', ['label', 'create', ISSUE_LABEL, '--force'], { stdio: 'ignore' });
} catch {
// label may already exist or lack permission; issue create can omit it
}
if (hit) {
execFileSync('gh', ['issue', 'comment', String(hit.number), '--body', issueBody], {
stdio: 'inherit',
});
console.log(`Updated issue #${hit.number}`);
} else {
try {
execFileSync(
'gh',
['issue', 'create', '--title', ISSUE_TITLE, '--label', ISSUE_LABEL, '--body', issueBody],
{ stdio: 'inherit' }
);
} catch {
execFileSync(
'gh',
['issue', 'create', '--title', ISSUE_TITLE, '--body', issueBody],
{ stdio: 'inherit' }
);
}
}
} catch (err) {
console.error(`Issue upsert failed: ${err.message || err}`);
process.exitCode = 1;
}
if (process.env.GITHUB_STEP_SUMMARY) {
fs.appendFileSync(path.resolve(process.env.GITHUB_STEP_SUMMARY), `${body}\n`);
}
}

Expand Down
77 changes: 77 additions & 0 deletions .github/workflows/build-options-legacy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
{
"os": [
"ubuntu-latest",
"windows-latest",
"macos-latest"
],
"unity-version": [
"4.7.2",
"5.6.7f1 (e80cc3114ac1)",
"2017.4.40f1",
"2018",
"2019.x",
"2020.*",
"2021.3.x",
"2022.3.*"
],
"include": [
{
"os": "ubuntu-latest",
"build-target": "StandaloneLinux64"
},
{
"os": "ubuntu-latest",
"build-target": "WebGL",
"build-args": "-colorSpace Gamma"
},
{
"os": "ubuntu-latest",
"build-target": "Android"
},
{
"os": "windows-latest",
"build-target": "StandaloneWindows64"
},
{
"os": "windows-latest",
"build-target": "Android"
},
{
"os": "macos-latest",
"build-target": "StandaloneOSX"
},
{
"os": "macos-latest",
"build-target": "iOS"
}
],
"exclude": [
{
"os": "ubuntu-latest",
"unity-version": "4.7.2"
},
{
"os": "macos-latest",
"unity-version": "4.7.2"
},
{
"os": "windows-latest",
"build-target": "Android",
"unity-version": "4.7.2"
},
{
"os": "ubuntu-latest",
"unity-version": "5.6.7f1 (e80cc3114ac1)"
},
{
"os": "macos-latest",
"build-target": "iOS",
"unity-version": "5.6.7f1 (e80cc3114ac1)"
},
{
"os": "windows-latest",
"build-target": "Android",
"unity-version": "5.6.7f1 (e80cc3114ac1)"
}
]
}
44 changes: 5 additions & 39 deletions .github/workflows/build-options.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,15 @@
"macos-latest"
],
"unity-version": [
"4.7.2",
"5.6.7f1 (e80cc3114ac1)",
"2017.4.40f1",
"2018",
"2019.x",
"2020.*",
"2021.3.x",
"2022.3.*",
"6000.0.x",
"6000.1.*",
"6000.2",
"6000.3",
"6000.4",
"6000.5"
"6000.5",
"6000.6",
"6000.7",
"7000.0"
],
"include": [
{
Expand Down Expand Up @@ -66,34 +61,5 @@
"os": "macos-latest",
"build-target": "iOS"
}
],
"exclude": [
{
"os": "ubuntu-latest",
"unity-version": "4.7.2"
},
{
"os": "macos-latest",
"unity-version": "4.7.2"
},
{
"os": "windows-latest",
"build-target": "Android",
"unity-version": "4.7.2"
},
{
"os": "ubuntu-latest",
"unity-version": "5.6.7f1 (e80cc3114ac1)"
},
{
"os": "macos-latest",
"build-target": "iOS",
"unity-version": "5.6.7f1 (e80cc3114ac1)"
},
{
"os": "windows-latest",
"build-target": "Android",
"unity-version": "5.6.7f1 (e80cc3114ac1)"
}
]
}
}
Loading
Loading