Code Monkey home page Code Monkey logo

planex's Introduction

Planex Build Status

Planex is a toolkit for building medium-sized collections of RPM packages which may depend on each other. It fills the gap between tools to build individual RPMs, such as rpmbuild and mock, and tools to build entire distributions, such as koji.

Installing Planex

The easiest way to install Planex is to use pip in a Python virtualenv.

Planex uses the Python bindings for librpm, which must be installed separately using the system package manager.

$ dnf install python2-rpm

If you want to install Planex in a virtualenv, remember to pass the --system-site-packages so the librpm bindings are available in the virtualenv.

$ virtualenv venv --system-site-packages
New python executable in /tmp/venv/bin/python2
Also creating executable in /tmp/venv/bin/python
Installing setuptools, pip, wheel...done.
$ source venv/bin/activate

Clone the source, install the requirements and install Planex

$ git clone https://github.com/xenserver/planex
Cloning into 'planex'...
remote: Counting objects: 11951, done.
remote: Compressing objects: 100% (78/78), done.
remote: Total 11951 (delta 143), reused 176 (delta 130), pack-reused 11743
Receiving objects: 100% (11951/11951), 2.17 MiB | 931.00 KiB/s, done.
Resolving deltas: 100% (8368/8368), done.
$ cd planex
$ pip install -r requirements.txt
...
$ python setup.py install

For development work, also install the test requirements and use python setup.py develop, which symlinks the code so you do not have to run python setup.py install after every edit.

$ pip install -r test-requirements.txt
...
$ python setup.py develop
$ nosetests

Design principles

Small, single purpose tools sequenced by make

Planex is a set of single-purpose tools designed to be run by a generic Makefile. We arrived at this structure after previous experience with a monolithic Makefile which described the entire build process in make, and a monolithic Python system which described the entire build process in Python. Planex is intended to be a mid-point between these two extremes.

  • We use make to figure out which files to rebuild and in what order. All we need to do is generate the dependency graph, after which we can benefit from features such as incremental, parallel builds with no extra work.
  • Small, single purpose tools are easier to write, understand and maintain than monlithic scripts. Although the Planex tools are intended to be run by make, they can easily be run by hand for testing and development.
  • Where make cannot understand part of our build process, we can use small tools to adapt it. For instance, it is impossible to use a URL as a make prerequisite - make only understands files on disk - so we cannot write a rule which directly downloads a source tarball. Instead, we have a tool (planex-fetch) which downloads a source defined in a spec file. Make calls planex-fetch, passing it the spec and asking it to download the source tarball. If the spec later changes, the source will be downloaded again.

Use standard tools in standard ways (the Stackoverflow test)

Using standard tools such as rpmbuild, mock and make means that when we run into trouble we can often find a solution by searching the web. This is not possible with a custom monolithic build script. Even though our monolithic Makefile was based on a standard tool, it used complex and unusual features of make for which documentation and tutorials were hard to find.

Look like upstream

Planex is designed to build medium-sized collections of RPM packages which depend on each other and are maintained by a small number of people. Distribution builders such as Fedora have a slightly different problem - they have to build huge collections of packages, many of which do not depend on each other and which are maintained by a large number of maintainers. This means that upstream tools are not always suitable for our purposes. However even when we have to write our own tools we should try to stay as close as possible to the upstream way of doing things so we can benefit from other upstream tools. Examples of this include fetching source tarballs instead of checking code out from Git, avoiding patching or re-writing spec files copied from upstream, and following rpmbuild's working directory structure for the spec file repository.

Defining which packages to build

The main input to Planex is a repository containing RPM spec files, possibly a few source files and a small Makefile.

.
├── Makefile
├── SOURCES
│   └── bar
│       └── bar.service
└── SPECS
    ├── bar.spec
    ├── bar.lnk
    └── foo.spec

Each spec file describes the sources needed to build a package, any other packages which are required to build it, how to build it and how to pack the resulting files into a binary package. Most package sources are not kept with the spec files - instead planex-fetch downloads them from the URLs given in the spec files. The source files could be static files on HTTP servers or tarballs produced dynamically by source control systems such as GitHub or BitBucket. A few sources can be kept in the spec file directory - these could be small temporary patches or resources such as SystemD service files which do not really belong anywhere else. This approach is not suitable for large numbers of frequently-changing extra sources, such as patchqueues.

