Skip to content

Commit 0d2eb7e

Browse files
committed
feat(*): first working implementation
1 parent 2c312ea commit 0d2eb7e

File tree

8 files changed

+627
-0
lines changed

8 files changed

+627
-0
lines changed

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@
2222
"homepage": "https://github.com/mitmadness/UnityInvoker#readme",
2323
"devDependencies": {
2424
"@types/node": "^7.0.12",
25+
"@types/pify": "^0.0.28",
2526
"tslint": "^5.1.0",
2627
"typescript": "^2.2.2"
28+
},
29+
"dependencies": {
30+
"pify": "^2.3.0"
2731
}
2832
}

src/index.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { IUnityOptions } from './unity_cli_options';
2+
import { UnityProcess } from './unity_process';
3+
4+
export { IUnityOptions } from './unity_cli_options';
5+
export * from './unity_finder';
6+
export { LinuxPlayerArch, OSXPlayerArch, WindowsPlayerArch, RendererType } from './unity_process';
7+
export { UnityCrashError } from './unity_invoker';
8+
9+
export function invokeUnity(options: IUnityOptions = {}): UnityProcess {
10+
return new UnityProcess().withOptions(options);
11+
}
12+
13+
export function invokeHeadlessUnity(options: IUnityOptions = {}): UnityProcess {
14+
return invokeUnity(options).logFile(null).batchmode().noGraphics().quit();
15+
}

src/logger.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export type SimpleLogger = (message: string) => void;
2+
3+
export function noopLogger(): void {
4+
// do nothing
5+
}

src/unity_cli_options.ts

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
export type Flag = boolean;
2+
3+
export type BuildTarget =
4+
'win32'|'win64'|'osx'|'linux'|'linux64'|'ios'|'android'|'web'|'webstreamed'|
5+
'webgl'|'xboxone'|'ps4'|'psp2'|'wsaplayer'|'tizen'|'samsungtv'|string;
6+
7+
export interface IUnityOptions {
8+
[customOption: string]: Flag|string|string[]|undefined;
9+
10+
batchmode?: Flag;
11+
12+
buildLinux32Player?: string;
13+
14+
buildLinux64Player?: string;
15+
16+
buildLinuxUniversalPlayer?: string;
17+
18+
buildOSXPlayer?: string;
19+
20+
buildOSX64Player?: string;
21+
22+
buildOSXUniversalPlayer?: string;
23+
24+
buildTarget?: BuildTarget|string;
25+
26+
buildWindowsPlayer?: string;
27+
28+
buildWindows64Player?: string;
29+
30+
cleanedLogFile?: Flag;
31+
32+
createProject?: string;
33+
34+
editorTestsCategories?: string;
35+
36+
editorTestsFilter?: string;
37+
38+
editorTestsResultFile?: string;
39+
40+
executeMethod?: string;
41+
42+
exportPackage?: string;
43+
44+
'force-d3d9'?: Flag;
45+
46+
'force-d3d11'?: Flag;
47+
48+
'force-glcore'?: Flag;
49+
50+
'force-glcore32'?: Flag;
51+
52+
'force-glcore33'?: Flag;
53+
54+
'force-glcore40'?: Flag;
55+
56+
'force-glcore41'?: Flag;
57+
58+
'force-glcore42'?: Flag;
59+
60+
'force-glcore43'?: Flag;
61+
62+
'force-glcore44'?: Flag;
63+
64+
'force-glcore45'?: Flag;
65+
66+
'force-gles'?: Flag;
67+
68+
'force-gles30'?: Flag;
69+
70+
'force-gles31'?: Flag;
71+
72+
'force-gles32'?: Flag;
73+
74+
'force-clamped'?: Flag;
75+
76+
'force-free'?: Flag;
77+
78+
importPackage?: string;
79+
80+
logFile?: string|Flag;
81+
82+
nographics?: Flag;
83+
84+
password?: string;
85+
86+
projectPath?: string;
87+
88+
quit?: Flag;
89+
90+
returnlicense?: Flag;
91+
92+
runEditorTests?: Flag;
93+
94+
serial?: string;
95+
96+
'silent-crashes'?: Flag;
97+
98+
username?: string;
99+
100+
'disable-assembly-updater'?: Flag|string[];
101+
}

