[gnome-ostree/wip/gjs-round2: 9/9] Port build to gjs



commit dda70e1ab5c4d8c8e71b031808921ed0b24e9819
Author: Colin Walters <walters verbum org>
Date:   Sun Dec 9 18:59:58 2012 -0500

    Port build to gjs
    
    Really almost there.

 Makefile-ostbuild.am                     |    5 +-
 src/libgsystem                           |    2 +-
 src/ostbuild/js/build.js                 |  764 ++++++++++++++++++++++++++++++
 src/ostbuild/js/buildutil.js             |   41 ++
 src/ostbuild/js/checkout.js              |   10 +-
 src/ostbuild/js/git_mirror.js            |    2 +-
 src/ostbuild/js/jsondb.js                |    2 +-
 src/ostbuild/js/procutil.js              |   57 ++-
 src/ostbuild/js/resolve.js               |    2 -
 src/ostbuild/js/snapshot.js              |   12 +-
 src/ostbuild/js/streamutil.js            |   10 +
 src/ostbuild/js/vcs.js                   |    6 +-
 src/ostbuild/ostbuild-js.in              |    1 +
 src/ostbuild/pyostbuild/builtin_build.py |  724 ----------------------------
 src/ostbuild/pyostbuild/main.py          |    3 +-
 15 files changed, 884 insertions(+), 757 deletions(-)
---
diff --git a/Makefile-ostbuild.am b/Makefile-ostbuild.am
index 6aa61ec..f7d6445 100644
--- a/Makefile-ostbuild.am
+++ b/Makefile-ostbuild.am
@@ -39,12 +39,11 @@ utils_SCRIPTS = \
 	src/ostbuild/ostree-build-compile-one \
 	src/ostbuild/ostree-build-yocto \
 	$(NULL)
-utilsdir = $(libdir)/ostbuild
+utilsdir = $(pkglibdir)
 
 pyostbuilddir=$(libdir)/ostbuild/pyostbuild
 pyostbuild_PYTHON =					\
 	src/ostbuild/pyostbuild/buildutil.py		\
-	src/ostbuild/pyostbuild/builtin_build.py	\
 	src/ostbuild/pyostbuild/builtins.py		\
 	src/ostbuild/pyostbuild/filemonitor.py		\
 	src/ostbuild/pyostbuild/task.py		\
@@ -68,6 +67,7 @@ jsostbuilddir=$(pkgdatadir)/js
 jsostbuild_DATA= \
 	src/ostbuild/js/argparse.js \
 	src/ostbuild/js/autobuilder.js \
+	src/ostbuild/js/build.js \
 	src/ostbuild/js/buildutil.js \
 	src/ostbuild/js/checkout.js \
 	src/ostbuild/js/config.js \
@@ -79,6 +79,7 @@ jsostbuild_DATA= \
 	src/ostbuild/js/procutil.js \
 	src/ostbuild/js/resolve.js \
 	src/ostbuild/js/snapshot.js \
+	src/ostbuild/js/streamutil.js \
 	src/ostbuild/js/task.js \
 	src/ostbuild/js/vcs.js \
 	$(NULL)
