Update Files

This commit is contained in:
2025-01-22 16:18:30 +01:00
parent ed4603cf95
commit a36294b518
16718 changed files with 2960346 additions and 0 deletions

View File

@ -0,0 +1,7 @@
export let Architecture = {
Default: 'default',
Arm7: 'arm7',
Arm8: 'arm8',
X86: 'x86',
X86_64: 'x86_64'
};

View File

@ -0,0 +1,282 @@
import {Callbacks} from './ProjectFile';
import * as fs from 'fs-extra';
import * as path from 'path';
import {KhaExporter} from './Exporters/KhaExporter';
import * as log from './log';
import * as chokidar from 'chokidar';
import * as crypto from 'crypto';
import * as Throttle from 'promise-parallel-throttle';
import { Options } from './Options';
export class AssetConverter {
options: Options;
exporter: KhaExporter;
platform: string;
assetMatchers: Array<{ match: string, options: any }>;
watcher: fs.FSWatcher;
constructor(exporter: KhaExporter, options: Options, assetMatchers: Array<{ match: string, options: any }>) {
this.exporter = exporter;
this.options = options;
this.platform = options.target;
this.assetMatchers = assetMatchers;
}
close(): void {
if (this.watcher) this.watcher.close();
}
static replacePattern(pattern: string, name: string, fileinfo: path.ParsedPath, options: any, from: string) {
let basePath: string = options.nameBaseDir ? path.join(from, options.nameBaseDir) : from;
let dirValue: string = path.relative(basePath, fileinfo.dir);
if (basePath.length > 0 && basePath[basePath.length - 1] === path.sep
&& dirValue.length > 0 && dirValue[dirValue.length - 1] !== path.sep) {
dirValue += path.sep;
}
if (options.namePathSeparator) {
dirValue = dirValue.split(path.sep).join(options.namePathSeparator);
}
const dirRegex = dirValue === ''
? /{dir}\//g
: /{dir}/g;
return pattern.replace(/{name}/g, name).replace(/{ext}/g, fileinfo.ext).replace(dirRegex, dirValue);
}
static createExportInfo(fileinfo: path.ParsedPath, keepextension: boolean, options: any, from: string): {name: string, destination: string} {
let nameValue = fileinfo.name;
let destination = fileinfo.name;
if (options.md5sum) {
let data = fs.readFileSync(path.join(fileinfo.dir, fileinfo.base));
let md5sum = crypto.createHash('md5').update(data).digest('hex'); // TODO yield generateMd5Sum(file);
destination += '_' + md5sum;
}
if ((keepextension || options.noprocessing) && (!options.destination || options.destination.indexOf('{ext}') < 0)) {
destination += fileinfo.ext;
}
if (options.destination) {
destination = AssetConverter.replacePattern(options.destination, destination, fileinfo, options, from);
}
if (options.destinationCallback) {
destination = options.destinationCallback(destination);
}
if (keepextension && (!options.name || options.name.indexOf('{ext}') < 0)) {
nameValue += fileinfo.ext;
}
if (options.name) {
nameValue = AssetConverter.replacePattern(options.name, nameValue, fileinfo, options, from);
}
return {name: nameValue, destination: destination};
}
watch(watch: boolean, match: string, temp: string, options: any): Promise<{ name: string, from: string, type: string, files: string[], file_sizes: number[], original_width: number, original_height: number, readable: boolean }[]> {
return new Promise<{ name: string, from: string, type: string, files: string[], file_sizes: number[], original_width: number, original_height: number, readable: boolean }[]>((resolve, reject) => {
let ready = false;
let files: string[] = [];
this.watcher = chokidar.watch(match, { ignored: /[\/\\]\.(svn|git|DS_Store)/, persistent: watch, followSymlinks: false });
const onFileChange = async (file: string) => {
const fileinfo = path.parse(file);
let outPath = fileinfo.name;
// with subfolders
if (options.destination) {
const from = path.resolve(options.baseDir, '..');
outPath = AssetConverter.replacePattern(options.destination, fileinfo.name, fileinfo, options, from);
}
log.info('Reexporting ' + outPath + fileinfo.ext);
switch (fileinfo.ext) {
case '.png':
case '.jpg':
case '.jpeg':
case '.hdr': {}
await this.exporter.copyImage(this.platform, file, outPath, {}, {});
break;
case '.ogg':
case '.mp3':
case '.flac':
case '.wav': {
await this.exporter.copySound(this.platform, file, outPath, {});
break;
}
case '.mp4':
case '.webm':
case '.mov':
case '.wmv':
case '.avi': {
await this.exporter.copyVideo(this.platform, file, outPath, {});
break;
}
case '.ttf':
await this.exporter.copyFont(this.platform, file, outPath, {});
break;
default:
await this.exporter.copyBlob(this.platform, file, outPath + fileinfo.ext, {});
}
for (let callback of Callbacks.postAssetReexporting) {
callback(outPath + fileinfo.ext);
}
};
this.watcher.on('add', (file: string) => {
if (ready) {
onFileChange(file);
}
else {
files.push(file);
}
});
if (watch) {
this.watcher.on('change', (file: string) => {
if (ready) {
onFileChange(file);
}
});
}
this.watcher.on('ready', async () => {
ready = true;
let parsedFiles: { name: string, from: string, type: string, files: string[], file_sizes: number[], original_width: number, original_height: number, readable: boolean }[] = [];
let cache: any = {};
let cachePath = path.join(temp, 'cache.json');
if (fs.existsSync(cachePath)) {
cache = JSON.parse(fs.readFileSync(cachePath, 'utf8'));
}
const self = this;
async function convertAsset( file: string, index: number ) {
let fileinfo = path.parse(file);
log.info('Exporting asset ' + (index + 1) + ' of ' + files.length + ' (' + fileinfo.base + ').');
const ext = fileinfo.ext.toLowerCase();
switch (ext) {
case '.png':
case '.jpg':
case '.jpeg':
case '.hdr': {
let exportInfo = AssetConverter.createExportInfo(fileinfo, false, options, self.exporter.options.from);
let images: { files: string[], sizes: number[] };
if (options.noprocessing) {
images = await self.exporter.copyBlob(self.platform, file, exportInfo.destination, options);
}
else {
images = await self.exporter.copyImage(self.platform, file, exportInfo.destination, options, cache);
}
if (!options.notinlist) {
parsedFiles.push({ name: exportInfo.name, from: file, type: 'image', files: images.files, file_sizes: images.sizes, original_width: options.original_width, original_height: options.original_height, readable: options.readable });
}
break;
}
case '.ogg':
case '.mp3':
case '.flac':
case '.wav': {
let exportInfo = AssetConverter.createExportInfo(fileinfo, false, options, self.exporter.options.from);
let sounds: { files: string[], sizes: number[] };
if (options.noprocessing) {
sounds = await self.exporter.copyBlob(self.platform, file, exportInfo.destination, options);
}
else {
sounds = await self.exporter.copySound(self.platform, file, exportInfo.destination, options);
}
if (sounds.files.length === 0) {
throw 'Audio file ' + file + ' could not be exported, you have to specify a path to ffmpeg.';
}
if (!options.notinlist) {
parsedFiles.push({ name: exportInfo.name, from: file, type: 'sound', files: sounds.files, file_sizes: sounds.sizes, original_width: undefined, original_height: undefined, readable: undefined });
}
break;
}
case '.ttf': {
let exportInfo = AssetConverter.createExportInfo(fileinfo, false, options, self.exporter.options.from);
let fonts: { files: string[], sizes: number[] };
if (options.noprocessing) {
fonts = await self.exporter.copyBlob(self.platform, file, exportInfo.destination, options);
}
else {
fonts = await self.exporter.copyFont(self.platform, file, exportInfo.destination, options);
}
if (!options.notinlist) {
parsedFiles.push({ name: exportInfo.name, from: file, type: 'font', files: fonts.files, file_sizes: fonts.sizes, original_width: undefined, original_height: undefined, readable: undefined });
}
break;
}
case '.mp4':
case '.webm':
case '.mov':
case '.wmv':
case '.avi': {
let exportInfo = AssetConverter.createExportInfo(fileinfo, false, options, self.exporter.options.from);
let videos: { files: string[], sizes: number[] };
if (options.noprocessing) {
videos = await self.exporter.copyBlob(self.platform, file, exportInfo.destination, options);
}
else {
videos = await self.exporter.copyVideo(self.platform, file, exportInfo.destination, options);
}
if (videos.files.length === 0) {
log.error('Video file ' + file + ' could not be exported, you have to specify a path to ffmpeg.');
}
if (!options.notinlist) {
parsedFiles.push({ name: exportInfo.name, from: file, type: 'video', files: videos.files, file_sizes: videos.sizes, original_width: undefined, original_height: undefined, readable: undefined });
}
break;
}
default: {
let exportInfo = AssetConverter.createExportInfo(fileinfo, true, options, self.exporter.options.from);
let blobs = await self.exporter.copyBlob(self.platform, file, exportInfo.destination, options);
if (!options.notinlist) {
parsedFiles.push({ name: exportInfo.name, from: file, type: 'blob', files: blobs.files, file_sizes: blobs.sizes, original_width: undefined, original_height: undefined, readable: undefined });
}
break;
}
}
}
if (this.options.parallelAssetConversion !== 0) {
let todo = files.map((file, index) => {
return async () => {
await convertAsset(file, index);
};
});
let processes = this.options.parallelAssetConversion === -1
? require('os').cpus().length - 1
: this.options.parallelAssetConversion;
await Throttle.all(todo, {
maxInProgress: processes,
});
} else {
let index = 0;
for (let file of files) {
await convertAsset(file, index);
index += 1;
}
}
fs.ensureDirSync(temp);
fs.writeFileSync(cachePath, JSON.stringify(cache), { encoding: 'utf8'});
resolve(parsedFiles);
});
});
}
async run(watch: boolean, temp: string): Promise<{ name: string, from: string, type: string, files: string[], file_sizes: number[], original_width: number, original_height: number, readable: boolean }[]> {
let files: { name: string, from: string, type: string, files: string[], file_sizes: number[], original_width: number, original_height: number, readable: boolean }[] = [];
for (let matcher of this.assetMatchers) {
files = files.concat(await this.watch(watch, matcher.match, temp, matcher.options));
}
return files;
}
}

View File

@ -0,0 +1,5 @@
export let AudioApi = {
Default: 'default',
DirectSound: 'directsound',
WASAPI: 'wasapi'
};

View File

@ -0,0 +1,5 @@
export class Color {
red: number;
green: number;
blue: number;
}

View File

@ -0,0 +1,45 @@
import * as child_process from 'child_process';
import * as fs from 'fs';
import * as log from './log';
export function convert(inFilename: string, outFilename: string, encoder: string, args: Array<string> = null): Promise<boolean> {
return new Promise((resolve, reject) => {
if (fs.existsSync(outFilename.toString()) && fs.statSync(outFilename.toString()).mtime.getTime() > fs.statSync(inFilename.toString()).mtime.getTime()) {
resolve(true);
return;
}
if (!encoder) {
resolve(false);
return;
}
let dirend = Math.max(encoder.lastIndexOf('/'), encoder.lastIndexOf('\\'));
let firstspace = encoder.indexOf(' ', dirend);
let exe = encoder.substr(0, firstspace);
let parts = encoder.substr(firstspace + 1).split(' ');
let options: string[] = [];
for (let i = 0; i < parts.length; ++i) {
let foundarg = false;
if (args !== null) {
for (let arg in args) {
if (parts[i] === '{' + arg + '}') {
options.push(args[arg]);
foundarg = true;
break;
}
}
}
if (foundarg) continue;
if (parts[i] === '{in}') options.push(inFilename.toString());
else if (parts[i] === '{out}') options.push(outFilename.toString());
else options.push(parts[i]);
}
// About stdio ignore: https://stackoverflow.com/a/20792428
let process = child_process.spawn(exe, options, {stdio: 'ignore'});
process.on('close', (code: number) => {
resolve(code === 0);
});
});
}

View File