src/unity_finder.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import * as fs from 'fs';
2+
import * as pify from 'pify';
3+
4+
const candidateUnityPaths = [
5+
'/opt/Unity/Editor/Unity', // Debian / Ubuntu
6+
'/Applications/Unity/Unity.app/Contents/MacOS/Unity', // MacOS
7+
'C:\\Program Files (x86)\\Unity\\Editor\\Unity.exe', // Windows x86
8+
'C:\\Program Files\\Unity\\Editor\\Unity.exe' // Windows x64
9+
];
10+
11+
let unityBinaryPath: string;
12+
13+
export async function getUnityPath(): Promise<string> {
14+
// this is not a pure function and it caches its result
15+
if (unityBinaryPath) { return unityBinaryPath; }
16+
17+
//=> Try all paths, take the first
18+
for (const path of candidateUnityPaths) {
19+
try {
20+
await pify(fs.access)(path, fs.constants.X_OK);
21+
return unityBinaryPath = path;
22+
} catch (err) { /* pass */ }
23+
}
24+
25+
//=> Oops, no Unity installation found
26+
const triedPaths = candidateUnityPaths.map(path => `"${path}"`).join(', ');
27+
28+
throw new Error(
29+
`Unable to locate Unity installation, tried all of these paths: ${triedPaths}. ` +
30+
`Please use setUnityPath('/path/to/unity/executable').`
31+
);
32+
}
33+
34+
export function setUnityPath(executablePath: string): void {
35+
unityBinaryPath = executablePath;
36+
}

src/unity_invoker.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { spawn } from 'child_process';
2+
import * as fs from 'fs';
3+
import * as os from 'os';
4+
import * as path from 'path';
5+
import * as pify from 'pify';
6+
import { SimpleLogger } from './logger';
7+
import { IUnityOptions } from './unity_cli_options';
8+
import { getUnityPath } from './unity_finder';
9+
10+
export async function runUnityProcess(options: IUnityOptions, logger: SimpleLogger): Promise<string> {
11+
//=> Generating an argv array from the arguments object
12+
const argv = toArgv(options);
13+
14+
//=> Spawn Unity process
15+
const unityProcess = spawn(await getUnityPath(), argv);
16+
17+
//=> Watch process' stdout to log in real time, and keep the complete output in case of crash
18+
let stdoutAggregator = '';
19+
function stdoutHandler(buffer: Buffer) {
20+
const message = buffer.toString();
21+
stdoutAggregator += message;
22+
logger(message.trim());
23+
}
24+
25+
unityProcess.stdout.on('data', stdoutHandler);
26+
unityProcess.stderr.on('data', stdoutHandler);
27+
28+
//=> Watch for the process to terminate, check return code
29+
return new Promise<string>((resolve, reject) => {
30+
unityProcess.once('close', async (close) => {
31+
if (close === 0) {
32+
resolve(stdoutAggregator);
33+
} else {
34+
const crashPath = await logUnityCrash(stdoutAggregator);
35+
reject(new UnityCrashError(
36+
`Unity process crashed! Editor log has been written to ${crashPath}`,
37+
stdoutAggregator
38+
));
39+
}
40+
});
41+
});
42+
}
43+
44+
export function toArgv(options: IUnityOptions): string[] {
45+
const argv: string[] = [];
46+
47+
Object.keys(options).forEach((key) => {
48+
const value = options[key];
49+
50+
//=> Pass on false, null, undefined, etc (disabled flags).
51+
if (!value) { return; }
52+
53+
//=> Enabled flags + options with value(s)
54+
argv.push(`-${key}`);
55+
56+
//=> Options with value(s)
57+
if (Array.isArray(value)) {
58+
const values = value as string[];
59+
values.forEach(v => argv.push(v));
60+
} else if (typeof value !== 'boolean') {
61+
argv.push(value);
62+
}
63+
});
64+
65+
return argv;
66+
}
67+
68+
async function logUnityCrash(unityLog: string): Promise<string> {
69+
const crashPath = path.join(os.tmpdir(), 'unity_crash.abcompiler.log');
70+
71+
await pify(fs.writeFile)(crashPath, unityLog);
72+
73+
return crashPath;
74+
}
75+
76+
export class UnityCrashError extends Error {
77+
constructor(message: string, public readonly unityLog: string) {
78+
super(message);
79+
Object.setPrototypeOf(this, UnityCrashError.prototype);
80+
}
81+
}

0 commit comments

Comments
 (0)