Overriding and augmenting packages

If a package relies on a source file which is not fully defined in its spec file, planex-fetch will not know where to get it. One way around this problem is to change the spec file to contain a full URL for the source, however if the spec file is for an upstream package which we just re-build we may not want to change it. Planex's solution to this is to add a 'link' file which defines additional sources to fetch and build into the package. The link file can modify the sources listed in the spec in several ways:

  • provide the URL of a source which does not have one
  • override a source URL in the spec file - for instance to use a local mirror instead of a public repository
  • provide the URL of a tarball of source files which are required by the package
  • provide the URL of a tarball containing a patchqueue which will be added to any patches already defined by the spec file

For development, it is also possible to override a source listed in the spec with the contents of a Git repository. To do this, create a link file with a .pin extension in the PINS directory. This will cause planex-fetch to make a tarball archive of the repository and use it in place of the tarball specified in the spec file.

planex's People

Contributors

alexbrett avatar andyhhp avatar ctxistvans avatar djs55 avatar edwintorok avatar euanh avatar freddy77 avatar jamesbulpin avatar johnelse avatar jonludlam avatar kostaslambda avatar lindig avatar makunterry avatar marksymsctx avatar matelakat avatar mcclurmc avatar mseri avatar ravippandey avatar robhoes avatar rosslagerwall avatar salvocambria avatar samuelconnolly avatar simonjbeaumont avatar srowe avatar thomassa avatar timsmithctx avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

planex's Issues

Stop including dist (e.g. el6) in the names of our intermediate srpms

Planex generates SRPMs, which it then feeds into mock. The SRPMs are intermediate files, built using rpmbuild on the host system; the final binaries are built in mock chroots. The mock build also builds new SRPMs.

If %{dist} is included in the package name - it is typically in the release - then the intermediate SRPM's name will not match the name of the binary RPM if the host system is not the same as the mock chroot. For instance, building foo-0.0.1-1.fc21.src.rpm will produce foo-0.0.1-1.el6.x86_64.rpm. This difference means that the target and source side patterns of a naive Make pattern rule won't match. To avoid this, when running rpmbuild we force %{dist} to the same value as it will have in mock, but this adds complexity when building packages for several different distributions on the same host.

The dependency generation and makefile have changed considerably since we made the %{dist} change. We should check whether it is still necessary to set it at all. One possibility is to unset it, producing intermediate SRPMs with names such as foo-0.0.1-1.src.rpm, and change the rule generation to match these to the appropriate binary RPMs.

Colour output should be optional

Add a command line switch and an environment variable to disable colour output.
Make sure that control codes don't upset dumb terminals.

progress output should be flushed regularly

When watching the output I notice that 'rpm -ihv' shows the hashes in the progress bar nicely, but the main output of planex seems to come along in big batches.

It would be better to flush the output after printing each debug/info line.

ValueError: can't parse specfile

On CentOS 6.4 guest:

+ git clone https://github.com/xapi-project/xen-api-libs-specs
Initialized empty Git repository in /root/xen-api-libs-specs/.git/

+ planex-configure --config-dir=xen-api-libs-specs

error: line 2: Illegal char '@' in: Version:        @VERSION@
Configuring package with spec file: xen-api-libs-specs/vhdd.spec.in
Traceback (most recent call last):
  File "/root/env/bin/planex-configure", line 9, in <module>
    load_entry_point('planex==0.0.0', 'console_scripts', 'planex-configure')()
  File "/root/env/lib/python2.6/site-packages/planex-0.0.0-py2.6.egg/planex/configure.py", line 329, in main
    sources = sources_from_spec(spec_path)
  File "/root/env/lib/python2.6/site-packages/planex-0.0.0-py2.6.egg/planex/configure.py", line 221, in sources_from_spec
    spec = planex.spec.Spec(spec_path)
  File "/root/env/lib/python2.6/site-packages/planex-0.0.0-py2.6.egg/planex/spec.py", line 92, in __init__
    self.spec = rpm.ts().parseSpec(path)
  File "/usr/lib64/python2.6/site-packages/rpm/transaction.py", line 35, in parseSpec
    return _rpmb.spec(specfile)