@ -0,0 +1,121 @@
import * as fs from 'fs-extra';
import * as path from 'path';
import {KhaExporter} from './KhaExporter';
import {convert} from '../Converter';
import {executeHaxe} from '../Haxe';
import {Options} from '../Options';
import {exportImage} from '../ImageTool';
import {Library} from '../Project';
const uuid = require('uuid');
export abstract class CSharpExporter extends KhaExporter {
constructor(options: Options) {
super(options);
fs.removeSync(path.join(this.options.to, this.sysdir() + '-build', 'Sources'));
}
includeFiles(dir: string, baseDir: string) {
if (!dir || !fs.existsSync(dir)) return;
let files = fs.readdirSync(dir);
for (let f in files) {
let file = path.join(dir, files[f]);
if (fs.existsSync(file) && fs.statSync(file).isDirectory()) this.includeFiles(file, baseDir);
else if (file.endsWith('.cs')) {
this.p('<Compile Include="' + path.relative(baseDir, file).replace(/\//g, '\\') + '" />', 2);
}
}
}
haxeOptions(name: string, targetOptions: any, defines: Array<string>) {
defines.push('no-root');
defines.push('no-compilation');
defines.push('sys_' + this.options.target);
defines.push('sys_g1');
defines.push('sys_g2');
defines.push('sys_a1');
defines.push('kha_cs');
defines.push('kha_' + this.options.target);
defines.push('kha_' + this.options.target + '_cs');
defines.push('kha_g1');
defines.push('kha_g2');
defines.push('kha_a1');
return {
from: this.options.from,
to: path.join(this.sysdir() + '-build', 'Sources'),
sources: this.sources,
libraries: this.libraries,
defines: defines,
parameters: this.parameters,
haxeDirectory: this.options.haxe,
system: this.sysdir(),
language: 'cs',
width: this.width,
height: this.height,
name: name,
main: this.options.main,
};
}
async export(name: string, targetOptions: any, haxeOptions: any): Promise<void> {
if (this.projectFiles) {
const projectUuid: string = uuid.v4();
this.exportSLN(projectUuid);
this.exportCsProj(projectUuid);
this.exportResources();
}
}
exportSLN(projectUuid: string) {
fs.ensureDirSync(path.join(this.options.to, this.sysdir() + '-build'));
this.writeFile(path.join(this.options.to, this.sysdir() + '-build', 'Project.sln'));
const solutionUuid = uuid.v4();
this.p('Microsoft Visual Studio Solution File, Format Version 11.00');
this.p('# Visual Studio 2010');
this.p('Project("{' + solutionUuid.toString().toUpperCase() + '}") = "HaxeProject", "Project.csproj", "{' + projectUuid.toString().toUpperCase() + '}"');
this.p('EndProject');
this.p('Global');
this.p('GlobalSection(SolutionConfigurationPlatforms) = preSolution', 1);
this.p('Debug|x86 = Debug|x86', 2);
this.p('Release|x86 = Release|x86', 2);
this.p('EndGlobalSection', 1);
this.p('GlobalSection(ProjectConfigurationPlatforms) = postSolution', 1);
this.p('{' + projectUuid.toString().toUpperCase() + '}.Debug|x86.ActiveCfg = Debug|x86', 2);
this.p('{' + projectUuid.toString().toUpperCase() + '}.Debug|x86.Build.0 = Debug|x86', 2);
this.p('{' + projectUuid.toString().toUpperCase() + '}.Release|x86.ActiveCfg = Release|x86', 2);
this.p('{' + projectUuid.toString().toUpperCase() + '}.Release|x86.Build.0 = Release|x86', 2);
this.p('EndGlobalSection', 1);
this.p('GlobalSection(SolutionProperties) = preSolution', 1);
this.p('HideSolutionNode = FALSE', 2);
this.p('EndGlobalSection', 1);
this.p('EndGlobal');
this.closeFile();
}
abstract backend(): string;
abstract exportCsProj(projectUuid: string): void;
abstract exportResources(): void;
async copySound(platform: string, from: string, to: string) {
return { files: [to], sizes: [1] };
}
async copyImage(platform: string, from: string, to: string, asset: any, cache: any) {
let format = await exportImage(this.options.kha, this.options.kraffiti, from, path.join(this.options.to, this.sysdir(), to), asset, undefined, false, false, cache);
return { files: [to + '.' + format], sizes: [1] };
}
async copyBlob(platform: string, from: string, to: string) {
fs.copySync(from, path.join(this.options.to, this.sysdir(), to), { overwrite: true });
return { files: [to], sizes: [1] };
}
async copyVideo(platform: string, from: string, to: string) {
return { files: [to], sizes: [1] };
}
}

View File

@ -0,0 +1,9 @@
import {Html5Exporter} from './Html5Exporter';
import {Options} from '../Options';
export class DebugHtml5Exporter extends Html5Exporter {
constructor(options: Options) {
super(options);
this.isDebug = true;
}
}

View File

@ -0,0 +1,88 @@
import * as child_process from 'child_process';
import * as fs from 'fs-extra';
import * as path from 'path';
import {KhaExporter} from './KhaExporter';
import {convert} from '../Converter';
import {executeHaxe} from '../Haxe';
import {Options} from '../Options';
import {exportImage} from '../ImageTool';
import * as log from '../log';
import {Library} from '../Project';
export class EmptyExporter extends KhaExporter {
constructor(options: Options) {
super(options);
}
backend(): string {
return 'Empty';
}
haxeOptions(name: string, targetOptions: any, defines: Array<string>) {
defines.push('sys_g1');
defines.push('sys_g2');
defines.push('sys_g3');
defines.push('sys_g4');
defines.push('sys_a1');
defines.push('sys_a2');
defines.push('kha_g1');
defines.push('kha_g2');
defines.push('kha_g3');
defines.push('kha_g4');
defines.push('kha_a1');
defines.push('kha_a2');
return {
from: this.options.from,
to: path.join(this.sysdir(), 'docs.xml'),
sources: this.sources,
libraries: this.libraries,
defines: defines,
parameters: this.parameters,
haxeDirectory: this.options.haxe,
system: this.sysdir(),
language: 'xml',
width: this.width,
height: this.height,
name: name,
main: this.options.main,
};
}
async export(name: string, _targetOptions: any, haxeOptions: any): Promise<void> {
fs.ensureDirSync(path.join(this.options.to, this.sysdir()));
try {
// Remove any @:export first
await executeHaxe(this.options.to, this.options.haxe, ['project-' + this.sysdir() + '.hxml']);
let doxresult = child_process.spawnSync('haxelib', ['run', 'dox', '-in', 'kha.*', '-i', path.join(this.sysdir(), 'docs.xml')], { env: process.env, cwd: path.normalize(this.options.to) });
if (doxresult.stdout.toString() !== '') {
log.info(doxresult.stdout.toString());
}
if (doxresult.stderr.toString() !== '') {
log.error(doxresult.stderr.toString());
}
}
catch (error) {
}
}
async copySound(platform: string, from: string, to: string) {
return { files: [''], sizes: [0] };
}
async copyImage(platform: string, from: string, to: string, asset: any) {
return { files: [''], sizes: [0] };
}
async copyBlob(platform: string, from: string, to: string) {
return { files: [''], sizes: [0] };
}
async copyVideo(platform: string, from: string, to: string) {
return { files: [''], sizes: [0] };
}
}

View File

@ -0,0 +1,36 @@
import * as fs from 'fs-extra';
export class Exporter {
out: number;
constructor() {
}
writeFile(file: string) {
this.out = fs.openSync(file, 'w');
}
closeFile() {
fs.closeSync(this.out);
}
p(line: string = '', indent: number = 0) {
let tabs = '';
for (let i = 0; i < indent; ++i) tabs += '\t';
let data = Buffer.from(tabs + line + '\n');
fs.writeSync(this.out, data, 0, data.length, null);
}
copyFile(from: string, to: string) {
fs.copySync(from, to, { overwrite: true });
}
copyDirectory(from: string, to: string) {
fs.copySync(from, to, { overwrite: true });
}
createDirectory(dir: string) {
fs.ensureDirSync(dir);
}
}

View File

@ -0,0 +1,148 @@
import * as fs from 'fs-extra';
import * as path from 'path';
import {KhaExporter} from './KhaExporter';
import {convert} from '../Converter';
import {executeHaxe} from '../Haxe';
import {Options} from '../Options';
import {exportImage} from '../ImageTool';
import {Library} from '../Project';
function adjustFilename(filename: string): string {
filename = filename.replace(/\./g, '_');
filename = filename.replace(/-/g, '_');
filename = filename.replace(/\//g, '_');
return filename;
}
export class FlashExporter extends KhaExporter {
images: Array<string>;
sounds: Array<string>;
blobs: Array<string>;
constructor(options: Options) {
super(options);
this.images = [];
this.sounds = [];
this.blobs = [];
}
backend(): string {
return 'Flash';
}
haxeOptions(name: string, targetOptions: any, defines: Array<string>) {
defines.push('swf-script-timeout=60');
defines.push('sys_' + this.options.target);
defines.push('sys_g1');
defines.push('sys_g2');
defines.push('sys_g3');
defines.push('sys_g4');
defines.push('sys_a1');
defines.push('sys_a2');
defines.push('kha_' + this.options.target);
defines.push('kha_stage3d');
defines.push('kha_g1');
defines.push('kha_g2');
defines.push('kha_g3');
defines.push('kha_g4');
defines.push('kha_a1');
defines.push('kha_a2');
if (this.options.embedflashassets) defines.push('KHA_EMBEDDED_ASSETS');
let defaultFlashOptions = {
framerate : 60,
stageBackground : 'ffffff',
swfVersion : '16.0'
};
let flashOptions = targetOptions ? (targetOptions.flash ? targetOptions.flash : defaultFlashOptions) : defaultFlashOptions;
return {
from: this.options.from,
to: path.join(this.sysdir(), 'kha.swf'),
sources: this.sources,
libraries: this.libraries,
defines: defines,
parameters: this.parameters,
haxeDirectory: this.options.haxe,
system: this.sysdir(),
language: 'as',
width: this.width,
height: this.height,
name: name,
main: this.options.main,
framerate: 'framerate' in flashOptions ? flashOptions.framerate : defaultFlashOptions.framerate,
stageBackground: 'stageBackground' in flashOptions ? flashOptions.stageBackground : defaultFlashOptions.stageBackground,
swfVersion : 'swfVersion' in flashOptions ? flashOptions.swfVersion : defaultFlashOptions.swfVersion,
};
}
async export(name: string, targetOptions: any, haxeOptions: any): Promise<void> {
if (this.options.embedflashassets) {
this.writeFile(path.join(this.options.to, '..', 'Sources', 'Assets.hx'));
this.p('package;');
this.p();
this.p('import flash.display.BitmapData;');
this.p('import flash.media.Sound;');
this.p('import flash.utils.ByteArray;');
this.p();
for (let image of this.images) {
this.p('@:bitmap("flash/' + image + '") class Assets_' + adjustFilename(image) + ' extends BitmapData { }');
}
this.p();
for (let sound of this.sounds) {
this.p('@:file("flash/' + sound + '") class Assets_' + adjustFilename(sound) + ' extends ByteArray { }');
}
this.p();
for (let blob of this.blobs) {
this.p('@:file("flash/' + blob + '") class Assets_' + adjustFilename(blob) + ' extends ByteArray { }');
}
this.p();
this.p('class Assets {');
this.p('public static function visit(): Void {', 1);
this.p('', 2);
this.p('}', 1);
this.p('}');
this.closeFile();
}
}
async copySound(platform: string, from: string, to: string) {
fs.ensureDirSync(path.join(this.options.to, this.sysdir(), path.dirname(to)));
await convert(from, path.join(this.options.to, this.sysdir(), to + '.mp3'), this.options.mp3);
return { files: [to + '.mp3'], sizes: [1] };
}
async copyImage(platform: string, from: string, to: string, asset: any, cache: any) {
let format = await exportImage(this.options.kha, this.options.kraffiti, from, path.join(this.options.to, this.sysdir(), to), asset, undefined, false, false, cache);
if (this.options.embedflashassets) this.images.push(to + '.' + format);
return { files: [to + '.' + format], sizes: [1] };
}
async copyBlob(platform: string, from: string, to: string) {
fs.copySync(from.toString(), path.join(this.options.to, this.sysdir(), to), { overwrite: true });
if (this.options.embedflashassets) this.blobs.push(to);
return { files: [to], sizes: [1] };
}
async copyVideo(platform: string, from: string, to: string) {
fs.ensureDirSync(path.join(this.options.to, this.sysdir(), path.dirname(to)));
await convert(from, path.join(this.options.to, this.sysdir(), to + '.mp4'), this.options.h264);
return { files: [to + '.mp4'], sizes: [1] };
}
addShader(shader: string) {
if (this.options.embedflashassets) this.blobs.push(shader);
}
}

View File

@ -0,0 +1,274 @@
import * as fs from 'fs-extra';
import * as path from 'path';
import {KhaExporter} from './KhaExporter';
import {convert} from '../Converter';
import {executeHaxe} from '../Haxe';
import {Options} from '../Options';
import {exportImage} from '../ImageTool';
import {Library} from '../Project';
import {VrApi} from '../VrApi';
export class Html5Exporter extends KhaExporter {
width: number;
height: number;
isDebug: boolean;
constructor(options: Options) {
super(options);
}
backend(): string {
return 'HTML5';
}
isADebugTarget() {
return this.isDebug;
}
isDebugHtml5() {
return this.sysdir() === 'debug-html5';
}
isNode() {
return false;
}
isHtml5Worker() {
return this.sysdir() === 'html5worker';
}
haxeOptions(name: string, targetOptions: any, defines: Array<string>) {
defines.push('sys_g1');
defines.push('sys_g2');
defines.push('sys_g3');
defines.push('sys_a1');
defines.push('sys_a2');
defines.push('kha_js');
defines.push('kha_g1');
defines.push('kha_g2');
defines.push('kha_g3');
defines.push('kha_a1');
defines.push('kha_a2');
if (targetOptions.html5.noKeyboard) {
defines.push('kha_no_keyboard');
}
if (targetOptions.html5.disableContextMenu) {
defines.push('kha_disable_context_menu');
}
if (this.options.vr === VrApi.WebVR) {
defines.push('kha_webvr');
}
let canvasId = targetOptions.html5.canvasId == null ? 'khanvas' : targetOptions.html5.canvasId;
defines.push('canvas_id=' + canvasId);
let scriptName = this.isHtml5Worker() ? 'khaworker' : 'kha';
if (targetOptions.html5.scriptName != null && !(this.isNode() || this.isDebugHtml5())) {
scriptName = targetOptions.html5.scriptName;
}
defines.push('script_name=' + scriptName);
let webgl = targetOptions.html5.webgl == null ? true : targetOptions.html5.webgl;
if (webgl) {
defines.push('sys_g4');
defines.push('kha_g4');
defines.push('kha_webgl');
} else {
defines.push('kha_html5_canvas');
}
if (this.isNode()) {
defines.push('nodejs');
defines.push('sys_node');
defines.push('sys_server');
defines.push('kha_node');
defines.push('kha_server');
}
else {
defines.push('sys_' + this.options.target);
defines.push('kha_' + this.options.target);
defines.push('kha_' + this.options.target + '_js');
defines.push('sys_html5');
defines.push('kha_html5');
defines.push('kha_html5_js');
}
if (this.isADebugTarget()) {
this.parameters.push('-debug');
defines.push('sys_debug_html5');
defines.push('kha_debug_html5');
defines.push('kha_html5');
}
if (this.isHtml5Worker()) {
defines.push('js-classic');
}
return {
from: this.options.from.toString(),
to: path.join(this.sysdir(), scriptName + '.js'),
sources: this.sources,
libraries: this.libraries,
defines: defines,
parameters: this.parameters,
haxeDirectory: this.options.haxe,
system: this.sysdir(),
language: 'js',
width: this.width,
height: this.height,
name: name,
main: this.options.main,
};
}
async export(name: string, _targetOptions: any, haxeOptions: any): Promise<void> {
let targetOptions = {
canvasId: 'khanvas',
scriptName: this.isHtml5Worker() ? 'khaworker' : 'kha',
unsafeEval: false,
expose: ''
};
if (_targetOptions != null && _targetOptions.html5 != null) {
let userOptions = _targetOptions.html5;
if (userOptions.canvasId != null) targetOptions.canvasId = userOptions.canvasId;
if (userOptions.scriptName != null) targetOptions.scriptName = userOptions.scriptName;
if (userOptions.unsafeEval != null) targetOptions.unsafeEval = userOptions.unsafeEval;
if (userOptions.expose != null) targetOptions.expose = userOptions.expose;
}
fs.ensureDirSync(path.join(this.options.to, this.sysdir()));
if (this.isADebugTarget()) { // support custom debug-html5 based targets
let electron = path.join(this.options.to, this.sysdir(), 'electron.js');
let protoelectron = fs.readFileSync(path.join(__dirname, '..', '..', 'Data', 'debug-html5', 'electron.js'), {encoding: 'utf8'});
protoelectron = protoelectron.replace(/{Width}/g, '' + this.width);
protoelectron = protoelectron.replace(/{Height}/g, '' + this.height);
protoelectron = protoelectron.replace(/{ext}/g, process.platform === 'win32' ? '\'.ico\'' : '\'.png\'');
fs.writeFileSync(electron.toString(), protoelectron);
let pack = path.join(this.options.to, this.sysdir(), 'package.json');
let protopackage = fs.readFileSync(path.join(__dirname, '..', '..', 'Data', 'debug-html5', 'package.json'), {encoding: 'utf8'});
protopackage = protopackage.replace(/{Name}/g, name);
fs.writeFileSync(pack.toString(), protopackage);
let index = path.join(this.options.to, this.sysdir(), 'index.html');
let protoindex = fs.readFileSync(path.join(__dirname, '..', '..', 'Data', 'debug-html5', 'index.html'), {encoding: 'utf8'});
protoindex = protoindex.replace(/{Name}/g, name);
protoindex = protoindex.replace(/{Width}/g, '' + this.width);
protoindex = protoindex.replace(/{Height}/g, '' + this.height);
protoindex = protoindex.replace(/{CanvasId}/g, '' + targetOptions.canvasId);
protoindex = protoindex.replace(/{ScriptName}/g, '' + targetOptions.scriptName);
protoindex = protoindex.replace(/{UnsafeEval}/g, targetOptions.unsafeEval ? '\'unsafe-eval\'' : '');
fs.writeFileSync(index.toString(), protoindex);
let preload = path.join(this.options.to, this.sysdir(), 'preload.js');
let protopreload = fs.readFileSync(path.join(__dirname, '..', '..', 'Data', 'debug-html5', 'preload.js'), {encoding: 'utf8'});
protopreload = protopreload.replace(/{Expose}/g, targetOptions.expose);
fs.writeFileSync(preload.toString(), protopreload);
}
else if (this.isNode()) {
let pack = path.join(this.options.to, this.sysdir(), 'package.json');
let protopackage = fs.readFileSync(path.join(__dirname, '..', '..', 'Data', 'node', 'package.json'), 'utf8');
protopackage = protopackage.replace(/{Name}/g, name);
fs.writeFileSync(pack, protopackage);
let protoserver = fs.readFileSync(path.join(__dirname, '..', '..', 'Data', 'node', 'server.js'), 'utf8');
fs.writeFileSync(path.join(this.options.to, this.sysdir(), 'server.js'), protoserver);
}
else if (!this.isHtml5Worker()) {
let index = path.join(this.options.to, this.sysdir(), 'index.html');
if (!fs.existsSync(index)) {
let protoindex = fs.readFileSync(path.join(__dirname, '..', '..', 'Data', 'html5', 'index.html'), {encoding: 'utf8'});
protoindex = protoindex.replace(/{Name}/g, name);
protoindex = protoindex.replace(/{Width}/g, '' + this.width);
protoindex = protoindex.replace(/{Height}/g, '' + this.height);
protoindex = protoindex.replace(/{CanvasId}/g, '' + targetOptions.canvasId);
protoindex = protoindex.replace(/{ScriptName}/g, '' + targetOptions.scriptName);
fs.writeFileSync(index.toString(), protoindex);
}
}
}
/*copyMusic(platform, from, to, encoders, callback) {
Files.createDirectories(this.directory.resolve(this.sysdir()).resolve(to).parent());
Converter.convert(from, this.directory.resolve(this.sysdir()).resolve(to + '.ogg'), encoders.oggEncoder, (ogg) => {
Converter.convert(from, this.directory.resolve(this.sysdir()).resolve(to + '.mp4'), encoders.aacEncoder, (mp4) => {
var files = [];
if (ogg) files.push(to + '.ogg');
if (mp4) files.push(to + '.mp4');
callback(files);
});
});
}*/
async copySound(platform: string, from: string, to: string, options: any) {
fs.ensureDirSync(path.join(this.options.to, this.sysdir(), path.dirname(to)));
let ogg = await convert(from, path.join(this.options.to, this.sysdir(), to + '.ogg'), this.options.ogg);
let ogg_size = (await fs.stat(path.join(this.options.to, this.sysdir(), to + '.ogg'))).size;
let mp4 = false;
let mp4_size = 0;
let mp3 = false;
let mp3_size = 0;
if (!this.isDebugHtml5()) {
mp4 = await convert(from, path.join(this.options.to, this.sysdir(), to + '.mp4'), this.options.aac);
if (mp4) {
mp4_size = (await fs.stat(path.join(this.options.to, this.sysdir(), to + '.mp4'))).size;
}
if (!mp4) {
mp3 = await convert(from, path.join(this.options.to, this.sysdir(), to + '.mp3'), this.options.mp3);
mp3_size = (await fs.stat(path.join(this.options.to, this.sysdir(), to + '.mp3'))).size;
}
}
let files: string[] = [];
let sizes: number[] = [];
if (ogg) { files.push(to + '.ogg'); sizes.push(ogg_size); }
if (mp4) { files.push(to + '.mp4'); sizes.push(mp4_size); }
if (mp3) { files.push(to + '.mp3'); sizes.push(mp3_size); }
return { files: files, sizes: sizes };
}
async copyImage(platform: string, from: string, to: string, options: any, cache: any) {
let format = await exportImage(this.options.kha, this.options.kraffiti, from, path.join(this.options.to, this.sysdir(), to), options, undefined, false, false, cache);
let stat = await fs.stat(path.join(this.options.to, this.sysdir(), to + '.' + format));
let size = stat.size;
return { files: [to + '.' + format], sizes: [size]};
}
async copyBlob(platform: string, from: string, to: string, options: any) {
fs.copySync(from.toString(), path.join(this.options.to, this.sysdir(), to), { overwrite: true, dereference: true });
let stat = await fs.stat(path.join(this.options.to, this.sysdir(), to));
let size = stat.size;
return { files: [to], sizes: [size]};
}
async copyVideo(platform: string, from: string, to: string, options: any) {
fs.ensureDirSync(path.join(this.options.to, this.sysdir(), path.dirname(to)));
let mp4 = false;
let mp4_size = 0;
if (!this.isDebugHtml5()) {
mp4 = await convert(from, path.join(this.options.to, this.sysdir(), to + '.mp4'), this.options.h264);
mp4_size = (await fs.stat(path.join(this.options.to, this.sysdir(), to + '.mp4'))).size;
}
let webm = await convert(from, path.join(this.options.to, this.sysdir(), to + '.webm'), this.options.webm);
let webm_size = (await fs.stat(path.join(this.options.to, this.sysdir(), to + '.webm'))).size;
let files: string[] = [];
let sizes: number[] = [];
if (mp4) { files.push(to + '.mp4'); sizes.push(mp4_size); }
if (webm) { files.push(to + '.webm'); sizes.push(webm_size); }
return { files: files, sizes: sizes };
}
}

View File

@ -0,0 +1,13 @@
import * as path from 'path';
import {Html5Exporter} from './Html5Exporter';
import {Options} from '../Options';
export class Html5WorkerExporter extends Html5Exporter {
constructor(options: Options) {
super(options);
}
backend(): string {
return 'HTML5-Worker';
}
}

View File

@ -0,0 +1,117 @@
import * as fs from 'fs-extra';
import * as path from 'path';
import {KhaExporter} from './KhaExporter';
import {convert} from '../Converter';
import {executeHaxe} from '../Haxe';
import {Options} from '../Options';
import {exportImage} from '../ImageTool';
import {Library} from '../Project';
export class JavaExporter extends KhaExporter {
constructor(options: Options) {
super(options);
}
haxeOptions(name: string, targetOptions: any, defines: Array<string>) {
const sources = path.join(this.options.to, this.sysdir(), 'Sources');
if (fs.existsSync(sources)) {
fs.removeSync(sources);
}
defines.push('no-compilation');
defines.push('sys_' + this.options.target);
defines.push('sys_g1');
defines.push('sys_g2');
defines.push('sys_a1');
defines.push('kha_' + this.options.target);
if (this.options.target !== 'java') {
defines.push('kha_java');
defines.push('kha_' + this.options.target + '_java');
}
defines.push('kha_g1');
defines.push('kha_g2');
defines.push('kha_a1');
return {
from: this.options.from,
to: path.join(this.sysdir(), 'Sources'),
sources: this.sources,
libraries: this.libraries,
defines: defines,
parameters: this.parameters,
haxeDirectory: this.options.haxe,
system: this.sysdir(),
language: 'java',
width: this.width,
height: this.height,
name: name,
main: this.options.main,
};
}
async export(name: string, targetOptions: any, haxeOptions: any): Promise<void> {
fs.ensureDirSync(path.join(this.options.to, this.sysdir()));
this.exportEclipseProject();
}
backend() {
return 'Java';
}
exportEclipseProject() {
this.writeFile(path.join(this.options.to, this.sysdir(), '.classpath'));
this.p('<?xml version="1.0" encoding="UTF-8"?>');
this.p('<classpath>');
this.p('\t<classpathentry kind="src" path="Sources/src"/>');
this.p('\t<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>');
this.p('\t<classpathentry kind="output" path="bin"/>');
this.p('</classpath>');
this.closeFile();
this.writeFile(path.join(this.options.to, this.sysdir(), '.project'));
this.p('<?xml version="1.0" encoding="UTF-8"?>');
this.p('<projectDescription>');
this.p('\t<name>' + path.parse(this.options.to).name + '</name>');
this.p('\t<comment></comment>');
this.p('\t<projects>');
this.p('\t</projects>');
this.p('\t<buildSpec>');
this.p('\t\t<buildCommand>');
this.p('\t\t\t<name>org.eclipse.jdt.core.javabuilder</name>');
this.p('\t\t\t<arguments>');
this.p('\t\t\t</arguments>');
this.p('\t\t</buildCommand>');
this.p('\t</buildSpec>');
this.p('\t<natures>');
this.p('\t\t<nature>org.eclipse.jdt.core.javanature</nature>');
this.p('\t</natures>');
this.p('</projectDescription>');
this.closeFile();
}
/*copyMusic(platform, from, to, encoders) {
this.copyFile(from, this.directory.resolve(this.sysdir()).resolve(to + '.wav'));
callback([to + '.wav']);
}*/
async copySound(platform: string, from: string, to: string) {
fs.copySync(from.toString(), path.join(this.options.to, this.sysdir(), to + '.wav'), { overwrite: true });
return { files: [to + '.wav'], sizes: [1] };
}
async copyImage(platform: string, from: string, to: string, asset: any, cache: any) {
let format = await exportImage(this.options.kha, this.options.kraffiti, from, path.join(this.options.to, this.sysdir(), to), asset, undefined, false, false, cache);
return { files: [to + '.' + format], sizes: [1] };
}
async copyBlob(platform: string, from: string, to: string) {
fs.copySync(from.toString(), path.join(this.options.to, this.sysdir(), to), { overwrite: true });
return { files: [to], sizes: [1] };
}
async copyVideo(platform: string, from: string, to: string) {
return { files: [to], sizes: [1] };
}
}

View File

@ -0,0 +1,101 @@
import * as path from 'path';
import {convert} from '../Converter';
import {Exporter} from './Exporter';
import {Options} from '../Options';
import {Library} from '../Project';
export abstract class KhaExporter extends Exporter {
width: number;
height: number;
sources: string[];
libraries: Library[];
name: string;
safename: string;
options: Options;
projectFiles: boolean;
parameters: string[];
systemDirectory: string;
constructor(options: Options) {
super();
this.options = options;
this.width = 640;
this.height = 480;
this.sources = [];
this.libraries = [];
this.addSourceDirectory(path.join(options.kha, 'Sources'));
this.projectFiles = !options.noproject;
this.parameters = [];
// this.parameters = ['--macro kha.internal.GraphicsBuilder.build("' + this.backend().toLowerCase() + '")'];
this.addSourceDirectory(path.join(options.kha, 'Backends', this.backend()));
}
sysdir(): string {
return this.systemDirectory;
}
abstract backend(): string;
abstract haxeOptions(name: string, targetOptions: any, defines: Array<string>): any;
async export(name: string, targetOptions: any, haxeOptions: any): Promise<void> {
return new Promise<void>((resolve, reject) => {
reject('Called an abstract function');
});
}
setWidthAndHeight(width: number, height: number): void {
this.width = width;
this.height = height;
}
setName(name: string): void {
this.name = name;
this.safename = name.replace(/ /g, '-');
}
setSystemDirectory(systemDirectory: string): void {
this.systemDirectory = systemDirectory;
}
addShader(shader: string): void {
}
addSourceDirectory(path: string): void {
this.sources.push(path);
}
addLibrary(library: Library): void {
this.libraries.push(library);
}
removeSourceDirectory(path: string): void {
for (let i = 0; i < this.sources.length; ++i) {
if (this.sources[i] === path) {
this.sources.splice(i, 1);
return;
}
}
}
async copyImage(platform: string, from: string, to: string, options: any, cache: any): Promise<{ files: Array<string>, sizes: Array<number>}> {
return {files: [], sizes: []};
}
async copySound(platform: string, from: string, to: string, options: any): Promise<{ files: Array<string>, sizes: Array<number>}> {
return {files: [], sizes: []};
}
async copyVideo(platform: string, from: string, to: string, options: any): Promise<{ files: Array<string>, sizes: Array<number>}> {
return {files: [], sizes: []};
}
async copyBlob(platform: string, from: string, to: string, options: any): Promise<{ files: Array<string>, sizes: Array<number>}> {
return {files: [], sizes: []};
}
async copyFont(platform: string, from: string, to: string, options: any): Promise<{ files: Array<string>, sizes: Array<number>}> {
return await this.copyBlob(platform, from, to + '.ttf', options);
}
}

View File

@ -0,0 +1,152 @@
import * as fs from 'fs-extra';
import * as path from 'path';
import * as defaults from '../defaults';
import {KhaExporter} from './KhaExporter';
import {convert} from '../Converter';
import {executeHaxe} from '../Haxe';
import {GraphicsApi} from '../GraphicsApi';
import {Platform} from '../Platform';
import {exportImage} from '../ImageTool';
import {Options} from '../Options';
import {Library} from '../Project';
export class KincExporter extends KhaExporter {
slowgc: boolean;
constructor(options: Options) {
super(options);
this.slowgc = options.slowgc;
// Files.removeDirectory(this.directory.resolve(Paths.get(this.sysdir() + "-build", "Sources")));
}
backend(): string {
return 'Kinc-hxcpp';
}
haxeOptions(name: string, targetOptions: any, defines: Array<string>) {
defines.push('no-compilation');
defines.push('include-prefix=hxinc');
if (!this.slowgc) {
defines.push('HXCPP_GC_GENERATIONAL');
}
defines.push('sys_' + this.options.target);
defines.push('sys_kore');
defines.push('sys_g1');
defines.push('sys_g2');
defines.push('sys_g3');
defines.push('sys_g4');
defines.push('sys_a1');
defines.push('sys_a2');
defines.push('kha_cpp');
defines.push('kha_' + this.options.target);
defines.push('kha_' + this.options.target + '_native');
defines.push('kha_' + this.options.target + '_cpp');
let graphics = this.options.graphics;
if (graphics === GraphicsApi.Default) {
graphics = defaults.graphicsApi(this.options.target);
}
defines.push('kha_' + graphics);
defines.push('kha_kore');
defines.push('kha_g1');
defines.push('kha_g2');
defines.push('kha_g3');
defines.push('kha_g4');
defines.push('kha_a1');
defines.push('kha_a2');
if (this.options.vr === 'gearvr') {
defines.push('vr_gearvr');
}
else if (this.options.vr === 'cardboard') {
defines.push('vr_cardboard');
}
else if (this.options.vr === 'rift') {
defines.push('vr_rift');
}
if (this.options.raytrace === 'dxr') {
defines.push('kha_dxr');
}
return {
from: this.options.from,
to: path.join(this.sysdir() + '-build', 'Sources'),
sources: this.sources,
libraries: this.libraries,
defines: defines,
parameters: this.parameters,
haxeDirectory: this.options.haxe,
system: this.sysdir(),
language: 'cpp',
width: this.width,
height: this.height,
name: name,
main: this.options.main,
};
}
async export(name: string, targetOptions: any, haxeOptions: any): Promise<void> {
}
async copySound(platform: string, from: string, to: string, options: any) {
if (options.quality < 1) {
fs.ensureDirSync(path.join(this.options.to, this.sysdir(), path.dirname(to)));
await convert(from, path.join(this.options.to, this.sysdir(), to + '.ogg'), this.options.ogg);
return { files: [to + '.ogg'], sizes: [1] };
}
else {
if (from.endsWith('.wav')) {
fs.copySync(from.toString(), path.join(this.options.to, this.sysdir(), to + '.wav'), { overwrite: true });
}
else {
throw 'Can not convert ' + from + ' to wav format.';
}
return { files: [to + '.wav'], sizes: [1] };
}
}
async copyImage(platform: string, from: string, to: string, options: any, cache: any) {
if (platform === Platform.iOS && options.quality < 1) {
let format = await exportImage(this.options.kha, this.options.kraffiti, from, path.join(this.options.to, this.sysdir(), to), options, 'pvr', true, false, cache);
return { files: [to + '.' + format], sizes: [1] };
}
else if (platform === Platform.Windows && options.quality < 1 && (this.options.graphics === GraphicsApi.OpenGL || this.options.graphics === GraphicsApi.Vulkan)) {
// let format = await exportImage(this.options.kha, from, path.join(this.options.to, this.sysdir(), to), options, 'ASTC', true, false, cache);
let format = await exportImage(this.options.kha, this.options.kraffiti, from, path.join(this.options.to, this.sysdir(), to), options, 'DXT5', true, false, cache);
return { files: [to + '.' + format], sizes: [1] };
}
else {
let format = await exportImage(this.options.kha, this.options.kraffiti, from, path.join(this.options.to, this.sysdir(), to), options, 'lz4', true, false, cache);
return { files: [to + '.' + format], sizes: [1] };
}
}
async copyBlob(platform: string, from: string, to: string) {
fs.copySync(from.toString(), path.join(this.options.to, this.sysdir(), to), { overwrite: true });
return { files: [to], sizes: [1] };
}
async copyVideo(platform: string, from: string, to: string) {
fs.ensureDirSync(path.join(this.options.to, this.sysdir(), path.dirname(to)));
if (platform === Platform.Windows) {
await convert(from, path.join(this.options.to, this.sysdir(), to + '.avi'), this.options.h264);
return { files: [to + '.avi'], sizes: [1] };
}
else if (platform === Platform.iOS || platform === Platform.OSX) {
await convert(from, path.join(this.options.to, this.sysdir(), to + '.mp4'), this.options.h264);
return { files: [to + '.mp4'], sizes: [1] };
}
else if (platform === Platform.Android) {
await convert(from, path.join(this.options.to, this.sysdir(), to + '.ts'), this.options.h264);
return { files: [to + '.ts'], sizes: [1] };
}
else {
await convert(from, path.join(this.options.to, this.sysdir(), to + '.ogv'), this.options.theora);
return { files: [to + '.ogv'], sizes: [1] };
}
}
}

View File

@ -0,0 +1,136 @@
import * as fs from 'fs-extra';
import * as path from 'path';
import * as defaults from '../defaults';
import {KhaExporter} from './KhaExporter';
import {convert} from '../Converter';
import {executeHaxe} from '../Haxe';
import {GraphicsApi} from '../GraphicsApi';
import {Platform} from '../Platform';
import {exportImage} from '../ImageTool';
import {Options} from '../Options';
import {Library} from '../Project';
export class KincHLExporter extends KhaExporter {
constructor(options: Options) {
super(options);
// Files.removeDirectory(this.directory.resolve(Paths.get(this.sysdir() + "-build", "Sources")));
}
backend(): string {
return 'Kinc-HL';
}
haxeOptions(name: string, targetOptions: any, defines: Array<string>) {
defines.push('no-compilation');
defines.push('sys_' + this.options.target);
defines.push('sys_g1');
defines.push('sys_g2');
defines.push('sys_g3');
defines.push('sys_g4');
defines.push('sys_a1');
defines.push('sys_a2');
defines.push('kha_hl');
defines.push('kha_' + this.options.target);
defines.push('kha_' + this.options.target + '_hl');
let graphics = this.options.graphics;
if (graphics === GraphicsApi.Default) {
graphics = defaults.graphicsApi(this.options.target);
}
defines.push('kha_' + graphics);
defines.push('kha_g1');
defines.push('kha_g2');
defines.push('kha_g3');
defines.push('kha_g4');
defines.push('kha_a1');
defines.push('kha_a2');
if (this.options.vr === 'gearvr') {
defines.push('vr_gearvr');
}
else if (this.options.vr === 'cardboard') {
defines.push('vr_cardboard');
}
else if (this.options.vr === 'rift') {
defines.push('vr_rift');
}
if (this.options.raytrace === 'dxr') {
defines.push('kha_dxr');
}
return {
from: this.options.from,
to: path.join(this.sysdir() + '-build', 'sources.c'),
sources: this.sources,
libraries: this.libraries,
defines: defines,
parameters: this.parameters,
haxeDirectory: this.options.haxe,
system: this.sysdir(),
language: 'hl',
width: this.width,
height: this.height,
name: name,
main: this.options.main,
};
}
async export(name: string, targetOptions: any, haxeOptions: any): Promise<void> {
}
async copySound(platform: string, from: string, to: string, options: any) {
if (options.quality < 1) {
fs.ensureDirSync(path.join(this.options.to, this.sysdir(), path.dirname(to)));
let ogg = await convert(from, path.join(this.options.to, this.sysdir(), to + '.ogg'), this.options.ogg);
return { files: [to + '.ogg'], sizes: [1] };
}
else {
fs.copySync(from.toString(), path.join(this.options.to, this.sysdir(), to + '.wav'), { overwrite: true });
return { files: [to + '.wav'], sizes: [1] };
}
}
async copyImage(platform: string, from: string, to: string, options: any, cache: any) {
if (platform === Platform.iOS && options.quality < 1) {
let format = await exportImage(this.options.kha, this.options.kraffiti, from, path.join(this.options.to, this.sysdir(), to), options, 'pvr', true, false, cache);
return { files: [to + '.' + format], sizes: [1] };
}
else if (platform === Platform.Windows && options.quality < 1 && (this.options.graphics === GraphicsApi.OpenGL || this.options.graphics === GraphicsApi.Vulkan)) {
// let format = await exportImage(this.options.kha, from, path.join(this.options.to, this.sysdir(), to), options, 'ASTC', true, false, cache);
let format = await exportImage(this.options.kha, this.options.kraffiti, from, path.join(this.options.to, this.sysdir(), to), options, 'DXT5', true, false, cache);
return { files: [to + '.' + format], sizes: [1] };
}
else {
let format = await exportImage(this.options.kha, this.options.kraffiti, from, path.join(this.options.to, this.sysdir(), to), options, 'lz4', true, false, cache);
return { files: [to + '.' + format], sizes: [1] };
}
}
async copyBlob(platform: string, from: string, to: string) {
fs.copySync(from.toString(), path.join(this.options.to, this.sysdir(), to).toString(), { overwrite: true });
return { files: [to], sizes: [1] };
}
async copyVideo(platform: string, from: string, to: string) {
fs.ensureDirSync(path.join(this.options.to, this.sysdir(), path.dirname(to)));
if (platform === Platform.Windows) {
await convert(from, path.join(this.options.to, this.sysdir(), to + '.avi'), this.options.h264);
return { files: [to + '.avi'], sizes: [1] };
}
else if (platform === Platform.iOS || platform === Platform.OSX) {
await convert(from, path.join(this.options.to, this.sysdir(), to + '.mp4'), this.options.h264);
return { files: [to + '.mp4'], sizes: [1] };
}
else if (platform === Platform.Android) {
await convert(from, path.join(this.options.to, this.sysdir(), to + '.ts'), this.options.h264);
return { files: [to + '.ts'], sizes: [1] };
}
else {
await convert(from, path.join(this.options.to, this.sysdir(), to + '.ogv'), this.options.theora);
return { files: [to + '.ogv'], sizes: [1] };
}
}
}

View File

@ -0,0 +1,105 @@
import * as fs from 'fs-extra';
import * as path from 'path';
import * as defaults from '../defaults';
import {KhaExporter} from './KhaExporter';
import {convert} from '../Converter';
import {executeHaxe} from '../Haxe';
import {GraphicsApi} from '../GraphicsApi';
import {Options} from '../Options';
import {exportImage} from '../ImageTool';
import {Library} from '../Project';
export class KromExporter extends KhaExporter {
width: number;
height: number;
constructor(options: Options) {
super(options);
}
backend(): string {
return 'Krom';
}
haxeOptions(name: string, targetOptions: any, defines: Array<string>) {
defines.push('sys_' + this.options.target);
defines.push('sys_g1');
defines.push('sys_g2');
defines.push('sys_g3');
defines.push('sys_g4');
defines.push('sys_a1');
defines.push('sys_a2');
defines.push('kha_js');
defines.push('kha_' + this.options.target);
defines.push('kha_' + this.options.target + '_js');
let graphics = this.options.graphics;
if (graphics === GraphicsApi.Default) {
graphics = defaults.graphicsApi(this.options.target);
}
defines.push('kha_' + graphics);
defines.push('kha_g1');
defines.push('kha_g2');
defines.push('kha_g3');
defines.push('kha_g4');
defines.push('kha_a1');
defines.push('kha_a2');
if (this.options.debug) {
this.parameters.push('-debug');
defines.push('js-classic');
}
return {
from: this.options.from.toString(),
to: path.join(this.sysdir(), 'krom.js.temp'),
realto: path.join(this.sysdir(), 'krom.js'),
sources: this.sources,
libraries: this.libraries,
defines: defines,
parameters: this.parameters,
haxeDirectory: this.options.haxe,
system: this.sysdir(),
language: 'js',
width: this.width,
height: this.height,
name: name,
main: this.options.main,
};
}
async export(name: string, targetOptions: any, haxeOptions: any): Promise<void> {
fs.ensureDirSync(path.join(this.options.to, this.sysdir()));
}
async copySound(platform: string, from: string, to: string, options: any) {
if (options.quality < 1) {
fs.ensureDirSync(path.join(this.options.to, this.sysdir(), path.dirname(to)));
let ogg = await convert(from, path.join(this.options.to, this.sysdir(), to + '.ogg'), this.options.ogg);
return { files: [to + '.ogg'], sizes: [1] };
}
else {
fs.copySync(from.toString(), path.join(this.options.to, this.sysdir(), to + '.wav'), { overwrite: true });
return { files: [to + '.wav'], sizes: [1] };
}
}
async copyImage(platform: string, from: string, to: string, options: any, cache: any) {
let format = await exportImage(this.options.kha, this.options.kraffiti, from, path.join(this.options.to, this.sysdir(), to), options, undefined, false, false, cache);
return { files: [to + '.' + format], sizes: [1] };
}
async copyBlob(platform: string, from: string, to: string) {
fs.copySync(from.toString(), path.join(this.options.to, this.sysdir(), to), { overwrite: true });
return { files: [to], sizes: [1] };
}
async copyVideo(platform: string, from: string, to: string) {
fs.ensureDirSync(path.join(this.options.to, this.sysdir(), path.dirname(to)));
let webm = await convert(from, path.join(this.options.to, this.sysdir(), to + '.webm'), this.options.webm);
let files: string[] = [];
let sizes: number[] = [];
if (webm) { files.push(to + '.webm'); sizes.push(1); }
return { files: files, sizes: sizes };
}
}

View File

@ -0,0 +1,17 @@
import * as path from 'path';
import {Html5Exporter} from './Html5Exporter';
import {Options} from '../Options';
export class NodeExporter extends Html5Exporter {
constructor(options: Options) {
super(options);
}
backend(): string {
return 'Node';
}
isNode() {
return true;
}
}

View File

@ -0,0 +1,165 @@
import * as fs from 'fs-extra';
import * as path from 'path';
import {CSharpExporter} from './CSharpExporter';
import {convert} from '../Converter';
import {executeHaxe} from '../Haxe';
import {Options} from '../Options';
import {exportImage} from '../ImageTool';
const uuid = require('uuid');
export class PlayStationMobileExporter extends CSharpExporter {
files: Array<string>;
constructor(options: Options) {
super(options);
this.files = [];
}
backend() {
return 'PSM';
}
exportSLN(projectUuid: string) {
fs.ensureDirSync(path.join(this.options.to, this.sysdir() + '-build'));
this.writeFile(path.join(this.options.to, this.sysdir() + '-build', 'Project.sln'));
const solutionUuid = uuid.v4();
this.p('Microsoft Visual Studio Solution File, Format Version 11.00');
this.p('# Visual Studio 2010');
this.p('Project("{' + solutionUuid.toString().toUpperCase() + '}") = "HaxeProject", "Project.csproj", "{' + projectUuid.toString().toUpperCase() + '}"');
this.p('EndProject');
this.p('Global');
this.p('GlobalSection(SolutionConfigurationPlatforms) = preSolution', 1);
this.p('Debug|Any CPU = Debug|Any CPU', 2);
this.p('Release|Any CPU = Release|Any CPU', 2);
this.p('EndGlobalSection', 1);
this.p('GlobalSection(ProjectConfigurationPlatforms) = postSolution', 1);
this.p('{" + projectUuid.toString().toUpperCase() + "}.Debug|Any CPU.ActiveCfg = Debug|Any CPU', 2);
this.p('{" + projectUuid.toString().toUpperCase() + "}.Debug|Any CPU.Build.0 = Debug|Any CPU', 2);
this.p('{" + projectUuid.toString().toUpperCase() + "}.Release|Any CPU.ActiveCfg = Release|Any CPU', 2);
this.p('{" + projectUuid.toString().toUpperCase() + "}.Release|Any CPU.Build.0 = Release|Any CPU', 2);
this.p('EndGlobalSection', 1);
this.p('GlobalSection(MonoDevelopProperties) = preSolution', 1);
this.p('StartupItem = Project.csproj', 2);
this.p('EndGlobalSection', 1);
this.p('EndGlobal');
this.closeFile();
}
exportResources() {
this.createDirectory(path.join(this.options.to, this.sysdir() + '-build', 'shaders'));
fs.writeFileSync(path.join(this.options.to, this.sysdir() + '-build', 'shaders', 'Simple.fcg'),
'void main(float4 out Color : COLOR, uniform float4 MaterialColor) {\n'
+ '\tColor = MaterialColor;\n'
+ '}\n');
fs.writeFileSync(path.join(this.options.to, this.sysdir() + '-build', 'shaders', 'Simple.vcg'),
'void main(float4 in a_Position : POSITION, float4 out v_Position : POSITION, uniform float4x4 WorldViewProj) {\n'
+ '\tv_Position = mul(a_Position, WorldViewProj);\n'
+ '}\n');
fs.writeFileSync(path.join(this.options.to, this.sysdir() + '-build', 'shaders', 'Texture.fcg'),
'void main(float2 in v_TexCoord : TEXCOORD0, float4 out Color : COLOR, uniform sampler2D Texture0 : TEXUNIT0) {\n'
+ '\tColor = tex2D(Texture0, v_TexCoord);\n'
+ '}\n');
fs.writeFileSync(path.join(this.options.to, this.sysdir() + '-build', 'shaders', 'Texture.vcg'),
'void main(float4 in a_Position : POSITION, float2 in a_TexCoord : TEXCOORD0, float4 out v_Position : POSITION, float2 out v_TexCoord : TEXCOORD0, uniform float4x4 WorldViewProj) {\n'
+ '\tv_Position = mul(a_Position, WorldViewProj);\n'
+ '\tv_TexCoord = a_TexCoord;\n'
+ '}\n');
let appxml = path.join(this.options.to, this.sysdir() + '-build', 'app.xml');
if (!fs.existsSync(appxml)) {
let appxmltext = fs.readFileSync(path.join(__dirname, 'Data', 'psm', 'app.xml'), {encoding: 'utf8'});
fs.writeFileSync(appxml.toString(), appxmltext);
}
}
exportCsProj(projectUuid: string) {
this.writeFile(path.join(this.options.to, this.sysdir() + '-build', 'Project.csproj'));
this.p('<?xml version="1.0" encoding="utf-8"?>');
this.p('<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">');
this.p('<PropertyGroup>', 1);
this.p('<Configuration Condition=" \'$(Configuration)\' == \'\' ">Debug</Configuration>', 2);
this.p('<Platform Condition=" \'$(Platform)\' == \'\' ">AnyCPU</Platform>', 2);
this.p('<ProductVersion>10.0.0</ProductVersion>', 2);
this.p('<SchemaVersion>2.0</SchemaVersion>', 2);
this.p('<ProjectGuid>{' + projectUuid.toString().toUpperCase() + '}</ProjectGuid>', 2);
this.p('<ProjectTypeGuids>{69878862-DA7D-4DC6-B0A1-50D8FAB4242F};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>', 2);
this.p('<OutputType>Exe</OutputType>', 2);
this.p('<RootNamespace>PSTest</RootNamespace>', 2);
this.p('<AssemblyName>PSTest</AssemblyName>', 2);
this.p('</PropertyGroup>', 1);
this.p('<PropertyGroup Condition=" \'$(Configuration)|$(Platform)\' == \'Debug|AnyCPU\' ">', 1);
this.p('<DebugSymbols>true</DebugSymbols>', 2);
this.p('<DebugType>full</DebugType>', 2);
this.p('<Optimize>false</Optimize>', 2);
this.p('<OutputPath>bin\\Debug</OutputPath>', 2);
this.p('<DefineConstants>DEBUG;</DefineConstants>', 2);
this.p('<ErrorReport>prompt</ErrorReport>', 2);
this.p('<WarningLevel>4</WarningLevel>', 2);
this.p('<ConsolePause>false</ConsolePause>', 2);
this.p('</PropertyGroup>', 1);
this.p('<PropertyGroup Condition=" \'$(Configuration)|$(Platform)\' == \'Release|AnyCPU\' ">', 1);
this.p('<DebugType>none</DebugType>', 2);
this.p('<Optimize>true</Optimize>', 2);
this.p('<OutputPath>bin\\Release</OutputPath>', 2);
this.p('<ErrorReport>prompt</ErrorReport>', 2);
this.p('<WarningLevel>4</WarningLevel>', 2);
this.p('<ConsolePause>false</ConsolePause>', 2);
this.p('</PropertyGroup>', 1);
this.p('<ItemGroup>', 1);
this.p('<Reference Include="System" />', 2);
this.p('<Reference Include="System.Xml" />', 2);
this.p('<Reference Include="System.Core" />', 2);
this.p('<Reference Include="Sce.PlayStation.Core" />', 2);
this.p('</ItemGroup>', 1);
this.p('<ItemGroup>', 1);
this.includeFiles(path.join(this.options.to, this.sysdir() + '-build', 'Sources', 'src'), path.join(this.options.to, this.sysdir() + '-build'));
this.p('</ItemGroup>', 1);
this.p('<ItemGroup>', 1);
this.p('<ShaderProgram Include="shaders\\Simple.fcg" />', 2);
this.p('<ShaderProgram Include="shaders\\Simple.vcg" />', 2);
this.p('<ShaderProgram Include="shaders\\Texture.fcg" />', 2);
this.p('<ShaderProgram Include="shaders\\Texture.vcg" />', 2);
this.p('</ItemGroup>', 1);
this.p('<ItemGroup>', 1);
this.p('<Folder Include="resources\\" />', 2);
this.p('</ItemGroup>', 1);
this.p('<ItemGroup>', 1);
for (let file of this.files) {
this.p('<Content Include="..\\' + this.sysdir() + '\\' + file.toString() + '">', 2);
this.p('<Link>resources\\' + file.toString() + '</Link>', 3);
this.p('</Content>', 2);
}
this.p('</ItemGroup>', 1);
this.p('<Import Project="$(MSBuildExtensionsPath)\\Sce\\Sce.Psm.CSharp.targets" />', 1);
this.p('</Project>');
this.closeFile();
}
/*copyMusic(platform, from, to, encoders, callback) {
callback();
}*/
async copySound(platform: string, from: string, to: string) {
return { files: [''], sizes: [1] };
}
async copyImage(platform: string, from: string, to: string, asset: any, cache: any) {
this.files.push(asset['file']);
let format = await exportImage(this.options.kha, this.options.kraffiti, from, path.join(this.options.to, this.sysdir(), to), asset, undefined, false, false, cache);
return { files: [to + '.' + format], sizes: [1] };
}
async copyBlob(platform: string, from: string, to: string) {
fs.copySync(from.toString(), path.join(this.options.to, this.sysdir(), to), { overwrite: true });
this.files.push(to);
return { files: [to], sizes: [1] };
}
async copyVideo(platform: string, from: string, to: string) {
return { files: [''], sizes: [1] };
}
}

View File

@ -0,0 +1,287 @@
import * as fs from 'fs-extra';
import * as path from 'path';
import {CSharpExporter} from './CSharpExporter';
import {convert} from '../Converter';
import {executeHaxe} from '../Haxe';
import {Options} from '../Options';
import {exportImage} from '../ImageTool';
const uuid = require('uuid');
export class WpfExporter extends CSharpExporter {
constructor(options: Options) {
super(options);
}
backend() {
return 'WPF';
}
exportResources() {
fs.ensureDirSync(path.join(this.options.to, this.sysdir() + '-build', 'Properties'));
this.writeFile(path.join(this.options.to, this.sysdir() + '-build', 'Properties', 'AssemblyInfo.cs'));
this.p('using System.Reflection;');
this.p('using System.Resources;');
this.p('using System.Runtime.CompilerServices;');
this.p('using System.Runtime.InteropServices;');
this.p('using System.Windows;');
this.p();
this.p('[assembly: AssemblyTitle("HaxeProject")]');
this.p('[assembly: AssemblyDescription("")]');
this.p('[assembly: AssemblyConfiguration("")]');
this.p('[assembly: AssemblyCompany("Kha Development Team")]');
this.p('[assembly: AssemblyProduct("HaxeProject")]');
this.p('[assembly: AssemblyCopyright("Copyright ? Kha Development Team 2018")]');
this.p('[assembly: AssemblyTrademark("")]');
this.p('[assembly: AssemblyCulture("")]');
this.p();
this.p('[assembly: ComVisible(false)]');
this.p();
this.p('//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]');
this.p();
this.p('[assembly: ThemeInfo(ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)]');
this.p();
this.p('// [assembly: AssemblyVersion("1.0.*")]');
this.p('[assembly: AssemblyVersion("1.0.0.0")]');
this.p('[assembly: AssemblyFileVersion("1.0.0.0")]');
this.closeFile();
this.writeFile(path.join(this.options.to, this.sysdir() + '-build', 'Properties', 'Resources.Designer.cs'));
this.p('namespace WpfApplication1.Properties {');
this.p('[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]', 1);
this.p('[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]', 1);
this.p('[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]', 1);
this.p('internal class Resources', 1);
this.p('{', 1);
this.p('private static global::System.Resources.ResourceManager resourceMan;', 2);
this.p('private static global::System.Globalization.CultureInfo resourceCulture;', 2);
this.p();
this.p('[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]', 2);
this.p('internal Resources() { }', 2);
this.p();
this.p('[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]', 2);
this.p('internal static global::System.Resources.ResourceManager ResourceManager', 2);
this.p('{', 2);
this.p('get', 3);
this.p('{', 3);
this.p('if ((resourceMan == null))', 4);
this.p('{', 4);
this.p('global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("WpfApplication1.Properties.Resources", typeof(Resources).Assembly);', 5);
this.p('resourceMan = temp;', 5);
this.p('}', 4);
this.p('return resourceMan;', 4);
this.p('}', 3);
this.p('}', 2);
this.p();
this.p('[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]', 2);
this.p('internal static global::System.Globalization.CultureInfo Culture', 2);
this.p('{', 2);
this.p('get', 3);
this.p('{', 3);
this.p('return resourceCulture;', 4);
this.p('}', 3);
this.p('set', 3);
this.p('{', 3);
this.p('resourceCulture = value;', 4);
this.p('}', 3);
this.p('}', 2);
this.p('}', 1);
this.p('}');
this.closeFile();
this.writeFile(path.join(this.options.to, this.sysdir() + '-build', 'Properties', 'Resources.resx'));
this.p('<?xml version="1.0" encoding="utf-8"?>');
this.p('<root>');
this.p('<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">');
this.p('<xsd:element name="root" msdata:IsDataSet="true">');
this.p('<xsd:complexType>');
this.p('<xsd:choice maxOccurs="unbounded">');
this.p('<xsd:element name="metadata">');
this.p('<xsd:complexType>');
this.p('<xsd:sequence>');
this.p('<xsd:element name="value" type="xsd:string" minOccurs="0" />');
this.p('</xsd:sequence>');
this.p('<xsd:attribute name="name" type="xsd:string" />');
this.p('<xsd:attribute name="type" type="xsd:string" />');
this.p('<xsd:attribute name="mimetype" type="xsd:string" />');
this.p('</xsd:complexType>');
this.p('</xsd:element>');
this.p('<xsd:element name="assembly">');
this.p('<xsd:complexType>');
this.p('<xsd:attribute name="alias" type="xsd:string" />');
this.p('<xsd:attribute name="name" type="xsd:string" />');
this.p('</xsd:complexType>');
this.p('</xsd:element>');
this.p('<xsd:element name="data">');
this.p('<xsd:complexType>');
this.p('<xsd:sequence>');
this.p('<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />');
this.p('<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />');
this.p('</xsd:sequence>');
this.p('<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />');
this.p('<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />');
this.p('<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />');
this.p('</xsd:complexType>');
this.p('</xsd:element>');
this.p('<xsd:element name="resheader">');
this.p('<xsd:complexType>');
this.p('<xsd:sequence>');
this.p('<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />');
this.p('</xsd:sequence>');
this.p('<xsd:attribute name="name" type="xsd:string" use="required" />');
this.p('</xsd:complexType>');
this.p('</xsd:element>');
this.p('</xsd:choice>');
this.p('</xsd:complexType>');
this.p('</xsd:element>');
this.p('</xsd:schema>');
this.p('<resheader name="resmimetype">');
this.p('<value>text/microsoft-resx</value>');
this.p('</resheader>');
this.p('<resheader name="version">');
this.p('<value>2.0</value>');
this.p('</resheader>');
this.p('<resheader name="reader">');
this.p('<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>');
this.p('</resheader>');
this.p('<resheader name="writer">');
this.p('<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>');
this.p('</resheader>');
this.p('</root>');
this.closeFile();
this.writeFile(path.join(this.options.to, this.sysdir() + '-build', 'Properties', 'Settings.Designer.cs'));
this.p('namespace WpfApplication1.Properties');
this.p('{');
this.p('[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]', 1);
this.p('[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "10.0.0.0")]', 1);
this.p('internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase', 1);
this.p('{', 1);
this.p('private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));', 2);
this.p();
this.p('public static Settings Default', 2);
this.p('{', 2);
this.p('get', 3);
this.p('{', 3);
this.p('return defaultInstance;', 4);
this.p('}', 3);
this.p('}', 2);
this.p('}', 1);
this.p('}');
this.closeFile();
this.writeFile(path.join(this.options.to, this.sysdir() + '-build', 'Properties', 'Settings.settings'));
this.p('<?xml version="1.0" encoding="utf-8"?>');
this.p('<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">');
this.p('<Profiles>');
this.p('<Profile Name="(Default)" />');
this.p('</Profiles>');
this.p('<Settings />');
this.p('</SettingsFile>');
this.closeFile();
}
exportCsProj(projectUuid: string) {
this.writeFile(path.join(this.options.to, this.sysdir() + '-build', 'Project.csproj'));
this.p('<?xml version="1.0" encoding="utf-8"?>');
this.p('<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">');
this.p('<PropertyGroup>', 1);
this.p('<Configuration Condition=" \'$(Configuration)\' == \'\' ">Debug</Configuration>', 2);
this.p('<Platform Condition=" \'$(Platform)\' == \'\' ">x86</Platform>', 2);
this.p('<ProductVersion>8.0.30703</ProductVersion>', 2);
this.p('<SchemaVersion>2.0</SchemaVersion>', 2);
this.p('<ProjectGuid>{' + projectUuid.toString().toUpperCase() + '}</ProjectGuid>', 2);
this.p('<OutputType>Library</OutputType>', 2);
this.p('<AppDesignerFolder>Properties</AppDesignerFolder>', 2);
this.p('<RootNamespace>WpfApplication1</RootNamespace>', 2);
this.p('<AssemblyName>WpfApplication1</AssemblyName>', 2);
this.p('<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>', 2);
this.p('<TargetFrameworkProfile>Client</TargetFrameworkProfile>', 2);
this.p('<FileAlignment>512</FileAlignment>', 2);
this.p('<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>', 2);
this.p('<WarningLevel>4</WarningLevel>', 2);
this.p('</PropertyGroup>', 1);
this.p('<PropertyGroup Condition=" \'$(Configuration)|$(Platform)\' == \'Debug|x86\' ">', 1);
this.p('<PlatformTarget>x86</PlatformTarget>', 2);
this.p('<DebugSymbols>true</DebugSymbols>', 2);
this.p('<DebugType>full</DebugType>', 2);
this.p('<Optimize>false</Optimize>', 2);
this.p('<OutputPath>bin\\Debug\\</OutputPath>', 2);
this.p('<DefineConstants>DEBUG;TRACE</DefineConstants>', 2);
this.p('<ErrorReport>prompt</ErrorReport>', 2);
this.p('<WarningLevel>4</WarningLevel>', 2);
this.p('</PropertyGroup>', 1);
this.p('<PropertyGroup Condition=" \'$(Configuration)|$(Platform)\' == \'Release|x86\' ">', 1);
this.p('<PlatformTarget>x86</PlatformTarget>', 2);
this.p('<DebugType>pdbonly</DebugType>', 2);
this.p('<Optimize>true</Optimize>', 2);
this.p('<OutputPath>bin\\Release\\</OutputPath>', 2);
this.p('<DefineConstants>TRACE</DefineConstants>', 2);
this.p('<ErrorReport>prompt</ErrorReport>', 2);
this.p('<WarningLevel>4</WarningLevel>', 2);
this.p('</PropertyGroup>', 1);
this.p('<ItemGroup>', 1);
this.p('<Reference Include="System" />', 2);
this.p('<Reference Include="System.Data" />', 2);
this.p('<Reference Include="System.Xml" />', 2);
this.p('<Reference Include="Microsoft.CSharp" />', 2);
this.p('<Reference Include="System.Core" />', 2);
this.p('<Reference Include="System.Xml.Linq" />', 2);
this.p('<Reference Include="System.Data.DataSetExtensions" />', 2);
this.p('<Reference Include="System.Xaml">', 2);
this.p('<RequiredTargetFramework>4.0</RequiredTargetFramework>', 3);
this.p('</Reference>', 2);
this.p('<Reference Include="WindowsBase" />', 2);
this.p('<Reference Include="PresentationCore" />', 2);
this.p('<Reference Include="PresentationFramework" />', 2);
this.p('</ItemGroup>', 1);
this.p('<ItemGroup>', 1);
this.includeFiles(path.join(this.options.to, this.sysdir() + '-build', 'Sources', 'src'), path.join(this.options.to, this.sysdir() + '-build'));
this.p('</ItemGroup>', 1);
this.p('<ItemGroup>', 1);
this.p('<Compile Include="Properties\\AssemblyInfo.cs">', 2);
this.p('<SubType>Code</SubType>', 3);
this.p('</Compile>', 2);
this.p('<Compile Include="Properties\\Resources.Designer.cs">', 2);
this.p('<AutoGen>True</AutoGen>', 3);
this.p('<DesignTime>True</DesignTime>', 3);
this.p('<DependentUpon>Resources.resx</DependentUpon>', 3);
this.p('</Compile>', 2);
this.p('<Compile Include="Properties\\Settings.Designer.cs">', 2);
this.p('<AutoGen>True</AutoGen>', 3);
this.p('<DependentUpon>Settings.settings</DependentUpon>', 3);
this.p('<DesignTimeSharedInput>True</DesignTimeSharedInput>', 3);
this.p('</Compile>', 2);
this.p('<EmbeddedResource Include="Properties\\Resources.resx">', 2);
this.p('<Generator>ResXFileCodeGenerator</Generator>', 3);
this.p('<LastGenOutput>Resources.Designer.cs</LastGenOutput>', 3);
this.p('</EmbeddedResource>', 2);
this.p('<None Include="Properties\\Settings.settings">', 2);
this.p('<Generator>SettingsSingleFileGenerator</Generator>', 3);
this.p('<LastGenOutput>Settings.Designer.cs</LastGenOutput>', 3);
this.p('</None>', 2);
this.p('<AppDesigner Include="Properties\\" />', 2);
this.p('</ItemGroup>', 1);
this.p('<Import Project="$(MSBuildToolsPath)\\Microsoft.CSharp.targets" />', 1);
this.p('</Project>');
this.closeFile();
}
/*copyMusic(platform, from, to, encoders, callback) {
Files.createDirectories(this.directory.resolve(this.sysdir()).resolve(to).parent());
Converter.convert(from, this.directory.resolve(this.sysdir()).resolve(to + '.mp4'), encoders.aacEncoder, () => {
callback([to + '.mp4']);
});
}*/
async copySound(platform: string, from: string, to: string) {
fs.copySync(from.toString(), path.join(this.options.to, this.sysdir(), to + '.wav'), { overwrite: true });
return { files: [to + '.wav'], sizes: [1] };
}
async copyVideo(platform: string, from: string, to: string) {
fs.ensureDirSync(path.join(this.options.to, this.sysdir(), path.dirname(to)));
await convert(from, path.join(this.options.to, this.sysdir(), to + '.wmv'), this.options.wmv);
return { files: [to + '.wmv'], sizes: [1] };
}
}

View File

@ -0,0 +1,10 @@
export let GraphicsApi = {
Default: 'default',
OpenGL: 'opengl',
OpenGL1: 'opengl1',
Direct3D9: 'direct3d9',
Direct3D11: 'direct3d11',
Direct3D12: 'direct3d12',
Metal: 'metal',
Vulkan: 'vulkan'
};

View File

@ -0,0 +1,37 @@
import * as child_process from 'child_process';
import * as fs from 'fs';
import * as path from 'path';
import * as log from './log';
import {sys} from './exec';
export function executeHaxe(from: string, haxeDirectory: string, options: string[]): Promise<void> {
return new Promise((resolve, reject) => {
let exe = 'haxe';
let env = process.env;
if (fs.existsSync(haxeDirectory) && fs.statSync(haxeDirectory).isDirectory()) {
let localexe = path.resolve(haxeDirectory, 'haxe' + sys());
if (!fs.existsSync(localexe)) localexe = path.resolve(haxeDirectory, 'haxe');
if (fs.existsSync(localexe)) exe = localexe;
const stddir = path.resolve(haxeDirectory, 'std');
if (fs.existsSync(stddir) && fs.statSync(stddir).isDirectory()) {
env.HAXE_STD_PATH = stddir;
}
}
let haxe = child_process.spawn(exe, options, {env: env, cwd: path.normalize(from)});
haxe.stdout.on('data', (data: any) => {
log.info(data.toString());
});
haxe.stderr.on('data', (data: any) => {
log.error(data.toString());
});
haxe.on('close', (code: number) => {
if (code === 0) {
resolve();
}
else reject('Haxe compiler error.');
});
});
}

View File

@ -0,0 +1,250 @@
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;
}
}
}
}

