#!/usr/bin/env node /* * * Copyright 2014 Canonical Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * */ var assert = require('assert'); var colors = require('colors'); var fs = require('fs'); var os = require('os'); var Q = require('q'); var path = require('path'); var shell = require('shelljs'); var ConfigParser = require('./config_parser'); var Constants = require('./constants'); var Utils = require('./utils'); var Manifest = require('./manifest'); var logger = require('./logger'); var PLATFORMS = Constants.PLATFORM_TYPES; module.exports.build = function(rootDir, target, nobuild, architecture, framework, debug) { var ubuntuDir = path.join(rootDir, 'platforms', 'ubuntu'); var campoDir = path.join(ubuntuDir, 'build'); if (!architecture) architecture = Constants.DEFAULT_ARCH; if (!framework) framework = Constants.DEFAULT_FRAMEWORK; assert.ok(fs.existsSync(ubuntuDir)); assert.ok(fs.existsSync(campoDir)); if (target === PLATFORMS.PHONE) return buildClickPackage(campoDir, ubuntuDir, nobuild, architecture, framework, debug); if (target === PLATFORMS.DESKTOP) return buildNative(campoDir, ubuntuDir, nobuild, debug); if (target === PLATFORMS.ALL) { return buildClickPackage(campoDir, ubuntuDir, nobuild, architecture, framework, debug).then(function () { return buildNative(campoDir, ubuntuDir, nobuild); }); } }; function buildClickPackage(campoDir, ubuntuDir, nobuild, architecture, framework, debug) { logger.info('Building Phone Application...'); assert.ok(architecture && architecture.match(/^[a-z0-9_]+$/)); Manifest.generate(path.join(ubuntuDir, 'config.xml'), ubuntuDir); var archDir = path.join(ubuntuDir, framework, architecture); var prefixDir = path.join(archDir, 'prefix'); if (!fs.existsSync(archDir)) shell.mkdir('-p', archDir); if (nobuild && fs.existsSync(path.join(prefixDir, 'cordova-ubuntu'))) { return Q(); } return checkChrootEnv(ubuntuDir, architecture, framework).then(function() { shell.rm('-rf', path.join(archDir, 'build')); shell.rm('-rf', prefixDir); shell.mkdir(path.join(archDir, 'build')); shell.mkdir(prefixDir); Utils.pushd(path.join(archDir, 'build')); var buildType = '"Debug"'; if (!debug) buildType = '"Release"'; var cmakeCmd = 'click chroot -a ' + architecture + ' -f ' + framework + ' run cmake ' + campoDir + ' -DCMAKE_INSTALL_PREFIX="' + prefixDir + '"' + ' -DCMAKE_BUILD_TYPE=' + buildType +' -DUBUNTU_TOUCH="1"'; var deps = additionalBuildDependencies(ubuntuDir).join(' ').replace(/ARCH/g, architecture); if (deps.length) cmakeCmd += ' -DADDITIONAL_DEPENDECIES="' + deps + '"'; return Utils.execAsync(cmakeCmd); }).then(function () { if (architecture != "i386" && framework != "ubuntu-sdk-15.04") Utils.execSync('find . -name AutomocInfo.cmake | xargs sed -i \'s;AM_QT_MOC_EXECUTABLE .*;AM_QT_MOC_EXECUTABLE "/usr/lib/\'$(dpkg-architecture -qDEB_BUILD_MULTIARCH)\'/qt5/bin/moc");\''); return Utils.execAsync('click chroot -a ' + architecture + ' -f ' + framework + ' run make -j ' + cpuCount()); }).then(function () { return Utils.execAsync('click chroot -a ' + architecture + ' -f ' + framework + ' run make install'); }).then(function () { Utils.cp(path.join(ubuntuDir, 'www', '*'), path.join(prefixDir, 'www')); Utils.cp(path.join(ubuntuDir, 'qml', '*'), path.join(prefixDir, 'qml')); Utils.cp(path.join(ubuntuDir, 'cordova.desktop'), prefixDir); Utils.cp(path.join(ubuntuDir, 'config.xml'), prefixDir); var content = JSON.parse(fs.readFileSync(path.join(ubuntuDir, 'manifest.json'), {encoding: "utf8"})); content.architecture = architecture; content.framework = framework; fs.writeFileSync(path.join(prefixDir, 'manifest.json'), JSON.stringify(content)); content = JSON.parse(fs.readFileSync(path.join(ubuntuDir, 'apparmor.json'), {encoding: "utf8"})); if (framework === "ubuntu-sdk-14.10") { content.policy_version = 1.2; } else { content.policy_version = 1.3; } content.policy_groups.push('webview'); fs.writeFileSync(path.join(prefixDir, 'apparmor.json'), JSON.stringify(content)); Utils.pushd(prefixDir); return Utils.execAsync('click build .'); }).then(function () { checkClickPackage(prefixDir); Utils.popd(); Utils.popd(); }); } function buildNative(campoDir, ubuntuDir, nobuild, debug) { logger.info('Building...'); var nativeDir = path.join(ubuntuDir, 'native'); var prefixDir = path.join(nativeDir, 'prefix'); if (nobuild && fs.existsSync(path.join(prefixDir, 'cordova-ubuntu'))) { return Q(); } return checkEnv(ubuntuDir).then(function() { Manifest.generate(path.join(ubuntuDir, 'config.xml'), ubuntuDir); shell.rm('-rf', prefixDir); shell.mkdir('-p', path.join(nativeDir, 'build')); shell.mkdir(prefixDir); Utils.pushd(path.join(nativeDir, 'build')); var buildType = '"Debug"'; if (!debug) buildType = '"Release"'; var cmakeCmd = 'cmake ' + campoDir + ' -DCMAKE_INSTALL_PREFIX="' + prefixDir + '"' + ' -DCMAKE_BUILD_TYPE=' + buildType; var deps = additionalBuildDependencies(ubuntuDir).join(' ').replace(/ARCH/g, ''); if (deps.length) cmakeCmd += ' -DADDITIONAL_DEPENDECIES="' + deps + '"'; var debDir; return Utils.execAsync(cmakeCmd).then(function () { return Utils.execAsync('make -j ' + cpuCount() + '; make install'); }).then(function () { Utils.cp(path.join(ubuntuDir, 'config.xml'), prefixDir); Utils.cp(path.join(ubuntuDir, 'www', '*'), path.join(prefixDir, 'www')); Utils.cp(path.join(ubuntuDir, 'qml', '*'), path.join(prefixDir, 'qml')); Utils.popd(); var manifest = JSON.parse(fs.readFileSync(path.join(ubuntuDir, 'manifest.json'), {encoding: "utf8"})); assert(manifest.name.length); assert(manifest.name.indexOf(' ') == -1); debDir = path.join(nativeDir, manifest.name); shell.rm('-rf', debDir); shell.mkdir('-p', path.join(debDir, 'debian')); Utils.cp(path.join(campoDir, '*'), debDir); Utils.cp(path.join(ubuntuDir, 'config.xml'), path.join(debDir, 'debian')); Utils.cp(path.join(ubuntuDir, 'www', '*'), path.join(debDir, 'www')); Utils.cp(path.join(ubuntuDir, 'qml', '*'), path.join(debDir, 'qml')); var destDir = path.join('/opt', manifest.name); var icon = fs.readFileSync(path.join(ubuntuDir, 'cordova.desktop'), {encoding: "utf8"}).match(/^Icon=(.+)$/m); icon = icon ? '/opt/' + manifest.name + '/' + icon[1] : 'qmlscene'; var maintainerEmail = manifest.maintainer.match('<.+>$'); maintainerEmail = maintainerEmail?maintainerEmail[0]:''; var maintainerName = manifest.maintainer.replace(maintainerEmail, '').trim(); maintainerEmail = maintainerEmail.replace('<', '').replace('>', ''); var props = { PACKAGE_NAME: manifest.name, PACKAGE_TITLE: manifest.title, PACKAGE_VERSION: manifest.version, MAINTAINER_NAME: maintainerName, MAINTAINER_EMAIL: maintainerEmail, PACKAGE_DESCRIPTION: manifest.description, PACKAGE_ICON: icon }; var templateDir = path.join(campoDir, 'bin', 'templates', 'project', 'misc'); var templates = [{source: path.join(templateDir, 'changelog'), dest: path.join(debDir, 'debian', 'changelog')}, {source: path.join(templateDir, 'compat'), dest: path.join(debDir, 'debian', 'compat')}, {source: path.join(templateDir, 'control'), dest: path.join(debDir, 'debian', 'control')}, {source: path.join(templateDir, 'rules'), dest: path.join(debDir, 'debian', 'rules')}, {source: path.join(templateDir, 'cordova.desktop'), dest: path.join(debDir, 'debian', 'cordova.desktop')}, {source: path.join(templateDir, 'install'), dest: path.join(debDir, 'debian', manifest.name + '.install')}]; for (var i = 0; i < templates.length; i++) { fillTemplate(templates[i].source, templates[i].dest, props); } logger.warn('Note: to build a debian package, run: '); logger.warn(' cd ' + debDir + '; ' + 'debuild'); }); }); }; /** * Generates a file from a template source, injecting the values contained in the * provided js object. */ function fillTemplate(source, dest, obj) { var content = fs.readFileSync(source, {encoding: "utf8"}); for (var prop in obj) { content = content.replace(new RegExp('{' + prop + '}', 'g'), obj[prop]); } fs.writeFileSync(dest, content); } function additionalBuildDependencies(ubuntuDir) { var config = new ConfigParser(path.join(ubuntuDir, 'config.xml')); var pkgConfig = []; config.etree.getroot().findall('./feature/deps/pkgConfig').forEach(function (element) { var list = JSON.parse(element.text); pkgConfig = pkgConfig.concat(list); }); return pkgConfig; } function checkClickPackage(prefixDir) { Utils.pushd(prefixDir); if (fs.existsSync('/usr/bin/click-run-checks')) { var cmd = '/usr/bin/click-run-checks *.click'; logger.debug(cmd); var output = shell.exec(cmd, { silent: true }).output; var json = '[', b = 0; for (var i = 0; i < output.length; i++) { if (output[i] == '{') { if (json.length > 1 && !b) json += ', '; b++; } else if (output[i] == '}') { b--; if (b == 0) json += '} '; } if (b > 0) { json += output[i]; } } json += ']'; var out = JSON.parse(json); for (var i = 0; i < out.length; i++) { if (out[i].warn) { for (var m in out[i].warn) { if (out[i].warn[m].text) { logger.warn(out[i].warn[m].text); if (out[i].warn[m].link) logger.warn(out[i].warn[m].link); } } } } for (var i = 0; i < out.length; i++) { if (out[i].error) { for (var m in out[i].error) { if (out[i].error[m].text) { logger.warn(out[i].error[m].text); if (out[i].error[m].link) logger.warn(out[i].error[m].link); } } } } } Utils.popd(); } function cpuCount() { return os.cpus().length; } function checkEnv(ubuntuDir) { var deps = [ 'click', 'cmake', 'libicu-dev', 'pkg-config', 'devscripts', 'qtbase5-dev', 'qtchooser', 'qtdeclarative5-dev', 'qtfeedback5-dev', 'qtlocation5-dev', 'qtmultimedia5-dev', 'qtpim5-dev', 'libqt5sensors5-dev', 'qtsystems5-dev' ]; deps = deps.concat(additionalDependencies(ubuntuDir)); deps = deps.join(' '); deps = deps.replace(/:ARCH/g, ''); if (!deps.length) return; var cmd = "dpkg-query -Wf'${db:Status-abbrev}' " + deps; var installCmd = "sudo apt-get install " + deps; var res = shell.exec(cmd); if (res.code !== 0 || res.output.match(/[^i ]/)) { logger.error("Error: missing packages " + deps); var deferred = Q.defer(); var readline = require('readline'); var rl = readline.createInterface(process.stdin, process.stdout); rl.setPrompt('Install missing packages? (Yn)> '); rl.prompt(); rl.on('line', function(line) { rl.close(); if (line !== 'Y' && line !== 'y') { deferred.reject(new Error()); return; } Utils.execAsync(installCmd).then(function() { deferred.resolve(); }).catch(function (error) { deferred.reject(new Error()); }).done(); }); return deferred.promise; } return Q(); } function checkChrootEnv(ubuntuDir, architecture, framework) { var deps = "cmake libicu-dev:ARCH pkg-config qtbase5-dev:ARCH qtchooser qtdeclarative5-dev:ARCH qtfeedback5-dev:ARCH qtlocation5-dev:ARCH qtmultimedia5-dev:ARCH qtpim5-dev:ARCH libqt5sensors5-dev:ARCH qtsystems5-dev:ARCH "; deps += additionalDependencies(ubuntuDir).join(' '); deps = deps.replace(/ARCH/g, architecture); var chrootCreateCmd = "sudo click chroot -a " + architecture + " -f " + framework + " create"; var chrootInstallCmd = "sudo click chroot -a " + architecture + " -f " + framework + " install " + deps; var cmd = "click chroot -a " + architecture + " -f " + framework + " run echo 1"; var res = shell.exec(cmd); if (res.code !== 0) { logger.error("\nError: missing " + architecture + " chroot"); logger.error("run:\n" + chrootCreateCmd); var deferred = Q.defer(); deferred.reject(new Error()); return deferred.promise; } cmd = "click chroot -a " + architecture + " -f " + framework + " run dpkg-query -Wf'${db:Status-abbrev}' " + deps; res = shell.exec(cmd); if (res.code !== 0 || res.output.match(/[^i ]/)) { logger.error("\nError: missing dependency inside " + architecture + " chroot"); logger.error("run:\n" + chrootInstallCmd); var deferred = Q.defer(); var readline = require('readline'); var rl = readline.createInterface(process.stdin, process.stdout); rl.setPrompt('Do you want install it now? (Yn)> '); rl.prompt(); rl.on('line', function(line) { rl.close(); if (line !== 'Y' && line !== 'y') { deferred.reject(new Error()); return; } Utils.execAsync(chrootInstallCmd).then(function() { deferred.resolve(); }).catch(function (error) { deferred.reject(new Error()); }).done(); }); return deferred.promise; } return Q(); } function additionalDependencies(ubuntuDir) { var config = new ConfigParser(path.join(ubuntuDir, 'config.xml')); var deb = []; config.etree.getroot().findall('./feature/deps/deb').forEach(function (element) { var list = JSON.parse(element.text); deb = deb.concat(list); }); return deb; }