ValueError: can't parse specfile

Dependency issues with xen-api-libs-specs

This issue might be related to https://github.com/xapi-project/xen-api-libs-specs

planex-build 's Output:

Executing: createrepo --update planex-build-root/RPMS
Building planex-build-root/SRPMS/ocaml-opasswd-0.9.0+8+g2325692-+0.src.rpm - build number: 1
Executing: rpmbuild --rebuild -v planex-build-root/SRPMS/ocaml-opasswd-0.9.0+8+g2325692-+0.src.rpm --target x86_64 --define _build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm
Failed to build rpm from srpm: planex-build-root/SRPMS/ocaml-opasswd-0.9.0+8+g2325692-+0.src.rpm

stdout
======
Installing planex-build-root/SRPMS/ocaml-opasswd-0.9.0+8+g2325692-+0.src.rpm
Building target platforms: x86_64
Building for target x86_64


stderr
======
error: Failed build dependencies:
    ocaml is needed by ocaml-opasswd-0.9.0+8+g2325692-+0.x86_64
    ocaml-findlib is needed by ocaml-opasswd-0.9.0+8+g2325692-+0.x86_64
    ocaml-ctypes is needed by ocaml-opasswd-0.9.0+8+g2325692-+0.x86_64

Build step 'Execute shell' marked build as failure
Finished: FAILURE

Don't rebuild if target is newer than sources

Planex currently cleans its environment completely, then rebuilds everything. This is fine for the build system, but we should have a mode for developers which acts more like make and only rebuilds what's needed.

planex-clone: Get all sources

If the source is not Source0, planex-clone won't clone it. planex-clone should also ignore tarball (non-git) urls.

Unittests fail on 32 bit machines

Running the unittests on a 32 bit machine fails.

An example failure:

======================================================================
FAIL: test_binary_package_paths (test_planex_spec.RpmTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/matel/citrix/planex/tests/test_planex_spec.py", line 68, in test_binary_package_paths
    "./RPMS/x86_64/ocaml-cohttp-devel-0.9.8-1.el6.x86_64.rpm"]))