View File

@ -0,0 +1,438 @@
import * as fs from 'fs-extra';
import * as path from 'path';
import {writeXml} from './XmlWriter';
import * as log from './log';
function copyAndReplace(from: string, to: string, names: string[], values: string[]) {
let data = fs.readFileSync(from, { encoding: 'utf8' });
for (let i = 0; i < names.length; ++i) {
data = data.replace(new RegExp(names[i], 'g'), values[i]);
}
fs.writeFileSync(to, data, { encoding: 'utf8' });
}
function escapeXml(s: string) {
return s.replace(/[<>&'"]/g, c => {
switch (c) {
case '<': return '&lt;';
case '>': return '&gt;';
case '&': return '&amp;';
case '\'': return '&apos;';
case '"': return '&quot;';
default: throw 'unreachable code';
}
}
);
}
function IntelliJ(projectdir: string, options: any) {
let indir = path.join(__dirname, '..', 'Data', 'intellij');
let outdir = path.join(projectdir, options.safeName + '-' + options.system + '-intellij');
let sources = '';
for (let i = 0; i < options.sources.length; ++i) {
if (path.isAbsolute(options.sources[i])) {
sources += ' <sourceFolder url="file://' + options.sources[i] + '" isTestSource="false" />\n';
}
else {
sources += ' <sourceFolder url="file://$MODULE_DIR$/' + path.relative(outdir, path.resolve(options.from, options.sources[i])).replace(/\\/g, '/') + '" isTestSource="false" />\n';
}
}
let libraries = '';
for (let i = 0; i < options.libraries.length; ++i) {
if (path.isAbsolute(options.libraries[i].libpath)) {
libraries += ' <content url="file://' + options.libraries[i].libroot + '">\n';
libraries += ' <sourceFolder url="file://' + options.libraries[i].libpath + '" isTestSource="false" />\n';
}
else {
libraries += ' <content url="file://$MODULE_DIR$/' + path.relative(outdir, path.resolve(options.from, options.libraries[i].libroot)).replace(/\\/g, '/') + '">\n';
libraries += ' <sourceFolder url="file://$MODULE_DIR$/' + path.relative(outdir, path.resolve(options.from, options.libraries[i].libpath)).replace(/\\/g, '/') + '" isTestSource="false" />\n';
}
libraries += ' </content>\n';
}
let args = '';
let defines = '';
for (let i = 0; i < options.defines.length; ++i) {
defines += options.defines[i];
if (i < options.defines.length - 1) defines += ',';
}
for (let param of options.parameters) {
defines += param + ',';
}
let target: string;
switch (options.language) {
case 'hl':
case 'cpp':
target = 'C++';
break;
case 'as':
target = 'Flash';
args = '-swf-version 16.0';
break;
case 'cs':
target = 'C#';
if (fs.existsSync(options.haxeDirectory) && fs.statSync(options.haxeDirectory).isDirectory() && fs.existsSync(path.join(options.haxeDirectory, 'netlib'))) {
args = '-net-std ' + path.relative(outdir, path.join(options.haxeDirectory, 'netlib'));
}
break;
case 'java':
target = 'Java';
if (fs.existsSync(options.haxeDirectory) && fs.statSync(options.haxeDirectory).isDirectory() && fs.existsSync(path.join(options.haxeDirectory, 'hxjava', 'hxjava-std.jar'))) {
args = '-java-lib ' + path.relative(outdir, path.join(options.haxeDirectory, 'hxjava', 'hxjava-std.jar'));
}
break;
case 'js':
target = 'JavaScript';
break;
}
fs.copySync(path.join(indir, 'name.iml'), path.join(outdir, options.name + '.iml'), { overwrite: true });
copyAndReplace(path.join(indir, 'name.iml'), path.join(outdir, options.name + '.iml'), ['{name}', '{sources}', '{libraries}', '{target}', '{system}', '{args}'], [options.safeName, sources, libraries, target, options.system, args]);
fs.copySync(path.join(indir, 'idea', 'compiler.xml'), path.join(outdir, '.idea', 'compiler.xml'), { overwrite: true });
copyAndReplace(path.join(indir, 'idea', 'haxe.xml'), path.join(outdir, '.idea', 'haxe.xml'), ['{defines}'], [defines]);
fs.copySync(path.join(indir, 'idea', 'misc.xml'), path.join(outdir, '.idea', 'misc.xml'), { overwrite: true });
copyAndReplace(path.join(indir, 'idea', 'modules.xml'), path.join(outdir, '.idea', 'modules.xml'), ['{name}'], [options.name]);
fs.copySync(path.join(indir, 'idea', 'vcs.xml'), path.join(outdir, '.idea', 'vcs.xml'), { overwrite: true });
copyAndReplace(path.join(indir, 'idea', 'name'), path.join(outdir, '.idea', '.name'), ['{name}'], [options.name]);
fs.copySync(path.join(indir, 'idea', 'copyright', 'profiles_settings.xml'), path.join(outdir, '.idea', 'copyright', 'profiles_settings.xml'), { overwrite: true });
}
function hxml(projectdir: string, options: any) {
let data = '';
let lines: Array<String> = [];
// returns only unique lines and '' otherwise
function unique(line: String): String {
if (lines.indexOf(line) === -1) {
lines.push(line);
return line;
}
return '';
}
for (let i = 0; i < options.sources.length; ++i) {
if (path.isAbsolute(options.sources[i])) {
data += unique('-cp ' + options.sources[i] + '\n');
}
else {
data += unique('-cp ' + path.relative(projectdir, path.resolve(options.from, options.sources[i])) + '\n'); // from.resolve('build').relativize(from.resolve(this.sources[i])).toString());
}
}
for (let i = 0; i < options.libraries.length; ++i) {
if (path.isAbsolute(options.libraries[i].libpath)) {
data += unique('-cp ' + options.libraries[i].libpath + '\n');
}
else {
data += unique('-cp ' + path.relative(projectdir, path.resolve(options.from, options.libraries[i].libpath)) + '\n'); // from.resolve('build').relativize(from.resolve(this.sources[i])).toString());
}
}
for (let d in options.defines) {
let define = options.defines[d];
data += unique('-D ' + define + '\n');
}
if (options.language === 'cpp') {
data += unique('-cpp ' + path.normalize(options.to) + '\n');
}
else if (options.language === 'cs') {
data += unique('-cs ' + path.normalize(options.to) + '\n');
if (fs.existsSync(options.haxeDirectory) && fs.statSync(options.haxeDirectory).isDirectory() && fs.existsSync(path.join(options.haxeDirectory, 'netlib'))) {
data += unique('-net-std ' + path.relative(projectdir, path.join(options.haxeDirectory, 'netlib')) + '\n');
}
}
else if (options.language === 'java') {
data += unique('-java ' + path.normalize(options.to) + '\n');
if (fs.existsSync(options.haxeDirectory) && fs.statSync(options.haxeDirectory).isDirectory() && fs.existsSync(path.join(options.haxeDirectory, 'hxjava', 'hxjava-std.jar'))) {
data += unique('-java-lib ' + path.relative(projectdir, path.join(options.haxeDirectory, 'hxjava', 'hxjava-std.jar')) + '\n');
}
}
else if (options.language === 'js') {
data += unique('-js ' + path.normalize(options.to) + '\n');
}
else if (options.language === 'as') {
data += unique('-swf ' + path.normalize(options.to) + '\n');
data += unique('-swf-version ' + options.swfVersion + '\n');
data += unique('-swf-header ' + options.width + ':' + options.height + ':' + options.framerate + ':' + options.stageBackground + '\n');
}
else if (options.language === 'xml') {
data += unique('-xml ' + path.normalize(options.to) + '\n');
data += unique('--macro include(\'kha\')\n');
}
else if (options.language === 'hl') {
data += unique('-hl ' + path.normalize(options.to) + '\n');
}
for (let param of options.parameters) {
data += unique(param + '\n');
}
if (!options.parameters.some((param: string) => param.includes('-main '))) {
const entrypoint = options ? options.main ? options.main : 'Main' : 'Main';
data += unique('-main ' + entrypoint + '\n');
}
fs.outputFileSync(path.join(projectdir, 'project-' + options.system + '.hxml'), data);
}
function FlashDevelop(projectdir: string, options: any) {
let platform: string;
switch (options.language) {
case 'hl':
case 'cpp':
platform = 'C++';
break;
case 'as':
platform = 'Flash Player';
break;
case 'cs':
platform = 'C#';
break;
case 'java':
platform = 'Java';
break;
case 'js':
platform = 'JavaScript';
break;
}
options.swfVersion = 'swfVersion' in options ? options.swfVersion : 16.0;
options.stageBackground = 'stageBackground' in options ? options.stageBackground : 'ffffff';
options.framerate = 'framerate' in options ? options.framerate : 30;
let swfVersion = parseFloat(options.swfVersion).toFixed(1).split('.');
let output: any = {
n: 'output',
e: [
{
n: 'movie',
outputType: 'Application'
},
{
n: 'movie',
input: ''
},
{
n: 'movie',
path: path.normalize(options.to)
},
{
n: 'movie',
fps: options.framerate
},
{
n: 'movie',
width: options.width
},
{
n: 'movie',
height: options.height
},
{
n: 'movie',
version: swfVersion[0]
},
{
n: 'movie',
minorVersion: swfVersion[1]
},
{
n: 'movie',
platform: platform
},
{
n: 'movie',
background: '#' + options.stageBackground
}
]
};
if (fs.existsSync(options.haxeDirectory) && fs.statSync(options.haxeDirectory).isDirectory()) {
output.e.push({
n: 'movie',
preferredSDK: path.relative(projectdir, options.haxeDirectory)
});
}
let classpaths: string[] = [];
for (let i = 0; i < options.sources.length; ++i) {
classpaths.push(path.relative(projectdir, path.resolve(options.from, options.sources[i])));
}
for (let i = 0; i < options.libraries.length; ++i) {
classpaths.push(path.relative(projectdir, path.resolve(options.from, options.libraries[i].libpath)));
}
let otheroptions: any = [
{
n: 'option',
showHiddenPaths: 'False'
}
];
if (options.language === 'cpp' || options.system === 'krom') {
otheroptions.push({
n: 'option',
testMovie: 'Custom'
});
otheroptions.push({
n: 'option',
testMovieCommand: 'run_' + options.system + '.bat'
});
}
else if (options.language === 'cs' || options.language === 'java') {
otheroptions.push({
n: 'option',
testMovie: 'OpenDocument'
});
otheroptions.push({
n: 'option',
testMovieCommand: ''
});
}
else if (options.language === 'js') {
otheroptions.push({
n: 'option',
testMovie: 'Webserver'
});
otheroptions.push({
n: 'option',
testMovieCommand: path.join(path.parse(options.to).dir, 'index.html')
});
}
else {
otheroptions.push({
n: 'option',
testMovie: 'Default'
});
}
let def = '';
for (let d of options.defines) {
def += '-D ' + d + '&#xA;';
}
if (options.language === 'java' && fs.existsSync(options.haxeDirectory) && fs.statSync(options.haxeDirectory).isDirectory() && fs.existsSync(path.join(options.haxeDirectory, 'hxjava', 'hxjava-std.jar'))) {
def += '-java-lib ' + path.relative(projectdir, path.join(options.haxeDirectory, 'hxjava', 'hxjava-std.jar')) + '&#xA;';
}
if (options.language === 'cs' && fs.existsSync(options.haxeDirectory) && fs.statSync(options.haxeDirectory).isDirectory() && fs.existsSync(path.join(options.haxeDirectory, 'netlib'))) {
def += '-net-std ' + path.relative(projectdir, path.join(options.haxeDirectory, 'netlib')) + '&#xA;';
}
def += '-D kha_output=&quot;' + path.resolve(path.join(projectdir, options.to)) + '&quot;&#xA;';
let mainClass = 'Main';
for (let param of options.parameters) {
const mainRe = /-main\s+([^\s]+)/.exec(param);
if (mainRe) {
mainClass = mainRe[1];
}
else {
def += escapeXml(param) + '&#xA;';
}
}
let project = {
n: 'project',
version: '2',
e: [
'Output SWF options',
output,
'Other classes to be compiled into your SWF',
{
n: 'classpaths',
e: classpaths
.reduce((a, b) => {
if (a.indexOf(b) < 0) a.push(b);
return a;
}, [] )
.map((e) => {
return {n: 'class', path: e};
})
},
'Build options',
{
n: 'build',
e: [
{
n: 'option',
directives: ''
},
{
n: 'option',
flashStrict: 'False'
},
{
n: 'option',
noInlineOnDebug: 'False'
},
{
n: 'option',
mainClass: mainClass
},
{
n: 'option',
enabledebug: options.language === 'as' ? 'True' : 'False'
},
{
n: 'option',
additional: def
}
]
},
'haxelib libraries',
{
n: 'haxelib',
e: [
'example: <library name="..." />'
]
},
'Class files to compile (other referenced classes will automatically be included)',
{
n: 'compileTargets',
e: [
{
n: 'compile',
path: '..\\Sources\\Main.hx'
}
]
},
'Paths to exclude from the Project Explorer tree',
{
n: 'hiddenPaths',
e: [
'example: <hidden path="..." />'
]
},
'Executed before build',
{
n: 'preBuildCommand'
},
'Executed after build',
{
n: 'postBuildCommand',
alwaysRun: 'False'
},
'Other project options',
{
n: 'options',
e: otheroptions
},
'Plugin storage',
{
n: 'storage'
}
]
};
writeXml(project, path.join(projectdir, options.safeName + '-' + options.system + '.hxproj'));
}
export function writeHaxeProject(projectdir: string, projectFiles: boolean, options: any) {
hxml(projectdir, options);
if (projectFiles) {
FlashDevelop(projectdir, options);
IntelliJ(projectdir, options);
}
}

