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-rpm-ostree.am
|
||||||
include Makefile-man.am
|
include Makefile-man.am
|
||||||
include Makefile-autobuilder.am
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
Summary: Commit RPMs to an OSTree repository
|
Summary: Commit RPMs to an OSTree repository
|
||||||
Name: rpm-ostree
|
Name: rpm-ostree
|
||||||
Version: 2015.1
|
Version: 2014.1
|
||||||
Release: 1%{?dist}
|
Release: 1%{?dist}
|
||||||
#VCS: https://github.com/cgwalters/rpm-ostree
|
#VCS: https://github.com/cgwalters/rpm-ostree
|
||||||
# This tarball is generated via "make -f Makefile.dist-packaging dist-snapshot"
|
# 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
|
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.
|
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
|
%prep
|
||||||
%setup -q -n %{name}-%{version}
|
%setup -q -n %{name}-%{version}
|
||||||
|
|
||||||
@ -53,12 +40,6 @@ make install DESTDIR=$RPM_BUILD_ROOT INSTALL="install -p -c"
|
|||||||
%{_bindir}/rpm-ostree
|
%{_bindir}/rpm-ostree
|
||||||
%{_libdir}/%{name}/
|
%{_libdir}/%{name}/
|
||||||
|
|
||||||
%files autobuilder
|
|
||||||
%{_bindir}/rpm-ostree-autobuilder
|
|
||||||
%{_libdir}/%{name}-autobuilder/
|
|
||||||
%{_datadir}/%{name}-autobuilder/
|
|
||||||
%{_mandir}/man1/*.gz
|
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
* Fri Mar 07 2014 Colin Walters <walters@verbum.org> - 2014.5-1
|
* Fri Mar 07 2014 Colin Walters <walters@verbum.org> - 2014.5-1
|
||||||
- Initial package
|
- 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