AssertionError: Lists differ: ['./RPMS/i386/ocaml-cohttp-0.9... != ['./RPMS/x86_64/ocaml-cohttp-0...

First differing element 0:
./RPMS/i386/ocaml-cohttp-0.9.8-1.el6.i386.rpm
./RPMS/x86_64/ocaml-cohttp-0.9.8-1.el6.x86_64.rpm

- ['./RPMS/i386/ocaml-cohttp-0.9.8-1.el6.i386.rpm',
?          ^^                            ^^

+ ['./RPMS/x86_64/ocaml-cohttp-0.9.8-1.el6.x86_64.rpm',
?          ^  +++                          ^  +++

-  './RPMS/i386/ocaml-cohttp-devel-0.9.8-1.el6.i386.rpm']
?          ^^                                  ^^

+  './RPMS/x86_64/ocaml-cohttp-devel-0.9.8-1.el6.x86_64.rpm']
?          ^  +++                                ^  +++


----------------------------------------------------------------------

auto-generated release number should include the package hard-coded release

I have the following package installed:

planex-0.6.0-1.fc21.noarch

I have just rebuilt a newer package, and planex has created the following version number:

planex-0.6.0-s0+0.6.0+229+g70a858b.fc21.noarch.rpm

Unfortunately, rpm considers release s0+0.6.0+229+g70a858b to come before release 1, so the new package is not offered as an upgrade.

$ rpmdev-vercmp 0.6.0-1 0.6.0-s0+0.6.0+229+g70a858b
planex-0.6.0-1 > planex-0.6.0-s0+0.6.0+229+g70a858b

dotgitdir attribute error

+ planex-configure --config-dir=xen-api-libs-specs
Configuring package with spec file: xen-api-libs-specs/vhdd.spec.in
url=git://github.com/jonludlam/vhdd
path=/jonludlam/vhdd
fragment=
trying /root/devel2/vhdd
trying /root/devel2/vhdd.git
trying /repos/vhdd
trying /root/github_mirror//jonludlam/vhdd.git
Located git repo at: None
Traceback (most recent call last):
  File "/root/env/bin/planex-configure", line 9, in <module>
    load_entry_point('planex==0.0.0', 'console_scripts', 'planex-configure')()
  File "/root/env/lib/python2.6/site-packages/planex-0.0.0-py2.6.egg/planex/configure.py", line 330, in main
    version = latest_git_tag(sources[0])
  File "/root/env/lib/python2.6/site-packages/planex-0.0.0-py2.6.egg/planex/configure.py", line 141, in latest_git_tag
    if dotgitdir.startswith("/repos"):
AttributeError: 'NoneType' object has no attribute 'startswith'
Build step 'Execute shell' marked build as failure

EPEL dependency

To use mock, PlanEx depends on EPEL. It needs to be documented.

Add file type checking to planex-fetch

planex-downloader checked whether the result of a download looked like an archive, and retried the download if it didn't. planex-fetch should do the same thing.

There is a Python library to wrap around libmagic, but it is more or less as ugly as just shelling out to file, as downloader used to do.

planex-pin --version and rpm -q planex disagree

$ planex-pin --version
planex-pin 0.6.0
$ rpm -q planex
planex-0.6.0-1+s0+0.6.0+233+g32a45d7.fc21.noarch

I don't think it will be possible to make them agree without quite a bit of hassle, though. We would need to find a way to feed the pinned version number through to setuptools. May just have to close this one as wontfix.

The error message from 'planex-pin update' when the treeish isn't specified is unhelpful

No #master specified on the repo path leads to:

# planex-pin add SPECS/foo.spec /repos/myrepo
# planex-pin update
Traceback (most recent call last):
  File "/usr/bin/planex-pin", line 9, in <module>
    load_entry_point('planex==0.6.0', 'console_scripts', 'planex-pin')()
  File "/usr/lib/python2.7/site-packages/planex-0.6.0-py2.7.egg/planex/pin.py", line 341, in _main
    main(sys.argv[1:])
  File "/usr/lib/python2.7/site-packages/planex-0.6.0-py2.7.egg/planex/pin.py", line 334, in main
    args.func(args)
  File "/usr/lib/python2.7/site-packages/planex-0.6.0-py2.7.egg/planex/pin.py", line 159, in update
    src_version = describe(repo, treeish if treeish else None)
  File "/usr/lib/python2.7/site-packages/planex-0.6.0-py2.7.egg/planex/pin.py", line 34, in describe
    sha = run(cmd)['stdout'].strip()
  File "/usr/lib/python2.7/site-packages/planex-0.6.0-py2.7.egg/planex/util.py", line 60, in run
    (" ".join([pipes.quote(word) for word in cmd])))
  File "/usr/lib64/python2.7/pipes.py", line 269, in quote
    for c in file:
TypeError: 'NoneType' object is not iterable

planex-pin remove should not leave an empty dict behind

planex-pin add SPECS/planex.spec  ../planex#master
planex-pin remove SPECS/planex.spec
 cat pins
# This file is auto-generated by planex-pin
# Do not edit directly, instead use planex-pin {add,list,remove}
{"SPECS/planex.spec": {}}

pins should be empty when all pins have been removed

planex-fetch cannot download multiple sources for the same spec file

planex-fetch currently takes one spec file name and one source file name as arguments, and downloads the source file as defined in the spec. If a spec file defines multiple sources, multiple invocations of planex-fetch are needed. However planex-fetch is usually run by a make pattern rule. The dependency rules generated by planex-depend to download sources are of the form:

foo.tar.gz: foo.spec
foo-bar.tar.gz: foo.spec

Make condenses these into:

foo.tar.gz foo-bar.tar.gz: foo.spec

make only passes one of the targets to the planex-fetch invocation [1], so the other target is not downloaded. Furthermore, because the first target is downloaded, the pattern rule will never fire again.

[]1] See https://www.gnu.org/software/make/manual/html_node/Pattern-Intro.html#Pattern-Intro for details.

The solution is either to make planex-fetch able to download several sources files in one invocation, then find a way to get make to pass all the target names to it, or to add an option to planex-fetch which makes it download all sources defined in the spec file.

Planex doesn't clone repos

Planex at the moment seems to assume, that some local clones of the repositories exist by looking at hard coded paths. Downloading the sources should be part of the build process. I recommend that we add a planex-clone command for this.