View File

@ -0,0 +1,58 @@
import * as cp from 'child_process';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import * as log from './log';
import * as exec from './exec';
function run(exe: string, from: string, to: string, width: number, height: number, format: string, background: number, transparent: boolean, callback: any) {
let params = ['from=' + from, 'to=' + to, 'format=' + format, 'keepaspect'];
if (width > 0) params.push('width=' + width);
if (height > 0) params.push('height=' + height);
if (background !== undefined && !transparent) params.push('background=' + background.toString(16));
if (transparent) params.push('transparent=' + background.toString(16));
let child = cp.spawn(exe, params);
child.stdout.on('data', (data: any) => {
// log.info('kraffiti stdout: ' + data);
});
child.stderr.on('data', (data: any) => {
log.error('kraffiti stderr: ' + data);
});
child.on('error', (err: any) => {
log.error('kraffiti error: ' + err);
});
child.on('close', (code: number) => {
if (code !== 0) log.error('kraffiti exited with code ' + code);
callback();
});
}
function findIcon(icon: string, from: string, options: any) {
if (icon && fs.existsSync(path.join(from, icon))) return path.join(from, icon);
if (fs.existsSync(path.join(from, 'icon.png'))) return path.join(from, 'icon.png');
else return path.join(options.kha, 'Kinc', 'Tools', exec.sysdir(), 'icon.png');
}
export function exportIco(icon: string, to: string, from: string, options: any) {
run(options.kraffiti, findIcon(icon, from.toString(), options), to.toString(), 0, 0, 'ico', undefined, false, function () { });
}
export function exportIcns(icon: string, to: string, from: string, options: any) {
run(options.kraffiti, findIcon(icon, from.toString(), options), to.toString(), 0, 0, 'icns', undefined, false, function () { });
}
export function exportPng(icon: string, to: string, width: number, height: number, background: number, transparent: boolean, from: string, options: any) {
run(options.kraffiti, findIcon(icon, from.toString(), options), to.toString(), width, height, 'png', background, transparent, function () { });
}
export function exportPng24(icon: string, to: string, width: number, height: number, background: number, transparent: boolean, from: string, options: any) {
run(options.kraffiti, findIcon(icon, from.toString(), options), to.toString(), width, height, 'png24', background, transparent, function () { });
}
export function exportBmp(icon: string, to: string, width: number, height: number, background: number, from: string, options: any) {
run(options.kraffiti, findIcon(icon, from.toString(), options), to.toString(), width, height, 'bmp', background, false, function () { });
}

