Skip to content

Commit 69c5940

Browse files
unity-cli@v1.4.0 (#32)
- refactored android sdk installation - require elevated permissions check on windows when installing android sdks - fix java exceptions on linux android installation - fix ProjectSettings/ProjectVersion.txt parsing for older projects - fix module validation - fix setup-project -u <version> not overriding the version in project - fix project template lookups for older unity versions prior to 2019
1 parent 8736559 commit 69c5940

14 files changed

Lines changed: 332 additions & 127 deletions

.github/workflows/build-options.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@
3535
"os": "windows-latest",
3636
"build-target": "StandaloneWindows64"
3737
},
38+
{
39+
"os": "windows-latest",
40+
"build-target": "Android"
41+
},
3842
{
3943
"os": "macos-latest",
4044
"build-target": "StandaloneOSX"
@@ -53,6 +57,11 @@
5357
"os": "macos-latest",
5458
"unity-version": "4.7.2"
5559
},
60+
{
61+
"os": "windows-latest",
62+
"build-target": "Android",
63+
"unity-version": "4.7.2"
64+
},
5665
{
5766
"os": "ubuntu-latest",
5867
"unity-version": "5.6.7f1 (e80cc3114ac1)"
@@ -61,6 +70,11 @@
6170
"os": "macos-latest",
6271
"build-target": "iOS",
6372
"unity-version": "5.6.7f1 (e80cc3114ac1)"
73+
},
74+
{
75+
"os": "windows-latest",
76+
"build-target": "Android",
77+
"unity-version": "5.6.7f1 (e80cc3114ac1)"
6478
}
6579
]
6680
}

.github/workflows/unity-build.yml

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@ jobs:
2525
UNITY_PROJECT_PATH: '' # Set from create-project step
2626
RUN_BUILD: '' # Set to true if the build pipeline package can be installed and used
2727
steps:
28+
- name: Free Disk Space
29+
if: ${{ matrix.os == 'ubuntu-latest' && matrix.unity-version == '6000.2' }}
30+
uses: endersonmenezes/free-disk-space@v2
31+
with:
32+
remove_android: true
33+
remove_dotnet: false
34+
remove_tool_cache: false
2835
- uses: actions/checkout@v5
2936
- uses: actions/setup-node@v4
3037
with:
@@ -96,6 +103,14 @@ jobs:
96103
npm install -g openupm-cli
97104
cd "${UNITY_PROJECT_PATH}"
98105
openupm add com.utilities.buildpipeline
106+
- name: Update Android Target Sdk Version
107+
if: ${{ matrix.build-target == 'Android' }}
108+
shell: bash
109+
run: |
110+
# update AndroidTargetSdkVersion to 32 in ProjectSettings/ProjectSettings.asset
111+
sed -i 's/AndroidTargetSdkVersion: [0-9]*/AndroidTargetSdkVersion: 32/' "${UNITY_PROJECT_PATH}/ProjectSettings/ProjectSettings.asset"
112+
# ensure android dependencies are installed
113+
unity-cli setup-unity -p "${UNITY_PROJECT_PATH}" -m android
99114
- name: Build Project
100115
if: ${{ env.RUN_BUILD == 'true' }}
101116
timeout-minutes: 60
@@ -120,7 +135,7 @@ jobs:
120135
if: always()
121136
uses: actions/upload-artifact@v4
122137
with:
123-
name: ${{ github.run_id }}.${{ github.run_attempt }} ${{ matrix.unity-version }} ${{ matrix.build-target }} logs
138+
name: ${{ github.run_id }}.${{ github.run_attempt }} ${{ matrix.os }} ${{ matrix.unity-version }} ${{ matrix.build-target }} logs
124139
retention-days: 1
125140
path: |
126141
${{ github.workspace }}/**/*.log

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@rage-against-the-pixel/unity-cli",
3-
"version": "1.3.3",
3+
"version": "1.4.0",
44
"description": "A command line utility for the Unity Game Engine.",
55
"author": "RageAgainstThePixel",
66
"license": "MIT",

src/android-sdk.ts

Lines changed: 108 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,20 @@ import {
99
ReadFileContents,
1010
ResolveGlobToPath
1111
} from './utilities';
12+
import { satisfies } from 'semver';
1213

1314
const logger = Logger.instance;
1415