planex-cache fails if no loopback repository is defined

planex-cache now allows you to change the name of the loopback repository, but it still insists that you have one. If you are only building a couple of packages which don't have mutual dependencies, you might not want or need a loopback repository. planex-cache should have a 'no loopback' option to cater for this situation.

Add planex-pin 'freeze' command to update base spec files to match pins

After developing for a while with a pinned repository, it would be nice if planex could help to update the base spec file and tag a new release. The planex-pin freeze command (or some other name - perhaps planex-pin release is better) should:

  • rewrite the base spec file, generating a new version number and resetting the release number
    • the new version number should be provided on the command line
  • prompt the user to fill in the changelog - perhaps helping by printing a summarized list of changes since the last tag
  • tag the pinned package source repository with the new version number

The user will be responsible for pushing the tag to the upstream package repository.

Concurrent createrepo runs can race and cause repo metadata corruption

Probably caused by non-atomic updates of repo metadata.

Traceback (most recent call last):
File "/usr/bin/planex-cache", line 9, in <module>
load_entry_point('planex==0.6.0', 'console_scripts', 'planex-cache')()
File "/usr/lib/python2.7/site-packages/planex-0.6.0-py2.7.egg/planex/cache.py", line 239, in
_main
main(sys.argv[1:])
File "/usr/lib/python2.7/site-packages/planex-0.6.0-py2.7.egg/planex/cache.py", line 217, in
main
pkg_hash = get_srpm_hash(srpm, yumbase, mock_config)
File "/usr/lib/python2.7/site-packages/planex-0.6.0-py2.7.egg/planex/cache.py", line 156, in
get_srpm_hash
pkgs = yumbase.pkgSack.returnNewestByNameArch(patterns=[req])
File "/usr/lib/python2.7/site-packages/yum/packageSack.py", line 520, in
returnNewestByNameArch
pkgs = calr("returnNewestByNameArch", naTup, patterns, ignore_case)
File "/usr/lib/python2.7/site-packages/yum/packageSack.py", line 595, in
_computeAggregateListResult
sackResult = apply(method, args)
File "/usr/lib/python2.7/site-packages/yum/sqlitesack.py", line 46, in newFunc
return func(*args, **kwargs)
File "/usr/lib/python2.7/site-packages/yum/sqlitesack.py", line 1472, in
returnNewestByNameArch
ignore_case)
File "/usr/lib/python2.7/site-packages/yum/packageSack.py", line 951, in
returnNewestByNameArch
ignore_case=ignore_case)
File "/usr/lib/python2.7/site-packages/yum/sqlitesack.py", line 1669, in returnPackages
pkgobjlist = self._buildPkgObjList(repoid, patterns, ignore_case)
File "/usr/lib/python2.7/site-packages/yum/sqlitesack.py", line 46, in newFunc
return func(*args, **kwargs)
File "/usr/lib/python2.7/site-packages/yum/sqlitesack.py", line 1628, in _buildPkgObjList
return self.searchNames(patterns)
File "/usr/lib/python2.7/site-packages/yum/sqlitesack.py", line 57, in newFunc
raise Errors.RepoError, str(e)
yum.Errors.RepoError: database disk image is malformed
/usr/share/planex/Makefile.rules:69: recipe for target
'_build/RPMS/x86_64/ocaml402-ocaml-text-0.7.1-2.el6.x86_64.rpm' failed
make: *** [_build/RPMS/x86_64/ocaml402-ocaml-text-0.7.1-2.el6.x86_64.rpm] Error 1
make: *** Waiting for unfinished jobs....

planex-init: Mock configuration

The Configuration directory should contain a template mock configuration, and planex should be able to generate a mock configuration based on that. Filling in the repositories as needed.

planex-fetch is confused by local patch sources

/usr/bin/planex-fetch: Failed to fetch oclock-1-cc-headers: Could not resolve host: oclock-1-cc-headers
Makefile:17: recipe for target 'SOURCES/oclock-0.3.tar.gz' failed

-bash-4.3$ grep ^Patch SPECS/ocaml-oclock.spec
Patch0: oclock-1-cc-headers
Patch1: oclock-2-destdir

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.