View File

@ -0,0 +1,171 @@
import * as child_process from 'child_process';
import * as fs from 'fs-extra';
import * as os from 'os';
import * as path from 'path';
import * as log from './log';
import {sys} from './exec';
function getWidthAndHeight(kha: string, exe: string, from: string, to: string, options: any, format: string, prealpha: boolean): Promise<{w: number, h: number}> {
return new Promise((resolve, reject) => {
let params = ['from=' + from, 'to=' + to, 'format=' + format, 'donothing'];
if (options.scale !== undefined && options.scale !== 1) {
params.push('scale=' + options.scale);
}
let process = child_process.spawn(exe, params);
let output = '';
process.stdout.on('data', (data: any) => {
output += data.toString();
});
process.stderr.on('data', (data: any) => {
});
process.on('close', (code: number) => {
if (code !== 0) {
log.error('kraffiti process exited with code ' + code + ' when trying to get size of ' + path.parse(from).name);
resolve({w: 0, h: 0});
return;
}
const lines = output.split('\n');
for (let line of lines) {
if (line.startsWith('#')) {
let numbers = line.substring(1).split('x');
resolve({w: parseInt(numbers[0]), h: parseInt(numbers[1])});
return;
}
}
resolve({w: 0, h: 0});
});
});
}
function convertImage(from: string, temp: string, to: string, kha: string, exe: string, params: string[], options: any, cache: any): Promise<void> {
return new Promise<void>((resolve, reject) => {
let process = child_process.spawn(exe, params);
let output = '';
process.stdout.on('data', (data: any) => {
output += data.toString();
});
process.stderr.on('data', (data: any) => {
});
process.on('close', (code: number) => {
if (code !== 0) {
log.error('kraffiti process exited with code ' + code + ' when trying to convert ' + path.parse(from).name);
resolve();
return;
}
fs.renameSync(temp, to);
const lines = output.split('\n');
for (let line of lines) {
if (line.startsWith('#')) {
let numbers = line.substring(1).split('x');
cache[to] = {};
cache[to].original_width = options.original_width = parseInt(numbers[0]);
cache[to].original_height = options.original_height = parseInt(numbers[1]);
resolve();
return;
}
}
resolve();
});
});
}
export async function exportImage(kha: string, exe: string, from: string, to: string, options: any, format: string, prealpha: boolean, poweroftwo: boolean, cache: any): Promise<string> {
if (format === undefined) {
if (from.toString().endsWith('.png')) format = 'png';
else if (from.toString().endsWith('.hdr')) format = 'hdr';
else format = 'jpg';
}
if (format === 'jpg' && (options.scale === undefined || options.scale === 1) && options.background === undefined) {
to = to + '.jpg';
}
else if (format === 'pvr') {
to = to + '.pvr';
}
else if (format === 'ASTC') {
to = to + '.astc.k';
}
else if (format === 'DXT5') {
to = to + '.dxt5.k';
}
else if (format === 'hdr') {
to = to + '.hdr';
}
else if (format === 'lz4') {
to += '.k';
}
else {
format = 'png';
if (prealpha) to = to + '.kng';
else to = to + '.png';
}
let temp = to + '.temp';
let outputformat = format;
if (format === 'png' && prealpha) {
outputformat = 'kng';
}
if (format === 'lz4') {
outputformat = 'k';
}
if (format === 'ASTC') {
outputformat = 'astc.k';
}
if (format === 'DXT5') {
outputformat = 'dxt5.k';
}
if (fs.existsSync(to) && fs.statSync(to).mtime.getTime() > fs.statSync(from.toString()).mtime.getTime()) {
if (cache[to] !== undefined) {
const cachedOptions = cache[to];
options.original_width = cachedOptions.original_width;
options.original_height = cachedOptions.original_height;
return outputformat;
}
let wh = await getWidthAndHeight(kha, exe, from, to, options, format, prealpha);
cache[to] = {};
cache[to].original_width = options.original_width = wh.w;
cache[to].original_height = options.original_height = wh.h;
return outputformat;
}
fs.ensureDirSync(path.dirname(to));
if (format === 'jpg' || format === 'hdr') {
fs.copySync(from, temp, { overwrite: true });
fs.renameSync(temp, to);
let wh = await getWidthAndHeight(kha, exe, from, to, options, format, prealpha);
options.original_width = wh.w;
options.original_height = wh.h;
return outputformat;
}
let params = ['from=' + from, 'to=' + temp, 'format=' + format];
if (!poweroftwo) {
params.push('filter=nearest');
}
if (prealpha) params.push('prealpha');
if (options.scale !== undefined && options.scale !== 1) {
params.push('scale=' + options.scale);
}
if (options.background !== undefined) {
params.push('transparent=' + ((options.background.red << 24) | (options.background.green << 16) | (options.background.blue << 8) | 0xff).toString(16));
}
if (poweroftwo) {
params.push('poweroftwo');
}
await convertImage(from, temp, to, kha, exe, params, options, cache);
return outputformat;
}

View File

@ -0,0 +1,51 @@
export class Options {
from: string;
to: string;
projectfile: string;
target: string;
vr: string;
raytrace: string;
main: string;
// intermediate: string;
graphics: string;
arch: string;
audio: string;
visualstudio: string;
kha: string;
haxe: string;
nohaxe: boolean;
ffmpeg: string;
krafix: string;
kraffiti: string;
noshaders: boolean;
parallelAssetConversion: number;
noproject: boolean;
onlydata: boolean;
embedflashassets: boolean;
compile: boolean;
run: boolean;
init: boolean;
name: string;
server: boolean;
port: string;
debug: boolean;
silent: boolean;
quiet: boolean;
watch: boolean;
watchport: string;
livereload: boolean;
glsl2: boolean;
shaderversion: string;
ogg: string;
aac: string;
mp3: string;
h264: string;
webm: string;
wmv: string;
theora: string;
slowgc: boolean;
nosigning: boolean;
}

View File

@ -0,0 +1,24 @@
export const Platform = {
Krom: 'krom',
Windows: 'windows',
WindowsApp: 'windowsapp',
PlayStation3: 'ps3',
iOS: 'ios',
OSX: 'osx',
Android: 'android',
Xbox360: 'xbox360',
Linux: 'linux',
HTML5: 'html5',
HTML5Worker: 'html5worker',
Flash: 'flash',
WPF: 'wpf',
Java: 'java',
PlayStationMobile: 'psm',
Node: 'node',
DebugHTML5: 'debug-html5',
Empty: 'empty',
Pi: 'pi',
tvOS: 'tvos',
FreeBSD: 'freebsd',
Emscripten: 'emscripten'
};

View File

@ -0,0 +1,336 @@
import * as child_process from 'child_process';
import * as fs from 'fs';
import * as path from 'path';
import * as log from './log';
import {loadProject} from './ProjectFile';
export class Library {
libpath: string;
libroot: string;
}
export class Target {
baseTarget: string;
backends: string[];
constructor(baseTarget: string, backends: string[]) {
this.baseTarget = baseTarget;
this.backends = backends;
}
}
function contains(main: string, sub: string) {
main = path.resolve(main);
sub = path.resolve(sub);
if (process.platform === 'win32') {
main = main.toLowerCase();
sub = sub.toLowerCase();
}
return sub.indexOf(main) === 0 && sub.slice(main.length)[0] === path.sep;
}
export class Project {
static platform: string;
static scriptdir: string;
name: string;
safeName: string;
version: string;
sources: string[];
defines: string[];
cdefines: string[];
cflags: string[];
cppflags: string[];
parameters: string[];
scriptdir: string;
libraries: Library[];
localLibraryPath: string;
windowOptions: any;
targetOptions: any;
assetMatchers: { match: string, options: any }[];
shaderMatchers: { match: string, options: any }[];
customTargets: Map<string, Target>;
stackSize: number;
id: string;
icon: string = null;
constructor(name: string) {
this.name = name;
this.safeName = name.replace(/[^A-z0-9\-\_]/g, '-');
this.version = '1.0';
this.sources = [];
this.defines = ['hxcpp_smart_strings'];
this.cdefines = [];
this.cflags = [];
this.cppflags = [];
this.parameters = [];
this.scriptdir = Project.scriptdir;
this.libraries = [];
this.localLibraryPath = 'Libraries';
this.assetMatchers = [];
this.shaderMatchers = [];
this.customTargets = new Map();
this.stackSize = 0;
this.windowOptions = {};
this.targetOptions = {
html5: {},
flash: {},
android: {},
android_native: {},
ios: {},
xboxOne: {},
playStation4: {},
switch: {},
xboxSeriesXS: {},
playStation5: {},
stadia: {}
};
}
getSafeName() {
return this.safeName;
}
async addProject(projectDir: string) {
if (!path.isAbsolute(projectDir)) {
projectDir = path.join(this.scriptdir, projectDir);
}
if (!fs.existsSync(path.join(projectDir, 'khafile.js')) && (fs.existsSync(path.join(projectDir, 'kincfile.js')) || fs.existsSync(path.join(projectDir, 'korefile.js')) || fs.existsSync(path.join(projectDir, 'kfile.js')))) {
this.libraries.push({
libpath: projectDir,
libroot: projectDir
});
}
else {
let project = await loadProject(projectDir, 'khafile.js', Project.platform);
this.assetMatchers = this.assetMatchers.concat(project.assetMatchers);
this.sources = this.sources.concat(project.sources);
this.shaderMatchers = this.shaderMatchers.concat(project.shaderMatchers);
this.defines = this.defines.concat(project.defines);
this.cdefines = this.cdefines.concat(project.cdefines);
this.cflags = this.cflags.concat(project.cflags);
this.cppflags = this.cppflags.concat(project.cppflags);
this.parameters = this.parameters.concat(project.parameters);
this.libraries = this.libraries.concat(project.libraries);
if (this.icon === null && project.icon !== null) this.icon = path.join(projectDir, project.icon);
for (let customTarget of project.customTargets.keys()) {
this.customTargets.set(customTarget, project.customTargets.get(customTarget));
}
// windowOptions and targetOptions are ignored
}
}
private unglob(str: string): string {
const globChars = ['\\@', '\\!', '\\+', '\\*', '\\?', '\\(', '\\[', '\\{', '\\)', '\\]', '\\}'];
str = str.replace(/\\/g, '/');
for (const char of globChars) {
str = str.replace(new RegExp(char, 'g'), char);
}
return str;
}
private getBaseDir(str: string): string {
// replace \\ to / if next char is not glob
str = str.replace(/\\([^@!+*?{}()[\]]|$)/g, '/$1');
// find non-globby path part
const globby = /[^\\][@!+*?{}()[\]]/;
while (globby.test(str)) {
str = path.posix.dirname(str);
}
str = this.removeGlobEscaping(str);
if (!str.endsWith('/')) str += '/';
return str;
}
private removeGlobEscaping(str: string): string {
return str.replace(/\\([@!+*?{}()[\]]|$)/g, '$1');
}
/**
* Add all assets matching the match glob relative to the directory containing the current khafile.
* Asset types are infered from the file suffix.
* Glob syntax is very simple, the most important patterns are * for anything and ** for anything across directories.
*/
addAssets(match: string, options: any) {
if (!options) options = {};
if (!path.isAbsolute(match)) {
let base = this.unglob(path.resolve(this.scriptdir));
if (!base.endsWith('/')) base += '/';
// if there is no nameBaseDir: extract relative assets path from match
const baseName = options.nameBaseDir == null ? this.getBaseDir(match) : options.nameBaseDir;
match = path.posix.join(base, match.replace(/\\/g, '/'));
options.baseDir = path.posix.join(this.removeGlobEscaping(base), baseName);
}
else {
options.baseDir = this.getBaseDir(match);
}
this.assetMatchers.push({ match: match, options: options });
}
addSources(source: string) {
this.sources.push(path.resolve(path.join(this.scriptdir, source)));
}
/**
* Add all shaders matching the match glob relative to the directory containing the current khafile.
* Glob syntax is very simple, the most important patterns are * for anything and ** for anything across directories.
*/
addShaders(match: string, options: any) {
if (!options) options = {};
if (!path.isAbsolute(match)) {
let base = this.unglob(path.resolve(this.scriptdir));
if (!base.endsWith('/')) {
base += '/';
}
match = base + match.replace(/\\/g, '/');
}
this.shaderMatchers.push({ match: match, options: options });
}
addDefine(define: string) {
this.defines.push(define);
}
addCDefine(define: string) {
this.cdefines.push(define);
}
addCFlag(flag: string) {
this.cflags.push(flag);
}
addCppFlag(flag: string) {
this.cppflags.push(flag);
}
addParameter(parameter: string) {
this.parameters.push(parameter);
}
addTarget(name: string, baseTarget: string, backends: string[]) {
this.customTargets.set(name, new Target(baseTarget, backends));
}
addLibrary(library: string): string {
this.addDefine(library);
let self = this;
function findLibraryDirectory(name: string) {
if (path.isAbsolute(name)) {
return { libpath: name, libroot: name };
}
// Tries to load the default library from inside the kha project.
// e.g. 'Libraries/wyngine'
let libpath = path.join(self.scriptdir, self.localLibraryPath, name);
if (fs.existsSync(libpath) && fs.statSync(libpath).isDirectory()) {
let dir = path.resolve(libpath);
return { libpath: dir, libroot: dir };
}
// If the library couldn't be found in Libraries folder, try
// looking in the haxelib folders.
// e.g. addLibrary('hxcpp') => '/usr/lib/haxelib/hxcpp/3,2,193'
try {
libpath = path.join(child_process.execSync('haxelib config', { encoding: 'utf8' }).trim(), name.replace(/\./g, ','));
}
catch (error) {
if (process.env.HAXEPATH) {
libpath = path.join(process.env.HAXEPATH, 'lib', name.toLowerCase());
}
}
if (fs.existsSync(libpath) && fs.statSync(libpath).isDirectory()) {
if (fs.existsSync(path.join(libpath, '.dev'))) {
libpath = fs.readFileSync(path.join(libpath, '.dev'), 'utf8');
if (!path.isAbsolute(libpath)) {
libpath = path.resolve(libpath);
}
return { libpath: libpath, libroot: libpath };
}
else if (fs.existsSync(path.join(libpath, '.current'))) {
// Get the latest version of the haxelib path,
// e.g. for 'hxcpp', latest version '3,2,193'
let current = fs.readFileSync(path.join(libpath, '.current'), 'utf8');
return { libpath: path.join(libpath, current.replace(/\./g, ',')), libroot: libpath };
}
}
// check relative path
if (fs.existsSync(path.resolve(name))) {
let libpath = path.resolve(name);
return { libpath: libpath, libroot: libpath };
}
// Show error if library isn't found in Libraries or haxelib folder
log.error('Error: Library ' + name + ' not found.');
log.error('Add it to the \'Libraries\' subdirectory of your project. You may also install it via haxelib but that\'s less cool.');
throw 'Library ' + name + ' not found.';
}
let libInfo = findLibraryDirectory(library);
let dir = libInfo.libpath;
if (dir !== '') {
for (let elem of this.libraries) {
if (elem.libroot === libInfo.libroot)
return '';
}
this.libraries.push({
libpath: dir,
libroot: libInfo.libroot
});
// If this is a haxelib library, there must be a haxelib.json
if (fs.existsSync(path.join(dir, 'haxelib.json'))) {
let options = JSON.parse(fs.readFileSync(path.join(dir, 'haxelib.json'), 'utf8'));
// If there is a classPath value, add that directory to be loaded.
// Otherwise, just load the current path.
if (options.classPath) {
// TODO find an example haxelib that has a classPath value
this.sources.push(path.join(dir, options.classPath));
}
else {
// e.g. '/usr/lib/haxelib/hxcpp/3,2,193'
this.sources.push(dir);
}
// If this haxelib has other library dependencies, add them too
if (options.dependencies) {
for (let dependency in options.dependencies) {
if (dependency.toLowerCase() !== 'kha') {
this.addLibrary(dependency);
}
}
}
}
else {
// If there is no haxelib.json file, then just load the library
// by the Sources folder.
// e.g. Libraries/wyngine/Sources
if (!fs.existsSync(path.join(dir, 'Sources'))) {
log.info('Warning: No haxelib.json and no Sources directory found in library ' + library + '.');
}
this.sources.push(path.join(dir, 'Sources'));
}
if (fs.existsSync(path.join(dir, 'extraParams.hxml'))) {
let params = fs.readFileSync(path.join(dir, 'extraParams.hxml'), 'utf8');
for (let parameter of params.split('\n')) {
let param = parameter.trim();
if (param !== '') {
if (param.startsWith('-lib')) {
// (DK)
// - '-lib xxx' is for linking a library via haxe, it forces the use of the haxelib version
// - this should be handled by khamake though, as it tracks the dependencies better (local folder or haxelib)
log.info('Ignoring ' + dir + '/extraParams.hxml "' + param + '"');
}
else {
this.addParameter(param);
}
}
}
}
this.addShaders(dir + '/Sources/Shaders/**', {});
}
return dir;
}
}

