forked from LeenkxTeam/LNXSDK
251 lines
7.2 KiB
TypeScript
251 lines
7.2 KiB
TypeScript
import {Callbacks} from './ProjectFile';
|
|
import * as child_process from 'child_process';
|
|
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
import * as chokidar from 'chokidar';
|
|
import * as log from './log';
|
|
import {sys} from './exec';
|
|
import { WebSocketServer, WebSocket } from 'ws';
|
|
|
|
export class HaxeCompiler {
|
|
from: string;
|
|
haxeDirectory: string;
|
|
hxml: string;
|
|
sourceMatchers: Array<string>;
|
|
watcher: fs.FSWatcher;
|
|
ready: boolean = true;
|
|
todo: boolean = false;
|
|
port: string = '7000';
|
|
isLiveReload: boolean = false;
|
|
wss: WebSocketServer;
|
|
wsClients: Array<WebSocket> = [];
|
|
temp: string;
|
|
to: string;
|
|
resourceDir: string;
|
|
compilationServer: child_process.ChildProcess;
|
|
sysdir: string;
|
|
|
|
constructor(from: string, temp: string, to: string, resourceDir: string, haxeDirectory: string, hxml: string, sourceDirectories: Array<string>, sysdir: string, port: string, isLiveReload: boolean, httpPort: string) {
|
|
this.from = from;
|
|
this.temp = temp;
|
|
this.to = to;
|
|
this.resourceDir = resourceDir;
|
|
this.haxeDirectory = haxeDirectory;
|
|
this.hxml = hxml;
|
|
this.sysdir = sysdir;
|
|
this.port = port;
|
|
this.isLiveReload = isLiveReload;
|
|
|
|
this.sourceMatchers = [];
|
|
for (let dir of sourceDirectories) {
|
|
this.sourceMatchers.push(path.join(dir, '**').replace(/\\/g, '/'));
|
|
}
|
|
|
|
if (isLiveReload) {
|
|
this.wss = new WebSocketServer({
|
|
port: parseInt(httpPort) + 1
|
|
});
|
|
this.wss.on('connection', (client) => {
|
|
if (this.wsClients.includes(client)) return;
|
|
this.wsClients.push(client);
|
|
});
|
|
}
|
|
}
|
|
|
|
close(): void {
|
|
if (this.watcher) this.watcher.close();
|
|
if (this.compilationServer) this.compilationServer.kill();
|
|
if (this.isLiveReload) this.wss.close();
|
|
}
|
|
|
|
async run(watch: boolean) {
|
|
if (watch) {
|
|
this.watcher = chokidar.watch(this.sourceMatchers, { ignored: /[\/\\]\.(git|DS_Store)/, persistent: true, ignoreInitial: true });
|
|
this.watcher.on('add', (file: string) => {
|
|
this.scheduleCompile();
|
|
});
|
|
this.watcher.on('change', (file: string) => {
|
|
this.scheduleCompile();
|
|
});
|
|
this.watcher.on('unlink', (file: string) => {
|
|
this.scheduleCompile();
|
|
});
|
|
this.startCompilationServer();
|
|
this.triggerCompilationServer();
|
|
}
|
|
else {
|
|
try {
|
|
await this.compile();
|
|
}
|
|
catch (error) {
|
|
return Promise.reject(error);
|
|
}
|
|
}
|
|
return Promise.resolve();
|
|
}
|
|
|
|
scheduleCompile() {
|
|
if (this.ready) {
|
|
this.triggerCompilationServer();
|
|
}
|
|
else {
|
|
this.todo = true;
|
|
}
|
|
}
|
|
|
|
runHaxeAgain(parameters: string[], onClose: (code: number, signal: string) => void): child_process.ChildProcess {
|
|
let exe = 'haxe';
|
|
let env = process.env;
|
|
if (fs.existsSync(this.haxeDirectory) && fs.statSync(this.haxeDirectory).isDirectory()) {
|
|
let localexe = path.resolve(this.haxeDirectory, 'haxe' + sys());
|
|
if (!fs.existsSync(localexe)) localexe = path.resolve(this.haxeDirectory, 'haxe');
|
|
if (fs.existsSync(localexe)) exe = localexe;
|
|
const stddir = path.resolve(this.haxeDirectory, 'std');
|
|
if (fs.existsSync(stddir) && fs.statSync(stddir).isDirectory()) {
|
|
env.HAXE_STD_PATH = stddir;
|
|
}
|
|
}
|
|
|
|
let haxe = child_process.spawn(exe, parameters, {env: env, cwd: path.normalize(this.from)});
|
|
|
|
haxe.stdout.on('data', (data: any) => {
|
|
log.info(data.toString());
|
|
});
|
|
|
|
haxe.stderr.on('data', (data: any) => {
|
|
log.error(data.toString());
|
|
});
|
|
|
|
haxe.on('close', onClose);
|
|
|
|
return haxe;
|
|
}
|
|
|
|
runHaxeAgainAndWait(parameters: string[]): Promise<void> {
|
|
return new Promise<void>((resolve, reject) => {
|
|
this.runHaxeAgain(parameters, (code, signal) => {
|
|
if (code === 0) {
|
|
resolve();
|
|
}
|
|
else {
|
|
reject();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
static cleanHxml(hxml: string): string {
|
|
let params: string[] = [];
|
|
let ignoreNext = false;
|
|
let parameters = hxml.split('\n');
|
|
for (let parameter of parameters) {
|
|
if (!parameter.startsWith('-main') && !parameter.startsWith('-js')) {
|
|
params.push(parameter);
|
|
}
|
|
}
|
|
return params.join('\n');
|
|
}
|
|
|
|
runHaxe(parameters: string[], onClose: (code: number, signal: string) => void): child_process.ChildProcess {
|
|
if (fs.existsSync(path.join(this.resourceDir, 'workers.txt'))) {
|
|
fs.unlinkSync(path.join(this.resourceDir, 'workers.txt'));
|
|
}
|
|
let haxe = this.runHaxeAgain(parameters, async (code: number, signal: string) => {
|
|
if (fs.existsSync(path.join(this.resourceDir, 'workers.txt'))) {
|
|
let hxml = fs.readFileSync(path.join(this.from, parameters[0]), {encoding: 'utf8'});
|
|
let workers = fs.readFileSync(path.join(this.resourceDir, 'workers.txt'), {encoding: 'utf8'});
|
|
let lines = workers.split('\n');
|
|
for (let line of lines) {
|
|
if (line.trim() === '') continue;
|
|
log.info('Creating ' + line + ' worker.');
|
|
let newhxml = HaxeCompiler.cleanHxml(hxml);
|
|
newhxml += '-main ' + line.trim() + '\n';
|
|
newhxml += '-js ' + path.join(this.sysdir, line.trim()) + '.js\n';
|
|
newhxml += '-D kha_in_worker\n';
|
|
fs.writeFileSync(path.join(this.from, 'temp.hxml'), newhxml, {encoding: 'utf8'});
|
|
await this.runHaxeAgainAndWait(['temp.hxml']);
|
|
}
|
|
onClose(code, signal);
|
|
}
|
|
else {
|
|
onClose(code, signal);
|
|
}
|
|
});
|
|
|
|
return haxe;
|
|
}
|
|
|
|
startCompilationServer() {
|
|
this.compilationServer = this.runHaxe(['--wait', this.port], (code: number) => {
|
|
log.info('Haxe compilation server stopped.');
|
|
});
|
|
}
|
|
|
|
triggerCompilationServer(): Promise<void> {
|
|
process.stdout.write('\x1Bc');
|
|
log.info('Haxe compilation...');
|
|
this.ready = false;
|
|
this.todo = false;
|
|
return new Promise((resolve, reject) => {
|
|
this.runHaxe(['--connect', this.port, this.hxml], (code: number) => {
|
|
if (this.to && fs.existsSync(path.join(this.from, this.temp))) {
|
|
fs.renameSync(path.join(this.from, this.temp), path.join(this.from, this.to));
|
|
}
|
|
this.ready = true;
|
|
if (code === 0) {
|
|
process.stdout.write('\x1Bc');
|
|
log.info('Haxe compile end.');
|
|
if (this.isLiveReload) {
|
|
this.wsClients.forEach(client => {
|
|
client.send(JSON.stringify({}));
|
|
});
|
|
}
|
|
for (let callback of Callbacks.postHaxeRecompilation) {
|
|
callback();
|
|
}
|
|
} else {
|
|
log.info('Haxe compile error.');
|
|
}
|
|
if (code === 0) {
|
|
resolve();
|
|
}
|
|
|
|
// (node:3630) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future,
|
|
// promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
|
|
// else reject('Haxe compiler error.');
|
|
|
|
if (this.todo) {
|
|
this.scheduleCompile();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
compile(): Promise<void> {
|
|
return new Promise<void>((resolve, reject) => {
|
|
this.runHaxe([this.hxml], (code: number) => {
|
|
if (code === 0) {
|
|
if (this.to && fs.existsSync(path.join(this.from, this.temp))) {
|
|
fs.renameSync(path.join(this.from, this.temp), path.join(this.from, this.to));
|
|
}
|
|
resolve();
|
|
}
|
|
else {
|
|
process.exitCode = 1;
|
|
log.error('Haxe compiler error.');
|
|
reject();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
private static spinRename(from: string, to: string): void {
|
|
for (; ; ) {
|
|
if (fs.existsSync(from)) {
|
|
fs.renameSync(from, to);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|