[ostree: 4/4] gnomeos: We can now build gobject-introspection
- From: Colin Walters <walters src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [ostree: 4/4] gnomeos: We can now build gobject-introspection
- Date: Wed, 4 Jan 2012 00:10:56 +0000 (UTC)
commit 5b0084994ec60a62dbc905632d037943d5379f1f
Author: Colin Walters <walters verbum org>
Date: Tue Jan 3 19:09:12 2012 -0500
gnomeos: We can now build gobject-introspection
Makefile-ostbuild.am | 20 +-
gnomeos/3.4/glib.txt | 1 +
gnomeos/3.4/gobject-introspection.txt | 1 +
gnomeos/3.4/gtk-doc-stub.txt | 2 +
gnomeos/3.4/libarchive.txt | 2 +
gnomeos/3.4/libxslt.txt | 2 +
gnomeos/3.4/manifest.json | 11 +
gnomeos/README | 56 +++++
gnomeos/README-testing-multiroot.md | 104 --------
gnomeos/yocto/commit-yocto-build.sh | 13 +-
src/ostbuild/ostbuild-fetch | 8 -
src/ostbuild/pyostbuild/buildutil.py | 38 +++
.../pyostbuild/builtin_autodiscover_meta.py | 5 +-
src/ostbuild/pyostbuild/builtin_build.py | 262 ++++++++++++++++++++
.../pyostbuild/builtin_chroot_compile_one.py | 28 +-
.../pyostbuild/builtin_commit_artifacts.py | 19 +-
src/ostbuild/pyostbuild/builtin_compile_one.py | 210 ++++++----------
src/ostbuild/pyostbuild/builtins.py | 12 +
src/ostbuild/pyostbuild/kvfile.py | 23 ++
src/ostbuild/pyostbuild/main.py | 1 +
src/ostbuild/pyostbuild/ostbuildrc.py | 41 +++
src/ostbuild/pyostbuild/subprocess_helpers.py | 53 ++++-
22 files changed, 614 insertions(+), 298 deletions(-)
---
diff --git a/Makefile-ostbuild.am b/Makefile-ostbuild.am
index f32a834..42074e2 100644
--- a/Makefile-ostbuild.am
+++ b/Makefile-ostbuild.am
@@ -19,21 +19,21 @@ ostbuild: src/ostbuild/ostbuild.in Makefile
sed -e s,@libdir\@,$(libdir), -e s,@datarootdir\@,$(datarootdir), -e s,@PYTHON\@,$(PYTHON), $< > $ tmp && mv $ tmp $@
bin_SCRIPTS += ostbuild
-bin_SCRIPTS += \
- src/ostbuild/ostbuild-nice-and-log-output \
- $(NULL)
-
pyostbuilddir=$(libdir)/ostbuild/pyostbuild
pyostbuild_PYTHON = \
- src/ostbuild/pyostbuild/__init__.py \
- src/ostbuild/pyostbuild/builtins.py \
- src/ostbuild/pyostbuild/main.py \
- src/ostbuild/pyostbuild/ostbuildlog.py \
- src/ostbuild/pyostbuild/subprocess_helpers.py \
+ src/ostbuild/pyostbuild/buildutil.py \
src/ostbuild/pyostbuild/builtin_autodiscover_meta.py \
+ src/ostbuild/pyostbuild/builtin_build.py \
src/ostbuild/pyostbuild/builtin_chroot_compile_one.py \
src/ostbuild/pyostbuild/builtin_commit_artifacts.py \
src/ostbuild/pyostbuild/builtin_compile_one.py \
+ src/ostbuild/pyostbuild/builtins.py \
+ src/ostbuild/pyostbuild/__init__.py \
+ src/ostbuild/pyostbuild/kvfile.py \
+ src/ostbuild/pyostbuild/main.py \
+ src/ostbuild/pyostbuild/ostbuildlog.py \
+ src/ostbuild/pyostbuild/ostbuildrc.py \
+ src/ostbuild/pyostbuild/subprocess_helpers.py \
$(NULL)
bin_PROGRAMS += src/ostbuild/ostbuild-user-chroot
@@ -41,3 +41,5 @@ bin_PROGRAMS += src/ostbuild/ostbuild-user-chroot
ostbuild_user_chroot_SOURCES = src/ostbuild/ostbuild-user-chroot.c
ostbuild_user_chroot_CFLAGS = $(AM_CFLAGS)
+bin_SCRIPTS += src/ostbuild/ostbuild-nice-and-log-output
+
diff --git a/gnomeos/3.4/glib.txt b/gnomeos/3.4/glib.txt
new file mode 100644
index 0000000..ddb33f6
--- /dev/null
+++ b/gnomeos/3.4/glib.txt
@@ -0,0 +1 @@
+SRC=git:git://git.gnome.org/glib
diff --git a/gnomeos/3.4/gobject-introspection.txt b/gnomeos/3.4/gobject-introspection.txt
new file mode 100644
index 0000000..142d75f
--- /dev/null
+++ b/gnomeos/3.4/gobject-introspection.txt
@@ -0,0 +1 @@
+SRC=git:git://git.gnome.org/gobject-introspection
diff --git a/gnomeos/3.4/gtk-doc-stub.txt b/gnomeos/3.4/gtk-doc-stub.txt
new file mode 100644
index 0000000..75dd7a4
--- /dev/null
+++ b/gnomeos/3.4/gtk-doc-stub.txt
@@ -0,0 +1,2 @@
+SRC=git:git://git.gnome.org/gtk-doc-stub
+COMPONENT=devel
diff --git a/gnomeos/3.4/libarchive.txt b/gnomeos/3.4/libarchive.txt
new file mode 100644
index 0000000..99a349c
--- /dev/null
+++ b/gnomeos/3.4/libarchive.txt
@@ -0,0 +1,2 @@
+SRC=svn:http://libarchive.googlecode.com/svn/trunk/libarchive-read-only
+CONFIGURE_OPTS=--disable-bsdtar --disable-bsdcpio
diff --git a/gnomeos/3.4/libxslt.txt b/gnomeos/3.4/libxslt.txt
new file mode 100644
index 0000000..a060dfa
--- /dev/null
+++ b/gnomeos/3.4/libxslt.txt
@@ -0,0 +1,2 @@
+SRC=git:git://git.gnome.org/libxslt
+EXTRA_OECONF = "--disable-static"
diff --git a/gnomeos/3.4/manifest.json b/gnomeos/3.4/manifest.json
new file mode 100644
index 0000000..9f23be1
--- /dev/null
+++ b/gnomeos/3.4/manifest.json
@@ -0,0 +1,11 @@
+{
+ "name": "gnomeos-3.4",
+ "architectures": ["i686"],
+ "base": "yocto/gnomeos-3.4",
+
+ "components": [
+ "gtk-doc-stub",
+ "gobject-introspection",
+ "glib"
+ ]
+}
diff --git a/gnomeos/README b/gnomeos/README
new file mode 100644
index 0000000..b024322
--- /dev/null
+++ b/gnomeos/README
@@ -0,0 +1,56 @@
+Overview
+--------
+
+The build process is divided into two levels:
+
+1) Yocto
+2) ostbuild
+
+Yocto is used as a reliable, well-maintained bootstrapping tool. It
+provides the basic filesystem layout as well as binaries for core
+build utilities like gcc and bash. This gets us out of circular
+dependency problems.
+
+At the end, the Yocto build process generates two tarballs: one for a
+base "runtime", and one "devel" with all of the development tools like
+gcc. We then import that into an OSTree branch
+e.g. "bases/gnomeos-3.4-yocto-i686-devel".
+
+Now we also assume that you have ostree installed on the host build
+system via e.g. jhbuild or RPM if doing a cross build. The core
+ostbuild tool can then chroot into a checkout of the Yocto base, and
+start generating artifacts.
+
+Each generated artifact is committed to an OSTree branch like
+"artifacts/gnomeos-3.4-i686-devel/libxslt/master/runtime". Then, a
+"compose" process merges together the individual filesystem trees into
+the final branches (e.g. gnomeos-3.4-i686-devel), and the process
+repeats.
+
+ostbuild details
+-------------------
+
+The simple goal of ostbuild is that it only takes as input a
+"manifest" which is basically just a list of components to build. A
+component is a pure metadata file which includes the git repository
+URL and branch name, as well as ./configure flags (--enable-foo).
+
+There is no support for building from "tarballs" - I want the ability
+to review all of the code that goes in, and to efficiently store
+source code updates.
+
+For GNOME, tarballs are mostly pointless - it's easy enough to just
+run autogen.sh. However there are two challenges:
+
+1) Tarballs for modules which self-build-depend may include
+ pre-generated files. For example - flex's tarball includes a
+ generated .c file for the parser. For these, we can either move
+ the module build to the Yocto level (thus giving a convenient way
+ to pull in host files), or possibly add the ability to
+ hardlink/copy in host binaries to ostbuild.
+
+2) Tarballs which include translations pulled from a different
+ location. For example - bison. For these, we basically have to
+ maintain our own git repositories.
+
+
diff --git a/gnomeos/yocto/commit-yocto-build.sh b/gnomeos/yocto/commit-yocto-build.sh
index f622d36..12a9702 100755
--- a/gnomeos/yocto/commit-yocto-build.sh
+++ b/gnomeos/yocto/commit-yocto-build.sh
@@ -26,16 +26,21 @@ BRANCH=$1
test -n "$BRANCH" || usage
shift
-ARCH=x86
+YOCTO_ARCH=x86
+MACHINE=i686
+
+BUILDROOT="gnomeos-3.4-${MACHINE}-${BRANCH}"
+BASE="bases/yocto/${BUILDROOT}"
OSTREE_VER=$(cd $SCRIPT_SRCDIR && git describe)
BUILDDIR=$WORKDIR/tmp-eglibc
OSTREE_REPO=$WORKDIR/repo
-BUILD_TAR=$BUILDDIR/deploy/images/gnomeos-contents-$BRANCH-qemu${ARCH}.tar.gz
+BUILD_TAR=$BUILDDIR/deploy/images/gnomeos-contents-$BRANCH-qemu${YOCTO_ARCH}.tar.gz
BUILD_TIME=$(date -r $BUILD_TAR)
-ostree --repo=${OSTREE_REPO} commit --skip-if-unchanged -s "Build from OSTree ${OSTREE_VER}" -b "gnomeos-yocto-$ARCH-$BRANCH" --tree=tar=${BUILD_TAR}
-ostree --repo=${OSTREE_REPO} diff "gnomeos-yocto-$ARCH-$BRANCH"^ "gnomeos-yocto-$ARCH-$BRANCH"
+ostree --repo=${OSTREE_REPO} commit --skip-if-unchanged -s "Build from OSTree ${OSTREE_VER}" -b "${BASE}" --tree=tar=${BUILD_TAR}
+ostree --repo=${OSTREE_REPO} diff "${BASE}"^ "${BASE}" || true
+cp ${OSTREE_REPO}/refs/heads/${BASE} ${OSTREE_REPO}/refs/heads/${BUILDROOT}
diff --git a/src/ostbuild/pyostbuild/buildutil.py b/src/ostbuild/pyostbuild/buildutil.py
new file mode 100755
index 0000000..26bea32
--- /dev/null
+++ b/src/ostbuild/pyostbuild/buildutil.py
@@ -0,0 +1,38 @@
+# 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.
+
+import re
+
+ARTIFACT_RE = re.compile(r'^artifact-([^,]+),([^,]+),([^,]+),([^,]+),(.+)-((?:runtime)|(?:devel))\.tar\.gz$')
+
+def parse_artifact_name(artifact_basename):
+ match = ARTIFACT_RE.match(artifact_basename)
+ if match is None:
+ raise ValueError("Invalid artifact basename %s" % (artifact_basename))
+ return {'buildroot': match.group(1),
+ 'buildroot_version': match.group(2),
+ 'name': match.group(3),
+ 'branch': match.group(4),
+ 'version': match.group(5),
+ 'type': match.group(6)}
+
+def branch_name_for_artifact(a):
+ return 'artifacts/%s/%s/%s/%s' % (a['buildroot'],
+ a['name'],
+ a['branch'],
+ a['type'])
+
diff --git a/src/ostbuild/pyostbuild/builtin_autodiscover_meta.py b/src/ostbuild/pyostbuild/builtin_autodiscover_meta.py
index 6808024..3d7ec8f 100755
--- a/src/ostbuild/pyostbuild/builtin_autodiscover_meta.py
+++ b/src/ostbuild/pyostbuild/builtin_autodiscover_meta.py
@@ -72,10 +72,7 @@ class OstbuildAutodiscoverMeta(builtins.Builtin):
def _discover_version_from_git(self):
if os.path.isdir('.git'):
- try:
- version = subprocess.check_output(['git', 'describe'])
- except subprocess.CalledProcessError, e:
- version = subprocess.check_output(['git', 'rev-parse', 'HEAD'])
+ version = subprocess.check_output(['git', 'describe', '--long', '--abbrev=42', '--always'])
return version.strip()
return None
diff --git a/src/ostbuild/pyostbuild/builtin_build.py b/src/ostbuild/pyostbuild/builtin_build.py
new file mode 100755
index 0000000..fa13f83
--- /dev/null
+++ b/src/ostbuild/pyostbuild/builtin_build.py
@@ -0,0 +1,262 @@
+# 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.
+
+import os,sys,subprocess,tempfile,re,shutil
+import argparse
+import json
+
+from . import builtins
+from .ostbuildlog import log, fatal
+from .subprocess_helpers import run_sync, run_sync_get_output
+from . import ostbuildrc
+from . import buildutil
+from . import kvfile
+
+class BuildOptions(object):
+ pass
+
+class OstbuildBuild(builtins.Builtin):
+ name = "build"
+ short_description = "Rebuild all artifacts from the given manifest"
+
+ def __init__(self):
+ builtins.Builtin.__init__(self)
+
+ def _ensure_vcs_mirror(self, name, keytype, uri, branch):
+ assert keytype == 'git'
+ mirror = os.path.join(self.srcdir, name)
+ tmp_mirror = mirror + '.tmp'
+ if os.path.isdir(tmp_mirror):
+ shutil.rmtree(tmp_mirror)
+ if not os.path.isdir(mirror):
+ run_sync(['git', 'clone', '--mirror', uri, tmp_mirror])
+ os.rename(tmp_mirror, mirror)
+ return mirror
+
+ def _get_vcs_checkout(self, name, keytype, mirrordir, branch):
+ checkoutdir = os.path.join(self.srcdir, '_checkouts')
+ if not os.path.isdir(checkoutdir):
+ os.makedirs(checkoutdir)
+ dest = os.path.join(checkoutdir, name)
+ tmp_dest = dest + '.tmp'
+ if os.path.isdir(dest):
+ shutil.rmtree(dest)
+ if os.path.isdir(tmp_dest):
+ shutil.rmtree(tmp_dest)
+ subprocess.check_call(['git', 'clone', '--depth=1', '-q', mirrordir, tmp_dest])
+ subprocess.check_call(['git', 'checkout', '-q', branch], cwd=tmp_dest)
+ subprocess.check_call(['git', 'submodule', 'update', '--init'], cwd=tmp_dest)
+ os.rename(tmp_dest, dest)
+ return dest
+
+ def _get_vcs_version_from_checkout(self, name):
+ vcsdir = os.path.join(self.srcdir, name)
+ return subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=vcsdir)
+
+ def _parse_src_key(self, srckey):
+ idx = srckey.find(':')
+ if idx < 0:
+ raise ValueError("Invalid SRC uri=%s" % (srckey, ))
+ keytype = srckey[:idx]
+ if keytype not in ('git'):
+ raise ValueError("Unsupported SRC uri=%s" % (srckey, ))
+ uri = srckey[idx+1:]
+ idx = uri.rfind('#')
+ if idx < 0:
+ branch = "master"
+ else:
+ branch = uri[idx+1:]
+ uri = uri[0:idx]
+ return (keytype, uri, branch)
+
+ def _parse_artifact_vcs_version(self, ver):
+ idx = ver.rfind('-')
+ if idx > 0:
+ vcs_ver = ver[idx+1:]
+ else:
+ vcs_ver = ver
+ if not vcs_ver.startswith('g'):
+ raise ValueError("Invalid artifact version '%s'" % (ver, ))
+ return vcs_ver[1:]
+
+ def _get_ostbuild_chroot_args(self, architecture):
+ current_machine = os.uname()[4]
+ if current_machine != architecture:
+ args = ['setarch', architecture]
+ else:
+ args = []
+ args.extend(['ostbuild', 'chroot-compile-one',
+ '--repo=' + self.repo])
+ return args
+
+ def _launch_debug_shell(self, architecture, buildroot, cwd=None):
+ args = self._get_ostbuild_chroot_args(architecture)
+ args.extend(['--buildroot=' + buildroot,
+ '--workdir=' + self.workdir,
+ '--debug-shell'])
+ run_sync(args, cwd=cwd, fatal_on_error=False, keep_stdin=True)
+ fatal("Exiting after debug shell")
+
+ def _build_one_component(self, name, architecture, meta):
+ (keytype, uri, branch) = self._parse_src_key(meta['SRC'])
+ component_vcs_mirror = self._ensure_vcs_mirror(name, keytype, uri, branch)
+ component_src = self._get_vcs_checkout(name, keytype, component_vcs_mirror, branch)
+ buildroot = '%s-%s-devel' % (self.manifest['name'], architecture)
+ branchname = 'artifacts/%s/%s/%s' % (buildroot, name, branch)
+ current_buildroot_version = run_sync_get_output(['ostree', '--repo=' + self.repo,
+ 'rev-parse', buildroot])
+ current_buildroot_version = current_buildroot_version.strip()
+ previous_commit_version = run_sync_get_output(['ostree', '--repo=' + self.repo,
+ 'rev-parse', branchname],
+ stderr=open('/dev/null', 'w'),
+ none_on_error=True)
+ if previous_commit_version is not None:
+ log("Previous build of '%s' is %s" % (branchname, previous_commit_version))
+ previous_artifact_version = run_sync_get_output(['ostree', '--repo=' + self.repo,
+ 'show', '--print-metadata-key=ostbuild-artifact-version', previous_commit_version])
+ previous_artifact_version = previous_artifact_version.strip()
+ previous_buildroot_version = run_sync_get_output(['ostree', '--repo=' + self.repo,
+ 'show', '--print-metadata-key=ostbuild-buildroot-version', previous_commit_version])
+ previous_buildroot_version = previous_buildroot_version.strip()
+
+ previous_vcs_version = self._parse_artifact_vcs_version(previous_artifact_version)
+ current_vcs_version = self._get_vcs_version_from_checkout(name)
+ vcs_version_matches = False
+ if previous_vcs_version == current_vcs_version:
+ vcs_version_matches = True
+ log("VCS version is unchanged from '%s'" % (previous_vcs_version, ))
+ else:
+ log("VCS version is now '%s', was '%s'" % (current_vcs_version, previous_vcs_version))
+ buildroot_version_matches = False
+ if vcs_version_matches:
+ buildroot_version_matches = (current_buildroot_version == previous_buildroot_version)
+ if buildroot_version_matches:
+ log("Already have build '%s' of src commit '%s' for '%s' in buildroot '%s'" % (previous_commit_version, previous_vcs_version, branchname, buildroot))
+ return
+ else:
+ log("Buildroot is now '%s'" % (current_buildroot_version, ))
+ else:
+ log("No previous build for '%s' found" % (branchname, ))
+
+ component_resultdir = os.path.join(self.workdir, name, 'results')
+ if os.path.isdir(component_resultdir):
+ shutil.rmtree(component_resultdir)
+ os.makedirs(component_resultdir)
+
+ chroot_args = self._get_ostbuild_chroot_args(architecture)
+ chroot_args.extend(['--buildroot=' + buildroot,
+ '--workdir=' + self.workdir,
+ '--resultdir=' + component_resultdir])
+ if self.buildopts.shell_on_failure:
+ ecode = run_sync(chroot_args, cwd=component_src, fatal_on_error=False)
+ if ecode != 0:
+ self._launch_debug_shell(architecture, buildroot, cwd=component_src)
+ else:
+ run_sync(chroot_args, cwd=component_src, fatal_on_error=True)
+ artifact_files = []
+ for name in os.listdir(component_resultdir):
+ if name.startswith('artifact-'):
+ log("Generated artifact file: %s" % (name, ))
+ artifact_files.append(os.path.join(component_resultdir, name))
+ assert len(artifact_files) >= 1 and len(artifact_files) <= 2
+ run_sync(['ostbuild', 'commit-artifacts',
+ '--repo=' + self.repo] + artifact_files)
+ artifacts = []
+ for filename in artifact_files:
+ parsed = buildutil.parse_artifact_name(os.path.basename(filename))
+ artifacts.append(parsed)
+ def _sort_artifact(a, b):
+ if a['type'] == b['type']:
+ return 0
+ elif a['type'] == 'runtime':
+ return -1
+ return 1
+ artifacts.sort(_sort_artifact)
+ return artifacts
+
+ def _compose(self, suffix, artifacts):
+ compose_contents = ['bases/' + self.manifest['base'] + '-' + suffix]
+ compose_contents.extend(artifacts)
+ child_args = ['ostree', '--repo=' + self.repo, 'compose',
+ '-b', self.manifest['name'] + '-' + suffix, '-s', 'Compose']
+ child_args.extend(compose_contents)
+ run_sync(child_args)
+
+ def execute(self, argv):
+ parser = argparse.ArgumentParser(description=self.short_description)
+ parser.add_argument('--manifest', required=True)
+ parser.add_argument('--start-at')
+ parser.add_argument('--shell-on-failure', action='store_true')
+ parser.add_argument('--debug-shell', action='store_true')
+
+ args = parser.parse_args(argv)
+
+ self.parse_config()
+
+ self.buildopts = BuildOptions()
+ self.buildopts.shell_on_failure = args.shell_on_failure
+
+ self.manifest = json.load(open(args.manifest))
+
+ if args.debug_shell:
+ debug_shell_arch = self.manifest['architectures'][0]
+ debug_shell_buildroot = '%s-%s-devel' % (self.manifest['name'], debug_shell_arch)
+ self._launch_debug_shell(debug_shell_arch, debug_shell_buildroot)
+
+ dirname = os.path.dirname(args.manifest)
+ components = self.manifest['components']
+ runtime_components = []
+ devel_components = []
+ runtime_artifacts = []
+ devel_artifacts = []
+ if args.start_at:
+ start_at_index = -1
+ for i,component_name in enumerate(components):
+ if component_name == args.start_at:
+ start_at_index = i
+ break
+ if start_at_index == -1:
+ fatal("Unknown component '%s' for --start-at" % (args.start_at, ))
+ else:
+ start_at_index = 0
+
+ for component_name in components[start_at_index:]:
+ for architecture in self.manifest['architectures']:
+ path = os.path.join(dirname, component_name + '.txt')
+ f = open(path)
+ component_meta = kvfile.parse(f)
+
+ artifact_branches = self._build_one_component(component_name, architecture, component_meta)
+
+ target_component = component_meta.get('COMPONENT')
+ if target_component == 'devel':
+ devel_components.append(component_name)
+ else:
+ runtime_components.append(component_name)
+ for branch in artifact_branches:
+ if branch['type'] == 'runtime':
+ runtime_artifacts.append(branch)
+ devel_artifacts.extend(artifact_branches)
+
+ f.close()
+
+ devel_branches = map(buildutil.branch_name_for_artifact, devel_artifacts)
+ self._compose(architecture + '-devel', devel_branches)
+ runtime_branches = map(buildutil.branch_name_for_artifact, runtime_artifacts)
+ self._compose(architecture + '-runtime', runtime_branches)
+
+builtins.register(OstbuildBuild)
diff --git a/src/ostbuild/pyostbuild/builtin_chroot_compile_one.py b/src/ostbuild/pyostbuild/builtin_chroot_compile_one.py
index 9414fad..37b15ac 100755
--- a/src/ostbuild/pyostbuild/builtin_chroot_compile_one.py
+++ b/src/ostbuild/pyostbuild/builtin_chroot_compile_one.py
@@ -39,13 +39,13 @@ class OstbuildChrootCompileOne(builtins.Builtin):
short_description = "Build artifacts from the current source directory in a chroot"
def execute(self, argv):
- parser = argparse.ArgumentParser(description="Build a module in a given root")
+ parser = argparse.ArgumentParser(description=self.short_description)
parser.add_argument('--workdir')
- parser.add_argument('--repo')
+ parser.add_argument('--repo', required=True)
parser.add_argument('--resultdir')
- parser.add_argument('--branch')
+ parser.add_argument('--buildroot', required=True)
parser.add_argument('--meta')
- parser.add_argument('--debug-shell', type=bool)
+ parser.add_argument('--debug-shell', action='store_true')
args = parser.parse_args(argv)
@@ -67,7 +67,7 @@ class OstbuildChrootCompileOne(builtins.Builtin):
workdir_is_tmp = (args.workdir is None)
if workdir_is_tmp:
- workdir = tempfile.mkdtemp(prefix='ostree-chroot-compile-')
+ workdir = tempfile.mkdtemp(prefix='ostbuild-chroot-compile-')
else:
workdir = args.workdir
@@ -79,10 +79,10 @@ class OstbuildChrootCompileOne(builtins.Builtin):
shutil.rmtree(child_tmpdir)
os.mkdir(child_tmpdir)
- rev = subprocess.check_output(['ostree', '--repo=' + args.repo, 'rev-parse', args.branch])
+ rev = subprocess.check_output(['ostree', '--repo=' + args.repo, 'rev-parse', args.buildroot])
rev=rev.strip()
- metadata['BUILDROOT'] = args.branch
+ metadata['BUILDROOT'] = args.buildroot
metadata['BUILDROOT_VERSION'] = rev
rootdir = os.path.join(workdir, 'root-' + rev)
@@ -130,14 +130,14 @@ class OstbuildChrootCompileOne(builtins.Builtin):
'--mount-proc', '/proc',
'--mount-bind', '/dev', '/dev',
'--mount-bind', child_tmpdir, '/tmp',
- '--mount-bind', os.getcwd(), chroot_sourcedir,
- '--mount-bind', args.resultdir, '/ostbuild/results',
- rootdir,
- '/bin/sh']
+ '--mount-bind', os.getcwd(), chroot_sourcedir]
+ if args.resultdir:
+ child_args.extend(['--mount-bind', args.resultdir, '/ostbuild/results'])
+ child_args.extend([rootdir, '/bin/sh'])
if not args.debug_shell:
- child_args += ['-c',
- 'cd "%s" && ostbuild-compile-one-impl OSTBUILD_RESULTDIR=/ostbuild/results OSTBUILD_META=_ostbuild-meta' % (chroot_sourcedir, )
- ]
+ child_args.extend(['-c',
+ 'cd "%s" && ostbuild compile-one --ostbuild-resultdir=/ostbuild/results --ostbuild-meta=_ostbuild-meta' % (chroot_sourcedir, )
+ ])
run_sync(child_args, env=BUILD_ENV)
if workdir_is_tmp:
diff --git a/src/ostbuild/pyostbuild/builtin_commit_artifacts.py b/src/ostbuild/pyostbuild/builtin_commit_artifacts.py
index af78e71..6f6f63c 100644
--- a/src/ostbuild/pyostbuild/builtin_commit_artifacts.py
+++ b/src/ostbuild/pyostbuild/builtin_commit_artifacts.py
@@ -26,13 +26,13 @@ import argparse
from . import builtins
from .ostbuildlog import log, fatal
from .subprocess_helpers import run_sync
+from . import buildutil
class OstbuildCommitArtifacts(builtins.Builtin):
name = "commit-artifacts"
short_description = "Commit artifacts to their corresponding repository branches"
def execute(self, argv):
- artifact_re = re.compile(r'^artifact-([^,]+),([^,]+),([^,]+),([^,]+),([^.]+)\.tar\.gz$')
parser = argparse.ArgumentParser(self.short_description)
parser.add_argument('--repo')
@@ -45,21 +45,14 @@ class OstbuildCommitArtifacts(builtins.Builtin):
for arg in args.artifacts:
basename = os.path.basename(arg)
- match = artifact_re.match(basename)
- if match is None:
- fatal("Invalid artifact name: %s" % (arg, ))
- buildroot = match.group(1)
- buildroot_version = match.group(2)
- name = match.group(3)
- branch = match.group(4)
- version = match.group(5)
+ parsed = buildutil.parse_artifact_name(basename)
- branch_name = 'artifacts/%s/%s/%s' % (buildroot, name, branch)
+ branch_name = buildutil.branch_name_for_artifact(parsed)
run_sync(['ostree', '--repo=' + args.repo,
- 'commit', '-b', branch_name, '-s', 'Build ' + version,
- '--add-metadata-string=ostree-buildroot-version=' + buildroot_version,
- '--add-metadata-string=ostree-artifact-version=' + version,
+ 'commit', '-b', branch_name, '-s', 'Build ' + parsed['version'],
+ '--add-metadata-string=ostbuild-buildroot-version=' + parsed['buildroot_version'],
+ '--add-metadata-string=ostbuild-artifact-version=' + parsed['version'],
'--skip-if-unchanged', '--tar-autocreate-parents', '--tree=tar=' + arg])
builtins.register(OstbuildCommitArtifacts)
diff --git a/src/ostbuild/pyostbuild/builtin_compile_one.py b/src/ostbuild/pyostbuild/builtin_compile_one.py
index edcae1c..4306706 100755
--- a/src/ostbuild/pyostbuild/builtin_compile_one.py
+++ b/src/ostbuild/pyostbuild/builtin_compile_one.py
@@ -36,43 +36,12 @@ _BLACKLIST_REGEXPS = map(re.compile,
_DEVEL_REGEXPS = map(re.compile,
[r'/usr/include/',
r'/usr/share/pkgconfig/',
+ r'/usr/share/aclocal/',
r'/(?:usr/)lib(?:|(?:32)|(?:64))/pkgconfig/.*\.pc$',
r'/(?:usr/)lib(?:|(?:32)|(?:64))/[^/]+\.so$'
+ r'/(?:usr/)lib(?:|(?:32)|(?:64))/[^/]+\.a$'
])
-class BuildSystemScanner(object):
- @classmethod
- def _find_file(cls, names):
- for name in names:
- if os.path.exists(name):
- return name
- return None
-
- @classmethod
- def get_configure_source_script(cls):
- return cls._find_file(('./configure.ac', './configure.in'))
-
- @classmethod
- def get_configure_script(cls):
- return cls._find_file(('./configure', ))
-
- @classmethod
- def get_bootstrap_script(cls):
- return cls._find_file(('./autogen.sh', ))
-
- @classmethod
- def get_silent_rules(cls):
- src = cls.get_configure_source_script()
- if not src:
- return False
- f = open(src)
- for line in f:
- if line.find('AM_SILENT_RULES') >= 0:
- f.close()
- return True
- f.close()
- return False
-
class OstbuildCompileOne(builtins.Builtin):
name = "compile-one"
short_description = "Build artifacts from the current source directory"
@@ -81,25 +50,12 @@ class OstbuildCompileOne(builtins.Builtin):
builtins.Builtin.__init__(self)
self.tempfiles = []
- def _search_file(self, filename, pattern):
- f = open(filename)
- for line in f:
- if line.startswith(pattern):
- f.close()
- return line
- f.close()
- return None
-
- def _find_buildapi_makevariable(self, name, builddir='.'):
- var = '.%s:' % (name, )
- line = None
- path = os.path.join(builddir, 'Makefile.in')
- if os.path.exists(path):
- line = self._search_file(path, var)
- path = os.path.join(builddir, 'Makefile')
- if not line and os.path.exists(path):
- line = self._search_file(path, var)
- return line is not None
+ def _has_buildapi_configure_variable(self, name):
+ var = '#buildapi-variable-%s' % (name, )
+ for line in open('configure'):
+ if line.find(var) >= 0:
+ return True
+ return False
def execute(self, args):
self.default_buildapi_jobs = ['-j', '%d' % (cpu_count() * 2, )]
@@ -134,9 +90,9 @@ class OstbuildCompileOne(builtins.Builtin):
for arg in args:
if arg.startswith('--ostbuild-resultdir='):
- self.ostbuild_resultdir=arg[len('ostbuild-resultdir='):]
- elif arg.startswith('ostbuild-meta='):
- self.ostbuild_meta=arg[len('ostbuild-meta='):]
+ self.ostbuild_resultdir=arg[len('--ostbuild-resultdir='):]
+ elif arg.startswith('--ostbuild-meta='):
+ self.ostbuild_meta=arg[len('--ostbuild-meta='):]
elif arg.startswith('--'):
self.configargs.append(arg)
else:
@@ -145,10 +101,10 @@ class OstbuildCompileOne(builtins.Builtin):
self.metadata = {}
if self.ostbuild_meta is None:
- output = subprocess.check_output(['ostbuild-autodiscover-meta'])
+ output = subprocess.check_output(['ostbuild', 'autodiscover-meta'])
ostbuild_meta_f = StringIO(output)
else:
- ostbuild_meta_f = open(ostbuild_meta)
+ ostbuild_meta_f = open(self.ostbuild_meta)
for line in ostbuild_meta_f:
(k,v) = line.split('=', 1)
@@ -160,46 +116,33 @@ class OstbuildCompileOne(builtins.Builtin):
if k not in self.metadata:
fatal('Missing required key "%s" in metadata' % (k, ))
- self.phase_bootstrap()
+ autogen_script = None
+ if not os.path.exists('configure'):
+ log("No 'configure' script found, looking for autogen/bootstrap")
+ for name in ['autogen', 'autogen.sh', 'bootstrap']:
+ if os.path.exists(name):
+ log("Using bootstrap script '%s'" % (name, ))
+ autogen_script = name
+ if autogen_script is None:
+ fatal("No configure or autogen script detected; unknown buildsystem")
- def phase_bootstrap(self):
- have_configure = BuildSystemScanner.get_configure_script()
- have_configure_source = BuildSystemScanner.get_configure_source_script()
- if not (have_configure or have_configure_source):
- fatal("No configure or bootstrap script detected; unknown buildsystem")
- return
-
- need_v1 = BuildSystemScanner.get_silent_rules()
- if need_v1:
- log("Detected AM_SILENT_RULES, adding --disable-silent-rules to configure")
- self.configargs.append('--disable-silent-rules')
-
- if have_configure:
- self.phase_configure()
+ if autogen_script is not None:
+ env = dict(os.environ)
+ env['NOCONFIGURE'] = '1'
+ run_sync(['./' + autogen_script], env=env)
else:
- bootstrap = BuildSystemScanner.get_bootstrap_script()
- if bootstrap:
- log("Detected bootstrap script: %s, using it" % (bootstrap, ))
- args = [bootstrap]
- # Add NOCONFIGURE; GNOME style scripts use this
- env = dict(os.environ)
- env['NOCONFIGURE'] = '1'
- run_sync(args, env=env)
- else:
- log("No bootstrap script found; using generic autoreconf")
- run_sync(['autoreconf', '-f', '-i'])
- self.phase_configure()
+ log("Using existing 'configure' script")
- def phase_configure(self):
use_builddir = True
- doesnot_support_builddir = self._find_buildapi_makevariable('buildapi-no-builddir')
+ doesnot_support_builddir = self._has_buildapi_configure_variable('no-builddir')
if doesnot_support_builddir:
- log("Found .buildapi-no-builddir; copying source tree to _build")
- shutil.rmtree('_build')
- os.mkdir('_build')
+ log("Found no-builddir Build API variable; copying source tree to _build")
+ if os.path.isdir('_build'):
+ shutil.rmtree('_build')
shutil.copytree('.', '_build', symlinks=True,
ignore=shutil.ignore_patterns('_build'))
use_builddir = False
+ builddir = '.'
if use_builddir:
builddir = '_build'
@@ -207,26 +150,20 @@ class OstbuildCompileOne(builtins.Builtin):
if not os.path.isdir(builddir):
os.mkdir(builddir)
- configstatus = 'config.status'
- if not os.path.exists(configstatus):
- if use_builddir:
- args = ['../configure']
- else:
- args = ['./configure']
- args.extend(self.configargs)
- if use_builddir:
- run_sync(args, cwd=builddir)
- else:
- run_sync(args)
+ if use_builddir:
+ args = ['../configure']
else:
- log("Found %s, skipping configure" % (configstatus, ))
- self.phase_build(builddir=builddir)
-
- build_status = False
-
- def phase_build(self, builddir=None):
- if not os.path.exists(os.path.join(builddir, 'Makefile')):
+ args = ['./configure']
+ args.extend(self.configargs)
+ if use_builddir:
+ run_sync(args, cwd=builddir)
+ else:
+ run_sync(args)
+
+ makefile_path = os.path.join(builddir, 'Makefile')
+ if not os.path.exists(makefile_path):
fatal("No Makefile found")
+
args = list(self.makeargs)
user_specified_jobs = False
for arg in args:
@@ -234,35 +171,18 @@ class OstbuildCompileOne(builtins.Builtin):
user_specified_jobs = True
if not user_specified_jobs:
- notparallel = self._find_buildapi_makevariable('NOTPARALLEL', builddir=builddir)
- if not notparallel:
+ has_notparallel = False
+ for line in open(makefile_path):
+ if line.startswith('.NOTPARALLEL'):
+ has_notparallel = True
+ log("Found .NOTPARALLEL")
+
+ if not has_notparallel:
log("Didn't find NOTPARALLEL, using parallel make by default")
args.extend(self.default_buildapi_jobs)
run_sync(args, cwd=builddir)
-
- self.phase_make_artifacts(builddir=builddir)
-
- def make_artifact(self, name, from_files, tempdir=None, resultdir=None):
- targz_name = name + '.tar.gz'
- (fd,filelist_temp)=tempfile.mkstemp(prefix='ostree-filelist-%s' % (name, ))
- os.close(fd)
- self.tempfiles.append(filelist_temp)
- f = open(filelist_temp, 'w')
- for filename in from_files:
- assert ('\n' not in filename)
- f.write(filename)
- f.write('\n')
- f.close()
- if resultdir:
- result_path = os.path.join(resultdir, targz_name)
- else:
- result_path = targz_name
- args = ['tar', '-c', '-z', '-C', tempdir, '-f', result_path, '-T', filelist_temp]
- run_sync(args)
- log("created: %s" % (os.path.abspath (result_path), ))
-
- def phase_make_artifacts(self, builddir=None):
+
name = self.metadata['NAME']
assert ',' not in name
branch = self.metadata['BRANCH']
@@ -280,7 +200,7 @@ class OstbuildCompileOne(builtins.Builtin):
artifact_prefix='artifact-%s,%s,%s,%s,%s' % (root_name, root_version, name, branch, version)
- tempdir = tempfile.mkdtemp(prefix='ostree-build-%s-' % (name,))
+ tempdir = tempfile.mkdtemp(prefix='ostbuild-%s-' % (name,))
self.tempfiles.append(tempdir)
args = ['make', 'install', 'DESTDIR=' + tempdir]
run_sync(args, cwd=builddir)
@@ -318,9 +238,6 @@ class OstbuildCompileOne(builtins.Builtin):
self.make_artifact(artifact_prefix + '-devel', devel_files, tempdir=tempdir, resultdir=self.ostbuild_resultdir)
self.make_artifact(artifact_prefix + '-runtime', runtime_files, tempdir=tempdir, resultdir=self.ostbuild_resultdir)
- self.phase_complete()
-
- def phase_complete(self):
for tmpname in self.tempfiles:
assert os.path.isabs(tmpname)
if os.path.isdir(tmpname):
@@ -331,5 +248,24 @@ class OstbuildCompileOne(builtins.Builtin):
pass
except OSError, e:
pass
-
+
+ def make_artifact(self, name, from_files, tempdir=None, resultdir=None):
+ targz_name = name + '.tar.gz'
+ (fd,filelist_temp)=tempfile.mkstemp(prefix='ostbuild-filelist-%s' % (name, ))
+ os.close(fd)
+ self.tempfiles.append(filelist_temp)
+ f = open(filelist_temp, 'w')
+ for filename in from_files:
+ assert ('\n' not in filename)
+ f.write(filename)
+ f.write('\n')
+ f.close()
+ if resultdir:
+ result_path = os.path.join(resultdir, targz_name)
+ else:
+ result_path = targz_name
+ args = ['tar', '-c', '-z', '-C', tempdir, '-f', result_path, '-T', filelist_temp]
+ run_sync(args)
+ log("created: %s" % (os.path.abspath (result_path), ))
+
builtins.register(OstbuildCompileOne)
diff --git a/src/ostbuild/pyostbuild/builtins.py b/src/ostbuild/pyostbuild/builtins.py
index a398b6a..b997c07 100755
--- a/src/ostbuild/pyostbuild/builtins.py
+++ b/src/ostbuild/pyostbuild/builtins.py
@@ -21,12 +21,24 @@ import os
import sys
import argparse
+from . import ostbuildrc
+from .ostbuildlog import log, fatal
+
_all_builtins = {}
class Builtin(object):
name = None
short_description = None
+ def parse_config(self):
+ self.repo = ostbuildrc.get_key('repo')
+ self.srcdir = ostbuildrc.get_key('srcdir')
+ if not os.path.isdir(self.srcdir):
+ fatal("Specified srcdir '%s' is not a directory" % (self.srcdir, ))
+ self.workdir = ostbuildrc.get_key('workdir')
+ if not os.path.isdir(self.workdir):
+ fatal("Specified workdir '%s' is not a directory", (self.workdir, ))
+
def execute(self, args):
raise NotImplementedError()
diff --git a/src/ostbuild/pyostbuild/kvfile.py b/src/ostbuild/pyostbuild/kvfile.py
new file mode 100755
index 0000000..075d4b0
--- /dev/null
+++ b/src/ostbuild/pyostbuild/kvfile.py
@@ -0,0 +1,23 @@
+# 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.
+
+def parse(stream):
+ ret = {}
+ for line in stream:
+ (k,v) = line.split('=', 1)
+ ret[k.strip()] = v.strip()
+ return ret
diff --git a/src/ostbuild/pyostbuild/main.py b/src/ostbuild/pyostbuild/main.py
index b30a6d6..eb9a659 100755
--- a/src/ostbuild/pyostbuild/main.py
+++ b/src/ostbuild/pyostbuild/main.py
@@ -23,6 +23,7 @@ import argparse
from . import builtins
from . import builtin_autodiscover_meta
+from . import builtin_build
from . import builtin_chroot_compile_one
from . import builtin_commit_artifacts
from . import builtin_compile_one
diff --git a/src/ostbuild/pyostbuild/ostbuildrc.py b/src/ostbuild/pyostbuild/ostbuildrc.py
new file mode 100755
index 0000000..413f7f1
--- /dev/null
+++ b/src/ostbuild/pyostbuild/ostbuildrc.py
@@ -0,0 +1,41 @@
+# 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.
+
+import os,sys,ConfigParser
+
+_config = None
+
+def get():
+ global _config
+ if _config is None:
+ configpath = os.path.expanduser('~/.config/ostbuild.cfg')
+ parser = ConfigParser.RawConfigParser()
+ parser.read([configpath])
+
+ _config = {}
+ for (k, v) in parser.items('global'):
+ _config[k.strip()] = v.strip()
+ return _config
+
+def get_key(name, provided_args=None):
+ config = get()
+ if provided_args:
+ v = provided_args.get(name)
+ if v is not None:
+ return v
+ return config[name]
+
diff --git a/src/ostbuild/pyostbuild/subprocess_helpers.py b/src/ostbuild/pyostbuild/subprocess_helpers.py
index 9d2476a..5b1557f 100755
--- a/src/ostbuild/pyostbuild/subprocess_helpers.py
+++ b/src/ostbuild/pyostbuild/subprocess_helpers.py
@@ -23,9 +23,7 @@ import subprocess
from .ostbuildlog import log, fatal
-def run_sync(args, cwd=None, env=None):
- log("running: %r" % (args,))
- f = open('/dev/null', 'r')
+def _get_env_for_cwd(cwd=None, env=None):
# This dance is necessary because we want to keep the PWD
# environment variable up to date. Not doing so is a recipie
# for triggering edge conditions in pwd lookup.
@@ -40,12 +38,57 @@ def run_sync(args, cwd=None, env=None):
env_copy['PWD'] = cwd
else:
env_copy = env
- proc = subprocess.Popen(args, stdin=f, stdout=sys.stdout, stderr=sys.stderr,
+ return env_copy
+
+def run_sync_get_output(args, cwd=None, env=None, stderr=None, none_on_error=False):
+ log("running: %s" % (subprocess.list2cmdline(args),))
+ env_copy = _get_env_for_cwd(cwd, env)
+ f = open('/dev/null', 'r')
+ if stderr is None:
+ stderr_target = sys.stderr
+ else:
+ stderr_target = stderr
+ proc = subprocess.Popen(args, stdin=f, stdout=subprocess.PIPE, stderr=stderr_target,
close_fds=True, cwd=cwd, env=env_copy)
f.close()
+ output = proc.communicate()[0].strip()
+ if proc.returncode != 0 and not none_on_error:
+ logfn = fatal
+ else:
+ logfn = log
+ logfn("pid %d exited with code %d, %d bytes of output" % (proc.pid, proc.returncode, len(output)))
+ if proc.returncode == 0:
+ return output
+ return None
+
+def run_sync(args, cwd=None, env=None, fatal_on_error=True, keep_stdin=False):
+ log("running: %s" % (subprocess.list2cmdline(args),))
+ # This dance is necessary because we want to keep the PWD
+ # environment variable up to date. Not doing so is a recipie
+ # for triggering edge conditions in pwd lookup.
+ if (cwd is not None) and (env is None or ('PWD' in env)):
+ if env is None:
+ env_copy = os.environ.copy()
+ else:
+ env_copy = env.copy()
+ if ('PWD' in env_copy) and (not cwd.startswith('/')):
+ env_copy['PWD'] = os.path.join(env_copy['PWD'], cwd)
+ else:
+ env_copy['PWD'] = cwd
+ else:
+ env_copy = env
+ if keep_stdin:
+ target_stdin = sys.stdin
+ else:
+ target_stdin = open('/dev/null', 'r')
+ proc = subprocess.Popen(args, stdin=target_stdin, stdout=sys.stdout, stderr=sys.stderr,
+ close_fds=True, cwd=cwd, env=env_copy)
+ if not keep_stdin:
+ target_stdin.close()
returncode = proc.wait()
- if returncode != 0:
+ if fatal_on_error and returncode != 0:
logfn = fatal
else:
logfn = log
logfn("pid %d exited with code %d" % (proc.pid, returncode))
+ return returncode
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]