View File

@ -0,0 +1,70 @@
import * as fs from 'fs';
import * as path from 'path';
import * as log from './log';
import {Platform} from './Platform';
import {Project} from './Project';
export let Callbacks = {
preAssetConversion: [() => {}],
preShaderCompilation: [() => {}],
preHaxeCompilation: [() => {}],
postHaxeCompilation: [() => {}],
postHaxeRecompilation: [() => {}],
postCppCompilation: [() => {}],
postAssetReexporting: [(filePath: string) => {}],
postBuild: [() => {}],
onFailure: [(error: any) => {}]
};
export async function loadProject(from: string, projectfile: string, platform: string): Promise<Project> {
return new Promise<Project>((resolve, reject) => {
fs.readFile(path.join(from, projectfile), 'utf8', (err, data) => {
if (err) {
throw new Error('Error reading ' + projectfile + ' from ' + from + '.');
}
let resolved = false;
let callbacks = {
preAssetConversion: () => {},
preShaderCompilation: () => {},
preHaxeCompilation: () => {},
postHaxeCompilation: () => {},
postHaxeRecompilation: () => {},
postCppCompilation: () => {},
postAssetReexporting: (filePath: string) => {},
postBuild: () => {},
onFailure: (error: any) => {}
};
let resolver = (project: Project) => {
resolved = true;
Callbacks.preAssetConversion.push(callbacks.preAssetConversion);
Callbacks.preShaderCompilation.push(callbacks.preShaderCompilation);
Callbacks.preHaxeCompilation.push(callbacks.preHaxeCompilation);
Callbacks.postHaxeCompilation.push(callbacks.postHaxeCompilation);
Callbacks.postHaxeRecompilation.push(callbacks.postHaxeRecompilation);
Callbacks.postCppCompilation.push(callbacks.postCppCompilation);
Callbacks.postAssetReexporting.push(callbacks.postAssetReexporting);
Callbacks.postBuild.push(callbacks.postBuild);
Callbacks.onFailure.push(callbacks.onFailure);
resolve(project);
};
process.on('exit', (code: number) => {
if (!resolved) {
console.error('Error: khafile.js did not call resolve, no project created.');
}
});
Project.platform = platform;
Project.scriptdir = from;
try {
let AsyncFunction = Object.getPrototypeOf(async () => {}).constructor;
new AsyncFunction('Project', 'Platform', 'platform', 'require', '__dirname', 'process', 'resolve', 'reject', 'callbacks', data)
(Project, Platform, platform, require, path.resolve(from), process, resolver, reject, callbacks);
}
catch (error) {
reject(error);
}
});
});
}

View File

@ -0,0 +1,4 @@
export let RayTraceApi = {
DXR: 'dxr',
None: 'none'
};

View File

@ -0,0 +1,502 @@
import * as child_process from 'child_process';
import * as fs from 'fs-extra';
import * as os from 'os';
import * as path from 'path';
import * as chokidar from 'chokidar';
import * as Throttle from 'promise-parallel-throttle';
import {KhaExporter} from './Exporters/KhaExporter';
import {GraphicsApi} from './GraphicsApi';
import {Options} from './Options';
import {Platform} from './Platform';
import {AssetConverter} from './AssetConverter';
import * as log from './log';
export interface Variable {
name: string;
type: string;
}
export class CompiledShader {
name: string;
files: string[];
inputs: Variable[];
outputs: Variable[];
uniforms: Variable[];
types: any[];
noembed: boolean;
constructor() {
this.files = [];
this.inputs = [];
this.outputs = [];
this.uniforms = [];
this.types = [];
this.noembed = false;
}
}
export class ShaderCompiler {
exporter: KhaExporter;
platform: string;
compiler: string;
type: string;
to: string;
temp: string;
builddir: string;
options: Options;
shaderMatchers: Array<{ match: string, options: any }>;
watcher: fs.FSWatcher;
constructor(exporter: KhaExporter, platform: string, compiler: string, to: string, temp: string, builddir: string, options: Options, shaderMatchers: Array<{ match: string, options: any }>) {
this.exporter = exporter;
if (platform.endsWith('-native')) platform = platform.substr(0, platform.length - '-native'.length);
if (platform.endsWith('-hl')) platform = platform.substr(0, platform.length - '-hl'.length);
this.platform = platform;
this.compiler = compiler;
this.type = ShaderCompiler.findType(platform, options);
this.options = options;
this.to = to;
this.temp = temp;
this.builddir = builddir;
this.shaderMatchers = shaderMatchers;
}
close(): void {
if (this.watcher) this.watcher.close();
}
static findType(platform: string, options: Options): string {
switch (platform) {
case Platform.Empty:
case Platform.Node:
return 'glsl';
case Platform.Flash:
return 'agal';
case Platform.Android:
if (options.graphics === GraphicsApi.Vulkan || options.graphics === GraphicsApi.Default) {
return 'spirv';
}
else if (options.graphics === GraphicsApi.OpenGL) {
return 'essl';
}
else {
throw new Error('Unsupported shader language.');
}
case Platform.HTML5:
case Platform.DebugHTML5:
case Platform.HTML5Worker:
case Platform.Pi:
return 'essl';
case Platform.tvOS:
case Platform.iOS:
if (options.graphics === GraphicsApi.Metal || options.graphics === GraphicsApi.Default) {
return 'metal';
}
else if (options.graphics === GraphicsApi.OpenGL) {
return 'essl';
}
else {
throw new Error('Unsupported shader language.');
}
case Platform.Windows:
if (options.graphics === GraphicsApi.Vulkan) {
return 'spirv';
}
else if (options.graphics === GraphicsApi.OpenGL) {
return 'glsl';
}
else if (options.graphics === GraphicsApi.Direct3D11 || options.graphics === GraphicsApi.Direct3D12 || options.graphics === GraphicsApi.Default) {
return 'd3d11';
}
else if (options.graphics === GraphicsApi.Direct3D9) {
return 'd3d9';
}
else {
throw new Error('Unsupported shader language.');
}
case Platform.WindowsApp:
return 'd3d11';
case Platform.Xbox360:
case Platform.PlayStation3:
return 'd3d9';
case Platform.Linux:
if (options.graphics === GraphicsApi.Vulkan || options.graphics === GraphicsApi.Default) {
return 'spirv';
}
else if (options.graphics === GraphicsApi.OpenGL) {
return 'glsl';
}
else {
throw new Error('Unsupported shader language.');
}
case Platform.OSX:
if (options.graphics === GraphicsApi.Metal || options.graphics === GraphicsApi.Default) {
return 'metal';
}
else if (options.graphics === GraphicsApi.OpenGL) {
return 'glsl';
}
else {
throw new Error('Unsupported shader language.');
}
case Platform.Krom:
if (options.graphics === GraphicsApi.Default) {
if (process.platform === 'win32') {
return 'd3d11';
}
else if (process.platform === 'darwin') {
return 'metal';
}
else {
return 'glsl';
}
}
else if (options.graphics === GraphicsApi.Vulkan) {
return 'spirv';
}
else if (options.graphics === GraphicsApi.Metal) {
return 'metal';
}
else if (options.graphics === GraphicsApi.OpenGL) {
return 'glsl';
}
else if (options.graphics === GraphicsApi.Direct3D11 || options.graphics === GraphicsApi.Direct3D12) {
return 'd3d11';
}
else if (options.graphics === GraphicsApi.Direct3D9) {
return 'd3d9';
}
else {
throw new Error('Unsupported shader language.');
}
case Platform.FreeBSD:
return 'glsl';
default:
return platform;
}
}
watch(watch: boolean, match: string, options: any, recompileAll: boolean) {
return new Promise<CompiledShader[]>((resolve, reject) => {
let shaders: string[] = [];
let ready = false;
this.watcher = chokidar.watch(match, { ignored: /[\/\\]\.(git|DS_Store)/, persistent: watch });
this.watcher.on('add', (filepath: string) => {
let file = path.parse(filepath);
if (ready) {
switch (file.ext) {
case '.glsl':
if (!file.name.endsWith('.inc') && this.isSupported(file.name)) {
log.info('Compiling ' + file.name);
this.compileShader(filepath, options, recompileAll);
}
break;
}
}
else {
switch (file.ext) {
case '.glsl':
if (!file.name.endsWith('.inc')) {
shaders.push(filepath);
}
break;
}
}
});
if (watch) {
this.watcher.on('change', (filepath: string) => {
let file = path.parse(filepath);
switch (file.ext) {
case '.glsl':
if (!file.name.endsWith('.inc') && this.isSupported(file.name)) {
log.info('Recompiling ' + file.name);
this.compileShader(filepath, options, recompileAll);
}
break;
}
});
}
this.watcher.on('unlink', (file: string) => {
});
this.watcher.on('ready', async () => {
ready = true;
let compiledShaders: CompiledShader[] = [];
const self = this;
async function compile(shader: any, index: number) {
let parsed = path.parse(shader);
if (self.isSupported(shader)) {
log.info('Compiling shader ' + (index + 1) + ' of ' + shaders.length + ' (' + parsed.base + ').');
let compiledShader: CompiledShader = null;
try {
compiledShader = await self.compileShader(shader, options, recompileAll);
}
catch (error) {
log.error('Compiling shader ' + (index + 1) + ' of ' + shaders.length + ' (' + parsed.base + ') failed:');
log.error(error);
return Promise.reject(error);
}
if (compiledShader === null) {
compiledShader = new CompiledShader();
compiledShader.noembed = options.noembed;
// mark variables as invalid, so they are loaded from previous compilation
compiledShader.files = null;
compiledShader.inputs = null;
compiledShader.outputs = null;
compiledShader.uniforms = null;
compiledShader.types = null;
}
if (compiledShader.files != null && compiledShader.files.length === 0) {
// TODO: Remove when krafix has been recompiled everywhere
compiledShader.files.push(parsed.name + '.' + self.type);
}
compiledShader.name = AssetConverter.createExportInfo(parsed, false, options, self.exporter.options.from).name;
compiledShaders.push(compiledShader);
}
else {
log.info('Skipping shader ' + (index + 1) + ' of ' + shaders.length + ' (' + parsed.base + ').');
}
++index;
return Promise.resolve();
}
if (this.options.parallelAssetConversion !== 0) {
let todo = shaders.map((shader, index) => {
return async () => {
await compile(shader, index);
};
});
let processes = this.options.parallelAssetConversion === -1
? require('os').cpus().length - 1
: this.options.parallelAssetConversion;
await Throttle.all(todo, {
maxInProgress: processes,
});
}
else {
let index = 0;
for (let shader of shaders) {
try {
await compile(shader, index);
}
catch (err) {
reject();
return;
}
index += 1;
}
}
resolve(compiledShaders);
return;
});
});
}
async run(watch: boolean, recompileAll: boolean): Promise<CompiledShader[]> {
let shaders: CompiledShader[] = [];
for (let matcher of this.shaderMatchers) {
shaders = shaders.concat(await this.watch(watch, matcher.match, matcher.options, recompileAll));
}
return shaders;
}
isSupported(file: string): boolean {
if (file.endsWith('.frag.glsl') || file.endsWith('.vert.glsl')) {
return true;
}
return this.type !== 'essl' && this.type !== 'agal';
}
compileShader(file: string, options: any, recompile: boolean): Promise<CompiledShader> {
return new Promise<CompiledShader>((resolve, reject) => {
if (!this.compiler) reject('No shader compiler found.');
if (this.type === 'none') {
resolve(new CompiledShader());
return;
}
let fileinfo = path.parse(file);
let from = file;
let to = path.join(this.to, fileinfo.name + '.' + this.type);
let temp = to + '.temp';
fs.stat(from, (fromErr: NodeJS.ErrnoException, fromStats: fs.Stats) => {
fs.stat(to, (toErr: NodeJS.ErrnoException, toStats: fs.Stats) => {
if (options.noprocessing) {
if (!toStats || toStats.mtime.getTime() < fromStats.mtime.getTime()) {
fs.copySync(from, to, { overwrite: true });
}
let compiledShader = new CompiledShader();
compiledShader.noembed = options.noembed;
resolve(compiledShader);
return;
}
fs.stat(this.compiler, (compErr: NodeJS.ErrnoException, compStats: fs.Stats) => {
if (!recompile && (fromErr || (!toErr && toStats.mtime.getTime() > fromStats.mtime.getTime() && toStats.mtime.getTime() > compStats.mtime.getTime()))) {
if (fromErr) log.error('Shader compiler error: ' + fromErr);
resolve(null);
}
else {
if (this.type === 'metal' && this.platform !== Platform.Krom) {
fs.ensureDirSync(path.join(this.builddir, 'Sources'));
let funcname = fileinfo.name;
funcname = funcname.replace(/-/g, '_');
funcname = funcname.replace(/\./g, '_');
funcname += '_main';
fs.writeFileSync(to, '>' + funcname, 'utf8');
to = path.join(this.builddir, 'Sources', fileinfo.name + '.' + this.type);
temp = to;
}
let parameters = [this.type === 'hlsl' ? 'd3d9' : this.type, from, temp, this.temp, this.platform];
if (this.options.shaderversion) {
parameters.push('--version');
parameters.push(this.options.shaderversion);
}
else if (this.platform === Platform.Krom && os.platform() === 'linux') {
parameters.push('--version');
parameters.push('110');
}
if (this.options.glsl2) {
parameters.push('--glsl2');
}
if (this.options.debug) {
parameters.push('--debug');
}
if (options.defines) {
for (let define of options.defines) {
parameters.push('-D' + define);
}
}
if (this.platform === Platform.HTML5 || this.platform === Platform.HTML5Worker || this.platform === Platform.Android) {
parameters.push('--relax');
}
parameters[1] = path.resolve(parameters[1]);
parameters[2] = path.resolve(parameters[2]);
parameters[3] = path.resolve(parameters[3]);
let child = child_process.spawn(this.compiler, parameters);
let errorLine = '';
let newErrorLine = true;
let errorData = false;
let compiledShader = new CompiledShader();
compiledShader.noembed = options.noembed;
function parseData(data: string) {
data = data.replace(':\\', '#\\'); // Filter out absolute paths on Windows
let parts = data.split(':');
if (parts.length >= 3) {
if (parts[0] === 'uniform') {
compiledShader.uniforms.push({name: parts[1], type: parts[2]});
}
else if (parts[0] === 'input') {
compiledShader.inputs.push({name: parts[1], type: parts[2]});
}
else if (parts[0] === 'output') {
compiledShader.outputs.push({name: parts[1], type: parts[2]});
}
else if (parts[0] === 'type') {
let type = data.substring(data.indexOf(':') + 1);
let name = type.substring(0, type.indexOf(':'));
let typedata = type.substring(type.indexOf(':') + 2);
typedata = typedata.substr(0, typedata.length - 1);
let members = typedata.split(',');
let memberdecls = [];
for (let member of members) {
let memberparts = member.split(':');
memberdecls.push({type: memberparts[1], name: memberparts[0]});
}
compiledShader.types.push({name: name, members: memberdecls});
}
}
else if (parts.length >= 2) {
if (parts[0] === 'file') {
const parsed = path.parse(parts[1].replace('#\\', ':\\'));
let name = parsed.name;
if (parsed.ext !== '.temp') name += parsed.ext;
compiledShader.files.push(name);
}
}
}
let stdOutString = '';
child.stdout.on('data', (data: any) => {
stdOutString += data.toString();
});
child.stderr.on('data', (data: any) => {
let str: string = data.toString();
for (let char of str) {
if (char === '\n') {
if (errorData) {
parseData(errorLine.trim());
}
else {
log.error(errorLine.trim());
}
errorLine = '';
newErrorLine = true;
errorData = false;
}
else if (newErrorLine && char === '#') {
errorData = true;
newErrorLine = false;
}
else {
errorLine += char;
newErrorLine = false;
}
}
});
child.on('close', (code: number) => {
if (stdOutString) {
if (code === 0) {
log.info(stdOutString);
}
else {
log.error(stdOutString);
}
}
if (errorLine.trim().length > 0) {
if (errorData) {
parseData(errorLine.trim());
}
else {
log.error(errorLine.trim());
}
}
if (code === 0) {
if (this.type !== 'metal' || this.platform === Platform.Krom) {
if (compiledShader.files === null || compiledShader.files.length === 0) {
fs.renameSync(temp, to);
}
for (let file of compiledShader.files) {
fs.renameSync(path.join(this.to, file + '.temp'), path.join(this.to, file));
}
}
resolve(compiledShader);
}
else {
process.exitCode = 1;
reject('Shader compiler error.');
}
});
}
});
});
});
});
}
}

View File

@ -0,0 +1,9 @@
export const VisualStudioVersion = {
VS2010: 'vs2010',
VS2012: 'vs2012',
VS2013: 'vs2013',
VS2015: 'vs2015',
VS2017: 'vs2017',
VS2019: 'vs2019',
VS2022: 'vs2022'
};

