worch - Let the orchestration waf through the suite.
This package provides a waf based Python module orch, short for “orchestrate”, which allows the creation of a meta-build system for a suite of related packages. The system is centered around a simple configuration language describing the installation and a number of interpreting methods to produce installation tasks. Finally, these tasks are handed to waf for sequenced execution.
Some features of worch:
- a simple, concise and flexible text-based configuration language which can fully describe the installation of a complex software suite.
- automated installation includes downloading of source archives in common formats, their unpacking, source preparation using popular configuration methods, building and finally installing and any additional steps the user defines
- the user defines the conventions that set file/directory installation patterns.
- batteries included for driving some popular native build mechanisms. Novel build systems can accommodated by providing custom feature methods which may be dynamically incorporated based on the configuration.
- in order to assure proper build order while allowing parallel builds, dependencies can be defined either implicitly by linking installation steps via the files they require or produce or by explicitly declaring dependencies between any two steps by name.
- idempotent build steps, repeating a build does not repeat successful steps.
- no hidden failures, errors abort the installation.
- packages can be grouped to assure parts of the suite are entirely built before others.
- while heavy use of environment variables is discouraged one can define build environment variables in the configuration. These can be applied on a per-package basis or can be defined on a package or group basis and applied to those packages which require them.
While waf supports all the way back to Python 2.3, worch requires at least Python 2.6 (eg, Sci. Linux 6). A script is provided to help build a modern version of python from source. See this doc for more info.
Some of the terms and concepts used by Worch.
- suite
- the collection of all the software installed together with Worch
- package
- software from a single source to be installed as an atomic unit. A package has a name and a version
- group
- a collection of related packages that must be fully installed independent from others in the suite
- step
- the installation of a package is broken down into a linear, series of steps. The steps include “download”, “unpack”, “patch”, “prepare”, “build” and “install”. Fully qualified steps names include the name of the package on which they operate, for example:
<package>_<step>
- feature
- one or more steps collected together in a named and reusable manner (“feature” is in the waf sense of the word)
- tool
- a mechanism to load external Python code largely to provide new features
- dependencies
- before a step can run it may require another step to run either in an absolute way or by depending on the production of some file by that other step.
The use of Worch consists largely of editing a configuration file set and running waf
with the orch
Python module to interpret them.
Worch comes with example configuration files that build a few simple Free Software programs. To exercise them:
Worch includes a copy of waf program in the top level directory but see the waf book for if you wish to explicitly install.
$ git clone https://github.com/brettviren/worch.git $ cd worch $ alias waf=`pwd`/waf
The alias is just for conveniences. You may also place it in your execution PATH
or execute it by giving it’s absolute path. On first run it will unpack itself in a .waf*
directory so needs to be place where you have write access.
Worch provides some ready-to-use configuration files under the examples sub-directory and below use the simple example.
$ waf --prefix=/tmp/worch-simple-example \ --orch-config=examples/simple/*.cfg \ configure Setting top to : /home/bviren/work/lbne/waffle/worch Setting out to : /home/bviren/work/lbne/waffle/worch/tmp 'configure' finished successfully (0.065s) $ ls ./tmp c4che config.log $ ls /tmp/worch-simple-example/ bc cmake hello
Notes:
- if multiple configuration files are given; they are effectively concatenated. (
tcsh
users beware that your shell sucks) - often just a main file needs to be specified and any other ones are implicitly loaded
- the
./tmp
directory is created as directed by theout
variable in the mainwscript
file and holds all intermediate build files - if the
configure
step is repeated it requires repetition of the options as well. --zones=orch
can be passed (toconfigure
andbuild
) to get more verbose output fromwaf
.
The build
command is default and need not be explicitly stated.
$ waf [-j4] [-vvv] > log Waf: Entering directory `/home/bviren/work/lbne/waffle/worch/tmp' [ 1/18] cmake_seturl: -> tmp/cmake-2.8.8.url [ 2/18] cmake_download: tmp/cmake-2.8.8.url -> tmp/downloads/cmake-2.8.8.tar.gz [ 3/18] cmake_unpack: tmp/downloads/cmake-2.8.8.tar.gz -> tmp/sources/cmake-2.8.8/bootstrap [ 4/18] cmake_prepare: tmp/sources/cmake-2.8.8/bootstrap -> tmp/builds/cmake-2.8.8-debug/cmake_install.cmake [ 5/18] cmake_build: tmp/builds/cmake-2.8.8-debug/cmake_install.cmake -> tmp/builds/cmake-2.8.8-debug/bin/cmake [ 6/18] cmake_install: tmp/builds/cmake-2.8.8-debug/bin/cmake -> ../../../../../../tmp/worch-simple-example/cmake/2.8.8/debug/bin/cmake [ 7/18] hello_seturl: -> tmp/hello-2.8.url [ 8/18] bc_seturl: -> tmp/bc-1.06.url [ 9/18] bc_download: tmp/bc-1.06.url -> tmp/downloads/bc-1.06.tar.gz [10/18] hello_download: tmp/hello-2.8.url -> tmp/downloads/hello-2.8.tar.gz [11/18] bc_unpack: tmp/downloads/bc-1.06.tar.gz -> tmp/sources/bc-1.06/configure [12/18] hello_unpack: tmp/downloads/hello-2.8.tar.gz -> tmp/sources/hello-2.8/configure [13/18] bc_prepare: tmp/sources/bc-1.06/configure -> tmp/builds/bc-1.06-debug/config.status [14/18] hello_prepare: tmp/sources/hello-2.8/configure -> tmp/builds/hello-2.8-debug/config.status [15/18] bc_build: tmp/builds/bc-1.06-debug/config.status -> tmp/builds/bc-1.06-debug/bc/bc [16/18] bc_install: tmp/builds/bc-1.06-debug/bc/bc -> ../../../../../../tmp/worch-simple-example/bc/1.06/debug/bin/bc [17/18] hello_build: tmp/builds/hello-2.8-debug/config.status -> tmp/builds/hello-2.8-debug/src/hello [18/18] hello_install: tmp/builds/hello-2.8-debug/src/hello -> ../../../../../../tmp/worch-simple-example/hello/2.8/debug/bin/hello Waf: Leaving directory `/home/bviren/work/lbne/waffle/worch/tmp' 'build' finished successfully (8m3.605s) $ waf Waf: Entering directory `/home/bviren/work/lbne/waffle/worch/tmp' Waf: Leaving directory `/home/bviren/work/lbne/waffle/worch/tmp' 'build' finished successfully (0.028s) $ ls ./tmp bc-1.06.url builds c4che cmake-2.8.8.url config.log downloads hello-2.8.url sources $ ls /tmp/worch-simple-example/*/*/* /tmp/worch-simple-example/bc/1.06/debug: bin info man /tmp/worch-simple-example/cmake/2.8.8/debug: bin doc man share /tmp/worch-simple-example/hello/2.8/debug: bin share
Notes:
- parallelism can be used with the
-j
option, verbosity increased with-v
- logging from each step is kept atomic and is not printed until that step finishes
- ordering of steps is determined by dependencies
- rerunning
waf
does not repeat the successful steps - waf users may expect an explicit “waf install” but it is not used by Worch
- all installation files are placed under the directory set by the
--prefix
option in theconfigure
step - this example installs each package into a specific
<name>/<version>/<qualifier>
directory, but other patterns are possible
The main user interaction, besides running waf
as above, is in writing configuration files to describe the installation.
The Worch configuration files are in the standard syntax expected by the Python ConfigParser
module (aka “INI” format). They consist of a number of named sections followed by key/value pair settings. They section title is surrounded by square brackets ”[]
” and the key/value pairs are separated by either “===” or ”:
”.
# this is a comment [section] key = value key: value
Worch adds to this simple syntax some these features:
- string value interpolation
- hierarchical structure
Most values are interpreted having a scalar string type. These values may contain the names of other keys surrounded by curly braces ”{}
”. These will have their value replaced by Worch.
[section] key1 = World key2 = Hello {key1}
The result is that the value of key2
will be ”Hello World
”. Keys must be used in the same hierarchical scope as they are defined. The hierarchy is described in the next section. In addition to interpolation being run on the items in the configuration, Worch provides a few additional key/value pairs:
- uname
- output of uname stored as
kernelname
,hostname
,kernelversion
,vendorstring
,machine
platform
- a name formed from the
kernelname
andmachine
gcc_dumpversion
- the native GCC version
gcc_dumpmachine
- the native GCC notion of the hosting machine architecture
gcc_multiarch
- the native multiarch string (Debian extension)
libc_version
- the libc version
ups_flavor
- the UPS flavor string
Additional keys may be provided based on the existence of keys in the configuration.
version_2digit
- at most the first two digits of the “.”-separated version string
version_underscore
- version string with “.” replaced with “_”
version_nodots
- version string with “.” removed
tagsdashed
- all tags concatenated with dashes
tagsunderscore
- all tags concatenated with underscores
Worch partitions the configuration logically into packages and groups of packages. This partitioning is done by interpreting certain keys as holding a list of sections names of a certain type. The mapping of key to type is held in the special keytype
section. The keytype
section used by Worch is:
[keytype] packages = package groups = group
This means that if the keys packages
or groups
are encountered, their values are interpreted as a list of section names of the “type” ”package
” or ”group
”. The interpretation begins at with one section, ”start
” by default and follows down any keytype
keys.
[start] groups = group1, group2 key = value_from_start [group group1] packages = package1, package2 key = value_from_group1 [package package1] key = value_from_package1 [package package2] some_other_key = {key}
The hierarchy built in this way causes all simple, scalar values to be copied down to the leafs, which are packages in this case. This means that each package gets a copy, possibly customized, of all scalar key/value pairs. The interpolation occurs late so resolution is performed with this final, leaf set. Using the example above:
- package1
- has
key
set tovalue_from_package1
- package2
- has
key
andsome_other_key
both set tovalue_from_group1
The configuration file can expresses dependencies between steps of different packages in two ways.
- implicitly through required/produced files
- explicitly by naming a package+step on which the current a particular package step depends
To express an explicit dependency a package configuration section specifies a depends
key with a comma-separated list of <step>:<package>_<step>
elements. For example:
depends = prepare:gmp_install
Building a package is split into a number of steps. A step is identified by a simple name. There is no limit to step names but a limited set are identified as covering most meta-build operations. They are:
- seturl
- write the URL of the source archive file (or repository) into a file to start the package dependencies
- download
- produce the source archive file (or repository clone) based on the URL
- unpack
- produce a directory of pristine source code
- patch
- modify the source code, in place, typically by applying a patch
- prepare
- prepare the source for building, for example running
cmake
or autoconf’sconfigure
script - build
- produce binaries from the source
- install
- place build results to a final installation location
A step may have a default, associated directory in which it is run. The directories are specified by the following configuration variables. These locations and their associated steps are:
download_dir
- download
source_dir
- unpack, patch
build_dir
- prepare, build, install
The common steps are then grouped and implemented by “features” which can then be applied to different packages. Features use the steps as “touch stones” so that different features can be swapped while others can be shared. An example is the tarball
and vcs
features both provide through to the “unpack” step. The “cmake” and “autoconf” features provide the “prepare” step.
Here is a list of “features” that worch provides and the steps they implement:
- tarball
- download and unpack a tar/zip file (seturl, download, unpack)
- vcs
- clone or checkout source from a version control system (git, hg, cvs, svn), (seturl, download, unpack)
- patch
- apply a patch to the source (patch)
- prepare
- a generic source preparation feature (prepare)
- autoconf
- prepare source using autoconf
configure
script (prepare) - cmake
- prepare source by calling cmake script (prepare)
- makemake
- run
make/make install
(build, install) - pypackage
- install a Python package via
setup.py
(prepare, build, install) - pythiainst
- special purpose feature for installing Pythia6 (prepare, build, install, and feature-specific steps)
The rest of this section gives some examples
Almost all packages start by a download of a source archive (tar or zip file or git repository). Worch will handle these steps using the tarball
feature. The example below shows how the GNU hello package makes use of this feature. A full, working example is in ./examples/simple.
[group gnuprograms] features = tarball autoconf srcpkg_ext = tar.gz source_unpacked = {package}-{version} source_package = {source_unpacked}.{srcpkg_ext} download_dir = downloads source_dir = sources source_url = http://ftp.gnu.org/gnu/{package}/{source_package} [package hello] version: 2.8
Notes:
- The
tarball
feature is added to a specialfeatures
key which is interpreted as a space separated list (fixme: should allow for comma-separated - space separation exposes a waf detail) - The package section is brief as it inherits from the group and only provides the information unique to the pacakge
- The
tarball
feature needs to know where the download and source directories are, how the source package, URL and eventual unpacked directory are named - The extension is pulled out to its own variable to accommodate multiple packages that are similar but may be archived/compressed differently (eg, another GNU package that happens to be compressed with BZ2)
The vast majority of packages are built with the configure/make/make install
pattern provided by GNU autoconf. The autoconf
feature can invoke this pattern. It follows on from the tarball
feature and thus requires some of the same keys to be defined. One does not typically need to redefine these but rather they are used in the same context. Here is a follow-on to the hello
example above but just showing the parts relevant to the autoconf
feature. Again, see the simple example for a fully working instance.
[group gnuprograms] tags = debug features = tarball autoconf source_unpacked = {package}-{version} source_package = {source_unpacked}.{srcpkg_ext} build_dir = builds/{package}-{version}-{tagsdashed} install_dir = {PREFIX}/{package}/{version}/{tagsdashed} [package hello] version: 2.8 depends = prepare:bc_install build_target = src/hello install_target = bin/hello
Notes:
- Here a
tags
key is introduced. Tags are used to indicate variants in the build. In this example a debug version ofhello
should be built (fixme: tags are not yet supported). - The build and install directories are specified while some source-related keys are reused from the
tarball
feature - A build and install target must be specified in order to satisfy waf requirements
- A
depends
key is used to place an artificial, contrived dependency on another package step.
Many native build systems can use the autoconf
feature by explicitly defining some variables that it uses. For example, building CMake does not use autoconf but it is close. Its package section can be defined like:
[package cmake] features = tarball autoconf unpacked_target = bootstrap prepare_script = bootstrap
This causes the tarball
and autoconf
features to look for a bootstrap
instead of a configure
script.
Waf honors expressed dependencies and will rerun a step when a dependency changes. However, not all dependencies that could be expressed are. In particular, if a step completes successfully and then one changes either its source code (which is not in a file explicitly depended on) or the worch configuration files then waf may have no way to notice a change.
However, waf provides a ”step
” command which will rerun an isolated step or steps without regards to dependencies. To indicate the step on uses the --files
options. Waf finds the step that has been declared to produce the given file(s) and reruns it.
In general, one must have detailed understanding of the implementation of a feature and its steps in order to know what to give to the --files
option. However, worch consistently creates a special “control” file after the successful completion of each step. This control file is consistently named like:
{control_dir}/{package}_{step}
The control_dir
may be defined in the configuration but defaults to simply ”controls/
” and is found in the “out” directory.
As an example, in the ORKA build it was found that the Geant4VMC package requires Geant4 to include the G3toG4.hh
header (despite that we try telling the package NO_G3TOG4
). To reconfigure Geant4 to include this header in the install requires adding -DGEANT4_USE_G3TOG4=ON
to the CMake command line. In order to avoid rebuilding the entire suite and just rerunning prepare
, build
and install
steps for Geant4 one can do:
# rerun the configure step to pick up # the changes to the configuration $ waf [...] configure # manually have waf (re)run each Geant4 step $ waf step --files=tmp/controls/geant_prepare $ waf step --files=tmp/controls/geant_build $ waf step --files=tmp/controls/geant_install # ditto for g4vmc $ waf step --files=tmp/controls/geant4_prepare $ waf step --files=tmp/controls/geant4_build $ waf step --files=tmp/controls/geant4_install # sop up any collateral changes, or continue with steps not yet run $ waf
Worch can produce a lot of debugging information. It has the concept of “zones” of logging. To add some verbosity just for worch logs one can do:
$ waf --zones=orch [...]
Every step which involves running an executable produces a log file in:
{out}/logs/worch_{package}_{step}.log.txt
The log file is composed of sections beginning with the following:
WORCH CMD
- the command line run
WORCH CWD
- the current working directory in which the command ran
WORCH TSK
- the step’s dependencies (input and output files) followed by a detailed dump of internal waf information
WORCH ENV
- a dump of the shell environment variables
WORCH command output
- the last part of the log file shows any output from the command itself
The log file for long-running steps may be found and monitored “live” by doing something similar to the following commands:
# Find the updating log $ ls -ltr tmp/logs/ | tail $ tail -f tmp/logs/worch_ilcroot_build.log.txt
Here ”tmp/
” is the directory specified by the ”waf --out=tmp
” flag.
If a command that is run by a step fails a shell script will be produced with everything that should reproduce the failure in-place (it is very much not portable). The location of the shell script is the current working directory where the command ran and is reported by waf. It should be run from the directory that holds it in order to reproduce the failure.
It is possible to bundle waf, worch and a set of configuration files into a single self-extracting python executable. This single file caries all the information required to build the corresponding suite of software. The scripts/
directory contains an example of how to do this in the form of the worch-bundle-header
script. It can be run like:
$ cd worch/ $ ./scripts/worch-prepare-bundle worch-test-bundle examples/simple-with-patch .
Note the last argument is the worch directory itself. When the resulting bundle, worch-test-bundle
in this case, contains
- a copy of waf
- the main
wscript
file andorch
module from the givenworch
directory - a copy of the given configuration directory
When the resulting bundle file is run (note: avoid running it from inside the worch
development directory) it will:
- unpack waf, run it once to produce the
.waf-*
directory and then delete waf - unpack
wscript
,orch
and configuration files
You can then run it as if it were waf
itself
$ mkdir /tmp/worch-test $ cp worch-test-bundle $ cd /tmp/worch-test $ ./worch-test-bundle --prefix=install --orch-config=examples/simple-with-patch/*.cfg configure $ waf