[gnome-ostree/wip/gjs: 1/2] Depend on gjs, rewrite autobuilder.py using it
- From: Colin Walters <walters src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-ostree/wip/gjs: 1/2] Depend on gjs, rewrite autobuilder.py using it
- Date: Thu, 6 Dec 2012 22:33:02 +0000 (UTC)
commit b92abb5be947acea19ec6522f9b51ecb64c10cfe
Author: Colin Walters <walters verbum org>
Date: Mon Nov 5 08:17:13 2012 -0500
Depend on gjs, rewrite autobuilder.py using it
This way we get to use the GNOME platform, which allows us to feed
back improvements. Specifically I found myself too limited by
Python's lack of a mainloop. We can now run multiple processes at
once easily.
.gitmodules | 3 +
Makefile-ostbuild.am | 34 +++-
Makefile.am | 28 +++
configure.ac | 24 ++
src/libgsystem | 1 +
src/ostbuild/js/autobuilder.js | 290 ++++++++++++++++++++++++
src/ostbuild/js/config.js | 42 ++++
src/ostbuild/js/jsondb.js | 85 +++++++
src/ostbuild/js/jsonutil.js | 19 ++
src/ostbuild/js/procutil.js | 30 +++
src/ostbuild/js/snapshot.js | 54 +++++
src/ostbuild/js/task.js | 130 +++++++++++
src/ostbuild/ostbuild-js.in | 10 +
src/ostbuild/pyostbuild/builtin_autobuilder.py | 263 ---------------------
src/ostbuild/pyostbuild/main.py | 18 ++-
15 files changed, 757 insertions(+), 274 deletions(-)
---
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..f456a14
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "src/libgsystem"]
+ path = src/libgsystem
+ url = git://git.gnome.org/libgsystem
diff --git a/Makefile-ostbuild.am b/Makefile-ostbuild.am
index d93a760..45c8231 100644
--- a/Makefile-ostbuild.am
+++ b/Makefile-ostbuild.am
@@ -15,16 +15,25 @@
# 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), \
+ -e s,@PYTHON\@,$(PYTHON), \
+ $(NULL)
+
+
ostbuild: src/ostbuild/ostbuild.in Makefile
- sed -e s,@libdir\@,$(libdir), \
- -e s,@datarootdir\@,$(datarootdir), \
- -e s,@pkgdatadir\@,$(pkgdatadir), \
- -e s,@PYTHON\@,$(PYTHON), \
- $< > $ tmp && mv $ tmp $@
+ sed $(substitutions) $< > $ tmp && mv $ tmp $@
EXTRA_DIST += ostbuild/ostbuild.in
+ostbuild-js: src/ostbuild/ostbuild-js.in Makefile
+ sed $(substitutions) $< > $ tmp && mv $ tmp $@
+EXTRA_DIST += ostbuild/ostbuild-js.in
+
if BUILDSYSTEM
-bin_SCRIPTS += ostbuild
+bin_SCRIPTS += ostbuild ostbuild-js
utils_SCRIPTS = \
src/ostbuild/ostree-build-compile-one \
@@ -35,7 +44,6 @@ utilsdir = $(libdir)/ostbuild
pyostbuilddir=$(libdir)/ostbuild/pyostbuild
pyostbuild_PYTHON = \
src/ostbuild/pyostbuild/buildutil.py \
- src/ostbuild/pyostbuild/builtin_autobuilder.py \
src/ostbuild/pyostbuild/builtin_build.py \
src/ostbuild/pyostbuild/builtin_checkout.py \
src/ostbuild/pyostbuild/builtin_deploy_qemu.py \
@@ -67,4 +75,16 @@ pyostbuild_PYTHON = \
src/ostbuild/pyostbuild/subprocess_helpers.py \
src/ostbuild/pyostbuild/vcs.py \
$(NULL)
+
+jsostbuilddir=$(pkgdatadir)/js
+jsostbuild_DATA= \
+ src/ostbuild/js/autobuilder.js \
+ src/ostbuild/js/config.js \
+ src/ostbuild/js/jsondb.js \
+ src/ostbuild/js/task.js \
+ src/ostbuild/js/procutil.js \
+ src/ostbuild/js/snapshot.js \
+ src/ostbuild/js/jsonutil.js \
+ $(NULL)
+
endif
diff --git a/Makefile.am b/Makefile.am
index 963bff0..f9b7fb7 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -34,8 +34,36 @@ privlibdir = $(pkglibdir)
privlib_LTLIBRARIES =
INSTALL_DATA_HOOKS =
+libgsystem_srcpath := src/libgsystem
+libgsystem_cflags = $(GIO_UNIX_CFLAGS) -I$(srcdir)/src/libgsystem
+libgsystem_libs = $(GIO_UNIX_LIBS)
+include src/libgsystem/Makefile-libgsystem.am
+privlib_LTLIBRARIES += libgsystem.la
include Makefile-ostbuild.am
+include $(INTROSPECTION_MAKEFILE)
+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 \
+ --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)
+
+girdir= $(pkgdatadir)/gir-1.0
+typelibdir= $(pkglibdir)/girepository-1.0
+
+gir_DATA = $(INTROSPECTION_GIRS)
+typelib_DATA = $(gir_DATA:.gir=.typelib)
+
install-data-hook: $(INSTALL_DATA_HOOKS)
release-tag:
diff --git a/configure.ac b/configure.ac
index 67db4a7..febaec5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -18,6 +18,27 @@ AM_INIT_AUTOMAKE([1.11 -Wno-portability foreign no-define tar-ustar no-dist-gzip
AM_MAINTAINER_MODE([enable])
AM_SILENT_RULES([yes])
+AC_PROG_CC
+AM_PROG_CC_C_O
+
+changequote(,)dnl
+if test "x$GCC" = "xyes"; then
+ WARN_CFLAGS="-Wall -Werror=strict-prototypes -Werror=missing-prototypes \
+ -Werror=implicit-function-declaration \
+ -Werror=pointer-arith -Werror=init-self -Werror=format=2 \
+ -Werror=format-security \
+ -Werror=missing-include-dirs -Werror=aggregate-return \
+ -Werror=declaration-after-statement"
+fi
+changequote([,])dnl
+AC_SUBST(WARN_CFLAGS)
+
+# Initialize libtool
+LT_PREREQ([2.2.4])
+LT_INIT([disable-static])
+
+GOBJECT_INTROSPECTION_REQUIRE([1.34.0])
+
AM_PATH_PYTHON([2.6])
AC_ARG_ENABLE(buildsystem,
@@ -25,6 +46,9 @@ AC_ARG_ENABLE(buildsystem,
enable_buildsystem=yes)
AM_CONDITIONAL(BUILDSYSTEM, test x$enable_buildsystem != xno)
+PKG_CHECK_MODULES(GIO_UNIX, [gio-unix-2.0 >= 2.34.0])
+GIO_UNIX_CFLAGS="$GIO_UNIX_CFLAGS -DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_34 -DGLIB_VERSION_MAX_ALLOWED=GLIB_VERSION_2_34"
+
AC_CONFIG_FILES([
Makefile
])
diff --git a/src/libgsystem b/src/libgsystem
new file mode 160000
index 0000000..c17376d
--- /dev/null
+++ b/src/libgsystem
@@ -0,0 +1 @@
+Subproject commit c17376d4acbddfa1909d24167d3ce24531b3db1a
diff --git a/src/ostbuild/js/autobuilder.js b/src/ostbuild/js/autobuilder.js
new file mode 100644
index 0000000..735bf80
--- /dev/null
+++ b/src/ostbuild/js/autobuilder.js
@@ -0,0 +1,290 @@
+#!/usr/bin/env gjs
+
+const GLib = imports.gi.GLib;
+const Gio = imports.gi.Gio;
+const Lang = imports.lang;
+const Format = imports.format;
+
+const GSystem = imports.gi.GSystem;
+
+const Task = imports.task;
+const JsonDB = imports.jsondb;
+const ProcUtil = imports.procutil;
+const JsonUtil = imports.jsonutil;
+const Snapshot = imports.snapshot;
+
+const loop = GLib.MainLoop.new(null, true);
+
+var AutoBuilderIface = <interface name="org.gnome.OSTreeBuild.AutoBuilder">
+<method name="queueBuild">
+ <arg type="as" direction="in" />
+</method>
+<method name="queueResolve">
+ <arg type="as" direction="in" />
+</method>
+<property name="Status" type="s" access="read" />
+</interface>;
+
+const AutoBuilder = new Lang.Class({
+ Name: 'AutoBuilder',
+
+ _init: function() {
+ this._resolve_proc = null;
+ this._build_proc = null;
+
+ this.config = imports.config.get();
+ this.workdir = Gio.File.new_for_path(this.config.getGlobal('workdir'));
+ this.prefix = this.config.getPrefix();
+ this._snapshot_dir = this.workdir.get_child('snapshots');
+ this._status_path = this.workdir.get_child('autobuilder-' + this.prefix + '.json');
+
+ this._build_needed = true;
+ this._full_resolve_needed = true;
+ this._queued_force_builds = [];
+ this._queued_force_resolve = [];
+ this._autoupdate_self = false;
+ this._resolve_timeout = 0;
+ this._resolve_is_full = false;
+ this._source_snapshot_path = null;
+ this._prev_source_snapshot_path = null;
+
+ this._impl = Gio.DBusExportedObject.wrapJSObject(AutoBuilderIface, this);
+ this._impl.export(Gio.DBus.session, '/org/gnome/OSTreeBuild/AutoBuilder');
+
+ let snapshotdir = this.workdir.get_child('snapshots');
+ GSystem.file_ensure_directory(snapshotdir, true, null);
+ this._src_db = new JsonDB.JsonDB(snapshotdir, this.prefix + '-src-snapshot');
+
+ let taskdir = new Task.TaskDir(this.workdir.get_child('tasks'));
+ this._resolve_taskset = taskdir.get(this.prefix + '-resolve');
+ this._build_taskset = taskdir.get(this.prefix + '-build');
+
+ this._source_snapshot_path = this._src_db.getLatestPath();
+
+ this._status_path = this.workdir.get_child('autobuilder-' + this.prefix + '.json');
+
+ this._fetch();
+ if (this._source_snapshot_path != null)
+ this._run_build();
+
+ this._updateStatus();
+ },
+
+ _updateStatus: function() {
+ let newStatus = "";
+ if (this._resolve_proc == null && this._build_proc == null) {
+ newStatus = "idle";
+ } else {
+ if (this._resolve_proc != null)
+ newStatus += "[resolving] ";
+ if (this._build_proc != null)
+ newStatus += "[building] ";
+ }
+ if (newStatus != this._status) {
+ this._status = newStatus;
+ print(this._status);
+ this._impl.emit_property_changed('Status', new GLib.Variant("s", this._status));
+ }
+
+ this._writeStatusFile();
+ },
+
+ get Status() {
+ return this._status;
+ },
+
+ queueBuild: function(components) {
+ this._queued_force_builds.push.apply(this._queued_force_builds, components);
+ this._build_needed = true;
+ log("queued builds: " + this._queued_force_builds);
+ if (this._build_proc == null)
+ this._run_build();
+ },
+
+ queueResolve: function(components) {
+ this._queued_force_resolve.push.apply(this._queued_force_resolve, components);
+ log("queued resolves: " + this._queued_force_resolve);
+ if (this._resolve_proc == null)
+ this._fetch();
+ },
+
+ _fetch: function() {
+ if (this._resolve_proc != null) {
+ this._full_resolve_needed = true;
+ return false;
+ }
+ let t = this._resolve_taskset.start();
+ let taskWorkdir = t.path;
+
+ if (this._autoupdate_self)
+ ProcUtil.runSync(['git', 'pull', '-r'])
+
+ let args = ['ostbuild', 'resolve', '--manifest=manifest.json',
+ '--fetch', '--fetch-keep-going'];
+ if (this._queued_force_resolve.length > 0) {
+ this._resolve_is_full = false;
+ args.push.apply(args, this._queued_force_resolve);
+ } else {
+ this._resolve_is_full = true;
+ }
+ this._queued_force_resolve = [];
+ let context = new GSystem.SubprocessContext({ argv: args });
+ context.set_stdout_file_path(t.logfile_path.get_path());
+ context.set_stderr_disposition(GSystem.SubprocessStreamDisposition.STDERR_MERGE);
+ this._resolve_proc = new GSystem.Subprocess({context: context});
+ this._resolve_proc.init(null);
+ log(Format.vprintf("Resolve task %s.%s started, pid=%s", [t.major, t.minor, this._resolve_proc.get_pid()]));
+ this._resolve_proc.wait(null, Lang.bind(this, this._onResolveExited));
+
+ this._updateStatus();
+
+ return false;
+ },
+
+ _onResolveExited: function(process, result) {
+ this._resolve_proc = null;
+ let [success, msg] = ProcUtil.asyncWaitCheckFinish(process, result);
+ log(Format.vprintf("resolve exited; success=%s msg=%s", [success, msg]))
+ this._resolve_taskset.finish(success);
+ this._prev_source_snapshot_path = this._source_snapshot_path;
+ this._source_snapshot_path = this._src_db.getLatestPath();
+ if (this._resolve_is_full) {
+ this._resolve_timeout = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 10*60,
+ Lang.bind(this, this._fetch));
+ }
+ let changed = (this._prev_source_snapshot_path == null ||
+ !this._prev_source_snapshot_path.equal(this._source_snapshot_path));
+ if (changed)
+ log(Format.vprintf("New version is %s", [this._source_snapshot_path.get_path()]))
+ if (!this._build_needed)
+ this._build_needed = changed;
+ if (this._build_needed && this._build_proc == null)
+ this._run_build();
+
+ if (this._full_resolve_needed) {
+ this._full_resolve_needed = false;
+ this._fetch();
+ }
+
+ this._updateStatus();
+ },
+
+ _run_build: function() {
+ let cancellable = null;
+ if (this._build_proc != null) throw new Error();
+ if (!this._build_needed) throw new Error();
+
+ this._build_needed = false;
+
+ let task = this._build_taskset.start();
+ let workdir = task.path;
+ let args = ['ostbuild', 'build', '--skip-vcs-matches',
+ '--src-snapshot=' + this._source_snapshot_path.get_path()];
+ args.push.apply(args, this._queued_force_builds);
+ this._queued_force_builds = [];
+
+ let version = this._src_db.parseVersionStr(this._source_snapshot_path.get_basename());
+ let meta = {'version': version,
+ 'version-path': this._snapshot_dir.get_relative_path(this._source_snapshot_path)};
+ let metaPath = workdir.get_child('meta.json');
+ JsonUtil.writeJsonFileAtomic(metaPath, meta, cancellable);
+
+ let context = new GSystem.SubprocessContext({ argv: args });
+ context.set_stdout_file_path(task.logfile_path.get_path());
+ context.set_stderr_disposition(GSystem.SubprocessStreamDisposition.STDERR_MERGE);
+ this._build_proc = new GSystem.Subprocess({context: context});
+ this._build_proc.init(null);
+ log(Format.vprintf("Build task %s.%s started, pid=%s", [task.major, task.minor, this._build_proc.get_pid()]));
+ this._build_proc.wait(null, Lang.bind(this, this._onBuildExited));
+
+ this._updateStatus();
+ },
+
+ _onBuildExited: function(process, result) {
+ if (this._build_proc == null) throw new Error();
+ this._build_proc = null;
+ let [success, msg] = ProcUtil.asyncWaitCheckFinish(process, result);
+ log(Format.vprintf("build exited; success=%s msg=%s", [success, msg]))
+ this._build_taskset.finish(success);
+ if (this._build_needed)
+ this._run_build()
+
+ this._updateStatus();
+ },
+
+ _getBuildDiffForTask: function(task) {
+ let cancellable = null;
+ if (task.build_diff != undefined)
+ return task.build_diff;
+ let metaPath = task.path.get_child('meta.json');
+ if (!metaPath.query_exists(null)) {
+ task.build_diff = null;
+ return task.build_diff;
+ }
+ let meta = JsonUtil.loadJson(metaPath, cancellable);
+ let snapshotPath = this._snapshot_dir.get_child(meta['version-path']);
+ let prevSnapshotPath = this._src_db.getPreviousPath(snapshotPath);
+ if (prevSnapshotPath == null) {
+ task.build_diff = null;
+ } else {
+ task.build_diff = Snapshot.snapshotDiff(this._src_db.loadFromPath(snapshotPath, cancellable),
+ this._src_db.loadFromPath(prevSnapshotPath, cancellable));
+ }
+ return task.build_diff;
+ },
+
+ _buildHistoryToJson: function() {
+ let cancellable = null;
+ let history = this._build_taskset.getHistory();
+ let l = history.length;
+ let MAXITEMS = 5;
+ let entries = [];
+ for (let i = Math.max(l - MAXITEMS, 0); i >= 0 && i < l; i++) {
+ let item = history[i];
+ let data = {v: Format.vprintf('%d.%d', [item.major, item.minor]),
+ state: item.state,
+ timestamp: item.timestamp};
+ entries.push(data);
+ let metaPath = item.path.get_child('meta.json');
+ if (metaPath.query_exists(cancellable)) {
+ data['meta'] = JsonUtil.loadJson(metaPath, cancellable);
+ }
+ data['diff'] = this._getBuildDiffForTask(item);
+ }
+ return entries;
+ },
+
+ _writeStatusFile: function() {
+ let cancellable = null;
+ let status = {'prefix': this.prefix};
+ if (this._source_snapshot_path != null) {
+ let version = this._src_db.parseVersionStr(this._source_snapshot_path.get_basename());
+ status['version'] = version;
+ status['version-path'] = this._snapshot_dir.get_relative_path(this._source_snapshot_path);
+ } else {
+ status['version'] = '';
+ }
+
+ status['build'] = this._buildHistoryToJson();
+
+ if (this._build_proc != null) {
+ let buildHistory = this._build_taskset.getHistory();
+ let activeBuild = buildHistory[buildHistory.length-1];
+ let buildStatus = status['build'];
+ let activeBuildJson = buildStatus[buildStatus.length-1];
+ let statusPath = activeBuild.path.get_child('status.json');
+ if (statusPath.query_exists(null)) {
+ activeBuildJson['build-status'] = JsonUtil.loadJson(statusPath);
+ }
+ }
+
+ JsonUtil.writeJsonFileAtomic(this._status_path, status, cancellable);
+ }
+});
+
+var ownId = Gio.DBus.session.own_name('org.gnome.OSTreeBuild', Gio.BusNameOwnerFlags.NONE,
+ function(name) {},
+ function(name) { loop.quit(); });
+
+var builder = new AutoBuilder();
+loop.run();
diff --git a/src/ostbuild/js/config.js b/src/ostbuild/js/config.js
new file mode 100644
index 0000000..d006387
--- /dev/null
+++ b/src/ostbuild/js/config.js
@@ -0,0 +1,42 @@
+const GLib = imports.gi.GLib;
+const Gio = imports.gi.Gio;
+
+const GSystem = imports.gi.GSystem;
+
+const Config = new Lang.Class({
+ Name: 'Config',
+
+ _init: function() {
+ this._keyfile = new GLib.KeyFile();
+ var path = GLib.build_filenamev([GLib.get_user_config_dir(), "ostbuild.cfg"]);
+ this._keyfile.load_from_file(path, GLib.KeyFileFlags.NONE);
+ },
+
+ getGlobal: function(key, defaultValue) {
+ try {
+ return this._keyfile.get_string("global", key);
+ } catch (e) {
+ if (e.domain == GLib.KeyFileError
+ && defaultValue != undefined)
+ return defaultValue;
+ throw e;
+ }
+ },
+
+ getPrefix: function() {
+ let pathname = GLib.build_filenamev([GLib.get_user_config_dir(), "ostbuild-prefix"]);
+ let path = Gio.File.new_for_path(pathname);
+ if (!path.query_exists(null))
+ throw new Error("No prefix set; use \"ostbuild prefix\" to set one");
+ let prefix = GSystem.file_load_contents_utf8(path, null);
+ return prefix.replace(/[ \r\n]/g, '');
+ }
+});
+
+var _instance = null;
+
+function get() {
+ if (_instance == null)
+ _instance = new Config();
+ return _instance;
+}
diff --git a/src/ostbuild/js/jsondb.js b/src/ostbuild/js/jsondb.js
new file mode 100644
index 0000000..7628c5a
--- /dev/null
+++ b/src/ostbuild/js/jsondb.js
@@ -0,0 +1,85 @@
+const GLib = imports.gi.GLib;
+const Gio = imports.gi.Gio;
+const Format = imports.format;
+
+const JsonUtil = imports.jsonutil;
+
+const JsonDB = new Lang.Class({
+ Name: 'JsonDB',
+
+ _init: function(path, prefix) {
+ this._path = path;
+ this._prefix = prefix;
+ this._re = /-(\d+)\.(\d+)-([0-9a-f]+).json$/;
+ },
+
+ parseVersion: function(basename) {
+ let match = this._re.exec(basename);
+ if (!match)
+ throw new Error("No JSONDB version in " + basename);
+ return [parseInt(match[1]), parseInt(match[2])];
+ },
+
+ parseVersionStr: function(basename) {
+ let [major, minor] = this.parseVersion(basename);
+ return Format.vprintf('%d.%d', [major, minor]);
+ },
+
+ _getAll: function() {
+ var result = [];
+ var e = this._path.enumerate_children('standard::*', Gio.FileQueryInfoFlags.NONE, null);
+ let info;
+ while ((info = e.next_file(null)) != null) {
+ let name = info.get_name();
+ if (name.indexOf(this._prefix) != 0)
+ continue;
+ if (name.lastIndexOf('.json') != name.length-5)
+ continue;
+ let match = this._re.exec(name);
+ if (!match)
+ throw new Error("Invalid JSONDB file " + name);
+ result.push([parseInt(match[1]), parseInt(match[2]),
+ match[3], name]);
+ }
+ result.sort(function(a, b) {
+ var aMajor = a[0]; var bMajor = b[0];
+ var aMinor = a[1]; var bMinor = b[1];
+ if (aMajor < bMajor) return 1;
+ else if (aMajor > bMajor) return -1;
+ else if (aMinor < bMinor) return 1;
+ else if (aMinor > bMinor) return -1;
+ else return 0;
+ });
+ return result;
+ },
+
+ getLatestPath: function() {
+ let all = this._getAll();
+ if (all.length == 0)
+ return null;
+ return this._path.get_child(all[0][3]);
+ },
+
+ getPreviousPath: function(path) {
+ let name = path.get_basename();
+ let [target_major, target_minor] = this.parseVersion(name);
+ let files = this._getAll();
+ let prev = null;
+ let found = false;
+ for (let i = files.length - 1; i >= 0; i--) {
+ let [major, minor, csum, fname] = files[i];
+ if (target_major == major && target_minor == minor) {
+ found = true;
+ break;
+ }
+ prev = fname;
+ }
+ if (found)
+ return this._path.get_child(prev);
+ return null;
+ },
+
+ loadFromPath: function(path, cancellable) {
+ return JsonUtil.loadJson(this._path.get_child(path.get_basename()), cancellable);
+ }
+});
diff --git a/src/ostbuild/js/jsonutil.js b/src/ostbuild/js/jsonutil.js
new file mode 100644
index 0000000..ab64073
--- /dev/null
+++ b/src/ostbuild/js/jsonutil.js
@@ -0,0 +1,19 @@
+const GLib = imports.gi.GLib;
+const Gio = imports.gi.Gio;
+
+/* jsonutil.js:
+ * Read/write JSON to/from GFile paths, very inefficiently.
+ */
+
+function writeJsonFileAtomic(path, data, cancellable) {
+ let buf = JSON.stringify(data, null, " ");
+ let s = path.replace(null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, cancellable);
+ s.write_bytes(new GLib.Bytes(buf), cancellable);
+ s.close(cancellable);
+}
+
+function loadJson(path, cancellable) {
+ let [success,contents,etag] = path.load_contents(cancellable);
+ return JSON.parse(contents);
+}
+
diff --git a/src/ostbuild/js/procutil.js b/src/ostbuild/js/procutil.js
new file mode 100644
index 0000000..69c65b6
--- /dev/null
+++ b/src/ostbuild/js/procutil.js
@@ -0,0 +1,30 @@
+const GLib = imports.gi.GLib;
+const Gio = imports.gi.Gio;
+
+const GSystem = imports.gi.GSystem;
+
+function runSync(args, stdoutDisposition, stderrDisposition) {
+ if (stdoutDisposition == undefined)
+ stdoutDisposition = GSystem.SubprocessStreamDisposition.INHERIT;
+ if (stderrDisposition == undefined)
+ stderrDisposition = GSystem.SubprocessStreamDisposition.INHERIT;
+ var proc = GSystem.Subprocess.new_simple_argv(args, stdoutDisposition, stderrDisposition);
+ proc.wait_sync_check(null);
+}
+
+
+function asyncWaitCheckFinish(process, result) {
+ let [waitSuccess, estatus] = process.wait_finish(result);
+ let success = false;
+ let errorMsg = null;
+ try {
+ GLib.spawn_check_exit_status(estatus);
+ 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;
+ }
+}
diff --git a/src/ostbuild/js/snapshot.js b/src/ostbuild/js/snapshot.js
new file mode 100644
index 0000000..dc2216f
--- /dev/null
+++ b/src/ostbuild/js/snapshot.js
@@ -0,0 +1,54 @@
+//
+// 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.
+
+function _componentDict(snapshot) {
+ let r = {};
+ for (let component in snapshot['components']) {
+ r[component['name']] = component;
+ }
+ let patches = snapshot['patches'];
+ 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];
+}
diff --git a/src/ostbuild/js/task.js b/src/ostbuild/js/task.js
new file mode 100644
index 0000000..018aaf8
--- /dev/null
+++ b/src/ostbuild/js/task.js
@@ -0,0 +1,130 @@
+const GLib = imports.gi.GLib;
+const Gio = imports.gi.Gio;
+const format = imports.format;
+
+const GSystem = imports.gi.GSystem;
+
+const VERSION_RE = /(\d+)\.(\d+)/;
+
+const TaskDir = new Lang.Class({
+ Name: 'TaskDir',
+
+ _init: function(path) {
+ this.path = path;
+ },
+
+ get: function(name) {
+ let child = this.path.get_child(name);
+ GSystem.file_ensure_directory(child, true, null);
+
+ return new TaskSet(child);
+ }
+});
+
+const TaskHistoryEntry = new Lang.Class({
+ Name: 'TaskHistoryEntry',
+
+ _init: function(path, state) {
+ this.path = path;
+ let match = VERSION_RE.exec(path.get_basename());
+ this.major = parseInt(match[1]);
+ this.minor = parseInt(match[2]);
+ this.timestamp = null;
+ this.logfile_path = null;
+ this.start_timestamp = null;
+
+ if (state == undefined) {
+ let statusPath = this.path.get_child('status');
+ if (statusPath.query_exists(null)) {
+ let ioStream = statusPath.read(null);
+ let info = ioStream.query_info("unix::mtime", null);
+ let contents = ioStream.read_bytes(8192, null);
+ this.state = contents;
+ ioStream.close(null);
+ this.timestamp = info.get_attribute_uint64("time::modified");
+ } else {
+ this.state = 'interrupted';
+ }
+ } else {
+ this.state = state;
+ this.start_timestamp = new Date().getTime() / 1000;
+ }
+ },
+
+ finish: function(success) {
+ let statusPath = this.path.get_child('status');
+ this.state = success ? 'success' : 'failed';
+ statusPath.replace_contents(this.state, null, false, 0, null);
+ this.timestamp = new Date().getTime() / 1000;
+ },
+
+ compareTo: function(a, b) {
+ function cmp(a, b) {
+ if (a == b) return 0;
+ else if (a < b) return -1;
+ else return 1;
+ }
+ let c = cmp(a.major, b.major);
+ if (c != 0) return c;
+ return cmp(a.minor, b.minor);
+ }
+});
+
+const TaskSet = new Lang.Class({
+ Name: 'TaskSet',
+
+ _init: function(path, prefix) {
+ this.path = path;
+
+ this._history = [];
+ this._running = false;
+ this._running_version = null;
+
+ this._load();
+ },
+
+ _load: function() {
+ var e = this.path.enumerate_children('standard::*', Gio.FileQueryInfoFlags.NONE, null);
+ let info;
+ while ((info = e.next_file(null)) != null) {
+ let name = info.get_name();
+ let childPath = this.path.get_child(name);
+ let match = VERSION_RE.exec(name);
+ if (!match)
+ continue;
+ this._history.push(new TaskHistoryEntry(childPath))
+ }
+ this._history.sort(TaskHistoryEntry.prototype.compareTo);
+ },
+
+ start: function() {
+ if (this._running) throw new Error();
+ this._running = true;
+ let yearver = new Date().getFullYear();
+ let lastversion = -1;
+ if (this._history.length > 0) {
+ let last = this._history[this._history.length-1];
+ if (last.major == yearver)
+ lastversion = last.minor;
+ else
+ lastversion = -1;
+ }
+ let historyPath = this.path.get_child(format.vprintf('%d.%d', [yearver, lastversion + 1]));
+ GSystem.file_ensure_directory(historyPath, true, null);
+ let entry = new TaskHistoryEntry(historyPath, 'running');
+ this._history.push(entry);
+ entry.logfile_path = historyPath.get_child('log');
+ return entry;
+ },
+
+ finish: function(success) {
+ if (!this._running) throw new Error();
+ let last = this._history[this._history.length-1];
+ last.finish(success);
+ this._running = false;
+ },
+
+ getHistory: function() {
+ return this._history;
+ }
+});
diff --git a/src/ostbuild/ostbuild-js.in b/src/ostbuild/ostbuild-js.in
new file mode 100755
index 0000000..1417760
--- /dev/null
+++ b/src/ostbuild/ostbuild-js.in
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+jsdir= pkgdatadir@/js
+arg=$1
+shift
+
+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}"
+
+exec gjs -I "${jsdir}" "${jsdir}/${arg}.js" "$@"
diff --git a/src/ostbuild/pyostbuild/main.py b/src/ostbuild/pyostbuild/main.py
index 1da65da..86c3423 100755
--- a/src/ostbuild/pyostbuild/main.py
+++ b/src/ostbuild/pyostbuild/main.py
@@ -22,7 +22,6 @@ import sys
import argparse
from . import builtins
-from . import builtin_autobuilder
from . import builtin_build
from . import builtin_checkout
from . import builtin_deploy_root
@@ -37,12 +36,16 @@ from . import builtin_repoweb_json
from . import builtin_resolve
from . import builtin_source_diff
+JS_BUILTINS = {'autobuilder': 'Run resolve and build'}
+
def usage(ecode):
print "Builtins:"
for builtin in builtins.get_all():
if builtin.name.startswith('privhelper'):
continue
print " %s - %s" % (builtin.name, builtin.short_description)
+ for name,short_description in JS_BUILTINS.items():
+ print " %s - %s" % (name, short_description)
return ecode
def main(args):
@@ -51,10 +54,17 @@ def main(args):
elif args[0] in ('-h', '--help'):
return usage(0)
else:
- builtin = builtins.get(args[0])
+ name = args[0]
+ builtin = builtins.get(name)
if builtin is None:
- print "error: Unknown builtin '%s'" % (args[0], )
- return usage(1)
+ js_builtin = JS_BUILTINS.get(name)
+ if js_builtin is None:
+ print "error: Unknown builtin '%s'" % (args[0], )
+ return usage(1)
+ else:
+ child_args = ['ostbuild-js', name]
+ child_args.extend(args[:1])
+ os.execvp('ostbuild-js', child_args)
return builtin.execute(args[1:])
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]