View File

@ -0,0 +1,7 @@
export let VrApi = {
GearVr: 'gearvr',
Cardboard: 'cardboard',
Oculus: 'oculus',
WebVR: 'webvr',
None: 'none'
};

View File

@ -0,0 +1,48 @@
import * as fs from 'fs-extra';
function printElement(elem: any, data: string, indents: number) {
for (let i = 0; i < indents; ++i) data += '\t';
if (typeof elem === 'string') {
data += '<!-- ' + elem + ' -->\n';
return data;
}
data += '<' + elem.n;
for (let a in elem) {
if (a === 'n') continue;
if (a === 'e') continue;
data += ' ' + a + '="' + elem[a] + '"';
}
if (elem.e === undefined || elem.e.length === 0) {
data += ' />\n';
}
else {
data += '>\n';
for (let e of elem.e) {
data = printElement(e, data, indents + 1);
}
for (let i = 0; i < indents; ++i) data += '\t';
data += '</' + elem.n + '>\n';
}
return data;
}
export function writeXml(xml: any, path: string) {
let data = '';
data += '<?xml version="1.0" encoding="utf-8"?>\n';
data += '<' + xml.n;
for (let a in xml) {
if (a === 'n') continue;
if (a === 'e') continue;
data += ' ' + a + '="' + xml[a] + '"';
}
data += '>\n';
for (let e of xml.e) {
data = printElement(e, data, 1);
}
data += '</' + xml.n + '>\n';
fs.outputFileSync(path, data);
}

View File

@ -0,0 +1,37 @@
import {GraphicsApi} from './GraphicsApi';
import {Platform} from './Platform';
export function graphicsApi(platform: string): string {
switch (platform) {
case Platform.Empty:
case Platform.Node:
case Platform.Android:
case Platform.HTML5:
case Platform.DebugHTML5:
case Platform.HTML5Worker:
case Platform.Pi:
case Platform.Linux:
return GraphicsApi.OpenGL;
case Platform.tvOS:
case Platform.iOS:
case Platform.OSX:
return GraphicsApi.Metal;
case Platform.Windows:
case Platform.WindowsApp:
return GraphicsApi.Direct3D11;
case Platform.Krom:
if (process.platform === 'win32') {
return GraphicsApi.Direct3D11;
}
else if (process.platform === 'darwin') {
return GraphicsApi.Metal;
}
else {
return GraphicsApi.OpenGL;
}
case Platform.FreeBSD:
return GraphicsApi.OpenGL;
default:
return platform;
}
}

View File

@ -0,0 +1,28 @@
import * as os from 'os';
export function sys() {
if (os.platform() === 'win32') {
return '.exe';
}
else {
return '';
}
}
export function sysdir() {
if (os.platform() === 'linux') {
if (os.arch() === 'arm') return 'linux_arm';
if (os.arch() === 'arm64') return 'linux_arm64';
else if (os.arch() === 'x64') return 'linux_x64';
else throw 'Unsupported CPU';
}
else if (os.platform() === 'win32') {
return 'windows_x64';
}
else if (os.platform() === 'freebsd') {
return 'freebsd_x64';
}
else {
return 'macos';
}
}

View File

@ -0,0 +1,75 @@
import * as fs from 'fs';
import * as path from 'path';
export function run(name: string, from: string, projectfile: string) {
if (!fs.existsSync(path.join(from, projectfile))) {
fs.writeFileSync(path.join(from, projectfile),
'let project = new Project(\'New Project\');\n'
+ 'project.addAssets(\'Assets/**\');\n'
+ 'project.addShaders(\'Shaders/**\');\n'
+ 'project.addSources(\'Sources\');\n'
+ 'resolve(project);\n',
{ encoding: 'utf8' });
}
if (!fs.existsSync(path.join(from, 'Assets'))) fs.mkdirSync(path.join(from, 'Assets'));
if (!fs.existsSync(path.join(from, 'Shaders'))) fs.mkdirSync(path.join(from, 'Shaders'));
if (!fs.existsSync(path.join(from, 'Sources'))) fs.mkdirSync(path.join(from, 'Sources'));
let friendlyName = name;
friendlyName = friendlyName.replace(/ /g, '_');
friendlyName = friendlyName.replace(/-/g, '_');
if (!fs.existsSync(path.join(from, 'Sources', 'Main.hx'))) {
let mainsource =
'package;\n\n'
+ 'import kha.Assets;\n'
+ 'import kha.Color;\n'
+ 'import kha.Framebuffer;\n'
+ 'import kha.Scheduler;\n'
+ 'import kha.System;\n\n'
+ 'class Main {\n'
+ '\tstatic var logo = ["1 1 1 1 111", "11 111 111", "1 1 1 1 1 1"];\n\n'
+ '\tstatic function update(): Void {\n'
+ '\t}\n\n'
+ '\tstatic function render(frames: Array<Framebuffer>): Void {\n'
+ '\t\t// As we are using only 1 window, grab the first framebuffer\n'
+ '\t\tfinal fb = frames[0];\n'
+ '\t\t// Now get the `g2` graphics object so we can draw\n'
+ '\t\tfinal g2 = fb.g2;\n'
+ '\t\t// Start drawing, and clear the framebuffer to `petrol`\n'
+ '\t\tg2.begin(true, Color.fromBytes(0, 95, 106));\n'
+ '\t\t// Offset all following drawing operations from the top-left a bit\n'
+ '\t\tg2.pushTranslation(64, 64);\n'
+ '\t\t// Fill the following rects with red\n'
+ '\t\tg2.color = Color.Red;\n\n'
+ '\t\t// Loop over the logo (Array<String>) and draw a rect for each "1"\n'
+ '\t\tfor (rowIndex in 0...logo.length) {\n'
+ '\t\t final row = logo[rowIndex];\n\n'
+ '\t\t for (colIndex in 0...row.length) {\n'
+ '\t\t switch row.charAt(colIndex) {\n'
+ '\t\t case "1": g2.fillRect(colIndex * 16, rowIndex * 16, 16, 16);\n'
+ '\t\t case _:\n'
+ '\t\t }\n'
+ '\t\t }\n'
+ '\t\t}\n\n'
+ '\t\t// Pop the pushed translation so it will not accumulate over multiple frames\n'
+ '\t\tg2.popTransformation();\n'
+ '\t\t// Finish the drawing operations\n'
+ '\t\tg2.end();\n'
+ '\t}\n\n'
+ '\tpublic static function main() {\n'
+ '\t\tSystem.start({title: "' + name + '", width: 1024, height: 768}, function (_) {\n'
+ '\t\t\t// Just loading everything is ok for small projects\n'
+ '\t\t\tAssets.loadEverything(function () {\n'
+ '\t\t\t\t// Avoid passing update/render directly,\n'
+ '\t\t\t\t// so replacing them via code injection works\n'
+ '\t\t\t\tScheduler.addTimeTask(function () { update(); }, 0, 1 / 60);\n'
+ '\t\t\t\tSystem.notifyOnFrames(function (frames) { render(frames); });\n'
+ '\t\t\t});\n'
+ '\t\t});\n'
+ '\t}\n'
+ '}\n';
fs.writeFileSync(path.join(from, 'Sources', 'Main.hx'), mainsource, { encoding: 'utf8' });
}
}

View File

@ -0,0 +1,437 @@
// Called from entry point, e.g. Kha/make.js
// This is where options are processed:
// e.g. '-t html5 --server'
import * as os from 'os';
import * as path from 'path';
import {Callbacks} from './ProjectFile';
import {GraphicsApi} from './GraphicsApi';
import {Architecture} from './Architecture';
import {AudioApi} from './AudioApi';
import {VrApi} from './VrApi';
import {RayTraceApi} from './RayTraceApi';
import {Options} from './Options';
import {Platform} from './Platform';
import {VisualStudioVersion} from './VisualStudioVersion';
let defaultTarget: string;
if (os.platform() === 'linux') {
defaultTarget = Platform.Linux;
}
else if (os.platform() === 'win32') {
defaultTarget = Platform.Windows;
}
else if (os.platform() === 'freebsd') {
defaultTarget = Platform.FreeBSD;
}
else {
defaultTarget = Platform.OSX;
}
let options: Array<any> = [
{
full: 'from',
value: true,
description: 'Location of your project',
default: '.'
},
{
full: 'to',
value: true,
description: 'Build location',
default: 'build'
},
{
full: 'projectfile',
value: true,
description: 'Name of your project file, defaults to "khafile.js"',
default: 'khafile.js'
},
{
full: 'target',
short: 't',
value: true,
description: 'Target platform',
default: defaultTarget
},
{
full: 'vr',
value: true,
description: 'Target VR device',
default: VrApi.None
},
{
full: 'raytrace',
value: true,
description: 'Target raytracing api',
default: RayTraceApi.None
},
{
full: 'main',
value: true,
description: 'Entrypoint for the haxe code (-main argument), defaults to "Main".',
default: 'Main'
},
{
full: 'intermediate',
description: 'Intermediate location for object files.',
value: true,
default: '',
hidden: true
},
{
full: 'graphics',
short: 'g',
description: 'Graphics api to use. Possible parameters are direct3d9, direct3d11, direct3d12, metal, vulkan and opengl.',
value: true,
default: GraphicsApi.Default
},
{
full: 'arch',
description: 'Target architecture to use. Possible parameters are arm7, arm8, x86, x86_64.',
value: true,
default: Architecture.Default
},
{
full: 'audio',
short: 'a',
description: 'Audio api to use. Possible parameters are directsound and wasapi.',
value: true,
default: AudioApi.Default
},
{
full: 'visualstudio',
short: 'v',
description: 'Version of Visual Studio to use. Possible parameters are vs2010, vs2012, vs2013, vs2015, vs2017, vs2019 and vs2022.',
value: true,
default: VisualStudioVersion.VS2022
},
{
full: 'kha',
short: 'k',
description: 'Location of Kha directory',
value: true,
default: ''
},
{
full: 'haxe',
description: 'Location of Haxe directory',
value: true,
default: ''
},
{
full: 'nohaxe',
description: 'Do not compile Haxe sources',
value: false,
},
{
full: 'ffmpeg',
description: 'Location of ffmpeg executable',
value: true,
default: ''
},
{
full: 'ogg',
description: 'Commandline for running the ogg encoder',
value: true,
default: ''
},
{
full: 'mp3',
description: 'Commandline for running the mp3 encoder',
value: true,
default: ''
},
{
full: 'aac',
description: 'Commandline for running the ffmpeg executable',
value: true,
default: ''
},
{
full: 'krafix',
description: 'Location of krafix shader compiler',
value: true,
default: ''
},
{
full: 'kraffiti',
description: 'Location of kraffiti image processing tool',
value: true,
default: ''
},
{
full: 'noshaders',
description: 'Do not compile shaders',
value: false
},
{
full: 'noproject',
description: 'Only source files. Don\'t generate project files.',
value: false,
},
{
full: 'onlydata',
description: 'Only assets/data. Don\'t generate project files.',
value: false,
},
{
full: 'embedflashassets',
description: 'Embed assets in swf for flash target',
value: false
},
{
full: 'compile',
description: 'Compile executable',
value: false
},
{
full: 'run',
description: 'Run executable',
value: false
},
{
full: 'init',
description: 'Init a Kha project inside the current directory',
value: false
},
{
full: 'name',
description: 'Project name to use when initializing a project',
value: true,
default: 'Project'
},
{
full: 'server',
description: 'Run local http server for html5 target',
value: false
},
{
full: 'port',
description: 'Running port for the server',
value: true,
default: 8080
},
{
full: 'debug',
description: 'Compile in debug mode.',
value: false
},
{
full: 'silent',
description: 'Silent mode.',
value: false
},
{
full: 'quiet',
description: 'Quiet mode. Like silent mode but prints error messages.',
value: false
},
{
full: 'watch',
short: 'w',
description: 'Watch files and recompile on change.',
value: false
},
{
full: 'watchport',
short: 'wp',
description: 'Port for the compilation server (default value is 7000).',
value: true,
default: '7000',
},
{
full: 'livereload',
description: 'Reload http server page on watch mode recompilations.',
value: false
},
{
full: 'glsl2',
description: 'Use experimental SPIRV-Cross glsl mode.',
value: false
},
{
full: 'shaderversion',
description: 'Set target shader version manually.',
value: true,
default: null
},
{
full: 'parallelAssetConversion',
description: 'Experimental - Spawn multiple processes during asset and shader conversion. Possible values:\n 0: disabled (default value)\n -1: choose number of processes automatically\n N: specify number of processes manually',
value: true,
default: 0
},
{
full: 'slowgc',
description: 'Disables generational garbage collection.',
value: false
},
{
full: 'nosigning',
value: false,
description: 'Disable code signing for iOS'
}
];
let parsedOptions: any = new Options();
function printHelpLine(options: String, description: String) {
let helpLine = options;
while (helpLine.length < 30) {
helpLine += ' ';
}
helpLine += '' + description;
console.log(helpLine);
console.log();
}
function printHelp() {
console.log('khamake options:\n');
for (let option of options) {
if (option.hidden) {
continue;
}
if (option.short) {
printHelpLine('-' + option.short + ' ' + '--' + option.full, option.description);
}
else {
printHelpLine('--' + option.full, option.description);
}
}
}
function isTarget(target: string) {
if (target.trim().length < 1) return false;
return true;
}
for (let option of options) {
if (option.value) {
parsedOptions[option.full] = option.default;
}
else {
parsedOptions[option.full] = false;
}
}
let targetIsDefault = true;
let args = process.argv;
for (let i = 2; i < args.length; ++i) {
let arg = args[i];
if (arg === '--') break;
if (arg[0] === '-') {
if (arg[1] === '-') {
if (arg.substr(2) === 'help') {
printHelp();
process.exit(0);
}
for (let option of options) {
if (arg.substr(2) === option.full) {
if (option.value) {
++i;
parsedOptions[option.full] = args[i];
if (option.full === 'target') {
targetIsDefault = false;
}
else if (option.full === 'parallelAssetConversion') {
parsedOptions[option.full] = parseInt(parsedOptions[option.full]);
}
}
else {
parsedOptions[option.full] = true;
}
}
}
}
else {
if (arg[1] === 'h') {
printHelp();
process.exit(0);
}
for (let option of options) {
if (option.short && arg[1] === option.short) {
if (option.value) {
++i;
parsedOptions[option.full] = args[i];
if (option.full === 'target') {
targetIsDefault = false;
}
}
else {
parsedOptions[option.full] = true;
}
}
}
}
}
else {
if (isTarget(arg)) {
parsedOptions.target = arg.toLowerCase();
targetIsDefault = false;
}
}
}
if (parsedOptions.run) {
parsedOptions.compile = true;
}
async function runKhamake() {
try {
let logInfo = function (text: string, newline: boolean) {
if (newline) {
console.log(text);
}
else {
process.stdout.write(text);
}
};
let logError = function (text: string, newline: boolean) {
if (newline) {
console.error(text);
}
else {
process.stderr.write(text);
}
};
await require('./main.js').run(parsedOptions, { info: logInfo, error: logError });
}
catch (error) {
if (error) {
console.log(error);
}
process.exit(1);
}
}
if (parsedOptions.init) {
console.log('Initializing Kha project.\n');
require('./init').run(parsedOptions.name, parsedOptions.from, parsedOptions.projectfile);
console.log('If you want to use the git version of Kha, execute "git init" and "git submodule add https://github.com/Kode/Kha.git".');
}
else if (parsedOptions.server) {
console.log('Running server on ' + parsedOptions.port);
let nstatic = require('node-static');
let fileServer = new nstatic.Server(path.join(parsedOptions.from, 'build', targetIsDefault ? 'html5' : parsedOptions.target), { cache: 0 });
let server = require('http').createServer(function (request: any, response: any) {
request.addListener('end', function () {
fileServer.serve(request, response);
}).resume();
});
server.on('error', function (e: any) {
if (e.code === 'EADDRINUSE') {
console.log('Error: Port ' + parsedOptions.port + ' is already in use.');
console.log('Please close the competing program (maybe another instance of khamake?)');
console.log('or switch to a different port using the --port argument.');
}
});
server.listen(parsedOptions.port);
if (parsedOptions.watch) runKhamake();
}
else {
runKhamake();
}

View File

@ -0,0 +1,12 @@
import * as path from 'path';
import {sysdir} from './exec';
let korepath = path.join(__dirname, '..', '..', '..', 'Kinc', 'Tools', sysdir());
export function init(options: any) {
korepath = path.join(options.kha, 'Kinc', 'Tools', sysdir());
}
export function get() {
return korepath;
}

View File

@ -0,0 +1,37 @@
let myInfo = function (text: string, newline: boolean) {
if (newline) {
console.log(text);
}
else {
process.stdout.write(text);
}
};
let myError = function (text: string, newline: boolean) {
if (newline) {
console.error(text);
}
else {
process.stderr.write(text);
}
};
export function set(log: {info: (text: string, newline: boolean) => void, error: (text: string, newline: boolean) => void}) {
myInfo = log.info;
myError = log.error;
}
export function silent(showErrors: boolean = false) {
myInfo = function () {};
if (!showErrors) {
myError = function () {};
}
}
export function info(text: string, newline: boolean = true) {
myInfo(text, newline);
}
export function error(text: string, newline: boolean = true) {
myError(text, newline);
}

View File