1516
/**
1617
* Checks if the required Android SDK is installed for the given Unity Editor and Project.
17-
* @param editorPath The path to the Unity Editor executable.
18+
* @param editor The UnityEditor instance.
1819
* @param projectPath The path to the Unity project.
1920
* @returns A promise that resolves when the check is complete.
2021
*/
21-
export async function CheckAndroidSdkInstalled(editorPath: string, projectPath: string): Promise<void> {
22-
logger.ci(`Checking Android SDK installation for:\n > Editor: ${editorPath}\n > Project: ${projectPath}`);
22+
export async function CheckAndroidSdkInstalled(editor: UnityEditor, projectPath: string): Promise<void> {
23+
logger.ci(`Checking Android SDK installation for:\n > Editor: ${editor.editorRootPath}\n > Project: ${projectPath}`);
2324
let sdkPath = undefined;
2425
await createRepositoryCfg();
25-
const rootEditorPath = UnityEditor.GetEditorRootPath(editorPath);
2626
const projectSettingsPath = path.join(projectPath, 'ProjectSettings/ProjectSettings.asset');
2727
const projectSettingsContent = await ReadFileContents(projectSettingsPath);
2828
const matchResult = projectSettingsContent.match(/(?<=AndroidTargetSdkVersion: )\d+/);
@@ -31,23 +31,23 @@ export async function CheckAndroidSdkInstalled(editorPath: string, projectPath:
3131

3232
if (androidTargetSdk === undefined || androidTargetSdk === 0) { return; }
3333

34-
sdkPath = await getAndroidSdkPath(rootEditorPath, androidTargetSdk);
34+
sdkPath = await getAndroidSdkPath(editor, androidTargetSdk);
3535

3636
if (sdkPath) {
3737
logger.ci(`Target Android SDK android-${androidTargetSdk} Installed in:\n > "${sdkPath}"`);
3838
return;
3939
}
4040

4141
logger.info(`Installing Android Target SDK:\n > android-${androidTargetSdk}`);
42-
const sdkManagerPath = await getSdkManager(rootEditorPath);
43-
const javaSdk = await getJDKPath(rootEditorPath);
42+
const sdkManagerPath = await getSdkManager(editor);
43+
const javaSdk = await getJDKPath(editor);
4444
await execSdkManager(sdkManagerPath, javaSdk, ['--licenses']);
4545
await execSdkManager(sdkManagerPath, javaSdk, ['--update']);
4646
await execSdkManager(sdkManagerPath, javaSdk, ['platform-tools', `platforms;android-${androidTargetSdk}`]);
47-
sdkPath = await getAndroidSdkPath(rootEditorPath, androidTargetSdk);
47+
sdkPath = await getAndroidSdkPath(editor, androidTargetSdk);
4848

4949
if (!sdkPath) {
50-
throw new Error(`Failed to install android-${androidTargetSdk} in ${rootEditorPath}`);
50+
throw new Error(`Failed to install android-${androidTargetSdk} in ${editor.editorRootPath}`);
5151
}
5252

5353
logger.ci(`Target Android SDK Installed in:\n > "${sdkPath}"`);
@@ -61,31 +61,79 @@ async function createRepositoryCfg(): Promise<void> {
6161
await fileHandle.close();
6262
}
6363

64-
async function getJDKPath(rootEditorPath: string): Promise<string> {
65-
const jdkPath = await ResolveGlobToPath([rootEditorPath, '**', 'AndroidPlayer', 'OpenJDK']);
64+
async function getJDKPath(editor: UnityEditor): Promise<string> {
65+
let jdkPath: string | undefined = undefined;
6666

67-
if (!jdkPath) {
68-
throw new Error(`Failed to resolve OpenJDK in ${rootEditorPath}`);
67+
if (editor.version.isGreaterThanOrEqualTo('2019.0.0')) {
68+
logger.debug('Using JDK bundled with Unity 2019+');
69+
jdkPath = await ResolveGlobToPath([editor.editorRootPath, '**', 'AndroidPlayer', 'OpenJDK/']);
70+
71+
if (!jdkPath) {
72+
throw new Error(`Failed to resolve OpenJDK in ${editor.editorRootPath}`);
73+
}
74+
} else {
75+
logger.debug('Using system JDK for Unity versions prior to 2019');
76+
jdkPath = process.env.JAVA_HOME || process.env.JDK_HOME;
77+
78+
if (!jdkPath) {
79+
throw new Error('JDK installation not found: No system JAVA_HOME or JDK_HOME defined');
80+
}
6981
}
7082

7183
await fs.promises.access(jdkPath, fs.constants.R_OK);
7284
logger.ci(`jdkPath:\n > "${jdkPath}"`);
7385
return jdkPath;
7486
}
7587

76-
async function getSdkManager(rootEditorPath: string): Promise<string> {
88+
async function getSdkManager(editor: UnityEditor): Promise<string> {
7789
let globPath: string[] = [];
78-
switch (process.platform) {
79-
case 'darwin':
80-
case 'linux':
81-
globPath = [rootEditorPath, '**', 'AndroidPlayer', '**', 'sdkmanager'];
82-
break;
83-
case 'win32':
84-
globPath = [rootEditorPath, '**', 'AndroidPlayer', '**', 'sdkmanager.bat'];
85-
break;
86-
default:
87-
throw new Error(`Unsupported platform: ${process.platform}`);
90+
if (editor.version.range('>=2019.0.0 <2021.0.0')) {
91+
logger.debug('Using sdkmanager bundled with Unity 2019 and 2020');
92+
switch (process.platform) {
93+
case 'darwin':
94+
case 'linux':
95+
globPath = [editor.editorRootPath, '**', 'AndroidPlayer', '**', 'sdkmanager'];
96+
break;
97+
case 'win32':
98+
globPath = [editor.editorRootPath, '**', 'AndroidPlayer', '**', 'sdkmanager.bat'];
99+
break;
100+
default:
101+
throw new Error(`Unsupported platform: ${process.platform}`);
102+
}
103+
} else if (editor.version.range('>=2021.0.0')) {
104+
logger.debug('Using cmdline-tools sdkmanager bundled with Unity 2021+');
105+
switch (process.platform) {
106+
case 'darwin':
107+
case 'linux':
108+
globPath = [editor.editorRootPath, '**', 'AndroidPlayer', '**', 'cmdline-tools', '**', 'sdkmanager'];
109+
break;
110+
case 'win32':
111+
globPath = [editor.editorRootPath, '**', 'AndroidPlayer', '**', 'cmdline-tools', '**', 'sdkmanager.bat'];
112+
break;
113+
default:
114+
throw new Error(`Unsupported platform: ${process.platform}`);
115+
}
116+
} else {
117+
logger.debug('Using system sdkmanager');
118+
const systemSdkPath = process.env.ANDROID_SDK_ROOT || process.env.ANDROID_HOME;
119+
120+
if (!systemSdkPath) {
121+
throw new Error('Android installation not found: No system ANDROID_SDK_ROOT or ANDROID_HOME defined');
122+
}
123+
124+
switch (process.platform) {
125+
case 'darwin':
126+
case 'linux':
127+
globPath = [systemSdkPath, 'cmdline-tools', 'latest', 'bin', 'sdkmanager'];
128+
break;
129+
case 'win32':
130+
globPath = [systemSdkPath, 'cmdline-tools', 'latest', 'bin', 'sdkmanager.bat'];
131+
break;
132+
default:
133+
throw new Error(`Unsupported platform: ${process.platform}`);
134+
}
88135
}
136+
89137
const sdkmanagerPath = await ResolveGlobToPath(globPath);
90138

91139
if (!sdkmanagerPath) {
@@ -97,19 +145,36 @@ async function getSdkManager(rootEditorPath: string): Promise<string> {
97145
return sdkmanagerPath;
98146
}
99147

100-
async function getAndroidSdkPath(rootEditorPath: string, androidTargetSdk: number): Promise<string | undefined> {
101-
logger.ci(`Attempting to locate Android SDK Path...\n > editorPath: ${rootEditorPath}\n > androidTargetSdk: ${androidTargetSdk}`);
148+
async function getAndroidSdkPath(editor: UnityEditor, androidTargetSdk: number): Promise<string | undefined> {
149+
logger.ci(`Attempting to locate Android SDK Path...\n > editorRootPath: ${editor.editorRootPath}\n > androidTargetSdk: ${androidTargetSdk}`);
102150
let sdkPath: string;
103151

104-
try {
105-
sdkPath = await ResolveGlobToPath([rootEditorPath, '**', 'PlaybackEngines', 'AndroidPlayer', 'SDK', 'platforms', `android-${androidTargetSdk}/`]);
106-
await fs.promises.access(sdkPath, fs.constants.R_OK);
107-
} catch (error) {
108-
logger.debug(`android-${androidTargetSdk} not installed`);
109-
return undefined;
152+
// if 2019+ test editor path, else use system android installation
153+
if (editor.version.isGreaterThanOrEqualTo('2019.0.0')) {
154+
logger.debug('Using Android SDK bundled with Unity 2019+');
155+
try {
156+
sdkPath = await ResolveGlobToPath([editor.editorRootPath, '**', 'PlaybackEngines', 'AndroidPlayer', 'SDK', 'platforms', `android-${androidTargetSdk}/`]);
157+
} catch (error) {
158+
logger.debug(`android-${androidTargetSdk} not installed`);
159+
return undefined;
160+
}
161+
} else { // fall back to system android installation
162+
logger.debug('Using system Android SDK for Unity versions prior to 2019');
163+
try {
164+
const systemSdkPath = process.env.ANDROID_SDK_ROOT || process.env.ANDROID_HOME;
165+
166+
if (!systemSdkPath) {
167+
logger.debug('Android installation not found: No system ANDROID_SDK_ROOT or ANDROID_HOME defined');
168+
return undefined;
169+
}
170+
171+
sdkPath = await ResolveGlobToPath([systemSdkPath, 'platforms', `android-${androidTargetSdk}/`]);
172+
} catch (error) {
173+
logger.debug(`android-${androidTargetSdk} not installed`);
174+
return undefined;
175+
}
110176
}
111177

112-
logger.ci(`Android sdkPath:\n > "${sdkPath}"`);
113178
return sdkPath;
114179
}
115180

@@ -123,25 +188,27 @@ async function execSdkManager(sdkManagerPath: string, javaPath: string, args: st
123188
fs.accessSync(sdkManagerPath, fs.constants.R_OK | fs.constants.X_OK);
124189
}
125190

191+
if (process.platform === 'win32' && !await isProcessElevated()) {
192+
throw new Error('Android SDK installation requires elevated (administrator) privileges. Please rerun as Administrator.');
193+
}
194+
126195
try {
127-
exitCode = await new Promise<number>((resolve, reject) => {
196+
exitCode = await new Promise<number>(async (resolve, reject) => {
197+
let cmdEnv = { ...process.env };
198+
cmdEnv.JAVA_HOME = javaPath;
199+
cmdEnv.JDK_HOME = javaPath;
200+
cmdEnv.SKIP_JDK_VERSION_CHECK = 'true';
128201
let cmd = sdkManagerPath;
129202
let cmdArgs = args;
130203

131204
if (process.platform === 'win32') {
132-
if (!isProcessElevated()) {
133-
throw new Error('Android SDK installation requires elevated (administrator) privileges. Please rerun as Administrator.');
134-
}
135-
136205
cmd = 'cmd.exe';
137206
cmdArgs = ['/c', sdkManagerPath, ...args];
138207
}
139208

140209
const child = spawn(cmd, cmdArgs, {
141210
stdio: ['pipe', 'pipe', 'pipe'],
142-
env: {
143-
JAVA_HOME: process.platform === 'win32' ? `"${javaPath}"` : javaPath
144-
}
211+
env: cmdEnv
145212
});
146213
const sigintHandler = () => child.kill('SIGINT');
147214
const sigtermHandler = () => child.kill('SIGTERM');

src/cli.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,14 @@ program.command('setup-unity')
225225
process.exit(1);
226226
}
227227

228-
const unityVersion = unityProject?.version ?? new UnityVersion(options.unityVersion, options.changeset, options.arch);
228+
let unityVersion: UnityVersion;
229+
230+
if (options.unityVersion) {
231+
unityVersion = new UnityVersion(options.unityVersion, options.changeset, options.arch);
232+
} else {
233+
unityVersion = unityProject!.version;
234+
}
235+
229236
const modules: string[] = options.modules ? options.modules.split(/[ ,]+/).filter(Boolean) : [];
230237
const buildTargets: string[] = options.buildTargets ? options.buildTargets.split(/[ ,]+/).filter(Boolean) : [];
231238
const moduleBuildTargetMap = UnityHub.GetPlatformTargetModuleMap();
@@ -262,7 +269,7 @@ program.command('setup-unity')
262269
output['UNITY_PROJECT_PATH'] = unityProject.projectPath;
263270

264271
if (modules.includes('android')) {
265-
await CheckAndroidSdkInstalled(unityEditor.editorPath, unityProject.projectPath);
272+
await CheckAndroidSdkInstalled(unityEditor, unityProject.projectPath);
266273
}
267274
}
268275

0 commit comments

Comments
 (0)