const gulp = require('gulp'); const path = require('path'); const os = require('os'); const fs = require('fs'); const fse = require('fs-extra'); const Haxe = require('./haxe'); const FlashPlayer = require('./flashplayer'); const Android = require('./android'); const Neko = require('./neko'); const Env = require('./env'); const debug = require('./debug'); const webserver = require('gulp-webserver'); const run = require('../run/index'); const tail = require('./tail'); const exec = require('./exec'); const deb = require('gulp-debian'); const {BuildSystem, Platform, Config} = require('./core'); const vfs = require('vinyl-fs'); const rename = require('gulp-rename'); const template = require('lodash.template'); const streamToPromise = (stream) => { return new Promise((resolve, reject) => { stream.on("end", resolve); stream.on("error", reject); }); }; /** * */ class Target { constructor(config, platform) { this.config = config; this.platform = platform; this.target = 'target'; } prepare() { return Promise.resolve(); } call() { throw 'Not Implemented'; } get targetPath() { return path.resolve(this.target, this.config.name, this.platform); } } /** * */ class Builder extends Target { constructor(config, platform, buildSystem, debug) { super(config, platform); this.buildSystem = buildSystem; this.debug = debug; } static register(buildSystem, builder) { Builder.factory[buildSystem] = builder; } static new(config, platform, buildSystem, debug) { return new Builder.factory[buildSystem](config, platform, buildSystem, debug); } } Builder.factory = {}; /** * */ class HaxeBuilder extends Builder { constructor(config, platform, buildSystem, debug) { super(config, platform, buildSystem, debug); this.haxe = new Haxe(); this.android = new Android(); } prepare() { let result = this.haxe.prepare().then(() => this.haxe.install(this.config.libs)); if (this.buildSystem === BuildSystem.OPENFL) { if (this.platform === Platform.ANDROID) { result = result .then(() => this.android.prepare()) .then(() => this.android.activate()) .then(() => this.android.sdkmanager([ 'tools', 'platform-tools', 'build-tools;27.0.3', //'platforms;android-19', 'platforms;android-26', //'ndk-bundle', 'lldb;3.1', 'cmake;3.6.4111459', ])); const AndroidSDK = this.android.android_home; const AndroidNDK = this.android.ndk_home; const answer = `echo "${[AndroidSDK, AndroidNDK].join('\n')}"`; result = result.then(() => { return exec('.', [answer, '|', this.haxe.haxelibBin, 'run', 'openfl', 'setup', this.platform].join(' ')); }); } } return result; } call() { const target = this.targetPath; switch (this.buildSystem) { case BuildSystem.OPENFL: return this.haxe.openfl('build', this.platform, this.config, this.debug) .then(result => streamToPromise(result.pipe(vfs.dest(target)))); case BuildSystem.HAXE: return this.haxe.build(this.platform, this.config, this.debug) .then(result => streamToPromise(result.pipe(vfs.dest(target)))); } } } Builder.register(BuildSystem.HAXE, HaxeBuilder); Builder.register(BuildSystem.OPENFL, HaxeBuilder); /** * */ class Packer extends Target { call() { throw 'Not Implemented'; } static register(platform, name, packer) { if (!Packer.factory[platform]) Packer.factory[platform] = {}; Packer.factory[platform][name] = packer; } static new(config, platform, name) { return new Packer.factory[platform][name](config); } } Packer.factory = {}; /** * */ class FlashHTMLPacker extends Packer { constructor(config) { super(config, Platform.FLASH); } call() { const target = this.targetPath; const indexTemplate = template(fs.readFileSync(path.resolve(__dirname, '..', 'template/flash/index.html'))); const index = indexTemplate(this.config); fs.writeFileSync(path.resolve(target, 'index.html'), index); fs.copyFileSync(path.resolve(__dirname, '..', 'template/flash/swf.js'), path.resolve(target, 'swf.js')); return Promise.resolve(); } } FlashHTMLPacker.NAME = 'html'; Packer.register(Platform.FLASH, FlashHTMLPacker.NAME, FlashHTMLPacker); /** * */ class LinuxDEBPacker extends Packer { constructor(config) { super(config, Platform.LINUX); } call() { const target = this.targetPath; const buildDir = path.join(os.tmpdir(), 'build', this.config.name, 'debian'); const iconfile = `${this.config.meta.filename}${path.extname(this.config.meta.icon)}`; const desktopTemplate = template(fs.readFileSync(path.resolve(__dirname, '..', 'template/linux/app.desktop'))); const desktop = desktopTemplate(Object.assign({iconfile:iconfile}, this.config)); fse.ensureDirSync(`${buildDir}/usr/share/applications`); fs.writeFileSync(`${buildDir}/usr/share/applications/${this.config.meta.filename}.desktop`, desktop); fse.copySync(`${target}`, `${buildDir}/usr/share/${this.config.meta.filename}/`); fse.copySync(this.config.meta.icon, `${buildDir}/usr/share/${this.config.meta.filename}/${iconfile}`); return gulp.src(`${buildDir}/*`) .pipe(deb({ package: this.config.meta.filename, version: this.config.meta.version, section: 'base', priority: 'optional', architecture: 'all', maintainer: this.config.meta.author, description: this.config.meta.title, changelog: [], postinst: [ 'if [ "$1" = "configure" ] && [ -x "`which update-menus 2>/dev/null`" ] ; then\n' + ' update-menus\n' + 'fi' ], _target: '/', _out: path.join(target, '..', 'debian'), _clean: true, _verbose: false })); } } LinuxDEBPacker.NAME = 'deb'; Packer.register(Platform.LINUX, LinuxDEBPacker.NAME, LinuxDEBPacker); /** * */ class Runner extends Target { constructor(config, platform, debug) { super(config, platform); this.debug = debug; } log(stream) { return stream .pipe(tail(debug.log)) .pipe(rename('out.log')) .pipe(gulp.dest(this.targetPath)); } static register(platform, runner) { Runner.factory[platform] = runner; } static new(config, platform, debug) { return new Runner.factory[platform](config, debug); } } Runner.factory = {}; /** * */ class FlashRunner extends Runner { constructor(config, debug) { super(config, Platform.FLASH, debug); this.player = new FlashPlayer(debug); } prepare() { return this.player.prepare(); } call() { const target = this.targetPath; const filename = path.resolve(target, this.config.meta.filename+'.swf'); const result = this.player.run(filename, {cwd: target, verbosity: 0}); return this.log(result); } } Runner.register(Platform.FLASH, FlashRunner); /** * */ class Html5Runner extends Runner { constructor(config, debug) { super(config, Platform.HTML5, debug); } call() { return gulp.src(this.targetPath) .pipe(webserver({ // ToDo: config? host: '0.0.0.0', port: 3000, open: true, fallback: 'index.html' })); } } Runner.register(Platform.HTML5, Html5Runner); /** * */ class LinuxRunner extends Runner { constructor(config, debug) { super(config, Platform.LINUX, debug); } call() { const target = this.targetPath; const filename = path.resolve(target, this.config.meta.filename); const result = gulp.src(filename).pipe(run("./<%=file.basename%>", {cwd: target, verbosity: 0})); return this.log(result); } } Runner.register(Platform.LINUX, LinuxRunner); Runner.register(Platform.WINDOWS, LinuxRunner); //ToDo: /** * */ class NekoRunner extends Runner { constructor(config, debug) { super(config, Platform.NEKO, debug); this.neko = new Neko(); } prepare() { return this.neko.prepare(); } call() { const target = this.targetPath; const filename = path.resolve(target, this.config.meta.filename+'.n'); const result = this.neko.run(filename, {cwd: target, verbosity: 0}); return this.log(result); } } Runner.register(Platform.NEKO, NekoRunner); /** * */ class AndroidRunner extends Runner { constructor(config, debug) { super(config, Platform.ANDROID, debug); this.android = new Android(); } prepare() { return this.android.prepare(); } call() { const target = this.targetPath; const filename = path.resolve(target, this.config.meta.filename+'-debug.apk'); return this.log(gulp.src(filename) .pipe(this.android.apk()) .pipe(this.android.install()) .pipe(this.android.start()) .pipe(this.android.logcat()) ); } } Runner.register(Platform.ANDROID, AndroidRunner); /** * */ class Project { constructor(buildSystem, platforms, config, prepare) { this.buildSystem = buildSystem; this.platforms = Array.isArray(platforms) ? platforms : [platforms]; this.config = config; this.prepare = prepare; } build(platform, debug) { const builder = Builder.new(this.config, platform, this.buildSystem, debug); const tasks = [builder.prepare.bind(builder)]; if (this.prepare) tasks.push(this.prepare); tasks.push(builder.call.bind(builder)); return tasks; } run(platform, debug) { const runner = Runner.new(this.config, platform, debug); return [ runner.prepare.bind(runner), runner.call.bind(runner), ]; } test(platform) { return this.build(platform, debug).concat(this.run(platform, debug)); } pack(platform, name) { const packer = Packer.new(this.config, platform, name); return [ packer.prepare.bind(packer), packer.call.bind(packer), ]; } bind(module, external_gulp /* ToDo: spike */) { const _gulp = (external_gulp || gulp); for (const platform of this.platforms) { module.exports[`${this.config.name}:${platform}:build`] = _gulp.series(this.build(platform)); if (Runner.factory[platform]) { module.exports[`${this.config.name}:${platform}:run`] = _gulp.series(this.run(platform)); module.exports[`${this.config.name}:${platform}:test`] = _gulp.series(this.test(platform)); } if (Packer.factory[platform]) { for (const name of Object.keys(Packer.factory[platform])) { module.exports[`${this.config.name}:${platform}:${name}`] = _gulp.series(this.pack(platform, name)); } } } return this; } } Project.BuildSystem = BuildSystem; Project.Platform = Platform; Project.Config = Config; Project.Builder = Builder; Project.Runner = Runner; module.exports = Project;