@ -0,0 +1,799 @@
import * as child_process from 'child_process';
import * as fs from 'fs-extra';
import * as os from 'os';
import * as path from 'path';
import {sys, sysdir} from './exec';
import * as korepath from './korepath';
import * as log from './log';
import {Options} from './Options';
import {Platform} from './Platform';
import {Project, Target, Library} from './Project';
import {loadProject, Callbacks} from './ProjectFile';
import {VisualStudioVersion} from './VisualStudioVersion';
import {AssetConverter} from './AssetConverter';
import {HaxeCompiler} from './HaxeCompiler';
import {ShaderCompiler, CompiledShader} from './ShaderCompiler';
import {KhaExporter} from './Exporters/KhaExporter';
import {DebugHtml5Exporter} from './Exporters/DebugHtml5Exporter';
import {EmptyExporter} from './Exporters/EmptyExporter';
import {FlashExporter} from './Exporters/FlashExporter';
import {Html5Exporter} from './Exporters/Html5Exporter';
import {Html5WorkerExporter} from './Exporters/Html5WorkerExporter';
import {JavaExporter} from './Exporters/JavaExporter';
import {KincExporter} from './Exporters/KincExporter';
import {KincHLExporter} from './Exporters/KincHLExporter';
import {KromExporter} from './Exporters/KromExporter';
import {NodeExporter} from './Exporters/NodeExporter';
import {PlayStationMobileExporter} from './Exporters/PlayStationMobileExporter';
import {WpfExporter} from './Exporters/WpfExporter';
import {writeHaxeProject} from './HaxeProject';
import * as Icon from './Icon';
let lastAssetConverter: AssetConverter;
let lastShaderCompiler: ShaderCompiler;
let lastHaxeCompiler: HaxeCompiler;
function fixName(name: string): string {
name = name.replace(/[-@\ \.\/\\]/g, '_');
if (name[0] === '0' || name[0] === '1' || name[0] === '2' || name[0] === '3' || name[0] === '4'
|| name[0] === '5' || name[0] === '6' || name[0] === '7' || name[0] === '8' || name[0] === '9') {
name = '_' + name;
}
return name;
}
function safeName(name: string): string {
return name.replace(/[^A-z0-9\-\_]/g, '-');
}
function createKorefile(name: string, exporter: KhaExporter, options: Options, targetOptions: any, libraries: Library[], cdefines: string[], cflags: string[], cppflags: string[], stackSize: number, version: string, id: string, korehl: boolean, icon: string): string {
let out = '';
out += 'let fs = require(\'fs\');\n';
out += 'let path = require(\'path\');\n';
out += 'let project = new Project(\'' + name + '\');\n';
if (version) {
out += 'project.version = \'' + version + '\';\n';
}
if (id) {
out += 'project.id = \'' + id + '\';\n';
}
if (icon != null) out += 'project.icon = \'' + icon + '\';\n';
for (let cdefine of cdefines) {
out += 'project.addDefine(\'' + cdefine + '\');\n';
}
for (let cppflag of cppflags) {
out += 'project.addCppFlag(\'' + cppflag + '\');\n';
}
for (let cflag of cflags) {
out += 'project.addCFlag(\'' + cflag + '\');\n';
}
out += 'project.addDefine(\'HXCPP_API_LEVEL=400\');\n';
out += 'project.addDefine(\'HXCPP_DEBUG\', \'Debug\');\n';
if (!options.slowgc) {
out += 'project.addDefine(\'HXCPP_GC_GENERATIONAL\');\n';
}
if (targetOptions) {
let koreTargetOptions: any = {};
for (let option in targetOptions) {
koreTargetOptions[option] = targetOptions[option];
}
out += 'project.targetOptions = ' + JSON.stringify(koreTargetOptions) + ';\n';
}
out += 'project.setDebugDir(\'' + path.relative(options.from, path.join(options.to, exporter.sysdir())).replace(/\\/g, '/') + '\');\n';
let buildpath = path.relative(options.from, path.join(options.to, exporter.sysdir() + '-build')).replace(/\\/g, '/');
if (buildpath.startsWith('..')) buildpath = path.resolve(path.join(options.from.toString(), buildpath));
out += 'await project.addProject(\'' + path.join(options.kha, 'Kinc').replace(/\\/g, '/') + '\');\n';
out += 'await project.addProject(\'' + buildpath.replace(/\\/g, '/') + '\');\n';
if (korehl) out += 'await project.addProject(\'' + path.join(options.kha, 'Backends', 'Kinc-HL').replace(/\\/g, '/') + '\');\n';
else out += 'await project.addProject(\'' + path.join(options.kha, 'Backends', 'Kinc-hxcpp').replace(/\\/g, '/') + '\');\n';
for (let lib of libraries) {
let libPath: string = lib.libpath.replace(/\\/g, '/');
out += 'if (fs.existsSync(path.join(\'' + libPath + '\', \'kfile.js\')) || fs.existsSync(path.join(\'' + libPath + '\', \'kincfile.js\')) || fs.existsSync(path.join(\'' + libPath + '\', \'korefile.js\'))) {\n';
out += '\tawait project.addProject(\'' + libPath + '\');\n';
out += '}\n';
}
if (stackSize) {
out += 'project.stackSize = ' + stackSize + ';\n';
}
out += 'project.flatten();\n';
out += 'resolve(project);\n';
return out;
}
function runKmake(options: string[]) {
return new Promise<void>((resolve, reject) => {
const child = child_process.spawn(path.join(korepath.get(), 'kmake' + sys()), options);
child.stdout.on('data', (data: any) => {
const str = data.toString();
log.info(str, false);
});
child.stderr.on('data', (data: any) => {
const str = data.toString();
log.error(str, false);
});
child.on('error', (err: any) => {
log.error('Could not start kmake.');
reject();
});
child.on('close', (code: number) => {
if (code === 0) {
resolve();
}
else {
reject();
}
});
});
}
async function exportProjectFiles(name: string, resourceDir: string, options: Options, exporter: KhaExporter, kore: boolean, korehl: boolean, icon: string,
libraries: Library[], targetOptions: any, defines: string[], cdefines: string[], cflags: string[], cppflags: string[], stackSize: number, version: string, id: string): Promise<string> {
if (options.haxe !== '') {
let haxeOptions = exporter.haxeOptions(name, targetOptions, defines);
haxeOptions.defines.push('kha');
haxeOptions.defines.push('kha_version=1810');
haxeOptions.safeName = safeName(haxeOptions.name);
haxeOptions.defines.push('kha_project_name=' + haxeOptions.name);
if (options.livereload) haxeOptions.defines.push('kha_live_reload');
if (options.debug && haxeOptions.parameters.indexOf('-debug') < 0) {
haxeOptions.parameters.push('-debug');
}
writeHaxeProject(options.to, !options.noproject, haxeOptions);
if (!options.nohaxe) {
let compiler = new HaxeCompiler(options.to, haxeOptions.to, haxeOptions.realto, resourceDir, options.haxe, 'project-' + exporter.sysdir() + '.hxml', haxeOptions.sources, exporter.sysdir(), options.watchport, options.livereload, options.port);
lastHaxeCompiler = compiler;
try {
await compiler.run(options.watch);
}
catch (error) {
return Promise.reject(error);
}
}
for (let callback of Callbacks.postHaxeCompilation) {
callback();
}
await exporter.export(name, targetOptions, haxeOptions);
}
let buildDir = path.join(options.to, exporter.sysdir() + '-build');
if (options.haxe !== '' && kore && !options.noproject) {
// If target is a Kore project, generate additional project folders here.
// generate the kincfile.js
fs.copySync(path.join(__dirname, '..', 'Data', 'hxcpp', 'kfile.js'), path.join(buildDir, 'kfile.js'), { overwrite: true });
fs.writeFileSync(path.join(options.to, 'kfile.js'), createKorefile(name, exporter, options, targetOptions, libraries, cdefines, cflags, cppflags, stackSize, version, id, false, icon));
// Similar to khamake.js -> main.js -> run(...)
// We now do kincmake.js -> main.js -> run(...)
// This will create additional project folders for the target,
// e.g. 'build/pi-build'
try {
const kmakeOptions = ['--from', options.from, '--to', buildDir, '--kfile', path.resolve(options.to, 'kfile.js'), '-t', koreplatform(options.target), '--noshaders',
'--graphics', options.graphics, '--arch', options.arch, '--audio', options.audio, '--vr', options.vr, '-v', options.visualstudio
];
if (options.nosigning) {
kmakeOptions.push('--nosigning');
}
if (options.debug) {
kmakeOptions.push('--debug');
}
if (options.run) {
kmakeOptions.push('--run');
}
if (options.compile) {
kmakeOptions.push('--compile');
}
await runKmake(kmakeOptions);
for (let callback of Callbacks.postCppCompilation) {
callback();
}
log.info('Done.');
return name;
}
catch (error) {
if (error) {
log.error('Error: ' + error);
}
else {
log.error('Error.');
}
process.exit(1);
return name;
}
}
else if (options.haxe !== '' && korehl && !options.noproject) {
fs.copySync(path.join(__dirname, '..', 'Data', 'hl', 'kore_sources.c'), path.join(buildDir, 'kore_sources.c'), { overwrite: true });
fs.copySync(path.join(__dirname, '..', 'Data', 'hl', 'kfile.js'), path.join(buildDir, 'kfile.js'), { overwrite: true });
fs.writeFileSync(path.join(options.to, 'kfile.js'), createKorefile(name, exporter, options, targetOptions, libraries, cdefines, cflags, cppflags, stackSize, version, id, korehl, icon));
try {
const kmakeOptions = ['--from', options.from, '--to', buildDir, '--kfile', path.resolve(options.to, 'kfile.js'), '-t', koreplatform(options.target), '--noshaders',
'--graphics', options.graphics, '--arch', options.arch, '--audio', options.audio, '--vr', options.vr, '-v', options.visualstudio
];
if (options.nosigning) {
kmakeOptions.push('--nosigning');
}
if (options.debug) {
kmakeOptions.push('--debug');
}
if (options.run) {
kmakeOptions.push('--run');
}
if (options.compile) {
kmakeOptions.push('--compile');
}
await runKmake(kmakeOptions);
for (let callback of Callbacks.postCppCompilation) {
callback();
}
log.info('Done.');
return name;
}
catch (error) {
if (error) {
log.error('Error: ' + error);
}
else {
log.error('Error.');
}
process.exit(1);
return name;
}
}
else {
// If target is not a Kore project, e.g. HTML5, finish building here.
log.info('Done.');
return name;
}
}
function checkKorePlatform(platform: string) {
return platform === 'windows'
|| platform === 'windowsapp'
|| platform === 'ios'
|| platform === 'osx'
|| platform === 'android'
|| platform === 'linux'
|| platform === 'emscripten'
|| platform === 'pi'
|| platform === 'tvos'
|| platform === 'ps4'
|| platform === 'xboxone'
|| platform === 'switch'
|| platform === 'xboxscarlett'
|| platform === 'ps5'
|| platform === 'freebsd';
}
function koreplatform(platform: string) {
if (platform.endsWith('-hl')) return platform.substr(0, platform.length - '-hl'.length);
else return platform;
}
let kore = false;
let korehl = false;
async function exportKhaProject(options: Options): Promise<string> {
log.info('Creating Kha project.');
let project: Project = null;
let foundProjectFile = false;
// get the khafile.js and load the config code,
// then create the project config object, which contains stuff
// like project name, assets paths, sources path, library path...
if (fs.existsSync(path.join(options.from, options.projectfile))) {
try {
project = await loadProject(options.from, options.projectfile, options.target);
}
catch (x) {
log.error(x);
throw 'Loading the projectfile failed.';
}
foundProjectFile = true;
}
if (!foundProjectFile) {
throw 'No khafile found.';
}
let temp = path.join(options.to, 'temp');
fs.ensureDirSync(temp);
let exporter: KhaExporter = null;
let target = options.target.toLowerCase();
let baseTarget = target;
let customTarget: Target = null;
if (project.customTargets.get(options.target)) {
customTarget = project.customTargets.get(options.target);
baseTarget = customTarget.baseTarget;
}
switch (baseTarget) {
case Platform.Krom:
exporter = new KromExporter(options);
break;
case Platform.Flash:
exporter = new FlashExporter(options);
break;
case Platform.HTML5:
exporter = new Html5Exporter(options);
break;
case Platform.HTML5Worker:
exporter = new Html5WorkerExporter(options);
break;
case Platform.DebugHTML5:
exporter = new DebugHtml5Exporter(options);
break;
case Platform.WPF:
exporter = new WpfExporter(options);
break;
case Platform.Java:
exporter = new JavaExporter(options);
break;
case Platform.PlayStationMobile:
exporter = new PlayStationMobileExporter(options);
break;
case Platform.Node:
exporter = new NodeExporter(options);
break;
case Platform.Empty:
exporter = new EmptyExporter(options);
break;
default:
if (baseTarget.endsWith('-hl')) {
korehl = true;
options.target = koreplatform(baseTarget);
if (!checkKorePlatform(options.target)) {
log.error(`Unknown platform: ${target} (baseTarget=$${baseTarget})`);
return Promise.reject('');
}
exporter = new KincHLExporter(options);
}
else {
kore = true;
options.target = koreplatform(baseTarget);
if (!checkKorePlatform(options.target)) {
log.error(`Unknown platform: ${target} (baseTarget=$${baseTarget})`);
return Promise.reject('');
}
exporter = new KincExporter(options);
}
break;
}
exporter.setSystemDirectory(target);
let buildDir = path.join(options.to, exporter.sysdir() + '-build');
// Create the target build folder
// e.g. 'build/pi'
fs.ensureDirSync(path.join(options.to, exporter.sysdir()));
let defaultWindowOptions = {
width: 800,
height: 600
};
let windowOptions = project.windowOptions ? project.windowOptions : defaultWindowOptions;
exporter.setName(project.name);
exporter.setWidthAndHeight(
'width' in windowOptions ? windowOptions.width : defaultWindowOptions.width,
'height' in windowOptions ? windowOptions.height : defaultWindowOptions.height
);
for (let source of project.sources) {
exporter.addSourceDirectory(source);
}
for (let library of project.libraries) {
exporter.addLibrary(library);
}
exporter.parameters = exporter.parameters.concat(project.parameters);
project.scriptdir = options.kha;
if (baseTarget !== Platform.Java && baseTarget !== Platform.WPF) {
project.addShaders('Sources/Shaders/**', {});
}
for (let callback of Callbacks.preAssetConversion) {
callback();
}
let assetConverter = new AssetConverter(exporter, options, project.assetMatchers);
lastAssetConverter = assetConverter;
let assets = await assetConverter.run(options.watch, temp);
if ((target === Platform.DebugHTML5 && process.platform === 'win32') || target === Platform.HTML5) {
Icon.exportIco(project.icon, path.join(options.to, exporter.sysdir(), 'favicon.ico'), options.from, options);
}
else if (target === Platform.DebugHTML5) {
Icon.exportPng(project.icon, path.join(options.to, exporter.sysdir(), 'favicon.png'), 256, 256, 0xffffffff, true, options.from, options);
}
let shaderDir = path.join(options.to, exporter.sysdir() + '-resources');
for (let callback of Callbacks.preShaderCompilation) {
callback();
}
fs.ensureDirSync(shaderDir);
let oldResources: any = null;
let recompileAllShaders = false;
try {
oldResources = JSON.parse(fs.readFileSync(path.join(options.to, exporter.sysdir() + '-resources', 'files.json'), 'utf8'));
for (let file of oldResources.files) {
if (file.type === 'shader') {
if (!file.files || file.files.length === 0) {
recompileAllShaders = true;
break;
}
}
}
}
catch (error) {
}
let exportedShaders: CompiledShader[] = [];
if (!options.noshaders) {
if (fs.existsSync(path.join(options.from, 'Backends'))) {
let libdirs = fs.readdirSync(path.join(options.from, 'Backends'));
for (let ld in libdirs) {
let libdir = path.join(options.from, 'Backends', libdirs[ld]);
if (fs.statSync(libdir).isDirectory()) {
let exe = path.join(libdir, 'krafix', 'krafix-' + options.target + '.exe');
if (fs.existsSync(exe)) {
options.krafix = exe;
}
}
}
}
let shaderCompiler = new ShaderCompiler(exporter, baseTarget, options.krafix, shaderDir, temp,
buildDir, options, project.shaderMatchers);
lastShaderCompiler = shaderCompiler;
try {
if (baseTarget !== Platform.Java && baseTarget !== Platform.WPF) {
exportedShaders = await shaderCompiler.run(options.watch, recompileAllShaders);
}
}
catch (err) {
return Promise.reject(err);
}
}
function findShader(name: string) {
let fallback: any = { };
fallback.files = [];
fallback.inputs = [];
fallback.outputs = [];
fallback.uniforms = [];
fallback.types = [];
try {
for (let file of oldResources.files) {
if (file.type === 'shader' && file.name === fixName(name)) {
return file;
}
}
}
catch (error) {
return fallback;
}
return fallback;
}
let files: {name: string, files: string[], file_sizes: number[], type: string, inputs: any[], outputs: any[], uniforms: any[], types: any[]}[] = [];
for (let asset of assets) {
let file: any = {
name: fixName(asset.name),
files: asset.files,
file_sizes: asset.file_sizes,
type: asset.type
};
if (file.type === 'image') {
file.original_width = asset.original_width;
file.original_height = asset.original_height;
if (asset.readable) file.readable = asset.readable;
}
files.push(file);
}
for (let shader of exportedShaders) {
if (shader.noembed) continue;
let oldShader = findShader(shader.name);
files.push({
name: fixName(shader.name),
files: shader.files === null ? oldShader.files : shader.files,
file_sizes: [1],
type: 'shader',
inputs: shader.inputs === null ? oldShader.inputs : shader.inputs,
outputs: shader.outputs === null ? oldShader.outputs : shader.outputs,
uniforms: shader.uniforms === null ? oldShader.uniforms : shader.uniforms,
types: shader.types === null ? oldShader.types : shader.types
});
}
// Sort to prevent files.json from changing between makes when no files have changed.
files.sort(function(a: any, b: any) {
if (a.name > b.name) return 1;
if (a.name < b.name) return -1;
return 0;
});
function secondPass() {
// First pass is for main project files. Second pass is for shaders.
// Will try to look for the folder, e.g. 'build/Shaders'.
// if it exists, export files similar to other a
let hxslDir = path.join('build', 'Shaders');
/** if (fs.existsSync(hxslDir) && fs.readdirSync(hxslDir).length > 0) {
addShaders(exporter, platform, project, from, to.resolve(exporter.sysdir() + '-resources'), temp, from.resolve(Paths.get(hxslDir)), krafix);
if (foundProjectFile) {
fs.outputFileSync(to.resolve(Paths.get(exporter.sysdir() + '-resources', 'files.json')).toString(), JSON.stringify({ files: files }, null, '\t'), { encoding: 'utf8' });
log.info('Assets done.');
exportProjectFiles(name, from, to, options, exporter, platform, khaDirectory, haxeDirectory, kore, project.libraries, project.targetOptions, callback);
}
else {
exportProjectFiles(name, from, to, options, exporter, platform, khaDirectory, haxeDirectory, kore, project.libraries, project.targetOptions, callback);
}
}*/
}
if (foundProjectFile) {
fs.outputFileSync(path.join(options.to, exporter.sysdir() + '-resources', 'files.json'), JSON.stringify({ files: files }, null, '\t'));
}
for (let callback of Callbacks.preHaxeCompilation) {
callback();
}
return await exportProjectFiles(project.name, path.join(options.to, exporter.sysdir() + '-resources'), options, exporter, kore, korehl, project.icon,
project.libraries, project.targetOptions, project.defines, project.cdefines, project.cflags, project.cppflags, project.stackSize, project.version, project.id);
}
function isKhaProject(directory: string, projectfile: string) {
return fs.existsSync(path.join(directory, 'Kha')) || fs.existsSync(path.join(directory, projectfile));
}
async function exportProject(options: Options): Promise<string> {
if (isKhaProject(options.from, options.projectfile)) {
return await exportKhaProject(options);
}
else {
log.error('Neither Kha directory nor project file (' + options.projectfile + ') found.');
return 'Unknown';
}
}
function runProject(options: any, name: string): Promise<void> {
return new Promise<void>((resolve, reject) => {
log.info('Running...');
let run = child_process.spawn(
path.join(process.cwd(), options.to, 'linux-build', name),
[],
{ cwd: path.join(process.cwd(), options.to, 'linux') });
run.stdout.on('data', function (data: any) {
log.info(data.toString());
});
run.stderr.on('data', function (data: any) {
log.error(data.toString());
});
run.on('close', function (code: number) {
resolve();
});
});
}
export let api = 2;
function findKhaVersion(dir: string): string {
let p = path.join(dir, '.git');
let hasGitInfo = false;
if (fs.existsSync(p)) {
let stat = fs.statSync(p);
hasGitInfo = stat.isDirectory();
// otherwise git might not utilize an in-place directory
if (!hasGitInfo) {
let contents = fs.readFileSync(p).toString('utf8', 0, 7);
hasGitInfo = contents === 'gitdir:';
}
}
if (hasGitInfo) {
let gitVersion = 'git-error';
try {
const output = child_process.spawnSync('git', ['rev-parse', 'HEAD'], {encoding: 'utf8', cwd: dir}).output;
for (const str of output) {
if (str != null && str.length > 0) {
gitVersion = str.substr(0, 8);
break;
}
}
}
catch (error) {
}
let gitStatus = 'git-error';
try {
const output = child_process.spawnSync('git', ['status', '--porcelain'], {encoding: 'utf8', cwd: dir}).output;
gitStatus = '';
for (const str of output) {
if (str != null && str.length > 0) {
gitStatus = str.trim();
break;
}
}
}
catch (error) {
}
if (gitStatus) {
return gitVersion + ', ' + gitStatus.replace(/\n/g, ',');
}
else {
return gitVersion;
}
}
else {
return '¯\\_(ツ)_/¯';
}
}
export async function run(options: Options, loglog: any): Promise<string> {
if (options.silent) {
log.silent();
}
else {
log.set(loglog);
}
if (options.quiet) {
log.silent(true);
}
if (!options.kha) {
let p = path.join(__dirname, '..', '..', '..');
if (fs.existsSync(p) && fs.statSync(p).isDirectory()) {
options.kha = p;
}
}
else {
options.kha = path.resolve(options.kha);
}
log.info('Using Kha (' + findKhaVersion(options.kha) + ') from ' + options.kha);
if (options.parallelAssetConversion === undefined) {
options.parallelAssetConversion = 0;
}
if (!options.haxe) {
let haxepath = path.join(options.kha, 'Tools', sysdir());
if (fs.existsSync(haxepath) && fs.statSync(haxepath).isDirectory()) options.haxe = haxepath;
}
if (!options.krafix) {
let krafixpath = path.join(options.kha, 'Kinc', 'Tools', sysdir(), 'krafix' + sys());
if (fs.existsSync(krafixpath)) options.krafix = krafixpath;
}
if (!options.kraffiti) {
const kraffitipath = path.join(options.kha, 'Kinc', 'Tools', sysdir(), 'kraffiti' + sys());
if (fs.existsSync(kraffitipath)) options.kraffiti = kraffitipath;
}
else {
log.info('Using kraffiti from ' + options.kraffiti);
}
if (!options.ogg && options.ffmpeg) {
options.ogg = options.ffmpeg + ' -nostdin -i {in} {out} -y';
}
if (!options.mp3 && options.ffmpeg) {
options.mp3 = options.ffmpeg + ' -nostdin -i {in} {out}';
}
if (!options.ogg) {
let oggpath = path.join(options.kha, 'Tools', sysdir(), 'oggenc' + sys());
if (fs.existsSync(oggpath)) options.ogg = oggpath + ' {in} -o {out} --quiet';
}
if (!options.mp3) {
let lamepath = path.join(options.kha, 'Tools', sysdir(), 'lame' + sys());
if (fs.existsSync(lamepath)) options.mp3 = lamepath + ' {in} {out}';
}
// if (!options.kravur) {
// let kravurpath = path.join(options.kha, 'Tools', 'kravur', 'kravur' + sys());
// if (fs.existsSync(kravurpath)) options.kravur = kravurpath + ' {in} {size} {out}';
// }
if (!options.aac && options.ffmpeg) {
options.aac = options.ffmpeg + ' -nostdin -i {in} {out}';
}
if (!options.h264 && options.ffmpeg) {
options.h264 = options.ffmpeg + ' -nostdin -i {in} {out}';
}
if (!options.webm && options.ffmpeg) {
options.webm = options.ffmpeg + ' -nostdin -i {in} {out}';
}
if (!options.wmv && options.ffmpeg) {
options.wmv = options.ffmpeg + ' -nostdin -i {in} {out}';
}
if (!options.theora && options.ffmpeg) {
options.theora = options.ffmpeg + ' -nostdin -i {in} {out}';
}
if (options.target === 'emscripten') {
console.log();
console.log('Please note that the html5 target\n'
+ 'is usually a better choice.\n'
+ 'In particular the html5 target usually runs faster\n'
+ 'than the emscripten target. That is because\n'
+ 'Haxe and JavaScript are similar in many ways and\n'
+ 'therefore the html5 target can make direct use of\n'
+ 'all of the optimizations in modern JavaScript\n'
+ 'runtimes. The emscripten target on the other hand\n'
+ 'has to provide its own garbage collector and many\n'
+ 'other performance critical pieces of infrastructure.'
);
console.log();
}
let name = '';
try {
name = await exportProject(options);
}
catch (err) {
for (let callback of Callbacks.onFailure) {
callback(err);
}
throw err;
}
for (let callback of Callbacks.postBuild) {
callback();
}
if ((options.target === Platform.Linux || options.target === Platform.FreeBSD) && options.run) {
await runProject(options, name);
}
return name;
}
export function close() {
if (lastAssetConverter) lastAssetConverter.close();
if (lastShaderCompiler) lastShaderCompiler.close();
if (lastHaxeCompiler) lastHaxeCompiler.close();
}