[gnome-ostree/wip/dyntask-2] wip



commit 953cb4c541adf5c3b5a6b103ccc39e204b9adba6
Author: Colin Walters <walters verbum org>
Date:   Thu Jan 24 09:13:51 2013 -0500

    wip

 Makefile-ostbuild.am                               |    5 +-
 src/ostbuild/js/buildutil.js                       |   27 ++
 src/ostbuild/js/builtins/make.js                   |   65 ++++
 src/ostbuild/js/builtins/run_task.js               |   57 ++++
 src/ostbuild/js/dyntask.js                         |  314 +++++++++++++++----
 src/ostbuild/js/jsonutil.js                        |   38 +++
 src/ostbuild/js/main.js                            |    2 +
 src/ostbuild/js/subtask.js                         |   34 +-
 .../js/{builtins/build.js => tasks/task-build.js}  |  101 +++----
 src/ostbuild/js/tasks/task-checksum.js             |  123 --------
 src/ostbuild/ostbuild.in                           |    2 +-
 11 files changed, 501 insertions(+), 267 deletions(-)
---
diff --git a/Makefile-ostbuild.am b/Makefile-ostbuild.am
index ef0ef1f..7ee25f1 100644
--- a/Makefile-ostbuild.am
+++ b/Makefile-ostbuild.am
@@ -60,21 +60,22 @@ jsostbuild_DATA= \
 jsostbuiltinsdir=$(jsostbuilddir)/builtins
 jsostbuiltins_DATA= \
 	src/ostbuild/js/builtins/autobuilder.js \
-	src/ostbuild/js/builtins/build.js \
 	src/ostbuild/js/builtins/build_disks.js \
 	src/ostbuild/js/builtins/checkout.js \
 	src/ostbuild/js/builtins/git_mirror.js \
+	src/ostbuild/js/builtins/make.js \
 	src/ostbuild/js/builtins/qa_make_disk.js \
 	src/ostbuild/js/builtins/qa_pull_deploy.js \
 	src/ostbuild/js/builtins/qa_smoketest.js \
 	src/ostbuild/js/builtins/prefix.js \
 	src/ostbuild/js/builtins/resolve.js \
+	src/ostbuild/js/builtins/run_task.js \
 	src/ostbuild/js/builtins/shell.js \
 	$(NULL)
 
 jsosttasksdir=$(jsostbuilddir)/tasks
 jsosttasks_DATA= \
-	src/ostbuild/js/tasks/task-checksum.js \
+	src/ostbuild/js/tasks/task-build.js \
 	$(NULL)
 
 endif