diff --git a/src/libgsystem b/src/libgsystem
index aac3668..79ad77e 160000
--- a/src/libgsystem
+++ b/src/libgsystem
@@ -1 +1 @@
-Subproject commit aac3668399f8058877cf21ba35617ec4cfecd500
+Subproject commit 79ad77e45b5095494361e3f525a105708d8d0cb1
diff --git a/src/ostbuild/js/build.js b/src/ostbuild/js/build.js
new file mode 100644
index 0000000..ecd708d
--- /dev/null
+++ b/src/ostbuild/js/build.js
@@ -0,0 +1,764 @@
+// 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 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 Config = imports.config;
+const BuildUtil = imports.buildutil;
+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'}
+
+var loop = GLib.MainLoop.new(null, true);
+
+const Build = new Lang.Class({
+    Name: "Build",
+
+    _resolveRefs: function(refs) {
+        if (refs.length == 0)
+            return [];
+        let args = ['ostree', '--repo=' + this.repo.get_path(), 'rev-parse']
+        args.push.apply(args, refs);
+        return ProcUtil.runSyncGetOutputLines(args, null);
+    },
+
+    _cleanStaleBuildroots: function(buildrootCachedir, keepRoot, cancellable) {
+	let direnum = buildrootCachedir.enumerate_children("standard::*,unix::mtime",
+							   Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, cancellable);
+        let rootMtimes = [];
+	let finfo;
+	while ((finfo = direnum.next_file(cancellable)) != null) {
+	    rootMtimes.push([buildrootCachedir.get_child(finfo.get_name()), finfo.get_attribute_uint32('unix::mtime')]);
+	}
+        rootMtimes.sort(function (a,b) { let ma = a[1]; let mb = b[1]; if (ma == mb) return 0; else if (ma < mb) return -1; return 1;});
+        for (let i = 0; i < rootMtimes.length - 2; i++) {
+	    let path = rootMtimes[i][0];
+            if (path.equal(keepRoot)) {
+                continue;
+	    }
+            log("Removing old cached buildroot " + path.get_path());
+            GSystem.shutil_rm_rf(path, cancellable);
+	}
+    },
+
+    _composeBuildroot: function(workdir, componentName, architecture, cancellable) {
+        let starttime = GLib.DateTime.new_now_utc();
+
+        let buildname = Format.vprintf('%s/%s/%s', [this._snapshot['prefix'], componentName, architecture]);
+        let buildrootCachedir = this.workdir.resolve_relative_path('roots/' + buildname);
+        GSystem.file_ensure_directory(buildrootCachedir, true, cancellable);
+
+        let components = this._snapshot['components']
+        let component = null;
+        let buildDependencies = [];
+        for (let i = 0; i < components.length; i++) {
+	    let component = components[i];
+            if (component['name'] == componentName)
+                break;
+            buildDependencies.push(component);
+	}
+
+        let refToRev = {};
+
+        let prefix = this._snapshot['prefix'];
+
+        let archBuildrootName = Format.vprintf('bases/%s/%s-%s-devel', [this._snapshot['base']['name'],
+									prefix,
+									architecture]);
+
+        log("Computing buildroot contents");
+
+        let archBuildrootRev = ProcUtil.runSyncGetOutputUTF8Stripped(['ostree', '--repo=' + this.repo.get_path(), 'rev-parse',
+								      archBuildrootName], cancellable);
+
+        refToRev[archBuildrootName] = archBuildrootRev;
+        let checkoutTrees = [[archBuildrootName, '/']];
+        let refsToResolve = [];
+        for (let i = 0; i < buildDependencies.length; i++) {
+	    let dependency = buildDependencies[i];
+            let buildname = Format.vprintf('components/%s/%s/%s', [prefix, dependency['name'], architecture]);
+            refsToResolve.push(buildname);
+            checkoutTrees.push([buildname, '/runtime']);
+            checkoutTrees.push([buildname, '/devel']);
+	}
+
+        let resolvedRefs = this._resolveRefs(refsToResolve);
+	for (let i = 0; i < refsToResolve.length; i++) {
+	    refToRev[refsToResolve[i]] = resolvedRefs[i];
+	}
+
+        let toChecksumData = '';
+
+	let creds = new Gio.Credentials();
+        let uid = creds.get_unix_user();
+        let gid = creds.get_unix_user();
+        let etcPasswd = Format.vprintf('root:x:0:0:root:/root:/bin/bash\nbuilduser:x:%d:%d:builduser:/:/bin/bash\n', [uid, gid]);
+        let etcGroup = Format.vprintf('root:x:0:root\nbuilduser:x:%d:builduser\n', [gid]);
+
+	toChecksumData += etcPasswd;
+	toChecksumData += etcGroup;
+
+	let [tmpPath, stream] = Gio.File.new_tmp("ostbuild-buildroot-XXXXXX.txt");
+	let dataOut = Gio.DataOutputStream.new(stream.get_output_stream());
+	for (let i = 0; i < checkoutTrees.length; i++) {
+	    let [branch, subpath] = checkoutTrees[i];
+	    let rev = refToRev[branch];
+	    toChecksumData += refToRev[branch];
+	    dataOut.put_string(refToRev[branch], cancellable);
+	    dataOut.put_byte(0, cancellable);
+	    dataOut.put_string(subpath, cancellable);
+	    dataOut.put_byte(0, cancellable);
+	}
+        dataOut.close(cancellable);
+
+	let newRootCacheid = GLib.compute_checksum_for_bytes(GLib.ChecksumType.SHA256, new GLib.Bytes(toChecksumData));
+
+        let cachedRoot = buildrootCachedir.get_child(newRootCacheid);
+        if (cachedRoot.query_exists(cancellable)) {
+            log("Reusing cached buildroot: " + cachedRoot.get_path());
+            this._cleanStaleBuildroots(buildrootCachedir, cachedRoot, cancellable);
+            GSystem.file_unlink(tmpPath, cancellable);
+            return cachedRoot;
+	}
+
+        if (checkoutTrees.length > 0) {
+            log("composing buildroot from %d parents (last: %s)" % (checkoutTrees.length,
+                                                                    checkoutTrees[checkoutTrees.length-1][0]));
+	}
+
+        let cachedRootTmp = cachedRoot.get_parent().get_child(cachedRoot.get_basename() + '.tmp');
+	GSystem.shutil_rm_rf(cachedRootTmp, cancellable);
+        ProcUtil.runSync(['ostree', '--repo=' + this.repo.get_path(),
+			  'checkout', '--user-mode', '--union',
+			  '--from-file=' + tmpPath.get_path(), cachedRootTmp.get_path()], cancellable);
+        GSystem.file_unlink(tmpPath, cancellable);
+
+        let builddirTmp = cachedRootTmp.get_child('ostbuild');
+        GSystem.file_ensure_directory(builddirTmp.resolve_relative_path('source/' + componentName), true, cancellable);
+	GSystem.file_ensure_directory(builddirTmp.get_child('results'), true, cancellable);
+	cachedRootTmp.resolve_relative_path('etc/passwd').replace_contents(etcPasswd, null, false,
+									   Gio.FileCreateFlags.REPLACE_DESTINATION, cancellable);
+	cachedRootTmp.resolve_relative_path('etc/group').replace_contents(etcGroup, null, false,
+									  Gio.FileCreateFlags.REPLACE_DESTINATION, cancellable);
+        GSystem.file_rename(cachedRootTmp, cachedRoot, cancellable);
+
+        this._cleanStaleBuildroots(buildrootCachedir, cachedRoot, cancellable);
+
+        let endtime = GLib.DateTime.new_now_utc();
+        log(Format.vprintf("Composed buildroot; %d seconds elapsed", [endtime.difference(starttime) / GLib.USEC_PER_SEC]));
+        return cachedRoot;
+     },
+
+    _analyzeBuildFailure: function(t, architecture, component, componentSrcdir,
+				   currentVcsVersion, previousVcsVersion,
+				   cancellable) {
+        let dataIn = Gio.DataInputStream.new(t.logfile_path.read());
+        let lines = StreamUtil.dataInputStreamReadLines(dataIn, cancellable);
+        dataIn.close();
+	let maxLines = 250;
+	lines = lines.splice(Math.max(0, lines.length-maxLines), maxLines);
+        for (let i = 0; i < lines.length; i++) {
+            print("| " + line);
+	}
+        if (currentVcsVersion && previousVcsVersion) {
+            let args = ['git', 'log', '--format=short'];
+            args.push(previousVcsVersion + '...' + currentVcsVersion);
+            let env = GLib.get_environ();
+            env.push('GIT_PAGER=cat');
+	    ProcUtil.runSync(args, cancellable, {cwd: componentSrcdir,
+						 env: env});
+        } else {
+            log("No previous build; skipping source diff");
+	}
+     },
+
+    _compareAny: function(a, b) {
+	if (typeof(a) == 'string') {
+	    return a == b;
+	} else if (a.length != undefined) {
+	    if (a.length != b.length)
+		return false;
+	    for (let i = 0; i < a.length; i++) {
+		if (a[i] != b[i]) {
+		    return false;
+		}
+	    }
+	} else {
+	    for (let k in a) {
+		if (b[k] != a[k])
+		    return false;
+	    }
+	    for (let k in b) {
+		if (a[k] == undefined)
+		    return false;
+	    }
+	}
+	return true;
+    },
+
+    _needsRebuild: function(previousMetadata, newMetadata) {
+        let buildKeys = ['config-opts', 'src', 'revision'];
+        for (let i = 0; i < buildKeys.length; i++) {
+	    let k = buildKeys[i];
+            if (!newMetadata[k]) {
+                return 'key ' + k + ' removed from new_metadata';
+	    }
+            if (previousMetadata[k]) {
+                let oldval = previousMetadata[k];
+                let newval = newMetadata[k];
+                if (!this._compareAny(oldval,newval)) {
+                    return Format.vprintf('key %s differs (%s -> %s)', [k, oldval, newval]);
+		}
+	    }
+	}
+            
+        if (previousMetadata['patches']) {
+            if (!newMetadata['patches']) {
+                return 'patches differ';
+	    }
+            let oldPatches = previousMetadata['patches'];
+            let newPatches = newMetadata['patches'];
+            let oldFiles = oldPatches['files'];
+            let newFiles = newPatches['files'];
+            if (oldFiles.length != newFiles.length) {
+                return 'patches differ';
+	    }
+            let oldSha256sums = oldPatches['files_sha256sums'];
+            let newSha256sums = newPatches['files_sha256sums'];
+            if ((!oldSha256sums || !newSha256sums) ||
+                !this._compareAny(oldSha256sums, newSha256sums)) {
+                return 'patch sha256sums differ';
+	    }
+	}
+        return null;
+    },
+
+    _computeSha256SumsForPatches: function(patchdir, component, cancellable) {
+        let patches = BuildUtil.getPatchPathsForComponent(patchdir, component);
+        let result = [];
+        for (let i = 0; i < patches.length; i++) {
+	    let contentsBytes = GSystem.file_map_readonly(patches[i], cancellable);
+	    let csum = GLib.compute_checksum_for_bytes(GLib.ChecksumType.SHA256,
+						       contentsBytes);
+            result.push(csum);
+	}
+        return result;
+    },
+
+    _saveComponentBuild: function(buildname, expandedComponent, cancellable) {
+        let buildRef = 'components/' + buildname;
+	let cachedata = {};
+	Lang.copyProperties(expandedComponent, cachedata);
+        cachedata['ostree'] = ProcUtil.runSyncGetOutputUTF8Stripped(['ostree', '--repo=' + this.repo.get_path(),
+								     'rev-parse', buildRef], cancellable);
+        this._componentBuildCache[buildname] = cachedata;
+        JsonUtil.writeJsonFileAtomic(this._componentBuildCachePath, this._componentBuildCache, cancellable);
+        return cachedata['ostree'];
+    },
+
+    _buildOneComponent: function(component, architecture, cancellable) {
+        let basename = component['name'];
+
+        let buildname = Format.vprintf('%s/%s/%s', [this._snapshot['prefix'], basename, architecture]);
+        let buildRef = 'components/' + buildname;
+
+        let currentVcsVersion = component['revision'];
+        let expandedComponent = Snapshot.expandComponent(this._snapshot, component);
+        let previousMetadata = this._componentBuildCache[buildname];
+        let wasInBuildCache = (previousMetadata != null);
+	let previousBuildVersion;
+        if (wasInBuildCache) {
+            previousBuildVersion = previousMetadata['ostree'];
+        } else {
+            previousBuildVersion = ProcUtil.runSyncGetOutputUTF8StrippedOrNull(['ostree', '--repo=' + this.repo.get_path(),
+										'rev-parse', buildRef]);
+	}
+	let previousVcsVersion;
+        if (previousMetadata != null) {
+            previousVcsVersion = previousMetadata['revision'];
+        } else if (previousBuildVersion != null) {
+            let jsonstr = ProcUtil.runSyncGetOutputUTF8(['ostree', '--repo=' + this.repo.get_path(),
+							 'cat', previousBuildVersion,
+							 '/_ostbuild-meta.json']);
+	    previousMetadata = JSON.parse(jsonstr);
+            previousVcsVersion = previousMetadata['revision'];
+        } else {
+            log("No previous build for " + buildname);
+            previousVcsVersion = null;
+	}
+
+        if (expandedComponent['patches']) {
+            let patchesRevision = expandedComponent['patches']['revision'];
+	    let patchdir;
+            if (this.args.patches_path) {
+                patchdir = Gio.File.new_for_path(this.args.patches_path);
+            } else if (this._cachedPatchdirRevision == patchesRevision) {
+                patchdir = this.patchdir;
+            } else {
+                patchdir = Vcs.checkoutPatches(this.mirrordir,
+                                               this.patchdir,
+                                               expandedComponent,
+					       cancellable,
+                                               {patchesPath: this.args.patches_path});
+                this.cachedPatchdirRevision = patchesRevision;
+	    }
+	    log("patchdir=" + patchdir.get_path());
+            if ((previousMetadata != null) &&
+                previousMetadata['patches'] &&
+                previousMetadata['patches']['revision'] &&
+                previousMetadata['patches']['revision'] == patchesRevision) {
+                // Copy over the sha256sums
+                expandedComponent['patches'] = previousMetadata['patches'];
+            } else {
+                let patchesSha256sums = this._computeSha256SumsForPatches(patchdir, expandedComponent, cancellable);
+                expandedComponent['patches']['files_sha256sums'] = patchesSha256sums;
+	    }
+        } else {
+            patchdir = null;
+	}
+
+        let forceRebuild = (this.forceBuildComponents[basename] ||
+                            expandedComponent['src'].indexOf('local:') == 0);
+
+        if (previousMetadata != null) {
+            let rebuildReason = this._needsRebuild(previousMetadata, expandedComponent);
+            if (rebuildReason == null) {
+                if (!forceRebuild) {
+                    log(Format.vprintf("Reusing cached build of %s at %s", [buildname, previousVcsVersion]));
+                    if (!wasInBuildCache) {
+                        return this._saveComponentBuild(buildname, expandedComponent, cancellable);
+		    }
+                    return previousBuildVersion;
+                } else {
+                    log("Build forced regardless");
+		}
+            } else {
+                log(Format.vprintf("Need rebuild of %s: %s", [buildname, rebuildReason]));
+	    }
+	}
+
+        let taskdir = new Task.TaskDir(this.workdir.get_child('tasks'));
+        let buildTaskset = taskdir.get(buildname);
+        let t = buildTaskset.start()
+        let workdir = t.path;
+
+        let tempMetadataPath = workdir.get_child('_ostbuild-meta.json');
+        JsonUtil.writeJsonFileAtomic(tempMetadataPath, expandedComponent, cancellable);
+
+        let checkoutdir = this.workdir.get_child('checkouts');
+        let componentSrc = checkoutdir.get_child(buildname);
+        GSystem.file_ensure_directory(componentSrc.get_parent(), true, cancellable);
+        let childArgs = ['ostbuild', 'checkout', '--snapshot=' + this._snapshotPath.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)
+            childArgs.push('--patches-path=' + patchdir);
+        ProcUtil.runSync(childArgs, cancellable);
+
+        GSystem.file_unlink(tempMetadataPath, cancellable);
+
+        let componentResultdir = workdir.get_child('results');
+        GSystem.file_ensure_directory(componentResultdir, true, cancellable);
+
+        let rootdir = this._composeBuildroot(workdir, basename, architecture, cancellable);
+
+        let tmpdir=workdir.get_child('tmp');
+        GSystem.file_ensure_directory(tmpdir, true, cancellable);
+
+        let srcCompileOnePath = this.libdir.get_child('ostree-build-compile-one');
+        let destCompileOnePath = rootdir.get_child('ostree-build-compile-one');
+	srcCompileOnePath.copy(destCompileOnePath, Gio.FileCopyFlags.OVERWRITE,
+			       cancellable, null);
+        GSystem.file_chmod(destCompileOnePath, 493, cancellable);
+        
+        let chrootSourcedir = Gio.File.new_for_path('/ostbuild/source/' + basename);
+
+        childArgs = ['setarch', architecture];
+        childArgs.push.apply(childArgs, BuildUtil.getBaseUserChrootArgs());
+        childArgs.push.apply(childArgs, [
+                '--mount-readonly', '/',
+                '--mount-proc', '/proc', 
+                '--mount-bind', '/dev', '/dev',
+                '--mount-bind', tmpdir.get_path(), '/tmp',
+                '--mount-bind', componentSrc.get_path(), chrootSourcedir.get_path(),
+                '--mount-bind', componentResultdir.get_path(), '/ostbuild/results',
+                '--chdir', chrootSourcedir.get_path(),
+                rootdir.get_path(), '/ostree-build-compile-one',
+                '--ostbuild-resultdir=/ostbuild/results',
+                '--ostbuild-meta=_ostbuild-meta.json']);
+	let envCopy = {};
+	Lang.copyProperties(BuildUtil.BUILD_ENV, envCopy);
+        envCopy['PWD'] = chrootSourcedir.get_path();
+        envCopy['CFLAGS'] = OPT_COMMON_CFLAGS[architecture];
+        envCopy['CXXFLAGS'] = OPT_COMMON_CFLAGS[architecture];
+
+	let context = new GSystem.SubprocessContext({ argv: childArgs });
+	context.set_stdout_file_path(t.logfile_path.get_path());
+	context.set_stderr_disposition(GSystem.SubprocessStreamDisposition.STDERR_MERGE);
+	context.set_environment(ProcUtil.objectToEnvironment(envCopy));
+	let proc = new GSystem.Subprocess({ context: context });
+	proc.init(cancellable);
+	let [res, estatus] = proc.wait_sync(cancellable);
+        if (!res) {
+            buildTaskset.finish(false);
+            this._analyzeBuildFailure(t, architecture, component, component_src,
+                                      currentVcsVersion, previousVcsVersion);
+	    throw new Error("Build failure in component " + buildname);
+	}
+
+        let recordedMetaPath = componentResultdir.get_child('_ostbuild-meta.json');
+        JsonUtil.writeJsonFileAtomic(recordedMetaPath, expandedComponent, cancellable);
+
+        let commitArgs = ['ostree', '--repo=' + this.repo.get_path(),
+			  'commit', '-b', buildRef, '-s', 'Build',
+			  '--owner-uid=0', '--owner-gid=0', '--no-xattrs', 
+			  '--skip-if-unchanged'];
+
+        let setuidFiles = expandedComponent['setuid'] || [];
+        let statoverridePath = null;
+        if (setuidFiles.length > 0) {
+	    let [statoverridePath, stream] = Gio.File.new_tmp("ostbuild-statoverride-XXXXXX.txt");
+	    let dataOut = Gio.DataOutputStream.new(stream.get_output_stream());
+	    for (let i = 0; i < setuidFiles.length; i++) {
+		dataOut.put_string("+2048 ", cancellable);
+		dataOut.put_string(path, cancellable);
+		dataOut.put_string("\n", cancellable);
+	    }
+            dataOut.close(cancellable);
+            commitArgs.push('--statoverride=' + statoverridePath.get_path());
+	}
+
+        ProcUtil.runSync(commitArgs, cancellable, {cwd: componentResultdir});
+        if (statoverridePath != null)
+            GSystem.file_unlink(statoverridePath, cancellable);
+
+        GSystem.shutil_rm_rf(tmpdir, cancellable);
+
+        let ostreeRevision = this._saveComponentBuild(buildname, expandedComponent, cancellable);
+
+        buildTaskset.finish(true);
+
+        return ostreeRevision;
+    },
+
+    _composeOneTarget: function(target, componentBuildRevs, cancellable) {
+        let base = target['base'];
+        let baseName = 'bases/' + base['name'];
+        let runtimeName = 'bases/' + base['runtime'];
+        let develName = 'bases/' + base['devel'];
+
+	let rootdir = this.workdir.get_child('roots');
+        let composeRootdir = rootdir.get_child(target['name']);
+	GSystem.shutil_rm_rf(composeRootdir, cancellable);
+        GSystem.file_ensure_directory(composeRootdir, cancellable);
+
+        let relatedRefs = {};
+        let baseRevision = ProcUtil.runSyncGetOutputUTF8Stripped(['ostree', '--repo=' + this.repo.get_path(),
+								  'rev-parse', baseName], cancellable);
+
+        let runtimeRevision = ProcUtil.runSyncGetOutputUTF8Stripped(['ostree', '--repo=' + this.repo.get_path(),
+								     'rev-parse', runtimeName], cancellable);
+        relatedRefs[runtimeName] = runtimeRevision;
+        let develRevision = ProcUtil.runSyncGetOutputUTF8Stripped(['ostree', '--repo=' + this.repo.get_path(),
+								   'rev-parse', develName], cancellable);
+        relatedRefs[develName] = develRevision;
+
+	for (let name in componentBuildRevs) {
+	    let rev = componentBuildRevs[name];
+            let buildRef = 'components/' + this._snapshot['prefix'] + '/' + name;
+            relatedRefs[buildRef] = rev;
+	}
+
+	let [relatedTmpPath, stream] = Gio.File.new_tmp("ostbuild-compose-XXXXXX.txt");
+	let dataOut = Gio.DataOutputStream.new(stream.get_output_stream());
+	for (let name in relatedRefs) {
+	    let rev = relatedRefs[name];
+	    dataOut.put_string(name, cancellable);
+	    dataOut.put_string(' ', cancellable);
+	    dataOut.put_string(rev, cancellable);
+	    dataOut.put_string('\n', cancellable);
+	}
+	dataOut.close(cancellable);
+
+        let composeContents = [[baseRevision, '/']];
+        for (let i = 0; i < target['contents'].length; i++) {
+	    let treeContent = target['contents'][i];
+            let name = treeContent['name'];
+            let rev = componentBuildRevs[name];
+            let subtrees = treeContent['trees'];
+            for (let j = 0; j < subtrees.length; j++) {
+		let subpath = subtrees[j];
+                compose_contents.append([rev, subpath]);
+	    }
+	}
+
+	let [contentsTmpPath, stream] = Gio.File.new_tmp("ostbuild-compose-XXXXXX.txt");
+	let dataOut = Gio.DataOutputStream.new(stream.get_output_stream());
+	for (let i = 0; i < composeContents.length; i++) {
+	    let [branch, subpath] = composeContents[i];
+            dataOut.put_string(branch, cancellable);
+	    dataOut.put_byte(0, cancellable);
+            dataOut.put_string(subpath, cancellable);
+	    dataOut.put_byte(0, cancellable);
+	}
+        dataOut.close(cancellable);
+
+        ProcUtil.runSync(['ostree', '--repo=' + this.repo.get_path(),
+			  'checkout', '--user-mode', '--no-triggers', '--union', 
+			  '--from-file=' + contentsTmpPath.get_path(), composeRootdir.get_path()],
+			cancellable);
+        GSystem.file_unlink(contentsTmppath, cancellable);
+
+        contentsPath = composeRootdir.get_child('contents.json');
+        JsonUtil.writeJsonFileAtomic(contentsPath, this._snapshot, cancellable);
+
+        let treename = 'trees/' + target['name'];
+        
+        ProcUtil.runSync(['ostree', '--repo=' + this.repo.get_path(),
+			 'commit', '-b', treename, '-s', 'Compose',
+			 '--owner-uid=0', '--owner-gid=0', '--no-xattrs', 
+			 '--related-objects-file=' + relatedTmpPath.get_path(),
+			 '--skip-if-unchanged'
+			 ], cancellable, {cwd: composeRootdir.get_path()});
+        GSystem.file_unlink(relatedTmpPath, cancellable);
+        GSystem.shutil_rm_rf(composeRootdir, cancellable);
+    },
+
+    /* Build the Yocto base system. */
+    _buildBase: function(architecture, cancellable) {
+        let basemeta = this._snapshot['base'];
+        let checkoutdir = this.workdir.get_child('checkouts').get_child(basemeta['name']);
+	GSystem.file_ensure_directory(checkoutdir, true, cancellable);
+
+	let ftype = checkoutdir.query_file_type(Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, cancellable);
+        if (ftype == Gio.FileType.SYMBOLIC_LINK)
+	    GSystem.file_unlink(checkoutdir, cancellable);
+
+        let [keytype, uri] = Vcs.parseSrcKey(basemeta['src']);
+        if (keytype == 'local') {
+	    GSystem.shutil_rm_rf(checkoutdir, cancellable);
+	    checkoutdir.make_symbolic_link(uri, cancellable);
+        } else {
+            Vcs.getVcsCheckout(this.mirrordir, keytype, uri, checkoutdir,
+                               basemeta['revision'], cancellable,
+                               {overwrite:false});
+	}
+
+        let builddirName = Format.vprintf('build-%s-%s', [basemeta['name'], architecture]);
+        let builddir = this.workdir.get_child(builddirName);
+
+        // Just keep reusing the old working directory downloads and sstate
+        let oldBuilddir = this.workdir.get_child('build-' + basemeta['name']);
+        let sstateDir = oldBuilddir.get_child('sstate-cache');
+        let downloads = oldBuilddir.get_child('downloads');
+
+        let cmd = ['linux-user-chroot', '--unshare-pid', '/',
+		   this.libdir.get_path() + '/ostree-build-yocto',
+		   checkoutdir.get_path(), builddir.get_path(), architecture,
+		   this.repo.get_path()];
+        // We specifically want to kill off any environment variables jhbuild
+        // may have set.
+        env = {};
+	Lang.copyProperties(BuildUtil.BUILD_ENV, env);
+        env['DL_DIR'] = downloads.get_path();
+        env['SSTATE_DIR'] = sstateDir.get_path();
+        ProcUtil.runSync(cmd, cancellable, {env:ProcUtil.objectToEnvironment(env)});
+    },
+        
+    execute: function(argv) {
+	let cancellable = null;
+
+        let parser = new ArgParse.ArgumentParser("Build multiple components and generate trees");
+        parser.addArgument('--prefix');
+        parser.addArgument('--src-snapshot');
+        parser.addArgument('--patches-path');
+        parser.addArgument('components', {nargs:'*'});
+        
+        let args = parser.parse(argv);
+	this.args = args;
+
+	this.config = Config.get();
+	this.workdir = Gio.File.new_for_path(this.config.getGlobal('workdir'));
+	this.mirrordir = Gio.File.new_for_path(this.config.getGlobal('mirrordir'));
+	this.patchdir = this.workdir.get_child('patches');
+	this.prefix = args.prefix || this.config.getPrefix();
+	this._snapshotDir = this.workdir.get_child('snapshots');
+	this.libdir = Gio.File.new_for_path(GLib.getenv('OSTBUILD_LIBDIR'));
+
+	this._srcDb = new JsonDB.JsonDB(this._snapshotDir, this.prefix + '-src-snapshot');
+	[this._snapshot, this._snapshotPath] = Snapshot.load(this._srcDb, this.prefix, args.snapshot, cancellable);
+	
+        this.forceBuildComponents = {};
+        this.cachedPatchdirRevision = null;
+
+	this.repo = this.workdir.get_child('repo');
+
+        GSystem.file_ensure_directory(this.repo, true, cancellable);
+        if (!this.repo.get_child('objects').query_exists(cancellable)) {
+            ProcUtil.runSync(['ostree', '--repo=' + this.repo.get_path(), 'init', '--archive'],
+			     cancellable);
+	}
+
+        let components = this._snapshot['components'];
+
+        let prefix = this._snapshot['prefix'];
+        let basePrefix = this._snapshot['base']['name'] + '/' + prefix;
+        let architectures = this._snapshot['architectures'];
+
+        for (let i = 0; i < architectures.length; i++) {
+            this._buildBase(architectures[i], cancellable);
+	}
+
+        let componentToArches = {};
+
+        let runtimeComponents = [];
+        let develComponents = [];
+
+        for (let i = 0; i < components.length; i++) {
+	    let component = components[i];
+            let name = component['name']
+
+            let isRuntime = (component['component'] || 'runtime') == 'runtime';
+
+            if (isRuntime) {
+                runtimeComponents.push(component);
+	    }
+            develComponents.push(component);
+
+	    let isNoarch = component['noarch'] || false;
+	    let componentArches;
+            if (isNoarch) {
+                // Just use the first specified architecture
+                componentArches = [architectures[0]];
+            } else {
+                componentArches = component['architectures'] || architectures;
+	    }
+            componentToArches[name] = componentArches;
+	}
+
+        for (let i = 0; i < args.components.length; i++) {
+	    let name = args.components[i];
+            let component = Snapshot.getComponent(this._snapshot, name);
+            this._forceBuildComponents[name] = true;
+	}
+
+        let componentsToBuild = [];
+        let componentSkippedCount = 0;
+        let componentBuildRevs = {};
+
+        for (let i = 0; i < components.length; i++) {
+	    let component = components[i];
+            for (let j = 0; j < architectures.length; j++) {
+                componentsToBuild.push([component, architectures[j]]);
+	    }
+	}
+
+        this._componentBuildCachePath = this.workdir.get_child('component-builds.json');
+        if (this._componentBuildCachePath.query_exists(cancellable)) {
+            this._componentBuildCache = JsonUtil.loadJson(this._componentBuildCachePath, cancellable);
+        } else {
+            this._componentBuildCache = {};
+	}
+
+        for (let i = 0; i < componentsToBuild.length; i++) {
+	    let [component, architecture] = componentsToBuild[i];
+            let archname = component['name'] + '/' + architecture;
+            let buildRev = this._buildOneComponent(component, architecture, cancellable);
+            componentBuildRevs[archname] = buildRev;
+	}
+
+        let targetsList = [];
+	let componentTypes = ['runtime', 'devel'];
+        for (let i = 0; i < componentTypes.length; i++) {
+	    let targetComponentType = componentTypes[i];
+            for (let i = 0; i < architectures.length; i++) {
+		let architecture = architectures[i];
+                let target = {};
+                targetsList.push(target);
+                target['name'] = prefix + '-' + architecture + '-' + target_component_type;
+
+                let runtimeRef = base_prefix + '-' + architecture + '-runtime';
+                let buildrootRef = base_prefix + '-' + architecture + '-devel';
+		let baseRef;
+                if (targetComponentType == 'runtime') {
+                    baseRef = runtimeRef;
+                } else {
+                    baseFef = buildrootRef;
+		}
+                target['base'] = {'name': baseRef,
+                                  'runtime': runtimeRef,
+                                  'devel': buildrootRef};
+
+		let targetComponents;
+                if (targetComponentType == 'runtime') {
+                    targetComponents = runtimeComponents;
+                } else {
+                    targetComponents = develComponents;
+		}
+                    
+                let contents = [];
+                for (let i = 0; i < target_components.length; i++) {
+		    let component = target_components[i];
+                    if (component['bootstrap']) {
+                        continue;
+		    }
+                    let buildsForComponent = componentToArches[component['name']];
+                    if (buildsForComponent.indexOf(architecture) == -1) {
+			continue;
+		    }
+                    let binaryName = component['name'] + '/' + architecture;
+                    let componentRef = {'name': binaryName};
+                    if (targetComponentType == 'runtime') {
+                        componentRef['trees'] = ['/runtime'];
+                    } else {
+                        componentRef['trees'] = ['/runtime', '/devel', '/doc']
+		    }
+                    contents.push(componentRef);
+		}
+                target['contents'] = contents;
+	    }
+	}
+
+        for (let i = 0; i < targets_list.length; i++) {
+	    let target = targets_list[i];
+            log(Format.vprintf("Composing %s from %d components", [target['name'], target['contents'].length]));
+            this._composeOneTarget(target, componentBuildRevs, cancellable);
+	}
+    }
+});
+
+
+var app = new Build();
+GLib.idle_add(GLib.PRIORITY_DEFAULT,
+	      function() { try { app.execute(ARGV); } finally { loop.quit(); }; return false; });
+loop.run();
+
diff --git a/src/ostbuild/js/buildutil.js b/src/ostbuild/js/buildutil.js
index 0d8fe0e..d9273a6 100644
--- a/src/ostbuild/js/buildutil.js
+++ b/src/ostbuild/js/buildutil.js
@@ -82,3 +82,44 @@ function resolveComponent(manifest, componentMeta) {
 
     return result;
 }
