blob: 2860f306bf2fbb4298ecce2fe97d08181965da31 [file] [log] [blame]
#!/usr/bin/env node
/*
*
* Copyright 2014-2016 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',
'libconnectivity-qt1-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' && line.length !== 0) {
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 libconnectivity-qt1-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' && line.length != 0) {
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;
}