diff --git a/src/ostbuild/js/buildutil.js b/src/ostbuild/js/buildutil.js
index 0f3d1c7..7246e5c 100644
--- a/src/ostbuild/js/buildutil.js
+++ b/src/ostbuild/js/buildutil.js
@@ -127,3 +127,30 @@ 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;
+}
diff --git a/src/ostbuild/js/builtins/make.js b/src/ostbuild/js/builtins/make.js
new file mode 100644
index 0000000..2f39876
--- /dev/null
+++ b/src/ostbuild/js/builtins/make.js
@@ -0,0 +1,65 @@
+// 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 Builtin = imports.builtin;
+const DynTask = imports.dyntask;
+const JsonDB = imports.jsondb;
+const ProcUtil = imports.procutil;
+const JsonUtil = imports.jsonutil;
+const Snapshot = imports.snapshot;
+const Config = imports.config;
+const BuildUtil = imports.buildutil;
+const Vcs = imports.vcs;
+const ArgParse = imports.argparse;
+
+const Make = new Lang.Class({
+    Name: 'Make',
+    Extends: Builtin.Builtin,
+
+    DESCRIPTION: "Execute tasks",
+
+    _init: function() {
+	this.parent();
+	this.parser.addArgument('tasknames', { nargs: '*' });
+    },
+
+    execute: function(args, loop, cancellable) {
+	this._loop = loop;
+	this._err = null;
+	let taskmaster = new DynTask.TaskMaster(this.workdir.get_child('tasks'),
+						{ onEmpty: Lang.bind(this, this._onTasksComplete) });
+	taskmaster.pushTasks(args.tasknames);
+	loop.run();
+	if (this._err)
+	    throw new Error("Error: " + this._err);
+	else
+	    print("Success!")
+    },
+
+    _onTasksComplete: function(success, err) {
+	if (!success)
+	    this._err = err;
+	this._loop.quit();
+    }
+});
diff --git a/src/ostbuild/js/builtins/run_task.js b/src/ostbuild/js/builtins/run_task.js
new file mode 100644
index 0000000..7b26118
--- /dev/null
+++ b/src/ostbuild/js/builtins/run_task.js
@@ -0,0 +1,57 @@
+// 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 Builtin = imports.builtin;
+const DynTask = imports.dyntask;
+const JsonDB = imports.jsondb;
+const ProcUtil = imports.procutil;
+const JsonUtil = imports.jsonutil;
+const Snapshot = imports.snapshot;
+const Config = imports.config;
+const BuildUtil = imports.buildutil;
+const Vcs = imports.vcs;
+const ArgParse = imports.argparse;
+
+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('outfd');
+    },
+
+    execute: function(args, loop, cancellable) {
+	let outstream = Gio.UnixOutputStream.new(parseInt(args.outfd), true);
+	let taskset = DynTask.TaskSet.prototype.getInstance();
+	let [taskDef, inputs] = taskset.getTask(args.taskName);
+	let instance = new taskDef(null, args.taskName, inputs);
+	let result = instance.executeSync(cancellable);
+	JsonUtil.writeJsonToStream(outstream, result || {}, cancellable);
+	outstream.close(cancellable);
+    }
+});
diff --git a/src/ostbuild/js/dyntask.js b/src/ostbuild/js/dyntask.js
index a99cab3..fafa130 100644
--- a/src/ostbuild/js/dyntask.js
+++ b/src/ostbuild/js/dyntask.js
@@ -22,28 +22,18 @@ const Lang = imports.lang;
 
 const GSystem = imports.gi.GSystem;
 const Params = imports.params;
+const JsonUtil = imports.jsonutil;
+const ProcUtil = imports.procutil;
+const BuildUtil = imports.buildutil;
 
 const VERSION_RE = /(\d+)\.(\d+)/;
 