+
+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'];
+}
diff --git a/src/ostbuild/js/checkout.js b/src/ostbuild/js/checkout.js
index e192f8d..f956f62 100644
--- a/src/ostbuild/js/checkout.js
+++ b/src/ostbuild/js/checkout.js
@@ -48,13 +48,13 @@ const Checkout = new Lang.Class({
 	this._snapshotDir = this.workdir.get_child('snapshots');
 
 	this._srcDb = new JsonDB.JsonDB(this._snapshotDir, this.prefix + '-src-snapshot');
-	this._snapshot = Snapshot.load(this._srcDb, this.prefix, args.snapshot, cancellable);
+	[this._snapshot, this._snapshotPath] = Snapshot.load(this._srcDb, this.prefix, args.snapshot, cancellable);
 
         let componentName = args.component;
 
 	let component;
         if (args.metadata_path != null) {
-	    component = JsonUtil.load(Gio.File.new_for_path(args.metadata_path));
+	    component = JsonUtil.loadJson(Gio.File.new_for_path(args.metadata_path), cancellable);
         } else {
             component = Snapshot.getExpanded(this._snapshot, componentName);
 	}
@@ -99,7 +99,7 @@ const Checkout = new Lang.Class({
 
         if (component['patches']) {
             if (args.patches_path == null) {
-                patchdir = Vcs.checkoutPatches(this.mirrordir, this.patchdir, component);
+                patchdir = Vcs.checkoutPatches(this.mirrordir, this.patchdir, component, cancellable);
             } else {
                 patchdir = args.patches_path
 	    }
@@ -118,7 +118,9 @@ const Checkout = new Lang.Class({
     }
 });
 
+let ecode = 1;
 var checkout = new Checkout();
 GLib.idle_add(GLib.PRIORITY_DEFAULT,
-	      function() { try { checkout.execute(ARGV); } finally { loop.quit(); }; return false; });
+	      function() { try { checkout.execute(ARGV); ecode = 0; } finally { loop.quit(); }; return false; });
 loop.run();
+ecode;
diff --git a/src/ostbuild/js/git_mirror.js b/src/ostbuild/js/git_mirror.js
index 664ce6a..8583e15 100644
--- a/src/ostbuild/js/git_mirror.js
+++ b/src/ostbuild/js/git_mirror.js
@@ -69,7 +69,7 @@ const GitMirror = new Lang.Class({
             this._snapshot['components'] = resolvedComponents;
             this._snapshot['patches'] = BuildUtil.resolveComponent(this._snapshot, this._snapshot['patches']);
         } else {
-	    this._snapshot = Snapshot.load(this._srcDb, this.prefix, args.snapshot, cancellable);
+	    [this._snapshot, this._snapshotPath] = Snapshot.load(this._srcDb, this.prefix, args.snapshot, cancellable);
 	}
 
 	let componentNames;
diff --git a/src/ostbuild/js/jsondb.js b/src/ostbuild/js/jsondb.js
index 41fd943..856aa6e 100644
--- a/src/ostbuild/js/jsondb.js
+++ b/src/ostbuild/js/jsondb.js
@@ -94,7 +94,7 @@ const JsonDB = new Lang.Class({
 	
 	let currentTime = GLib.DateTime.new_now_utc();
 
-	let buf = JSON.stringify(obj);
+	let buf = JSON.stringify(obj, null, "  ");
 	let csum = GLib.compute_checksum_for_string(GLib.ChecksumType.SHA256, buf, -1);
         
 	let latestVersion;
diff --git a/src/ostbuild/js/procutil.js b/src/ostbuild/js/procutil.js
index a42b654..6fcfdf2 100644
--- a/src/ostbuild/js/procutil.js
+++ b/src/ostbuild/js/procutil.js
@@ -3,13 +3,29 @@ 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 _setContextFromParams(context, params) {
-    params = Params.parse(params, {cwd: null});
+    params = Params.parse(params, {cwd: null,
+				   env: null,
+				   stderr: null });
     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)
+	context.set_stderr_disposition(params.stderr);
 }
 
 function _wait_sync_check_internal(proc, cancellable) {
@@ -42,19 +58,21 @@ function _runSyncGetOutputInternal(args, cancellable, params, splitLines) {
     proc.init(cancellable);
     let input = proc.get_stdout_pipe();
     let dataIn = Gio.DataInputStream.new(input);
-    let resultLines = [];
-    let resultBuf = '';
-    while (true) {
-	let [line, len] = dataIn.read_line_utf8(cancellable);
-	if (line == null)
-	    break;
-	if (splitLines)
-	    resultLines.push(line);
-	else
-	    resultBuf += line;
+
+    let result;
+    if (splitLines) {
+	result = StreamUtil.dataInputStreamReadLines(dataIn, cancellable);
+    } else {
+	result = '';
+	while (true) {
+	    let [line, len] = dataIn.read_line_utf8(cancellable);
+	    if (line == null)
+		break;
+	    result += line;
+	}
     }
     _wait_sync_check_internal(proc, cancellable);
-    return splitLines ? resultLines : resultBuf;
+    return result;
 }
 
 function runSyncGetOutputLines(args, cancellable, params) {
@@ -65,6 +83,21 @@ function runSyncGetOutputUTF8(args, cancellable, params) {
     return _runSyncGetOutputInternal(args, cancellable, params, false);
 }
 
+function runSyncGetOutputUTF8Stripped(args, cancellable, params) {
+    return _runSyncGetOutputInternal(args, cancellable, params, false).replace(/[ \n]+$/, '');
+}
+
+function runSyncGetOutputUTF8StrippedOrNull(args, cancellable, params) {
+    try {
+	params.stderr = Gio.SubprocessStreamDisposition.NULL;
+	return runSyncGetOutputUTF8Stripped(args, cancellable, params);
+    } catch (e) {
+	if (e.domain == GLib.spawn_exit_error_quark())
+	    return null;
+	throw e;
+    }
+}
+
 function asyncWaitCheckFinish(process, result) {
     let [waitSuccess, estatus] = process.wait_finish(result);
     let success = false;
diff --git a/src/ostbuild/js/resolve.js b/src/ostbuild/js/resolve.js
index 48c547e..025d1a6 100644
--- a/src/ostbuild/js/resolve.js
+++ b/src/ostbuild/js/resolve.js
@@ -49,8 +49,6 @@ const Resolve = new Lang.Class({
 					help:"Also perform a git fetch"});
         parser.addArgument('--fetch-keep-going', {action:'storeTrue',
 						  help:"Don't exit on fetch failures"});
-        parser.addArgument('--stamp-file',
-                           {help: "If manifest changes, create this file"});
         parser.addArgument('components', {nargs:'*',
 					  help:"List of component names to git fetch"});
 
diff --git a/src/ostbuild/js/snapshot.js b/src/ostbuild/js/snapshot.js
index 0038c8a..b9dd294 100644
--- a/src/ostbuild/js/snapshot.js
+++ b/src/ostbuild/js/snapshot.js
@@ -60,9 +60,11 @@ function snapshotDiff(a, b) {
 
 function load(db, prefix, pathName, cancellable) {
     if (pathName) {
-	return db.loadFromPath(Gio.File.new_for_path(pathName), cancellable);
+	let path = Gio.File.new_for_path(pathName);
+	return [db.loadFromPath(Gio.File.new_for_path(pathName), cancellable), path];
     } else if (prefix) {
-	return db.loadFromPath(db.getLatestPath(), cancellable);
+	let path = db.getLatestPath();
+	return [db.loadFromPath(path, cancellable), path];
     } else {
 	throw new Error("No prefix or snapshot specified");
     }
@@ -79,8 +81,8 @@ function getComponent(snapshot, name, allowNone) {
 function expandComponent(snapshot, component) {
     let r = {};
     Lang.copyProperties(component, r);
-    let patchMeta = getComponent(snapshot, 'patches', true);
-    if (patchMeta != null) {
+    let patchMeta = snapshot['patches'];
+    if (patchMeta) {
 	let componentPatchFiles = component['patches'] || [];
 	if (componentPatchFiles.length > 0) {
 	    let patches = {};
@@ -89,7 +91,7 @@ function expandComponent(snapshot, component) {
 	    r['patches'] = patches;
 	}
     }
-    let configOpts = new Array(snapshot['config-opts'] || []);
+    let configOpts = (snapshot['config-opts'] || []).concat();
     configOpts.push.apply(configOpts, component['config-opts'] || []);
     r['config-opts'] = configOpts;
     return r;
diff --git a/src/ostbuild/js/streamutil.js b/src/ostbuild/js/streamutil.js
new file mode 100644
index 0000000..b028ea9
--- /dev/null
+++ b/src/ostbuild/js/streamutil.js
@@ -0,0 +1,10 @@
+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;
+}
diff --git a/src/ostbuild/js/vcs.js b/src/ostbuild/js/vcs.js
index ab60044..fb8305b 100644
--- a/src/ostbuild/js/vcs.js
+++ b/src/ostbuild/js/vcs.js
@@ -69,7 +69,7 @@ function getVcsCheckout(mirrordir, keytype, uri, dest, branch, cancellable, para
         GSystem.file_unlink(dest, cancellable);
     } else if (ftype == Gio.FileType.DIRECTORY) {
         if (params.overwrite) {
-	    GSystem.shutil_rm_rf(dest);
+	    GSystem.shutil_rm_rf(dest, cancellable);
         } else {
             tmpDest = dest;
 	}
@@ -113,11 +113,11 @@ function parseSrcKey(srckey) {
 }
     
 function checkoutPatches(mirrordir, patchdir, component, cancellable, params) {
-    params = Params.parse(params, { patches_path: null });
+    params = Params.parse(params, { patchesPath: null });
     let patches = component['patches'];
     let patches_keytype = null;
     let patches_uri = null;
-    if (params.patches_path != null) {
+    if (params.patchesPath != null) {
         patches_keytype = local;
 	patches_uri = patches_path;
         patchdir = patches_uri;
diff --git a/src/ostbuild/ostbuild-js.in b/src/ostbuild/ostbuild-js.in
index 31c6bbc..7b43a92 100755
--- a/src/ostbuild/ostbuild-js.in
+++ b/src/ostbuild/ostbuild-js.in
@@ -8,5 +8,6 @@ export GI_TYPELIB_PATH="@pkglibdir@/girepository-1.0${GI_TYPELIB_PATH:+:$GI_TYPE
 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_LIBDIR= pkglibdir@
 
 exec gjs -I "${jsdir}" "${jsdir}/${arg}.js" "$@"
diff --git a/src/ostbuild/pyostbuild/main.py b/src/ostbuild/pyostbuild/main.py
index 708e3dd..30aa989 100755
--- a/src/ostbuild/pyostbuild/main.py
+++ b/src/ostbuild/pyostbuild/main.py
@@ -22,14 +22,13 @@ import sys
 import argparse
 
 from . import builtins
-from . import builtin_build
 
 JS_BUILTINS = {'autobuilder': "Run resolve and build",
                'checkout': "Check out source tree",
                'prefix': "Display or modify \"prefix\" (build target)",
                'git-mirror': "Update internal git mirror for one or more components",
                'resolve': "Expand git revisions in source to exact targets",
-               'qemu-pull-deploy': "Copy from local repository into qemu disk and deploy"};
+               'build': "Build multiple components and generate trees"};
 
 def usage(ecode):
     print "Builtins:"



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