core: Remove src/autobuilder
This will move to a separate repository. This allows a clearer separation between the core tool (which is shipped on client systems too), and the compose infrastructure. Furthermore, I want to make the autobuilder a Docker container.
This commit is contained in:
parent
0ad262b2c4
commit
df2b355f38
@ -1,64 +0,0 @@
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
Version: GnuPG v2.0.22 (GNU/Linux)
|
||||
|
||||
mQINBFLqoTIBEADWmOEk9BrBQT2PJpkX/Bba6UgPmZaUN7J6TL85vOtO/Kv+HuNk
|
||||
HnU5LfnSNzyizIW/KppXZyF88Mw7Ol7VH1NF3w58nnQcIWSS5lDUr4nAnngpGJEb
|
||||
lZh/9Fuz6dQUJKG8GhqHQumrseKDvR99nBEm2V+PpCvTR9p7ZTgjcqktuZGNZ++Z
|
||||
lwdGX71xcN6HiwM7TGFZGWrSuNg8uubnFg+0Jfbm0rGpC0eNBkxzlLp6hWxb1PFD
|
||||
OjTvvo+RTbTcmRv/xO8tL5LZLMEHKT8a0G4z3NknbBcVeezhjCACh46RNzMeiTWX
|
||||
Kkp7rKxgt1HLsQlxFnSQpAyXNumGIngGJGDTLTB5HyyHxFEUkcY9jZbE4C13sa1P
|
||||
MaYb6B/LnX0XjgEqc1Hg2bqOrI1N9PN0mv5kExbjy5P3XA7ef4wdV/HyyipB5aw5
|
||||
tiDd0dAbbdMqoQZ7/BmcqxVPd0R5LH+wkDhEH9rCxmr8hQlZEn6nWDE4dnNyn55F
|
||||
NqXouCa4IDocgSlNE6KFB6T+4IDufoO/Drmx+RNsuz5qI1RuwKal6IKSvnzTJlL9
|
||||
0kjSQmL1u49obvmLZUCb5kiiznZFs0f0/VeP8jXdbU70Ygu/h5AXZlGcSbMHsgil
|
||||
OJY0jTZXBT+PShLcX4XqttN0iCylqIPIcpH4q3B5XoJHlaE2iHpVYFhLbQARAQAB
|
||||
tElSUE0tT1NUcmVlIERlbW8gU2lnbmluZyBLZXkgKE5vdCBmb3IgcHJvZHVjdGlv
|
||||
biB1c2UpIDx3YWx0ZXJzQHZlcmJ1bS5vcmc+iQI5BBMBAgAjBQJS6qEyAhsDBwsJ
|
||||
CAcDAgEGFQgCCQoLBBYCAwECHgECF4AACgkQ6QluiIQ4M9+mhhAAnWbmKUl9pHIk
|
||||
GG9wXEb6dv8KLuvGz2LW/fE26TsJgL0uEvUfYh3PoUwHszFhAjgZYqU8V1BrChQR
|
||||
y0g6SGlbQypktRj4nCSfAP5UZqESmKyV8zdZuvbi6RJ3HB3U9TTmLJXpMor41RuP
|
||||
iqDnXyjsu+bPDTcsgHvapDgE3mMj++a3ahUFTFCCAdPefSuD6Ddb6yDgNKeJskaB
|
||||
iMPvZcxEJFF1NAWtFG1WkyJgJYWALXNXggxpMlh2SJbTZSHJ5O0HPypvvPmZpBwL
|
||||
A8y3PbyIvx65AkOQyXFDuZyxfpHKuz2LVAodduKm32S8ZyImKqcURqVYjkkS5juY
|
||||
Bx08DB+BnaZq7Yq58A6wHSNMXxGmUrb7LhTXfRn3FQkUq9K44U+wG19k7nBxNJTU
|
||||
1d+k1U+PpHA9uHEvplicyBKmjlbz7FlygZSVkresnTGQvjNQgoykXaOjVcxeeOPg
|
||||
TPNKhauU8grCRAVM+/YgRPYYoh4QF/rbzwGhD00UKh9SHq33rtyFx6Ku7nRXvmDg
|
||||
8v3Aksv4YbmzNXcQNen67ov6Oqv5W6kfTOnYICSNt001OWVmsFToKZnN3PHyet/E
|
||||
Jp3Dm9dkBcmPxnDbdo7Q+huw9D1g3iCOgEHFKBNG6QZyht2q/L7YutAdLep3F0xm
|
||||
FS3xaQLgmOLd4jbRienpejVhQoIZQQOJAhwEEAECAAYFAlLusyYACgkQqGbXzK4I
|
||||
cpEoqQ/+PZhpz2ahZPwf0lS3NRFe60U4rILAHs07PgkfEmlZt11jnP0noUlvrYB5
|
||||
xCqKIM5t6/1DE4d0jPXx7A338tJ1pjkYEzxo1BqRsVKvYxO2L1vndb7NUou/WXGb
|
||||
nh/Xc6VE+Dvq9F0K41FXJ74VXhsJsHQTXeGlgec/L74HZSCbBU9PuyTgw0iz/KOO
|
||||
gcnxIybuK04lNsJ82t7unB2t4oGVUcZp9b+OdIjwT6aPl364gLSEf70/x8A3qitu
|
||||
7MqKrHfKSoEHtBibGPrBT7aafTGfNwL5tlh4HnHo7EsVjmKA0nzBsXgle//vn1m8
|
||||
YyBwE1BS03bcN2cSu56yZ8RGKFalVjrpKD+MzzFaL+4v0FYRESyFdChQviA7NOjr
|
||||
SMjdz0I19Q2d/Uz7Wyrlt/b6a+dkBXePwABsvFsAETbW2Bo9y1isSe9mT4D0Abee
|
||||
cR3c06+NGez1i/t6BEF82UZrdUXqB/uqe2uGxcnnnrjfQQtRtl8XDdUWE2rHRGB1
|
||||
xJ2vjELiUlEFpcDwx4UC6xVJm/g/sRTgVc7wEfjiNCKX3mrkP3zBAWP+/DeHx7ud
|
||||
V7gZ9AATkEvrGrP0Q/53d95UljYUsZ/TLEDpR6Josdz2CiEvOzf4niAbCEAN9TuW
|
||||
ON1L1dzYmiVYAb3f9j7B2MhD+SGBUDO9X9EvU3tWRcxp6xuOuxK5Ag0EUuqhMgEQ
|
||||
ANMTgMR6sMB2DCliJ4Ej49to7i4ulEKmkkDwHUyZHGBtELItTrV64xWg55jgHRo+
|
||||
1NvzDhbcQENhylZncsaKCkbNjQwr+tDYGnvg5r/yM7oVTndKBu0WOKoTIolmn9S1
|
||||
Cv5ak+zVV0LiHqD6g5C7CVryYLdrypf5jV+g9urS5JUN+z33J8btB85ZGVHIvdeD
|
||||
Bb5QkIr7ttEX2nEtssqGc34VjG4+hnWLDhtGDl7y4YY9KrwT59PGN3NkI9K5gH6s
|
||||
k4O8TOOCSHp7o+s+Y6q5GWRSq4eNMHCmaSPrYMI6T7UAYtTVFOMM+Vqh5TDTuf0j
|
||||
j4Q4/pOQ/P48rTjVYExw6zZxSK0+zxWxsMxRGoKtjpxz/G2DlNut9qDmjN1OA+BR
|
||||
EvawaS5ay58uQjEU5C3m4hMtSSleV0eJBtNAwdCrrYvIIjXoFUTZA905U1VvCDs5
|
||||
UCUOxWo/6h8SQUYqxEcJSDW+cdSeUXk1vh/ajEdQ//nVY1L7MNXv5t8Wn21KUw0q
|
||||
HgStMhOYgM1kgY9A7ib0oqR3TrNGGWUIoFtbNoNgGvYhq8ylPFe24J3h9z4p5hSo
|
||||
2N5TYEXZFRns8qN4PQdf3Y0TT+QG9SxukrV0vrdcX6ma1/WRZWG3INqneaAaFHY3
|
||||
MiHYOFjetZxUAxAAsOhwj3zIPtiPDenZ3GQvxza2mIjXABEBAAGJAh8EGAECAAkF
|
||||
AlLqoTICGwwACgkQ6QluiIQ4M9/0NRAAosUFqqcXr8HfshHCzgFef5ItDInDL6cc
|
||||
ozvIBUPGJNOmP/KS+jTu8SBlc8dEhb/rFmWWpYytpV19QBXrH0ASkUYhknTaZWxP
|
||||
MlonsGDgO+P902KCmByGZp5VrnhNaITyEmN14y8+tUagdUUSrd+eo8n6heA6Egip
|
||||
327ftqgt/7kgmum0mX2I6ayhBEjfQOSP2p2Uplx504EZygp02MALJ2ZiGhhOpThP
|
||||
cGPmQAL52gQlOGg9drTFPfrvQG57ZGyTP/WNUlcKNW4xRKyMcfUnEGvlbaqLXSRv
|
||||
4WrAkr6GJglzUvQyak3mW7ZmoJ14AqVWxPwRrd5M9CU0Xi8Bvh8wp/oSI5PKkm6A
|
||||
lFEe2zdCAW1vs63aFu5/Q7OW8HQiQ23do2rXykxm4aKuI6iZppORMSzMF0m0kV3w
|
||||
EFLcfI1gJ+X4b+klJ8j3XyXrA8dpuMbYTODSWV/RLhDx7Nj6NNCLC7ejeKox8MD5
|
||||
G76mbi73uk0oyuZz/tDpXCv+bepxbj0TJI0SCVIsQsqfyGsEqdpjTwRmz605kXJ6
|
||||
zUMkCcw2VnIu8J1H5eU8UCeLh9lz3qXZy9XExzgmiF0F1Gj/GqUGFU+S59lKyuI0
|
||||
hJk+rhJ/3rJorVYmOatV8P9WKwMyYLZx4X7jVosoT5G8XEJ6IuPUbXo21ndLmno6
|
||||
YUsDeX1U94I=
|
||||
=9PDu
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
@ -1,84 +0,0 @@
|
||||
# Copyright (C) 2011,2014 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.
|
||||
|
||||
substitutions= \
|
||||
-e s,@libdir\@,$(libdir), \
|
||||
-e s,@pkglibdir\@,$(pkglibdir), \
|
||||
-e s,@datarootdir\@,$(datarootdir), \
|
||||
-e s,@pkgdatadir\@,$(pkgdatadir), \
|
||||
$(NULL)
|
||||
|
||||
rpm-ostree-autobuilder: src/autobuilder/rpm-ostree-autobuilder.in Makefile
|
||||
sed $(substitutions) $< > $@.tmp && mv $@.tmp $@
|
||||
EXTRA_DIST += src/autobuilder/rpm-ostree-autobuilder.in
|
||||
|
||||
autobuilder_privlibdir=$(libdir)/$(PACKAGE)-autobuilder
|
||||
autobuilder_privlib_PROGRAMS = rpm-ostree-autobuilder-builtin-console
|
||||
rpm_ostree_autobuilder_builtin_console_SOURCES = src/autobuilder/rpm-ostree-autobuilder-builtin-console.c
|
||||
rpm_ostree_autobuilder_builtin_console_CFLAGS = $(AM_CFLAGS) $(PKGDEP_RPMOSTREE_CFLAGS)
|
||||
rpm_ostree_autobuilder_builtin_console_LDADD = $(AM_LDFLAGS) $(PKGDEP_RPMOSTREE_LIBS) -lreadline
|
||||
|
||||
autobuilder_privlib_SCRIPTS = src/autobuilder/rpmqa-sorted
|
||||
|
||||
bin_SCRIPTS += rpm-ostree-autobuilder \
|
||||
$(NULL)
|
||||
|
||||
jsautobuilderdir=$(pkgdatadir)-autobuilder/js
|
||||
jsautobuilder_DATA= \
|
||||
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/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/autobuilder/js/tasks/task-treecompose.js \
|
||||
src/autobuilder/js/tasks/task-ensure-disk-caches.js \
|
||||
src/autobuilder/js/tasks/task-repoweb.js \
|
||||
src/autobuilder/js/tasks/task-smoketest.js \
|
||||
src/autobuilder/js/tasks/task-zdisks.js \
|
||||
src/autobuilder/js/tasks/testbase.js \
|
||||
$(NULL)
|
||||
|
||||
testdatadir=$(pkgdatadir)-autobuilder
|
||||
testdata_DATA = src/autobuilder/rpm-ostree-export-journal-to-serialdev \
|
||||
src/autobuilder/rpm-ostree-export-journal-to-serialdev.service \
|
||||
$(NULL)
|
@ -1,16 +0,0 @@
|
||||
INTROSPECTION_GIRS += GSystem-1.0.gir
|
||||
|
||||
GSystem-1.0.gir: libgsystem.la Makefile
|
||||
GSystem_1_0_gir_NAMESPACE = GSystem
|
||||
GSystem_1_0_gir_VERSION = 1.0
|
||||
GSystem_1_0_gir_LIBS = libgsystem.la
|
||||
GSystem_1_0_gir_CFLAGS = $(libgsystem_cflags)
|
||||
GSystem_1_0_gir_SCANNERFLAGS = \
|
||||
--warn-all \
|
||||
--warn-error \
|
||||
--symbol-prefix=gs_ \
|
||||
--identifier-prefix=GS \
|
||||
--c-include="libgsystem.h" \
|
||||
$(NULL)
|
||||
GSystem_1_0_gir_INCLUDES = Gio-2.0
|
||||
GSystem_1_0_gir_FILES = $(libgsystem_la_SOURCES)
|
@ -30,4 +30,3 @@ EXTRA_DIST += autogen.sh COPYING
|
||||
|
||||
include Makefile-rpm-ostree.am
|
||||
include Makefile-man.am
|
||||
include Makefile-autobuilder.am
|
||||
|
@ -1,6 +1,6 @@
|
||||
Summary: Commit RPMs to an OSTree repository
|
||||
Name: rpm-ostree
|
||||
Version: 2015.1
|
||||
Version: 2014.1
|
||||
Release: 1%{?dist}
|
||||
#VCS: https://github.com/cgwalters/rpm-ostree
|
||||
# This tarball is generated via "make -f Makefile.dist-packaging dist-snapshot"
|
||||
@ -24,19 +24,6 @@ Requires: /usr/bin/yum
|
||||
This tool takes a set of packages, and commits them to an OSTree
|
||||
repository. At the moment, it is intended for use on build servers.
|
||||
|
||||
%package autobuilder
|
||||
Summary: Build server for rpm-ostree
|
||||
Group: System Environment/Base
|
||||
Requires: %{name}%{?_isa} = %{version}-%{release}
|
||||
Requires: /usr/bin/gjs
|
||||
Requires: /usr/bin/guestmount
|
||||
Requires: libguestfs-gobject
|
||||
BuildRequires: /usr/bin/g-ir-scanner
|
||||
BuildRequires: readline-devel
|
||||
|
||||
%description autobuilder
|
||||
An automatic build server for rpm-ostree.
|
||||
|
||||
%prep
|
||||
%setup -q -n %{name}-%{version}
|
||||
|
||||
@ -53,12 +40,6 @@ make install DESTDIR=$RPM_BUILD_ROOT INSTALL="install -p -c"
|
||||
%{_bindir}/rpm-ostree
|
||||
%{_libdir}/%{name}/
|
||||
|
||||
%files autobuilder
|
||||
%{_bindir}/rpm-ostree-autobuilder
|
||||
%{_libdir}/%{name}-autobuilder/
|
||||
%{_datadir}/%{name}-autobuilder/
|
||||
%{_mandir}/man1/*.gz
|
||||
|
||||
%changelog
|
||||
* Fri Mar 07 2014 Colin Walters <walters@verbum.org> - 2014.5-1
|
||||
- Initial package
|
||||
|
@ -1,181 +0,0 @@
|
||||
// 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;
|
||||
}
|
||||
});
|
@ -1,71 +0,0 @@
|
||||
// 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);
|
||||
}
|
||||
});
|
@ -1,137 +0,0 @@
|
||||
// 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' : 'rpm-ostree-autobuilder',
|
||||
'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('products.json');
|
||||
if (!manifest.query_exists(null)) {
|
||||
throw new Error("No products.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 rpm-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));
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
// 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);
|
||||
}
|
||||
});
|
@ -1,288 +0,0 @@
|
||||
// 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;
|
||||
|
||||
const Autobuilder = new Lang.Class({
|
||||
Name: 'Autobuilder',
|
||||
Extends: Builtin.Builtin,
|
||||
|
||||
DESCRIPTION: "Trigger builds every 3 hours",
|
||||
|
||||
_init: function() {
|
||||
this.parent();
|
||||
|
||||
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);
|
||||
|
||||
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));
|
||||
|
||||
// Build every hour
|
||||
this._buildTimeout = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT,
|
||||
60 * 60,
|
||||
Lang.bind(this, this._triggerTreeCompose));
|
||||
this._currentBuildPath = null;
|
||||
|
||||
|
||||
let path = Gio.File.new_for_path("cmd.socket");
|
||||
GSystem.shutil_rm_rf(path, cancellable);
|
||||
let commandSocketAddress = Gio.UnixSocketAddress.new(path.get_path());
|
||||
this._cmdSocketService = Gio.SocketService.new();
|
||||
this._cmdSocketService.add_address(commandSocketAddress,
|
||||
Gio.SocketType.STREAM,
|
||||
Gio.SocketProtocol.DEFAULT,
|
||||
null);
|
||||
|
||||
this._updateStatus();
|
||||
this._triggerTreeCompose();
|
||||
|
||||
this._cmdSocketService.connect('incoming', this._onCmdSocketIncoming.bind(this));
|
||||
this._clientIdSerial = 0;
|
||||
this._clients = {};
|
||||
|
||||
loop.run();
|
||||
},
|
||||
|
||||
_onCmdSocketIncoming: function(svc, connection, source) {
|
||||
this._clientIdSerial++;
|
||||
let clientData = { 'serial': this._clientIdSerial,
|
||||
'connection': connection,
|
||||
'datainstream': Gio.DataInputStream.new(connection.get_input_stream()),
|
||||
'outstandingWrite': false,
|
||||
'bufs': [] };
|
||||
this._clients[this._clientIdSerial] = clientData;
|
||||
print("Connection from client " + clientData.serial);
|
||||
clientData.datainstream.read_line_async(GLib.PRIORITY_DEFAULT, null,
|
||||
this._onClientLineReady.bind(this, clientData));
|
||||
},
|
||||
|
||||
_onClientSpliceComplete: function(clientData, stream, result) {
|
||||
stream.splice_finish(result);
|
||||
clientData.outstandingWrite = false;
|
||||
this._rescheduleClientWrite(clientData);
|
||||
},
|
||||
|
||||
_rescheduleClientWrite: function(clientData) {
|
||||
if (clientData.bufs.length == 0 ||
|
||||
clientData.outstandingWrite)
|
||||
return;
|
||||
let buf = clientData.bufs.shift();
|
||||
let membuf = Gio.MemoryInputStream.new_from_bytes(GLib.Bytes.new(buf));
|
||||
clientData.outstandingWrite = true;
|
||||
clientData.connection.get_output_stream()
|
||||
.splice_async(membuf, 0, GLib.PRIORITY_DEFAULT, null,
|
||||
this._onClientSpliceComplete.bind(this, clientData));
|
||||
},
|
||||
|
||||
_writeClient: function(clientData, buf) {
|
||||
clientData.bufs.push(buf + '\n');
|
||||
this._rescheduleClientWrite(clientData);
|
||||
},
|
||||
|
||||
_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;
|
||||
},
|
||||
|
||||
_onClientLineReady: function(clientData, stream, result) {
|
||||
let [line,len] = stream.read_line_finish_utf8(result);
|
||||
if (line == null) {
|
||||
print("Disconnect from client " + clientData.serial);
|
||||
delete this._clients[clientData.serial];
|
||||
return;
|
||||
}
|
||||
let spcIdx = line.indexOf(' ');
|
||||
let cmd, rest = "";
|
||||
if (spcIdx == -1)
|
||||
cmd = line;
|
||||
else {
|
||||
cmd = line.substring(0, spcIdx);
|
||||
rest = line.substring(spcIdx + 1);
|
||||
}
|
||||
print("[client " + clientData.serial + ']' + " Got cmd: " + cmd + " rest: " + rest);
|
||||
|
||||
if (cmd == 'status') {
|
||||
this._writeClient(clientData, this._status);
|
||||
} else if (cmd == 'treecompose') {
|
||||
this._triggerTreeCompose();
|
||||
this._writeClient(clientData, 'Treecompose queued');
|
||||
} else if (cmd == 'pushtask') {
|
||||
let nextSpcIdx = rest.indexOf(' ');
|
||||
let taskName;
|
||||
let args = {};
|
||||
if (nextSpcIdx != -1) {
|
||||
taskName = rest.substring(0, nextSpcIdx);
|
||||
rest = rest.substring(nextSpcIdx + 1);
|
||||
} else {
|
||||
taskName = rest;
|
||||
rest = null;
|
||||
}
|
||||
taskName = taskName.replace(/ /g, '');
|
||||
let parsedArgs = true;
|
||||
if (rest != null) {
|
||||
try {
|
||||
args = this._parseParameters(rest.split(' '));
|
||||
} catch (e) {
|
||||
this._writeClient(clientData, 'Invalid parameters: ' + e);
|
||||
parsedArgs = false;
|
||||
}
|
||||
}
|
||||
if (parsedArgs) {
|
||||
try {
|
||||
let lastBuildPath = this._resultsDir.get_child('tasks/treecompose');
|
||||
let lastBuildRealPath = GSystem.file_realpath(lastBuildPath);
|
||||
let taskPath = lastBuildRealPath.get_child(taskName);
|
||||
// Remove an already extant version of this
|
||||
GSystem.shutil_rm_rf(taskPath, null);
|
||||
this._taskmaster.pushTask(lastBuildRealPath, taskName, args, { force: true });
|
||||
this._updateStatus();
|
||||
this._writeClient(clientData, this._status);
|
||||
} catch (e) {
|
||||
this._writeClient(clientData, 'Caught exception: ' + e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this._writeClient(clientData, 'Unknown command: ' + cmd);
|
||||
}
|
||||
print("Done processing cmd: " + cmd);
|
||||
clientData.datainstream.read_line_async(GLib.PRIORITY_DEFAULT, null,
|
||||
this._onClientLineReady.bind(this, clientData));
|
||||
},
|
||||
|
||||
_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 runningTaskNames = [];
|
||||
let queuedTaskNames = [];
|
||||
for (let i = 0; i < taskstateList.length; i++) {
|
||||
let taskstate = taskstateList[i];
|
||||
if (taskstate.running) {
|
||||
runningTasks.push(taskstate);
|
||||
runningTaskNames.push(taskstate.task.name);
|
||||
} else {
|
||||
queuedTaskNames.push(taskstate.task.name);
|
||||
}
|
||||
}
|
||||
if (runningTasks.length == 0 && queuedTaskNames.length == 0) {
|
||||
newStatus = "[idle]";
|
||||
} else {
|
||||
newStatus = "running:";
|
||||
for (let i = 0; i < runningTasks.length; i++) {
|
||||
newStatus += ' ' + runningTasks[i].task.name;
|
||||
let params = runningTasks[i].task.taskData.parameters;
|
||||
for (let n in params) {
|
||||
newStatus += ' ' + n + '=' + params[n];
|
||||
}
|
||||
}
|
||||
if (queuedTaskNames.length > 0)
|
||||
newStatus += " queued:";
|
||||
for (let i = 0; i < queuedTaskNames.length; i++) {
|
||||
newStatus += " " + queuedTaskNames[i];
|
||||
}
|
||||
}
|
||||
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': runningTaskNames,
|
||||
'queued': queuedTaskNames,
|
||||
'systemLoad': loadAvg}, null);
|
||||
}
|
||||
},
|
||||
|
||||
_triggerTreeCompose: function() {
|
||||
let cancellable = null;
|
||||
|
||||
if (this._taskmaster.isTaskQueued('treecompose'))
|
||||
return true;
|
||||
|
||||
this._currentBuildPath = this._buildsDir.allocateNewVersion(cancellable);
|
||||
this._taskmaster.pushTask(this._currentBuildPath, 'treecompose', { });
|
||||
|
||||
this._updateStatus();
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
@ -1,78 +0,0 @@
|
||||
// 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 });
|
||||
}
|
||||
}));
|
||||
}
|
||||
});
|
@ -1,103 +0,0 @@
|
||||
// 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();
|
||||
}
|
||||
});
|
@ -1,54 +0,0 @@
|
||||
// -*- 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 LibQA = imports.libqa;
|
||||
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());
|
||||
}
|
||||
});
|
@ -1,42 +0,0 @@
|
||||
// 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);
|
||||
}
|
||||
});
|
@ -1,31 +0,0 @@
|
||||
// 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();
|
||||
}
|
||||
});
|
@ -1,98 +0,0 @@
|
||||
// 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 GSystem = imports.gi.GSystem;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
function openReplace(path, cancellable) {
|
||||
GSystem.shutil_rm_rf(path, cancellable);
|
||||
return path.replace(null, false,
|
||||
Gio.FileCreateFlags.REPLACE_DESTINATION,
|
||||
cancellable);
|
||||
}
|
@ -1,180 +0,0 @@
|
||||
// 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();
|
||||
},
|
||||
});
|
@ -1,82 +0,0 @@
|
||||
// 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);
|
||||
}
|
||||
|
@ -1,23 +0,0 @@
|
||||
// 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;
|
||||
}
|
@ -1,479 +0,0 @@
|
||||
// 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 Guestfs = imports.gi.Guestfs;
|
||||
|
||||
const GSystem = imports.gi.GSystem;
|
||||
const Params = imports.params;
|
||||
const ProcUtil = imports.procutil;
|
||||
const GuestFish = imports.guestfish;
|
||||
const JSUtil = imports.jsutil;
|
||||
|
||||
const BOOT_UUID = "fdcaea3b-2775-45ef-b441-b46a4a18e8c4";
|
||||
const ROOT_UUID = "d230f7f0-99d3-4244-8bd9-665428054831";
|
||||
const SWAP_UUID = "61f066e3-ac18-464e-bcc7-e7c3a623cec1";
|
||||
|
||||
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;
|
||||
qemuPathString = GLib.find_program_in_path('qemu-system-x86_64');
|
||||
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 or qemu-system-x86_64");
|
||||
}
|
||||
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 _installSyslinux(gfHandle, cancellable) {
|
||||
let syslinuxPaths = ['/usr/share/syslinux/mbr.bin', '/usr/lib/syslinux/mbr.bin'].map(function (a) { return Gio.File.new_for_path(a); });
|
||||
let syslinuxPath = null;
|
||||
for (let i = 0; i < syslinuxPaths.length; i++) {
|
||||
let path = syslinuxPaths[i];
|
||||
if (path.query_exists(null)) {
|
||||
syslinuxPath = path;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (syslinuxPath == null)
|
||||
throw new Error("Couldn't find syslinux mbr.bin in any of " + JSON.stringify(syslinuxPaths));
|
||||
|
||||
let [,syslinuxData,] = syslinuxPath.load_contents(cancellable);
|
||||
|
||||
gfHandle.pwrite_device("/dev/sda", syslinuxData, 0);
|
||||
}
|
||||
|
||||
function createDisk(diskpath, cancellable, params) {
|
||||
params = Params.parse(params, { sizeMb: 8 * 1024,
|
||||
bootsizeMb: 200,
|
||||
swapsizeMb: 64 });
|
||||
let guestfishProcess;
|
||||
|
||||
ProcUtil.runSync(['qemu-img', 'create', '-o', 'compat=0.10', '-f', 'qcow2', diskpath.get_path(), '' + params.sizeMb + 'M'], cancellable);
|
||||
let gfHandle = Guestfs.Session.new();
|
||||
gfHandle.add_drive(diskpath.get_path(), null);
|
||||
gfHandle.launch();
|
||||
gfHandle.part_init("/dev/sda", "mbr");
|
||||
let diskBytesize = gfHandle.blockdev_getsize64("/dev/sda");
|
||||
let diskSectorsize = gfHandle.blockdev_getss("/dev/sda");
|
||||
print(Format.vprintf("bytesize: %s sectorsize: %s", [diskBytesize, diskSectorsize]));
|
||||
let bootsizeSectors = 0;
|
||||
if (params.bootsizeMb)
|
||||
bootsizeSectors = params.bootsizeMb * 1024 / diskSectorsize * 1024;
|
||||
let swapsizeSectors = 0;
|
||||
if (params.swapsizeMb)
|
||||
swapsizeSectors = params.swapsizeMb * 1024 / diskSectorsize * 1024;
|
||||
let rootsizeSectors = diskBytesize / diskSectorsize - bootsizeSectors - swapsizeSectors - 64;
|
||||
print(Format.vprintf("boot: %s swap: %s root: %s", [bootsizeSectors, swapsizeSectors, rootsizeSectors]));
|
||||
let bootOffset = 64;
|
||||
let swapOffset = bootOffset + bootsizeSectors;
|
||||
let rootOffset = swapOffset + swapsizeSectors;
|
||||
let endOffset = rootOffset + rootsizeSectors;
|
||||
|
||||
let bootPartitionOffset = -1
|
||||
let swapPartitionOffset = -1;
|
||||
let rootPartitionOffset = 1;
|
||||
if (bootsizeSectors > 0) {
|
||||
gfHandle.part_add("/dev/sda", "p", bootOffset, swapOffset - 1);
|
||||
bootPartitionOffset = 1;
|
||||
rootPartitionOffset++;
|
||||
}
|
||||
if (swapsizeSectors > 0) {
|
||||
gfHandle.part_add("/dev/sda", "p", swapOffset, rootOffset - 1);
|
||||
swapPartitionOffset = 2;
|
||||
rootPartitionOffset++;
|
||||
}
|
||||
gfHandle.part_add("/dev/sda", "p", rootOffset, endOffset - 1);
|
||||
if (bootsizeSectors > 0) {
|
||||
gfHandle.mkfs("ext4", "/dev/sda" + bootPartitionOffset, null);
|
||||
gfHandle.set_e2uuid("/dev/sda1", BOOT_UUID);
|
||||
}
|
||||
if (swapsizeSectors > 0) {
|
||||
gfHandle.mkswap_U(SWAP_UUID, "/dev/sda" + swapPartitionOffset);
|
||||
}
|
||||
let rootPartition = "/dev/sda" + rootPartitionOffset;
|
||||
gfHandle.mkfs("xfs", rootPartition, null);
|
||||
gfHandle.xfs_admin(rootPartition, new Guestfs.XfsAdmin({ "uuid": ROOT_UUID}));
|
||||
gfHandle.mount(rootPartition, "/");
|
||||
gfHandle.mkdir_mode("/boot", 493);
|
||||
if (bootPartitionOffset > 0)
|
||||
gfHandle.mount("/dev/sda" + bootPartitionOffset, "/boot");
|
||||
gfHandle.extlinux("/boot");
|
||||
// It's understandable that extlinux wants the loader to be
|
||||
// immutable...except that later breaks our ability to set SELinux
|
||||
// security contexts on it.
|
||||
gfHandle.set_e2attrs("/boot/ldlinux.sys", "i", new Guestfs.SetE2attrs({ clear: Guestfs.Tristate.TRUE }));
|
||||
gfHandle.umount_all();
|
||||
_installSyslinux(gfHandle, cancellable);
|
||||
gfHandle.part_set_bootable("/dev/sda", 1, true);
|
||||
gfHandle.shutdown();
|
||||
}
|
||||
|
||||
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('rpm-ostree-export-journal-to-serialdev');
|
||||
let exportScriptService = datadir.resolve_relative_path('rpm-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,
|
||||
params) {
|
||||
params = Params.parse(params, { addKernelArgs: ['quiet'] });
|
||||
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 20\nPROMPT 1\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', '--disable-fsync', '--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 deployCmd = adminCmd.concat([]);
|
||||
deployCmd.push('deploy');
|
||||
|
||||
let rootArg = 'root=UUID=' + ROOT_UUID;
|
||||
deployCmd.push('--karg=' + rootArg);
|
||||
|
||||
for (let i = 0; i < params.addKernelArgs.length; i++)
|
||||
deployCmd.push('--karg=' + params.addKernelArgs[i]);
|
||||
|
||||
deployCmd.push.apply(deployCmd, ['--os=' + osname, '--origin-file=' + tmpOrigin.get_path(), revOrTarget]);
|
||||
|
||||
ProcUtil.runSync(deployCmd, cancellable,
|
||||
{logInitiation: true, env: adminEnv});
|
||||
|
||||
let sysroot = OSTree.Sysroot.new(mntdir);
|
||||
sysroot.load(null);
|
||||
let deployments = sysroot.get_deployments();
|
||||
let newDeployment = deployments[0];
|
||||
let newDeploymentDirectory = sysroot.get_deployment_directory(newDeployment);
|
||||
|
||||
let defaultFstab = 'UUID=' + ROOT_UUID + ' / xfs defaults 1 1\n\
|
||||
UUID=' + BOOT_UUID + ' /boot ext4 defaults 1 2\n\
|
||||
UUID=' + SWAP_UUID + ' swap swap defaults 0 0\n';
|
||||
let fstabPath = newDeploymentDirectory.resolve_relative_path('etc/fstab');
|
||||
fstabPath.replace_contents(defaultFstab, null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, cancellable);
|
||||
|
||||
print("Labeling deployment root");
|
||||
let relabelCmd = adminCmd.concat(['instutil', 'selinux-ensure-labeled', newDeploymentDirectory.get_path(), ""]);
|
||||
ProcUtil.runSync(relabelCmd,
|
||||
cancellable,
|
||||
{ logInitiation: true });
|
||||
};
|
||||
|
||||
function requestSnapshotForTree(task, resultdir, refname, revision) {
|
||||
let imageCacheDir = task.cachedir.get_child('images');
|
||||
let osname = task._products['osname'];
|
||||
let originRepoUrl = this._products['repo'];
|
||||
|
||||
let refUnix = ref.replace(/\//g, '-');
|
||||
let diskDir = this._imageCacheDir.get_child(refUnix);
|
||||
GSystem.file_ensure_directory(diskDir, true, cancellable);
|
||||
let diskName = revision + '.qcow2';
|
||||
let diskPath = diskDir.get_child(diskName);
|
||||
let diskPathTmp = diskDir.get_child(diskName);
|
||||
if (!diskPath.query_exists(null))
|
||||
LibQA.createDisk(diskPath, cancellable);
|
||||
let mntdir = Gio.File.new_for_path('mnt');
|
||||
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, ref, revision, originRepoUrl,
|
||||
cancellable);
|
||||
} finally {
|
||||
gfmnt.umount(cancellable);
|
||||
}
|
||||
|
||||
print("Successfully updated " + diskDir.get_path() + " to " + revision);
|
||||
}
|
||||
|
||||
function getACachedDisk(diskDir, cancellable) {
|
||||
let cachedDisk = null;
|
||||
let e = null;
|
||||
try {
|
||||
e = diskDir.enumerate_children('standard::name', 0, cancellable);
|
||||
let info;
|
||||
while ((info = e.next_file(cancellable)) != null) {
|
||||
let name = info.get_name();
|
||||
if (!JSUtil.stringEndswith(name, '.qcow2'))
|
||||
continue;
|
||||
cachedDisk = e.get_child(info);
|
||||
break;
|
||||
}
|
||||
} finally {
|
||||
if (e) e.close(null);
|
||||
}
|
||||
|
||||
return cachedDisk;
|
||||
}
|
@ -1,96 +0,0 @@
|
||||
// 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 > 0 && (ARGV[0] == '-h' || ARGV[0] == '--help')) {
|
||||
ecode = usage(0);
|
||||
} else {
|
||||
let name;
|
||||
if (ARGV.length < 1) {
|
||||
name = "autobuilder";
|
||||
} else {
|
||||
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;
|
||||
|
||||
|
||||
|
@ -1,34 +0,0 @@
|
||||
// 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;
|
||||
}
|
@ -1,240 +0,0 @@
|
||||
// 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);
|
||||
});
|
||||
}
|
@ -1,199 +0,0 @@
|
||||
//
|
||||
// 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));
|
||||
}
|
||||
});
|
@ -1,27 +0,0 @@
|
||||
// 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;
|
||||
}
|
@ -1,471 +0,0 @@
|
||||
// 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, pushParams) {
|
||||
this._setTaskBuildPath(taskName, buildPath);
|
||||
this._pushTask(taskName, parameters, pushParams);
|
||||
},
|
||||
|
||||
_pushTask: function(name, parameters, pushParams) {
|
||||
let taskDef = this._taskset.getTaskDef(name);
|
||||
let taskData = new TaskData(taskDef, parameters);
|
||||
pushParams = Params.parse(pushParams, { force: false });
|
||||
if (!this._isTaskPending(name)) {
|
||||
let scheduleMinSecs = taskDef.TaskScheduleMinSecs;
|
||||
if (!pushParams.force && 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.shutil_rm_rf(this.taskCwd, cancellable);
|
||||
GSystem.file_ensure_directory(this.taskCwd, false, cancellable);
|
||||
|
||||
let baseArgv = ['rpm-ostree-autobuilder', '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);
|
||||
}
|
||||
});
|
@ -1,123 +0,0 @@
|
||||
// -*- indent-tabs-mode: nil; tab-width: 2; -*-
|
||||
// Copyright (C) 2013,2014 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 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 TaskEnsureDiskCaches = new Lang.Class({
|
||||
Name: 'TaskEnsureDiskCaches',
|
||||
Extends: Task.Task,
|
||||
|
||||
TaskDef: {
|
||||
TaskName: "ensure-disk-caches",
|
||||
TaskAfter: ['treecompose'],
|
||||
},
|
||||
|
||||
DefaultParameters: { regenerate: null },
|
||||
|
||||
_ensureDiskForProduct: function(ref, revision, cancellable) {
|
||||
let refUnix = ref.replace(/\//g, '-');
|
||||
let diskDir = this._imageCacheDir.get_child(refUnix);
|
||||
GSystem.file_ensure_directory(diskDir, true, cancellable);
|
||||
let cachedDisk = LibQA.getACachedDisk(diskDir, cancellable);
|
||||
if (cachedDisk) {
|
||||
let name = cachedDisk.get_basename();
|
||||
if (name.indexOf(revision) == 0) {
|
||||
print("Cached disk is up to date");
|
||||
return;
|
||||
}
|
||||
if (!this.parameters.regenerate) {
|
||||
print("Found cached disk " + cachedDisk.get_path() + " for " + ref);
|
||||
return;
|
||||
} else {
|
||||
print("Regenerating...");
|
||||
}
|
||||
}
|
||||
|
||||
let diskPath = diskDir.get_child(revision + '.qcow2');
|
||||
let diskPathTmp = diskDir.get_child(revision + '.qcow2.tmp');
|
||||
LibQA.createDisk(diskPathTmp, cancellable);
|
||||
let mntdir = Gio.File.new_for_path('mnt');
|
||||
GSystem.file_ensure_directory(mntdir, true, cancellable);
|
||||
let gfmnt = new GuestFish.GuestMount(diskPathTmp, { partitionOpts: LibQA.DEFAULT_GF_PARTITION_OPTS,
|
||||
readWrite: true });
|
||||
gfmnt.mount(mntdir, cancellable);
|
||||
try {
|
||||
let osname = this._products['osname'];
|
||||
let originRepoUrl = this._products['repo'];
|
||||
let addKernelArgs = this._products['kargs'];
|
||||
if (!addKernelArgs) addKernelArgs = [];
|
||||
LibQA.pullDeploy(mntdir, this.repo, osname, ref, revision, originRepoUrl,
|
||||
cancellable, { addKernelArgs: addKernelArgs });
|
||||
print("Doing initial labeling");
|
||||
ProcUtil.runSync(['ostree', 'admin', '--sysroot=' + mntdir.get_path(),
|
||||
'instutil', 'selinux-ensure-labeled',
|
||||
mntdir.get_path(),
|
||||
""],
|
||||
cancellable,
|
||||
{ logInitiation: true });
|
||||
} finally {
|
||||
gfmnt.umount(cancellable);
|
||||
}
|
||||
GSystem.file_rename(diskPathTmp, diskPath, cancellable);
|
||||
let newDiskName = diskPath.get_basename();
|
||||
print("Successfully created disk cache " + diskPath.get_path());
|
||||
let e = null;
|
||||
try {
|
||||
e = diskDir.enumerate_children('standard::name', 0, cancellable);
|
||||
let info;
|
||||
while ((info = e.next_file(cancellable)) != null) {
|
||||
let name = info.get_name();
|
||||
if (!JSUtil.stringEndswith(name, '.qcow2'))
|
||||
continue;
|
||||
if (name != newDiskName) {
|
||||
let child = e.get_child(info);
|
||||
print("Removing old " + child.get_path());
|
||||
GSystem.file_unlink(child, cancellable);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (e) e.close(null);
|
||||
}
|
||||
},
|
||||
|
||||
execute: function(cancellable) {
|
||||
this._imageCacheDir = this.cachedir.get_child('images');
|
||||
this._products = JsonUtil.loadJson(this.workdir.get_child('products.json'), cancellable);
|
||||
this._productsBuilt = JsonUtil.loadJson(this.builddir.get_child('products-built.json'), cancellable);
|
||||
let productTrees = this._productsBuilt['trees'];
|
||||
for (let ref in productTrees) {
|
||||
this._ensureDiskForProduct(ref, productTrees[ref]['rev'], cancellable);
|
||||
}
|
||||
},
|
||||
});
|
@ -1,156 +0,0 @@
|
||||
// -*- indent-tabs-mode: nil; tab-width: 2; -*-
|
||||
// Copyright (C) 2013,2014 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 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 TaskRepoweb = new Lang.Class({
|
||||
Name: 'TaskRepoweb',
|
||||
Extends: Task.Task,
|
||||
|
||||
TaskDef: {
|
||||
TaskName: "repoweb",
|
||||
TaskAfter: ['build']
|
||||
},
|
||||
|
||||
MAXDEPTH: 100,
|
||||
|
||||
DefaultParameters: { },
|
||||
|
||||
_readFileLinesToObject: function(path, cancellable) {
|
||||
let stream = path.read(cancellable);
|
||||
let datain = Gio.DataInputStream.new(stream);
|
||||
let result = {};
|
||||
while (true) {
|
||||
let [line, len] = dataIn.read_line_utf8(cancellable);
|
||||
if (line == null)
|
||||
break;
|
||||
result[line] = 1;
|
||||
}
|
||||
datain.close(null);
|
||||
return result;
|
||||
},
|
||||
|
||||
_statsForDiff: function(diffTxt) {
|
||||
let nAdded = 0;
|
||||
let nRemoved = 0;
|
||||
let nModified = 0;
|
||||
|
||||
let i = 0;
|
||||
while (true) {
|
||||
let next = diffTxt.indexOf('\n', i);
|
||||
if (diffTxt[i] == 'A')
|
||||
nAdded++;
|
||||
else if (diffTxt[i] == 'D')
|
||||
nRemoved++;
|
||||
else if (diffTxt[i] == 'M')
|
||||
nModified++;
|
||||
if (next < 0)
|
||||
break;
|
||||
else
|
||||
i = next+1;
|
||||
}
|
||||
return [nAdded, nRemoved, nModified];
|
||||
},
|
||||
|
||||
_generateHistoryFor: function(refname, revision, curDepth, cancellable) {
|
||||
let commitDataOut = this._repoWebPath.get_child('commit-' + revision + '.json');
|
||||
if (curDepth > this.MAXDEPTH)
|
||||
return;
|
||||
if (commitDataOut.query_exists(null))
|
||||
return;
|
||||
|
||||
let [,commitObject] = this._repo.load_variant(OSTree.ObjectType.COMMIT,
|
||||
revision);
|
||||
let ts = OSTree.commit_get_timestamp(commitObject);
|
||||
let parent = OSTree.commit_get_parent(commitObject);
|
||||
|
||||
if (parent) {
|
||||
let [,parentCommit] = this._repo.load_variant_if_exists(OSTree.ObjectType.COMMIT,
|
||||
parent);
|
||||
if (!parentCommit) {
|
||||
print("For ref " + refname + ": couldn't find parent " + parent);
|
||||
parent = null;
|
||||
}
|
||||
}
|
||||
|
||||
let commitData = { 't': ts,
|
||||
'parent': parent };
|
||||
|
||||
if (parent) {
|
||||
let argv = ['ostree', '--repo='+this._repo.get_path().get_path(), 'diff', parent, revision];
|
||||
let procctx = new GSystem.SubprocessContext({ argv: argv });
|
||||
let diffTxtPath = Gio.File.new_for_path('diff.txt');
|
||||
procctx.set_stdout_file_path(diffTxtPath.get_path());
|
||||
let proc = GSystem.Subprocess.new(procctx, cancellable);
|
||||
proc.init(null);
|
||||
proc.wait_sync_check(cancellable);
|
||||
let diffTxt = GSystem.file_load_contents_utf8(diffTxtPath, cancellable);
|
||||
let [nAdded, nRemoved, nModified] = this._statsForDiff(diffTxt);
|
||||
GSystem.file_unlink(diffTxtPath, cancellable);
|
||||
commitData['diffstats'] = { 'added': nAdded,
|
||||
'removed': nRemoved,
|
||||
'modified': nModified
|
||||
};
|
||||
let diffData = {'difftxt': diffTxt};
|
||||
JsonUtil.writeJsonFileAtomic(this._repoWebPath.get_child('diff-' + revision + '.json'), diffData, cancellable);
|
||||
}
|
||||
|
||||
print("Generated data for " + revision);
|
||||
JsonUtil.writeJsonFileAtomic(commitDataOut, commitData, cancellable);
|
||||
|
||||
if (parent)
|
||||
this._generateHistoryFor(refname, parent, curDepth+1, cancellable);
|
||||
},
|
||||
|
||||
execute: function(cancellable) {
|
||||
this._repoPath = this.workdir.get_child('repo');
|
||||
this._repoWebPath = this.workdir.get_child('repoweb-data');
|
||||
|
||||
GSystem.file_ensure_directory(this._repoWebPath, true, cancellable);
|
||||
|
||||
this._repo = new OSTree.Repo({ path: this._repoPath });
|
||||
this._repo.open(cancellable);
|
||||
|
||||
let [,allRefs] = this._repo.list_refs(null, cancellable);
|
||||
let allRefsCopy = {};
|
||||
|
||||
for (let refName in allRefs) {
|
||||
let revision = allRefs[refName];
|
||||
allRefsCopy[refName] = revision;
|
||||
print("Generating history for " + refName);
|
||||
this._generateHistoryFor(refName, revision, 0, cancellable);
|
||||
}
|
||||
JsonUtil.writeJsonFileAtomic(this._repoWebPath.get_child('refs.json'), { 'refs': allRefsCopy }, cancellable);
|
||||
},
|
||||
});
|
@ -1,99 +0,0 @@
|
||||
// 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 = ['rpm-ostree-autobuilder', '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);
|
||||
}
|
||||
});
|
@ -1,51 +0,0 @@
|
||||
// -*- 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: ['ensure-disk-caches'],
|
||||
},
|
||||
|
||||
RequiredMessageIDs: ["39f53479d3a045ac8e11786248231fbf" // multi-user.target
|
||||
],
|
||||
|
||||
FailedMessageIDs: ["fc2e22bc6ee647b6b90729ab34a250b1", // coredump
|
||||
],
|
||||
|
||||
CompletedTag: 'smoketested'
|
||||
});
|
@ -1,204 +0,0 @@
|
||||
// Copyright (C) 2011,2013,2014 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 OSTree = imports.gi.OSTree;
|
||||
|
||||
const Task = imports.task;
|
||||
const Params = imports.params;
|
||||
const FileUtil = imports.fileutil;
|
||||
const ProcUtil = imports.procutil;
|
||||
const JSUtil = imports.jsutil;
|
||||
const StreamUtil = imports.streamutil;
|
||||
const JsonUtil = imports.jsonutil;
|
||||
const Snapshot = imports.snapshot;
|
||||
const BuildUtil = imports.buildutil;
|
||||
const Vcs = imports.vcs;
|
||||
|
||||
const TaskTreeCompose = new Lang.Class({
|
||||
Name: "TaskTreeCompose",
|
||||
Extends: Task.Task,
|
||||
|
||||
TaskDef: {
|
||||
TaskName: "treecompose",
|
||||
},
|
||||
|
||||
DefaultParameters: {onlyTreesMatching: null},
|
||||
|
||||
BuildState: { 'failed': 'failed',
|
||||
'successful': 'successful',
|
||||
'unchanged': 'unchanged' },
|
||||
|
||||
_composeProduct: function(ref, treefileData, cancellable) {
|
||||
let [,origRevision] = this.ostreeRepo.resolve_rev(ref, true);
|
||||
if (origRevision == null)
|
||||
print("Starting new build of " + ref);
|
||||
else
|
||||
print("Starting build of " + ref + " previous: " + origRevision);
|
||||
|
||||
let argv = ['rpm-ostree'];
|
||||
|
||||
let treefilePath = Gio.File.new_for_path('treefile.json');
|
||||
JsonUtil.writeJsonFileAtomic(treefilePath, treefileData, cancellable);
|
||||
|
||||
argv.push.apply(argv, ['treecompose', treefilePath.get_path()]);
|
||||
let productNameUnix = ref.replace(/\//g, '_');
|
||||
let buildOutputPath = Gio.File.new_for_path('log-' + productNameUnix + '.txt');
|
||||
print("Running: " + argv.map(GLib.shell_quote).join(' '));
|
||||
let procContext = new GSystem.SubprocessContext({ argv: argv });
|
||||
GSystem.shutil_rm_rf(buildOutputPath, cancellable);
|
||||
procContext.set_stdout_file_path(buildOutputPath.get_path());
|
||||
procContext.set_stderr_disposition(GSystem.SubprocessStreamDisposition.STDERR_MERGE);
|
||||
let proc = new GSystem.Subprocess({ context: procContext });
|
||||
proc.init(cancellable);
|
||||
try {
|
||||
proc.wait_sync_check(cancellable);
|
||||
} catch (e) {
|
||||
print("Build of " + ref + " failed");
|
||||
return this.BuildState.failed;
|
||||
}
|
||||
let [,newRevision] = this.ostreeRepo.resolve_rev(ref, true);
|
||||
treefileData.rev = newRevision;
|
||||
|
||||
if (origRevision == newRevision)
|
||||
return this.BuildState.unchanged;
|
||||
return this.BuildState.successful;
|
||||
},
|
||||
|
||||
_inheritAttribute: function(obj, source, attrName, defaultValue) {
|
||||
let value = source[attrName];
|
||||
if (!value)
|
||||
value = this._productData[attrName];
|
||||
if (!value)
|
||||
value = defaultValue;
|
||||
obj[attrName] = value;
|
||||
},
|
||||
|
||||
_inheritExtendAttributeList: function(obj, source, attrName) {
|
||||
let result = [];
|
||||
let value = this._productData[attrName];
|
||||
if (value)
|
||||
result.push.apply(result, value);
|
||||
let subValue = source[attrName];
|
||||
if (subValue)
|
||||
result.push.apply(result, subValue);
|
||||
obj[attrName] = result;
|
||||
},
|
||||
|
||||
_generateTreefile: function(ref, release, architecture,
|
||||
subproductData) {
|
||||
let treefile = JSON.parse(JSON.stringify(this._productData));
|
||||
delete treefile.products;
|
||||
|
||||
treefile.ref = ref;
|
||||
|
||||
this._inheritExtendAttributeList(treefile, subproductData, 'packages');
|
||||
this._inheritExtendAttributeList(treefile, subproductData, 'postprocess');
|
||||
this._inheritExtendAttributeList(treefile, subproductData, 'units');
|
||||
|
||||
treefile.release = release;
|
||||
delete treefile.releases;
|
||||
treefile.architecture = architecture;
|
||||
delete treefile.architectures;
|
||||
|
||||
this._inheritAttribute(treefile, subproductData, "comment", "");
|
||||
|
||||
this._inheritExtendAttributeList(treefile, subproductData, 'repos');
|
||||
this._inheritExtendAttributeList(treefile, subproductData, 'repos_data');
|
||||
|
||||
let overrideRepo = this.workdir.get_child('overrides');
|
||||
if (overrideRepo.query_exists(null)) {
|
||||
print("Using override repo: " + overrideRepo.get_path());
|
||||
treefile.repos_data.push('[rpm-ostree-internal-overrides]\n' +
|
||||
'name=Internal rpm-ostee overrides\n' +
|
||||
'baseurl=file://' + overrideRepo.get_path() + '\n' +
|
||||
'metadata_expire=1m\n' +
|
||||
'enabled=1\ngpgcheck=0\n');
|
||||
}
|
||||
|
||||
return treefile;
|
||||
},
|
||||
|
||||
execute: function(cancellable) {
|
||||
let productPath = this.workdir.get_child('products.json');
|
||||
let productData = JsonUtil.loadJson(productPath, cancellable);
|
||||
this._productData = productData;
|
||||
|
||||
let releases = productData['releases'];
|
||||
let architectures = productData['architectures'];
|
||||
let products = productData['products'];
|
||||
let successful = [];
|
||||
let failed = [];
|
||||
let unchanged = [];
|
||||
let productTrees = {};
|
||||
let buildmasterName = 'buildmaster';
|
||||
for (let i = 0; i < releases.length; i++) {
|
||||
for (let j = 0; j < architectures.length; j++) {
|
||||
for (let productName in products) {
|
||||
for (let treeName in products[productName]) {
|
||||
let release = releases[i];
|
||||
let architecture = architectures[j];
|
||||
let ref = [this._productData['osname'], release, architecture, buildmasterName, productName, treeName].join('/');
|
||||
if (this.parameters.onlyTreesMatching &&
|
||||
ref.indexOf(this.parameters.onlyTreesMatching) == -1) {
|
||||
log("Skipping " + ref + " which does not match " + this.parameters.onlyTreesMatching);
|
||||
continue;
|
||||
}
|
||||
let subproductData = products[productName][treeName];
|
||||
let treefileData = this._generateTreefile(ref, release, architecture, subproductData);
|
||||
let result = this._composeProduct(ref, treefileData, cancellable);
|
||||
switch (result) {
|
||||
case this.BuildState.successful: {
|
||||
productTrees[ref] = treefileData;
|
||||
successful.push(ref);
|
||||
}
|
||||
break;
|
||||
case this.BuildState.failed: {
|
||||
failed.push(ref);
|
||||
}
|
||||
break;
|
||||
case this.BuildState.unchanged: {
|
||||
productTrees[ref] = treefileData;
|
||||
unchanged.push(ref);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Error("Invalid result from composeProduct: " + result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let productsBuilt = { successful: successful,
|
||||
failed: failed,
|
||||
unchanged: unchanged,
|
||||
trees: productTrees };
|
||||
let productsBuiltPath = this.builddir.get_child('products-built.json');
|
||||
JsonUtil.writeJsonFileAtomic(productsBuiltPath, productsBuilt, cancellable);
|
||||
print("Successful: " + successful.join(' '));
|
||||
print("Failed: " + failed.join(' '));
|
||||
print("Unchanged: " + unchanged.join(' '));
|
||||
|
||||
let modifiedPath = Gio.File.new_for_path('modified.json');
|
||||
JsonUtil.writeJsonFileAtomic(modifiedPath, { modified: successful.length > 0 }, cancellable);
|
||||
}
|
||||
});
|
@ -1,145 +0,0 @@
|
||||
// -*- indent-tabs-mode: nil; tab-width: 2; -*-
|
||||
// Copyright (C) 2013,2014 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 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 TaskZDisks = new Lang.Class({
|
||||
Name: 'TaskZDisks',
|
||||
Extends: Task.Task,
|
||||
|
||||
TaskDef: {
|
||||
TaskName: "zdisks",
|
||||
TaskAfter: ['ensure-disk-caches']
|
||||
},
|
||||
|
||||
_exportDiskForProduct: function(ref, revision, cancellable) {
|
||||
let refUnix = ref.replace(/\//g, '-');
|
||||
let diskDir = this._imageExportDir.get_child(refUnix);
|
||||
GSystem.file_ensure_directory(diskDir, true, cancellable);
|
||||
let latestDisk = LibQA.getACachedDisk(this._imageCacheDir.get_child(refUnix), cancellable);
|
||||
|
||||
if (!latestDisk) {
|
||||
throw new Error("No cached disk found for " + ref);
|
||||
}
|
||||
|
||||
let formats = this._products['image_formats'];
|
||||
if (!formats) formats = [];
|
||||
|
||||
let newDiskName = null;
|
||||
let newVdiName = null;
|
||||
|
||||
if (formats.indexOf('qcow2') >= 0) {
|
||||
let newDiskPath = diskDir.get_child(revision + '.qcow2.xz');
|
||||
newDiskName = newDiskPath.get_basename();
|
||||
if (!newDiskPath.query_exists(null)) {
|
||||
let newDiskPathTmp = Gio.File.new_for_path(revision + '.qcow2.xz.tmp');
|
||||
|
||||
print("Creating " + newDiskPath.get_path());
|
||||
|
||||
let xzCtx = new GSystem.SubprocessContext({ argv: [ 'xz' ] })
|
||||
xzCtx.set_stdin_file_path(latestDisk.get_path());
|
||||
xzCtx.set_stdout_file_path(newDiskPathTmp.get_path());
|
||||
let xz = new GSystem.Subprocess({ context: xzCtx });
|
||||
xz.init(cancellable);
|
||||
xz.wait_sync_check(cancellable);
|
||||
|
||||
print("Completed compression, renaming to " + newDiskPath.get_path());
|
||||
GSystem.file_rename(newDiskPathTmp, newDiskPath, cancellable);
|
||||
} else {
|
||||
print("Already have " + newDiskPath.get_path());
|
||||
}
|
||||
BuildUtil.atomicSymlinkSwap(newDiskPath.get_parent().get_child('latest-qcow2.xz'),
|
||||
newDiskPath, cancellable);
|
||||
}
|
||||
|
||||
if (formats.indexOf('vdi') >= 0) {
|
||||
let vdiTmpPath = Gio.File.new_for_path(revision + '.vdi.tmp');
|
||||
let newVdiPath = diskDir.get_child(revision + '.vdi.bz2');
|
||||
newVdiName = newVdiPath.get_basename();
|
||||
if (!newVdiPath.query_exists(null)) {
|
||||
let newVdiPathTmp = Gio.File.new_for_path(revision + '.vdi.bz2.tmp');
|
||||
|
||||
print("Creating " + vdiTmpPath.get_path());
|
||||
ProcUtil.runSync(['qemu-img', 'convert', '-O', 'vdi',
|
||||
latestDisk.get_path(), vdiTmpPath.get_path()],
|
||||
cancellable,
|
||||
{ logInitiation: true });
|
||||
|
||||
print("Creating " + newVdiPathTmp.get_path());
|
||||
let xzCtx = new GSystem.SubprocessContext({ argv: [ 'bzip2' ] })
|
||||
xzCtx.set_stdin_file_path(vdiTmpPath.get_path());
|
||||
xzCtx.set_stdout_file_path(newVdiPathTmp.get_path());
|
||||
let xz = new GSystem.Subprocess({ context: xzCtx });
|
||||
xz.init(cancellable);
|
||||
xz.wait_sync_check(cancellable);
|
||||
|
||||
print("Completed VDI compression, renaming to " + newVdiPathTmp.get_path());
|
||||
GSystem.file_rename(newVdiPathTmp, newVdiPath, cancellable);
|
||||
GSystem.file_unlink(vdiTmpPath, cancellable);
|
||||
} else {
|
||||
print("Already have " + newVdiPath.get_path());
|
||||
}
|
||||
BuildUtil.atomicSymlinkSwap(newVdiPath.get_parent().get_child('latest-vdi.bz2'),
|
||||
newVdiPath, cancellable);
|
||||
}
|
||||
|
||||
let e = null;
|
||||
try {
|
||||
e = diskDir.enumerate_children('standard::name', 0, cancellable);
|
||||
let info;
|
||||
while ((info = e.next_file(cancellable)) != null) {
|
||||
let name = info.get_name();
|
||||
if (name == newDiskName || name == newVdiName)
|
||||
continue;
|
||||
let child = e.get_child(info);
|
||||
if (JSUtil.stringEndswith(name, '.qcow2.xz') ||
|
||||
JSUtil.stringEndswith(name, '.vdi.bz2')) {
|
||||
print("Removing old " + child.get_path());
|
||||
GSystem.file_unlink(child, cancellable);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (e) e.close(null);
|
||||
}
|
||||
},
|
||||
|
||||
execute: function(cancellable) {
|
||||
this._imageCacheDir = this.cachedir.get_child('images');
|
||||
this._imageExportDir = this.workdir.get_child('images/auto');
|
||||
this._products = JsonUtil.loadJson(this.workdir.get_child('products.json'), cancellable);
|
||||
this._productsBuilt = JsonUtil.loadJson(this.builddir.get_child('products-built.json'), cancellable);
|
||||
let productTrees = this._productsBuilt['trees'];
|
||||
for (let ref in productTrees) {
|
||||
this._exportDiskForProduct(ref, productTrees[ref]['rev'], cancellable);
|
||||
}
|
||||
}
|
||||
});
|
@ -1,666 +0,0 @@
|
||||
// -*- 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 GuestFish = imports.guestfish;
|
||||
|
||||
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, osname, repo, diskPath, cancellable) {
|
||||
print("Testing disk " + diskPath.get_path());
|
||||
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;
|
||||
|
||||
// HACK
|
||||
if (diskPath.get_basename().indexOf('x86_64') >= 0)
|
||||
this._diskArch = 'x86_64';
|
||||
else
|
||||
this._diskArch = 'i686';
|
||||
|
||||
let qemuArgs = LibQA.getDefaultQemuOptions({ parallel: true });
|
||||
|
||||
let [gfmnt, mntdir] = LibQA.newReadWriteMount(diskPath, cancellable);
|
||||
try {
|
||||
LibQA.modifyBootloaderAppendKernelArgs(mntdir, ["console=ttyS0"], cancellable);
|
||||
|
||||
let [currentDir, currentEtcDir] = LibQA.getDeployDirs(mntdir, osname);
|
||||
|
||||
LibQA.injectExportJournal(currentDir, currentEtcDir, 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=' + diskPath.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(diskPath, cancellable);
|
||||
try {
|
||||
this._parentTask._postQemu(mntdir, cancellable);
|
||||
} finally {
|
||||
gfmnt.umount(cancellable);
|
||||
}
|
||||
|
||||
if (this._failed) {
|
||||
return this._failedMessage;
|
||||
}
|
||||
|
||||
print("Completed testing of " + diskPath.get_basename());
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
const TestBase = new Lang.Class({
|
||||
Name: 'TestBase',
|
||||
Extends: Task.Task,
|
||||
|
||||
TaskDef: {
|
||||
TaskName: "testbase",
|
||||
TaskAfter: ['builddisks'],
|
||||
},
|
||||
|
||||
TestTrees: ['-runtime'],
|
||||
CompleteIdleWaitSeconds: 10,
|
||||
|
||||
BaseRequiredMessageIDs: ["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) {
|
||||
},
|
||||
|
||||
_fileLinkHere: function(diskPath, cancellable) {
|
||||
let dest = Gio.File.new_for_path(diskPath.get_basename());
|
||||
try {
|
||||
GSystem.file_linkcopy(diskPath, dest, 0, cancellable);
|
||||
return dest;
|
||||
} catch (e) {
|
||||
if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND))
|
||||
throw e;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
getDiskSnapshotForRevision: function(ref, revision, cancellable) {
|
||||
let osname = this._products['osname'];
|
||||
let originRepoUrl = this._products['repo'];
|
||||
|
||||
let snapshotPath = Gio.File.new_for_path('overlay-' + revision + '.qcow2');
|
||||
|
||||
let refUnix = ref.replace(/\//g, '-');
|
||||
let diskDir = this._imageCacheDir.get_child(refUnix);
|
||||
GSystem.file_ensure_directory(diskDir, true, cancellable);
|
||||
let exactDiskName = revision + '.qcow2';
|
||||
let exactDiskPath = diskDir.get_child(exactDiskName);
|
||||
let cwdDiskLink = this._fileLinkHere(exactDiskPath, cancellable);
|
||||
if (cwdDiskLink) {
|
||||
print("Acquired link to exact disk " + cwdDiskLink.get_path());
|
||||
LibQA.createDiskSnapshot(cwdDiskLink, snapshotPath, cancellable);
|
||||
return snapshotPath;
|
||||
}
|
||||
let e = null;
|
||||
try {
|
||||
e = diskDir.enumerate_children('standard::name', 0, cancellable);
|
||||
let info;
|
||||
while ((info = e.next_file(cancellable)) != null) {
|
||||
let name = info.get_name();
|
||||
if (!JSUtil.stringEndswith(name, '.qcow2'))
|
||||
continue;
|
||||
cwdDiskLink = this._fileLinkHere(e.get_child(info), cancellable);
|
||||
if (cwdDiskLink)
|
||||
break;
|
||||
}
|
||||
} finally {
|
||||
if (e) e.close(null);
|
||||
}
|
||||
if (!cwdDiskLink) {
|
||||
throw new Error("Unable to find cached disk in " + diskDir.get_path());
|
||||
}
|
||||
print("Acquired link to disk " + cwdDiskLink.get_path());
|
||||
print("Creating snapshot " + snapshotPath.get_path() + " for update");
|
||||
LibQA.createDiskSnapshot(cwdDiskLink, snapshotPath, cancellable);
|
||||
|
||||
let mntdir = Gio.File.new_for_path('mnt');
|
||||
GSystem.file_ensure_directory(mntdir, true, cancellable);
|
||||
let gfmnt = new GuestFish.GuestMount(snapshotPath, { partitionOpts: LibQA.DEFAULT_GF_PARTITION_OPTS,
|
||||
readWrite: true });
|
||||
gfmnt.mount(mntdir, cancellable);
|
||||
try {
|
||||
LibQA.pullDeploy(mntdir, this.repo, osname, ref, revision, originRepoUrl,
|
||||
cancellable);
|
||||
} finally {
|
||||
gfmnt.umount(cancellable);
|
||||
}
|
||||
|
||||
print("Successfully updated " + snapshotPath.get_path() + " to " + revision);
|
||||
|
||||
return snapshotPath;
|
||||
},
|
||||
|
||||
execute: function(cancellable) {
|
||||
this._imageCacheDir = this.cachedir.get_child('images');
|
||||
GSystem.file_ensure_directory(this._imageCacheDir, true, cancellable);
|
||||
this._products = JsonUtil.loadJson(this.workdir.get_child('products.json'), cancellable);
|
||||
this._productsBuilt = JsonUtil.loadJson(this.builddir.get_child('products-built.json'), cancellable);
|
||||
let productTrees = this._productsBuilt['trees'];
|
||||
for (let ref in productTrees) {
|
||||
let revision = productTrees[ref]['rev'];
|
||||
let snapshotDisk = this.getDiskSnapshotForRevision(ref, revision, cancellable);
|
||||
try {
|
||||
let refUnix = ref.replace(/\//g, '-');
|
||||
let refWorkdir = Gio.File.new_for_path('work-' + refUnix);
|
||||
GSystem.file_ensure_directory(refWorkdir, true, cancellable);
|
||||
let test = new TestOneDisk(this,
|
||||
this.BaseRequiredMessageIDs.concat(this.RequiredMessageIDs),
|
||||
this.BaseFailedMessageIDs.concat(this.FailedMessageIDs),
|
||||
this.StatusMessageID);
|
||||
let failedMsg = test.execute(refWorkdir, this._products['osname'], this.repo,
|
||||
snapshotDisk, cancellable);
|
||||
if (failedMsg) {
|
||||
print("Testing of " + ref + " at " + revision + " failed");
|
||||
}
|
||||
} finally {
|
||||
// These are too big to stick around
|
||||
GSystem.shutil_rm_rf(snapshotDisk, cancellable);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
@ -1,431 +0,0 @@
|
||||
// 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, '');
|
||||
}
|
@ -1,194 +0,0 @@
|
||||
// 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);
|
||||
}
|
||||
});
|
@ -1,113 +0,0 @@
|
||||
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
|
||||
*
|
||||
* Copyright (C) 2014 Colin Walters <walters@verbum.org>
|
||||
*
|
||||
* This program 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 licence 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.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <glib-unix.h>
|
||||
#include <gio/gunixsocketaddress.h>
|
||||
#include <stdio.h>
|
||||
#include <readline/readline.h>
|
||||
|
||||
#include "libgsystem.h"
|
||||
|
||||
static GOptionEntry option_entries[] = {
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
int
|
||||
main (int argc, char **argv)
|
||||
{
|
||||
GError *local_error = NULL;
|
||||
GError **error = &local_error;
|
||||
GCancellable *cancellable = NULL;
|
||||
GOptionContext *context = g_option_context_new ("- netcat console");
|
||||
gs_unref_object GSocketAddress *address = NULL;
|
||||
gs_unref_object GSocketClient *socketclient = NULL;
|
||||
gs_unref_object GSocketConnection *socketconn = NULL;
|
||||
GOutputStream *out;
|
||||
GInputStream *in;
|
||||
gs_unref_object GDataInputStream *datain = NULL;
|
||||
const char *address_arg;
|
||||
|
||||
g_option_context_add_main_entries (context, option_entries, NULL);
|
||||
|
||||
if (argc > 1)
|
||||
address_arg = argv[1];
|
||||
else
|
||||
address_arg = "cmd.socket";
|
||||
|
||||
address = g_unix_socket_address_new (address_arg);
|
||||
socketclient = g_socket_client_new ();
|
||||
socketconn = g_socket_client_connect (socketclient,
|
||||
(GSocketConnectable*)address,
|
||||
cancellable, error);
|
||||
if (!socketconn)
|
||||
goto out;
|
||||
|
||||
out = g_io_stream_get_output_stream ((GIOStream*)socketconn);
|
||||
in = g_io_stream_get_input_stream ((GIOStream*)socketconn);
|
||||
datain = g_data_input_stream_new (in);
|
||||
|
||||
while (TRUE)
|
||||
{
|
||||
char *line = readline ("> ");
|
||||
gsize bytes_written;
|
||||
const guint8 nl = '\n';
|
||||
|
||||
if (!line)
|
||||
break;
|
||||
|
||||
if (!g_output_stream_write_all (out, line, strlen (line),
|
||||
&bytes_written,
|
||||
cancellable, error))
|
||||
goto out;
|
||||
|
||||
free (line);
|
||||
|
||||
if (!g_output_stream_write_all (out, &nl, 1,
|
||||
&bytes_written,
|
||||
cancellable, error))
|
||||
goto out;
|
||||
|
||||
{
|
||||
gsize bytes_read;
|
||||
gs_free char *resline =
|
||||
g_data_input_stream_read_line_utf8 (datain,
|
||||
&bytes_read,
|
||||
cancellable, error);
|
||||
|
||||
if (!resline)
|
||||
goto out;
|
||||
|
||||
g_print ("%s\n", resline);
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
if (local_error)
|
||||
{
|
||||
g_printerr ("%s\n", local_error->message);
|
||||
g_error_free (local_error);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
@ -1,35 +0,0 @@
|
||||
#!/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@-autobuilder/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@-autobuilder
|
||||
export OSTBUILD_LIBDIR=@pkglibdir@-autobuilder
|
||||
|
||||
argv1="$1"
|
||||
externalpath="$OSTBUILD_LIBDIR/rpm-ostree-autobuilder-builtin-$1"
|
||||
if test -x "$externalpath"; then
|
||||
shift
|
||||
exec "$externalpath" "$@"
|
||||
fi
|
||||
|
||||
exec $OSTBUILD_GDB gjs -I "${jsdir}" "${jsdir}/main.js" "$@"
|
@ -1,2 +0,0 @@
|
||||
#!/bin/bash
|
||||
exec journalctl -o json -b -f --no-tail > /dev/virtio-ports/org.gnome.journaljson
|
@ -1,5 +0,0 @@
|
||||
[Unit]
|
||||
Description=QA: Copy bootup journal log to /dev/ttyS1
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/bin/rpm-ostree-export-journal-to-serialdev
|
@ -1,3 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
rpm -qa --dbpath=$(pwd) | sort
|
Loading…
Reference in New Issue
Block a user