-const TaskMaster = new Lang.Class({
-    Name: 'TaskMaster',
-
-    _init: function(path, params) {
-	params = Params.parse(params, {maxConcurrent: 4,
-				       onEmpty: null});
-	this.path = path;
-	this.maxConcurrent = params.maxConcurrent;
-	this._onEmpty = params.onEmpty;
-	this.cancellable = null;
-	this._idleRecalculateId = 0;
-	this._taskSerial = 1;
+var _tasksetInstance = null;
+const TaskSet = new Lang.Class({
+    Name: 'TaskSet',
+    
+    _init: function() {
 	this._tasks = [];
-	this._executing = [];
-	this._pendingTasksList = [];
-	this._seenTasks = {};
-	this._completeTasks = {};
-	this._taskErrors = {};
-
 	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;
@@ -54,8 +44,7 @@ const TaskMaster = new Lang.Class({
 		if (defname.indexOf('Task') !== 0)
 		    continue;
 		let cls = taskMod[defname];
-		let instance = new cls;
-		this.register(instance);
+		this.register(cls);
 	    }
 	}
     },
@@ -64,52 +53,85 @@ const TaskMaster = new Lang.Class({
 	this._tasks.push(taskdef);
     },
 
-    _pushRecurse: function(taskName, seen) {
-	if (seen[taskName])
-	    return null;
-	let result = null;
+    getAllTasks: function() {
+	return this._tasks;
+    },
+
+    getTask: function(taskName, params) {
+	params = Params.parse(params, { allowNone: false })
 	for (let i = 0; i < this._tasks.length; i++) {
 	    let taskDef = this._tasks[i];
-	    let pattern = taskDef.getPattern();
+	    let pattern = taskDef.prototype.TaskPattern;
 	    let re = pattern[0];
 	    let match = re.exec(taskName);
 	    if (!match)
 		continue;
-
-	    let serial = this._taskSerial;
-	    this._taskSerial++;
 	    let vars = {};
 	    for (let i = 1; i < pattern.length; i++) {
 		vars[pattern[i]] = match[i];
 	    }
-	    let specifiedDependencies = taskDef.getDepends(vars);;
-	    let waitingDependencies = {};
-	    for (let j = 0; j < specifiedDependencies.length; j++) {
-		let depName = specifiedDependencies[j];
-		if (!this._completeTasks[depName]) {
-		    let depTask = this._pushRecurse(depName, seen);
-		    waitingDependencies[depName] = depTask;
-		}
-	    }
-	    result = {name: taskName,
-		      def: taskDef,
-		      vars: vars,
-		      dependencies: specifiedDependencies,
-		      waitingDependencies: waitingDependencies,
-		      serial: serial,
-		      result: null };
-	    this._pendingTasksList.push(result);
-	    seen[taskName] = true;
-	    break;
+	    return [taskDef, vars];
 	}
-	if (!result)
+	if (!params.allowNone)
 	    throw new Error("No task definition matches " + taskName);
+	return null;
+    },
+
+    getInstance: function() {
+	if (!_tasksetInstance)
+	    _tasksetInstance = new TaskSet();
+	return _tasksetInstance;
+    }
+});
+    
+const TaskMaster = new Lang.Class({
+    Name: 'TaskMaster',
+
+    _init: function(path, params) {
+	params = Params.parse(params, {maxConcurrent: 4,
+				       onEmpty: null});
+	this.path = path;
+	this.maxConcurrent = params.maxConcurrent;
+	this._onEmpty = params.onEmpty;
+	this.cancellable = null;
+	this._idleRecalculateId = 0;
+	this._executing = [];
+	this._pendingTasksList = [];
+	this._seenTasks = {};
+	this._completeTasks = {};
+	this._taskErrors = {};
+	this._caughtError = false;
+
+	this._taskset = TaskSet.prototype.getInstance();
+    },
+
+    _pushRecurse: function(taskName, seen) {
+	if (seen[taskName])
+	    return null;
+	let [taskDef, inputs] = this._taskset.getTask(taskName);
+	let specifiedDependencies = taskDef.prototype.getDepends(inputs);
+	let waitingDependencies = {};
+	for (let j = 0; j < specifiedDependencies.length; j++) {
+	    let depName = specifiedDependencies[j];
+	    if (!this._completeTasks[depName]) {
+		let depTask = this._pushRecurse(depName, seen);
+		waitingDependencies[depName] = depTask;
+	    }
+	}
+	let instance = new taskDef(this, taskName, inputs);
+	instance.onComplete = Lang.bind(this, this._onComplete, instance);
+	instance.dependencies = specifiedDependencies;
+	instance.waitingDependencies = waitingDependencies;
+	this._pendingTasksList.push(instance);
+	seen[taskName] = true;
 	this._queueRecalculate();
-	return result;
+	return instance;
     },
 
-    push: function(taskName) {
-	return this._pushRecurse(taskName, {});
+    pushTasks: function(taskNames) {
+	let seen = {};
+	for (let i = 0; i < taskNames.length; i++)
+	    this._pushRecurse(taskNames[i], seen);
     },
 
     _queueRecalculate: function() {
@@ -137,7 +159,7 @@ const TaskMaster = new Lang.Class({
 
 	if (this._executing.length == 0 &&
 	    this._pendingTasksList.length == 0) {
-	    this._onEmpty();
+	    this._onEmpty(true, null);
 	    return;
 	} else if (this._pendingTasksList.length == 0) {
 	    return;
@@ -156,20 +178,24 @@ const TaskMaster = new Lang.Class({
     _onComplete: function(result, error, task) {
 	if (error) {
 	    print("TaskMaster: While executing " + task.name + ": " + error);
-	    this._taskErrors[task.name] = error;
+	    if (!this._caughtError) {
+		this._caughtError = true;
+		this._onEmpty(false, error);
+	    }
+	    return;
 	} else {
 	    print("TaskMaster: Completed: " + task.name + " : " + JSON.stringify(result));
 	}
 	let idx = -1;
 	for (let i = 0; i < this._executing.length; i++) {
 	    let executingTask = this._executing[i];
-	    if (executingTask.serial != task.serial)
+	    if (executingTask !== task)
 		continue;
 	    idx = i;
 	    break;
 	}
 	if (idx == -1)
-	    throw new Error("TaskMaster: Internal error - Failed to find completed task serial:" + task.serial);
+	    throw new Error("TaskMaster: Internal error - Failed to find completed task:" + task.name);
 	task.result = result;
 	this._completeTasks[task.name] = task;
 	this._executing.splice(idx, 1);
@@ -197,12 +223,8 @@ const TaskMaster = new Lang.Class({
 	       !this._hasDeps(this._pendingTasksList[0])) {
 	    let task = this._pendingTasksList.shift();
 	    print("TaskMaster: running: " + task.name);
-	    let depResults = [];
-	    for (let i = 0; i < task.dependencies.length; i++) {
-		let depName = task.dependencies[i];
-		depResults.push(this._completeTasks[depName].result);
-	    }
-	    task.def.execute(task.vars, depResults, this.cancellable, Lang.bind(this, this._onComplete, task));
+	    let cls = task.def;
+	    task.execute(this.cancellable);
 	    this._executing.push(task);
 	}
     }
@@ -211,22 +233,182 @@ const TaskMaster = new Lang.Class({
 const TaskDef = new Lang.Class({
     Name: 'TaskDef',
 
-    _init: function() {
-    },
+    TaskPattern: null,
 
-    getPattern: function() {
-	throw new Error("Not implemented");
+    _init: function(taskmaster, name, inputs) {
+	this.taskmaster = taskmaster;
+	this.name = name;
+	this.inputs = inputs;
     },
 
     getDepends: function(inputs) {
 	return [];
     },
 
-    execute: function(inputs, dependResults, cancellable, onComplete) {
+    execute: function(cancellable) {
 	throw new Error("Not implemented");
     }
 });
 
+const SubTaskDef = new Lang.Class({
+    Name: 'SubTaskDef',
+    Extends: TaskDef,
+
+    PreserveStdout: true,
+    RetainFailed: 1,
+    RetainSuccess: 5,
+
+    _VERSION_RE: /(\d+\d\d\d\d)\.(\d+)/,
+
+    _loadVersionsFrom: function(dir, cancellable) {
+	let e = dir.enumerate_children('standard::*', Gio.FileQueryInfoFlags.NONE, cancellable);
+	let info;
+	let results = [];
+	while ((info = e.next_file(cancellable)) != null) {
+	    let name = info.get_name();
+	    let match = this._VERSION_RE.exec(name);
+	    if (!match)
+		continue;
+	    results.push(name);
+	}
+	results.sort(BuildUtil.compareVersions);
+	return results;
+    },
+
+    _cleanOldVersions: function(dir, retain, cancellable) {
+	let versions = this._loadVersionsFrom(dir, cancellable);
+	while (versions.length > retain) {
+	    let child = dir.get_child(versions.shift());
+	    GSystem.shutil_rm_rf(child, cancellable);
+	}
+    },
+
+    executeSync: function(cancellable) {
+	throw new Error("Not implemented");
+    },
+
+    execute: function(cancellable) {
+	this._asyncOutstanding = 0;
+	this._cancellable = cancellable;
+
+	this._subtaskdir = this.taskmaster.path.resolve_relative_path(this.name.substr(1));
+	GSystem.file_ensure_directory(this._subtaskdir, true, cancellable);
+	
+	let allVersions = [];
+
+	this._successDir = this._subtaskdir.get_child('successful');
+	GSystem.file_ensure_directory(this._successDir, true, cancellable);
+	let successVersions = this._loadVersionsFrom(this._successDir, cancellable);
+	for (let i = 0; i < successVersions.length; i++) {
+	    allVersions.push([true, successVersions[i]]);
+	}
+
+	this._failedDir = this._subtaskdir.get_child('failed');
+	GSystem.file_ensure_directory(this._failedDir, true, cancellable);
+	let failedVersions = this._loadVersionsFrom(this._failedDir, cancellable);
+	for (let i = 0; i < failedVersions.length; i++) {
+	    allVersions.push([false, failedVersions[i]]);
+	}
+
+	allVersions.sort(function (a, b) {
+	    let [successA, versionA] = a;
+	    let [successB, versionB] = b;
+	    return BuildUtil.compareVersions(versionA, versionB);
+	});
+
+	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 version = null;
+	if (allVersions.length > 0) {
+	    let [lastSuccess, lastVersion] = allVersions[allVersions.length-1];
+	    let match = this._VERSION_RE.exec(lastVersion);
+	    if (!match) throw new Error();
+	    let lastYmd = match[1];
+	    let lastSerial = match[2];
+	    if (lastYmd == currentYmd) {
+		version = currentYmd + '.' + (parseInt(lastSerial) + 1);
+	    }
+	}
+	if (version === null) {
+	    version = currentYmd + '.0';
+	}
+
+	this._workdir = this._subtaskdir.get_child(version);
+	GSystem.shutil_rm_rf(this._workdir, cancellable);
+	GSystem.file_ensure_directory(this._workdir, true, cancellable);
+
+	let baseArgv = ['ostbuild', 'run-task', this.name];
+	let context = new GSystem.SubprocessContext({ argv: baseArgv });
+	context.set_cwd(this._workdir.get_path());
+	context.set_stdin_disposition(GSystem.SubprocessStreamDisposition.PIPE);
+	if (this.PreserveStdout) {
+	    let outPath = this._workdir.get_child('output.txt');
+	    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._workdir.get_child('errors.txt');
+	    context.set_stderr_file_path(errPath.get_path());
+	}
+	let [success, resultpipe, fdno] = context.open_pipe_read(cancellable);
+	context.argv_append(''+fdno);
+	this._proc = new GSystem.Subprocess({ context: context });
+	this._proc.init(cancellable);
+
+	this._proc.wait(cancellable, Lang.bind(this, this._onChildExited));
+	this._asyncOutstanding += 1;
+	
+	this._result = null;
+	this._error = null;
+	JsonUtil.loadJsonFromStreamAsync(resultpipe, cancellable, Lang.bind(this, this._onResultRead));
+	this._asyncOutstanding += 1;
+    },
+    
+    _onAsyncOpComplete: function(error) {
+	this._asyncOutstanding--;
+	if (error && !this._error)
+	    this._error = error;
+	if (this._asyncOutstanding != 0)
+	    return;
+	if (this._error) {
+	    this.onComplete(null, this._error);
+	} else {
+	    this.onComplete(this._result, null);
+	}
+    },
+
+    _onChildExited: function(proc, result) {
+	let [success, errmsg] = ProcUtil.asyncWaitCheckFinish(proc, result);
+	let target;
+	if (!success) {
+	    target = this._failedDir.get_child(this._workdir.get_basename());
+	    GSystem.file_rename(this._workdir, target, null);
+	    this._cleanOldVersions(this._failedDir, this.RetainFailed, null);
+	    this._onAsyncOpComplete(new Error(errmsg));
+	} else {
+	    target = this._successDir.get_child(this._workdir.get_basename());
+	    GSystem.file_rename(this._workdir, target, null);
+	    this._cleanOldVersions(this._successDir, this.RetainSuccess, null);
+	    this._onAsyncOpComplete(null);
+	}
+	print("Stored results of " + this.name + " in " + target.get_path());
+    },
+
+    _onResultRead: function(result, error) {
+	if (!error) {
+	    let childResult = result['result'];
+	    if (childResult)
+		this._result = childResult;
+	    this._onAsyncOpComplete(null);
+	} else {
+	    this._onAsyncOpComplete(error);
+	}
+    }
+});
+
 function demo(argv) {
     var loop = GLib.MainLoop.new(null, true);
     let ecode = 1;
diff --git a/src/ostbuild/js/jsonutil.js b/src/ostbuild/js/jsonutil.js
index 04a82ca..24deb88 100644
--- a/src/ostbuild/js/jsonutil.js
+++ b/src/ostbuild/js/jsonutil.js
@@ -27,6 +27,44 @@ function writeJsonToStream(stream, data, cancellable) {
     stream.write_bytes(new GLib.Bytes(buf), cancellable);
 }
 
+function writeJsonToStreamAsync(stream, data, cancellable, onComplete) {
+    let buf = JSON.stringify(data, null, "  ");
+    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);
diff --git a/src/ostbuild/js/main.js b/src/ostbuild/js/main.js
index ae92800..0f1e2e2 100755
--- a/src/ostbuild/js/main.js
+++ b/src/ostbuild/js/main.js
@@ -26,7 +26,9 @@ const BUILTINS = ['autobuilder',
                   'resolve',
                   'build',
                   'build-disks',
+                  'make',
                   'shell',
+                  'run-task',
                   'qa-make-disk',
 		  'qa-pull-deploy',
 		  'qa-smoketest'];
diff --git a/src/ostbuild/js/subtask.js b/src/ostbuild/js/subtask.js
index ddab482..dca1b7a 100644
--- a/src/ostbuild/js/subtask.js
+++ b/src/ostbuild/js/subtask.js
@@ -92,6 +92,23 @@ const TaskSet = new Lang.Class({
 	this._load();
     },
 
+    _load: function() {
+       var e = this.path.enumerate_children('standard::*', Gio.FileQueryInfoFlags.NONE, null);
+       let info;
+       let history = [];
+       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;
+           history.push(new TaskHistoryEntry(childPath))
+       }
+       history.sort(TaskHistoryEntry.prototype.compareTo);
+       this._history = history;
+       this._cleanOldEntries();
+    },
+
     _cleanOldEntries: function() {
 	while (this._history.length > this._maxVersions) {
 	    let task = this._history.shift();
@@ -99,23 +116,6 @@ const TaskSet = new Lang.Class({
 	}
     },
 
-    _load: function() {
-	var e = this.path.enumerate_children('standard::*', Gio.FileQueryInfoFlags.NONE, null);
-	let info;
-	let history = [];
-	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;
-	    history.push(new TaskHistoryEntry(childPath))
-	}
-	history.sort(TaskHistoryEntry.prototype.compareTo);
-	this._history = history;
-	this._cleanOldEntries();
-    },
-
     _onProcessComplete: function(proc, result) {
 	if (!this._running) throw new Error();
 
diff --git a/src/ostbuild/js/builtins/build.js b/src/ostbuild/js/tasks/task-build.js
similarity index 92%
rename from src/ostbuild/js/builtins/build.js
rename to src/ostbuild/js/tasks/task-build.js
index 7c54a8a..5040dbf 100644
--- a/src/ostbuild/js/builtins/build.js
+++ b/src/ostbuild/js/tasks/task-build.js
@@ -23,7 +23,7 @@ const Format = imports.format;
 const GSystem = imports.gi.GSystem;
 
 const Builtin = imports.builtin;
-const SubTask = imports.subtask;
+const DynTask = imports.dyntask;
 const JsonDB = imports.jsondb;
 const ProcUtil = imports.procutil;
 const StreamUtil = imports.streamutil;
@@ -35,13 +35,13 @@ const Vcs = imports.vcs;
 const ArgParse = imports.argparse;
 
 const OPT_COMMON_CFLAGS = {'i686': '-O2 -g -m32 -march=i686 -mtune=atom -fasynchronous-unwind-tables',
-                           'x86_64': '-O2 -g -m64 -mtune=generic'}
+                           'x86_64': '-O2 -g -m64 -mtune=generic'};
 
-const Build = new Lang.Class({
-    Name: "Build",
-    Extends: Builtin.Builtin,
+const TaskBuild = new Lang.Class({
+    Name: "TaskBuild",
+    Extends: DynTask.SubTaskDef,
 
-    DESCRIPTION: "Build multiple components and generate trees",
+    TaskPattern: [/build\/(.*)$/, 'PREFIX'],
 
     _resolveRefs: function(refs) {
         if (refs.length == 0)
@@ -290,6 +290,7 @@ const Build = new Lang.Class({
 
 	let prefix = this._snapshot.data['prefix'];
         let buildname = Format.vprintf('%s/%s/%s', [prefix, basename, architecture]);
+        let unixBuildname = buildname.replace(/\//g, '_');
         let buildRef = 'components/' + buildname;
 
         let currentVcsVersion = component['revision'];
@@ -320,16 +321,13 @@ const Build = new Lang.Class({
 	let patchdir;
         if (expandedComponent['patches']) {
             let patchesRevision = expandedComponent['patches']['revision'];
-            if (this.args.patches_path) {
-                patchdir = Gio.File.new_for_path(this.args.patches_path);
-            } else if (this._cachedPatchdirRevision == patchesRevision) {
+            if (this._cachedPatchdirRevision == patchesRevision) {
                 patchdir = this.patchdir;
             } else {
                 patchdir = Vcs.checkoutPatches(this.mirrordir,
                                                this.patchdir,
                                                expandedComponent,
-					       cancellable,
-                                               {patchesPath: this.args.patches_path});
+					       cancellable);
                 this._cachedPatchdirRevision = patchesRevision;
 	    }
             if ((previousMetadata != null) &&
@@ -366,33 +364,31 @@ const Build = new Lang.Class({
 	    }
 	}
 
-        let taskdir = this.workdir.get_child('tasks');
-        let buildTaskset = new SubTask.TaskSet(taskdir.get_child(buildname));
+	let cwd = Gio.File.new_for_path('.');
+	let buildWorkdir = cwd.get_child('tmp-' + unixBuildname);
+	GSystem.file_ensure_directory(buildWorkdir, true, cancellable);
 
-	let workdir = buildTaskset.prepare();
-
-        let tempMetadataPath = workdir.get_child('_ostbuild-meta.json');
+        let tempMetadataPath = buildWorkdir.get_child('_ostbuild-meta.json');
         JsonUtil.writeJsonFileAtomic(tempMetadataPath, expandedComponent, cancellable);
 
-        let componentSrc = workdir.get_child(basename);
+        let componentSrc = buildWorkdir.get_child(basename);
         let childArgs = ['ostbuild', 'checkout', '--snapshot=' + this._snapshot.path.get_path(),
 			 '--checkoutdir=' + componentSrc.get_path(),
 			 '--metadata-path=' + tempMetadataPath.get_path(),
 			 '--overwrite', basename];
-        if (this.args.patches_path)
-            childArgs.push('--patches-path=' + this.args.patches_path);
-        else if (patchdir)
+        if (patchdir) {
             childArgs.push('--patches-path=' + patchdir.get_path());
-        ProcUtil.runSync(childArgs, cancellable, { logInitiation: true });
+	}
+        ProcUtil.runSync(childArgs, cancellable);
 
         GSystem.file_unlink(tempMetadataPath, cancellable);
 
-        let componentResultdir = workdir.get_child('results');
+        let componentResultdir = buildWorkdir.get_child('results');
         GSystem.file_ensure_directory(componentResultdir, true, cancellable);
 
-        let rootdir = this._composeBuildroot(workdir, basename, architecture, cancellable);
+        let rootdir = this._composeBuildroot(buildWorkdir, basename, architecture, cancellable);
 
-        let tmpdir=workdir.get_child('tmp');
+        let tmpdir=buildWorkdir.get_child('tmp');
         GSystem.file_ensure_directory(tmpdir, true, cancellable);
 
         let srcCompileOnePath = this.libdir.get_child('ostree-build-compile-one');
@@ -424,26 +420,11 @@ const Build = new Lang.Class({
 
 	let context = new GSystem.SubprocessContext({ argv: childArgs });
 	context.set_environment(ProcUtil.objectToEnvironment(envCopy));
-
-	let mainContext = new GLib.MainContext();
-	mainContext.push_thread_default();
-	let loop = GLib.MainLoop.new(mainContext, true);
-	let t;
-	try {
-	    t = buildTaskset.start(context, cancellable, Lang.bind(this, this._onBuildComplete, loop));
-	    print("Started child process " + context.argv.map(GLib.shell_quote).join(' '));
-	    loop.run();
-	} finally {
-	    mainContext.pop_thread_default();
-	}
-	let buildSuccess = this._currentBuildSucceded;
-	let msg = this._currentBuildSuccessMsg;
-
-        if (!buildSuccess) {
-            this._analyzeBuildFailure(t, architecture, component, componentSrc,
-                                      currentVcsVersion, previousVcsVersion, cancellable);
-	    throw new Error("Build failure in component " + buildname + " : " + msg);
-	}
+	
+	let proc = new GSystem.Subprocess({ context: context });
+	proc.init(cancellable);
+	print("Started child process " + context.argv.map(GLib.shell_quote).join(' '));
+	proc.wait_sync_check(cancellable);
 
         let recordedMetaPath = componentResultdir.get_child('_ostbuild-meta.json');
         JsonUtil.writeJsonFileAtomic(recordedMetaPath, expandedComponent, cancellable);
@@ -472,7 +453,7 @@ const Build = new Lang.Class({
         if (statoverridePath != null)
             GSystem.file_unlink(statoverridePath, cancellable);
 
-        GSystem.shutil_rm_rf(tmpdir, cancellable);
+        GSystem.shutil_rm_rf(buildWorkdir, cancellable);
 
         let ostreeRevision = this._saveComponentBuild(buildname, expandedComponent, cancellable);
 
@@ -623,8 +604,7 @@ const Build = new Lang.Class({
 	Lang.copyProperties(BuildUtil.BUILD_ENV, env);
         env['DL_DIR'] = downloads.get_path();
         env['SSTATE_DIR'] = sstateDir.get_path();
-        ProcUtil.runSync(cmd, cancellable, { logInitiation: true,
-					     env:ProcUtil.objectToEnvironment(env) });
+        ProcUtil.runSync(cmd, cancellable, {env:ProcUtil.objectToEnvironment(env)});
 
 	let componentTypes = ['runtime', 'devel'];
         for (let i = 0; i < componentTypes.length; i++) {
@@ -642,26 +622,31 @@ const Build = new Lang.Class({
 	builtRevisionPath.replace_contents(basemeta['revision'], null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, cancellable);
     },
 
-    _init: function() {
-	this.parent();
-        this.parser.addArgument('--prefix');
-        this.parser.addArgument('--snapshot');
-        this.parser.addArgument('--patches-path');
+    executeSync: function(cancellable) {
+	let prefix = this.inputs.PREFIX;
 
         this.forceBuildComponents = {};
         this.cachedPatchdirRevision = null;
-    },
-        
-    execute: function(args, loop, cancellable) {
-	this._initSnapshot(args.prefix, args.snapshot, cancellable);
-	this.args = args;
+
+	this.prefix = prefix;
+	this.config = Config.get();
+	this.workdir = Gio.File.new_for_path(this.config.getGlobal('workdir'));
+	this.patchdir = this.workdir.get_child('patches');
+	this.mirrordir = Gio.File.new_for_path(this.config.getGlobal('mirrordir'));
+	this.libdir = Gio.File.new_for_path(GLib.getenv('OSTBUILD_LIBDIR'));
+	this.repo = this.workdir.get_child('repo');
+	let snapshotDir = this.workdir.get_child('snapshots');
+	let srcdb = new JsonDB.JsonDB(snapshotDir.get_child(prefix));
+	let snapshotPath = srcdb.getLatestPath();
+	let data = srcdb.loadFromPath(snapshotPath, cancellable);
+	this._snapshot = new Snapshot.Snapshot(data, snapshotPath);
 
         let components = this._snapshot.data['components'];
 
 	let buildresultDir = this.workdir.get_child('builds').get_child(this.prefix);
 	let builddb = new JsonDB.JsonDB(buildresultDir);
 
-	let targetSourceVersion = builddb.parseVersionStr(this._snapshot.path.get_basename())
+	let targetSourceVersion = builddb.parseVersionStr(this._snapshot.path.get_basename());
 
 	let haveLocalComponent = false;
         for (let i = 0; i < components.length; i++) {
diff --git a/src/ostbuild/ostbuild.in b/src/ostbuild/ostbuild.in
index 01d75bc..258d9d0 100755
--- a/src/ostbuild/ostbuild.in
+++ b/src/ostbuild/ostbuild.in
@@ -25,4 +25,4 @@ export GIO_USE_VFS=local
 export OSTBUILD_DATADIR= pkgdatadir@
 export OSTBUILD_LIBDIR= pkglibdir@
 
-exec gjs -I "${jsdir}" "${jsdir}/main.js" "$@"
+exec $OSTBUILD_GDB gjs -I "${jsdir}" "${jsdir}/main.js" "$@"



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]