Import lots of code from gnome-continuous
This doesn't really do anything yet, but the idea is to start doing Continuous-like things (such as actually booting the result).
This commit is contained in:
parent
220773f213
commit
66a19b6475
@ -22,54 +22,50 @@ substitutions= \
|
||||
-e s,@pkgdatadir\@,$(pkgdatadir), \
|
||||
$(NULL)
|
||||
|
||||
rpm-ostree-autobuilder: src/rpm-ostree-autobuilder.in Makefile
|
||||
rpm-ostree-autobuilder: src/autobuilder/rpm-ostree-autobuilder.in Makefile
|
||||
sed $(substitutions) $< > $@.tmp && mv $@.tmp $@
|
||||
EXTRA_DIST += src/rpm-ostree-autobuilder.in
|
||||
EXTRA_DIST += src/autobuilder/rpm-ostree-autobuilder.in
|
||||
|
||||
bin_SCRIPTS += rpm-ostree-autobuilder \
|
||||
$(NULL)
|
||||
|
||||
jsautobuilderdir=$(pkgdatadir)/js
|
||||
jsautobuilder_DATA= \
|
||||
src/js/argparse.js \
|
||||
src/js/asyncutil.js \
|
||||
src/js/buildutil.js \
|
||||
src/js/builtin.js \
|
||||
src/js/fileutil.js \
|
||||
src/js/task.js \
|
||||
src/js/jsonutil.js \
|
||||
src/js/jsutil.js \
|
||||
src/js/main.js \
|
||||
src/js/libqa.js \
|
||||
src/js/guestfish.js \
|
||||
src/js/params.js \
|
||||
src/js/procutil.js \
|
||||
src/js/snapshot.js \
|
||||
src/js/streamutil.js \
|
||||
src/js/vcs.js \
|
||||
src/js/versioneddir.js \
|
||||
src/autobuilder/js/argparse.js \
|
||||
src/autobuilder/js/asyncutil.js \
|
||||
src/autobuilder/js/buildutil.js \
|
||||
src/autobuilder/js/builtin.js \
|
||||
src/autobuilder/js/fileutil.js \
|
||||
src/autobuilder/js/task.js \
|
||||
src/autobuilder/js/jsonutil.js \
|
||||
src/autobuilder/js/jsutil.js \
|
||||
src/autobuilder/js/main.js \
|
||||
src/autobuilder/js/libqa.js \
|
||||
src/autobuilder/js/guestfish.js \
|
||||
src/autobuilder/js/params.js \
|
||||
src/autobuilder/js/procutil.js \
|
||||
src/autobuilder/js/snapshot.js \
|
||||
src/autobuilder/js/streamutil.js \
|
||||
src/autobuilder/js/vcs.js \
|
||||
src/autobuilder/js/versioneddir.js \
|
||||
$(NULL)
|
||||
|
||||
jsautobuiltinsdir=$(jsautobuilderdir)/builtins
|
||||
jsautobuiltins_DATA= \
|
||||
src/js/builtins/autobuilder.js \
|
||||
src/js/builtins/checkout.js \
|
||||
src/js/builtins/git_mirror.js \
|
||||
src/js/builtins/make.js \
|
||||
src/js/builtins/qa_make_disk.js \
|
||||
src/js/builtins/run_task.js \
|
||||
src/js/builtins/shell.js \
|
||||
src/autobuilder/js/builtins/autobuilder.js \
|
||||
src/autobuilder/js/builtins/git_mirror.js \
|
||||
src/autobuilder/js/builtins/make.js \
|
||||
src/autobuilder/js/builtins/qa_make_disk.js \
|
||||
src/autobuilder/js/builtins/run_task.js \
|
||||
src/autobuilder/js/builtins/shell.js \
|
||||
$(NULL)
|
||||
|
||||
jsautotasksdir=$(jsautobuilderdir)/tasks
|
||||
jsautotasks_DATA= \
|
||||
src/js/tasks/task-build.js \
|
||||
src/js/tasks/task-bdiff.js \
|
||||
src/js/tasks/task-builddisks.js \
|
||||
src/js/tasks/task-smoketest.js \
|
||||
src/js/tasks/task-integrationtest.js \
|
||||
src/js/tasks/task-memusage.js \
|
||||
src/js/tasks/task-applicationstest.js \
|
||||
src/js/tasks/task-zdisks.js \
|
||||
src/js/tasks/testbase.js \
|
||||
src/autobuilder/js/tasks/task-build.js \
|
||||
src/autobuilder/js/tasks/task-bdiff.js \
|
||||
src/autobuilder/js/tasks/task-builddisks.js \
|
||||
src/autobuilder/js/tasks/task-smoketest.js \
|
||||
src/autobuilder/js/tasks/task-zdisks.js \
|
||||
src/autobuilder/js/tasks/testbase.js \
|
||||
$(NULL)
|
||||
|
@ -42,3 +42,5 @@ typelibdir = $(pkglibdir)/girepository-1.0
|
||||
|
||||
gir_DATA = $(INTROSPECTION_GIRS)
|
||||
typelib_DATA = $(gir_DATA:.gir=.typelib)
|
||||
|
||||
include Makefile-autobuilder.am
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 4ea458abb5f0db4c2fd51b000a0a7b9e8f510721
|
||||
Subproject commit 25f755e086282b17491038b08885d726dcf48303
|
181
src/autobuilder/js/argparse.js
Normal file
181
src/autobuilder/js/argparse.js
Normal file
@ -0,0 +1,181 @@
|
||||
// Copyright (C) 2012,2013 Colin Walters <walters@verbum.org>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the
|
||||
// Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
// Boston, MA 02111-1307, USA.
|
||||
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Lang = imports.lang;
|
||||
|
||||
const ArgumentParser = new Lang.Class({
|
||||
Name: 'ArgumentParser',
|
||||
|
||||
_init: function(description) {
|
||||
this.description = description;
|
||||
this._opts = [];
|
||||
this._namedArgs = [];
|
||||
this._optNames = {};
|
||||
this._argNames = {};
|
||||
},
|
||||
|
||||
usage: function() {
|
||||
let buf = 'Usage: ' + this.description + '\n';
|
||||
for (let i = 0; i < this._opts.length; i++) {
|
||||
let opt = this._opts[i];
|
||||
let names = opt._names;
|
||||
for (let j = 0; j < names.length; j++) {
|
||||
let name = names[j];
|
||||
buf += name;
|
||||
if (j < names.length - 1)
|
||||
buf += ", ";
|
||||
}
|
||||
if (opt.description)
|
||||
buf += ' ' + opt.description;
|
||||
buf += '\n';
|
||||
}
|
||||
for (let i = 0; i < this._namedArgs.length; i++) {
|
||||
let arg = this._namedArgs[i];
|
||||
buf += arg._varName + "\n";
|
||||
}
|
||||
return buf;
|
||||
},
|
||||
|
||||
addArgument: function(nameOrNames, opts) {
|
||||
if (!opts)
|
||||
opts = {};
|
||||
let names;
|
||||
if (nameOrNames instanceof Array)
|
||||
names = nameOrNames;
|
||||
else
|
||||
names = [nameOrNames];
|
||||
|
||||
if (opts.action == undefined)
|
||||
opts.action = 'store';
|
||||
if (opts.nargs == undefined)
|
||||
opts.nargs = '1';
|
||||
|
||||
if (names.length == 0) {
|
||||
throw new Error("Must specify at least one argument");
|
||||
} else if (names.length == 1 && names[0][0] != '-') {
|
||||
let name = names[0];
|
||||
this._namedArgs.push(opts);
|
||||
this._argNames[name] = opts;
|
||||
opts._varName = name;
|
||||
} else {
|
||||
opts._names = names;
|
||||
this._opts.push(opts);
|
||||
|
||||
opts._varName = null;
|
||||
|
||||
let shortOpt = null;
|
||||
|
||||
for (let i = 0; i < names.length; i++) {
|
||||
let name = names[i];
|
||||
if (this._optNames[name]) {
|
||||
throw new Error("Argument " + name + " already added");
|
||||
} else if (names.length != 1 && name[0] != '-') {
|
||||
throw new Error("Argument " + name + " does not start with -");
|
||||
}
|
||||
|
||||
this._optNames[name] = opts;
|
||||
if (opts._varName == null) {
|
||||
if (name.indexOf('--') == 0)
|
||||
opts._varName = name.substr(2).replace(/-/g, '_');
|
||||
else if (shortOpt == null && name[0] == '-' && name[1] != '-')
|
||||
shortOpt = name.substr(1);
|
||||
}
|
||||
}
|
||||
if (opts._varName == null)
|
||||
opts._varName = shortOpt;
|
||||
}
|
||||
},
|
||||
|
||||
_failed: function() {
|
||||
print(this.usage());
|
||||
throw new Error("Argument parsing failed");
|
||||
},
|
||||
|
||||
parse: function(argv) {
|
||||
let result = {};
|
||||
let rest = [];
|
||||
|
||||
for (let name in this._optNames) {
|
||||
let opts = this._optNames[name];
|
||||
if (opts.action == 'store') {
|
||||
result[opts._varName] = null;
|
||||
} else if (opts.action == 'storeTrue') {
|
||||
result[opts._varName] = false;
|
||||
} else if (opts.action == 'append') {
|
||||
result[opts._varName] = [];
|
||||
}
|
||||
}
|
||||
for (let name in this._argNames) {
|
||||
let opts = this._argNames[name];
|
||||
if (opts.nargs == '*')
|
||||
result[name] = [];
|
||||
else
|
||||
result[name] = null;
|
||||
}
|
||||
|
||||
let rest = [];
|
||||
|
||||
for (let i = 0; i < argv.length; i++) {
|
||||
let arg = argv[i];
|
||||
if (arg[0] == '-') {
|
||||
let equalsIdx = arg.indexOf('=');
|
||||
let opts;
|
||||
if (equalsIdx != -1)
|
||||
opts = this._optNames[arg.substr(0, equalsIdx)];
|
||||
else
|
||||
opts = this._optNames[arg];
|
||||
|
||||
if (!opts) this._failed();
|
||||
|
||||
if (opts.action == 'store' || opts.action == 'append') {
|
||||
let val;
|
||||
if (equalsIdx == -1) {
|
||||
if (i == argv.length - 1) this._failed();
|
||||
val = argv[i+1];
|
||||
i++;
|
||||
} else {
|
||||
val = arg.substr(equalsIdx+1);
|
||||
}
|
||||
if (opts.action == 'store')
|
||||
result[opts._varName] = val;
|
||||
else
|
||||
result[opts._varName].push(val);
|
||||
} else if (opts.action == 'storeTrue') {
|
||||
result[opts._varName] = true;
|
||||
}
|
||||
} else {
|
||||
rest.push(arg);
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < this._namedArgs.length; i++) {
|
||||
let opts = this._namedArgs[i];
|
||||
let varName = opts._varName;
|
||||
if (opts.nargs == '*') {
|
||||
result[varName].push.apply(result[varName], rest);
|
||||
} else {
|
||||
if (rest.length == 0) this._failed();
|
||||
let value = rest.shift();
|
||||
result[varName] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
});
|
71
src/autobuilder/js/asyncutil.js
Normal file
71
src/autobuilder/js/asyncutil.js
Normal file
@ -0,0 +1,71 @@
|
||||
// Copyright (C) 2012,2013 Colin Walters <walters@verbum.org>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the
|
||||
// Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
// Boston, MA 02111-1307, USA.
|
||||
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Lang = imports.lang;
|
||||
|
||||
const AsyncSet = new Lang.Class({
|
||||
Name: 'AsyncSet',
|
||||
|
||||
_init: function(callback, cancellable) {
|
||||
this._callback = callback;
|
||||
this._cancellable = cancellable;
|
||||
this._results = {};
|
||||
this._err = null;
|
||||
this._children = [];
|
||||
},
|
||||
|
||||
addGAsyncResult: function(name, callback) {
|
||||
this._children.push(callback);
|
||||
let wrapped = Lang.bind(this, function(object, result) {
|
||||
let success = false;
|
||||
try {
|
||||
this._results[name] = callback(object, result);
|
||||
success = true;
|
||||
} catch (e) {
|
||||
if (this._cancellable)
|
||||
this._cancellable.cancel();
|
||||
if (!this._err) {
|
||||
this._err = e.toString();
|
||||
this._checkCallback();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let i;
|
||||
for (i = 0; i < this._children.length; i++) {
|
||||
let child = this._children[i];
|
||||
if (child === callback) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == this._children.length)
|
||||
throw new Error("Failed to find child task");
|
||||
this._children.splice(i, 1);
|
||||
this._checkCallback();
|
||||
});
|
||||
return wrapped;
|
||||
},
|
||||
|
||||
_checkCallback: function() {
|
||||
if (this._err)
|
||||
this._callback(null, this._err);
|
||||
else if (this._children.length == 0)
|
||||
this._callback(this._results, null);
|
||||
}
|
||||
});
|
137
src/autobuilder/js/buildutil.js
Normal file
137
src/autobuilder/js/buildutil.js
Normal file
@ -0,0 +1,137 @@
|
||||
// Copyright (C) 2011 Colin Walters <walters@verbum.org>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the
|
||||
// Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
// Boston, MA 02111-1307, USA.
|
||||
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Lang = imports.lang;
|
||||
|
||||
const GSystem = imports.gi.GSystem;
|
||||
|
||||
const BUILD_ENV = {
|
||||
'HOME' : '/',
|
||||
'HOSTNAME' : 'ostbuild',
|
||||
'LANG': 'C',
|
||||
'PATH' : '/usr/bin:/bin:/usr/sbin:/sbin',
|
||||
'SHELL' : '/bin/bash',
|
||||
'TERM' : 'vt100',
|
||||
'TMPDIR' : '/tmp',
|
||||
'TZ': 'EST5EDT'
|
||||
};
|
||||
|
||||
function getPatchPathsForComponent(patchdir, component) {
|
||||
let patches = component['patches'];
|
||||
if (!patches)
|
||||
return [];
|
||||
let patchSubdir = patches['subdir'];
|
||||
let subPatchdir;
|
||||
if (patchSubdir) {
|
||||
subPatchdir = patchdir.get_child(patchSubdir);
|
||||
} else {
|
||||
subPatchdir = patchdir;
|
||||
}
|
||||
let result = [];
|
||||
let files = patches['files'];
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
result.push(subPatchdir.get_child(files[i]));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function findUserChrootPath() {
|
||||
// We need to search PATH here manually so we correctly pick up an
|
||||
// ostree install in e.g. ~/bin even though we're going to set PATH
|
||||
// below for our children inside the chroot.
|
||||
let userChrootPath = null;
|
||||
let elts = GLib.getenv('PATH').split(':');
|
||||
for (let i = 0; i < elts.length; i++) {
|
||||
let dir = Gio.File.new_for_path(elts[i]);
|
||||
let child = dir.get_child('linux-user-chroot');
|
||||
if (child.query_exists(null)) {
|
||||
userChrootPath = child;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return userChrootPath;
|
||||
}
|
||||
|
||||
function getBaseUserChrootArgs() {
|
||||
let path = findUserChrootPath();
|
||||
return [path.get_path(), '--unshare-pid', '--unshare-ipc', '--unshare-net'];
|
||||
}
|
||||
|
||||
function compareVersions(a, b) {
|
||||
let adot = a.indexOf('.');
|
||||
while (adot != -1) {
|
||||
let bdot = b.indexOf('.');
|
||||
if (bdot == -1)
|
||||
return 1;
|
||||
let aSub = parseInt(a.substr(0, adot));
|
||||
let bSub = parseInt(b.substr(0, bdot));
|
||||
if (aSub > bSub)
|
||||
return 1;
|
||||
else if (aSub < bSub)
|
||||
return -1;
|
||||
a = a.substr(adot + 1);
|
||||
b = b.substr(bdot + 1);
|
||||
adot = a.indexOf('.');
|
||||
}
|
||||
if (b.indexOf('.') != -1)
|
||||
return -1;
|
||||
let aSub = parseInt(a);
|
||||
let bSub = parseInt(b);
|
||||
if (aSub > bSub)
|
||||
return 1;
|
||||
else if (aSub < bSub)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
function atomicSymlinkSwap(linkPath, newTarget, cancellable) {
|
||||
let parent = linkPath.get_parent();
|
||||
let tmpLinkPath = parent.get_child('current-new.tmp');
|
||||
GSystem.shutil_rm_rf(tmpLinkPath, cancellable);
|
||||
let relpath = GSystem.file_get_relpath(parent, newTarget);
|
||||
tmpLinkPath.make_symbolic_link(relpath, cancellable);
|
||||
GSystem.file_rename(tmpLinkPath, linkPath, cancellable);
|
||||
}
|
||||
|
||||
function checkIsWorkDirectory(dir) {
|
||||
let manifest = dir.get_child('manifest.json');
|
||||
if (!manifest.query_exists(null)) {
|
||||
throw new Error("No manifest.json found in " + dir.get_path());
|
||||
}
|
||||
let dotGit = dir.get_child('.git');
|
||||
if (dotGit.query_exists(null)) {
|
||||
throw new Error(".git found in " + dir.get_path() + "; are you in a gnome-ostree checkout?");
|
||||
}
|
||||
}
|
||||
|
||||
function formatElapsedTime(microseconds) {
|
||||
let millis = microseconds / 1000;
|
||||
if (millis > 1000) {
|
||||
let seconds = millis / 1000;
|
||||
return Format.vprintf("%.1f s", [seconds]);
|
||||
}
|
||||
return Format.vprintf("%.1f ms", [millis]);
|
||||
}
|
||||
|
||||
function timeSubtask(name, cb) {
|
||||
let startTime = GLib.get_monotonic_time();
|
||||
cb();
|
||||
let endTime = GLib.get_monotonic_time();
|
||||
print("Subtask " + name + " complete in " + formatElapsedTime(endTime - startTime));
|
||||
}
|
67
src/autobuilder/js/builtin.js
Normal file
67
src/autobuilder/js/builtin.js
Normal file
@ -0,0 +1,67 @@
|
||||
// Copyright (C) 2012,2013 Colin Walters <walters@verbum.org>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the
|
||||
// Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
// Boston, MA 02111-1307, USA.
|
||||
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Lang = imports.lang;
|
||||
|
||||
const GSystem = imports.gi.GSystem;
|
||||
|
||||
const Params = imports.params;
|
||||
const JsonUtil = imports.jsonutil;
|
||||
const ArgParse = imports.argparse;
|
||||
const Snapshot = imports.snapshot;
|
||||
const BuildUtil = imports.buildutil;
|
||||
|
||||
const Builtin = new Lang.Class({
|
||||
Name: 'Builtin',
|
||||
|
||||
DESCRIPTION: null,
|
||||
|
||||
_init: function() {
|
||||
this.parser = new ArgParse.ArgumentParser(this.DESCRIPTION);
|
||||
this._workdirInitialized = false;
|
||||
},
|
||||
|
||||
_initWorkdir: function(workdir, cancellable) {
|
||||
if (this._workdirInitialized)
|
||||
return;
|
||||
this._workdirInitialized = true;
|
||||
if (workdir === null)
|
||||
workdir = Gio.File.new_for_path('.');
|
||||
else if (typeof(workdir) == 'string')
|
||||
workdir = Gio.File.new_for_path(workdir);
|
||||
|
||||
BuildUtil.checkIsWorkDirectory(workdir);
|
||||
|
||||
this.workdir = workdir;
|
||||
this.mirrordir = workdir.get_child('src');
|
||||
GSystem.file_ensure_directory(this.mirrordir, true, cancellable);
|
||||
this.patchdir = this.workdir.get_child('patches');
|
||||
},
|
||||
|
||||
_initSnapshot: function(workdir, snapshotPath, cancellable) {
|
||||
this._initWorkdir(workdir, cancellable);
|
||||
let path = Gio.File.new_for_path(snapshotPath);
|
||||
this._snapshot = Snapshot.fromFile(path, cancellable);
|
||||
},
|
||||
|
||||
main: function(argv, loop, cancellable) {
|
||||
let args = this.parser.parse(argv);
|
||||
this.execute(args, loop, cancellable);
|
||||
}
|
||||
});
|
209
src/autobuilder/js/builtins/autobuilder.js
Normal file
209
src/autobuilder/js/builtins/autobuilder.js
Normal file
@ -0,0 +1,209 @@
|
||||
// Copyright (C) 2012,2013 Colin Walters <walters@verbum.org>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the
|
||||
// Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
// Boston, MA 02111-1307, USA.
|
||||
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Lang = imports.lang;
|
||||
|
||||
const GSystem = imports.gi.GSystem;
|
||||
|
||||
const Builtin = imports.builtin;
|
||||
const Task = imports.task;
|
||||
const JsonUtil = imports.jsonutil;
|
||||
const BuildUtil = imports.buildutil;
|
||||
const ProcUtil = imports.procutil;
|
||||
const VersionedDir = imports.versioneddir;
|
||||
|
||||
var AutoBuilderIface = '<node> \
|
||||
<interface name="org.gnome.OSTreeBuild.AutoBuilder"> \
|
||||
<method name="queueResolve"> \
|
||||
<arg type="as" direction="in" /> \
|
||||
</method> \
|
||||
<property name="Status" type="s" access="read" /> \
|
||||
</interface> \
|
||||
</node>';
|
||||
|
||||
const Autobuilder = new Lang.Class({
|
||||
Name: 'Autobuilder',
|
||||
Extends: Builtin.Builtin,
|
||||
|
||||
DESCRIPTION: "Automatically fetch git repositories and build",
|
||||
|
||||
_init: function() {
|
||||
this.parent();
|
||||
|
||||
this.parser.addArgument('--autoupdate-self', { action: 'store' });
|
||||
|
||||
this._buildNeeded = true;
|
||||
this._initialResolveNeeded = true;
|
||||
this._fullResolveNeeded = true;
|
||||
this._resolveTimeout = 0;
|
||||
this._resolveSrcUrls = [];
|
||||
},
|
||||
|
||||
execute: function(args, loop, cancellable) {
|
||||
this._initWorkdir(null, cancellable);
|
||||
|
||||
this._buildsDir = new VersionedDir.VersionedDir(this.workdir.get_child('builds'));
|
||||
|
||||
this._resultsDir = this.workdir.get_child('results');
|
||||
GSystem.file_ensure_directory(this._resultsDir, true, cancellable);
|
||||
|
||||
if (args.autoupdate_self)
|
||||
this._autoupdate_self = Gio.File.new_for_path(args.autoupdate_self);
|
||||
|
||||
this._ownId = Gio.DBus.session.own_name('org.gnome.OSTreeBuild', Gio.BusNameOwnerFlags.NONE,
|
||||
function(name) {},
|
||||
function(name) { loop.quit(); });
|
||||
|
||||
this._impl = Gio.DBusExportedObject.wrapJSObject(AutoBuilderIface, this);
|
||||
this._impl.export(Gio.DBus.session, '/org/gnome/OSTreeBuild/AutoBuilder');
|
||||
|
||||
this._taskmaster = new Task.TaskMaster(this.workdir);
|
||||
this._taskmaster.connect('task-executing', Lang.bind(this, this._onTaskExecuting));
|
||||
this._taskmaster.connect('task-complete', Lang.bind(this, this._onTaskCompleted));
|
||||
|
||||
/* Start an initial, non-fetching resolve */
|
||||
this._runResolve();
|
||||
/* Flag immediately that we need a full resolve */
|
||||
this._fullResolveNeeded = true;
|
||||
/* And set a timeout for 10 minutes for the next full resolve */
|
||||
this._resolveTimeout = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT,
|
||||
60 * 10, Lang.bind(this, this._triggerFullResolve));
|
||||
|
||||
this._updateStatus();
|
||||
|
||||
loop.run();
|
||||
},
|
||||
|
||||
_onTaskExecuting: function(taskmaster, task) {
|
||||
print("Task " + task.name + " executing on " + task.buildName);
|
||||
this._updateStatus();
|
||||
},
|
||||
|
||||
_onTaskCompleted: function(taskmaster, task, success, error) {
|
||||
let cancellable = null;
|
||||
|
||||
if (task.name == 'resolve') {
|
||||
if (!task.changed) {
|
||||
print("Resolve is unchanged");
|
||||
this._buildsDir.deleteCurrentVersion(null);
|
||||
}
|
||||
this._runResolve();
|
||||
}
|
||||
|
||||
let resultsPath;
|
||||
|
||||
if (success) {
|
||||
print("Task " + task.name + " complete: " + task.buildName);
|
||||
resultsPath = this._resultsDir.get_child('successful');
|
||||
} else {
|
||||
print("Task " + task.name + " failed: " + task.buildName);
|
||||
resultsPath = this._resultsDir.get_child('failed');
|
||||
}
|
||||
|
||||
BuildUtil.atomicSymlinkSwap(resultsPath, task.buildPath, null);
|
||||
|
||||
this._updateStatus();
|
||||
},
|
||||
|
||||
_updateStatus: function() {
|
||||
let newStatus = "";
|
||||
let taskstateList = this._taskmaster.getTaskState();
|
||||
let runningTasks = [];
|
||||
let queuedTasks = [];
|
||||
for (let i = 0; i < taskstateList.length; i++) {
|
||||
let taskstate = taskstateList[i];
|
||||
let name = taskstate.task.name;
|
||||
if (taskstate.running)
|
||||
runningTasks.push(name);
|
||||
else
|
||||
queuedTasks.push(name);
|
||||
}
|
||||
if (runningTasks.length == 0 && queuedTasks.length == 0) {
|
||||
newStatus = "[idle]";
|
||||
} else {
|
||||
newStatus = "running: " + JSON.stringify(runningTasks);
|
||||
if (queuedTasks.length)
|
||||
newStatus += " queued: " + JSON.stringify(queuedTasks);
|
||||
}
|
||||
if (newStatus != this._status) {
|
||||
this._status = newStatus;
|
||||
print(this._status);
|
||||
let [success,loadAvg,etag] = Gio.File.new_for_path('/proc/loadavg').load_contents(null);
|
||||
loadAvg = loadAvg.toString().replace(/\n$/, '').split(' ');
|
||||
let statusPath = Gio.File.new_for_path('autobuilder-status.json');
|
||||
JsonUtil.writeJsonFileAtomic(statusPath, {'running': runningTasks,
|
||||
'queued': queuedTasks,
|
||||
'systemLoad': loadAvg}, null);
|
||||
this._impl.emit_property_changed('Status', new GLib.Variant("s", this._status));
|
||||
}
|
||||
},
|
||||
|
||||
get Status() {
|
||||
return this._status;
|
||||
},
|
||||
|
||||
queueResolve: function(srcUrls) {
|
||||
print("Queuing force resolve for " + JSON.stringify(srcUrls));
|
||||
this._resolveSrcUrls.push.apply(this._resolveSrcUrls, srcUrls);
|
||||
this._runResolve();
|
||||
},
|
||||
|
||||
_triggerFullResolve: function() {
|
||||
this._fullResolveNeeded = true;
|
||||
this._runResolve();
|
||||
return true;
|
||||
},
|
||||
|
||||
_runResolve: function() {
|
||||
let cancellable = null;
|
||||
|
||||
if (!(this._initialResolveNeeded ||
|
||||
this._resolveSrcUrls.length > 0 ||
|
||||
this._fullResolveNeeded))
|
||||
return;
|
||||
|
||||
if (this._taskmaster.isTaskQueued('resolve'))
|
||||
return;
|
||||
|
||||
if (this._autoupdate_self)
|
||||
ProcUtil.runSync(['git', 'pull', '-r'], cancellable,
|
||||
{ cwd: this._autoupdate_self })
|
||||
|
||||
let previousVersion = this._buildsDir.currentVersion(cancellable);
|
||||
let buildPath = this._buildsDir.allocateNewVersion(cancellable);
|
||||
let version = this._buildsDir.pathToVersion(buildPath);
|
||||
if (previousVersion) {
|
||||
let lastBuildPath = this._buildsDir.createPathForVersion(previousVersion);
|
||||
BuildUtil.atomicSymlinkSwap(buildPath.get_child('last-build'), lastBuildPath, null);
|
||||
}
|
||||
|
||||
if (this._initialResolveNeeded) {
|
||||
this._initialResolveNeeded = false;
|
||||
this._taskmaster.pushTask(buildPath, 'resolve', { });
|
||||
} else if (this._fullResolveNeeded) {
|
||||
this._fullResolveNeeded = false;
|
||||
this._taskmaster.pushTask(buildPath, 'resolve', { fetchAll: true });
|
||||
} else {
|
||||
this._taskmaster.pushTask(buildPath, 'resolve', { fetchSrcUrls: this._resolveSrcUrls });
|
||||
}
|
||||
this._resolveSrcUrls = [];
|
||||
|
||||
this._updateStatus();
|
||||
}
|
||||
});
|
78
src/autobuilder/js/builtins/git_mirror.js
Normal file
78
src/autobuilder/js/builtins/git_mirror.js
Normal file
@ -0,0 +1,78 @@
|
||||
// Copyright (C) 2011,2012 Colin Walters <walters@verbum.org>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the
|
||||
// Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
// Boston, MA 02111-1307, USA.
|
||||
|
||||
const Gio = imports.gi.Gio;
|
||||
const Lang = imports.lang;
|
||||
|
||||
const Builtin = imports.builtin;
|
||||
const Snapshot = imports.snapshot;
|
||||
const Vcs = imports.vcs;
|
||||
const JsonUtil = imports.jsonutil;
|
||||
|
||||
const GitMirror = new Lang.Class({
|
||||
Name: 'GitMirror',
|
||||
Extends: Builtin.Builtin,
|
||||
|
||||
DESCRIPTION: "Update internal git mirror for one or more components",
|
||||
|
||||
_init: function() {
|
||||
this.parent();
|
||||
this.parser.addArgument('--workdir');
|
||||
this.parser.addArgument('--manifest');
|
||||
this.parser.addArgument('--snapshot');
|
||||
this.parser.addArgument('--timeout-sec', { help: "Cache fetch results for provided number of seconds" });
|
||||
this.parser.addArgument('--fetch', {action:'storeTrue',
|
||||
help:"Also do a git fetch for components"});
|
||||
this.parser.addArgument(['-k', '--keep-going'], {action:'storeTrue',
|
||||
help: "Don't exit on fetch failures"});
|
||||
this.parser.addArgument('components', {nargs:'*'});
|
||||
},
|
||||
|
||||
execute: function(args, loop, cancellable) {
|
||||
this._initWorkdir(args.workdir, cancellable);
|
||||
|
||||
if (!args.timeout_sec)
|
||||
args.timeout_sec = 0;
|
||||
|
||||
if (args.manifest != null) {
|
||||
let manifestPath = Gio.File.new_for_path(args.manifest)
|
||||
this._snapshot = Snapshot.fromFile(manifestPath, cancellable, { prepareResolve: true });
|
||||
} else {
|
||||
this._initSnapshot(null, args.snapshot, cancellable);
|
||||
}
|
||||
|
||||
let componentNames;
|
||||
if (args.components.length == 0) {
|
||||
componentNames = this._snapshot.getAllComponentNames();
|
||||
} else {
|
||||
componentNames = args.components;
|
||||
}
|
||||
|
||||
componentNames.forEach(Lang.bind(this, function (name) {
|
||||
let component = this._snapshot.getComponent(name);
|
||||
|
||||
if (!args.fetch) {
|
||||
Vcs.ensureVcsMirror(this.mirrordir, component, cancellable);
|
||||
} else {
|
||||
print("Running git fetch for " + name);
|
||||
Vcs.fetch(this.mirrordir, component, cancellable,
|
||||
{ keepGoing:args.keep_going,
|
||||
timeoutSec: args.timeout_sec });
|
||||
}
|
||||
}));
|
||||
}
|
||||
});
|
103
src/autobuilder/js/builtins/make.js
Normal file
103
src/autobuilder/js/builtins/make.js
Normal file
@ -0,0 +1,103 @@
|
||||
// Copyright (C) 2012,2013 Colin Walters <walters@verbum.org>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the
|
||||
// Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
// Boston, MA 02111-1307, USA.
|
||||
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Lang = imports.lang;
|
||||
|
||||
const GSystem = imports.gi.GSystem;
|
||||
|
||||
const Builtin = imports.builtin;
|
||||
const Task = imports.task;
|
||||
|
||||
const Make = new Lang.Class({
|
||||
Name: 'Make',
|
||||
Extends: Builtin.Builtin,
|
||||
|
||||
DESCRIPTION: "Execute tasks",
|
||||
|
||||
_init: function() {
|
||||
this.parent();
|
||||
this.parser.addArgument(['-n', '--only'], { action: 'storeTrue',
|
||||
help: "Don't process tasks after this" });
|
||||
this.parser.addArgument(['-x', '--skip'], { action: 'append',
|
||||
help: "Don't process tasks after this" });
|
||||
this.parser.addArgument('taskname');
|
||||
this.parser.addArgument('parameters', { nargs: '*' });
|
||||
},
|
||||
|
||||
execute: function(args, loop, cancellable) {
|
||||
this._initWorkdir(null, cancellable);
|
||||
this._loop = loop;
|
||||
this._failed = false;
|
||||
this._cancellable = cancellable;
|
||||
this._oneOnly = args.only;
|
||||
let taskmaster = new Task.TaskMaster(this.workdir,
|
||||
{ onEmpty: Lang.bind(this, this._onTasksComplete),
|
||||
processAfter: !args.only,
|
||||
skip: args.skip });
|
||||
this._taskmaster = taskmaster;
|
||||
taskmaster.connect('task-executing', Lang.bind(this, this._onTaskExecuting));
|
||||
taskmaster.connect('task-complete', Lang.bind(this, this._onTaskCompleted));
|
||||
let params = this._parseParameters(args.parameters);
|
||||
let buildPath = Gio.File.new_for_path('local');
|
||||
GSystem.file_ensure_directory(buildPath, false, cancellable);
|
||||
taskmaster.pushTask(buildPath, args.taskname, params);
|
||||
loop.run();
|
||||
if (!this._failed)
|
||||
print("Success!")
|
||||
},
|
||||
|
||||
_parseParameters: function(paramStrings) {
|
||||
let params = {};
|
||||
for (let i = 0; i < paramStrings.length; i++) {
|
||||
let param = paramStrings[i];
|
||||
let idx = param.indexOf('=');
|
||||
if (idx == -1)
|
||||
throw new Error("Invalid key=value syntax");
|
||||
let k = param.substr(0, idx);
|
||||
let v = JSON.parse(param.substr(idx+1));
|
||||
params[k] = v;
|
||||
}
|
||||
return params;
|
||||
},
|
||||
|
||||
_onTaskExecuting: function(taskmaster, task) {
|
||||
print("Task " + task.name + " executing on " + task.buildName);
|
||||
let output = task.taskCwd.get_child('output.txt');
|
||||
if (this._oneOnly) {
|
||||
let context = new GSystem.SubprocessContext({ argv: ['tail', '-f', output.get_path() ] });
|
||||
this._tail = new GSystem.Subprocess({ context: context });
|
||||
this._tail.init(null);
|
||||
}
|
||||
},
|
||||
|
||||
_onTaskCompleted: function(taskmaster, task, success, error) {
|
||||
if (this._oneOnly)
|
||||
this._tail.request_exit();
|
||||
if (success) {
|
||||
print("Task " + task.name + " complete: " + task.buildName);
|
||||
} else {
|
||||
this._failed = true;
|
||||
print("Task " + task.name + " failed: " + task.buildName);
|
||||
}
|
||||
},
|
||||
|
||||
_onTasksComplete: function() {
|
||||
this._loop.quit();
|
||||
}
|
||||
});
|
53
src/autobuilder/js/builtins/qa_make_disk.js
Normal file
53
src/autobuilder/js/builtins/qa_make_disk.js
Normal file
@ -0,0 +1,53 @@
|
||||
// -*- indent-tabs-mode: nil; tab-width: 2; -*-
|
||||
// Copyright (C) 2013 Colin Walters <walters@verbum.org>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the
|
||||
// Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
// Boston, MA 02111-1307, USA.
|
||||
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Lang = imports.lang;
|
||||
const Format = imports.format;
|
||||
|
||||
const GSystem = imports.gi.GSystem;
|
||||
|
||||
const Builtin = imports.builtin;
|
||||
const ArgParse = imports.argparse;
|
||||
const ProcUtil = imports.procutil;
|
||||
const GuestFish = imports.guestfish;
|
||||
|
||||
const QaMakeDisk = new Lang.Class({
|
||||
Name: 'QaMakeDisk',
|
||||
Extends: Builtin.Builtin,
|
||||
|
||||
DESCRIPTION: "Generate a disk image",
|
||||
|
||||
_init: function() {
|
||||
this.parent();
|
||||
this.parser.addArgument('diskpath');
|
||||
},
|
||||
|
||||
execute: function(args, loop, cancellable) {
|
||||
let path = Gio.File.new_for_path(args.diskpath);
|
||||
if (path.query_exists(null))
|
||||
throw new Error("" + path.get_path() + " exists");
|
||||
|
||||
let tmppath = path.get_parent().get_child(path.get_basename() + '.tmp');
|
||||
GSystem.shutil_rm_rf(tmppath, cancellable);
|
||||
LibQA.createDisk(tmppath, cancellable);
|
||||
GSystem.file_rename(tmppath, path, cancellable);
|
||||
print("Created: " + path.get_path());
|
||||
}
|
||||
});
|
42
src/autobuilder/js/builtins/run_task.js
Normal file
42
src/autobuilder/js/builtins/run_task.js
Normal file
@ -0,0 +1,42 @@
|
||||
// Copyright (C) 2012,2013 Colin Walters <walters@verbum.org>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the
|
||||
// Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
// Boston, MA 02111-1307, USA.
|
||||
|
||||
const Lang = imports.lang;
|
||||
|
||||
const Builtin = imports.builtin;
|
||||
const Task = imports.task;
|
||||
|
||||
const RunTask = new Lang.Class({
|
||||
Name: 'RunTask',
|
||||
Extends: Builtin.Builtin,
|
||||
|
||||
DESCRIPTION: "Internal helper to execute a task",
|
||||
|
||||
_init: function() {
|
||||
this.parent();
|
||||
this.parser.addArgument('taskName');
|
||||
this.parser.addArgument('parameters');
|
||||
},
|
||||
|
||||
execute: function(args, loop, cancellable) {
|
||||
let taskset = Task.TaskSet.prototype.getInstance();
|
||||
let taskDef = taskset.getTask(args.taskName);
|
||||
let params = JSON.parse(args.parameters);
|
||||
let instance = new taskDef(params);
|
||||
instance.execute(cancellable);
|
||||
}
|
||||
});
|
31
src/autobuilder/js/builtins/shell.js
Normal file
31
src/autobuilder/js/builtins/shell.js
Normal file
@ -0,0 +1,31 @@
|
||||
// Copyright (C) 2012,2013 Colin Walters <walters@verbum.org>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the
|
||||
// Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
// Boston, MA 02111-1307, USA.
|
||||
|
||||
const Lang = imports.lang;
|
||||
|
||||
const Builtin = imports.builtin;
|
||||
|
||||
const Shell = new Lang.Class({
|
||||
Name: 'Shell',
|
||||
Extends: Builtin.Builtin,
|
||||
|
||||
DESCRIPTION: "Interactive shell",
|
||||
|
||||
execute: function(args, loop, cancellable) {
|
||||
imports.console.interact();
|
||||
}
|
||||
});
|
89
src/autobuilder/js/fileutil.js
Normal file
89
src/autobuilder/js/fileutil.js
Normal file
@ -0,0 +1,89 @@
|
||||
// Copyright (C) 2012,2013 Colin Walters <walters@verbum.org>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the
|
||||
// Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
// Boston, MA 02111-1307, USA.
|
||||
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gio = imports.gi.Gio;
|
||||
|
||||
const Params = imports.params;
|
||||
|
||||
function walkDirInternal(dir, matchParams, callback, cancellable, queryStr, depth, sortByName) {
|
||||
let denum = dir.enumerate_children(queryStr, Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS,
|
||||
cancellable);
|
||||
let info;
|
||||
let subdirs = [];
|
||||
|
||||
if (depth > 0) {
|
||||
depth -= 1;
|
||||
}
|
||||
|
||||
let sortedFiles = [];
|
||||
while ((info = denum.next_file(cancellable)) != null) {
|
||||
let name = info.get_name();
|
||||
let child = dir.get_child(name);
|
||||
let ftype = info.get_file_type();
|
||||
|
||||
if (depth != 0) {
|
||||
if (ftype == Gio.FileType.DIRECTORY) {
|
||||
subdirs.push(child);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (matchParams.nameRegex && matchParams.nameRegex.exec(name) === null)
|
||||
continue;
|
||||
if (matchParams.fileType !== null && matchParams.fileType != info.get_file_type())
|
||||
continue;
|
||||
if (matchParams.contentType != null && matchParams.contentType != info.get_content_type())
|
||||
continue;
|
||||
if (!sortByName)
|
||||
callback(child, cancellable);
|
||||
else
|
||||
sortedFiles.push(child);
|
||||
}
|
||||
if (sortByName) {
|
||||
sortedFiles.sort(function (a, b) {
|
||||
return a.get_basename().localeCompare(b.get_basename());
|
||||
});
|
||||
for (let i = 0; i < sortedFiles.length; i++) {
|
||||
callback(sortedFiles[i], cancellable);
|
||||
}
|
||||
}
|
||||
|
||||
denum.close(cancellable);
|
||||
|
||||
if (sortByName) {
|
||||
subdirs.sort(function (a, b) {
|
||||
return a.get_basename().localeCompare(b.get_basename());
|
||||
});
|
||||
}
|
||||
for (let i = 0; i < subdirs.length; i++) {
|
||||
walkDirInternal(subdirs[i], matchParams, callback, cancellable, queryStr, depth);
|
||||
}
|
||||
}
|
||||
|
||||
function walkDir(dir, matchParams, callback, cancellable) {
|
||||
matchParams = Params.parse(matchParams, { nameRegex: null,
|
||||
fileType: null,
|
||||
contentType: null,
|
||||
depth: -1,
|
||||
sortByName: false });
|
||||
let queryStr = 'standard::name,standard::type,unix::mode';
|
||||
if (matchParams.contentType)
|
||||
queryStr += ',standard::fast-content-type';
|
||||
let depth = matchParams.depth;
|
||||
walkDirInternal(dir, matchParams, callback, cancellable, queryStr, depth, matchParams.sortByName);
|
||||
}
|
180
src/autobuilder/js/guestfish.js
Normal file
180
src/autobuilder/js/guestfish.js
Normal file
@ -0,0 +1,180 @@
|
||||
// Copyright (C) 2012,2013 Colin Walters <walters@verbum.org>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the
|
||||
// Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
// Boston, MA 02111-1307, USA.
|
||||
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Lang = imports.lang;
|
||||
|
||||
const GSystem = imports.gi.GSystem;
|
||||
const Params = imports.params;
|
||||
const ProcUtil = imports.procutil;
|
||||
|
||||
const LibGuestfs = new Lang.Class({
|
||||
Name: 'LibGuestfs',
|
||||
|
||||
_init: function(diskpath, params) {
|
||||
this._params = Params.parse(params, {useLockFile: true,
|
||||
partitionOpts: ['-i'],
|
||||
readWrite: false});
|
||||
this._diskpath = diskpath;
|
||||
this._readWrite = params.readWrite
|
||||
this._partitionOpts = params.partitionOpts;
|
||||
if (params.useLockFile) {
|
||||
let lockfilePath = diskpath.get_parent().get_child(diskpath.get_basename() + '.guestfish-lock');
|
||||
this._lockfilePath = lockfilePath;
|
||||
} else {
|
||||
this._lockfilePath = null;
|
||||
}
|
||||
},
|
||||
|
||||
_lock: function() {
|
||||
if (this._lockfilePath) {
|
||||
let stream = this._lockfilePath.create(Gio.FileCreateFlags.NONE, cancellable);
|
||||
stream.close(cancellable);
|
||||
}
|
||||
},
|
||||
|
||||
_unlock: function() {
|
||||
if (this._lockfilePath != null) {
|
||||
GSystem.file_unlink(this._lockfilePath, cancellable);
|
||||
}
|
||||
},
|
||||
|
||||
_appendOpts: function(argv) {
|
||||
argv.push.apply(argv, ['-a', this._diskpath.get_path()]);
|
||||
if (this._readWrite)
|
||||
argv.push('--rw');
|
||||
else
|
||||
argv.push('--ro');
|
||||
argv.push.apply(argv, this._partitionOpts);
|
||||
}
|
||||
});
|
||||
|
||||
const GuestFish = new Lang.Class({
|
||||
Name: 'GuestFish',
|
||||
Extends: LibGuestfs,
|
||||
|
||||
run: function(input, cancellable) {
|
||||
this._lock();
|
||||
try {
|
||||
let guestfishArgv = ['guestfish'];
|
||||
this._appendOpts(guestfishArgv);
|
||||
return ProcUtil.runProcWithInputSyncGetLines(guestfishArgv, cancellable, input);
|
||||
} finally {
|
||||
this._unlock();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const GuestMount = new Lang.Class({
|
||||
Name: 'GuestMount',
|
||||
Extends: LibGuestfs,
|
||||
|
||||
mount: function(mntdir, cancellable) {
|
||||
this._lock();
|
||||
try {
|
||||
this._mntdir = mntdir;
|
||||
this._mountPidFile = mntdir.get_parent().get_child(mntdir.get_basename() + '.guestmount-pid');
|
||||
|
||||
if (this._mountPidFile.query_exists(null))
|
||||
throw new Error("guestfish pid file exists: " + this._mountPidFile.get_path());
|
||||
|
||||
let guestmountArgv = ['guestmount', '-o', 'allow_root',
|
||||
'--pid-file', this._mountPidFile.get_path()];
|
||||
this._appendOpts(guestmountArgv);
|
||||
guestmountArgv.push(mntdir.get_path());
|
||||
print('Mounting ' + mntdir.get_path() + ' : ' + guestmountArgv.join(' '));
|
||||
let context = new GSystem.SubprocessContext({ argv: guestmountArgv });
|
||||
let proc = new GSystem.Subprocess({ context: context });
|
||||
proc.init(cancellable);
|
||||
|
||||
// guestfish daemonizes, so this process will exit, and
|
||||
// when it has, we know the mount is ready. If there was
|
||||
// a way to get notified instead of this indirect fashion,
|
||||
// we'd do that.
|
||||
proc.wait_sync_check(cancellable);
|
||||
|
||||
this._mounted = true;
|
||||
} catch (e) {
|
||||
this._unlock();
|
||||
}
|
||||
},
|
||||
|
||||
umount: function(cancellable) {
|
||||
if (!this._mounted)
|
||||
return;
|
||||
|
||||
let pidStr = GSystem.file_load_contents_utf8(this._mountPidFile, cancellable);
|
||||
if (pidStr.length == 0) {
|
||||
this._mounted = false;
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < 30; i++) {
|
||||
// See "man guestmount" for why retry loops here might be needed if this
|
||||
// script is running on a client machine with programs that watch for new mounts
|
||||
try {
|
||||
ProcUtil.runSync(['fusermount', '-u', this._mntdir.get_path()], cancellable,
|
||||
{logInitiation: true});
|
||||
break;
|
||||
} catch (e) {
|
||||
if (!(e.origError && e.origError.domain == GLib.spawn_exit_error_quark()))
|
||||
throw e;
|
||||
else {
|
||||
let proc = GSystem.Subprocess.new_simple_argv(['fuser', '-m', this._mntdir.get_path()],
|
||||
GSystem.SubprocessStreamDisposition.INHERIT,
|
||||
GSystem.SubprocessStreamDisposition.INHERIT,
|
||||
cancellable);
|
||||
proc.init(cancellable);
|
||||
proc.wait_sync(cancellable);
|
||||
let creds = new Gio.Credentials();
|
||||
proc = GSystem.Subprocess.new_simple_argv(['ls', '-al', '/proc/' + creds.get_unix_pid() + '/fd'],
|
||||
GSystem.SubprocessStreamDisposition.INHERIT,
|
||||
GSystem.SubprocessStreamDisposition.INHERIT,
|
||||
cancellable);
|
||||
proc.init(cancellable);
|
||||
proc.wait_sync(cancellable);
|
||||
|
||||
GLib.usleep(GLib.USEC_PER_SEC);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let pid = parseInt(pidStr);
|
||||
let guestfishExited = false;
|
||||
let guestfishTimeoutSecs = 5 * 60;
|
||||
for (let i = 0; i < guestfishTimeoutSecs; i++) {
|
||||
let killContext = new GSystem.SubprocessContext({argv: ['kill', '-0', ''+pid]});
|
||||
killContext.set_stderr_disposition(GSystem.SubprocessStreamDisposition.NULL);
|
||||
let killProc = new GSystem.Subprocess({context: killContext});
|
||||
killProc.init(null);
|
||||
let [waitSuccess, ecode] = killProc.wait_sync(null);
|
||||
let [killSuccess, statusStr] = ProcUtil.getExitStatusAndString(ecode);
|
||||
if (killSuccess) {
|
||||
print("Awaiting termination of guestfish, pid=" + pid + " timeout=" + (guestfishTimeoutSecs - i) + "s");
|
||||
GLib.usleep(GLib.USEC_PER_SEC);
|
||||
} else {
|
||||
guestfishExited = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!guestfishExited)
|
||||
throw new Error("guestfish failed to exit");
|
||||
this._mounted = false;
|
||||
this._unlock();
|
||||
},
|
||||
});
|
82
src/autobuilder/js/jsonutil.js
Normal file
82
src/autobuilder/js/jsonutil.js
Normal file
@ -0,0 +1,82 @@
|
||||
// Copyright (C) 2012,2013 Colin Walters <walters@verbum.org>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the
|
||||
// Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
// Boston, MA 02111-1307, USA.
|
||||
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gio = imports.gi.Gio;
|
||||
|
||||
/* jsonutil.js:
|
||||
* Read/write JSON to/from GFile paths, very inefficiently.
|
||||
*/
|
||||
|
||||
function serializeJson(data) {
|
||||
return JSON.stringify(data, null, " ");
|
||||
}
|
||||
|
||||
function writeJsonToStream(stream, data, cancellable) {
|
||||
let buf = serializeJson(data);
|
||||
stream.write_bytes(new GLib.Bytes(buf), cancellable);
|
||||
}
|
||||
|
||||
function writeJsonToStreamAsync(stream, data, cancellable, onComplete) {
|
||||
let buf = serializeJson(data);
|
||||
stream.write_bytes_async(new GLib.Bytes(buf), GLib.PRIORITY_DEFAULT,
|
||||
cancellable, function(stream, result) {
|
||||
let err = null;
|
||||
try {
|
||||
stream.write_bytes_finish(result);
|
||||
} catch (e) {
|
||||
err = e;
|
||||
}
|
||||
onComplete(err != null, err);
|
||||
});
|
||||
}
|
||||
|
||||
function loadJsonFromStream(stream, cancellable) {
|
||||
let membuf = Gio.MemoryOutputStream.new_resizable();
|
||||
membuf.splice(stream, Gio.OutputStreamSpliceFlags.CLOSE_TARGET, cancellable);
|
||||
let bytes = membuf.steal_as_bytes();
|
||||
return JSON.parse(bytes.toArray().toString());
|
||||
}
|
||||
|
||||
function loadJsonFromStreamAsync(stream, cancellable, onComplete) {
|
||||
let membuf = Gio.MemoryOutputStream.new_resizable();
|
||||
membuf.splice_async(stream, Gio.OutputStreamSpliceFlags.CLOSE_TARGET, GLib.PRIORITY_DEFAULT,
|
||||
cancellable, function(stream, result) {
|
||||
let err = null;
|
||||
let res = null;
|
||||
try {
|
||||
stream.splice_finish(result);
|
||||
let bytes = membuf.steal_as_bytes();
|
||||
res = JSON.parse(bytes.toArray().toString());
|
||||
} catch (e) {
|
||||
err = e;
|
||||
}
|
||||
onComplete(res, err);
|
||||
});
|
||||
}
|
||||
|
||||
function writeJsonFileAtomic(path, data, cancellable) {
|
||||
let s = path.replace(null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, cancellable);
|
||||
writeJsonToStream(s, data, cancellable);
|
||||
s.close(cancellable);
|
||||
}
|
||||
|
||||
function loadJson(path, cancellable) {
|
||||
let [success,contents,etag] = path.load_contents(cancellable);
|
||||
return JSON.parse(contents);
|
||||
}
|
||||
|
23
src/autobuilder/js/jsutil.js
Normal file
23
src/autobuilder/js/jsutil.js
Normal file
@ -0,0 +1,23 @@
|
||||
// Copyright (C) 2013 Colin Walters <walters@verbum.org>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the
|
||||
// Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
// Boston, MA 02111-1307, USA.
|
||||
|
||||
function stringEndswith(s, suffix) {
|
||||
let i = s.lastIndexOf(suffix);
|
||||
if (i == -1)
|
||||
return false;
|
||||
return i == s.length - suffix.length;
|
||||
}
|
407
src/autobuilder/js/libqa.js
Normal file
407
src/autobuilder/js/libqa.js
Normal file
@ -0,0 +1,407 @@
|
||||
// Copyright (C) 2012,2013 Colin Walters <walters@verbum.org>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the
|
||||
// Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
// Boston, MA 02111-1307, USA.
|
||||
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Lang = imports.lang;
|
||||
const OSTree = imports.gi.OSTree;
|
||||
|
||||
const GSystem = imports.gi.GSystem;
|
||||
const Params = imports.params;
|
||||
const ProcUtil = imports.procutil;
|
||||
const GuestFish = imports.guestfish;
|
||||
|
||||
const DEFAULT_GF_PARTITION_OPTS = ['-m', '/dev/sda3', '-m', '/dev/sda1:/boot'];
|
||||
|
||||
function linuxGetMemTotalMb() {
|
||||
let [success,contents] = GLib.file_get_contents('/proc/meminfo');
|
||||
let contentLines = contents.toString().split(/\n/);
|
||||
for (let i = 0; contentLines.length; i++) {
|
||||
let line = contentLines[i];
|
||||
if (line.indexOf('MemTotal:') == 0) {
|
||||
return parseInt(/([0-9]+) kB/.exec(line)[1]) / 1024;
|
||||
}
|
||||
}
|
||||
throw new Error("Couldn't determine total memory from /proc/meminfo");
|
||||
}
|
||||
|
||||
function getQemuPath() {
|
||||
let fallbackPaths = ['/usr/libexec/qemu-kvm']
|
||||
let qemuPathString = GLib.find_program_in_path('qemu-kvm');
|
||||
qemuPathString = GLib.find_program_in_path('qemu-kvm');
|
||||
if (!qemuPathString)
|
||||
qemuPathString = GLib.find_program_in_path('kvm');
|
||||
if (qemuPathString == null) {
|
||||
for (let i = 0; i < fallbackPaths.length; i++) {
|
||||
let path = Gio.File.new_for_path(fallbackPaths[i]);
|
||||
if (!path.query_exists(null))
|
||||
continue;
|
||||
qemuPathString = path.get_path();
|
||||
}
|
||||
}
|
||||
if (qemuPathString == null) {
|
||||
throw new Error("Unable to find qemu-kvm");
|
||||
}
|
||||
return qemuPathString;
|
||||
}
|
||||
|
||||
function getDefaultQemuOptions(params) {
|
||||
params = Params.parse(params, { parallel: false });
|
||||
let args = [getQemuPath(), '-vga', 'std', '-usb', '-usbdevice', 'tablet', '-net', 'none'];
|
||||
|
||||
let systemMemoryMb = linuxGetMemTotalMb();
|
||||
let minimumGuestMemoryMb = 768;
|
||||
let maximumGuestMemoryMb = 4 * 1024;
|
||||
// As a guess, use 1/4 of host memory, rounded up to the nearest
|
||||
// multiple of 128M; subject to above constraints as a lame
|
||||
// default...we need global coordination here.
|
||||
let guestMemoryGuessMb = Math.floor(systemMemoryMb / 4 / 128) * 128;
|
||||
let guestMemory = Math.floor(Math.max(minimumGuestMemoryMb,
|
||||
Math.min(maximumGuestMemoryMb,
|
||||
guestMemoryGuessMb)));
|
||||
args.push.apply(args, ['-m', ''+guestMemory]);
|
||||
|
||||
if (params.parallel) {
|
||||
let nCores = Math.min(16, GLib.get_num_processors());
|
||||
args.push.apply(args, ['-smp', ''+nCores]);
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
function newReadWriteMount(diskpath, cancellable) {
|
||||
let mntdir = Gio.File.new_for_path('mnt');
|
||||
GSystem.file_ensure_directory(mntdir, true, cancellable);
|
||||
let gfmnt = new GuestFish.GuestMount(diskpath, {partitionOpts: DEFAULT_GF_PARTITION_OPTS,
|
||||
readWrite: true});
|
||||
gfmnt.mount(mntdir, cancellable);
|
||||
return [gfmnt, mntdir];
|
||||
}
|
||||
|
||||
function createDisk(diskpath, cancellable) {
|
||||
let sizeMb = 8 * 1024;
|
||||
let bootsizeMb = 200;
|
||||
let swapsizeMb = 64;
|
||||
|
||||
let guestfishProcess;
|
||||
|
||||
ProcUtil.runSync(['qemu-img', 'create', '-f', 'qcow2', diskpath.get_path(), '' + sizeMb + 'M'], cancellable);
|
||||
let makeDiskCmd = 'launch\n\
|
||||
part-init /dev/sda mbr\n\
|
||||
blockdev-getsize64 /dev/sda\n\
|
||||
blockdev-getss /dev/sda\n';
|
||||
let gf = new GuestFish.GuestFish(diskpath, {partitionOpts: [], readWrite: true});
|
||||
let lines = gf.run(makeDiskCmd, cancellable);
|
||||
if (lines.length != 2)
|
||||
throw new Error("guestfish returned unexpected output lines (" + lines.length + ", expected 2");
|
||||
let diskBytesize = parseInt(lines[0]);
|
||||
let diskSectorsize = parseInt(lines[1]);
|
||||
print(Format.vprintf("bytesize: %s sectorsize: %s", [diskBytesize, diskSectorsize]));
|
||||
let bootsizeSectors = bootsizeMb * 1024 / diskSectorsize * 1024;
|
||||
let swapsizeSectors = swapsizeMb * 1024 / diskSectorsize * 1024;
|
||||
let rootsizeSectors = diskBytesize / diskSectorsize - bootsizeSectors - swapsizeSectors - 64;
|
||||
let bootOffset = 64;
|
||||
let swapOffset = bootOffset + bootsizeSectors;
|
||||
let rootOffset = swapOffset + swapsizeSectors;
|
||||
let endOffset = rootOffset + rootsizeSectors;
|
||||
|
||||
let partconfig = Format.vprintf('launch\n\
|
||||
part-add /dev/sda p %s %s\n\
|
||||
part-add /dev/sda p %s %s\n\
|
||||
part-add /dev/sda p %s %s\n\
|
||||
part-set-bootable /dev/sda 1 true\n\
|
||||
mkfs ext4 /dev/sda1\n\
|
||||
set-e2label /dev/sda1 gnostree-boot\n\
|
||||
mkswap-L gnostree-swap /dev/sda2\n\
|
||||
mkfs ext4 /dev/sda3\n\
|
||||
set-e2label /dev/sda3 gnostree-root\n\
|
||||
mount /dev/sda3 /\n\
|
||||
mkdir /boot\n\
|
||||
', [bootOffset, swapOffset - 1,
|
||||
swapOffset, rootOffset - 1,
|
||||
rootOffset, endOffset - 1]);
|
||||
print("partition config: ", partconfig);
|
||||
gf.run(partconfig, cancellable);
|
||||
}
|
||||
|
||||
function createDiskSnapshot(diskpath, newdiskpath, cancellable) {
|
||||
ProcUtil.runSync(['qemu-img', 'create', '-f', 'qcow2', '-o', 'backing_file=' + diskpath.get_path(),
|
||||
newdiskpath.get_path()], cancellable);
|
||||
}
|
||||
|
||||
function copyDisk(srcpath, destpath, cancellable) {
|
||||
ProcUtil.runSync(['qemu-img', 'convert', '-O', 'qcow2', srcpath.get_path(),
|
||||
destpath.get_path()], cancellable);
|
||||
}
|
||||
|
||||
function getSysrootAndCurrentDeployment(mntdir, osname) {
|
||||
let sysroot = OSTree.Sysroot.new(mntdir);
|
||||
sysroot.load(null);
|
||||
let deployments = sysroot.get_deployments().filter(function (deployment) {
|
||||
return deployment.get_osname() == osname;
|
||||
});
|
||||
if (deployments.length == 0)
|
||||
throw new Error("No deployments for " + osname + " in " + mntdir.get_path());
|
||||
let current = deployments[0];
|
||||
return [sysroot, current];
|
||||
}
|
||||
|
||||
function getDeployDirs(mntdir, osname) {
|
||||
let [sysroot, current] = getSysrootAndCurrentDeployment(mntdir, osname);
|
||||
let deployDir = sysroot.get_deployment_directory(current);
|
||||
return [deployDir, deployDir.get_child('etc')];
|
||||
}
|
||||
|
||||
function modifyBootloaderAppendKernelArgs(mntdir, kernelArgs, cancellable) {
|
||||
let confPath = mntdir.resolve_relative_path('boot/syslinux/syslinux.cfg');
|
||||
let conf = GSystem.file_load_contents_utf8(confPath, cancellable);
|
||||
let lines = conf.split('\n');
|
||||
let modifiedLines = [];
|
||||
|
||||
let didModify = false;
|
||||
let kernelArg = kernelArgs.join(' ');
|
||||
let kernelLineRe = /\tAPPEND /;
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
let line = lines[i];
|
||||
let match = kernelLineRe.exec(line);
|
||||
if (!match) {
|
||||
modifiedLines.push(line);
|
||||
} else {
|
||||
modifiedLines.push(line + ' ' + kernelArg);
|
||||
didModify = true;
|
||||
}
|
||||
}
|
||||
if (!didModify)
|
||||
throw new Error("Failed to find APPEND option in syslinux.cfg");
|
||||
let modifiedConf = modifiedLines.join('\n');
|
||||
confPath.replace_contents(modifiedConf, null, false,
|
||||
Gio.FileCreateFlags.NONE,
|
||||
cancellable);
|
||||
}
|
||||
|
||||
function getMultiuserWantsDir(currentEtcDir) {
|
||||
return currentEtcDir.resolve_relative_path('systemd/system/multi-user.target.wants');
|
||||
}
|
||||
|
||||
function getDatadir() {
|
||||
return Gio.File.new_for_path(GLib.getenv('OSTBUILD_DATADIR'));
|
||||
}
|
||||
|
||||
function injectExportJournal(currentDir, currentEtcDir, cancellable) {
|
||||
let binDir = currentDir.resolve_relative_path('usr/bin');
|
||||
let multiuserWantsDir = getMultiuserWantsDir(currentEtcDir);
|
||||
let datadir = getDatadir();
|
||||
let exportScript = datadir.resolve_relative_path('tests/gnome-ostree-export-journal-to-serialdev');
|
||||
let exportScriptService = datadir.resolve_relative_path('tests/gnome-ostree-export-journal-to-serialdev.service');
|
||||
let exportBin = binDir.get_child(exportScript.get_basename());
|
||||
exportScript.copy(exportBin, Gio.FileCopyFlags.OVERWRITE, cancellable, null, null);
|
||||
GSystem.file_chmod(exportBin, 493, cancellable);
|
||||
exportScriptService.copy(multiuserWantsDir.get_child(exportScriptService.get_basename()), Gio.FileCopyFlags.OVERWRITE, cancellable, null, null);
|
||||
let journalConfPath = currentEtcDir.resolve_relative_path('systemd/journald.conf');
|
||||
journalConfPath.replace_contents('[Journal]\n\
|
||||
RateLimitInterval=0\n', null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, cancellable);
|
||||
}
|
||||
|
||||
function injectTestUserCreation(currentDir, currentEtcDir, username, params, cancellable) {
|
||||
params = Params.parse(params, { password: null });
|
||||
let execLine;
|
||||
if (params.password === null) {
|
||||
execLine = Format.vprintf('/bin/sh -c "/usr/sbin/useradd %s; passwd -d %s"',
|
||||
[username, username]);
|
||||
} else {
|
||||
execLine = Format.vprintf('/bin/sh -c "/usr/sbin/useradd %s; echo %s | passwd --stdin %s',
|
||||
[username, params.password, username]);
|
||||
}
|
||||
let addUserService = '[Unit]\n\
|
||||
Description=Add user %s\n\
|
||||
Before=gdm.service\n\
|
||||
[Service]\n\
|
||||
ExecStart=%s\n\
|
||||
Type=oneshot\n';
|
||||
addUserService = Format.vprintf(addUserService, [username, execLine]);
|
||||
|
||||
let addUserServicePath = getMultiuserWantsDir(currentEtcDir).get_child('gnome-ostree-add-user-' + username + '.service');
|
||||
addUserServicePath.replace_contents(addUserService, null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, cancellable);
|
||||
}
|
||||
|
||||
function enableAutologin(currentDir, currentEtcDir, username, cancellable) {
|
||||
let gdmCustomPath = currentEtcDir.resolve_relative_path('gdm/custom.conf');
|
||||
let keyfile = new GLib.KeyFile();
|
||||
keyfile.load_from_file(gdmCustomPath.get_path(), GLib.KeyFileFlags.NONE);
|
||||
keyfile.set_string('daemon', 'AutomaticLoginEnable', 'true');
|
||||
keyfile.set_string('daemon', 'AutomaticLogin', username);
|
||||
gdmCustomPath.replace_contents(keyfile.to_data()[0], null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, cancellable);
|
||||
}
|
||||
|
||||
function _findFirstFileMatching(dir, prefix, cancellable) {
|
||||
let d = dir.enumerate_children('standard::*', Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, cancellable);
|
||||
let finfo;
|
||||
try {
|
||||
while ((finfo = d.next_file(cancellable)) != null) {
|
||||
let name = finfo.get_name();
|
||||
if (name.indexOf(prefix) == 0) {
|
||||
return dir.get_child(name);
|
||||
}
|
||||
}
|
||||
throw new Error("Couldn't find " + prefix + " in " + dir.get_path());
|
||||
} finally {
|
||||
d.close(null);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function _findCurrentKernel(mntdir, osname, cancellable) {
|
||||
let [sysroot, current] = getSysrootAndCurrentDeployment(mntdir, osname);
|
||||
let deployBootdir = sysroot.get_deployment_directory(current).resolve_relative_path('boot');
|
||||
return [_findFirstFileMatching(deployBootdir, 'vmlinuz-', cancellable),
|
||||
_findFirstFileMatching(deployBootdir, 'initramfs-', cancellable)];
|
||||
};
|
||||
|
||||
function _findCurrentOstreeBootArg(mntdir, cancellable) {
|
||||
let bootLoaderEntriesDir = mntdir.resolve_relative_path('boot/loader/entries');
|
||||
let conf = _findFirstFileMatching(bootLoaderEntriesDir, 'ostree-', cancellable);
|
||||
let contents = GSystem.file_load_contents_utf8(conf, cancellable);
|
||||
let lines = contents.split('\n');
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
let line = lines[i];
|
||||
if (line.indexOf('options ') != 0)
|
||||
continue;
|
||||
let options = line.substr(8).split(' ');
|
||||
for (let j = 0; j < options.length; j++) {
|
||||
let opt = options[j];
|
||||
if (opt.indexOf('ostree=') != 0)
|
||||
continue;
|
||||
return opt;
|
||||
}
|
||||
}
|
||||
throw new Error("Failed to find ostree= kernel argument");
|
||||
}
|
||||
|
||||
function pullDeploy(mntdir, srcrepo, osname, target, revision, originRepoUrl, cancellable) {
|
||||
let ostreedir = mntdir.get_child('ostree');
|
||||
let ostreeOsdir = ostreedir.resolve_relative_path('deploy/' + osname);
|
||||
|
||||
let adminCmd = ['ostree', 'admin', '--sysroot=' + mntdir.get_path()];
|
||||
let adminEnv = GLib.get_environ();
|
||||
adminEnv.push('LIBGSYSTEM_ENABLE_GUESTFS_FUSE_WORKAROUND=1');
|
||||
let procdir = mntdir.get_child('proc');
|
||||
if (!procdir.query_exists(cancellable)) {
|
||||
ProcUtil.runSync(adminCmd.concat(['init-fs', mntdir.get_path()]), cancellable,
|
||||
{logInitiation: true, env: adminEnv});
|
||||
}
|
||||
|
||||
let revOrTarget;
|
||||
if (revision)
|
||||
revOrTarget = revision;
|
||||
else
|
||||
revOrTarget = target;
|
||||
|
||||
// Remove any existing bootloader configuration, and stub out an
|
||||
// empty syslinux configuration that we can use to bootstrap.
|
||||
let bootLoaderLink = mntdir.resolve_relative_path('boot/loader');
|
||||
GSystem.shutil_rm_rf(bootLoaderLink, cancellable);
|
||||
let bootLoaderDir0 = mntdir.resolve_relative_path('boot/loader.0');
|
||||
GSystem.shutil_rm_rf(bootLoaderDir0, cancellable);
|
||||
bootLoaderLink.make_symbolic_link('loader.0', cancellable);
|
||||
GSystem.file_ensure_directory(bootLoaderDir0, true, cancellable);
|
||||
let syslinuxPath = mntdir.resolve_relative_path('boot/loader/syslinux.cfg');
|
||||
syslinuxPath.replace_contents('TIMEOUT 10\n', null, false, Gio.FileCreateFlags.NONE, cancellable);
|
||||
|
||||
// A compatibility symlink for syslinux
|
||||
let syslinuxDir = mntdir.resolve_relative_path('boot/syslinux');
|
||||
GSystem.shutil_rm_rf(syslinuxDir, cancellable);
|
||||
GSystem.file_ensure_directory(syslinuxDir, true, cancellable);
|
||||
let syslinuxLink = mntdir.resolve_relative_path('boot/syslinux/syslinux.cfg');
|
||||
syslinuxLink.make_symbolic_link('../loader/syslinux.cfg', cancellable);
|
||||
|
||||
// Also blow alway all existing deployments here for the OS; this
|
||||
// will clean up disks that were using the old ostree model.
|
||||
GSystem.shutil_rm_rf(ostreeOsdir, cancellable);
|
||||
|
||||
let repoPath = ostreedir.get_child('repo');
|
||||
let repoArg = '--repo=' + repoPath.get_path();
|
||||
ProcUtil.runSync(adminCmd.concat(['os-init', osname]), cancellable,
|
||||
{logInitiation: true, env: adminEnv});
|
||||
if (originRepoUrl)
|
||||
ProcUtil.runSync(['ostree', repoArg,
|
||||
'remote', 'add', osname, originRepoUrl, target],
|
||||
cancellable, { logInitiation: true });
|
||||
|
||||
ProcUtil.runSync(['ostree', repoArg,
|
||||
'pull-local', '--remote=' + osname, srcrepo.get_path(), revOrTarget], cancellable,
|
||||
{logInitiation: true, env: adminEnv});
|
||||
|
||||
let origin = GLib.KeyFile.new();
|
||||
origin.set_string('origin', 'refspec', osname + ':' + target);
|
||||
let [originData, len] = origin.to_data();
|
||||
let tmpOrigin = Gio.File.new_for_path('origin.tmp');
|
||||
tmpOrigin.replace_contents(originData, null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, cancellable);
|
||||
|
||||
let rootArg = 'root=LABEL=gnostree-root';
|
||||
ProcUtil.runSync(adminCmd.concat(['deploy', '--karg=' + rootArg, '--karg=quiet', '--karg=splash',
|
||||
'--os=' + osname, '--origin-file=' + tmpOrigin.get_path(), revOrTarget]), cancellable,
|
||||
{logInitiation: true, env: adminEnv});
|
||||
|
||||
let defaultFstab = 'LABEL=gnostree-root / ext4 defaults 1 1\n\
|
||||
LABEL=gnostree-boot /boot ext4 defaults 1 2\n\
|
||||
LABEL=gnostree-swap swap swap defaults 0 0\n';
|
||||
let fstabPath = ostreeOsdir.resolve_relative_path('current/etc/fstab');
|
||||
fstabPath.replace_contents(defaultFstab, null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, cancellable);
|
||||
};
|
||||
|
||||
function bootloaderInstall(diskpath, workdir, osname, cancellable) {
|
||||
let qemuArgs = getDefaultQemuOptions();
|
||||
|
||||
let tmpKernelPath = workdir.get_child('kernel.img');
|
||||
let tmpInitrdPath = workdir.get_child('initrd.img');
|
||||
|
||||
let [gfmnt, mntdir] = newReadWriteMount(diskpath, cancellable);
|
||||
let ostreeArg;
|
||||
try {
|
||||
let [kernelPath, initrdPath] = _findCurrentKernel(mntdir, osname, cancellable)
|
||||
ostreeArg = _findCurrentOstreeBootArg(mntdir, cancellable);
|
||||
|
||||
// Copy
|
||||
kernelPath.copy(tmpKernelPath, 0, cancellable, null, null);
|
||||
initrdPath.copy(tmpInitrdPath, 0, cancellable, null, null);
|
||||
} finally {
|
||||
gfmnt.umount(cancellable);
|
||||
}
|
||||
|
||||
let consoleOutput = workdir.get_child('bootloader-console.out');
|
||||
|
||||
let kernelArgv = ['console=ttyS0', 'panic=1', 'root=LABEL=gnostree-root', 'rw', ostreeArg,
|
||||
'systemd.journald.forward_to_console=true',
|
||||
'systemd.unit=gnome-ostree-install-bootloader.target'];
|
||||
|
||||
qemuArgs.push.apply(qemuArgs, ['-drive', 'file=' + diskpath.get_path() + ',if=virtio',
|
||||
'-vnc', 'none',
|
||||
'-no-reboot',
|
||||
'-serial', 'file:' + consoleOutput.get_path(),
|
||||
'-chardev', 'socket,id=charmonitor,path=qemu.monitor,server,nowait',
|
||||
'-mon', 'chardev=charmonitor,id=monitor,mode=control',
|
||||
'-kernel', tmpKernelPath.get_path(),
|
||||
'-initrd', tmpInitrdPath.get_path(),
|
||||
'-append', kernelArgv.join(' ')
|
||||
]);
|
||||
|
||||
ProcUtil.runSync(qemuArgs, cancellable, { cwd: workdir.get_path(),
|
||||
logInitiation: true });
|
||||
|
||||
tmpKernelPath.delete(cancellable);
|
||||
tmpInitrdPath.delete(cancellable);
|
||||
}
|
93
src/autobuilder/js/main.js
Normal file
93
src/autobuilder/js/main.js
Normal file
@ -0,0 +1,93 @@
|
||||
// Copyright (C) 2011 Colin Walters <walters@verbum.org>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the
|
||||
// Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
// Boston, MA 02111-1307, USA.
|
||||
|
||||
const GLib = imports.gi.GLib;
|
||||
|
||||
const Format = imports.format;
|
||||
|
||||
const BUILTINS = ['autobuilder',
|
||||
'git-mirror',
|
||||
'make',
|
||||
'qa-make-disk',
|
||||
'run-task',
|
||||
'shell'];
|
||||
|
||||
function getModule(unixName) {
|
||||
return imports.builtins[unixName.replace(/-/g, '_')];
|
||||
}
|
||||
|
||||
function getClass(unixName) {
|
||||
let module = getModule(unixName);
|
||||
let camelParts = unixName.split(/-/);
|
||||
let camel = camelParts.map(function (part) {
|
||||
return part[0].toLocaleUpperCase() + part.substr(1);
|
||||
}).join('');
|
||||
return module[camel];
|
||||
}
|
||||
|
||||
function usage(ecode) {
|
||||
print("Builtins:");
|
||||
for (let i = 0; i < BUILTINS.length; i++) {
|
||||
let unixName = BUILTINS[i];
|
||||
let description = getClass(unixName).prototype.DESCRIPTION;
|
||||
print(Format.vprintf(" %s - %s", [unixName, description]));
|
||||
}
|
||||
return ecode;
|
||||
}
|
||||
|
||||
let ecode;
|
||||
if (ARGV.length < 1) {
|
||||
ecode = usage(1);
|
||||
} else if (ARGV[0] == '-h' || ARGV[0] == '--help') {
|
||||
ecode = usage(0);
|
||||
} else {
|
||||
let name = ARGV[0];
|
||||
let found = false;
|
||||
for (let i = 0; i < BUILTINS.length; i++) {
|
||||
if (BUILTINS[i] == name) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
usage(1);
|
||||
} else {
|
||||
let argv = ARGV.concat();
|
||||
argv.shift();
|
||||
|
||||
let loop = GLib.MainLoop.new(null, true);
|
||||
let cls = getClass(name);
|
||||
let instance = new cls;
|
||||
let cancellable = null;
|
||||
GLib.idle_add(GLib.PRIORITY_DEFAULT,
|
||||
function() {
|
||||
ecode = 1;
|
||||
try {
|
||||
instance.main(argv, loop, cancellable);
|
||||
ecode = 0;
|
||||
} finally {
|
||||
loop.quit();
|
||||
}
|
||||
return false;
|
||||
});
|
||||
loop.run();
|
||||
}
|
||||
}
|
||||
ecode;
|
||||
|
||||
|
||||
|
34
src/autobuilder/js/params.js
Normal file
34
src/autobuilder/js/params.js
Normal file
@ -0,0 +1,34 @@
|
||||
// Taken from gnome-shell/js/misc/params.js under the GNU General Public License
|
||||
// parse:
|
||||
// @params: caller-provided parameter object, or %null
|
||||
// @defaults: function-provided defaults object
|
||||
// @allowExtras: whether or not to allow properties not in @default
|
||||
//
|
||||
// Examines @params and fills in default values from @defaults for
|
||||
// any properties in @defaults that don't appear in @params. If
|
||||
// @allowExtras is not %true, it will throw an error if @params
|
||||
// contains any properties that aren't in @defaults.
|
||||
//
|
||||
// If @params is %null, this returns the values from @defaults.
|
||||
//
|
||||
// Return value: a new object, containing the merged parameters from
|
||||
// @params and @defaults
|
||||
function parse(params, defaults, allowExtras) {
|
||||
let ret = {}, prop;
|
||||
|
||||
if (!params)
|
||||
params = {};
|
||||
|
||||
for (prop in params) {
|
||||
if (!(prop in defaults) && !allowExtras)
|
||||
throw new Error('Unrecognized parameter "' + prop + '"');
|
||||
ret[prop] = params[prop];
|
||||
}
|
||||
|
||||
for (prop in defaults) {
|
||||
if (!(prop in params))
|
||||
ret[prop] = defaults[prop];
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
240
src/autobuilder/js/procutil.js
Normal file
240
src/autobuilder/js/procutil.js
Normal file
@ -0,0 +1,240 @@
|
||||
// Copyright (C) 2012,2013 Colin Walters <walters@verbum.org>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the
|
||||
// Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
// Boston, MA 02111-1307, USA.
|
||||
|
||||
const Format = imports.format;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gio = imports.gi.Gio;
|
||||
|
||||
const GSystem = imports.gi.GSystem;
|
||||
const Params = imports.params;
|
||||
const StreamUtil = imports.streamutil;
|
||||
|
||||
function objectToEnvironment(o) {
|
||||
let r = [];
|
||||
for (let k in o)
|
||||
r.push(k + "=" + o[k]);
|
||||
return r;
|
||||
}
|
||||
|
||||
function _newContext(argv, params) {
|
||||
let context = new GSystem.SubprocessContext({argv: argv});
|
||||
params = Params.parse(params, {cwd: null,
|
||||
env: null,
|
||||
stderr: null,
|
||||
logInitiation: false });
|
||||
if (typeof(params.cwd) == 'string')
|
||||
context.set_cwd(params.cwd);
|
||||
else if (params.cwd)
|
||||
context.set_cwd(params.cwd.get_path());
|
||||
|
||||
if (params.env)
|
||||
context.set_environment(params.env);
|
||||
|
||||
if (params.stderr != null)
|
||||
context.set_stderr_disposition(params.stderr);
|
||||
return [context, params];
|
||||
}
|
||||
|
||||
function _wait_sync_check_internal(proc, cancellable) {
|
||||
try {
|
||||
proc.wait_sync_check(cancellable);
|
||||
} catch (e) {
|
||||
if (e.domain == GLib.spawn_exit_error_quark() ||
|
||||
e.matches(GLib.SpawnError, GLib.SpawnError.FAILED)) {
|
||||
let err = new Error(Format.vprintf("Child process %s: %s", [JSON.stringify(proc.context.argv), e.message]));
|
||||
err.origError = e;
|
||||
throw err;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function runSync(argv, cancellable, params) {
|
||||
let [context, pparams] = _newContext(argv, params);
|
||||
let proc = new GSystem.Subprocess({context: context});
|
||||
proc.init(cancellable);
|
||||
if (pparams.logInitiation)
|
||||
print(Format.vprintf("Started child process %s: pid=%s", [proc.context.argv.map(GLib.shell_quote).join(' '), proc.get_pid()]));
|
||||
_wait_sync_check_internal(proc, cancellable);
|
||||
}
|
||||
|
||||
function _runSyncGetOutputInternal(argv, cancellable, params, subParams) {
|
||||
subParams = Params.parse(subParams, { splitLines: false,
|
||||
grep: null });
|
||||
let [context, pparams] = _newContext(argv, params);
|
||||
context.set_stdout_disposition(GSystem.SubprocessStreamDisposition.PIPE);
|
||||
context.set_stderr_disposition(GSystem.SubprocessStreamDisposition.INHERIT);
|
||||
let proc = new GSystem.Subprocess({context: context});
|
||||
proc.init(cancellable);
|
||||
if (pparams.logInitiation)
|
||||
print(Format.vprintf("Started child process %s: pid=%s", [JSON.stringify(proc.context.argv), proc.get_pid()]));
|
||||
let input = proc.get_stdout_pipe();
|
||||
let dataIn = Gio.DataInputStream.new(input);
|
||||
|
||||
let result = null;
|
||||
if (subParams.grep) {
|
||||
let grep = subParams.grep;
|
||||
while (true) {
|
||||
let [line, len] = dataIn.read_line_utf8(cancellable);
|
||||
if (line == null)
|
||||
break;
|
||||
result = grep.exec(line);
|
||||
if (result != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (subParams.splitLines) {
|
||||
result = StreamUtil.dataInputStreamReadLines(dataIn, cancellable);
|
||||
} else {
|
||||
result = '';
|
||||
while (true) {
|
||||
let [line, len] = dataIn.read_line_utf8(cancellable);
|
||||
if (line == null)
|
||||
break;
|
||||
result += (line + '\n');
|
||||
}
|
||||
}
|
||||
dataIn.close(cancellable);
|
||||
_wait_sync_check_internal(proc, cancellable);
|
||||
return result;
|
||||
}
|
||||
|
||||
function runSyncGetOutputLines(args, cancellable, params) {
|
||||
return _runSyncGetOutputInternal(args, cancellable, params, { splitLines: true });
|
||||
}
|
||||
|
||||
function runSyncGetOutputUTF8(args, cancellable, params) {
|
||||
return _runSyncGetOutputInternal(args, cancellable, params);
|
||||
}
|
||||
|
||||
function runSyncGetOutputUTF8Stripped(args, cancellable, params) {
|
||||
return _runSyncGetOutputInternal(args, cancellable, params).replace(/[ \n]+$/, '');
|
||||
}
|
||||
|
||||
function runSyncGetOutputUTF8StrippedOrNull(args, cancellable, params) {
|
||||
if (!params)
|
||||
params = {};
|
||||
try {
|
||||
params.stderr = GSystem.SubprocessStreamDisposition.NULL;
|
||||
return runSyncGetOutputUTF8Stripped(args, cancellable, params);
|
||||
} catch (e) {
|
||||
if (e.origError && e.origError.domain == GLib.spawn_exit_error_quark())
|
||||
return null;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
function runSyncGetOutputGrep(args, pattern, cancellable, params) {
|
||||
return _runSyncGetOutputInternal(args, cancellable, params, { grep: pattern });
|
||||
}
|
||||
|
||||
function getExitStatusAndString(ecode) {
|
||||
try {
|
||||
GLib.spawn_check_exit_status(ecode);
|
||||
return [true, null];
|
||||
} catch (e) {
|
||||
if (e.domain == GLib.spawn_exit_error_quark() ||
|
||||
e.matches(GLib.SpawnError, GLib.SpawnError.FAILED))
|
||||
return [false, e.message];
|
||||
else
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
function asyncWaitCheckFinish(process, result) {
|
||||
let [waitSuccess, ecode] = process.wait_finish(result);
|
||||
return getExitStatusAndString(ecode);
|
||||
}
|
||||
|
||||
function runWithTempContextAndLoop(func) {
|
||||
let mainContext = new GLib.MainContext();
|
||||
let mainLoop = GLib.MainLoop.new(mainContext, true);
|
||||
try {
|
||||
mainContext.push_thread_default();
|
||||
return func(mainLoop);
|
||||
} finally {
|
||||
mainContext.pop_thread_default();
|
||||
}
|
||||
}
|
||||
|
||||
function _runProcWithInputSyncGetLinesInternal(mainLoop, argv, cancellable, input) {
|
||||
let context = new GSystem.SubprocessContext({ argv: argv });
|
||||
context.set_stdout_disposition(GSystem.SubprocessStreamDisposition.PIPE);
|
||||
context.set_stdin_disposition(GSystem.SubprocessStreamDisposition.PIPE);
|
||||
let proc = new GSystem.Subprocess({context: context});
|
||||
proc.init(cancellable);
|
||||
let stdinPipe = proc.get_stdin_pipe();
|
||||
let memStream = Gio.MemoryInputStream.new_from_bytes(new GLib.Bytes(input));
|
||||
let asyncOps = 3;
|
||||
function asyncOpComplete() {
|
||||
asyncOps--;
|
||||
if (asyncOps == 0)
|
||||
mainLoop.quit();
|
||||
}
|
||||
function onSpliceComplete(stdinPipe, result) {
|
||||
try {
|
||||
let bytesWritten = stdinPipe.splice_finish(result);
|
||||
} finally {
|
||||
asyncOpComplete();
|
||||
}
|
||||
}
|
||||
let closeBoth = Gio.OutputStreamSpliceFlags.CLOSE_SOURCE | Gio.OutputStreamSpliceFlags.CLOSE_TARGET;
|
||||
stdinPipe.splice_async(memStream, closeBoth, GLib.PRIORITY_DEFAULT,
|
||||
cancellable, onSpliceComplete);
|
||||
|
||||
let procException = null;
|
||||
function onProcExited(proc, result) {
|
||||
try {
|
||||
let [success, statusText] = asyncWaitCheckFinish(proc, result);
|
||||
if (!success)
|
||||
procException = statusText;
|
||||
} finally {
|
||||
asyncOpComplete();
|
||||
}
|
||||
}
|
||||
proc.wait(cancellable, onProcExited);
|
||||
|
||||
let stdoutPipe = proc.get_stdout_pipe();
|
||||
let stdoutData = Gio.DataInputStream.new(stdoutPipe);
|
||||
let lines = [];
|
||||
function onReadLine(datastream, result) {
|
||||
try {
|
||||
let [line, len] = stdoutData.read_line_finish_utf8(result);
|
||||
if (line == null)
|
||||
asyncOpComplete();
|
||||
else {
|
||||
lines.push(line);
|
||||
stdoutData.read_line_async(GLib.PRIORITY_DEFAULT, cancellable, onReadLine);
|
||||
}
|
||||
} catch (e) {
|
||||
asyncOpComplete();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
stdoutData.read_line_async(GLib.PRIORITY_DEFAULT, cancellable, onReadLine);
|
||||
|
||||
mainLoop.run();
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
function runProcWithInputSyncGetLines(argv, cancellable, input) {
|
||||
return runWithTempContextAndLoop(function (loop) {
|
||||
return _runProcWithInputSyncGetLinesInternal(loop, argv, cancellable, input);
|
||||
});
|
||||
}
|
199
src/autobuilder/js/snapshot.js
Normal file
199
src/autobuilder/js/snapshot.js
Normal file
@ -0,0 +1,199 @@
|
||||
//
|
||||
// Copyright (C) 2012 Colin Walters <walters@verbum.org>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the
|
||||
// Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
// Boston, MA 02111-1307, USA.
|
||||
|
||||
const Lang = imports.lang;
|
||||
|
||||
const JsonUtil = imports.jsonutil;
|
||||
const Params = imports.params;
|
||||
|
||||
function _componentDict(snapshot) {
|
||||
let r = {};
|
||||
let components = snapshot['components'];
|
||||
for (let i = 0; i < components.length; i++) {
|
||||
let component = components[i];
|
||||
let name = component['name'];
|
||||
if (r[name])
|
||||
throw new Error("Duplicate component name " + name);
|
||||
r[name] = component;
|
||||
}
|
||||
|
||||
let patches = snapshot['patches'];
|
||||
if (patches['name'])
|
||||
r[patches['name']] = patches;
|
||||
|
||||
let base = snapshot['base'];
|
||||
r[base['name']] = base;
|
||||
return r;
|
||||
}
|
||||
|
||||
function snapshotDiff(a, b) {
|
||||
let a_components = _componentDict(a);
|
||||
let b_components = _componentDict(b);
|
||||
|
||||
let added = [];
|
||||
let modified = [];
|
||||
let removed = [];
|
||||
|
||||
for (let name in a_components) {
|
||||
let c_a = a_components[name];
|
||||
let c_b = b_components[name];
|
||||
if (c_b == undefined) {
|
||||
removed.push(name);
|
||||
} else if (c_a['revision'] != c_b['revision']) {
|
||||
modified.push(name);
|
||||
}
|
||||
}
|
||||
for (let name in b_components) {
|
||||
if (a_components[name] == undefined) {
|
||||
added.push(name);
|
||||
}
|
||||
}
|
||||
return [added, modified, removed];
|
||||
}
|
||||
|
||||
function fromFile(path, cancellable, args) {
|
||||
let data = JsonUtil.loadJson(path, cancellable);
|
||||
return new Snapshot(data, path, args);
|
||||
}
|
||||
|
||||
const Snapshot = new Lang.Class({
|
||||
Name: 'Snapshot',
|
||||
|
||||
_init: function(data, path, params) {
|
||||
params = Params.parse(params, { prepareResolve: false });
|
||||
this.data = data;
|
||||
this.path = path;
|
||||
if (params.prepareResolve) {
|
||||
data['patches'] = this._resolveComponent(data, data['patches']);
|
||||
data['base'] = this._resolveComponent(data, data['base']);
|
||||
for (let i = 0; i < data['components'].length; i++) {
|
||||
let component = this._resolveComponent(data, data['components'][i]);
|
||||
data['components'][i] = component;
|
||||
}
|
||||
}
|
||||
this._componentDict = _componentDict(data);
|
||||
this._componentNames = [];
|
||||
for (let k in this._componentDict)
|
||||
this._componentNames.push(k);
|
||||
},
|
||||
|
||||
_resolveComponent: function(manifest, componentMeta) {
|
||||
if (!componentMeta)
|
||||
return {};
|
||||
|
||||
let result = {};
|
||||
Lang.copyProperties(componentMeta, result);
|
||||
let origSrc = componentMeta['src'];
|
||||
let name = componentMeta['name'];
|
||||
|
||||
if (origSrc.indexOf('tarball:') == 0) {
|
||||
if (!name)
|
||||
throw new Error("Component src " + origSrc + " has no name attribute");
|
||||
if (!componentMeta['checksum'])
|
||||
throw new Error("Component src " + origSrc + " has no checksum attribute");
|
||||
return result;
|
||||
}
|
||||
|
||||
let didExpand = false;
|
||||
let vcsConfig = manifest['vcsconfig'];
|
||||
for (let vcsprefix in vcsConfig) {
|
||||
let expansion = vcsConfig[vcsprefix];
|
||||
let prefix = vcsprefix + ':';
|
||||
if (origSrc.indexOf(prefix) == 0) {
|
||||
result['src'] = expansion + origSrc.substr(prefix.length);
|
||||
didExpand = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let src, idx;
|
||||
if (name == undefined) {
|
||||
if (didExpand) {
|
||||
src = origSrc;
|
||||
idx = src.lastIndexOf(':');
|
||||
name = src.substr(idx+1);
|
||||
} else {
|
||||
src = result['src'];
|
||||
idx = src.lastIndexOf('/');
|
||||
name = src.substr(idx+1);
|
||||
}
|
||||
let i = name.lastIndexOf('.git');
|
||||
if (i != -1 && i == name.length - 4) {
|
||||
name = name.substr(0, name.length - 4);
|
||||
}
|
||||
name = name.replace(/\//g, '-');
|
||||
result['name'] = name;
|
||||
}
|
||||
|
||||
let branchOrTag = result['branch'] || result['tag'];
|
||||
if (!branchOrTag) {
|
||||
result['branch'] = 'master';
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
_expandComponent: function(component) {
|
||||
let r = {};
|
||||
Lang.copyProperties(component, r);
|
||||
let patchMeta = this.data['patches'];
|
||||
if (patchMeta) {
|
||||
let componentPatchFiles = component['patches'] || [];
|
||||
if (componentPatchFiles.length > 0) {
|
||||
let patches = {};
|
||||
Lang.copyProperties(patchMeta, patches);
|
||||
patches['files'] = componentPatchFiles;
|
||||
r['patches'] = patches;
|
||||
}
|
||||
}
|
||||
let configOpts = (this.data['config-opts'] || []).concat();
|
||||
configOpts.push.apply(configOpts, component['config-opts'] || []);
|
||||
r['config-opts'] = configOpts;
|
||||
return r;
|
||||
},
|
||||
|
||||
getAllComponentNames: function() {
|
||||
return this._componentNames;
|
||||
},
|
||||
|
||||
getComponentMap: function() {
|
||||
return this._componentDict;
|
||||
},
|
||||
|
||||
getComponent: function(name, allowNone) {
|
||||
let r = this._componentDict[name] || null;
|
||||
if (!r && !allowNone)
|
||||
throw new Error("No component " + name + " in snapshot");
|
||||
return r;
|
||||
},
|
||||
|
||||
getMatchingSrc: function(src, allowNone) {
|
||||
let result = [];
|
||||
for (let i = 0; i < this._componentNames.length; i++) {
|
||||
let name = this._componentNames[i];
|
||||
let component = this.getComponent(name, false);
|
||||
if (component['src'] == src)
|
||||
result.push(component);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
getExpanded: function(name) {
|
||||
return this._expandComponent(this.getComponent(name));
|
||||
}
|
||||
});
|
27
src/autobuilder/js/streamutil.js
Normal file
27
src/autobuilder/js/streamutil.js
Normal file
@ -0,0 +1,27 @@
|
||||
// Copyright (C) 2012,2013 Colin Walters <walters@verbum.org>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the
|
||||
// Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
// Boston, MA 02111-1307, USA.
|
||||
|
||||
function dataInputStreamReadLines(dataIn, cancellable) {
|
||||
let result = [];
|
||||
while (true) {
|
||||
let [line, len] = dataIn.read_line_utf8(cancellable);
|
||||
if (line == null)
|
||||
break;
|
||||
result.push(line);
|
||||
}
|
||||
return result;
|
||||
}
|
469
src/autobuilder/js/task.js
Normal file
469
src/autobuilder/js/task.js
Normal file
@ -0,0 +1,469 @@
|
||||
// Copyright (C) 2012,2013 Colin Walters <walters@verbum.org>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the
|
||||
// Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
// Boston, MA 02111-1307, USA.
|
||||
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Format = imports.format;
|
||||
const Lang = imports.lang;
|
||||
const Signals = imports.signals;
|
||||
|
||||
const GSystem = imports.gi.GSystem;
|
||||
const OSTree = imports.gi.OSTree;
|
||||
const Params = imports.params;
|
||||
const VersionedDir = imports.versioneddir;
|
||||
const JsonUtil = imports.jsonutil;
|
||||
const ProcUtil = imports.procutil;
|
||||
const BuildUtil = imports.buildutil;
|
||||
|
||||
const DefaultTaskDef = {
|
||||
TaskName: '',
|
||||
TaskAfter: [],
|
||||
TaskScheduleMinSecs: 0,
|
||||
|
||||
PreserveStdout: true,
|
||||
RetainFailed: 5,
|
||||
RetainSuccess: 5,
|
||||
};
|
||||
|
||||
var _tasksetInstance = null;
|
||||
const TaskSet = new Lang.Class({
|
||||
Name: 'TaskSet',
|
||||
|
||||
_init: function() {
|
||||
this._tasks = [];
|
||||
let taskdir = Gio.File.new_for_path(GLib.getenv('OSTBUILD_DATADIR')).resolve_relative_path('js/tasks');
|
||||
let denum = taskdir.enumerate_children('standard::*', 0, null);
|
||||
let finfo;
|
||||
|
||||
for (let taskmodname in imports.tasks) {
|
||||
let taskMod = imports.tasks[taskmodname];
|
||||
for (let defname in taskMod) {
|
||||
if (defname.indexOf('Task') !== 0
|
||||
|| defname == 'Task')
|
||||
continue;
|
||||
let cls = taskMod[defname];
|
||||
this.register(cls);
|
||||
}
|
||||
}
|
||||
|
||||
denum.close(null);
|
||||
},
|
||||
|
||||
register: function(task) {
|
||||
this._tasks.push(task);
|
||||
},
|
||||
|
||||
getAllTasks: function() {
|
||||
return this._tasks;
|
||||
},
|
||||
|
||||
getTask: function(taskName) {
|
||||
for (let i = 0; i < this._tasks.length; i++) {
|
||||
let taskConstructor = this._tasks[i];
|
||||
let taskDef = taskConstructor.prototype.TaskDef;
|
||||
let curName = taskDef.TaskName;
|
||||
if (curName == taskName)
|
||||
return taskConstructor;
|
||||
}
|
||||
throw new Error("No task definition matches " + taskName);
|
||||
},
|
||||
|
||||
getTaskDef: function(taskName) {
|
||||
let taskDef = this.getTask(taskName).prototype.TaskDef;
|
||||
return Params.parse(taskDef, DefaultTaskDef);
|
||||
},
|
||||
|
||||
getInstance: function() {
|
||||
if (!_tasksetInstance)
|
||||
_tasksetInstance = new TaskSet();
|
||||
return _tasksetInstance;
|
||||
},
|
||||
|
||||
getTasksAfter: function(taskName) {
|
||||
let ret = [];
|
||||
for (let i = 0; i < this._tasks.length; i++) {
|
||||
let taskConstructor = this._tasks[i];
|
||||
let taskDef = Params.parse(taskConstructor.prototype.TaskDef, DefaultTaskDef);
|
||||
let after = taskDef.TaskAfter;
|
||||
for (let j = 0; j < after.length; j++) {
|
||||
let a = after[j];
|
||||
if (a == taskName) {
|
||||
ret.push(taskDef.TaskName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
});
|
||||
|
||||
const TaskData = new Lang.Class({
|
||||
Name: 'TaskData',
|
||||
|
||||
_init: function(taskDef, parameters) {
|
||||
this.name = taskDef.TaskName;
|
||||
this.taskDef = taskDef;
|
||||
this.parameters = parameters;
|
||||
},
|
||||
});
|
||||
|
||||
const TaskMaster = new Lang.Class({
|
||||
Name: 'TaskMaster',
|
||||
|
||||
_init: function(workdir, params) {
|
||||
params = Params.parse(params, { onEmpty: null,
|
||||
processAfter: true,
|
||||
skip: [] });
|
||||
|
||||
this.workdir = workdir;
|
||||
this.tasksPath = workdir.get_child('tasks');
|
||||
GSystem.file_ensure_directory(this.tasksPath, true, null);
|
||||
|
||||
this.completedTasksPath = workdir.get_child('results/tasks');
|
||||
GSystem.file_ensure_directory(this.completedTasksPath, true, null);
|
||||
|
||||
this._processAfter = params.processAfter;
|
||||
this._skipTasks = {};
|
||||
for (let i = 0; i < params.skip.length; i++)
|
||||
this._skipTasks[params.skip[i]] = true;
|
||||
this.maxConcurrent = GLib.get_num_processors();
|
||||
this._onEmpty = params.onEmpty;
|
||||
this.cancellable = null;
|
||||
this._idleRecalculateId = 0;
|
||||
this._executing = [];
|
||||
this._pendingTasksList = [];
|
||||
this._seenTasks = {};
|
||||
this._taskErrors = {};
|
||||
this._caughtError = false;
|
||||
|
||||
this._taskset = TaskSet.prototype.getInstance();
|
||||
|
||||
// string -> [ lastExecutedSecs, taskData ]
|
||||
this._scheduledTaskTimeouts = {};
|
||||
},
|
||||
|
||||
_setTaskBuildPath: function(taskName, buildPath) {
|
||||
let taskLink = this.tasksPath.get_child(taskName);
|
||||
BuildUtil.atomicSymlinkSwap(taskLink, buildPath, this.cancellable);
|
||||
},
|
||||
|
||||
_pushTaskDataImmediate: function(taskData) {
|
||||
this._pendingTasksList.push(taskData);
|
||||
this._queueRecalculate();
|
||||
},
|
||||
|
||||
pushTask: function(buildPath, taskName, parameters) {
|
||||
this._setTaskBuildPath(taskName, buildPath);
|
||||
this._pushTask(taskName, parameters);
|
||||
},
|
||||
|
||||
_pushTask: function(name, parameters) {
|
||||
let taskDef = this._taskset.getTaskDef(name);
|
||||
let taskData = new TaskData(taskDef, parameters);
|
||||
if (!this._isTaskPending(name)) {
|
||||
let scheduleMinSecs = taskDef.TaskScheduleMinSecs;
|
||||
if (scheduleMinSecs > 0) {
|
||||
let info = this._scheduledTaskTimeouts[name];
|
||||
if (!info) {
|
||||
info = [ 0, null ];
|
||||
this._scheduledTaskTimeouts[name] = info;
|
||||
}
|
||||
let lastExecutedSecs = info[0];
|
||||
let pendingExecData = info[1];
|
||||
let currentTime = GLib.get_monotonic_time() / GLib.USEC_PER_SEC;
|
||||
if (pendingExecData != null) {
|
||||
// Nothing, already scheduled
|
||||
let delta = (lastExecutedSecs + scheduleMinSecs) - currentTime;
|
||||
print("Already scheduled task " + name + " remaining=" + delta);
|
||||
} else if (lastExecutedSecs == 0) {
|
||||
print("Scheduled task " + name + " executing immediately");
|
||||
this._pushTaskDataImmediate(taskData);
|
||||
info[0] = currentTime;
|
||||
} else {
|
||||
let delta = (lastExecutedSecs + scheduleMinSecs) - currentTime;
|
||||
print("Scheduled task " + name + " delta=" + delta);
|
||||
let timeoutId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT,
|
||||
Math.max(delta, 0),
|
||||
Lang.bind(this, this._executeScheduledTask, name));
|
||||
info[0] = currentTime;
|
||||
info[1] = taskData;
|
||||
}
|
||||
} else {
|
||||
this._pushTaskDataImmediate(taskData);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_executeScheduledTask: function(name) {
|
||||
print("Executing scheduled task " + name);
|
||||
let currentTime = GLib.get_monotonic_time() / GLib.USEC_PER_SEC;
|
||||
let info = this._scheduledTaskTimeouts[name];
|
||||
let taskData = info[1];
|
||||
info[0] = currentTime;
|
||||
info[1] = null;
|
||||
this._pushTaskDataImmediate(taskData);
|
||||
},
|
||||
|
||||
_isTaskPending: function(taskName) {
|
||||
for (let i = 0; i < this._pendingTasksList.length; i++) {
|
||||
let pending = this._pendingTasksList[i];
|
||||
if (pending.name == taskName)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
isTaskQueued: function(taskName) {
|
||||
return this._isTaskPending(taskName) || this.isTaskExecuting(taskName);
|
||||
},
|
||||
|
||||
isTaskExecuting: function(taskName) {
|
||||
for (let i = 0; i < this._executing.length; i++) {
|
||||
let executingRunner = this._executing[i];
|
||||
if (executingRunner.taskData.name == taskName)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
getTaskState: function() {
|
||||
let r = [];
|
||||
for (let i = 0; i < this._pendingTasksList.length; i++) {
|
||||
r.push({running: false, task: this._pendingTasksList[i] });
|
||||
}
|
||||
for (let i = 0; i < this._executing.length; i++) {
|
||||
r.push({running: true, task: this._executing[i] });
|
||||
}
|
||||
return r;
|
||||
},
|
||||
|
||||
_queueRecalculate: function() {
|
||||
if (this._idleRecalculateId > 0)
|
||||
return;
|
||||
this._idleRecalculateId = GLib.idle_add(GLib.PRIORITY_DEFAULT, Lang.bind(this, this._recalculate));
|
||||
},
|
||||
|
||||
_recalculate: function() {
|
||||
this._idleRecalculateId = 0;
|
||||
|
||||
if (this._executing.length == 0 &&
|
||||
this._pendingTasksList.length == 0) {
|
||||
if (this._onEmpty)
|
||||
this._onEmpty();
|
||||
return;
|
||||
} else if (this._pendingTasksList.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let notExecuting = [];
|
||||
let executing = [];
|
||||
for (let i = 0; i < this._pendingTasksList.length; i++) {
|
||||
let pending = this._pendingTasksList[i];
|
||||
if (this.isTaskExecuting(pending.name))
|
||||
executing.push(pending);
|
||||
else
|
||||
notExecuting.push(pending);
|
||||
}
|
||||
|
||||
this._pendingTasksList = notExecuting.concat(executing);
|
||||
|
||||
this._reschedule();
|
||||
},
|
||||
|
||||
_onComplete: function(success, error, runner) {
|
||||
let taskName = runner.taskData.name;
|
||||
let idx = -1;
|
||||
for (let i = 0; i < this._executing.length; i++) {
|
||||
let executingRunner = this._executing[i];
|
||||
if (executingRunner !== runner)
|
||||
continue;
|
||||
idx = i;
|
||||
break;
|
||||
}
|
||||
if (idx == -1)
|
||||
throw new Error("TaskMaster: Internal error - Failed to find completed task:" + taskName);
|
||||
this._executing.splice(idx, 1);
|
||||
|
||||
let link = this.completedTasksPath.get_child(taskName);
|
||||
BuildUtil.atomicSymlinkSwap(link, runner.buildPath, this.cancellable);
|
||||
|
||||
if (success && runner.changed) {
|
||||
let taskDef = runner.taskData.taskDef;
|
||||
let after = this._taskset.getTasksAfter(taskName);
|
||||
for (let i = 0; i < after.length; i++) {
|
||||
let afterTaskName = after[i];
|
||||
this._setTaskBuildPath(afterTaskName, runner.buildPath);
|
||||
if (!this._skipTasks[afterTaskName] && this._processAfter)
|
||||
this._pushTask(afterTaskName, {});
|
||||
}
|
||||
}
|
||||
this.emit('task-complete', runner, success, error);
|
||||
this._queueRecalculate();
|
||||
},
|
||||
|
||||
_reschedule: function() {
|
||||
while (this._executing.length < this.maxConcurrent &&
|
||||
this._pendingTasksList.length > 0 &&
|
||||
!this.isTaskExecuting(this._pendingTasksList[0].name)) {
|
||||
let task = this._pendingTasksList.shift();
|
||||
let runner = new TaskRunner(this, task, Lang.bind(this, function(success, error) {
|
||||
this._onComplete(success, error, runner);
|
||||
}));
|
||||
runner.executeInSubprocess(this.cancellable);
|
||||
this._executing.push(runner);
|
||||
this.emit('task-executing', runner);
|
||||
}
|
||||
}
|
||||
});
|
||||
Signals.addSignalMethods(TaskMaster.prototype);
|
||||
|
||||
const Task = new Lang.Class({
|
||||
Name: 'Task',
|
||||
|
||||
DefaultParameters: {},
|
||||
|
||||
_init: function(parameters) {
|
||||
this.name = this.TaskName;
|
||||
this.parameters = Params.parse(parameters, this.DefaultParameters);
|
||||
|
||||
this.workdir = Gio.File.new_for_path(GLib.getenv('_OSTBUILD_WORKDIR'));
|
||||
BuildUtil.checkIsWorkDirectory(this.workdir);
|
||||
this.builddir = Gio.File.new_for_path(GLib.getenv('_OSTBUILD_BUILDDIR'));
|
||||
|
||||
let relpath = this.workdir.get_relative_path(this.builddir);
|
||||
if (relpath == 'local') {
|
||||
this._buildName = 'local';
|
||||
} else if (relpath.indexOf('builds/') == 0) {
|
||||
relpath = relpath.substring(7);
|
||||
this._buildName = VersionedDir.relpathToVersion(relpath);
|
||||
} else {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
this.mirrordir = this.workdir.get_child('src');
|
||||
GSystem.file_ensure_directory(this.mirrordir, true, null);
|
||||
this.cachedir = this.workdir.resolve_relative_path('cache/raw');
|
||||
GSystem.file_ensure_directory(this.cachedir, true, null);
|
||||
|
||||
this.libdir = Gio.File.new_for_path(GLib.getenv('OSTBUILD_LIBDIR'));
|
||||
this.repo = this.workdir.get_child('repo');
|
||||
this.ostreeRepo = new OSTree.Repo({ path: this.repo });
|
||||
if (!this.ostreeRepo.get_path().query_exists(null))
|
||||
this.ostreeRepo.create(OSTree.RepoMode.ARCHIVE_Z2, null);
|
||||
this.ostreeRepo.open(null);
|
||||
},
|
||||
|
||||
execute: function(cancellable) {
|
||||
throw new Error("Not implemented");
|
||||
},
|
||||
});
|
||||
|
||||
const TaskRunner = new Lang.Class({
|
||||
Name: 'TaskRunner',
|
||||
|
||||
_init: function(taskmaster, taskData, onComplete) {
|
||||
this.taskmaster = taskmaster;
|
||||
this.taskData = taskData;
|
||||
this.onComplete = onComplete;
|
||||
this.name = taskData.name;
|
||||
|
||||
this.workdir = taskmaster.workdir;
|
||||
BuildUtil.checkIsWorkDirectory(this.workdir);
|
||||
},
|
||||
|
||||
executeInSubprocess: function(cancellable) {
|
||||
this._cancellable = cancellable;
|
||||
|
||||
this._startTimeMillis = GLib.get_monotonic_time() / 1000;
|
||||
|
||||
// To prevent tasks from stomping on each other's toes, we put the task
|
||||
// cwd in its own task dir. If a task has any results it wants to pass
|
||||
// on between builds, it needs to write to _OSTBUILD_BUILDDIR.
|
||||
let buildPath = this.taskmaster.tasksPath.resolve_relative_path(this.name);
|
||||
this.buildPath = GSystem.file_realpath(buildPath);
|
||||
|
||||
this.buildName = this.workdir.get_relative_path(this.buildPath);
|
||||
this.taskCwd = this.buildPath.get_child(this.name);
|
||||
GSystem.file_ensure_directory(this.taskCwd, false, cancellable);
|
||||
|
||||
let baseArgv = ['ostbuild', 'run-task', this.name, JSON.stringify(this.taskData.parameters)];
|
||||
let context = new GSystem.SubprocessContext({ argv: baseArgv });
|
||||
context.set_cwd(this.taskCwd.get_path());
|
||||
let childEnv = GLib.get_environ();
|
||||
childEnv.push('_OSTBUILD_BUILDDIR=' + this.buildPath.get_path());
|
||||
childEnv.push('_OSTBUILD_WORKDIR=' + this.workdir.get_path());
|
||||
context.set_environment(childEnv);
|
||||
if (this.taskData.taskDef.PreserveStdout) {
|
||||
let outPath = this.taskCwd.get_child('output.txt');
|
||||
GSystem.shutil_rm_rf(outPath, cancellable);
|
||||
context.set_stdout_file_path(outPath.get_path());
|
||||
context.set_stderr_disposition(GSystem.SubprocessStreamDisposition.STDERR_MERGE);
|
||||
} else {
|
||||
context.set_stdout_disposition(GSystem.SubprocessStreamDisposition.NULL);
|
||||
let errPath = this.taskCwd.get_child('errors.txt');
|
||||
context.set_stderr_file_path(errPath.get_path());
|
||||
}
|
||||
this._proc = new GSystem.Subprocess({ context: context });
|
||||
this._proc.init(cancellable);
|
||||
|
||||
let targetPath = this.workdir.get_relative_path(this.taskCwd);
|
||||
|
||||
let meta = { taskMetaVersion: 0,
|
||||
buildPath: this.buildName,
|
||||
complete: false,
|
||||
path: targetPath };
|
||||
JsonUtil.writeJsonFileAtomic(this.taskCwd.get_child('meta.json'), meta, cancellable);
|
||||
|
||||
this._proc.wait(cancellable, Lang.bind(this, this._onChildExited));
|
||||
},
|
||||
|
||||
_onChildExited: function(proc, result) {
|
||||
let cancellable = this._cancellable;
|
||||
let [success, errmsg] = ProcUtil.asyncWaitCheckFinish(proc, result);
|
||||
let target;
|
||||
|
||||
this.changed = true;
|
||||
let modifiedPath = this.taskCwd.get_child('modified.json');
|
||||
if (modifiedPath.query_exists(null)) {
|
||||
let data = JsonUtil.loadJson(modifiedPath, null);
|
||||
this.changed = data['modified'];
|
||||
}
|
||||
|
||||
this.onComplete(success, errmsg);
|
||||
|
||||
if (!this.changed)
|
||||
return;
|
||||
|
||||
let elapsedMillis = GLib.get_monotonic_time() / 1000 - this._startTimeMillis;
|
||||
let targetPath = this.workdir.get_relative_path(this.taskCwd);
|
||||
|
||||
let meta = { taskMetaVersion: 0,
|
||||
buildPath: this.buildName,
|
||||
complete: true,
|
||||
success: success,
|
||||
errmsg: errmsg,
|
||||
elapsedMillis: elapsedMillis,
|
||||
path: targetPath };
|
||||
let statusTxtPath = this.taskCwd.get_child('status.txt');
|
||||
if (statusTxtPath.query_exists(null)) {
|
||||
let contents = GSystem.file_load_contents_utf8(statusTxtPath, cancellable);
|
||||
meta['status'] = contents.replace(/[ \n]+$/, '');
|
||||
}
|
||||
|
||||
JsonUtil.writeJsonFileAtomic(this.taskCwd.get_child('meta.json'), meta, cancellable);
|
||||
}
|
||||
});
|
140
src/autobuilder/js/tasks/task-bdiff.js
Normal file
140
src/autobuilder/js/tasks/task-bdiff.js
Normal file
@ -0,0 +1,140 @@
|
||||
// Copyright (C) 2011,2012,2013 Colin Walters <walters@verbum.org>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the
|
||||
// Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
// Boston, MA 02111-1307, USA.
|
||||
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Lang = imports.lang;
|
||||
const Format = imports.format;
|
||||
|
||||
const GSystem = imports.gi.GSystem;
|
||||
|
||||
const Builtin = imports.builtin;
|
||||
const Task = imports.task;
|
||||
const ProcUtil = imports.procutil;
|
||||
const StreamUtil = imports.streamutil;
|
||||
const JsonUtil = imports.jsonutil;
|
||||
const Snapshot = imports.snapshot;
|
||||
const BuildUtil = imports.buildutil;
|
||||
const Vcs = imports.vcs;
|
||||
const ArgParse = imports.argparse;
|
||||
|
||||
const TaskBdiff = new Lang.Class({
|
||||
Name: "TaskBdiff",
|
||||
Extends: Task.Task,
|
||||
|
||||
TaskDef: {
|
||||
TaskName: "bdiff",
|
||||
TaskAfter: ['build'],
|
||||
},
|
||||
|
||||
_gitLogToJson: function(repoDir, specification) {
|
||||
let log = ProcUtil.runSyncGetOutputLines(['git', 'log', '--format=email', specification],
|
||||
null,
|
||||
{ cwd: repoDir, logInitiation: true });
|
||||
let r = [];
|
||||
if (log.length == 0)
|
||||
return r;
|
||||
let currentItem = null;
|
||||
let parsingHeaders = false;
|
||||
let fromRegex = /^From ([0-9a-f]{40}) /;
|
||||
for (let i = 0; i < log.length; i++) {
|
||||
let line = log[i];
|
||||
let match = fromRegex.exec(line);
|
||||
if (match) {
|
||||
if (currentItem !== null) {
|
||||
r.push(currentItem);
|
||||
}
|
||||
currentItem = {'Checksum': match[1]};
|
||||
parsingHeaders = true;
|
||||
} else if (parsingHeaders) {
|
||||
if (line.length == 0) {
|
||||
parsingHeaders = false;
|
||||
} else {
|
||||
let idx = line.indexOf(':');
|
||||
let k = line.substr(0, idx);
|
||||
let v = line.substr(idx+1);
|
||||
currentItem[k] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (currentItem !== null) {
|
||||
r.push(currentItem);
|
||||
}
|
||||
return r;
|
||||
},
|
||||
|
||||
_diffstat: function(repoDir, specification) {
|
||||
return ProcUtil.runSyncGetOutputUTF8(['git', 'diff', '--stat', specification], null,
|
||||
{ cwd: repoDir });
|
||||
},
|
||||
|
||||
execute: function(cancellable) {
|
||||
let latestSnapshotPath = this.builddir.get_child('snapshot.json');
|
||||
let previousSnapshotPath = this.builddir.get_child('last-build/snapshot.json');
|
||||
if (!previousSnapshotPath.query_exists(cancellable))
|
||||
return;
|
||||
|
||||
let latestBuildSnapshot = Snapshot.fromFile(latestSnapshotPath, cancellable);
|
||||
let previousBuildSnapshot = Snapshot.fromFile(previousSnapshotPath, cancellable);
|
||||
|
||||
let added = [];
|
||||
let modified = [];
|
||||
let removed = [];
|
||||
|
||||
let result = { added: added,
|
||||
modified: modified,
|
||||
removed: removed };
|
||||
|
||||
let modifiedNames = [];
|
||||
|
||||
let latestComponentMap = latestBuildSnapshot.getComponentMap();
|
||||
let previousComponentMap = previousBuildSnapshot.getComponentMap();
|
||||
for (let componentName in latestComponentMap) {
|
||||
let componentA = latestBuildSnapshot.getComponent(componentName);
|
||||
let componentB = previousBuildSnapshot.getComponent(componentName, true);
|
||||
|
||||
if (componentB === null)
|
||||
added.push(componentName);
|
||||
else if (componentB.revision != componentA.revision)
|
||||
modifiedNames.push(componentName);
|
||||
}
|
||||
for (let componentName in previousComponentMap) {
|
||||
let componentA = latestBuildSnapshot.getComponent(componentName, true);
|
||||
|
||||
if (componentA === null)
|
||||
removed.push(componentName);
|
||||
}
|
||||
|
||||
for (let i = 0; i < modifiedNames.length; i++) {
|
||||
let componentName = modifiedNames[i];
|
||||
let latestComponent = latestBuildSnapshot.getComponent(componentName);
|
||||
let previousComponent = previousBuildSnapshot.getComponent(componentName);
|
||||
let latestRevision = latestComponent.revision;
|
||||
let previousRevision = previousComponent.revision;
|
||||
let mirrordir = Vcs.ensureVcsMirror(this.mirrordir, previousComponent, cancellable);
|
||||
|
||||
let gitlog = this._gitLogToJson(mirrordir, previousRevision + '...' + latestRevision);
|
||||
let diffstat = this._diffstat(mirrordir, previousRevision + '..' + latestRevision);
|
||||
modified.push({ previous: previousComponent,
|
||||
latest: latestComponent,
|
||||
gitlog: gitlog,
|
||||
diffstat: diffstat });
|
||||
}
|
||||
|
||||
JsonUtil.writeJsonFileAtomic(this.builddir.get_child('bdiff.json'), result, cancellable);
|
||||
}
|
||||
});
|
1532
src/autobuilder/js/tasks/task-build.js
Normal file
1532
src/autobuilder/js/tasks/task-build.js
Normal file
File diff suppressed because it is too large
Load Diff
153
src/autobuilder/js/tasks/task-builddisks.js
Normal file
153
src/autobuilder/js/tasks/task-builddisks.js
Normal file
@ -0,0 +1,153 @@
|
||||
// -*- indent-tabs-mode: nil; tab-width: 2; -*-
|
||||
// Copyright (C) 2013 Colin Walters <walters@verbum.org>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the
|
||||
// Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
// Boston, MA 02111-1307, USA.
|
||||
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Lang = imports.lang;
|
||||
const Format = imports.format;
|
||||
|
||||
const GSystem = imports.gi.GSystem;
|
||||
|
||||
const Builtin = imports.builtin;
|
||||
const ArgParse = imports.argparse;
|
||||
const Task = imports.task;
|
||||
const ProcUtil = imports.procutil;
|
||||
const BuildUtil = imports.buildutil;
|
||||
const LibQA = imports.libqa;
|
||||
const JsonUtil = imports.jsonutil;
|
||||
const JSUtil = imports.jsutil;
|
||||
const GuestFish = imports.guestfish;
|
||||
|
||||
const IMAGE_RETAIN_COUNT = 2;
|
||||
|
||||
const TaskBuildDisks = new Lang.Class({
|
||||
Name: 'TaskBuildDisks',
|
||||
Extends: Task.Task,
|
||||
|
||||
TaskDef: {
|
||||
TaskName: "builddisks",
|
||||
TaskAfter: ['build'],
|
||||
},
|
||||
|
||||
_imageSubdir: 'images',
|
||||
_inheritPreviousDisk: true,
|
||||
_onlyTreeSuffixes: ['-runtime', '-devel-debug'],
|
||||
|
||||
execute: function(cancellable) {
|
||||
let isLocal = this._buildName == 'local';
|
||||
let baseImageDir = this.workdir.resolve_relative_path(this._imageSubdir);
|
||||
GSystem.file_ensure_directory(baseImageDir, true, cancellable);
|
||||
|
||||
let currentImageLink = baseImageDir.get_child('current');
|
||||
|
||||
let targetImageDir = baseImageDir.get_child(this._buildName);
|
||||
if (!isLocal && targetImageDir.query_exists(null)) {
|
||||
print("Already created " + targetImageDir.get_path());
|
||||
return;
|
||||
}
|
||||
|
||||
print("Creating image for buildName=" + this._buildName);
|
||||
|
||||
let buildDataPath = this.builddir.get_child('build.json');
|
||||
let buildData = JsonUtil.loadJson(buildDataPath, cancellable);
|
||||
|
||||
let workImageDir = Gio.File.new_for_path('images');
|
||||
GSystem.file_ensure_directory(workImageDir, true, cancellable);
|
||||
|
||||
let destPath = workImageDir.get_child('build-' + this._buildName + '.json');
|
||||
GSystem.shutil_rm_rf(destPath, cancellable);
|
||||
GSystem.file_linkcopy(buildDataPath, destPath, Gio.FileCopyFlags.ALL_METADATA, cancellable);
|
||||
|
||||
let targets = buildData['targets'];
|
||||
|
||||
let osname = buildData['snapshot']['osname'];
|
||||
let originRepoUrl = buildData['snapshot']['repo'];
|
||||
|
||||
for (let targetName in targets) {
|
||||
let matched = false;
|
||||
for (let i = 0; i < this._onlyTreeSuffixes.length; i++) {
|
||||
if (JSUtil.stringEndswith(targetName, this._onlyTreeSuffixes[i])) {
|
||||
matched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!matched)
|
||||
continue;
|
||||
let targetRevision = buildData['targets'][targetName];
|
||||
let squashedName = osname + '-' + targetName.substr(targetName.lastIndexOf('/') + 1);
|
||||
let diskName = squashedName + '.qcow2';
|
||||
let diskPath = workImageDir.get_child(diskName);
|
||||
let prevPath = currentImageLink.get_child(diskName);
|
||||
let prevExists = prevPath.query_exists(null);
|
||||
GSystem.shutil_rm_rf(diskPath, cancellable);
|
||||
let doCloneDisk = this._inheritPreviousDisk && prevExists;
|
||||
if (doCloneDisk) {
|
||||
LibQA.copyDisk(prevPath, diskPath, cancellable);
|
||||
} else {
|
||||
LibQA.createDisk(diskPath, cancellable);
|
||||
}
|
||||
let mntdir = Gio.File.new_for_path('mnt-' + squashedName);
|
||||
GSystem.file_ensure_directory(mntdir, true, cancellable);
|
||||
let gfmnt = new GuestFish.GuestMount(diskPath, { partitionOpts: LibQA.DEFAULT_GF_PARTITION_OPTS,
|
||||
readWrite: true });
|
||||
gfmnt.mount(mntdir, cancellable);
|
||||
try {
|
||||
LibQA.pullDeploy(mntdir, this.repo, osname, targetName, targetRevision, originRepoUrl,
|
||||
cancellable);
|
||||
} finally {
|
||||
gfmnt.umount(cancellable);
|
||||
}
|
||||
// Assume previous disks have successfully installed a bootloader
|
||||
if (!doCloneDisk) {
|
||||
LibQA.bootloaderInstall(diskPath, Gio.File.new_for_path('.'), osname, cancellable);
|
||||
print("Bootloader installation complete");
|
||||
}
|
||||
|
||||
this._postDiskCreation(squashedName, diskPath, cancellable);
|
||||
print("post-disk creation complete");
|
||||
}
|
||||
|
||||
if (isLocal) {
|
||||
let localImageDir = baseImageDir.get_child('local');
|
||||
GSystem.shutil_rm_rf(localImageDir, cancellable);
|
||||
GSystem.file_rename(workImageDir, localImageDir, cancellable);
|
||||
return;
|
||||
}
|
||||
|
||||
GSystem.file_rename(workImageDir, targetImageDir, cancellable);
|
||||
|
||||
let currentInfo = null;
|
||||
let oldCurrent = null;
|
||||
try {
|
||||
currentInfo = currentImageLink.query_info('standard::symlink-target', Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, cancellable);
|
||||
} catch (e) {
|
||||
if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND))
|
||||
throw e;
|
||||
}
|
||||
if (currentInfo) {
|
||||
oldCurrent = currentImageLink.get_parent().resolve_relative_path(currentInfo.get_symlink_target());
|
||||
}
|
||||
BuildUtil.atomicSymlinkSwap(baseImageDir.get_child('current'), targetImageDir, cancellable);
|
||||
if (!isLocal && oldCurrent)
|
||||
GSystem.shutil_rm_rf(oldCurrent, cancellable);
|
||||
},
|
||||
|
||||
_postDiskCreation: function(squashedName, diskPath, cancellable) {
|
||||
// Nothing, this is used by zdisks
|
||||
}
|
||||
});
|
99
src/autobuilder/js/tasks/task-resolve.js
Normal file
99
src/autobuilder/js/tasks/task-resolve.js
Normal file
@ -0,0 +1,99 @@
|
||||
// Copyright (C) 2011 Colin Walters <walters@verbum.org>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the
|
||||
// Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
// Boston, MA 02111-1307, USA.
|
||||
|
||||
const Gio = imports.gi.Gio;
|
||||
const Lang = imports.lang;
|
||||
const GSystem = imports.gi.GSystem;
|
||||
|
||||
const Task = imports.task;
|
||||
const ProcUtil = imports.procutil;
|
||||
const JsonUtil = imports.jsonutil;
|
||||
const Snapshot = imports.snapshot;
|
||||
const Vcs = imports.vcs;
|
||||
|
||||
const TaskResolve = new Lang.Class({
|
||||
Name: "TaskResolve",
|
||||
Extends: Task.Task,
|
||||
|
||||
TaskDef: {
|
||||
TaskName: "resolve",
|
||||
},
|
||||
|
||||
DefaultParameters: {fetchAll: false,
|
||||
fetchSrcUrls: [],
|
||||
fetchComponents: [],
|
||||
timeoutSec: 10},
|
||||
|
||||
_writeSnapshotToBuild: function(cancellable) {
|
||||
let data = this._snapshot.data;
|
||||
let buf = JsonUtil.serializeJson(data);
|
||||
|
||||
let oldSnapshot = this.builddir.get_child('last-build/snapshot.json');
|
||||
if (oldSnapshot.query_exists(cancellable)) {
|
||||
let oldBytes = GSystem.file_map_readonly(oldSnapshot, cancellable);
|
||||
let oldCsum = GLib.compute_checksum_for_bytes(GLib.ChecksumType.SHA256, oldBytes);
|
||||
let newCsum = GLib.compute_checksum_for_string(GLib.ChecksumType.SHA256, buf, -1);
|
||||
if (oldCsum == newCsum)
|
||||
return false;
|
||||
}
|
||||
|
||||
let snapshot = this.builddir.get_child('snapshot.json');
|
||||
JsonUtil.writeJsonFileAtomic(snapshot, data, cancellable);
|
||||
return true;
|
||||
},
|
||||
|
||||
execute: function(cancellable) {
|
||||
let manifestPath = this.workdir.get_child('manifest.json');
|
||||
this._snapshot = Snapshot.fromFile(manifestPath, cancellable, { prepareResolve: true });
|
||||
|
||||
let componentsToFetch = this.parameters.fetchComponents.slice();
|
||||
let srcUrls = this.parameters.fetchSrcUrls;
|
||||
for (let i = 0; i < srcUrls; i++) {
|
||||
let matches = snapshot.getMatchingSrc(srcUrls[i]);
|
||||
componentsToFetch.push.apply(matches);
|
||||
}
|
||||
|
||||
let gitMirrorArgs = ['ostbuild', 'git-mirror', '--timeout-sec=' + this.parameters.timeoutSec,
|
||||
'--workdir=' + this.workdir.get_path(),
|
||||
'--manifest=' + manifestPath.get_path()];
|
||||
if (this.parameters.fetchAll || componentsToFetch.length > 0) {
|
||||
gitMirrorArgs.push('--fetch');
|
||||
gitMirrorArgs.push('-k');
|
||||
gitMirrorArgs.push.apply(gitMirrorArgs, componentsToFetch);
|
||||
}
|
||||
ProcUtil.runSync(gitMirrorArgs, cancellable, { logInitiation: true });
|
||||
|
||||
let componentNames = this._snapshot.getAllComponentNames();
|
||||
for (let i = 0; i < componentNames.length; i++) {
|
||||
let component = this._snapshot.getComponent(componentNames[i]);
|
||||
let tagOrBranch = component['tag'] || component['branch'];
|
||||
let mirrordir = Vcs.ensureVcsMirror(this.mirrordir, component, cancellable);
|
||||
let revision = Vcs.describeVersion(mirrordir, tagOrBranch);
|
||||
component['revision'] = revision;
|
||||
}
|
||||
|
||||
let modified = this._writeSnapshotToBuild(cancellable);
|
||||
if (modified) {
|
||||
print("New source snapshot");
|
||||
} else {
|
||||
print("Source snapshot unchanged");
|
||||
}
|
||||
|
||||
let modifiedPath = Gio.File.new_for_path('modified.json');
|
||||
JsonUtil.writeJsonFileAtomic(modifiedPath, { modified: modified }, cancellable);
|
||||
}
|
||||
});
|
52
src/autobuilder/js/tasks/task-smoketest.js
Normal file
52
src/autobuilder/js/tasks/task-smoketest.js
Normal file
@ -0,0 +1,52 @@
|
||||
// -*- indent-tabs-mode: nil; tab-width: 2; -*-
|
||||
// Copyright (C) 2013 Colin Walters <walters@verbum.org>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the
|
||||
// Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
// Boston, MA 02111-1307, USA.
|
||||
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Lang = imports.lang;
|
||||
const Format = imports.format;
|
||||
|
||||
const GSystem = imports.gi.GSystem;
|
||||
|
||||
const Builtin = imports.builtin;
|
||||
const ArgParse = imports.argparse;
|
||||
const ProcUtil = imports.procutil;
|
||||
const Task = imports.task;
|
||||
const TestBase = imports.tasks.testbase;
|
||||
const LibQA = imports.libqa;
|
||||
const JSUtil = imports.jsutil;
|
||||
const JSONUtil = imports.jsonutil;
|
||||
|
||||
const TaskSmoketest = new Lang.Class({
|
||||
Name: 'TaskSmoketest',
|
||||
Extends: TestBase.TestBase,
|
||||
|
||||
TaskDef: {
|
||||
TaskName: "smoketest",
|
||||
TaskAfter: ['builddisks'],
|
||||
},
|
||||
|
||||
RequiredMessageIDs: ["0ce153587afa4095832d233c17a88001" // gnome-session startup ok
|
||||
],
|
||||
|
||||
FailedMessageIDs: ["fc2e22bc6ee647b6b90729ab34a250b1", // coredump
|
||||
"10dd2dc188b54a5e98970f56499d1f73" // gnome-session required component failed
|
||||
],
|
||||
|
||||
CompletedTag: 'smoketested'
|
||||
});
|
49
src/autobuilder/js/tasks/task-zdisks.js
Normal file
49
src/autobuilder/js/tasks/task-zdisks.js
Normal file
@ -0,0 +1,49 @@
|
||||
// -*- indent-tabs-mode: nil; tab-width: 2; -*-
|
||||
// Copyright (C) 2013 Colin Walters <walters@verbum.org>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the
|
||||
// Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
// Boston, MA 02111-1307, USA.
|
||||
|
||||
const Gio = imports.gi.Gio;
|
||||
const Lang = imports.lang;
|
||||
|
||||
const BuildDisks = imports.tasks['task-builddisks'];
|
||||
|
||||
const TaskZDisks = new Lang.Class({
|
||||
Name: 'TaskZDisks',
|
||||
Extends: BuildDisks.TaskBuildDisks,
|
||||
|
||||
TaskDef: {
|
||||
TaskName: "zdisks",
|
||||
TaskAfter: ['smoketest'],
|
||||
TaskScheduleMinSecs: 3*60*60, // Only do this every 3 hours
|
||||
},
|
||||
|
||||
_imageSubdir: 'images/z',
|
||||
_inheritPreviousDisk: false,
|
||||
_onlyTreeSuffixes: ['-runtime', '-devel-debug'],
|
||||
|
||||
_postDiskCreation: function(squashedName, diskPath, cancellable) {
|
||||
let parent = diskPath.get_parent();
|
||||
let outPath = parent.get_child(squashedName + '-' + this._buildName + '.qcow2.gz');
|
||||
let outStream = outPath.create(Gio.FileCreateFlags.REPLACE_DESTINATION, cancellable);
|
||||
let compressor = Gio.ZlibCompressor.new(Gio.ZlibCompressorFormat.GZIP, 7);
|
||||
let outConverter = Gio.ConverterOutputStream.new(outStream, compressor);
|
||||
let inStream = diskPath.read(cancellable);
|
||||
outConverter.splice(inStream, Gio.OutputStreamSpliceFlags.CLOSE_SOURCE |
|
||||
Gio.OutputStreamSpliceFlags.CLOSE_TARGET, cancellable);
|
||||
diskPath.delete(cancellable);
|
||||
}
|
||||
});
|
646
src/autobuilder/js/tasks/testbase.js
Normal file
646
src/autobuilder/js/tasks/testbase.js
Normal file
@ -0,0 +1,646 @@
|
||||
// -*- indent-tabs-mode: nil; tab-width: 2; -*-
|
||||
// Copyright (C) 2013 Colin Walters <walters@verbum.org>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the
|
||||
// Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
// Boston, MA 02111-1307, USA.
|
||||
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Lang = imports.lang;
|
||||
const Format = imports.format;
|
||||
|
||||
const GSystem = imports.gi.GSystem;
|
||||
|
||||
const Builtin = imports.builtin;
|
||||
const ArgParse = imports.argparse;
|
||||
const ProcUtil = imports.procutil;
|
||||
const Params = imports.params;
|
||||
const Task = imports.task;
|
||||
const LibQA = imports.libqa;
|
||||
const JSUtil = imports.jsutil;
|
||||
const JSONUtil = imports.jsonutil;
|
||||
|
||||
const TIMEOUT_SECONDS = 10 * 60;
|
||||
|
||||
const CommandSocketIface = '<node> \
|
||||
<interface name="org.gnome.Continuous.Command"> \
|
||||
<method name="AsyncMessage"> \
|
||||
<arg name="msgId" direction="in" type="s"/> \
|
||||
<arg name="value" direction="in" type="v"/> \
|
||||
</method> \
|
||||
<method name="Screenshot"> \
|
||||
<arg name="name" direction="in" type="s"/> \
|
||||
</method> \
|
||||
<signal name="ScreenshotComplete"> \
|
||||
<arg name="name" direction="out" type="s"/> \
|
||||
</signal> \
|
||||
</interface> \
|
||||
</node>';
|
||||
|
||||
const CommandSocketProxy = new Lang.Class({
|
||||
Name: 'CommandSocketProxy',
|
||||
|
||||
_init: function(connection,
|
||||
asyncMessageHandler,
|
||||
screenshotHandler) {
|
||||
this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(CommandSocketIface, this);
|
||||
this._dbusImpl.export(connection, '/org/gnome/Continuous/Command');
|
||||
this._asyncMessageHandler = asyncMessageHandler;
|
||||
this._screenshotHandler = screenshotHandler;
|
||||
},
|
||||
|
||||
Screenshot: function(name) {
|
||||
this._screenshotHandler(name);
|
||||
},
|
||||
|
||||
AsyncMessage: function(msgId, value) {
|
||||
this._asyncMessageHandler(msgId, value);
|
||||
}
|
||||
});
|
||||
|
||||
const TestOneDisk = new Lang.Class({
|
||||
Name: 'TestOneDisk',
|
||||
|
||||
_init: function(parentTask, testRequiredMessageIds, testFailedMessageIds, testStatusMessageId) {
|
||||
this._parentTask = parentTask;
|
||||
this._testRequiredMessageIds = testRequiredMessageIds;
|
||||
this._testFailedMessageIds = testFailedMessageIds;
|
||||
this._statusMessageId = testStatusMessageId;
|
||||
},
|
||||
|
||||
_fail: function(message) {
|
||||
if (this._failed)
|
||||
return;
|
||||
this._failed = true;
|
||||
this._failedMessage = message;
|
||||
this._screenshot({ isFinal: true });
|
||||
},
|
||||
|
||||
_onQemuExited: function(proc, result) {
|
||||
let [success, status] = ProcUtil.asyncWaitCheckFinish(proc, result);
|
||||
this._qemu = null;
|
||||
this._loop.quit();
|
||||
if (!success) {
|
||||
this._fail("Qemu exited with status " + status);
|
||||
}
|
||||
},
|
||||
|
||||
_onTimeout: function() {
|
||||
print("Timeout reached");
|
||||
for (let msgid in this._pendingRequiredMessageIds) {
|
||||
print("Did not see MESSAGE_ID=" + msgid);
|
||||
}
|
||||
this._fail("Timed out");
|
||||
this._loop.quit();
|
||||
},
|
||||
|
||||
_onJournalOpen: function(file, result) {
|
||||
try {
|
||||
this._journalStream = file.read_finish(result);
|
||||
this._journalDataStream = Gio.DataInputStream.new(this._journalStream);
|
||||
this._openedJournal = true;
|
||||
this._readingJournal = true;
|
||||
this._journalDataStream.read_line_async(GLib.PRIORITY_DEFAULT, this._cancellable,
|
||||
Lang.bind(this, this._onJournalReadLine));
|
||||
} catch (e) {
|
||||
this._fail("Journal open failed: " + e);
|
||||
this._loop.quit();
|
||||
}
|
||||
},
|
||||
|
||||
_onJournalReadLine: function(stream, result) {
|
||||
this._readingJournal = false;
|
||||
let line, len;
|
||||
try {
|
||||
[line, len] = stream.read_line_finish_utf8(result);
|
||||
} catch (e) {
|
||||
this._fail(e.toString());
|
||||
this._loop.quit();
|
||||
throw e;
|
||||
}
|
||||
if (this._foundAllMessageIds || this._failed)
|
||||
return;
|
||||
if (!line)
|
||||
return;
|
||||
let data = JSON.parse(line);
|
||||
let message = data['MESSAGE'];
|
||||
let messageId = data['MESSAGE_ID'];
|
||||
let identifier = data['SYSLOG_IDENTIFIER'] || data['_EXE'];
|
||||
if (message)
|
||||
this._journalTextStream.write_all(identifier + ': ' + message + "\n", null);
|
||||
if (line) {
|
||||
if (messageId) {
|
||||
if (this._pendingRequiredMessageIds[messageId]) {
|
||||
print("Found required message ID " + messageId);
|
||||
print(message);
|
||||
delete this._pendingRequiredMessageIds[messageId];
|
||||
this._countPendingRequiredMessageIds--;
|
||||
} else if (this._failMessageIds[messageId]) {
|
||||
this._fail("Found failure message ID " + messageId);
|
||||
print(message);
|
||||
this._loop.quit();
|
||||
}
|
||||
if (messageId === this._statusMessageId) {
|
||||
print(message);
|
||||
let statusTxtPath = Gio.File.new_for_path('status.txt');
|
||||
statusTxtPath.replace_contents(message + '\n', null, false,
|
||||
Gio.FileCreateFlags.REPLACE_DESTINATION, this._cancellable);
|
||||
}
|
||||
this._parentTask._handleMessage(data, this._cancellable);
|
||||
}
|
||||
if (this._countPendingRequiredMessageIds == 0 && !this._foundAllMessageIds) {
|
||||
print("Found all required message IDs, waiting for " + this._parentTask.CompleteIdleWaitSeconds);
|
||||
this._foundAllMessageIds = true;
|
||||
this._parentTask._onSuccess();
|
||||
GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, this._parentTask.CompleteIdleWaitSeconds,
|
||||
Lang.bind(this, this._onFinalWait));
|
||||
} else {
|
||||
this._readingJournal = true;
|
||||
this._journalDataStream.read_line_async(GLib.PRIORITY_DEFAULT, this._cancellable,
|
||||
Lang.bind(this, this._onJournalReadLine));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_onJournalChanged: function(monitor, file, otherFile, eventType) {
|
||||
if (this._foundAllMessageIds || this._failed)
|
||||
return;
|
||||
if (!this._openedJournal) {
|
||||
this._openedJournal = true;
|
||||
file.read_async(GLib.PRIORITY_DEFAULT,
|
||||
this._cancellable,
|
||||
Lang.bind(this, this._onJournalOpen));
|
||||
} else if (!this._readingJournal) {
|
||||
this._readingJournal = true;
|
||||
this._journalDataStream.read_line_async(GLib.PRIORITY_DEFAULT, this._cancellable,
|
||||
Lang.bind(this, this._onJournalReadLine));
|
||||
}
|
||||
},
|
||||
|
||||
_onQemuCapabiltiesRead: function(datain, result) {
|
||||
print("QMP server greeting received");
|
||||
let [response, len] = datain.read_line_finish_utf8(result);
|
||||
this._qmpGreetingReceived = true;
|
||||
this._qmpCommand({ "execute": "qmp_capabilities" },
|
||||
Lang.bind(this, function() {
|
||||
print("qmp_capabilities complete");
|
||||
this._qmpCapabilitiesReceived = true;
|
||||
}));
|
||||
},
|
||||
|
||||
_ensureQemuConnection: function() {
|
||||
if (this._qmpSocketConn)
|
||||
return false;
|
||||
let monitorPath = this._subworkdir.get_child("qemu.monitor");
|
||||
if (!monitorPath.query_exists (null)) {
|
||||
this._qmpConnectionAttempts++;
|
||||
if (this._qmpConnectionAttempts > 10)
|
||||
throw new Error("Failed to connect to qemu monitor after " + this._qmpConnectionAttempts + " attempts");
|
||||
return true;
|
||||
}
|
||||
let path = Gio.File.new_for_path('.').get_relative_path(monitorPath);
|
||||
let address = Gio.UnixSocketAddress.new_with_type(path, Gio.UnixSocketAddressType.PATH);
|
||||
let socketClient = new Gio.SocketClient();
|
||||
this._qmpSocketConn = socketClient.connect(address, this._cancellable);
|
||||
this._qmpOut = Gio.DataOutputStream.new(this._qmpSocketConn.get_output_stream());
|
||||
this._qmpIn = Gio.DataInputStream.new(this._qmpSocketConn.get_input_stream());
|
||||
this._qmpIn.read_line_async(GLib.PRIORITY_DEFAULT, this._cancellable,
|
||||
Lang.bind(this, this._onQemuCapabiltiesRead));
|
||||
return false;
|
||||
},
|
||||
|
||||
_onQemuCommandComplete: function(datain, result) {
|
||||
let [response, len] = datain.read_line_finish_utf8(result);
|
||||
let responseData = null;
|
||||
try {
|
||||
responseData = JSON.parse(response);
|
||||
} catch (e) {}
|
||||
if (responseData && responseData.error)
|
||||
print("command response error=" + JSON.stringify(responseData.error));
|
||||
let onComplete = this._qmpCommandOutstanding.shift();
|
||||
if (this._qmpCommandOutstanding.length == 1)
|
||||
this._qmpIn.read_line_async(GLib.PRIORITY_DEFAULT, this._cancellable,
|
||||
Lang.bind(this, this._onQemuCommandComplete));
|
||||
onComplete();
|
||||
},
|
||||
|
||||
_qmpCommand: function(cmd, onComplete) {
|
||||
this._ensureQemuConnection();
|
||||
let cmdStr = JSON.stringify(cmd);
|
||||
if (!this._qmpGreetingReceived)
|
||||
throw new Error("Attempting QMP command without having received greeting");
|
||||
this._qmpOut.put_string(cmdStr, this._cancellable);
|
||||
this._qmpCommandOutstanding.push(onComplete);
|
||||
if (this._qmpCommandOutstanding.length == 1)
|
||||
this._qmpIn.read_line_async(GLib.PRIORITY_DEFAULT, this._cancellable,
|
||||
Lang.bind(this, this._onQemuCommandComplete));
|
||||
},
|
||||
|
||||
_onScreenshotComplete: function(filename, isFinal, name) {
|
||||
print("screenshot complete for " + filename);
|
||||
let filePath = this._subworkdir.get_child(filename);
|
||||
let modified = true;
|
||||
|
||||
if (!isFinal && name == null) {
|
||||
let contentsBytes = GSystem.file_map_readonly(filePath, this._cancellable);
|
||||
let csum = GLib.compute_checksum_for_bytes(GLib.ChecksumType.SHA256,
|
||||
contentsBytes);
|
||||
|
||||
modified = this._lastScreenshotChecksum != csum;
|
||||
if (!modified) {
|
||||
GSystem.file_unlink(filePath, this._cancellable);
|
||||
} else {
|
||||
this._lastScreenshotChecksum = csum;
|
||||
}
|
||||
this._screenshotSerial++;
|
||||
}
|
||||
|
||||
// Convert to PNG if possible
|
||||
let screenshotPath = filePath;
|
||||
if (modified && imports.gi.GdkPixbuf) {
|
||||
let GdkPixbuf = imports.gi.GdkPixbuf;
|
||||
let pixbuf = null;
|
||||
try {
|
||||
pixbuf = GdkPixbuf.Pixbuf.new_from_file(filePath.get_path());
|
||||
|
||||
} catch (e) {
|
||||
if (e.domain != GdkPixbuf.PixbufError)
|
||||
throw e;
|
||||
print(e);
|
||||
}
|
||||
if (pixbuf != null) {
|
||||
let outFilename = this._subworkdir.get_child(filename.replace(/ppm$/, 'png'));
|
||||
screenshotPath = outFilename;
|
||||
pixbuf.savev(outFilename.get_path(), "png", [], []);
|
||||
}
|
||||
GSystem.file_unlink(filePath, this._cancellable);
|
||||
}
|
||||
|
||||
if (name == null) {
|
||||
this._requestingScreenshot = false;
|
||||
} else {
|
||||
this._parentTask._screenshotTaken(screenshotPath);
|
||||
this._commandProxy._dbusImpl.emit_signal('ScreenshotComplete',
|
||||
GLib.Variant.new('(s)', [name]));
|
||||
}
|
||||
|
||||
if (isFinal) {
|
||||
print("Final screenshot complete");
|
||||
this._qmpCommand({"execute": "system_powerdown"},
|
||||
Lang.bind(this, this._onFinalPoweroff));
|
||||
}
|
||||
},
|
||||
|
||||
_onFinalPoweroff: function() {
|
||||
print("Poweroff request sent");
|
||||
GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 5,
|
||||
Lang.bind(this, function() {
|
||||
print("Poweroff timeout ");
|
||||
this._loop.quit();
|
||||
}));
|
||||
},
|
||||
|
||||
_screenshot: function(params) {
|
||||
params = Params.parse(params, { isFinal: false,
|
||||
name: null });
|
||||
if (params.name == null) {
|
||||
if (this._requestingScreenshot)
|
||||
return;
|
||||
this._requestingScreenshot = true;
|
||||
}
|
||||
let filename;
|
||||
if (params.name == null) {
|
||||
if (params.isFinal)
|
||||
filename = "screenshot-final.ppm";
|
||||
else
|
||||
filename = "screenshot-" + this._screenshotSerial + ".ppm";
|
||||
} else {
|
||||
filename = "screenshot-" + params.name + ".ppm";
|
||||
}
|
||||
|
||||
this._qmpCommand({"execute": "screendump", "arguments": { "filename": filename }},
|
||||
Lang.bind(this, this._onScreenshotComplete, filename, params.isFinal, params.name));
|
||||
},
|
||||
|
||||
_idleScreenshot: function() {
|
||||
if (this._foundAllMessageIds)
|
||||
return false;
|
||||
if (this._qmpCapabilitiesReceived)
|
||||
this._screenshot(false);
|
||||
return true;
|
||||
},
|
||||
|
||||
_onFinalWait: function() {
|
||||
print("Final wait complete at " + GLib.DateTime.new_now_local().format('%c'));
|
||||
|
||||
this._screenshot({ isFinal: true });
|
||||
},
|
||||
|
||||
_onCommandChannelScreenshot: function(name) {
|
||||
this._screenshot({ name: name });
|
||||
},
|
||||
|
||||
_onCommandSocketDBusReady: function(iostream, result) {
|
||||
this._commandSocketDBus = Gio.DBusConnection.new_finish(result);
|
||||
this._commandProxy = new CommandSocketProxy(this._commandSocketDBus,
|
||||
Lang.bind(this._parentTask, this._parentTask._onCommandChannelAsyncMessage),
|
||||
Lang.bind(this, this._onCommandChannelScreenshot));
|
||||
print("Command DBus connection open");
|
||||
},
|
||||
|
||||
_onCommandSocketConnected: function(client, result) {
|
||||
this._commandSocketConn = client.connect_finish(result);
|
||||
print("Connected to command socket");
|
||||
Gio.DBusConnection.new(this._commandSocketConn, null, 0,
|
||||
null, this._cancellable,
|
||||
Lang.bind(this, this._onCommandSocketDBusReady));
|
||||
},
|
||||
|
||||
_tryCommandConnection: function() {
|
||||
if (this._commandSocketDBus || this._complete)
|
||||
return false;
|
||||
printerr("DEBUG: Querying command socket");
|
||||
if (!this._commandSocketPath.query_exists(null)) {
|
||||
print("commandSocketPath " + this._commandSocketPath.get_path() + " does not exist yet");
|
||||
this._commandConnectionAttempts++;
|
||||
if (this._commandConnectionAttempts > 5) {
|
||||
this._fail("Command connection didn't appear at " + this._commandSocketPath.get_path());
|
||||
this._loop.quit();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
printerr("Connecting to command socket...");
|
||||
let path = Gio.File.new_for_path('.').get_relative_path(this._commandSocketPath);
|
||||
let address = Gio.UnixSocketAddress.new_with_type(path, Gio.UnixSocketAddressType.PATH);
|
||||
let socketClient = new Gio.SocketClient();
|
||||
socketClient.connect_async(address, this._cancellable,
|
||||
Lang.bind(this, this._onCommandSocketConnected));
|
||||
return false;
|
||||
},
|
||||
|
||||
execute: function(subworkdir, buildData, repo, diskPath, cancellable) {
|
||||
print("Testing disk " + diskPath.get_path());
|
||||
this._buildData = buildData;
|
||||
this._repo = repo;
|
||||
this._subworkdir = subworkdir;
|
||||
this._loop = GLib.MainLoop.new(null, true);
|
||||
this._foundAllMessageIds = false;
|
||||
this._complete = false;
|
||||
this._failed = false;
|
||||
this._journalStream = null;
|
||||
this._journalDataStream = null;
|
||||
this._commandConnectionAttempts = 0;
|
||||
this._commandSocketDBus = null;
|
||||
this._openedJournal = false;
|
||||
this._readingJournal = false;
|
||||
this._pendingRequiredMessageIds = {};
|
||||
this._requestingScreenshot = false;
|
||||
this._failMessageIds = {};
|
||||
this._countPendingRequiredMessageIds = 0;
|
||||
this._screenshotSerial = 0;
|
||||
this._lastScreenshotChecksum = null;
|
||||
this._qmpGreetingReceived = false;
|
||||
this._qmpSocket = null;
|
||||
this._qmpCommandOutstanding = [];
|
||||
this._qmpConnectionAttempts = 0;
|
||||
this._qmpCapabilitiesReceived = false;
|
||||
print("Will wait for message IDs: " + JSON.stringify(this._testRequiredMessageIds));
|
||||
for (let i = 0; i < this._testRequiredMessageIds.length; i++) {
|
||||
this._pendingRequiredMessageIds[this._testRequiredMessageIds[i]] = true;
|
||||
this._countPendingRequiredMessageIds += 1;
|
||||
}
|
||||
for (let i = 0; i < this._testFailedMessageIds.length; i++) {
|
||||
this._failMessageIds[this._testFailedMessageIds[i]] = true;
|
||||
}
|
||||
this._cancellable = cancellable;
|
||||
|
||||
let osname = this._buildData['snapshot']['osname'];
|
||||
|
||||
// HACK
|
||||
if (diskPath.get_basename().indexOf('x86_64') >= 0)
|
||||
this._diskArch = 'x86_64';
|
||||
else
|
||||
this._diskArch = 'i686';
|
||||
|
||||
let qemuArgs = LibQA.getDefaultQemuOptions({ parallel: true });
|
||||
|
||||
let diskClone = subworkdir.get_child('testoverlay-' + diskPath.get_basename());
|
||||
GSystem.shutil_rm_rf(diskClone, cancellable);
|
||||
|
||||
LibQA.createDiskSnapshot(diskPath, diskClone, cancellable);
|
||||
let [gfmnt, mntdir] = LibQA.newReadWriteMount(diskClone, cancellable);
|
||||
try {
|
||||
LibQA.modifyBootloaderAppendKernelArgs(mntdir, ["console=ttyS0"], cancellable);
|
||||
|
||||
let [currentDir, currentEtcDir] = LibQA.getDeployDirs(mntdir, osname);
|
||||
|
||||
LibQA.injectExportJournal(currentDir, currentEtcDir, cancellable);
|
||||
LibQA.injectTestUserCreation(currentDir, currentEtcDir, 'testuser', {}, cancellable);
|
||||
LibQA.enableAutologin(currentDir, currentEtcDir, 'testuser', cancellable);
|
||||
|
||||
this._parentTask._prepareDisk(mntdir, this._diskArch, cancellable);
|
||||
} finally {
|
||||
gfmnt.umount(cancellable);
|
||||
}
|
||||
|
||||
let consoleOutput = subworkdir.get_child('console.out');
|
||||
let journalJson = subworkdir.get_child('journal.json');
|
||||
let journalText = subworkdir.get_child('journal.txt');
|
||||
GSystem.shutil_rm_rf(journalText, cancellable);
|
||||
this._journalTextStream = journalText.replace(null, false,
|
||||
Gio.FileCreateFlags.REPLACE_DESTINATION,
|
||||
cancellable);
|
||||
this._commandSocketPath = subworkdir.get_child('command.sock');
|
||||
|
||||
let commandSocketRelpath = subworkdir.get_relative_path(this._commandSocketPath);
|
||||
qemuArgs.push.apply(qemuArgs, ['-drive', 'file=' + diskClone.get_path() + ',if=virtio',
|
||||
'-vnc', 'none',
|
||||
'-serial', 'file:' + consoleOutput.get_path(),
|
||||
'-chardev', 'socket,id=charmonitor,path=qemu.monitor,server,nowait',
|
||||
'-mon', 'chardev=charmonitor,id=monitor,mode=control',
|
||||
'-device', 'virtio-serial',
|
||||
'-chardev', 'file,id=journaljson,path=' + journalJson.get_path(),
|
||||
'-device', 'virtserialport,chardev=journaljson,name=org.gnome.journaljson',
|
||||
'-chardev', 'socket,id=commandchan,server,path=' + commandSocketRelpath,
|
||||
'-device', 'virtserialport,chardev=commandchan,name=org.gnome.commandchan']);
|
||||
|
||||
let qemuContext = new GSystem.SubprocessContext({ argv: qemuArgs });
|
||||
qemuContext.set_cwd(subworkdir.get_path());
|
||||
let qemu = new GSystem.Subprocess({context: qemuContext});
|
||||
this._qemu = qemu;
|
||||
print("starting qemu : " + qemuArgs.join(' '));
|
||||
qemu.init(cancellable);
|
||||
|
||||
GLib.timeout_add(GLib.PRIORITY_DEFAULT, 250, Lang.bind(this, this._ensureQemuConnection));
|
||||
|
||||
qemu.wait(cancellable, Lang.bind(this, this._onQemuExited));
|
||||
|
||||
let journalMonitor = journalJson.monitor_file(0, cancellable);
|
||||
journalMonitor.connect('changed', Lang.bind(this, this._onJournalChanged));
|
||||
|
||||
let commandConnectAttemptTimeoutId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 1,
|
||||
Lang.bind(this, this._tryCommandConnection));
|
||||
let timeoutId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, TIMEOUT_SECONDS,
|
||||
Lang.bind(this, this._onTimeout));
|
||||
|
||||
// Let's only do a screenshot every 3 seconds, I think it's slowing things down...
|
||||
let screenshotTimeoutId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 3,
|
||||
Lang.bind(this, this._idleScreenshot));
|
||||
|
||||
this._loop.run();
|
||||
|
||||
this._complete = true;
|
||||
|
||||
if (this._qemu) {
|
||||
this._qemu.force_exit();
|
||||
}
|
||||
|
||||
if (this._journalTextStream)
|
||||
this._journalTextStream.close(null);
|
||||
|
||||
GLib.source_remove(timeoutId);
|
||||
|
||||
let [gfmnt, mntdir] = LibQA.newReadWriteMount(diskClone, cancellable);
|
||||
try {
|
||||
this._parentTask._postQemu(mntdir, cancellable);
|
||||
} finally {
|
||||
gfmnt.umount(cancellable);
|
||||
}
|
||||
|
||||
//GSystem.shutil_rm_rf(diskClone, cancellable);
|
||||
|
||||
if (this._failed) {
|
||||
throw new Error(this._failedMessage);
|
||||
}
|
||||
|
||||
print("Completed testing of " + diskPath.get_basename());
|
||||
}
|
||||
});
|
||||
|
||||
const TestBase = new Lang.Class({
|
||||
Name: 'TestBase',
|
||||
Extends: Task.Task,
|
||||
|
||||
TaskDef: {
|
||||
TaskName: "testbase",
|
||||
TaskAfter: ['builddisks'],
|
||||
},
|
||||
|
||||
TestTrees: ['-runtime'],
|
||||
CompleteIdleWaitSeconds: 10,
|
||||
|
||||
BaseRequiredMessageIDs: ["39f53479d3a045ac8e11786248231fbf", // graphical.target
|
||||
"f77379a8490b408bbe5f6940505a777b", // systemd-journald
|
||||
],
|
||||
|
||||
BaseFailedMessageIDs: [],
|
||||
|
||||
RequiredMessageIDs: [],
|
||||
FailedMessageIDs: [],
|
||||
|
||||
StatusMessageID: [],
|
||||
|
||||
CompletedTag: null,
|
||||
|
||||
_prepareDisk: function(mntdir, cancellable) {
|
||||
// Nothing, intended for subclasses
|
||||
},
|
||||
|
||||
// For subclasses
|
||||
_onCommandChannelAsyncMessage: function(msgId, value) {
|
||||
print("Received command async message " + msgId);
|
||||
},
|
||||
|
||||
_handleMessage: function(message, cancellable) {
|
||||
return false;
|
||||
},
|
||||
|
||||
_onSuccess: function() {
|
||||
},
|
||||
|
||||
_postQemu: function(mntdir, cancellable) {
|
||||
},
|
||||
|
||||
_screenshotTaken: function(path) {
|
||||
},
|
||||
|
||||
execute: function(cancellable) {
|
||||
let imageDir = this.workdir.get_child('images');
|
||||
let currentImages = imageDir.get_child('current');
|
||||
|
||||
let e = currentImages.enumerate_children('standard::*', Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS,
|
||||
cancellable);
|
||||
let info;
|
||||
let buildJson;
|
||||
let disksToTest = [];
|
||||
|
||||
while ((info = e.next_file(cancellable)) != null) {
|
||||
let name = info.get_name();
|
||||
if (name.indexOf('build-') == 0 && JSUtil.stringEndswith(name, '.json')) {
|
||||
buildJson = e.get_child(info);
|
||||
continue;
|
||||
}
|
||||
if (!JSUtil.stringEndswith(name, '.qcow2'))
|
||||
continue;
|
||||
let matches = false;
|
||||
for (let i = 0; i < this.TestTrees.length; i++) {
|
||||
let tree = this.TestTrees[i];
|
||||
if (JSUtil.stringEndswith(name, tree + '.qcow2')) {
|
||||
matches = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!matches) {
|
||||
print("Skipping disk " + name + " not in " + JSON.stringify(this.TestTrees));
|
||||
continue;
|
||||
}
|
||||
disksToTest.push(name);
|
||||
}
|
||||
e.close(null);
|
||||
if (disksToTest.length == 0)
|
||||
throw new Error("Didn't find any matching .qcow2 disks in " + currentImages.get_path());
|
||||
this._buildData = null;
|
||||
if (buildJson != null)
|
||||
this._buildData = JSONUtil.loadJson(buildJson, cancellable);
|
||||
for (let i = 0; i < disksToTest.length; i++) {
|
||||
let name = disksToTest[i];
|
||||
let workdirName = 'work-' + name.replace(/\.qcow2$/, '');
|
||||
let subworkdir = Gio.File.new_for_path(workdirName);
|
||||
GSystem.file_ensure_directory(subworkdir, true, cancellable);
|
||||
let test = new TestOneDisk(this,
|
||||
this.BaseRequiredMessageIDs.concat(this.RequiredMessageIDs),
|
||||
this.BaseFailedMessageIDs.concat(this.FailedMessageIDs),
|
||||
this.StatusMessageID);
|
||||
test.execute(subworkdir, this._buildData, this.repo, currentImages.get_child(name), cancellable);
|
||||
}
|
||||
|
||||
let buildData = this._buildData;
|
||||
if (buildJson != null && this.CompletedTag !== null) {
|
||||
let snapshot = buildData['snapshot'];
|
||||
this.ostreeRepo.prepare_transaction(cancellable);
|
||||
for (let targetName in buildData['targets']) {
|
||||
let targetRev = buildData['targets'][targetName];
|
||||
let lastSlash = targetName.lastIndexOf('/');
|
||||
let testedRefName = snapshot['osname'] + '/' + this.CompletedTag + targetName.substr(lastSlash);
|
||||
this.ostreeRepo.transaction_set_ref(null, testedRefName, targetRev);
|
||||
print(Format.vprintf("Wrote ref: %s => %s", [testedRefName, targetRev]));
|
||||
}
|
||||
this.ostreeRepo.commit_transaction(cancellable);
|
||||
} else {
|
||||
print("No build json found, not tagging");
|
||||
}
|
||||
}
|
||||
});
|
431
src/autobuilder/js/vcs.js
Normal file
431
src/autobuilder/js/vcs.js
Normal file
@ -0,0 +1,431 @@
|
||||
// Copyright (C) 2011,2012 Colin Walters <walters@verbum.org>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the
|
||||
// Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
// Boston, MA 02111-1307, USA.
|
||||
|
||||
const Params = imports.params;
|
||||
const Format = imports.format;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const GSystem = imports.gi.GSystem;
|
||||
|
||||
const ProcUtil = imports.procutil;
|
||||
const BuildUtil = imports.buildutil;
|
||||
const JSUtil = imports.jsutil;
|
||||
|
||||
function getMirrordir(mirrordir, keytype, uri, params) {
|
||||
params = Params.parse(params, {prefix: ''});
|
||||
let colon = uri.indexOf('://');
|
||||
let scheme, rest;
|
||||
if (colon >= 0) {
|
||||
scheme = uri.substr(0, colon);
|
||||
rest = uri.substr(colon+3);
|
||||
} else {
|
||||
scheme = 'file';
|
||||
if (GLib.path_is_absolute(uri))
|
||||
rest = uri.substr(1);
|
||||
else
|
||||
rest = uri;
|
||||
}
|
||||
let prefix = params.prefix ? params.prefix + '/' : '';
|
||||
let relpath = prefix + keytype + '/' + scheme + '/' + rest;
|
||||
return mirrordir.resolve_relative_path(relpath);
|
||||
}
|
||||
|
||||
function _processCheckoutSubmodules(mirrordir, parentUri, cwd, cancellable) {
|
||||
let lines = ProcUtil.runSyncGetOutputLines(['git', 'submodule', 'status'],
|
||||
cancellable, {cwd: cwd});
|
||||
let haveSubmodules = false;
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
let line = lines[i];
|
||||
if (line == '') continue;
|
||||
haveSubmodules = true;
|
||||
line = line.substr(1);
|
||||
let [subChecksum, subName, rest] = line.split(' ');
|
||||
let configKey = Format.vprintf('submodule.%s.url', [subName]);
|
||||
let subUrl = ProcUtil.runSyncGetOutputUTF8Stripped(['git', 'config', '-f', '.gitmodules', configKey],
|
||||
cancellable, {cwd: cwd});
|
||||
print("processing submodule " + subUrl);
|
||||
if (subUrl.indexOf('../') == 0) {
|
||||
subUrl = _makeAbsoluteUrl(parentUri, subUrl);
|
||||
}
|
||||
let localMirror = getMirrordir(mirrordir, 'git', subUrl);
|
||||
ProcUtil.runSync(['git', 'config', configKey, 'file://' + localMirror.get_path()],
|
||||
cancellable, {cwd:cwd});
|
||||
ProcUtil.runSync(['git', 'submodule', 'update', '--init', subName], cancellable, {cwd: cwd});
|
||||
_processCheckoutSubmodules(mirrordir, subUrl, cwd.get_child(subName), cancellable);
|
||||
}
|
||||
}
|
||||
|
||||
function getVcsCheckout(mirrordir, component, dest, cancellable, params) {
|
||||
params = Params.parse(params, {overwrite: true,
|
||||
quiet: false});
|
||||
|
||||
let [keytype, uri] = parseSrcKey(component['src']);
|
||||
let revision;
|
||||
let moduleMirror;
|
||||
let addUpstream;
|
||||
if (keytype == 'git' || keytype == 'local') {
|
||||
revision = component['revision'];
|
||||
moduleMirror = getMirrordir(mirrordir, keytype, uri);
|
||||
addUpstream = true;
|
||||
} else if (keytype == 'tarball') {
|
||||
revision = 'tarball-import-' + component['checksum'];
|
||||
moduleMirror = getMirrordir(mirrordir, 'tarball', component['name']);
|
||||
addUpstream = false;
|
||||
} else {
|
||||
throw new Error("Unsupported src uri");
|
||||
}
|
||||
let checkoutdirParent = dest.get_parent();
|
||||
GSystem.file_ensure_directory(checkoutdirParent, true, cancellable);
|
||||
let tmpDest = checkoutdirParent.get_child(dest.get_basename() + '.tmp');
|
||||
GSystem.shutil_rm_rf(tmpDest, cancellable);
|
||||
let ftype = dest.query_file_type(Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, cancellable);
|
||||
if (ftype == Gio.FileType.SYMBOLIC_LINK) {
|
||||
GSystem.file_unlink(dest, cancellable);
|
||||
} else if (ftype == Gio.FileType.DIRECTORY) {
|
||||
if (params.overwrite) {
|
||||
GSystem.shutil_rm_rf(dest, cancellable);
|
||||
} else {
|
||||
tmpDest = dest;
|
||||
}
|
||||
}
|
||||
ftype = tmpDest.query_file_type(Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, cancellable);
|
||||
if (ftype != Gio.FileType.DIRECTORY) {
|
||||
ProcUtil.runSync(['git', 'clone', '-q', '--origin', 'localmirror',
|
||||
'--no-checkout', moduleMirror.get_path(), tmpDest.get_path()], cancellable);
|
||||
if (addUpstream)
|
||||
ProcUtil.runSync(['git', 'remote', 'add', 'upstream', uri], cancellable, {cwd: tmpDest});
|
||||
} else {
|
||||
ProcUtil.runSync(['git', 'fetch', 'localmirror'], cancellable, {cwd: tmpDest});
|
||||
}
|
||||
ProcUtil.runSync(['git', 'checkout', '-q', revision], cancellable, {cwd: tmpDest});
|
||||
_processCheckoutSubmodules(mirrordir, uri, tmpDest, cancellable);
|
||||
if (!tmpDest.equal(dest)) {
|
||||
GSystem.file_rename(tmpDest, dest, cancellable);
|
||||
}
|
||||
return dest;
|
||||
}
|
||||
|
||||
function clean(keytype, checkoutdir, cancellable) {
|
||||
ProcUtil.runSync(['git', 'clean', '-d', '-f', '-x'], cancellable,
|
||||
{cwd: checkoutdir});
|
||||
}
|
||||
|
||||
function parseSrcKey(srckey) {
|
||||
let idx = srckey.indexOf(':');
|
||||
if (idx < 0) {
|
||||
throw new Error("Invalid SRC uri=" + srckey);
|
||||
}
|
||||
let keytype = srckey.substr(0, idx);
|
||||
if (!(keytype == 'git' || keytype == 'local' || keytype == 'tarball')) {
|
||||
throw new Error("Unsupported SRC uri=" + srckey);
|
||||
}
|
||||
let uri = srckey.substr(idx+1);
|
||||
return [keytype, uri];
|
||||
}
|
||||
|
||||
function checkoutPatches(mirrordir, patchdir, component, cancellable) {
|
||||
let patches = component['patches'];
|
||||
|
||||
let [patchesKeytype, patchesUri] = parseSrcKey(patches['src']);
|
||||
if (patchesKeytype == 'local')
|
||||
return Gio.File.new_for_path(patchesUri);
|
||||
else if (patchesKeytype != 'git')
|
||||
throw new Error("Unhandled keytype " + patchesKeytype);
|
||||
|
||||
let patchesMirror = getMirrordir(mirrordir, patchesKeytype, patchesUri);
|
||||
getVcsCheckout(mirrordir, patches, patchdir, cancellable,
|
||||
{overwrite: true,
|
||||
quiet: true});
|
||||
return patchdir;
|
||||
}
|
||||
|
||||
function getLastfetchPath(mirrordir, keytype, uri, branch) {
|
||||
let mirror = getMirrordir(mirrordir, keytype, uri);
|
||||
let branchSafename = branch.replace(/[\/.]/g, '_');
|
||||
return mirror.get_parent().get_child(mirror.get_basename() + '.lastfetch-' + branchSafename);
|
||||
}
|
||||
|
||||
function _listSubmodules(mirrordir, mirror, keytype, uri, branch, cancellable) {
|
||||
let currentVcsVersion = ProcUtil.runSyncGetOutputUTF8(['git', 'rev-parse', branch], cancellable,
|
||||
{cwd: mirror}).replace(/[ \n]/g, '');
|
||||
let tmpCheckout = getMirrordir(mirrordir, keytype, uri, {prefix:'_tmp-checkouts'});
|
||||
GSystem.shutil_rm_rf(tmpCheckout, cancellable);
|
||||
GSystem.file_ensure_directory(tmpCheckout.get_parent(), true, cancellable);
|
||||
ProcUtil.runSync(['git', 'clone', '-q', '--no-checkout', mirror.get_path(), tmpCheckout.get_path()], cancellable);
|
||||
ProcUtil.runSync(['git', 'checkout', '-q', '-f', currentVcsVersion], cancellable,
|
||||
{cwd: tmpCheckout});
|
||||
let submodules = []
|
||||
let lines = ProcUtil.runSyncGetOutputLines(['git', 'submodule', 'status'],
|
||||
cancellable, {cwd: tmpCheckout});
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
let line = lines[i];
|
||||
if (line == '') continue;
|
||||
line = line.substr(1);
|
||||
let [subChecksum, subName, rest] = line.split(' ');
|
||||
let subUrl = ProcUtil.runSyncGetOutputUTF8Stripped(['git', 'config', '-f', '.gitmodules',
|
||||
Format.vprintf('submodule.%s.url', [subName])], cancellable,
|
||||
{cwd: tmpCheckout});
|
||||
submodules.push([subChecksum, subName, subUrl]);
|
||||
}
|
||||
GSystem.shutil_rm_rf(tmpCheckout, cancellable);
|
||||
return submodules;
|
||||
}
|
||||
|
||||
function _makeAbsoluteUrl(parent, relpath)
|
||||
{
|
||||
let origParent = parent;
|
||||
let origRelpath = relpath;
|
||||
if (JSUtil.stringEndswith(parent, '/'))
|
||||
parent = parent.substr(0, parent.length - 1);
|
||||
let methodIndex = parent.indexOf("://");
|
||||
if (methodIndex == -1)
|
||||
throw new Error("Invalid method");
|
||||
let firstSlash = parent.indexOf('/', methodIndex + 3);
|
||||
if (firstSlash == -1)
|
||||
throw new Error("Invalid url");
|
||||
let parentPath = parent.substr(firstSlash);
|
||||
while (relpath.indexOf('../') == 0) {
|
||||
let i = parentPath.lastIndexOf('/');
|
||||
if (i < 0)
|
||||
throw new Error("Relative path " + origRelpath + " is too long for parent " + origParent);
|
||||
relpath = relpath.substr(3);
|
||||
parentPath = parentPath.substr(0, i);
|
||||
}
|
||||
parent = parent.substr(0, firstSlash) + parentPath;
|
||||
if (relpath.length == 0)
|
||||
return parent;
|
||||
return parent + '/' + relpath;
|
||||
}
|
||||
|
||||
function ensureVcsMirror(mirrordir, component, cancellable,
|
||||
params) {
|
||||
params = Params.parse(params, { fetch: false,
|
||||
fetchKeepGoing: false,
|
||||
timeoutSec: 0 });
|
||||
let [keytype, uri] = parseSrcKey(component['src']);
|
||||
if (keytype == 'git' || keytype == 'local') {
|
||||
let branch = component['branch'] || component['tag'];
|
||||
return _ensureVcsMirrorGit(mirrordir, uri, branch, cancellable, params);
|
||||
} else if (keytype == 'tarball') {
|
||||
let name = component['name'];
|
||||
let checksum = component['checksum'];
|
||||
if (!checksum) {
|
||||
throw new Error("Component " + name + " missing checksum attribute");
|
||||
}
|
||||
return _ensureVcsMirrorTarball(mirrordir, name, uri, checksum, cancellable, params);
|
||||
} else {
|
||||
throw new Error("Unhandled keytype=" + keytype);
|
||||
}
|
||||
}
|
||||
|
||||
function _ensureVcsMirrorGit(mirrordir, uri, branch, cancellable, params) {
|
||||
let keytype = 'git';
|
||||
let fetch = params.fetch;
|
||||
let mirror = getMirrordir(mirrordir, keytype, uri);
|
||||
let tmpMirror = mirror.get_parent().get_child(mirror.get_basename() + '.tmp');
|
||||
let didUpdate = false;
|
||||
let lastFetchPath = getLastfetchPath(mirrordir, keytype, uri, branch);
|
||||
let lastFetchContents = null;
|
||||
let currentTime = GLib.DateTime.new_now_utc();
|
||||
let lastFetchContents = null;
|
||||
let lastFetchInfo = null;
|
||||
try {
|
||||
lastFetchInfo = lastFetchPath.query_info('time::modified', Gio.FileQueryInfoFlags.NONE, cancellable);
|
||||
} catch (e) {
|
||||
if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND))
|
||||
throw e;
|
||||
}
|
||||
if (lastFetchInfo != null) {
|
||||
lastFetchContents = GSystem.file_load_contents_utf8(lastFetchPath, cancellable).replace(/[ \n]/g, '');
|
||||
if (params.timeoutSec > 0) {
|
||||
let lastFetchTime = GLib.DateTime.new_from_unix_local(lastFetchInfo.get_attribute_uint64('time::modified'));
|
||||
let diff = currentTime.difference(lastFetchTime) / 1000 / 1000;
|
||||
if (diff < params.timeoutSec) {
|
||||
fetch = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
GSystem.shutil_rm_rf(tmpMirror, cancellable);
|
||||
if (!mirror.query_exists(cancellable)) {
|
||||
ProcUtil.runSync(['git', 'clone', '--mirror', uri, tmpMirror.get_path()], cancellable,
|
||||
{ logInitiation: true });
|
||||
ProcUtil.runSync(['git', 'config', 'gc.auto', '0'], cancellable,
|
||||
{ cwd: tmpMirror,
|
||||
logInitiation: true });
|
||||
GSystem.file_rename(tmpMirror, mirror, cancellable);
|
||||
} else if (fetch) {
|
||||
try {
|
||||
ProcUtil.runSync(['git', 'fetch'], cancellable, { cwd: mirror,
|
||||
logInitiation: true });
|
||||
} catch (e) {
|
||||
if (!params.fetchKeepGoing)
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
let currentVcsVersion = ProcUtil.runSyncGetOutputUTF8(['git', 'rev-parse', branch], cancellable,
|
||||
{cwd: mirror}).replace(/[ \n]/g, '');
|
||||
|
||||
let changed = currentVcsVersion != lastFetchContents;
|
||||
if (changed) {
|
||||
print(Format.vprintf("last fetch %s differs from branch %s", [lastFetchContents, currentVcsVersion]));
|
||||
_listSubmodules(mirrordir, mirror, keytype, uri, branch, cancellable).forEach(function (elt) {
|
||||
let [subChecksum, subName, subUrl] = elt;
|
||||
print("Processing submodule " + subName + " at " + subChecksum + " from " + subUrl);
|
||||
if (subUrl.indexOf('../') == 0) {
|
||||
subUrl = _makeAbsoluteUrl(uri, subUrl);
|
||||
print("Absolute URL: " + subUrl);
|
||||
}
|
||||
_ensureVcsMirrorGit(mirrordir, subUrl, subChecksum, cancellable, params);
|
||||
});
|
||||
}
|
||||
|
||||
if (changed || (fetch && params.timeoutSec > 0)) {
|
||||
lastFetchPath.replace_contents(currentVcsVersion, null, false, 0, cancellable);
|
||||
}
|
||||
|
||||
return mirror;
|
||||
}
|
||||
|
||||
function _ensureVcsMirrorTarball(mirrordir, name, uri, checksum, cancellable, params) {
|
||||
let fetch = params.fetch;
|
||||
let mirror = getMirrordir(mirrordir, 'tarball', name);
|
||||
let tmpMirror = mirror.get_parent().get_child(mirror.get_basename() + '.tmp');
|
||||
|
||||
if (!mirror.query_exists(cancellable)) {
|
||||
GSystem.shutil_rm_rf(tmpMirror, cancellable);
|
||||
GSystem.file_ensure_directory(tmpMirror, true, cancellable);
|
||||
ProcUtil.runSync(['git', 'init', '--bare'], cancellable,
|
||||
{ cwd: tmpMirror, logInitiation: true });
|
||||
ProcUtil.runSync(['git', 'config', 'gc.auto', '0'], cancellable,
|
||||
{ cwd: tmpMirror, logInitiation: true });
|
||||
GSystem.file_rename(tmpMirror, mirror, cancellable);
|
||||
}
|
||||
|
||||
let importTag = 'tarball-import-' + checksum;
|
||||
let gitRevision = ProcUtil.runSyncGetOutputUTF8StrippedOrNull(['git', 'rev-parse', importTag],
|
||||
cancellable, { cwd: mirror });
|
||||
if (gitRevision != null) {
|
||||
return mirror;
|
||||
}
|
||||
|
||||
// First, we get a clone of the tarball git repo
|
||||
let tmpCheckoutPath = mirrordir.get_child('tarball-cwd-' + name);
|
||||
GSystem.shutil_rm_rf(tmpCheckoutPath, cancellable);
|
||||
ProcUtil.runSync(['git', 'clone', mirror.get_path(), tmpCheckoutPath.get_path()], cancellable,
|
||||
{ logInitiation: true });
|
||||
// Now, clean the contents out
|
||||
ProcUtil.runSync(['git', 'rm', '-r', '--ignore-unmatch', '.'], cancellable,
|
||||
{ cwd: tmpCheckoutPath,
|
||||
logInitiation: true });
|
||||
|
||||
// Download the tarball
|
||||
let tmpPath = mirrordir.get_child('tarball-' + name);
|
||||
GSystem.shutil_rm_rf(tmpPath, cancellable);
|
||||
GSystem.file_ensure_directory(tmpPath.get_parent(), true, cancellable);
|
||||
ProcUtil.runSync(['curl', '-L', '-v', '-o', tmpPath.get_path(), uri], cancellable,
|
||||
{ logInitiation: true });
|
||||
|
||||
// And verify the checksum
|
||||
let tarballData = GSystem.file_map_readonly(tmpPath, cancellable);
|
||||
let actualChecksum = GLib.compute_checksum_for_bytes(GLib.ChecksumType.SHA256, tarballData);
|
||||
if (actualChecksum != checksum) {
|
||||
throw new Error("Downloaded " + uri + " expected checksum=" + checksum + " actual=" + actualChecksum);
|
||||
}
|
||||
|
||||
let decompOpt = null;
|
||||
if (JSUtil.stringEndswith(uri, '.xz'))
|
||||
decompOpt = '--xz';
|
||||
else if (JSUtil.stringEndswith(uri, '.bz2'))
|
||||
decompOpt = '--bzip2';
|
||||
else if (JSUtil.stringEndswith(uri, '.gz'))
|
||||
decompOpt = '--gzip';
|
||||
|
||||
// Extract the tarball to our checkout
|
||||
let args = ['tar', '-C', tmpCheckoutPath.get_path(), '-x'];
|
||||
if (decompOpt !== null)
|
||||
args.push(decompOpt);
|
||||
args.push('-f');
|
||||
args.push(tmpPath.get_path());
|
||||
ProcUtil.runSync(args, cancellable, { logInitiation: true });
|
||||
|
||||
tarballData = null; // Clear this out in the hope the GC eliminates it
|
||||
GSystem.file_unlink(tmpPath, cancellable);
|
||||
|
||||
// Automatically strip the first element if there's exactly one directory
|
||||
let e = tmpCheckoutPath.enumerate_children('standard::*', Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS,
|
||||
cancellable);
|
||||
let info;
|
||||
let nFiles = 0;
|
||||
let lastFileType = null;
|
||||
let lastFile = null;
|
||||
let lastInfo = null;
|
||||
while ((info = e.next_file(cancellable)) != null) {
|
||||
if (info.get_name() == '.git')
|
||||
continue;
|
||||
nFiles++;
|
||||
lastFile = e.get_child(info);
|
||||
lastInfo = info;
|
||||
}
|
||||
e.close(cancellable);
|
||||
if (nFiles == 1 && lastInfo.get_file_type() == Gio.FileType.DIRECTORY) {
|
||||
e = lastFile.enumerate_children('standard::*', Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS,
|
||||
cancellable);
|
||||
while ((info = e.next_file(cancellable)) != null) {
|
||||
let child = e.get_child(info);
|
||||
if (!child.equal(lastFile))
|
||||
GSystem.file_rename(child, tmpCheckoutPath.get_child(info.get_name()), cancellable);
|
||||
}
|
||||
lastFile.delete(cancellable);
|
||||
e.close(cancellable);
|
||||
}
|
||||
|
||||
let msg = 'Automatic import of ' + uri;
|
||||
ProcUtil.runSync(['git', 'add', '.'],
|
||||
cancellable, { cwd: tmpCheckoutPath });
|
||||
ProcUtil.runSync(['git', 'commit', '-a', '--author=Automatic Tarball Importer <ostree-list@gnome.org>', '-m', msg ],
|
||||
cancellable, { cwd: tmpCheckoutPath });
|
||||
ProcUtil.runSync(['git', 'tag', '-m', msg, '-a', importTag ],
|
||||
cancellable, { cwd: tmpCheckoutPath });
|
||||
ProcUtil.runSync(['git', 'push', '--tags', 'origin', "master:master" ],
|
||||
cancellable, { cwd: tmpCheckoutPath });
|
||||
|
||||
GSystem.shutil_rm_rf(tmpCheckoutPath, cancellable);
|
||||
|
||||
return mirror;
|
||||
}
|
||||
|
||||
function uncacheRepository(mirrordir, keytype, uri, branch, cancellable) {
|
||||
let lastFetchPath = getLastfetchPath(mirrordir, keytype, uri, branch);
|
||||
GSystem.shutil_rm_rf(lastFetchPath, cancellable);
|
||||
}
|
||||
|
||||
function fetch(mirrordir, component, cancellable, params) {
|
||||
params = Params.parse(params, {keepGoing: false, timeoutSec: 0});
|
||||
ensureVcsMirror(mirrordir, component, cancellable,
|
||||
{ fetch:true,
|
||||
fetchKeepGoing: params.keepGoing,
|
||||
timeoutSec: params.timeoutSec });
|
||||
}
|
||||
|
||||
function describeVersion(dirpath, branch) {
|
||||
let args = ['git', 'describe', '--long', '--abbrev=42', '--always'];
|
||||
if (branch) {
|
||||
args.push(branch);
|
||||
}
|
||||
return ProcUtil.runSyncGetOutputUTF8(args, null, {cwd:dirpath}).replace(/[ \n]/g, '');
|
||||
}
|
194
src/autobuilder/js/versioneddir.js
Normal file
194
src/autobuilder/js/versioneddir.js
Normal file
@ -0,0 +1,194 @@
|
||||
// Copyright (C) 2012,2013 Colin Walters <walters@verbum.org>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the
|
||||
// Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
// Boston, MA 02111-1307, USA.
|
||||
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Lang = imports.lang;
|
||||
const Format = imports.format;
|
||||
|
||||
const GSystem = imports.gi.GSystem;
|
||||
const BuildUtil = imports.buildutil;
|
||||
const FileUtil = imports.fileutil;
|
||||
const JsonUtil = imports.jsonutil;
|
||||
|
||||
function relpathToVersion(relpath) {
|
||||
let parts = relpath.split('/');
|
||||
return parts[0] + parts[1] + parts[2] + '.' + parts[3];
|
||||
}
|
||||
|
||||
const VersionedDir = new Lang.Class({
|
||||
Name: 'VersionedDir',
|
||||
|
||||
_YMD_SERIAL_VERSION_RE: /^(\d+)(\d\d)(\d\d)\.(\d+)$/,
|
||||
_YEAR_OR_SERIAL_VERSION_RE: /^(\d+)$/,
|
||||
_MONTH_OR_DAY_VERSION_RE: /^\d\d$/,
|
||||
|
||||
_init: function(path) {
|
||||
this.path = path;
|
||||
this._cachedResults = null;
|
||||
GSystem.file_ensure_directory(this.path, true, null);
|
||||
},
|
||||
|
||||
_createPathForParsedVersion: function(year, month, day, serial) {
|
||||
return this.path.resolve_relative_path(Format.vprintf('%s/%s/%s/%s',
|
||||
[year, month, day, serial]));
|
||||
},
|
||||
|
||||
createPathForVersion: function(ymdSerial) {
|
||||
let match = this._YMD_SERIAL_VERSION_RE.exec(ymdSerial);
|
||||
if (!match) throw new Error();
|
||||
return this.path.resolve_relative_path(match[1] + '/' + match[2] + '/' +
|
||||
match[3] + '/' + match[4]);
|
||||
},
|
||||
|
||||
_iterateChildrenMatching: function(dir, pattern, callback, cancellable) {
|
||||
let e = dir.enumerate_children('standard::*', Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, cancellable);
|
||||
let info;
|
||||
while ((info = e.next_file(cancellable)) != null) {
|
||||
let srcpath = e.get_child(info);
|
||||
let name = info.get_name();
|
||||
let match = pattern.exec(name);
|
||||
if (!match)
|
||||
continue;
|
||||
callback(srcpath, match, cancellable);
|
||||
}
|
||||
e.close(null);
|
||||
},
|
||||
|
||||
_loadYear: function(yeardir, results, cancellable) {
|
||||
this._iterateChildrenMatching(yeardir, this._MONTH_OR_DAY_VERSION_RE,
|
||||
Lang.bind(this, function(srcpath, match, cancellable) {
|
||||
this._loadMonth(srcpath, results, cancellable);
|
||||
}), cancellable);
|
||||
},
|
||||
|
||||
_loadMonth: function(monthdir, results, cancellable) {
|
||||
this._iterateChildrenMatching(monthdir, this._MONTH_OR_DAY_VERSION_RE,
|
||||
Lang.bind(this, function(srcpath, match, cancellable) {
|
||||
this._loadDay(srcpath, results, cancellable);
|
||||
}), cancellable);
|
||||
},
|
||||
|
||||
_loadDay: function(daydir, results, cancellable) {
|
||||
this._iterateChildrenMatching(daydir, this._YEAR_OR_SERIAL_VERSION_RE,
|
||||
Lang.bind(this, function(srcpath, match, cancellable) {
|
||||
results.push(this.pathToVersion(srcpath));
|
||||
}), cancellable);
|
||||
},
|
||||
|
||||
_convertLegacyLayout: function(cancellable) {
|
||||
this._iterateChildrenMatching(this.path, this._YMD_SERIAL_VERSION_RE,
|
||||
Lang.bind(this, function(srcpath, match, cancellable) {
|
||||
let path = this._createPathForParsedVersion(match[1], match[2], match[3], match[4]);
|
||||
print("convert " + srcpath.get_path() + " -> " + path.get_path());
|
||||
GSystem.file_ensure_directory(path.get_parent(), true, cancellable);
|
||||
GSystem.file_rename(srcpath, path, cancellable);
|
||||
}), cancellable);
|
||||
},
|
||||
|
||||
pathToVersion: function(path) {
|
||||
return relpathToVersion(this.path.get_relative_path(path));
|
||||
},
|
||||
|
||||
loadVersions: function(cancellable) {
|
||||
if (this._cachedResults !== null)
|
||||
return this._cachedResults;
|
||||
let results = [];
|
||||
this._convertLegacyLayout(cancellable);
|
||||
this._iterateChildrenMatching(this.path, this._YEAR_OR_SERIAL_VERSION_RE,
|
||||
Lang.bind(this, function(srcpath, match, cancellable) {
|
||||
this._loadYear(srcpath, results, cancellable);
|
||||
}), cancellable);
|
||||
results.sort(BuildUtil.compareVersions);
|
||||
this._cachedResults = results;
|
||||
return results;
|
||||
},
|
||||
|
||||
currentVersion: function(cancellable) {
|
||||
let versions = this.loadVersions(cancellable);
|
||||
if (versions.length > 0)
|
||||
return versions[versions.length - 1];
|
||||
return null;
|
||||
},
|
||||
|
||||
_makeDirUpdateIndex: function(path, cancellable) {
|
||||
let relpath = this.path.get_relative_path(path);
|
||||
if (relpath == null) {
|
||||
GSystem.file_ensure_directory(path, true, cancellable);
|
||||
return;
|
||||
}
|
||||
|
||||
let parent = path.get_parent();
|
||||
this._makeDirUpdateIndex(parent, cancellable);
|
||||
|
||||
let created = false;
|
||||
try {
|
||||
path.make_directory(cancellable);
|
||||
created = true;
|
||||
} catch (e) {
|
||||
if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.EXISTS))
|
||||
throw e;
|
||||
}
|
||||
let indexJsonPath = parent.get_child('index.json');
|
||||
if (!created)
|
||||
created = !indexJsonPath.query_exists(null);
|
||||
if (created) {
|
||||
let childNames = [];
|
||||
FileUtil.walkDir(parent, { depth: 1,
|
||||
fileType: Gio.FileType.DIRECTORY },
|
||||
Lang.bind(this, function(filePath, cancellable) {
|
||||
childNames.push(filePath.get_basename());
|
||||
}), cancellable);
|
||||
JsonUtil.writeJsonFileAtomic(indexJsonPath,
|
||||
{ 'subdirs': childNames }, cancellable);
|
||||
}
|
||||
},
|
||||
|
||||
allocateNewVersion: function(cancellable) {
|
||||
let currentTime = GLib.DateTime.new_now_utc();
|
||||
let currentYmd = Format.vprintf('%d%02d%02d', [currentTime.get_year(),
|
||||
currentTime.get_month(),
|
||||
currentTime.get_day_of_month()]);
|
||||
let versions = this.loadVersions(cancellable);
|
||||
let newVersion = null;
|
||||
if (versions.length > 0) {
|
||||
let last = versions[versions.length-1];
|
||||
let match = this._YMD_SERIAL_VERSION_RE.exec(last);
|
||||
if (!match) throw new Error();
|
||||
let lastYmd = match[1] + match[2] + match[3];
|
||||
let lastSerial = match[4];
|
||||
if (lastYmd == currentYmd) {
|
||||
newVersion = currentYmd + '.' + (parseInt(lastSerial) + 1);
|
||||
}
|
||||
}
|
||||
if (newVersion === null)
|
||||
newVersion = currentYmd + '.0';
|
||||
let path = this.createPathForVersion(newVersion);
|
||||
this._makeDirUpdateIndex(path, cancellable);
|
||||
this._cachedResults.push(newVersion);
|
||||
return path;
|
||||
},
|
||||
|
||||
deleteCurrentVersion: function(cancellable) {
|
||||
let versions = this.loadVersions(cancellable);
|
||||
if (versions.length == 0)
|
||||
throw new Error();
|
||||
let last = versions.pop();
|
||||
let path = this.createPathForVersion(last);
|
||||
GSystem.shutil_rm_rf(path, cancellable);
|
||||
}
|
||||
});
|
28
src/autobuilder/rpm-ostree-autobuilder.in
Executable file
28
src/autobuilder/rpm-ostree-autobuilder.in
Executable file
@ -0,0 +1,28 @@
|
||||
#!/bin/sh
|
||||
# Copyright (C) 2012,2013 Colin Walters <walters@verbum.org>
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the
|
||||
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
# Boston, MA 02111-1307, USA.
|
||||
|
||||
jsdir=@pkgdatadir@/js
|
||||
|
||||
export GI_TYPELIB_PATH="@pkglibdir@/girepository-1.0${GI_TYPELIB_PATH:+:$GI_TYPELIB_PATH}"
|
||||
export LD_LIBRARY_PATH="@pkglibdir@/${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
|
||||
# Don't auto-spawn a session bus
|
||||
export GIO_USE_VFS=local
|
||||
export OSTBUILD_DATADIR=@pkgdatadir@
|
||||
export OSTBUILD_LIBDIR=@pkglibdir@
|
||||
|
||||
exec $OSTBUILD_GDB gjs -I "${jsdir}" "${jsdir}/main.js" "$@"
|
Loading…
Reference in New Issue
Block a user