1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
|
Launchpad Buildout
******************
Launchpad is moving to using the buildout_ (or "zc.buildout") build system. It
will gradually replace the sourcecode directory, and hopefully also the
Launchpad Makefile.
Buildout's biggest strength is managing Python packages. That will also be our
primary focus for it. Meanwhile, apt will continue to manage our Python
language installation, as well some or all of our non-Python dependencies, such
as PostgreSQL. While Bazaar will continue to be an essential part of our
toolchain, we will move away from using it to incorporate source code trees of
our dependencies.
If you are not interested in our `Motivations`_ or in an `Introduction to
zc.buildout`_, all developers will at least want to read the very brief
sections about the `Set Up`_ and the `Everyday Usage`_.
Developers who manage source dependencies probably should read the general
information about `Managing Dependencies and Scripts`_, but will also find
detailed instructions to `Add a Package`_, to `Upgrade a Package`_, to `Add a
Script`_, to `Add a File Modified By Buildout`_, and to `Work with Unreleased
or Forked Packages`_.
.. _buildout: http://www.buildout.org/
===========
Motivations
===========
These motivations are labeled as "[INTERNAL]" or "[EXTERNAL]" to indicate
whether it is pertinent for internal dependencies, which we on the Launchpad
team create and release ourselves; or external dependencies, which other
parties, in and out of Canonical, create and release.
* We want more careful specification of our dependencies across branches.
[INTERNAL] [EXTERNAL]
This is a real concern pertinent both for our "trunks" (devel, stable,
db-devel, db-stable) and for our development boxes. For instance, now, in our
trunks, when we want to update a dependency, we need to make sure that *all*
the current Launchpad trunks work with the dependency initially; then submit
a new Launchpad branch that uses the change dependency. A mistake can
even potentially break one or both of the db-* trunks, since PQM only tests
against one branch (usually devel), and sourcecode changes affect all
branches at once. For simplicity, speed, and safety, we want to be able to
submit a single branch that incorporates the source dependencies and the
associated changes at once.
This is also true, if less critical and easier to work around, on developer
boxes. Without care, changes to sourcecode when working on dependencies will
affect all a developer's branches, polluting test results with false
negatives or false positives.
* We want to default to using released versions of our software dependencies.
[EXTERNAL]
A significant number of projects do not always have a pristine trunk, and
many also spend extra effort on polish, bug fixes, and compatibility before a
release. If we do not desperately need a new feature on trunk, using a
release is generally regarded as a safer, better practice. Our current usage
of bzr branches of the development trunks does not encourage this practice.
* We want to be encouraged to make the effort to interact with upstream
projects to have our patches integrated. [EXTERNAL]
Interacting and negotiating with upstream is undeniably more time-consuming
than our current practice of maintaining local bzr branches with our patches,
especially short-term. But our current practice is not good open-source
community behavior--an ironic characteristic for a project like Launchpad. It
also can cause problems down the road, for instance, if the patch becomes
stale and we want to migrate to new releases.
* We want to be protected from changes and differences in our operating system.
[INTERNAL] [EXTERNAL]
This is a concern both over time and across different Launchpad environments.
First, our operating system, Ubuntu, is driven by many needs and goals.
Launchpad is among them, but generally Launchpad serves Ubuntu, not the
reverse. For instance, recently Jaunty dropped Launchpad's Python version.
The Ubuntu developers had good reason--Python 2.4 has not been supported by
the Python developers for some time--but it caused a significant
inconvenience to the Launchpad team. Managing our dependencies, particularly
the Python library dependencies, can help alleviate these problems.
Second, Launchpad developers run a significantly different version of the
operating system than that run in production. Maintaining our Python library
dependencies ourselves can also help alleviate these concerns.
* We want to be able to easily use standard packages of our primary
programming language, Python. [EXTERNAL]
Our Python library dependencies are distributed for many operating systems--
Windows, Mac, and other flavors of Linux--in a unified location and format:
PyPI, using distutils. Using Python library dependencies in their standard
distributions makes it easier for us to reuse code.
* We want to be encouraged to release Python packages of our open-source
code. [INTERNAL]
We are beginning to realize our aspirations of abstracting and releasing some
of our code. Much of that code is in Python. Consuming standard Python
packages encourages us to follow good practice in releasing our own Python
packages. Dogfooding can help keep us honest, and good official releases can
help us support and encourage a community of users.
Meanwhile, we want to be able to keep certain aspects of the legacy story.
* We need deployment to not need network access.
* We need to be able to use custom releases of our Python dependencies, if
absolutely necessary.
* We would like to be able to follow security releases in our operating system.
We will be able to support the first two of these aspects entirely.
As to the third, we will continue to follow operating system security releases
for most or all of the dependencies that are not Python packages--a very
similar situation to the one before Buildout integration.
===========================
Introduction to zc.buildout
===========================
Buildout is a relatively simple system that increases in complexity as it is
extended via "recipes". It works on top of distutils_ and setuptools_. It
uses declarative ini-style files to direct its work. The `Buildout site`_
points to a variety of documents describing and documenting its use.
For Launchpad, Buildout is hidden behind a Makefile as of this writing. If
the Makefile is removed, developers will typically run ``bootstrap.py`` in the
branch, and then run ``bin/buildout``. After that, the ``bin`` directory will
have the commands to start, stop, test, and debug the software. If you are
interested in example direct usage of Buildout, you may want to read `the
"Hacking" document in the Launchpad wiki`_ that describes the usage patterns in
``lazr.*`` packages.
.. _`the "Hacking" document in the Launchpad wiki`: https://dev.launchpad.net/Hacking
Launchpad's Buildout usage is roughly of medium complexity. It is more
complex than that needed by a package with few dependencies and simple usage
(see lazr.uri, for instance), but less complex than that of other large
applications that use the buildout system. More complexity can come by
building more non-Python tools and by having multiple configuration variations,
for instance.
The documentation below will focus on using Launchpad's buildout. See the
links given above for a more thorough general review.
.. _distutils: http://docs.python.org/distutils/index.html
.. _setuptools: http://peak.telecommunity.com/DevCenter/setuptools
.. _`Buildout site`: http://www.buildout.org/
======
Set Up
======
If you use the ``rocketfuel-get`` script, run that, and you will be done.
If you don't, you have just a bit more initial set up. I'll assume you
maintain a pattern similar to what the ``rocketfuel-*`` scripts use: you have a
local, pristine branch of trunk from which you make your other branches. You
manually update the trunk and rsync sourcecode when necessary. When you make
a branch, you use ``utilities/link-external-sourcecode``.
Developers that take this approach should do the following, where ``trunk`` is
the trunk branch from which you make local branches.
::
bzr co lp:lp-source-dependencies download-cache
Then run ``make`` in the trunk.
See the `Everyday Usage: Manual`_ section for further instructions on how to
work without the ``rocketfuel-*`` scripts.
.. _`Everyday Usage: Manual`: Manual_
==============
Everyday Usage
==============
``rocketfuel`` Scripts
======================
If you typically use ``rocketfuel-get``, and you don't change source
dependencies, you should not have any further changes, except that ``bin/test``
has replaced ``test.py``. ``rocketfuel-branch`` and
``link-external-dependencies`` will Do the Right Thing.
Manual
======
If you don't use the rocketfuel scripts, you will still use
``link-external-dependencies`` as before. When a buildout complains that it
cannot find a version of a dependency, do the following, from within the
branch::
bzr up download-cache
After this, retry your make (or run ``bin/buildout`` from the branch).
That's it for everyday usage.
=================================
Managing Dependencies and Scripts
=================================
What if you need to change or add dependencies or scripts? As you might
expect, you need to know a bit more about what's going on, although we can
still keep this at a fairly high level.
First let's talk a little about the anatomy of what we have set up. To be
clear, much of this is based on our own decisions of what to do. If you see
something problematic, bring it up with the Foundations team. Maybe together
we can come up with another approach that meets our needs better.
If you saw the top-level Launchpad directory before we started using Buildout,
you might notice seven new items in the checkout.
``bootstrap.py``
This is the standard bootstrapping file provided by the Buildout
distribution. As of this writing, the Makefile uses this file, and you do
not have to modify it or call it directly.
Without a Makefile, the first step of developing a source tree that uses
Buildout is to run this file in the top level directory with the Python
executable that you wish to use for the source tree. It creates a ``bin``
directory, if it does not already exist, and puts a ``buildout`` executable
there. The next step is to run ``bin/buildout``, which, unless you supply
a ``-c`` option to specify a configuration file, will look for a
buildout.cfg file by default to discover what to do.
Again, as of this writing, the Makefile uses this file, and it does not
need to be modified, so you need not concern yourself with it further at
this time.
``ez_setup.py``
This is another standard file from another project. In this case, it is
the file provided by the setuptools project to install setuptools. It is
used by the ``setup.py`` file, described below. It does not need to be
modified or called directly.
``setup.py``
This is the file that uses ``distutils``, extended by ``setuptools``, to
specify direct dependencies, scripts, and other elements of the local
source tree.
Buildout uses it, but the reverse is not true: ``setup.py`` does not know
about Buildout. This means that packages that use Buildout for development
do not have to require it when they are being installed in other software
as a dependency.
Describing this file in full is well beyond the scope of this document. We
will give recipes for modifying it for certain tasks below. For more
information beyond these recipes, see the setuptools and distutils
documentation.
``buildout.cfg``
This is the default configuration file that ``bin/buildout`` will look to
for instructions.
Describing it in full is well beyond the scope of this document. However,
we will give an overview here.
Configuration files for Buildout are comprised of sections with key-value
pairs.
The key-value pairs are separated with new lines, when the subsequent line
is not indented. The key and value are each separated with an equals sign.
::
foo = bar
baz = bing
bah
boo
sha = zam
That example shows three keys, 'foo', 'baz', and 'sha'. The 'baz' key has
a string with two new lines (which might be interpreted one several ways,
as defined for that key).
The ``[buildout]`` section is the starting point for Buildout to determine
what to do. It looks for an ``extends`` key to find any additional files
to merge in; we use this for ``versions.cfg``, discussed below.
In addition to general configuration and initialization such as this, it
looks in the ``develop`` key to find source trees to develop as part of the
buildout. In the standard Launchpad configuration, we develop only
Launchpad itself (the current directory, or '.'). This means that the
local ``setup.py`` will be run. If you want to develop Launchpad while you
develop another dependency, you can link another source tree in, and
specify an additional ``develop`` directory in another line::
[buildout]
develop = .
lazr_uri_branch
The other basic key in the ``[buildout]`` section that we'll highlight here
is ``parts``. This key identifies the other sections in buildout.cfg that
will be processed. A section that is not identified in the ``[buildout]``
sections ``parts`` key will usually be ignored (unless chosen for another
role by another key elsewhere).
Sections other than ``[buildout]`` that are specified as parts always must
specify a ``recipe``: an identifier that determines what code should
process that section. You'll see a variety of recipes in Launchpad's
buildout.cfg, including ``z3c.recipe.filetemplate``, ``zc.recipe.egg``, and
others.
``versions.cfg``
As mentioned above, ``buildout.cfg`` extends ``versions.cfg`` by
specifying it in the ``extends`` key of the ``[buildout]`` section.
Versions.cfg specifies the precise versions of the dependencies we use.
This means that we can have several versions of a dependency available
locally, but we only build the precise one we specify. We give an
example of its use below. To read about the mechanism used, see the
zc.buildout documentation of the ``versions`` option in the ``[buildout]``
section.
``eggs``
The ``eggs`` directory holds the eggs built from the downloaded
distributions. Unless you set it up differently yourself, this directory is
shared by all your branches. This directory is local to your system--we do
not manage it in a branch. One reason for this is that eggs are often
platform-specific.
``download-cache``
The ``download-cache`` directory is a set of downloaded distributions--that
is, exact copies of the items that would typically be obtained from the
Python Package Index ("PyPI"), or another download source. We manage the
download cache as a shared resource across all of our developers with a bzr
branch in a Launchpad project called ``lp-source-dependencies``.
When we run buildout, Buildout reads a special key and value in the
``[buildout]`` section: ``install-from-cache = true``. This means that, by
default, Buildout will *not* use network access to find packages, but
*only* look in the download cache. This has many advantages.
- First, it helps us keep our deployment boxes from needing network access
out to PyPI and other download sites.
- Second, it makes the buildout much faster, because it does not have to
look out on the net for every dependency.
- Third, it makes the buildout more repeatable, because we are more
insulated from outages at download sites such as PyPI, and poor release
management.
- Fourth, it makes our deployments more auditable, because we can tell
exactly what we are deploying.
- Fifth, it gives us a single obvious place to put custom package
distributions, as we'll discuss below.
The downside is that adding and upgrading packages takes a small additional
step, as we'll see below.
``buildout-templates``
The last additional item in the checkout is the ``buildout-templates``
directory. This is used to hold templates that are used by the
section in buildout.cfg that uses the ``z3c.recipe.filetemplate`` recipe.
This can be used for many things, but we are using it as an alternate way
for producing scripts when the zc.recipe.egg approach is insufficient.
In addition to these seven listings, after you have run the Makefile (or
``bin/buildout``), you will see an additional listing:
``bin``
The ``bin`` directory has already been discussed many times. After running
the bootstrap.py, it holds the ``buildout`` script which can be used to
process Buildout configuration files. In Launchpad's case, after running
the buildout, it also holds many executables, including scripts to test
Launchpad; to run it; to run Python or IPython with Launchpad's sourcetree
and dependencies available; to run harness or iharness (with IPython) with
the sourcetree, dependencies, and database connections; or to perform
several other tasks. For now, the Makefile provides aliases for many of
these.
Now that you have an introduction to the pertinent files and directories, we'll
move on to trying to perform tasks in the buildout. We'll discuss adding a
dependency, upgrading a dependency, adding a script, adding an arbitrary file,
and working with unreleased packages.
Add a Package
=============
Let's suppose that we want to add the "lazr.foo" package as a dependency.
1. Add the new package to the ``setup.py`` file in the ``install_requires``
list.
Generally, our policy is to only set minimum version numbers in this file,
or none at all. It doesn't really matter for an application like
Launchpad, but it a good rule for library packages, so we follow it for
consistency. Therefore, we might simply add ``'lazr.foo'`` to
install_requires, or ``'lazr.foo >= 1.1'`` if we know that we are depending
on features introduced in version 1.1 of lazr.foo.
2. [OPTIONAL] If you know it, add the desired version number to versions.cfg
now.
For instance, if you know you want lazr.foo 1.1.2, add this line to the
``[versions]`` section of ``versions.cfg``::
lazr.foo = 1.1.2
3. [OPTIONAL] Add the desired distribution of lazr.foo 1.1.2 to the
``download-cache/dist`` directory.
4. Run the following command (or your variation)::
./bin/buildout -v buildout:install-from-cache=false | tee out.txt | grep 'Picked'
The first part (``./bin/buildout -v buildout:install-from-cache=false``)
will run buildout, allowing it to download source packages from the
Internet to ``download-cache/dist``. The second part (``tee out.txt``) will
dump the full output to the ``out.txt`` file in case you need to debug a
problem. The last part (``grep 'Picked'``) will filter the output so that
only additional packages--dependencies of your dependency--will be listed.
Look at the output. You need to see if it means that you have dependencies,
some of which may be indirect dependencies. Here's an imaginary example
output::
Picked: ipython = 0.9.1
Picked: lazr.foom = 1.4
Picked: zope.bar = 3.6.1
Picked: z3c.shazam = 2.0.1
At this time, note that the output will include at least one, and possibly
more, spurious "Picked:" listings. ipython, in particular, shows up
repeatedly.
In our example, other than the spurious ``ipython`` listing, this means
that these packages have also been added to your ``download-cache/dist``
directory. You also need to add those versions to the ``versions.cfg``
file::
lazr.foom = 1.4
zope.bar = 3.6.1
z3c.shazam = 2.0.1
5. Test.
[TODO] Note that you can tell ec2test to include all uncommitted
distributions from the local download-cache in its tests. If you do this,
you cannot use the ec2test feature to submit on test success. Also, if you
have uncommitted distributions and you do *not* explicitly tell ec2test to
include or ignore the uncommitted distributions, it will refuse to start
an instance.
6. Commit the changes (``cd download-cache``, ``bzr up``,
``bzr commit -m 'Add lazr.foom 1.1.2 and depdendencies to the download
cache'``) to the shared download cache when you are sure it is what you
want.
You should only add packages to the download-cache.
*Never* modify a package in the download-cache.
Only remove a package if you have explicit permission to do so.
Upgrade a Package
=================
Sometimes you need to upgrade a dependency. This may require additional
dependency additions or upgrades.
If you already know what version you want, the simplest thing to try is to
modify versions.cfg to specify the new version and run steps 4, 5, and 6 of the
`Add a Package`_ instructions.
If you don't know what version you want, but just want to see what happens when
you upgrade to the most recent revision, you can clear out the versions of the
packages for upgrade and give it a try (that is, run steps 4, 5, and 6 of the
`Add a Package`_ instructions). Note that, when not given an explicit version
number, our buildout is set to prefer final releases over alpha and beta
releases. If you want to temporarily override this behavior, include
``buildout:prefer-final=false`` as another argument to ``bin/buildout``.
Add a Script
============
We often need scripts that are run in a certain environment defined by Python
dependencies, and sometimes even different Python executables. Several of the
scripts we have are specified using the setuptools-based spelling that the
``zc.recipe.egg`` recipe supports.
For the common case, in ``setup.py``, add a string in the ``console_scripts``
list of the ``entry_points`` argument. Here's an example string::
'run = canonical.launchpad.scripts.runlaunchpad:start_launchpad'
This will create a script named ``run`` in the ``bin`` directory that calls the
``start_launchpad`` function in the
``canonical.launchpad.scripts.runlaunchpad`` module.
See the `zc.recipe.egg documentation`_ for more information on how to add
scripts using this method.
.. _`zc.recipe.egg documentation`: http://pypi.python.org/pypi/zc.recipe.egg
Add a File Modified By Buildout
===============================
Sometimes we need more control for the way our scripts are generated, or we
need other files processed during a buildout. Writing a custom zc.buildout
recipe is one way, but well out of the scope of this document. Read the
zc.buildout documentation for direction.
A much easier, and more limited approach is to use `z3c.recipe.filetemplate`_
to build the file. The recipe uses the ``buildout-templates`` directory,
which is a mirror of the Launchpad source tree. The recipe searches the tree
for files ending in '.in', performs variable substitution on them, and then be
copies them into the Launchpad source tree.
To add a file using the recipe, simply create mirrors of the source tree
directories that you need under ``buildout-templates/``, and create a .in file
template at the desired location. Take a look at
``buildout-templates/bin/`` for examples of what is possible.
.. _`z3c.recipe.filetemplate`: http://pypi.python.org/pypi/z3c.recipe.filetemplate
Work with Unreleased or Forked Packages
=======================================
Sometimes you need to work with unreleased or forked packages. FeedValidator_,
for instance, makes nightly zip releases but other than that only offers svn
access. Similarly, we may require a patched or unreleased version of a package
for some purpose. Hopefully, these situations will be rare, but they do occur.
While `other answers`_ are available for Buildout, our solution is to use the
download-cache. Basically, make a custom source distribution with a unique
suffix in the name, and use it (and its version name) for the normal process of
adding or updating a package, as described above. Because the custom package
is in the download-cache, it will be found and used.
Here's an example of making a custom distribution of FeedValidator.
FeedValidator is a Subversion project. We check it out::
svn co http://feedvalidator.googlecode.com/svn/trunk/feedvalidator/src feedvalidator
Next, we ``cd feedvalidator``, and, using a Python that has setuptools
installed, we run the following command::
python setup.py egg_info -r -bDEV sdist
For this example, imagine that the current revision of the repository is 1049.
Because setuptools has built-in Subversion support, the command above will
create a tar.gz in the ``dist`` directory named
``feedvalidator-0.0.0DEV-r1049.tar.gz``. The -r option specifies that the
subversion revision should be part of the package name. The -bDEV option
specifies that the 'DEV' suffix should be added to the version number.
We could then put the tar.gz file in Launchpad's ``download-cache/dist``
directory, specify ``feedvalidator = 0.0.0DEV-r1049`` in the ``versions.cfg``
file, and proceed with the usual steps to update or add a new package.
If you use a bzr branch, you might use the ``-d`` option instead of the ``-r``
option when you create the distribution. This will add the date instead of the
revision::
python setup.py egg_info -d -bDEV sdist
For instance, this might produce a distribution for the ``lazr.restful``
project with a name like this: ``lazr.restful-0.9.1DEV-20090512.tar.gz``.
See the setuptools documentation for more information about `the egg_info
command`_.
.. _FeedValidator: http://feedvalidator.org/
.. _`other answers`: http://pypi.python.org/pypi/zc.buildoutsftp
.. _`the egg_info command`: http://peak.telecommunity.com/DevCenter/setuptools#tagging-and-daily-build-or-snapshot-releases
=====================
Possible Future Goals
=====================
- No longer use system site-packages.
- No longer use make.
- Get rid of the sourcecode directory.
|