bzr-builder-0.7.3/.testr.conf 0000644 0000000 0000000 00000000224 11740346202 014153 0 ustar 0000000 0000000 [DEFAULT]
test_command=BZR_PLUGINS_AT=builder@`pwd` bzr selftest -s bp.builder --subunit
test_id_option=--load-list $IDFILE
test_list_option=--list
bzr-builder-0.7.3/COPYING 0000644 0000000 0000000 00000104513 11740346202 013126 0 ustar 0000000 0000000 GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
Copyright (C)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
Copyright (C)
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
.
bzr-builder-0.7.3/TODO 0000644 0000000 0000000 00000000427 11740346202 012562 0 ustar 0000000 0000000 - Documentation.
- Work out how to get tarballs for non-native packages if we want that.
- Decide on error on existing target directory vs. re-use and pull changes.
- Decide what should happen if you nest in to a directory that exists in
the branch that you are nesting in to.
bzr-builder-0.7.3/__init__.py 0000644 0000000 0000000 00000017714 11740346202 014212 0 ustar 0000000 0000000 # bzr-builder: a bzr plugin to constuct trees based on recipes
# Copyright 2009 Canonical Ltd.
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more details.
# You should have received a copy of the GNU General Public License along
# with this program. If not, see .
"""The bzr-builder plugin allows you to construct a branch from a 'recipe'.
The recipe is a series of pointers to branches and instructions for how they
should be combined. There are two ways to combine branches, by merging, and
by nesting, allowing much flexibility.
A recipe is just a text file that starts with a line such as::
# bzr-builder format 0.3 deb-version 1.0+{revno}-{revno:packaging}
The format specifier is there to allow the syntax to be changed in later
versions, and the meaning of "deb-version" will be explained later.
The next step is the define the base branch, this is the branch that will
be places at the root, e.g. just put::
lp:foo
to use the trunk of "foo" hosted on Launchpad.
Next comes any number of lines of other branches to be merged in, but using
a slightly different format. To merge a branch in to the base specify
something like::
merge packaging lp:~foo-dev/foo/packaging
which specifies we are merging a branch we will refer to as "packaging", which
can be found at the given URI. The name you give to the branch as the second
item doesn't have to match anything else, it's just an identifier specific
to the recipe.
If you wish to nest a branch then you use a similar line::
nest artwork lp:foo-images images
This specifies that we are nesting the branch at lp:foo-images, which we will
call "artwork", and we will place it locally in to the "images" directory.
You can then continue in this fashion for as many branches as you like. It
is also possible to nest and merge branches into nested branches. For example
to merge a branch in to the "artwork" branch we put the following on the line
below that one, indented by two spaces::
merge artwork-fixes lp:~bob/foo-images/fix-12345
which will merge Bob's fixes branch into the "artwork" branch which we nested
at "images".
It is also possible to specify a particular revision of a branch by appending
a revisionspec to the line. For instance::
nest docs lp:foo-docs doc tag:1.0
will nest the revision pointed to by the "1.0" tag of that branch. The format
for the revisionspec is identical to that taken by the "--revision" argument
to many bzr commands. See "bzr help revisionspec" for details.
You can also merge specific subdirectories from a branch with a "nest-part"
line like
nest-part packaging lp:~foo-dev/foo/packaging debian
which specifies that the only the debian/ subdirectory should be merged. This
works even if the branches share no revision history. You can optionally
specify the subdirectory and revision in the target with a line like
nest-part libfoo lp:libfoo src lib/foo tag:release-1.2
which will put the "src" directory of libfoo in "lib/foo", using the revision
of libfoo tagged "release-1.2".
It is also possible to run an arbitrary command at a particular point in the
construction process. For example::
run autoreconf -i
will run autotools at a particular point. Doing things with branches is usually
preferred, but sometimes it is the easier or only way to achieve something.
Note that you usually shouldn't rely on having general Internet access when
assembling the recipe, so commands that require it should be avoided.
You can then build this branch by running::
bzr build foo.recipe working-dir
(assuming you saved it as foo.recipe in your current directory).
Once the command finished it will have placed the result in "working-dir".
It is also possible to produce Debian source packages from a recipe, assuming
that one of the branches in the recipe contains some appropriate packaging.
You can do this using the "bzr dailydeb" command, which takes the same
arguments as "build". Only this time in the working dir you will find a source
package and a directory containing the code that the packages was built from
once it is done. Also take a look at the "--key-id" and "--dput" arguments to
have "bzr dailydeb" sign and upload the source package somewhere.
To build Debian source package that you desire you should make sure that
"deb-version" is set to an appropriate value on the first line of your
recipe. This will be used as the version number of the package. The
value you put there also allows for substitution of values in to it based
on various things when the recipe is processed:
* {time} will be substituted with the current date and time, such as
200908191512.
* {date} will be substituted with just the current date, such as
20090819.
* {revno} will be the revno of the base branch (the first specified).
* {revno:} will be substituted with the revno for the
branch named in the recipe.
* {debupstream}/{debupstream:} will be replaced by the upstream
portion of the version number taken from debian/changelog in the branch.
For example, if debian/changelog has a version number of "1.0-1" then this
would evaluate to "1.0".
* {debupstream-base}/{debupstream-base:} will be replaced by the
upstream portion of the version number taken from debian/changelog in the
branch, with any VCS markers stripped. For example, if debian/changelog
has a version number of "1.0~bzr43-1" then this would evaluate to "1.0~".
For any upstream versions without a VCS marker, a "+" is added to the
version ("1.0-1" becomes "1.0+").
* {debversion}/{debversion:} will be substituted with
the exact version string from debian/changelog in the branch.
* {revtime}/{revtime:} will be substituted with the date and
time of the revision that was built, such as 201108191512.
* {revdate}/{revdate:} will be substituted with the date
of the revision that was built, such as 20111222.
* {latest-tag}/{latest-tag:} will be replaced with the
name of the tag found on the most recent revision in the
branch mainline that has a tag.
* {git-commit}/{git-commit:} will be substituted with the last 7
characters of the SHA1 checksum of the revision that was built, if the
revision was imported from a Git repository.
* {svn-revno}/{svn-revno:} will be substituted with the
Subversion revision number of the revision that was built, if the
revision was imported from a Subversion repository.
Instruction syntax summary:
* nest NAME BRANCH TARGET-DIR [REVISION]
* merge NAME BRANCH [REVISION]
* nest-part NAME BRANCH SUBDIR [TARGET-DIR [REVISION]]
* run COMMAND
Format versions:
0.1 - original format.
0.2 - added "run" instruction.
0.3 - added "nest-part" instruction.
0.4 - made "deb-version" optional, added several new substitution variables.
{debupstream} now only looks for changelog in the root branch, not the
resulting tree
"""
from __future__ import absolute_import
from bzrlib.plugins.builder.info import (
bzr_plugin_version as version_info,
)
if version_info[3] == 'final':
version_string = '%d.%d.%d' % version_info[:3]
else:
version_string = '%d.%d.%d%s%d' % version_info
__version__ = version_string
from bzrlib.commands import plugin_cmds
plugin_cmds.register_lazy("cmd_build", [], "bzrlib.plugins.builder.cmds")
plugin_cmds.register_lazy("cmd_dailydeb", [], "bzrlib.plugins.builder.cmds")
def test_suite():
from unittest import TestSuite
from bzrlib.plugins.builder import tests
result = TestSuite()
result.addTest(tests.test_suite())
return result
bzr-builder-0.7.3/backports.py 0000644 0000000 0000000 00000026733 11740346202 014444 0 ustar 0000000 0000000 # bzr-builder: a bzr plugin to constuct trees based on recipes
# Copyright 2010 Canonical Ltd.
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more details.
# You should have received a copy of the GNU General Public License along
# with this program. If not, see .
"""Copies/backports features from more recent versions of packages
This allows bzr-builder to continue to work with older bzrlib and python-debian
versions while using features from newer versions.
"""
# NOTE FOR DEVELOPERS: with each backport please include a comment saying which
# version of what you are backporting from, to make it easier to copy bugfixes
# made in later version (and so that we know which things we can drop from this
# module if bzr-builder ever raises which package versions it depends on).
import os
import pwd
import re
import socket
from bzrlib.merge import (
Merger,
Merge3Merger,
)
from bzrlib import (
errors,
generate_ids,
osutils,
revision as _mod_revision,
transform,
ui,
)
# Backport of bzrlib.merge.MergeIntoMerger, introduced in bzr 2.2rc1.
# (Also backports PathNotInTree, _MergeTypeParameterizer, MergeIntoMergeType)
class PathNotInTree(errors.BzrError):
_fmt = """Merge-into failed because %(tree)s does not contain %(path)s."""
def __init__(self, path, tree):
errors.BzrError.__init__(self, path=path, tree=tree)
class MergeIntoMerger(Merger):
"""Merger that understands other_tree will be merged into a subdir.
This also changes the Merger api so that it uses real Branch, revision_id,
and RevisonTree objects, rather than using revision specs.
"""
def __init__(self, this_tree, other_branch, other_tree, target_subdir,
source_subpath, other_rev_id=None):
"""Create a new MergeIntoMerger object.
source_subpath in other_tree will be effectively copied to
target_subdir in this_tree.
:param this_tree: The tree that we will be merging into.
:param other_branch: The Branch we will be merging from.
:param other_tree: The RevisionTree object we want to merge.
:param target_subdir: The relative path where we want to merge
other_tree into this_tree
:param source_subpath: The relative path specifying the subtree of
other_tree to merge into this_tree.
"""
# It is assumed that we are merging a tree that is not in our current
# ancestry, which means we are using the "EmptyTree" as our basis.
null_ancestor_tree = this_tree.branch.repository.revision_tree(
_mod_revision.NULL_REVISION)
super(MergeIntoMerger, self).__init__(
this_branch=this_tree.branch,
this_tree=this_tree,
other_tree=other_tree,
base_tree=null_ancestor_tree,
)
self._target_subdir = target_subdir
self._source_subpath = source_subpath
self.other_branch = other_branch
if other_rev_id is None:
other_rev_id = other_tree.get_revision_id()
self.other_rev_id = self.other_basis = other_rev_id
self.base_is_ancestor = True
self.backup_files = True
self.merge_type = Merge3Merger
self.show_base = False
self.reprocess = False
self.interesting_ids = None
self.merge_type = _MergeTypeParameterizer(MergeIntoMergeType,
target_subdir=self._target_subdir,
source_subpath=self._source_subpath)
if self._source_subpath != '':
# If this isn't a partial merge make sure the revisions will be
# present.
self._maybe_fetch(self.other_branch, self.this_branch,
self.other_basis)
def set_pending(self):
if self._source_subpath != '':
return
Merger.set_pending(self)
class _MergeTypeParameterizer(object):
"""Wrap a merge-type class to provide extra parameters.
This is hack used by MergeIntoMerger to pass some extra parameters to its
merge_type. Merger.do_merge() sets up its own set of parameters to pass to
the 'merge_type' member. It is difficult override do_merge without
re-writing the whole thing, so instead we create a wrapper which will pass
the extra parameters.
"""
def __init__(self, merge_type, **kwargs):
self._extra_kwargs = kwargs
self._merge_type = merge_type
def __call__(self, *args, **kwargs):
kwargs.update(self._extra_kwargs)
return self._merge_type(*args, **kwargs)
def __getattr__(self, name):
return getattr(self._merge_type, name)
class MergeIntoMergeType(Merge3Merger):
"""Merger that incorporates a tree (or part of a tree) into another."""
# Backport note: the definition of _finish_computing_transform is copied
# from Merge3Merger in bzr 2.2 (it is supposed to be inherited from
# Merge3Merger, but was only introduced in 2.2).
def _finish_computing_transform(self):
"""Finalize the transform and report the changes.
This is the second half of _compute_transform.
"""
child_pb = ui.ui_factory.nested_progress_bar()
try:
fs_conflicts = transform.resolve_conflicts(self.tt, child_pb,
lambda t, c: transform.conflict_pass(t, c, self.other_tree))
finally:
child_pb.finished()
if self.change_reporter is not None:
from bzrlib import delta
delta.report_changes(
self.tt.iter_changes(), self.change_reporter)
self.cook_conflicts(fs_conflicts)
from bzrlib import trace
for conflict in self.cooked_conflicts:
trace.warning(conflict)
def __init__(self, *args, **kwargs):
"""Initialize the merger object.
:param args: See Merge3Merger.__init__'s args.
:param kwargs: See Merge3Merger.__init__'s keyword args, except for
source_subpath and target_subdir.
:keyword source_subpath: The relative path specifying the subtree of
other_tree to merge into this_tree.
:keyword target_subdir: The relative path where we want to merge
other_tree into this_tree
"""
# All of the interesting work happens during Merge3Merger.__init__(),
# so we have have to hack in to get our extra parameters set.
self._source_subpath = kwargs.pop('source_subpath')
self._target_subdir = kwargs.pop('target_subdir')
super(MergeIntoMergeType, self).__init__(*args, **kwargs)
def _compute_transform(self):
child_pb = ui.ui_factory.nested_progress_bar()
try:
entries = self._entries_to_incorporate()
entries = list(entries)
for num, (entry, parent_id) in enumerate(entries):
child_pb.update('Preparing file merge', num, len(entries))
parent_trans_id = self.tt.trans_id_file_id(parent_id)
trans_id = transform.new_by_entry(self.tt, entry,
parent_trans_id, self.other_tree)
finally:
child_pb.finished()
self._finish_computing_transform()
def _entries_to_incorporate(self):
"""Yields pairs of (inventory_entry, new_parent)."""
other_inv = self.other_tree.inventory
subdir_id = other_inv.path2id(self._source_subpath)
if subdir_id is None:
# XXX: The error would be clearer if it gave the URL of the source
# branch, but we don't have a reference to that here.
raise PathNotInTree(self._source_subpath, "Source tree")
subdir = other_inv[subdir_id]
parent_in_target = osutils.dirname(self._target_subdir)
target_id = self.this_tree.inventory.path2id(parent_in_target)
if target_id is None:
raise PathNotInTree(self._target_subdir, "Target tree")
name_in_target = osutils.basename(self._target_subdir)
merge_into_root = subdir.copy()
merge_into_root.name = name_in_target
if merge_into_root.file_id in self.this_tree.inventory:
# Give the root a new file-id.
# This can happen fairly easily if the directory we are
# incorporating is the root, and both trees have 'TREE_ROOT' as
# their root_id. Users will expect this to Just Work, so we
# change the file-id here.
# Non-root file-ids could potentially conflict too. That's really
# an edge case, so we don't do anything special for those. We let
# them cause conflicts.
merge_into_root.file_id = generate_ids.gen_file_id(name_in_target)
yield (merge_into_root, target_id)
if subdir.kind != 'directory':
# No children, so we are done.
return
for ignored_path, entry in other_inv.iter_entries_by_dir(subdir_id):
parent_id = entry.parent_id
if parent_id == subdir.file_id:
# The root's parent ID has changed, so make sure children of
# the root refer to the new ID.
parent_id = merge_into_root.file_id
yield (entry, parent_id)
def get_maintainer():
"""Create maintainer string using the same algorithm as in dch.
From version 0.1.19 python-debian has this function in debian.changelog
"""
env = os.environ
regex = re.compile(r"^(.*)\s+<(.*)>$")
# Split email and name
if 'DEBEMAIL' in env:
match_obj = regex.match(env['DEBEMAIL'])
if match_obj:
if not 'DEBFULLNAME' in env:
env['DEBFULLNAME'] = match_obj.group(1)
env['DEBEMAIL'] = match_obj.group(2)
if 'DEBEMAIL' not in env or 'DEBFULLNAME' not in env:
if 'EMAIL' in env:
match_obj = regex.match(env['EMAIL'])
if match_obj:
if not 'DEBFULLNAME' in env:
env['DEBFULLNAME'] = match_obj.group(1)
env['EMAIL'] = match_obj.group(2)
# Get maintainer's name
if 'DEBFULLNAME' in env:
maintainer = env['DEBFULLNAME']
elif 'NAME' in env:
maintainer = env['NAME']
else:
# Use password database if no data in environment variables
try:
maintainer = re.sub(r',.*', '', pwd.getpwuid(os.getuid()).pw_gecos)
except (KeyError, AttributeError):
# TBD: Use last changelog entry value
maintainer = "bzr-builder"
# Get maintainer's mail address
if 'DEBEMAIL' in env:
email = env['DEBEMAIL']
elif 'EMAIL' in env:
email = env['EMAIL']
else:
addr = None
if os.path.exists('/etc/mailname'):
f = open('/etc/mailname')
try:
addr = f.readline().strip()
finally:
f.close()
if not addr:
addr = socket.getfqdn()
if addr:
user = pwd.getpwuid(os.getuid()).pw_name
if not user:
addr = None
else:
addr = "%s@%s" % (user, addr)
if addr:
email = addr
else:
# TBD: Use last changelog entry value
email = "none@example.org"
return (maintainer, email)
bzr-builder-0.7.3/cmds.py 0000644 0000000 0000000 00000043370 11740346202 013376 0 ustar 0000000 0000000 # bzr-builder: a bzr plugin to constuct trees based on recipes
# Copyright 2009 Canonical Ltd.
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more details.
# You should have received a copy of the GNU General Public License along
# with this program. If not, see .
"""Subcommands provided by bzr-builder."""
from StringIO import StringIO
import datetime
import os
import shutil
import tempfile
from bzrlib import (
errors,
lazy_regex,
trace,
transport as _mod_transport,
urlutils,
)
from bzrlib.branch import Branch
from bzrlib.commands import Command
from bzrlib.option import Option
from bzrlib.plugins.builder.recipe import (
BaseRecipeBranch,
build_tree,
RecipeParser,
resolve_revisions,
SAFE_INSTRUCTIONS,
)
def write_manifest_to_transport(location, base_branch,
possible_transports=None):
"""Write a manifest to disk.
:param location: Location to write to
:param base_branch: Recipe base branch
"""
child_transport = _mod_transport.get_transport(location,
possible_transports=possible_transports)
base_transport = child_transport.clone('..')
base_transport.create_prefix()
basename = base_transport.relpath(child_transport.base)
base_transport.put_bytes(basename, str(base_branch))
def get_branch_from_recipe_location(recipe_location, safe=False,
possible_transports=None):
"""Return the base branch for the specified recipe.
:param recipe_location: The URL of the recipe file to retrieve.
:param safe: if True, reject recipes that would cause arbitrary code
execution.
"""
if safe:
permitted_instructions = SAFE_INSTRUCTIONS
else:
permitted_instructions = None
try:
(basename, f) = get_recipe_from_location(recipe_location, possible_transports)
except errors.NoSuchFile:
raise errors.BzrCommandError("Specified recipe does not exist: "
"%s" % recipe_location)
try:
parser = RecipeParser(f, filename=recipe_location)
finally:
f.close()
return parser.parse(permitted_instructions=permitted_instructions)
def get_branch_from_branch_location(branch_location, possible_transports=None,
revspec=None):
"""Return the base branch for the branch location.
:param branch_location: The URL of the branch to retrieve.
"""
# Make sure it's actually a branch
Branch.open(branch_location)
return BaseRecipeBranch(branch_location, None,
RecipeParser.NEWEST_VERSION, revspec=revspec)
def get_old_recipe(if_changed_from, possible_transports=None):
try:
(basename, f) = get_recipe_from_location(if_changed_from, possible_transports)
except errors.NoSuchFile:
return None
try:
old_recipe = RecipeParser(f,
filename=if_changed_from).parse()
finally:
f.close()
return old_recipe
launchpad_recipe_re = lazy_regex.lazy_compile(
r'^https://code.launchpad.net/~(.*)/\+recipe/(.*)$')
def get_recipe_from_launchpad(username, recipe_name, location):
"""Load a recipe from Launchpad.
:param username: The launchpad user name
:param recipe_name: Recipe name
:param location: Original location (used for error reporting)
:return: Text of the recipe
"""
from launchpadlib.launchpad import Launchpad
lp = Launchpad.login_with("bzr-builder", "production")
try:
person = lp.people[username]
except KeyError:
raise errors.NoSuchFile(location,
"No such Launchpad user %s" % username)
recipe = person.getRecipe(name=recipe_name)
if recipe is None:
raise errors.NoSuchFile(location,
"Launchpad user %s has no recipe %s" % (
username, recipe_name))
return recipe.recipe_text
def get_recipe_from_location(location, possible_transports=None):
"""Open a recipe as a file-like object from a URL.
:param location: The recipe location
:param possible_transports: Possible transports to use
:return: Tuple with basename and file-like object
"""
m = launchpad_recipe_re.match(location)
if m:
(username, recipe_name) = m.groups()
text = get_recipe_from_launchpad(username, recipe_name,
location)
return (recipe_name, StringIO(text))
child_transport = _mod_transport.get_transport(location,
possible_transports=possible_transports)
recipe_transport = child_transport.clone('..')
basename = recipe_transport.relpath(child_transport.base)
return basename, recipe_transport.get(basename)
def get_prepared_branch_from_location(location,
safe=False, possible_transports=None,
revspec=None):
"""Common code to prepare a branch and do substitutions.
:param location: a path to a recipe file or branch to work from.
:param if_changed_from: an optional location of a manifest to
compare the recipe against.
:param safe: if True, reject recipes that would cause arbitrary code
execution.
:return: A tuple with (retcode, base_branch). If retcode is None
then the command execution should continue.
"""
try:
base_branch = get_branch_from_recipe_location(location, safe=safe,
possible_transports=possible_transports)
except (_mod_transport.LateReadError, errors.ReadError):
# Presume unable to read means location is a directory rather than a file
base_branch = get_branch_from_branch_location(location,
possible_transports=possible_transports)
else:
if revspec is not None:
raise errors.BzrCommandError("--revision only supported when "
"building from branch")
return base_branch
class cmd_build(Command):
"""Build a tree based on a branch or a recipe.
Pass the path of a recipe file or a branch to build and the directory to
work in.
See "bzr help builder" for more information on what a recipe is.
"""
takes_args = ["location", "working_directory"]
takes_options = [
Option('manifest', type=str, argname="path",
help="Path to write the manifest to."),
Option('if-changed-from', type=str, argname="path",
help="Only build if the outcome would be different "
"to that specified in the specified manifest."),
'revision',
]
def run(self, location, working_directory, manifest=None,
if_changed_from=None, revision=None):
if revision is not None and len(revision) > 0:
if len(revision) != 1:
raise errors.BzrCommandError("only a single revision can be "
"specified")
revspec = revision[0]
else:
revspec = None
possible_transports = []
base_branch = get_prepared_branch_from_location(location,
possible_transports=possible_transports, revspec=revspec)
if if_changed_from is not None:
old_recipe = get_old_recipe(if_changed_from, possible_transports)
else:
old_recipe = None
changed = resolve_revisions(base_branch, if_changed_from=old_recipe)
if not changed:
trace.note("Unchanged")
return 0
manifest_path = manifest or os.path.join(working_directory,
"bzr-builder.manifest")
build_tree(base_branch, working_directory)
write_manifest_to_transport(manifest_path, base_branch,
possible_transports)
class cmd_dailydeb(Command):
"""Build a deb based on a 'recipe' or from a branch.
See "bzr help builder" for more information on what a recipe is.
If you do not specify a working directory then a temporary
directory will be used and it will be removed when the command
finishes.
"""
takes_options = cmd_build.takes_options + [
Option("package", type=str,
help="The package name to use in the changelog entry. "
"If not specified then the package from the "
"previous changelog entry will be used, so it "
"must be specified if there is no changelog."),
Option("distribution", type=str,
help="The distribution to target. If not specified "
"then the same distribution as the last entry "
"in debian/changelog will be used."),
Option("dput", type=str, argname="target",
help="dput the built package to the specified "
"dput target."),
Option("key-id", type=str, short_name="k",
help="Sign the packages with the specified GnuPG key. "
"Must be specified if you use --dput."),
Option("no-build",
help="Just ready the source package and don't "
"actually build it."),
Option("watch-ppa", help="Watch the PPA the package was "
"dput to and exit with 0 only if it builds and "
"publishes successfully."),
Option("append-version", type=str, help="Append the "
"specified string to the end of the version used "
"in debian/changelog."),
Option("safe", help="Error if the recipe would cause"
" arbitrary code execution."),
Option("allow-fallback-to-native",
help="Allow falling back to a native package if the upstream "
"tarball can not be found."),
]
takes_args = ["location", "working_basedir?"]
def run(self, location, working_basedir=None, manifest=None,
if_changed_from=None, package=None, distribution=None,
dput=None, key_id=None, no_build=None, watch_ppa=False,
append_version=None, safe=False, allow_fallback_to_native=False):
try:
try:
import debian
except ImportError:
# In older versions of python-debian the main package was named
# debian_bundle
import debian_bundle
except ImportError:
raise errors.BzrCommandError("The 'debian' python module "
"is required for 'bzr dailydeb'. Install the "
"python-debian package.")
from bzrlib.plugins.builder.deb_util import (
add_autobuild_changelog_entry,
build_source_package,
calculate_package_dir,
changelog,
debian_source_package_name,
dput_source_package,
extract_upstream_tarball,
force_native_format,
get_source_format,
sign_source_package,
target_from_dput,
)
from bzrlib.plugins.builder.deb_version import (
check_expanded_deb_version,
substitute_branch_vars,
substitute_time,
)
if dput is not None and key_id is None:
raise errors.BzrCommandError("You must specify --key-id if you "
"specify --dput.")
if watch_ppa:
if not dput:
raise errors.BzrCommandError(
"cannot watch a ppa without doing dput.")
else:
# Check we can calculate a PPA url.
target_from_dput(dput)
possible_transports = []
base_branch = get_prepared_branch_from_location(location, safe=safe,
possible_transports=possible_transports)
# Save the unsubstituted version
template_version = base_branch.deb_version
if if_changed_from is not None:
old_recipe = get_old_recipe(if_changed_from, possible_transports)
else:
old_recipe = None
if base_branch.deb_version is not None:
time = datetime.datetime.utcnow()
substitute_time(base_branch, time)
changed = resolve_revisions(base_branch, if_changed_from=old_recipe,
substitute_branch_vars=substitute_branch_vars)
check_expanded_deb_version(base_branch)
else:
changed = resolve_revisions(base_branch, if_changed_from=old_recipe)
if not changed:
trace.note("Unchanged")
return 0
if working_basedir is None:
temp_dir = tempfile.mkdtemp(prefix="bzr-builder-")
working_basedir = temp_dir
else:
temp_dir = None
if not os.path.exists(working_basedir):
os.makedirs(working_basedir)
package_name = self._calculate_package_name(location, package)
if template_version is None:
working_directory = os.path.join(working_basedir,
"%s-direct" % (package_name,))
else:
working_directory = os.path.join(working_basedir,
"%s-%s" % (package_name, template_version))
try:
# we want to use a consistent package_dir always to support
# updates in place, but debuild etc want PACKAGE-UPSTREAMVERSION
# on disk, so we build_tree with the unsubstituted version number
# and do a final rename-to step before calling into debian build
# tools. We then rename the working dir back.
manifest_path = os.path.join(working_directory, "debian",
"bzr-builder.manifest")
build_tree(base_branch, working_directory)
control_path = os.path.join(working_directory, "debian", "control")
if not os.path.exists(control_path):
if package is None:
raise errors.BzrCommandError("No control file to "
"take the package name from, and --package not "
"specified.")
else:
package = debian_source_package_name(control_path)
write_manifest_to_transport(manifest_path, base_branch,
possible_transports)
autobuild = (base_branch.deb_version is not None)
if autobuild:
# Add changelog also substitutes {debupstream}.
add_autobuild_changelog_entry(base_branch, working_directory,
package, distribution=distribution,
append_version=append_version)
else:
if append_version:
raise errors.BzrCommandError("--append-version only "
"supported for autobuild recipes (with a 'deb-version' "
"header)")
with open(os.path.join(working_directory, "debian", "changelog")) as cl_f:
contents = cl_f.read()
cl = changelog.Changelog(file=contents)
package_name = cl.package
package_version = cl.version
package_dir = calculate_package_dir(package_name, package_version,
working_basedir)
# working_directory -> package_dir: after this debian stuff works.
os.rename(working_directory, package_dir)
try:
current_format = get_source_format(package_dir)
if (package_version.debian_version is not None or
current_format == "3.0 (quilt)"):
# Non-native package
try:
extract_upstream_tarball(base_branch.branch, package_name,
package_version.upstream_version, working_basedir)
except errors.NoSuchTag, e:
if not allow_fallback_to_native:
raise errors.BzrCommandError(
"Unable to find the upstream source. Import it "
"as tag %s or build with "
"--allow-fallback-to-native." % e.tag_name)
else:
force_native_format(package_dir, current_format)
if not no_build:
build_source_package(package_dir,
tgz_check=not allow_fallback_to_native)
if key_id is not None:
sign_source_package(package_dir, key_id)
if dput is not None:
dput_source_package(package_dir, dput)
finally:
# package_dir -> working_directory
# FIXME: may fail in error unwind, masking the original exception.
os.rename(package_dir, working_directory)
# Note that this may write a second manifest.
if manifest is not None:
write_manifest_to_transport(manifest, base_branch,
possible_transports)
finally:
if temp_dir is not None:
shutil.rmtree(temp_dir)
if watch_ppa:
from bzrlib.plugins.builder.ppa import watch
(owner, archive) = target_from_dput(dput)
if not watch(owner, archive, package_name, base_branch.deb_version):
return 2
def _calculate_package_name(self, recipe_location, package):
"""Calculate the directory name that should be used while debuilding."""
recipe_name = urlutils.basename(recipe_location)
if recipe_name.endswith(".recipe"):
recipe_name = recipe_name[:-len(".recipe")]
return package or recipe_name
bzr-builder-0.7.3/deb_util.py 0000644 0000000 0000000 00000032145 11740346202 014235 0 ustar 0000000 0000000 # bzr-builder: a bzr plugin to constuct trees based on recipes
# Copyright 2009-2011 Canonical Ltd.
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more details.
# You should have received a copy of the GNU General Public License along
# with this program. If not, see .
"""Debian-specific utility functions."""
from base64 import standard_b64decode
from email import utils
import errno
import os
import shutil
import signal
import subprocess
from bzrlib import (
errors,
export as _mod_export,
osutils,
trace,
)
from bzrlib.plugins.builder.deb_version import substitute_changelog_vars
from bzrlib.plugins.builder.recipe import (
SubstitutionUnavailable,
)
try:
from debian import changelog, deb822
except ImportError:
# In older versions of python-debian the main package was named
# debian_bundle
from debian_bundle import changelog, deb822
try:
get_maintainer = changelog.get_maintainer
except AttributeError:
# Implementation of get_maintainer was added after 0.1.18 so import same
# function from backports module if python-debian doesn't have it.
from bzrlib.plugins.builder.backports import get_maintainer
# The default distribution used by add_autobuild_changelog_entry()
DEFAULT_UBUNTU_DISTRIBUTION = "lucid"
class MissingDependency(errors.BzrError):
pass
def target_from_dput(dput):
"""Convert a dput specification to a LP API specification.
:param dput: A dput command spec like ppa:team-name.
:return: A LP API target like team-name/ppa.
"""
ppa_prefix = 'ppa:'
if not dput.startswith(ppa_prefix):
raise errors.BzrCommandError('%r does not appear to be a PPA. '
'A dput target like \'%suser[/name]\' must be used.'
% (dput, ppa_prefix))
base, _, suffix = dput[len(ppa_prefix):].partition('/')
if not suffix:
suffix = 'ppa'
return base, suffix
def debian_source_package_name(control_path):
"""Open a debian control file and extract the package name.
"""
f = open(control_path, 'r')
try:
control = deb822.Deb822(f)
# Debian policy states package names are [a-z0-9][a-z0-9.+-]+ so ascii
return control["Source"].encode("ascii")
finally:
f.close()
def reconstruct_pristine_tar(dest, delta, dest_filename):
"""Reconstruct a pristine tarball from a directory and a delta.
:param dest: Directory to pack
:param delta: pristine-tar delta
:param dest_filename: Destination filename
"""
command = ["pristine-tar", "gentar", "-",
os.path.abspath(dest_filename)]
_run_command(command, dest,
"Reconstructing pristine tarball",
"Generating tar from delta failed",
not_installed_msg="pristine-tar is not installed",
indata=delta)
def extract_upstream_tarball(branch, package, version, dest_dir):
"""Extract the upstream tarball from a branch.
:param branch: Branch with the upstream pristine tar data
:param package: Package name
:param version: Package version
:param dest_dir: Destination directory
"""
tag_names = ["upstream-%s" % version, "upstream/%s" % version]
for tag_name in tag_names:
try:
revid = branch.tags.lookup_tag(tag_name)
except errors.NoSuchTag:
pass
else:
break
else:
raise errors.NoSuchTag(tag_names[0])
tree = branch.repository.revision_tree(revid)
rev = branch.repository.get_revision(revid)
if 'deb-pristine-delta' in rev.properties:
uuencoded = rev.properties['deb-pristine-delta']
dest_filename = "%s_%s.orig.tar.gz" % (package, version)
elif 'deb-pristine-delta-bz2' in rev.properties:
uuencoded = rev.properties['deb-pristine-delta-bz2']
dest_filename = "%s_%s.orig.tar.bz2" % (package, version)
else:
uuencoded = None
if uuencoded is not None:
delta = standard_b64decode(uuencoded)
dest = os.path.join(dest_dir, "orig")
try:
_mod_export.export(tree, dest, format='dir')
reconstruct_pristine_tar(dest, delta,
os.path.join(dest_dir, dest_filename))
finally:
if os.path.exists(dest):
shutil.rmtree(dest)
else:
# Default to .tar.gz
dest_filename = "%s_%s.orig.tar.gz" % (package, version)
_mod_export.export(tree, os.path.join(dest_dir, dest_filename),
per_file_timestamps=True)
def add_autobuild_changelog_entry(base_branch, basedir, package,
distribution=None, author_name=None, author_email=None,
append_version=None):
"""Add a new changelog entry for an autobuild.
:param base_branch: Recipe base branch
:param basedir: Base working directory
:param package: package name
:param distribution: Optional distribution (defaults to last entry
distribution)
:param author_name: Name of the build requester
:param author_email: Email of the build requester
:param append_version: Optional version suffix to add
"""
debian_dir = os.path.join(basedir, "debian")
if not os.path.exists(debian_dir):
os.makedirs(debian_dir)
cl_path = os.path.join(debian_dir, "changelog")
file_found = False
if os.path.exists(cl_path):
file_found = True
cl_f = open(cl_path)
try:
contents = cl_f.read()
finally:
cl_f.close()
cl = changelog.Changelog(file=contents)
else:
cl = changelog.Changelog()
if len(cl._blocks) > 0:
if distribution is None:
distribution = cl._blocks[0].distributions.split()[0]
else:
if file_found:
if len(contents.strip()) > 0:
reason = ("debian/changelog didn't contain any "
"parseable stanzas")
else:
reason = "debian/changelog was empty"
else:
reason = "debian/changelog was not present"
if distribution is None:
distribution = DEFAULT_UBUNTU_DISTRIBUTION
if base_branch.format in (0.1, 0.2, 0.3):
try:
substitute_changelog_vars(base_branch, None, cl)
except SubstitutionUnavailable, e:
raise errors.BzrCommandError("No previous changelog to "
"take the upstream version from as %s was "
"used: %s: %s." % (e.name, e.reason, reason))
# Use debian packaging environment variables
# or default values if they don't exist
if author_name is None or author_email is None:
author_name, author_email = get_maintainer()
# The python-debian package breaks compatibility at version 0.1.20 by
# switching to expecting (but not checking for) unicode rather than
# bytestring inputs. Detect this and decode environment if needed.
if getattr(changelog.Changelog, "__unicode__", None) is not None:
enc = osutils.get_user_encoding()
author_name = author_name.decode(enc)
author_email = author_email.decode(enc)
author = "%s <%s>" % (author_name, author_email)
date = utils.formatdate(localtime=True)
version = base_branch.deb_version
if append_version is not None:
version += append_version
try:
changelog.Version(version)
except (changelog.VersionError, ValueError), e:
raise errors.BzrCommandError("Invalid deb-version: %s: %s"
% (version, e))
cl.new_block(package=package, version=version,
distributions=distribution, urgency="low",
changes=['', ' * Auto build.', ''],
author=author, date=date)
cl_f = open(cl_path, 'wb')
try:
cl.write_to_open_file(cl_f)
finally:
cl_f.close()
def calculate_package_dir(package_name, package_version, working_basedir):
"""Calculate the directory name that should be used while debuilding.
:param base_branch: Recipe base branch
:param package_version: Version of the package
:param package_name: Package name
:param working_basedir: Base directory
"""
package_basedir = "%s-%s" % (package_name, package_version.upstream_version)
package_dir = os.path.join(working_basedir, package_basedir)
return package_dir
def _run_command(command, basedir, msg, error_msg,
not_installed_msg=None, env=None, success_exit_codes=None, indata=None):
""" Run a command in a subprocess.
:param command: list with command and parameters
:param msg: message to display to the user
:param error_msg: message to display if something fails.
:param not_installed_msg: the message to display if the command
isn't available.
:param env: Optional environment to use rather than os.environ.
:param success_exit_codes: Exit codes to consider succesfull, defaults to [0].
:param indata: Data to write to standard input
"""
def subprocess_setup():
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
trace.note(msg)
# Hide output if -q is in use.
quiet = trace.is_quiet()
if quiet:
kwargs = {"stderr": subprocess.STDOUT, "stdout": subprocess.PIPE}
else:
kwargs = {}
if env is not None:
kwargs["env"] = env
trace.mutter("running: %r", command)
try:
proc = subprocess.Popen(command, cwd=basedir,
stdin=subprocess.PIPE, preexec_fn=subprocess_setup, **kwargs)
except OSError, e:
if e.errno != errno.ENOENT:
raise
if not_installed_msg is None:
raise
raise MissingDependency(msg=not_installed_msg)
output = proc.communicate(indata)
if success_exit_codes is None:
success_exit_codes = [0]
if proc.returncode not in success_exit_codes:
if quiet:
raise errors.BzrCommandError("%s: %s" % (error_msg, output))
else:
raise errors.BzrCommandError(error_msg)
def build_source_package(basedir, tgz_check=True):
command = ["/usr/bin/debuild"]
if tgz_check:
command.append("--tgz-check")
else:
command.append("--no-tgz-check")
command.extend(["-i", "-I", "-S", "-uc", "-us"])
_run_command(command, basedir,
"Building the source package",
"Failed to build the source package",
not_installed_msg="debuild is not installed, please install "
"the devscripts package.")
def get_source_format(path):
"""Retrieve the source format name from a package.
:param path: Path to the package
:return: String with package format
"""
source_format_path = os.path.join(path, "debian", "source", "format")
if not os.path.exists(source_format_path):
return "1.0"
f = open(source_format_path, 'r')
try:
return f.read().strip()
finally:
f.close()
def convert_3_0_quilt_to_native(path):
"""Convert a package in 3.0 (quilt) format to 3.0 (native).
This applies all patches in the package and updates the
debian/source/format file.
:param path: Path to the package on disk
"""
path = os.path.abspath(path)
patches_dir = os.path.join(path, "debian", "patches")
series_file = os.path.join(patches_dir, "series")
if os.path.exists(series_file):
_run_command(["quilt", "push", "-a", "-v"], path,
"Applying quilt patches",
"Failed to apply quilt patches",
not_installed_msg="quilt is not installed, please install it.",
env={"QUILT_SERIES": series_file, "QUILT_PATCHES": patches_dir},
success_exit_codes=(0, 2))
if os.path.exists(patches_dir):
shutil.rmtree(patches_dir)
f = open(os.path.join(path, "debian", "source", "format"), 'w')
try:
f.write("3.0 (native)\n")
finally:
f.close()
def force_native_format(working_tree_path, current_format):
"""Make sure a package is a format that supports native packages.
:param working_tree_path: Path to the package
"""
if current_format == "3.0 (quilt)":
convert_3_0_quilt_to_native(working_tree_path)
elif current_format not in ("1.0", "3.0 (native)"):
raise errors.BzrCommandError("Unknown source format %s" %
current_format)
def sign_source_package(basedir, key_id):
command = ["/usr/bin/debsign", "-S", "-k%s" % key_id]
_run_command(command, basedir,
"Signing the source package",
"Signing the package failed",
not_installed_msg="debsign is not installed, please install "
"the devscripts package.")
def dput_source_package(basedir, target):
command = ["/usr/bin/debrelease", "-S", "--dput", target]
_run_command(command, basedir,
"Uploading the source package",
"Uploading the package failed",
not_installed_msg="debrelease is not installed, please "
"install the devscripts package.")
bzr-builder-0.7.3/deb_version.py 0000644 0000000 0000000 00000017546 11740346202 014755 0 ustar 0000000 0000000 # bzr-builder: a bzr plugin to constuct trees based on recipes
# Copyright 2009-2011 Canonical Ltd.
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more details.
# You should have received a copy of the GNU General Public License along
# with this program. If not, see .
from bzrlib import (
errors,
lazy_regex,
)
from bzrlib.plugins.builder.recipe import (
BranchSubstitutionVariable,
DateVariable,
GitCommitVariable,
LatestTagVariable,
RevdateVariable,
RevtimeVariable,
RevnoVariable,
SubstitutionUnavailable,
SubversionRevnumVariable,
TimeVariable,
branch_vars,
simple_vars,
)
try:
from debian import changelog
except ImportError:
# In older versions of python-debian the main package was named
# debian_bundle
from debian_bundle import changelog
class DebUpstreamVariable(BranchSubstitutionVariable):
basename = "debupstream"
minimum_format = 0.1
def __init__(self, branch_name, version):
super(DebUpstreamVariable, self).__init__(branch_name)
self._version = version
@classmethod
def from_changelog(cls, branch_name, changelog):
if len(changelog._blocks) > 0:
return cls(branch_name, changelog._blocks[0].version)
else:
return cls(branch_name, None)
def get(self):
if self._version is None:
raise SubstitutionUnavailable(self.name,
"No previous changelog to take the upstream version from")
# Should we include the epoch?
return self._version.upstream_version
class DebVersionVariable(BranchSubstitutionVariable):
basename = "debversion"
minimum_format = 0.4
def __init__(self, branch_name, version):
super(DebVersionVariable, self).__init__(branch_name)
self._version = version
@classmethod
def from_changelog(cls, branch_name, changelog):
if len(changelog._blocks) > 0:
return cls(branch_name, changelog._blocks[0].version)
else:
return cls(branch_name, None)
def get(self):
if self._version is None:
raise SubstitutionUnavailable(self.name,
"No previous changelog to take the version from")
return str(self._version)
dfsg_regex = lazy_regex.lazy_compile(
r'[+.]*dfsg[.]*[0-9]+')
version_regex = lazy_regex.lazy_compile(
r'([~+])(svn[0-9]+|bzr[0-9]+|git[0-9a-f]+)')
def version_extract_base(version):
version = dfsg_regex.sub("", version)
return version_regex.sub("\\1", version)
class DebUpstreamBaseVariable(DebUpstreamVariable):
basename = "debupstream-base"
minimum_format = 0.4
def get(self):
version = super(DebUpstreamBaseVariable, self).get()
version = version_extract_base(version)
if version[-1] not in ("~", "+"):
version += "+"
return version
ok_to_preserve = [DebUpstreamVariable, DebUpstreamBaseVariable,
DebVersionVariable]
deb_branch_vars = [DebVersionVariable, DebUpstreamBaseVariable, DebUpstreamVariable]
def check_expanded_deb_version(base_branch):
checked_version = base_branch.deb_version
if checked_version is None:
return
for token in ok_to_preserve:
if issubclass(token, BranchSubstitutionVariable):
for name in base_branch.list_branch_names():
checked_version = checked_version.replace(
token.determine_name(name), "")
checked_version = checked_version.replace(
token.determine_name(None), "")
else:
checked_version = checked_version.replace(
token.name, "")
if "{" in checked_version:
available_tokens = [var.name for var in simple_vars if
var.available_in(base_branch.format)]
for var_kls in branch_vars + deb_branch_vars:
if not var_kls.available_in(base_branch.format):
continue
for name in base_branch.list_branch_names():
available_tokens.append(var_kls.determine_name(name))
available_tokens.append(var_kls.determine_name(None))
raise errors.BzrCommandError("deb-version not fully "
"expanded: %s. Valid substitutions in recipe format %s are: %s"
% (base_branch.deb_version, base_branch.format,
available_tokens))
def substitute_branch_vars(base_branch, branch_name, branch, revid):
"""Substitute the branch variables for the given branch name in deb_version.
Where deb_version has a place to substitute the revno for a branch
this will substitute it for the given branch name.
:param branch_name: the name of the RecipeBranch to substitute.
:param branch: Branch object for the branch
:param revid: Revision id in the branch for which to return the revno
"""
if base_branch.deb_version is None:
return
revno_var = RevnoVariable(branch_name, branch, revid)
base_branch.deb_version = revno_var.replace(base_branch.deb_version)
if base_branch.format < 0.4:
# The other variables were introduced in recipe format 0.4
return
svn_revno_var = SubversionRevnumVariable(branch_name, branch, revid)
base_branch.deb_version = svn_revno_var.replace(base_branch.deb_version)
git_commit_var = GitCommitVariable(branch_name, branch, revid)
base_branch.deb_version = git_commit_var.replace(base_branch.deb_version)
latest_tag_var = LatestTagVariable(branch_name, branch, revid)
base_branch.deb_version = latest_tag_var.replace(base_branch.deb_version)
revdate_var = RevdateVariable(branch_name, branch, revid)
base_branch.deb_version = revdate_var.replace(base_branch.deb_version)
revtime_var = RevtimeVariable(branch_name, branch, revid)
base_branch.deb_version = revtime_var.replace(base_branch.deb_version)
tree = branch.repository.revision_tree(revid)
cl_file_id = tree.path2id("debian/changelog")
if cl_file_id is not None:
tree.lock_read()
try:
cl = changelog.Changelog(tree.get_file_text(cl_file_id))
substitute_changelog_vars(base_branch, branch_name, cl)
finally:
tree.unlock()
def substitute_changelog_vars(base_branch, branch_name, changelog):
"""Substitute variables related from a changelog.
:param branch_name: Branch name (None for root branch)
:param changelog: Changelog object to use
"""
from bzrlib.plugins.builder.deb_version import DebUpstreamVariable, DebUpstreamBaseVariable, DebVersionVariable
debupstream_var = DebUpstreamVariable.from_changelog(branch_name, changelog)
base_branch.deb_version = debupstream_var.replace(base_branch.deb_version)
if base_branch.format < 0.4:
# The other variables were introduced in recipe format 0.4
return
debupstreambase_var = DebUpstreamBaseVariable.from_changelog(
branch_name, changelog)
base_branch.deb_version = debupstreambase_var.replace(base_branch.deb_version)
pkgversion_var = DebVersionVariable.from_changelog(branch_name, changelog)
base_branch.deb_version = pkgversion_var.replace(base_branch.deb_version)
def substitute_time(base_branch, time):
"""Substitute the time in to deb_version if needed.
:param time: a datetime.datetime with the desired time.
"""
if base_branch.deb_version is None:
return
base_branch.deb_version = TimeVariable(time).replace(base_branch.deb_version)
base_branch.deb_version = DateVariable(time).replace(base_branch.deb_version)
bzr-builder-0.7.3/info.py 0000644 0000000 0000000 00000001345 11740346202 013377 0 ustar 0000000 0000000 # bzr-builder: a bzr plugin to constuct trees based on recipes
# Copyright 2011 Canonical Ltd.
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more details.
# You should have received a copy of the GNU General Public License along
# with this program. If not, see .
bzr_plugin_version = (0, 7, 3, 'final', 0)
bzr-builder-0.7.3/ppa.py 0000644 0000000 0000000 00000007434 11740346202 013231 0 ustar 0000000 0000000 # ppa support for bzr builder.
#
# Copyright: Canonical Ltd. (C) 2009
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more details.
# You should have received a copy of the GNU General Public License along
# with this program. If not, see .
import time
from bzrlib import (
errors,
trace,
)
from launchpadlib.launchpad import Launchpad
def get_lp():
return Launchpad.login_with('bzr-builder', 'production')
def watch(owner_name, archive_name, package_name, version):
"""Watch a package build.
:return: True once the package built and published, or False if it fails
or there is a timeout waiting.
"""
version = str(version)
trace.note("Logging into Launchpad")
launchpad = get_lp()
owner = launchpad.people[owner_name]
archive = owner.getPPAByName(name=archive_name)
end_states = ['FAILEDTOBUILD', 'FULLYBUILT']
important_arches = ['amd64', 'i386', 'armel']
trace.note("Waiting for version %s of %s to build." % (version, package_name))
start = time.time()
while True:
sourceRecords = list(archive.getPublishedSources(
source_name=package_name, version=version))
if not sourceRecords:
if time.time() - 900 > start:
# Over 15 minutes and no source yet, upload FAIL.
raise errors.BzrCommandError("No source record in %s/%s for "
"package %s=%s after 15 minutes." % (owner_name,
archive_name, package_name, version))
return False
trace.note("Source not available yet - waiting.")
time.sleep(60)
continue
pkg = sourceRecords[0]
if pkg.status.lower() not in ('published', 'pending'):
trace.note("Package status: %s" % (pkg.status,))
time.sleep(60)
continue
# FIXME: LP should export this as an attribute.
source_id = pkg.self_link.rsplit('/', 1)[1]
buildSummaries = archive.getBuildSummariesForSourceIds(
source_ids=[source_id])[source_id]
if buildSummaries['status'] in end_states:
break
if buildSummaries['status'] == 'NEEDSBUILD':
# We ignore non-virtual PPA architectures that are sparsely
# supplied with buildds.
missing = []
for build in buildSummaries['builds']:
arch = build['arch_tag']
if arch in important_arches:
missing.append(arch)
if not missing:
break
extra = ' on ' + ', '.join(missing)
else:
extra = ''
trace.note("%s is still in %s%s" % (pkg.display_name,
buildSummaries['status'], extra))
time.sleep(60)
trace.note("%s is now %s" % (pkg.display_name, buildSummaries['status']))
result = True
if pkg.status.lower() != 'published':
result = False # should this perhaps keep waiting?
if buildSummaries['status'] != 'FULLYBUILT':
if buildSummaries['status'] == 'NEEDSBUILD':
# We're stopping early cause the important_arches are built.
builds = pkg.getBuilds()
for build in builds:
if build.arch_tag in important_arches:
if build.buildstate != 'Successfully built':
result = False
else:
result = False
return result
bzr-builder-0.7.3/recipe.py 0000644 0000000 0000000 00000150424 11740346202 013716 0 ustar 0000000 0000000 # bzr-builder: a bzr plugin to constuct trees based on recipes
# Copyright 2009 Canonical Ltd.
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more details.
# You should have received a copy of the GNU General Public License along
# with this program. If not, see .
import os
import signal
import subprocess
import time
from bzrlib import (
branch as _mod_branch,
bzrdir,
errors,
merge,
revision,
revisionspec,
tag,
trace,
transport,
urlutils,
version_info as bzr_version_info,
)
try:
from bzrlib.errors import NoWhoami
except ImportError:
NoWhoami = object()
try:
MergeIntoMerger = merge.MergeIntoMerger
except (AttributeError, NameError):
from bzrlib.plugins.builder.backports import MergeIntoMerger
def subprocess_setup():
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
MERGE_INSTRUCTION = "merge"
NEST_PART_INSTRUCTION = "nest-part"
NEST_INSTRUCTION = "nest"
RUN_INSTRUCTION = "run"
USAGE = {
MERGE_INSTRUCTION: 'merge NAME BRANCH [REVISION]',
NEST_INSTRUCTION: 'nest NAME BRANCH TARGET-DIR [REVISION]',
NEST_PART_INSTRUCTION:
'nest-part NAME BRANCH SUBDIR [TARGET-DIR [REVISION]]',
RUN_INSTRUCTION: 'run COMMAND',
}
SAFE_INSTRUCTIONS = [
MERGE_INSTRUCTION, NEST_PART_INSTRUCTION, NEST_INSTRUCTION]
class SubstitutionUnavailable(errors.BzrError):
_fmt = """Substitution for %(name)s not available: %(reason)s"""
def __init__(self, name, reason):
errors.BzrError.__init__(self, name=name, reason=reason)
class SubstitutionVariable(object):
"""A substitution variable for a version string."""
def replace(self, value):
"""Replace name with value."""
raise NotImplementedError(self.replace)
@classmethod
def available_in(cls, format):
"""Check if this variable is available in a particular format."""
raise NotImplementedError(cls.available_in)
class SimpleSubstitutionVariable(SubstitutionVariable):
name = None
minimum_format = None
def replace(self, value):
if not self.name in value:
return value
return value.replace(self.name, self.get())
def get(self):
raise NotImplementedError(self.value)
@classmethod
def available_in(self, format):
return (format >= self.minimum_format)
class BranchSubstitutionVariable(SimpleSubstitutionVariable):
basename = None
def __init__(self, branch_name=None):
super(BranchSubstitutionVariable, self).__init__()
self.branch_name = branch_name
@classmethod
def determine_name(cls, branch_name):
if branch_name is None:
return "{%s}" % cls.basename
else:
return "{%s:%s}" % (cls.basename, branch_name)
@property
def name(self):
return self.determine_name(self.branch_name)
class TimeVariable(SimpleSubstitutionVariable):
name = "{time}"
minimum_format = 0.1
def __init__(self, time):
self._time = time
def get(self):
return self._time.strftime("%Y%m%d%H%M")
class DateVariable(SimpleSubstitutionVariable):
name = "{date}"
minimum_format = 0.1
def __init__(self, time):
self._time = time
def get(self):
return self._time.strftime("%Y%m%d")
class RevisionVariable(BranchSubstitutionVariable):
minimum_format = 0.1
def __init__(self, branch_name, branch, revid):
super(RevisionVariable, self).__init__(branch_name)
self.branch = branch
self.revid = revid
class RevnoVariable(RevisionVariable):
basename = "revno"
minimum_format = 0.1
def get_revno(self):
try:
revno = self.branch.revision_id_to_revno(self.revid)
return str(revno)
except errors.NoSuchRevision:
# We need to load and use the full revno map after all
result = self.branch.get_revision_id_to_revno_map().get(
self.revid)
if result is None:
return result
return ".".join(result)
def get(self):
revno = self.get_revno()
if revno is None:
raise errors.BzrCommandError("Can't substitute revno of "
"branch %s in deb-version, as it's revno can't be "
"determined" % revno)
return revno
class RevtimeVariable(RevisionVariable):
basename = "revtime"
minimum_format = 0.4
def get(self):
rev = self.branch.repository.get_revision(self.revid)
return time.strftime("%Y%m%d%H%M", time.gmtime(rev.timestamp))
class RevdateVariable(RevisionVariable):
basename = "revdate"
minimum_format = 0.4
def get(self):
rev = self.branch.repository.get_revision(self.revid)
return time.strftime("%Y%m%d", time.gmtime(rev.timestamp))
def extract_svn_revnum(rev):
try:
foreign_revid = rev.foreign_revid
except AttributeError:
try:
(mapping_name, uuid, bp, srevnum) = rev.revision_id.split(":", 3)
except ValueError:
raise errors.InvalidRevisionId(rev.revision_id, None)
if not mapping_name.startswith("svn-"):
raise errors.InvalidRevisionId(rev.revision_id, None)
return int(srevnum)
else:
if rev.mapping.vcs.abbreviation == "svn":
return foreign_revid[2]
else:
raise errors.InvalidRevisionId(rev.revision_id, None)
class SubversionRevnumVariable(RevisionVariable):
basename = "svn-revno"
minimum_format = 0.4
def get(self):
rev = self.branch.repository.get_revision(self.revid)
try:
revno = extract_svn_revnum(rev)
except errors.InvalidRevisionId:
raise errors.BzrCommandError("unable to expand %s for %r in %r: "
"not a Subversion revision" % (
self.name, self.revid, self.branch))
return str(revno)
def extract_git_foreign_revid(rev):
try:
foreign_revid = rev.foreign_revid
except AttributeError:
try:
(mapping_name, foreign_revid) = rev.revision_id.split(":", 1)
except ValueError:
raise errors.InvalidRevisionId(rev.revision_id, None)
if not mapping_name.startswith("git-"):
raise errors.InvalidRevisionId(rev.revision_id, None)
return foreign_revid
else:
if rev.mapping.vcs.abbreviation == "git":
return foreign_revid
else:
raise errors.InvalidRevisionId(rev.revision_id, None)
class GitCommitVariable(RevisionVariable):
basename = "git-commit"
minimum_format = 0.4
def get(self):
rev = self.branch.repository.get_revision(self.revid)
try:
commit_sha = extract_git_foreign_revid(rev)
except errors.InvalidRevisionId:
raise errors.BzrCommandError("unable to expand %s for %r in %r: "
"not a Git revision" % (
self.name, self.revid, self.branch))
return commit_sha[:7]
class LatestTagVariable(RevisionVariable):
basename = "latest-tag"
minimum_format = 0.4
def get(self):
reverse_tag_dict = self.branch.tags.get_reverse_tag_dict()
self.branch.lock_read()
try:
graph = self.branch.repository.get_graph()
for revid in graph.iter_lefthand_ancestry(self.revid):
if revid in reverse_tag_dict:
return reverse_tag_dict[revid][0]
else:
raise errors.BzrCommandError("No tags set on branch %s mainline" %
self.branch_name)
finally:
self.branch.unlock()
branch_vars = [RevnoVariable, SubversionRevnumVariable,
GitCommitVariable, LatestTagVariable, RevdateVariable,
RevtimeVariable]
simple_vars = [TimeVariable, DateVariable]
class CommandFailedError(errors.BzrError):
_fmt = "The command \"%(command)s\" failed."
def __init__(self, command):
super(CommandFailedError, self).__init__()
self.command = command
def ensure_basedir(to_transport):
"""Ensure that the basedir of to_transport exists.
It is allowed to already exist currently, to reuse directories.
:param to_transport: The Transport to ensure that the basedir of
exists.
"""
try:
to_transport.mkdir('.')
except errors.FileExists:
pass
except errors.NoSuchFile:
raise errors.BzrCommandError('Parent of "%s" does not exist.'
% to_transport.base)
def pull_or_branch(tree_to, br_to, br_from, to_transport, revision_id,
accelerator_tree=None, possible_transports=None):
"""Either pull or branch from a branch.
Depending on whether the target branch and tree exist already this
will either pull from the source branch, or branch from it. If it
returns this function will return a branch and tree for the target,
after creating either if necessary.
:param tree_to: The WorkingTree to pull in to, or None. If not None then
br_to must not be None.
:param br_to: The Branch to pull in to, or None to branch.
:param br_from: The Branch to pull/branch from.
:param to_transport: A Transport for the root of the target.
:param revision_id: the revision id to pull/branch.
:param accelerator_tree: A tree to take contents from that is faster than
extracting from br_from, or None.
:param possible_transports: A list of transports that can be reused, or
None.
:return: A tuple of (target tree, target branch) which are the updated
tree and branch, created if necessary. They are locked, and you
should use these instead of tree_to and br_to if they were passed
in, including for unlocking.
"""
created_tree_to = False
created_br_to = False
if br_to is None:
# We do a "branch"
ensure_basedir(to_transport)
dir = br_from.bzrdir.sprout(to_transport.base, revision_id,
possible_transports=possible_transports,
accelerator_tree=accelerator_tree,
source_branch=br_from,
stacked=(bzr_version_info >= (2, 3, 0)))
try:
tree_to = dir.open_workingtree()
except errors.NoWorkingTree:
# There's no working tree, so it's probably in a no-trees repo,
# but the whole point of this is to create trees, so we should
# forcibly create one.
tree_to = dir.create_workingtree()
br_to = tree_to.branch
created_br_to = True
tag._merge_tags_if_possible(br_from, br_to)
created_tree_to = True
else:
# We do a "pull"
if tree_to is not None:
# FIXME: should these pulls overwrite?
tree_to.pull(br_from, stop_revision=revision_id,
possible_transports=possible_transports)
else:
br_to.pull(br_from, stop_revision=revision_id,
possible_transports=possible_transports)
tree_to = br_to.bzrdir.create_workingtree()
# Ugh, we have to assume that the caller replaces their reference
# to the branch with the one we return.
br_to.unlock()
br_to = tree_to.branch
br_to.lock_write()
created_tree_to = True
if created_tree_to:
tree_to.lock_write()
try:
if created_br_to:
br_to.lock_write()
try:
conflicts = tree_to.conflicts()
if len(conflicts) > 0:
# FIXME: better reporting
raise errors.BzrCommandError("Conflicts... aborting.")
except:
if created_br_to:
br_to.unlock()
raise
except:
if created_tree_to:
tree_to.unlock()
raise
return tree_to, br_to
def merge_branch(child_branch, tree_to, br_to, possible_transports=None):
"""Merge the branch specified by child_branch.
:param child_branch: the RecipeBranch to retrieve the branch and revision to
merge from.
:param tree_to: the WorkingTree to merge in to.
:param br_to: the Branch to merge in to.
"""
if child_branch.branch is None:
child_branch.branch = _mod_branch.Branch.open(child_branch.url,
possible_transports=possible_transports)
child_branch.branch.lock_read()
try:
tag._merge_tags_if_possible(child_branch.branch, br_to)
if child_branch.revspec is not None:
merge_revspec = revisionspec.RevisionSpec.from_string(
child_branch.revspec)
try:
merge_revid = merge_revspec.as_revision_id(
child_branch.branch)
except errors.InvalidRevisionSpec, e:
# Give the user a hint if they didn't mean to speciy
# a revspec.
e.extra = (". Did you not mean to specify a revspec "
"at the end of the merge line?")
raise e
else:
merge_revid = child_branch.branch.last_revision()
child_branch.revid = merge_revid
try:
merger = merge.Merger.from_revision_ids(None, tree_to, merge_revid,
other_branch=child_branch.branch, tree_branch=br_to)
except errors.UnrelatedBranches:
# Let's just try and hope for the best.
merger = merge.Merger.from_revision_ids(None, tree_to, merge_revid,
other_branch=child_branch.branch, tree_branch=br_to,
base=revision.NULL_REVISION)
merger.merge_type = merge.Merge3Merger
if (merger.base_rev_id == merger.other_rev_id and
merger.other_rev_id is not None):
# Nothing to do.
return
conflict_count = merger.do_merge()
merger.set_pending()
if conflict_count:
# FIXME: better reporting
raise errors.BzrCommandError("Conflicts from merge")
config = br_to.get_config()
try:
committer = config.username()
except NoWhoami:
committer = "bzr-builder "
tree_to.commit("Merge %s" %
urlutils.unescape_for_display(child_branch.url, 'utf-8'),
committer=committer)
finally:
child_branch.branch.unlock()
def nest_part_branch(child_branch, tree_to, br_to, subpath, target_subdir=None):
"""Merge the branch subdirectory specified by child_branch.
:param child_branch: the RecipeBranch to retrieve the branch and revision to
merge from.
:param tree_to: the WorkingTree to merge in to.
:param br_to: the Branch to merge in to.
:param subpath: only merge files from branch that are from this path.
e.g. subpath='/debian' will only merge changes from that directory.
:param target_subdir: (optional) directory in target to merge that
subpath into. Defaults to basename of subpath.
"""
child_branch.branch = _mod_branch.Branch.open(child_branch.url)
child_branch.branch.lock_read()
try:
child_branch.resolve_revision_id()
other_tree = child_branch.branch.basis_tree()
other_tree.lock_read()
try:
if target_subdir is None:
target_subdir = os.path.basename(subpath)
# Create any missing parent directories
target_subdir_parent = os.path.dirname(target_subdir)
missing = []
while tree_to.path2id(target_subdir_parent) is None:
missing.append(target_subdir_parent)
target_subdir_parent = os.path.dirname(target_subdir_parent)
for path in reversed(missing):
tree_to.mkdir(path)
merger = MergeIntoMerger(this_tree=tree_to, other_tree=other_tree,
other_branch=child_branch.branch, target_subdir=target_subdir,
source_subpath=subpath, other_rev_id=child_branch.revid)
merger.set_base_revision(revision.NULL_REVISION, child_branch.branch)
conflict_count = merger.do_merge()
merger.set_pending()
finally:
other_tree.unlock()
finally:
child_branch.branch.unlock()
if conflict_count:
# FIXME: better reporting
raise errors.BzrCommandError("Conflicts from merge")
tree_to.commit("Merge %s of %s" %
(subpath, urlutils.unescape_for_display(child_branch.url, 'utf-8')))
def update_branch(base_branch, tree_to, br_to, to_transport,
possible_transports=None):
if base_branch.branch is None:
base_branch.branch = _mod_branch.Branch.open(base_branch.url,
possible_transports=possible_transports)
base_branch.branch.lock_read()
try:
base_branch.resolve_revision_id()
tree_to, br_to = pull_or_branch(tree_to, br_to, base_branch.branch,
to_transport, base_branch.revid,
possible_transports=possible_transports)
finally:
base_branch.branch.unlock()
return tree_to, br_to
def _resolve_revisions_recurse(new_branch, substitute_branch_vars,
if_changed_from=None):
changed = False
new_branch.branch = _mod_branch.Branch.open(new_branch.url)
new_branch.branch.lock_read()
try:
new_branch.resolve_revision_id()
if substitute_branch_vars is not None:
substitute_branch_vars(new_branch.name, new_branch.branch, new_branch.revid)
if (if_changed_from is not None
and (new_branch.revspec is not None
or if_changed_from.revspec is not None)):
if if_changed_from.revspec is not None:
changed_revspec = revisionspec.RevisionSpec.from_string(
if_changed_from.revspec)
changed_revision_id = changed_revspec.as_revision_id(
new_branch.branch)
else:
changed_revision_id = new_branch.branch.last_revision()
if new_branch.revid != changed_revision_id:
changed = True
for index, instruction in enumerate(new_branch.child_branches):
child_branch = instruction.recipe_branch
if_changed_child = None
if if_changed_from is not None:
if_changed_child = if_changed_from.child_branches[index].recipe_branch
if child_branch is not None:
child_changed = _resolve_revisions_recurse(child_branch,
substitute_branch_vars,
if_changed_from=if_changed_child)
if child_changed:
changed = child_changed
return changed
finally:
new_branch.branch.unlock()
def resolve_revisions(base_branch, if_changed_from=None, substitute_branch_vars=None):
"""Resolve all the unknowns in base_branch.
This walks the RecipeBranch and calls substitute_branch_vars for
each child branch.
If if_changed_from is not None then it should be a second RecipeBranch
to compare base_branch against. If the shape, or the revision ids differ
then the function will return True.
:param base_branch: the RecipeBranch we plan to build.
:param if_changed_from: the RecipeBranch that we want to compare against.
:param substitute_branch_vars: Callable called for
each branch with (name, bzr branch and last revision)
:return: False if if_changed_from is not None, and the shape and revisions
of the two branches don't differ. True otherwise.
"""
changed = False
if if_changed_from is not None:
changed = base_branch.different_shape_to(if_changed_from)
if_changed_from_revisions = if_changed_from
if changed:
if_changed_from_revisions = None
if substitute_branch_vars is not None:
real_subsitute_branch_vars = lambda n, b, r: substitute_branch_vars(base_branch, n, b, r)
else:
real_subsitute_branch_vars = None
changed_revisions = _resolve_revisions_recurse(base_branch,
real_subsitute_branch_vars,
if_changed_from=if_changed_from_revisions)
if not changed:
changed = changed_revisions
if if_changed_from is not None and not changed:
return False
return True
def _build_inner_tree(base_branch, target_path, possible_transports=None):
revision_of = ""
if base_branch.revspec is not None:
revision_of = "revision '%s' of " % base_branch.revspec
trace.note("Retrieving %s'%s' to put at '%s'."
% (revision_of, base_branch.url, target_path))
to_transport = transport.get_transport(target_path,
possible_transports=possible_transports)
try:
tree_to, br_to = bzrdir.BzrDir.open_tree_or_branch(target_path)
# Should we commit any changes in the tree here? If we don't
# then they will get folded up in to the first merge.
except errors.NotBranchError:
tree_to = None
br_to = None
if tree_to is not None:
tree_to.lock_write()
try:
if br_to is not None:
br_to.lock_write()
try:
tree_to, br_to = update_branch(base_branch, tree_to, br_to,
to_transport, possible_transports=possible_transports)
for instruction in base_branch.child_branches:
instruction.apply(target_path, tree_to, br_to)
finally:
# Is this ok if tree_to is created by pull_or_branch?
if br_to is not None:
br_to.unlock()
finally:
if tree_to is not None:
tree_to.unlock()
def build_tree(base_branch, target_path, possible_transports=None):
"""Build the RecipeBranch at a path.
Follow the instructions embodied in RecipeBranch and build a tree
based on them rooted at target_path. If target_path exists and
is the root of the branch then the branch will be updated based on
what the RecipeBranch requires.
:param base_branch: a RecipeBranch to build.
:param target_path: the path to the base of the desired output.
"""
trace.note("Building tree.")
_build_inner_tree(base_branch, target_path,
possible_transports=possible_transports)
class ChildBranch(object):
"""A child branch in a recipe.
If the nest path is not None it is the path relative to the recipe branch
where the child branch should be placed. If it is None then the child
branch should be merged instead of nested.
"""
can_have_children = False
def __init__(self, recipe_branch, nest_path=None):
self.recipe_branch = recipe_branch
self.nest_path = nest_path
def apply(self, target_path, tree_to, br_to, possible_transports=None):
raise NotImplementedError(self.apply)
def as_tuple(self):
return (self.recipe_branch, self.nest_path)
def _get_revid_part(self):
if self.recipe_branch.revid is not None:
revid_part = " revid:%s" % self.recipe_branch.revid
elif self.recipe_branch.revspec is not None:
revid_part = " %s" % self.recipe_branch.revspec
else:
revid_part = ""
return revid_part
def __repr__(self):
return "<%s %r>" % (self.__class__.__name__, self.nest_path)
class CommandInstruction(ChildBranch):
def apply(self, target_path, tree_to, br_to, possible_transports=None):
# it's a command
trace.note("Running '%s' in '%s'." % (self.nest_path, target_path))
proc = subprocess.Popen(self.nest_path, cwd=target_path,
preexec_fn=subprocess_setup, shell=True, stdin=subprocess.PIPE)
proc.communicate()
if proc.returncode != 0:
raise CommandFailedError(self.nest_path)
def as_text(self):
return "%s %s" % (RUN_INSTRUCTION, self.nest_path)
class MergeInstruction(ChildBranch):
def apply(self, target_path, tree_to, br_to, possible_transports=None):
revision_of = ""
if self.recipe_branch.revspec is not None:
revision_of = "revision '%s' of " % self.recipe_branch.revspec
trace.note("Merging %s'%s' in to '%s'."
% (revision_of, self.recipe_branch.url, target_path))
merge_branch(self.recipe_branch, tree_to, br_to,
possible_transports=possible_transports)
def as_text(self):
revid_part = self._get_revid_part()
return "%s %s %s%s" % (
MERGE_INSTRUCTION, self.recipe_branch.name,
self.recipe_branch.url, revid_part)
def __repr__(self):
return "<%s %r>" % (self.__class__.__name__, self.recipe_branch.name)
class NestPartInstruction(ChildBranch):
def __init__(self, recipe_branch, subpath, target_subdir):
ChildBranch.__init__(self, recipe_branch)
self.subpath = subpath
self.target_subdir = target_subdir
def apply(self, target_path, tree_to, br_to):
nest_part_branch(self.recipe_branch, tree_to, br_to, self.subpath,
self.target_subdir)
def as_text(self):
revid_part = self._get_revid_part()
if revid_part:
target_subdir = self.target_subdir
if target_subdir is None:
target_subdir = self.subpath
target_revid_part = " %s%s" % (
target_subdir, revid_part)
elif self.target_subdir is not None:
target_revid_part = " %s" % self.target_subdir
else:
target_revid_part = ""
return "%s %s %s %s%s" % (
NEST_PART_INSTRUCTION, self.recipe_branch.name,
self.recipe_branch.url, self.subpath, target_revid_part)
class NestInstruction(ChildBranch):
can_have_children = True
def apply(self, target_path, tree_to, br_to, possible_transports=None):
_build_inner_tree(self.recipe_branch,
target_path=os.path.join(target_path, self.nest_path),
possible_transports=possible_transports)
def as_text(self):
revid_part = self._get_revid_part()
return "%s %s %s %s%s" % (
NEST_INSTRUCTION, self.recipe_branch.name,
self.recipe_branch.url, self.nest_path, revid_part)
def __repr__(self):
return "<%s %r>" % (self.__class__.__name__,
self.recipe_branch.name)
class RecipeBranch(object):
"""A nested structure that represents a Recipe.
A RecipeBranch has a name and a url (the name can be None for the
root branch), and optionally child branches that are either merged
or nested.
The child_branches attribute is a list of tuples of ChildBranch objects.
The revid attribute records the revid that the url and revspec resolved
to when the RecipeBranch was built, or None if it has not been built.
:ivar revid: after this recipe branch has been built this is set to the
revision ID that was merged/nested from the branch at self.url.
"""
def __init__(self, name, url, revspec=None):
"""Create a RecipeBranch.
:param name: the name for the branch, or None if it is the root.
:param url: the URL from which to retrieve the branch.
:param revspec: a revision specifier for the revision of the branch
to use, or None (the default) to use the last revision.
"""
self.name = name
self.url = url
self.revspec = revspec
self.child_branches = []
self.revid = None
self.branch = None
def resolve_revision_id(self):
"""Resolve the revision id for this branch.
"""
if self.revspec is not None:
revspec = revisionspec.RevisionSpec.from_string(self.revspec)
revision_id = revspec.as_revision_id(self.branch)
else:
revision_id = self.branch.last_revision()
self.revid = revision_id
def merge_branch(self, branch):
"""Merge a child branch in to this one.
:param branch: the RecipeBranch to merge.
"""
self.child_branches.append(MergeInstruction(branch))
def nest_part_branch(self, branch, subpath=None, target_subdir=None):
"""Merge subdir of a child branch into this one.
:param branch: the RecipeBranch to merge.
:param subpath: only merge files from branch that are from this path.
e.g. subpath='/debian' will only merge changes from that directory.
:param target_subdir: (optional) directory in target to merge that
subpath into. Defaults to basename of subpath.
"""
self.child_branches.append(
NestPartInstruction(branch, subpath, target_subdir))
def nest_branch(self, location, branch):
"""Nest a child branch in to this one.
:param location: the relative path at which this branch should be nested.
:param branch: the RecipeBranch to nest.
"""
assert location not in [b.nest_path for b in self.child_branches],\
"%s already has branch nested there" % location
self.child_branches.append(NestInstruction(branch, location))
def run_command(self, command):
"""Set a command to be run.
:param command: the command to be run
"""
self.child_branches.append(CommandInstruction(None, command))
def different_shape_to(self, other_branch):
"""Tests whether the name, url and child_branches are the same"""
if self.name != other_branch.name:
return True
if self.url != other_branch.url:
return True
if len(self.child_branches) != len(other_branch.child_branches):
return True
for index, instruction in enumerate(self.child_branches):
child_branch = instruction.recipe_branch
nest_location = instruction.nest_path
other_instruction = other_branch.child_branches[index]
other_child_branch = other_instruction.recipe_branch
other_nest_location = other_instruction.nest_path
if nest_location != other_nest_location:
return True
if ((child_branch is None and other_child_branch is not None)
or (child_branch is not None and other_child_branch is None)):
return True
# if child_branch is None then other_child_branch must be
# None too, meaning that they are both run instructions,
# we would compare their nest locations (commands), but
# that has already been done, so just guard
if (child_branch is not None
and child_branch.different_shape_to(other_child_branch)):
return True
return False
def iter_all_instructions(self):
"""Iter over all instructions under this branch."""
for instruction in self.child_branches:
yield instruction
child_branch = instruction.recipe_branch
if child_branch is None:
continue
for instruction in child_branch.iter_all_instructions():
yield instruction
def iter_all_branches(self):
"""Iterate over all branches."""
yield self
for instruction in self.child_branches:
child_branch = instruction.recipe_branch
if child_branch is None:
continue
for subbranch in child_branch.iter_all_branches():
yield subbranch
def lookup_branch(self, name):
"""Lookup a branch by its name."""
for branch in self.iter_all_branches():
if branch.name == name:
return branch
else:
raise KeyError(name)
def list_branch_names(self):
"""List all of the branch names under this one.
:return: a list of the branch names.
:rtype: list(str)
"""
return [branch.name for branch in self.iter_all_branches()
if branch.name is not None]
def __repr__(self):
return "<%s %r>" % (self.__class__.__name__, self.name)
class BaseRecipeBranch(RecipeBranch):
"""The RecipeBranch that is at the root of a recipe."""
def __init__(self, url, deb_version, format, revspec=None):
"""Create a BaseRecipeBranch.
:param deb_version: the template to use for the version number.
Should be None for anything except the root branch.
"""
super(BaseRecipeBranch, self).__init__(None, url, revspec=revspec)
self.deb_version = deb_version
self.format = format
def _add_child_branches_to_manifest(self, child_branches, indent_level):
manifest = ""
for instruction in child_branches:
manifest += "%s%s\n" % (
" " * indent_level, instruction.as_text())
if instruction.can_have_children:
manifest += self._add_child_branches_to_manifest(
instruction.recipe_branch.child_branches,
indent_level+1)
return manifest
def __str__(self):
return self.get_recipe_text(validate=True)
def get_recipe_text(self, validate=False):
manifest = "# bzr-builder format %s" % str(self.format)
if self.deb_version is not None:
# TODO: should we store the expanded version that was used?
manifest += " deb-version %s" % (self.deb_version,)
manifest += "\n"
if self.revid is not None:
manifest += "%s revid:%s\n" % (self.url, self.revid)
elif self.revspec is not None:
manifest += "%s %s\n" % (self.url, self.revspec)
else:
manifest += "%s\n" % (self.url,)
manifest += self._add_child_branches_to_manifest(self.child_branches,
0)
if validate:
# Sanity check.
# TODO: write a function that compares the result of this parse with
# the branch that we built it from.
RecipeParser(manifest).parse()
return manifest
class RecipeParseError(errors.BzrError):
_fmt = "Error parsing %(filename)s:%(line)s:%(char)s: %(problem)s."
def __init__(self, filename, line, char, problem):
errors.BzrError.__init__(self, filename=filename, line=line, char=char,
problem=problem)
class InstructionParseError(RecipeParseError):
_fmt = RecipeParseError._fmt + "\nUsage: %(usage)s"
def __init__(self, filename, line, char, problem, instruction):
RecipeParseError.__init__(self, filename, line, char, problem)
self.usage = USAGE[instruction]
class ForbiddenInstructionError(RecipeParseError):
def __init__(self, filename, line, char, problem, instruction_name=None):
RecipeParseError.__init__(self, filename, line, char, problem)
self.instruction_name = instruction_name
class RecipeParser(object):
"""Parse a recipe.
The parse() method is probably the only one that interests you.
"""
whitespace_chars = " \t"
eol_char = "\n"
digit_chars = ("0", "1", "2", "3", "4", "5", "6", "7", "8", "9")
NEWEST_VERSION = 0.4
def __init__(self, f, filename=None):
"""Create a RecipeParser.
:param f: either the recipe as a string, or a file like object to
take it from.
:param filename: the filename of the recipe if known (for error
reporting).
"""
if getattr(f, "read", None) is not None:
self.text = f.read()
else:
self.text = f
self.filename = filename
if filename is None:
self.filename = "recipe"
def parse(self, permitted_instructions=None):
"""Parse the recipe.
:param permitted_instructions: a list of instructions that you
want to allow. Defaults to None allowing them all.
:type permitted_instructions: list(str) or None
:return: a RecipeBranch representing the recipe.
"""
self.lines = self.text.split("\n")
self.index = 0
self.line_index = 0
self.current_line = self.lines[self.line_index]
self.current_indent_level = 0
self.seen_nicks = set()
self.seen_paths = {".": 1}
(version, deb_version) = self.parse_header()
self.version = version
last_instruction = None
active_branches = []
last_branch = None
while self.line_index < len(self.lines):
if self.is_blankline():
self.new_line()
continue
comment = self.parse_comment_line()
if comment is not None:
self.new_line()
continue
old_indent_level = self.parse_indent()
if old_indent_level is not None:
if (old_indent_level < self.current_indent_level
and last_instruction != NEST_INSTRUCTION):
self.throw_parse_error("Not allowed to indent unless "
"after a '%s' line" % NEST_INSTRUCTION)
if old_indent_level < self.current_indent_level:
active_branches.append(last_branch)
else:
unindent = self.current_indent_level - old_indent_level
active_branches = active_branches[:unindent]
if last_instruction is None:
url = self.take_to_whitespace("branch to start from")
revspec = self.parse_optional_revspec()
self.new_line()
last_branch = BaseRecipeBranch(url, deb_version,
self.version, revspec=revspec)
active_branches = [last_branch]
last_instruction = ""
else:
instruction = self.parse_instruction(
permitted_instructions=permitted_instructions)
if instruction == RUN_INSTRUCTION:
self.parse_whitespace("the command",
instruction=instruction)
command = self.take_to_newline().strip()
self.new_line()
active_branches[-1].run_command(command)
else:
branch_id = self.parse_branch_id(instruction)
url = self.parse_branch_url(instruction)
if instruction == NEST_INSTRUCTION:
location = self.parse_branch_location(instruction)
if instruction == NEST_PART_INSTRUCTION:
path = self.parse_subpath(instruction)
target_subdir = self.parse_optional_path()
if target_subdir == '':
target_subdir = None
revspec = None
else:
revspec = self.parse_optional_revspec()
else:
revspec = self.parse_optional_revspec()
self.new_line(instruction)
last_branch = RecipeBranch(branch_id, url, revspec=revspec)
if instruction == NEST_INSTRUCTION:
active_branches[-1].nest_branch(location, last_branch)
elif instruction == MERGE_INSTRUCTION:
active_branches[-1].merge_branch(last_branch)
elif instruction == NEST_PART_INSTRUCTION:
active_branches[-1].nest_part_branch(
last_branch, path, target_subdir)
last_instruction = instruction
if len(active_branches) == 0:
self.throw_parse_error("Empty recipe")
return active_branches[0]
def parse_header(self):
self.parse_char("#")
self.parse_word("bzr-builder", require_whitespace=False)
self.parse_word("format")
version, version_str = self.peek_float("format version")
if version > self.NEWEST_VERSION:
self.throw_parse_error("Unknown format: '%s'" % str(version))
self.take_chars(len(version_str))
deb_version = self.parse_optional_deb_version()
self.new_line()
return version, deb_version
def parse_instruction(self, permitted_instructions=None):
if self.version < 0.2:
options = (MERGE_INSTRUCTION, NEST_INSTRUCTION)
options_str = "'%s' or '%s'" % options
elif self.version < 0.3:
options = (MERGE_INSTRUCTION, NEST_INSTRUCTION, RUN_INSTRUCTION)
options_str = "'%s', '%s' or '%s'" % options
else:
options = (MERGE_INSTRUCTION, NEST_INSTRUCTION,
NEST_PART_INSTRUCTION, RUN_INSTRUCTION)
options_str = "'%s', '%s', '%s' or '%s'" % options
instruction = self.peek_to_whitespace()
if instruction is None:
self.throw_parse_error("End of line while looking for %s"
% options_str)
if instruction in options:
if permitted_instructions is not None:
if instruction not in permitted_instructions:
self.throw_parse_error("The '%s' instruction is "
"forbidden" % instruction,
cls=ForbiddenInstructionError,
instruction_name=instruction)
self.take_chars(len(instruction))
return instruction
self.throw_parse_error("Expecting %s, got '%s'"
% (options_str, instruction))
def parse_branch_id(self, instruction):
self.parse_whitespace("the branch id", instruction=instruction)
branch_id = self.peek_to_whitespace()
if branch_id is None:
self.throw_parse_error("End of line while looking for the "
"branch id", cls=InstructionParseError,
instruction=instruction)
if branch_id in self.seen_nicks:
self.throw_parse_error("'%s' was already used to identify "
"a branch." % branch_id)
self.take_chars(len(branch_id))
self.seen_nicks.add(branch_id)
return branch_id
def parse_branch_url(self, instruction):
self.parse_whitespace("the branch url", instruction=instruction)
branch_url = self.take_to_whitespace("the branch url", instruction)
return branch_url
def parse_branch_location(self, instruction):
# FIXME: Needs a better term
self.parse_whitespace("the location to nest")
location = self.peek_to_whitespace()
if location is None:
self.throw_parse_error("End of line while looking for the "
"location to nest", cls=InstructionParseError,
instruction=instruction)
norm_location = os.path.normpath(location)
if norm_location in self.seen_paths:
self.throw_parse_error("The path '%s' is a duplicate of "
"the one used on line %d." % (location,
self.seen_paths[norm_location]),
InstructionParseError, instruction=instruction)
if os.path.isabs(norm_location):
self.throw_parse_error("Absolute paths are not allowed: %s"
% location, InstructionParseError, instruction=instruction)
if norm_location.startswith(".."):
self.throw_parse_error("Paths outside the current directory "
"are not allowed: %s" % location,
cls=InstructionParseError, instruction=instruction)
self.take_chars(len(location))
self.seen_paths[norm_location] = self.line_index + 1
return location
def parse_subpath(self, instruction):
self.parse_whitespace("the subpath to merge", instruction=instruction)
location = self.take_to_whitespace("the subpath to merge", instruction)
return location
def parse_revspec(self):
self.parse_whitespace("the revspec")
revspec = self.take_to_whitespace("the revspec")
return revspec
def parse_optional_deb_version(self):
self.parse_whitespace("'deb-version'", require=False)
actual = self.peek_to_whitespace()
if actual is None:
# End of line, no version
return None
if actual != "deb-version":
self.throw_expecting_error("deb-version", actual)
self.take_chars(len("deb-version"))
self.parse_whitespace("a value for 'deb-version'")
return self.take_to_whitespace("a value for 'deb-version'")
def parse_optional_revspec(self):
self.parse_whitespace(None, require=False)
revspec = self.peek_to_whitespace()
if revspec is not None:
self.take_chars(len(revspec))
return revspec
def parse_optional_path(self):
self.parse_whitespace(None, require=False)
path = self.peek_to_whitespace()
if path is not None:
self.take_chars(len(path))
return path
def throw_parse_error(self, problem, cls=None, **kwargs):
if cls is None:
cls = RecipeParseError
raise cls(self.filename, self.line_index + 1,
self.index + 1, problem, **kwargs)
def throw_expecting_error(self, expected, actual):
self.throw_parse_error("Expecting '%s', got '%s'"
% (expected, actual))
def throw_eol(self, expected):
self.throw_parse_error("End of line while looking for '%s'" % expected)
def new_line(self, instruction=None):
# Jump over any whitespace
self.parse_whitespace(None, require=False)
remaining = self.peek_to_whitespace()
if remaining != None:
kwargs = {}
if instruction is not None:
kwargs = {
'cls': InstructionParseError,
'instruction': instruction}
self.throw_parse_error("Expecting the end of the line, got '%s'"
% remaining, **kwargs)
self.index = 0
self.line_index += 1
if self.line_index >= len(self.lines):
self.current_line = None
else:
self.current_line = self.lines[self.line_index]
def is_blankline(self):
whitespace = self.peek_whitespace()
if whitespace is None:
return True
return self.peek_char(skip=len(whitespace)) is None
def take_char(self):
if self.index >= len(self.current_line):
return None
self.index += 1
return self.current_line[self.index-1]
def take_chars(self, num):
ret = ""
for i in range(num):
char = self.take_char()
if char is None:
return None
ret += char
return ret
def peek_char(self, skip=0):
if self.index + skip >= len(self.current_line):
return None
return self.current_line[self.index + skip]
def parse_char(self, char):
actual = self.peek_char()
if actual is None:
self.throw_eol(char)
if actual == char:
self.take_char()
return char
self.throw_expecting_error(char, actual)
def parse_indent(self):
"""Parse the indent from the start of the line."""
# FIXME: should just peek the whitespace
new_indent = self.parse_whitespace(None, require=False)
# FIXME: These checks should probably come after we check whether
# any change in indent is legal at this point:
# "Indents of 3 spaces aren't allowed" -> make it 2 spaces
# -> "oh, you aren't allowed to indent at that point anyway"
if "\t" in new_indent:
self.throw_parse_error("Indents may not be done by tabs")
if (len(new_indent) % 2 != 0):
self.throw_parse_error("Indent not a multiple of two spaces")
new_indent_level = len(new_indent) / 2
if new_indent_level != self.current_indent_level:
old_indent_level = self.current_indent_level
self.current_indent_level = new_indent_level
if (new_indent_level > old_indent_level
and new_indent_level - old_indent_level != 1):
self.throw_parse_error("Indented by more than two spaces "
"at once")
return old_indent_level
return None
def parse_whitespace(self, looking_for, require=True, instruction=None):
if require:
actual = self.peek_char()
if actual is None:
kwargs = {}
if instruction is not None:
kwargs = {
'cls': InstructionParseError,
'instruction': instruction,
}
self.throw_parse_error("End of line while looking for "
"%s" % looking_for, **kwargs)
if actual not in self.whitespace_chars:
self.throw_parse_error("Expecting whitespace before %s, "
"got '%s'." % (looking_for, actual))
ret = ""
actual = self.peek_char()
while (actual is not None and actual in self.whitespace_chars):
self.take_char()
ret += actual
actual = self.peek_char()
return ret
def peek_whitespace(self):
ret = ""
char = self.peek_char()
if char is None:
return char
count = 0
while char is not None and char in self.whitespace_chars:
ret += char
count += 1
char = self.peek_char(skip=count)
return ret
def parse_word(self, expected, require_whitespace=True):
self.parse_whitespace("'%s'" % expected, require=require_whitespace)
length = len(expected)
actual = self.peek_to_whitespace()
if actual == expected:
self.take_chars(length)
return expected
if actual is None:
self.throw_eol(expected)
self.throw_expecting_error(expected, actual)
def peek_to_whitespace(self):
ret = ""
char = self.peek_char()
if char is None:
return char
count = 0
while char is not None and char not in self.whitespace_chars:
ret += char
count += 1
char = self.peek_char(skip=count)
return ret
def take_to_whitespace(self, looking_for, instruction=None):
text = self.peek_to_whitespace()
if text is None:
kwargs = {}
if instruction is not None:
kwargs = {
'cls': InstructionParseError,
'instruction': instruction}
self.throw_parse_error("End of line while looking for %s"
% looking_for, **kwargs)
self.take_chars(len(text))
return text
def peek_float(self, looking_for):
self.parse_whitespace(looking_for)
ret = self._parse_integer()
conv_fn = int
if ret == "":
self.throw_parse_error("Expecting a float, got '%s'" %
self.peek_to_whitespace())
if self.peek_char(skip=len(ret)) == ".":
conv_fn = float
ret2 = self._parse_integer(skip=len(ret)+1)
if ret2 == "":
self.throw_parse_error("Expecting a float, got '%s'" %
self.peek_to_whitespace())
ret += "." + ret2
try:
fl = conv_fn(ret)
except ValueError:
self.throw_parse_error("Expecting a float, got '%s'" % ret)
return (fl, ret)
def _parse_integer(self, skip=0):
i = skip
ret = ""
while True:
char = self.peek_char(skip=i)
if char not in self.digit_chars:
break
ret += char
i = i+1
return ret
def parse_integer(self):
ret = self._parse_integer()
if ret == "":
self.throw_parse_error("Expected an integer, found %s" %
self.peek_to_whitespace())
self.take_chars(len(ret))
return ret
def take_to_newline(self):
text = self.current_line[self.index:]
self.index += len(text)
return text
def parse_comment_line(self):
whitespace = self.peek_whitespace()
if whitespace is None:
return ""
if self.peek_char(skip=len(whitespace)) is None:
return ""
if self.peek_char(skip=len(whitespace)) != "#":
return None
self.parse_whitespace(None, require=False)
comment = self.current_line[self.index:]
self.index += len(comment)
return comment
bzr-builder-0.7.3/setup.py 0000755 0000000 0000000 00000001237 11740346202 013607 0 ustar 0000000 0000000 #!/usr/bin/python
from info import *
from distutils.core import setup
if __name__ == '__main__':
version = bzr_plugin_version[:3]
version_string = ".".join([str(x) for x in version])
setup(name="bzr-builder",
version=version_string,
description="Turn a recipe in to a bzr branch",
author="James Westby",
author_email="james.westby@canonical.com",
license="GNU GPL v3",
url="http://launchpad.net/bzr-builder",
packages=['bzrlib.plugins.builder',
'bzrlib.plugins.builder.tests',
],
package_dir={'bzrlib.plugins.builder': '.'},
)
bzr-builder-0.7.3/tests/ 0000755 0000000 0000000 00000000000 11740346202 013231 5 ustar 0000000 0000000 bzr-builder-0.7.3/tests/__init__.py 0000644 0000000 0000000 00000003023 11740346202 015340 0 ustar 0000000 0000000 # bzr-builder: a bzr plugin to constuct trees based on recipes
# Copyright 2009 Canonical Ltd.
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more details.
# You should have received a copy of the GNU General Public License along
# with this program. If not, see .
import os
from unittest import TestSuite
from bzrlib.tests import (
TestUtil,
)
try:
from bzrlib.tests.features import (
Feature,
)
except ImportError: # bzr < 2.5
from bzrlib.tests import (
Feature,
)
class _PristineTarFeature(Feature):
def feature_name(self):
return '/usr/bin/pristine-tar'
def _probe(self):
return os.path.exists("/usr/bin/pristine-tar")
PristineTarFeature = _PristineTarFeature()
def test_suite():
loader = TestUtil.TestLoader()
suite = TestSuite()
testmod_names = [
'blackbox',
'deb_util',
'deb_version',
'recipe',
]
suite.addTest(loader.loadTestsFromModuleNames(["%s.test_%s" % (__name__, i)
for i in testmod_names]))
return suite
bzr-builder-0.7.3/tests/test_blackbox.py 0000644 0000000 0000000 00000075634 11740346202 016446 0 ustar 0000000 0000000 # bzr-builder: a bzr plugin to constuct trees based on recipes
# Copyright 2009-2010 Canonical Ltd.
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more details.
# You should have received a copy of the GNU General Public License along
# with this program. If not, see .
from base64 import standard_b64encode
import os
import signal
import subprocess
from textwrap import dedent
from bzrlib import (
export as _mod_export,
osutils,
workingtree,
)
from bzrlib.branch import Branch
from bzrlib.tests import (
TestCaseWithTransport,
)
from bzrlib.plugins.builder.tests import (
Feature,
PristineTarFeature,
)
try:
from debian import changelog
except ImportError:
from debian_bundle import changelog
class _NotUnderFakeRootFeature(Feature):
def _probe(self):
return 'FAKEROOTKEY' not in os.environ
def feature_name(self):
return 'not running inside fakeroot'
NotUnderFakeRootFeature = _NotUnderFakeRootFeature()
def make_pristine_tar_delta(dest, tarball_path):
"""Create a pristine-tar delta for a tarball.
:param dest: Directory to generate pristine tar delta for
:param tarball_path: Path to the tarball
:return: pristine-tarball
"""
def subprocess_setup():
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
# If tarball_path is relative, the cwd=dest parameter to Popen will make
# pristine-tar faaaail. pristine-tar doesn't use the VFS either, so we
# assume local paths.
tarball_path = osutils.abspath(tarball_path)
command = ["pristine-tar", "gendelta", tarball_path, "-"]
proc = subprocess.Popen(command, stdout=subprocess.PIPE,
cwd=dest, preexec_fn=subprocess_setup,
stderr=subprocess.PIPE)
(stdout, stderr) = proc.communicate()
if proc.returncode != 0:
raise Exception("Generating delta from tar failed: %s" % stderr)
return stdout
class BlackboxBuilderTests(TestCaseWithTransport):
if not getattr(TestCaseWithTransport, "assertPathDoesNotExist", None):
# Compatibility with bzr < 2.4
def assertPathDoesNotExist(self, path):
self.failIfExists(path)
def assertPathExists(self, path):
self.failUnlessExists(path)
def setUp(self):
super(BlackboxBuilderTests, self).setUp()
# Replace DEBEMAIL and DEBFULLNAME so that they are known values
# for the changelog checks.
overrideEnv = getattr(self, "overrideEnv", None)
if overrideEnv is None:
# Pre-2.3 versions of Bazaar did not provide self.overrideEnv
overrideEnv = self._captureVar
overrideEnv("DEBEMAIL", "maint@maint.org")
overrideEnv("DEBFULLNAME", "M. Maintainer")
def _get_file_contents(self, filename, mode="r"):
"""Helper to read contents of a file
Use check_file_content instead to just assert the contents match."""
self.assertPathExists(filename)
f = open(filename, mode)
try:
return f.read()
finally:
f.close()
def test_cmd_builder_exists(self):
self.run_bzr("build --help")
def test_cmd_builder_requires_recipe_file_argument(self):
err = self.run_bzr("build", retcode=3)[1]
self.assertEqual("bzr: ERROR: command 'build' requires argument "
"LOCATION\n", err)
def test_cmd_builder_requires_working_dir_argument(self):
err = self.run_bzr("build recipe", retcode=3)[1]
self.assertEqual("bzr: ERROR: command 'build' requires argument "
"WORKING_DIRECTORY\n", err)
def test_cmd_builder_nonexistant_recipe(self):
err = self.run_bzr("build recipe working", retcode=3)[1]
self.assertEqual("bzr: ERROR: Specified recipe does not exist: "
"recipe\n", err)
def test_cmd_builder_simple_recipe(self):
self.build_tree_contents([("recipe", "# bzr-builder format 0.1 "
"deb-version 1\nsource\n")])
source = self.make_branch_and_tree("source")
self.build_tree(["source/a"])
source.add(["a"])
revid = source.commit("one")
self.run_bzr("build -q recipe working")
self.assertPathExists("working/a")
tree = workingtree.WorkingTree.open("working")
self.assertEqual(revid, tree.last_revision())
self.assertPathExists("working/bzr-builder.manifest")
self.check_file_contents("working/bzr-builder.manifest",
"# bzr-builder format 0.1 deb-version 1\nsource revid:%s\n"
% revid)
def test_cmd_builder_simple_branch(self):
source = self.make_branch_and_tree("source")
self.build_tree(["source/a"])
source.add(["a"])
revid = source.commit("one")
self.run_bzr("build -q source working")
self.assertPathExists("working/a")
tree = workingtree.WorkingTree.open("working")
self.assertEqual(revid, tree.last_revision())
self.assertPathExists("working/bzr-builder.manifest")
self.check_file_contents("working/bzr-builder.manifest",
"# bzr-builder format 0.4\nsource revid:%s\n"
% revid)
def test_cmd_builder_simple_recipe_no_debversion(self):
self.build_tree_contents([("recipe", "# bzr-builder format 0.1\n"
"source\n")])
source = self.make_branch_and_tree("source")
self.build_tree(["source/a"])
source.add(["a"])
revid = source.commit("one")
self.run_bzr("build -q recipe working")
self.assertPathExists("working/a")
tree = workingtree.WorkingTree.open("working")
self.assertEqual(revid, tree.last_revision())
self.assertPathExists("working/bzr-builder.manifest")
self.check_file_contents("working/bzr-builder.manifest",
"# bzr-builder format 0.1\nsource revid:%s\n"
% revid)
def test_cmd_builder_manifest(self):
self.build_tree_contents([("recipe", "# bzr-builder format 0.1 "
"deb-version 1\nsource\n")])
source = self.make_branch_and_tree("source")
self.build_tree(["source/a"])
source.add(["a"])
revid = source.commit("one")
self.run_bzr("build -q recipe working --manifest manifest")
self.assertPathExists("working/a")
self.assertPathExists("manifest")
self.check_file_contents("manifest", "# bzr-builder format 0.1 "
"deb-version 1\nsource revid:%s\n" % revid)
def test_cmd_builder_if_changed_does_not_exist(self):
self.build_tree_contents([("recipe", "# bzr-builder format 0.1 "
"deb-version 1\nsource\n")])
source = self.make_branch_and_tree("source")
self.build_tree(["source/a"])
source.add(["a"])
source.commit("one")
out, err = self.run_bzr("build recipe working "
"--if-changed-from manifest")
def test_cmd_builder_if_changed_not_changed(self):
source = self.make_branch_and_tree("source")
self.build_tree(["source/a"])
source.add(["a"])
revid = source.commit("one")
self.build_tree_contents([("recipe", "# bzr-builder format 0.1 "
"deb-version 1\nsource 1\n")])
self.build_tree_contents([("old-manifest", "# bzr-builder format 0.1 "
"deb-version 1\nsource revid:%s\n" % revid)])
out, err = self.run_bzr("build recipe working --manifest manifest "
"--if-changed-from old-manifest")
self.assertPathDoesNotExist("working")
self.assertPathDoesNotExist("manifest")
self.assertEqual("Unchanged\n", err)
def test_cmd_builder_if_changed_changed(self):
source = self.make_branch_and_tree("source")
self.build_tree(["source/a"])
source.add(["a"])
revid = source.commit("one")
self.build_tree_contents([("recipe", "# bzr-builder format 0.1 "
"deb-version 1\nsource 1\n")])
self.build_tree_contents([("old-manifest", "# bzr-builder format 0.1 "
"deb-version 1\nsource revid:foo\n")])
out, err = self.run_bzr("build -q recipe working --manifest manifest "
"--if-changed-from old-manifest")
self.assertPathExists("working/a")
self.assertPathExists("manifest")
self.check_file_contents("manifest", "# bzr-builder format 0.1 "
"deb-version 1\nsource revid:%s\n" % revid)
def test_cmd_dailydeb(self):
self.requireFeature(NotUnderFakeRootFeature)
#TODO: define a test feature for debuild and require it here.
source = self.make_branch_and_tree("source")
self.build_tree(["source/a", "source/debian/"])
self.build_tree_contents([("source/debian/rules",
"#!/usr/bin/make -f\nclean:\n"),
("source/debian/control",
"Source: foo\nMaintainer: maint maint@maint.org\n\n"
"Package: foo\nArchitecture: all\n")])
source.add(["a", "debian/", "debian/rules", "debian/control"])
revid = source.commit("one")
self.build_tree_contents([("test.recipe", "# bzr-builder format 0.1 "
"deb-version 1\nsource 1\n")])
out, err = self.run_bzr("dailydeb -q test.recipe working "
"--manifest manifest")
self.assertPathDoesNotExist("working/a")
package_root = "working/test-1/"
self.assertPathExists(os.path.join(package_root, "a"))
self.assertPathExists(os.path.join(package_root,
"debian/bzr-builder.manifest"))
self.assertPathExists("manifest")
self.check_file_contents("manifest", "# bzr-builder format 0.1 "
"deb-version 1\nsource revid:%s\n" % revid)
self.check_file_contents(os.path.join(package_root,
"debian/bzr-builder.manifest"),
"# bzr-builder format 0.1 deb-version 1\nsource revid:%s\n"
% revid)
cl_contents = self._get_file_contents(
os.path.join(package_root, "debian/changelog"))
self.assertEqual("foo (1) lucid; urgency=low\n",
cl_contents.splitlines(True)[0])
def test_cmd_dailydeb_no_work_dir(self):
self.requireFeature(NotUnderFakeRootFeature)
#TODO: define a test feature for debuild and require it here.
if getattr(self, "permit_dir", None) is not None:
self.permit_dir('/') # Allow the made working dir to be accessed.
source = self.make_branch_and_tree("source")
self.build_tree(["source/a", "source/debian/"])
self.build_tree_contents([("source/debian/rules",
"#!/usr/bin/make -f\nclean:\n"),
("source/debian/control",
"Source: foo\nMaintainer: maint maint@maint.org\n\n"
"Package: foo\nArchitecture: all\n")])
source.add(["a", "debian/", "debian/rules", "debian/control"])
source.commit("one")
self.build_tree_contents([("test.recipe", "# bzr-builder format 0.1 "
"deb-version 1\nsource 1\n")])
out, err = self.run_bzr("dailydeb -q test.recipe "
"--manifest manifest")
def test_cmd_dailydeb_if_changed_from_non_existant(self):
self.requireFeature(NotUnderFakeRootFeature)
#TODO: define a test feature for debuild and require it here.
if getattr(self, "permit_dir", None) is not None:
self.permit_dir('/') # Allow the made working dir to be accessed.
source = self.make_branch_and_tree("source")
self.build_tree(["source/a", "source/debian/"])
self.build_tree_contents([("source/debian/rules",
"#!/usr/bin/make -f\nclean:\n"),
("source/debian/control",
"Source: foo\nMaintainer: maint maint@maint.org\n"
"\nPackage: foo\nArchitecture: all\n")])
source.add(["a", "debian/", "debian/rules", "debian/control"])
source.commit("one")
self.build_tree_contents([("test.recipe", "# bzr-builder format 0.1 "
"deb-version 1\nsource 1\n")])
out, err = self.run_bzr("dailydeb -q test.recipe "
"--manifest manifest --if-changed-from bar")
def make_upstream_version(self, version, contents,
pristine_tar_format=None):
upstream = self.make_branch_and_tree("upstream")
self.build_tree_contents(contents)
upstream.smart_add([upstream.basedir])
revprops = {}
if pristine_tar_format is not None:
_mod_export.export(upstream, "export")
if pristine_tar_format == "gz":
tarfile_path = "export.tar.gz"
_mod_export.export(upstream, tarfile_path, "tgz")
revprops["deb-pristine-delta"] = standard_b64encode(
make_pristine_tar_delta(
"export", "export.tar.gz"))
elif pristine_tar_format == "bz2":
tarfile_path = "export.tar.bz2"
_mod_export.export(upstream, tarfile_path, "tbz2")
revprops["deb-pristine-delta-bz2"] = standard_b64encode(
make_pristine_tar_delta(
"export", "export.tar.bz2"))
else:
raise AssertionError("unknown pristine tar format %s" %
pristine_tar_format)
else:
tarfile_path = "export.tar.gz"
_mod_export.export(upstream, tarfile_path, "tgz")
tarfile_sha1 = osutils.sha_file_by_name(tarfile_path)
revid = upstream.commit("import upstream %s" % version,
revprops=revprops)
source = Branch.open("source")
source.repository.fetch(upstream.branch.repository)
source.tags.set_tag("upstream-%s" % version, revid)
return tarfile_sha1
def make_simple_package(self, path):
source = self.make_branch_and_tree(path)
self.build_tree([os.path.join(path, "a"),
os.path.join(path, "debian/")])
cl_contents = ("package (0.1-1) unstable; urgency=low\n * foo\n"
" -- maint Tue, 04 Aug 2009 "
"10:03:10 +0100\n")
self.build_tree_contents([
(os.path.join(path, "debian/rules"),
"#!/usr/bin/make -f\nclean:\n"),
(os.path.join(path, "debian/control"),
"Source: package\nMaintainer: maint maint@maint.org\n\n"
"Package: package\nArchitecture: all\n"),
(os.path.join(path, "debian/changelog"),
cl_contents)
])
source.add(["a", "debian/", "debian/rules", "debian/control",
"debian/changelog"])
source.commit("one")
return source
def test_cmd_dailydeb_no_build(self):
self.make_simple_package("source")
self.build_tree_contents([("test.recipe", "# bzr-builder format 0.1 "
"deb-version 1\nsource 1\n")])
out, err = self.run_bzr("dailydeb -q test.recipe "
"--manifest manifest --no-build working")
new_cl_contents = ("package (1) unstable; urgency=low\n\n"
" * Auto build.\n\n -- M. Maintainer ")
actual_cl_contents = self._get_file_contents(
"working/test-1/debian/changelog")
self.assertStartsWith(actual_cl_contents, new_cl_contents)
for fn in os.listdir("working"):
self.assertFalse(fn.endswith(".changes"))
def test_cmd_dailydeb_with_package_from_changelog(self):
#TODO: define a test feature for debuild and require it here.
self.requireFeature(NotUnderFakeRootFeature)
self.make_simple_package("source")
self.build_tree_contents([("test.recipe", "# bzr-builder format 0.1 "
"deb-version 1\nsource 1\n")])
out, err = self.run_bzr("dailydeb -q test.recipe "
"--manifest manifest --if-changed-from bar working")
new_cl_contents = ("package (1) unstable; urgency=low\n\n"
" * Auto build.\n\n -- M. Maintainer ")
actual_cl_contents = self._get_file_contents(
"working/test-1/debian/changelog")
self.assertStartsWith(actual_cl_contents, new_cl_contents)
def test_cmd_dailydeb_with_version_from_changelog(self):
self.requireFeature(NotUnderFakeRootFeature)
self.make_simple_package("source")
self.build_tree_contents([("test.recipe", "# bzr-builder format 0.4 "
"deb-version {debversion}-2\nsource 1\n")])
out, err = self.run_bzr(
"dailydeb --allow-fallback-to-native -q test.recipe working")
new_cl_contents = ("package (0.1-2) unstable; urgency=low\n\n"
" * Auto build.\n\n -- M. Maintainer ")
cl = changelog.Changelog(self._get_file_contents(
"working/test-{debversion}-2/debian/changelog"))
self.assertEquals("0.1-1-2", str(cl._blocks[0].version))
def test_cmd_dailydeb_with_version_from_other_branch_changelog(self):
self.requireFeature(NotUnderFakeRootFeature)
source = self.make_simple_package("source")
other = self.make_simple_package("other")
cl_contents = ("package (0.4-1) unstable; urgency=low\n * foo\n"
" -- maint Tue, 04 Aug 2009 "
"10:03:10 +0100\n")
self.build_tree_contents([
(os.path.join("other", "debian", "changelog"), cl_contents)
])
other.commit("new changelog entry")
self.build_tree_contents([("test.recipe", "# bzr-builder format 0.4 "
"deb-version {debversion:other}.2\n"
"source 1\n"
"nest other other other\n")])
out, err = self.run_bzr(
"dailydeb --allow-fallback-to-native -q test.recipe working")
cl = changelog.Changelog(self._get_file_contents(
"working/test-{debversion:other}.2/debian/changelog"))
self.assertEquals("0.4-1.2", str(cl._blocks[0].version))
def test_cmd_dailydeb_with_upstream_version_from_changelog(self):
self.requireFeature(NotUnderFakeRootFeature)
self.make_simple_package("source")
self.build_tree_contents([("test.recipe", "# bzr-builder format 0.1 "
"deb-version {debupstream}-2\nsource 1\n")])
out, err = self.run_bzr(
"dailydeb --allow-fallback-to-native -q test.recipe working")
new_cl_contents = ("package (0.1-2) unstable; urgency=low\n\n"
" * Auto build.\n\n -- M. Maintainer ")
actual_cl_contents = self._get_file_contents(
"working/test-{debupstream}-2/debian/changelog")
self.assertStartsWith(actual_cl_contents, new_cl_contents)
def test_cmd_dailydeb_with_append_version(self):
self.requireFeature(NotUnderFakeRootFeature)
self.make_simple_package("source")
self.build_tree_contents([("test.recipe", "# bzr-builder format 0.1 "
"deb-version 1\nsource 1\n")])
out, err = self.run_bzr("dailydeb -q test.recipe working "
"--append-version ~ppa1")
new_cl_contents = ("package (1~ppa1) unstable; urgency=low\n\n"
" * Auto build.\n\n -- M. Maintainer ")
actual_cl_contents = self._get_file_contents(
"working/test-1/debian/changelog")
self.assertStartsWith(actual_cl_contents, new_cl_contents)
def test_cmd_dailydeb_with_nonascii_maintainer_in_changelog(self):
user_enc = osutils.get_user_encoding()
try:
os.environ["DEBFULLNAME"] = u"Micha\u25c8 Sawicz".encode(user_enc)
except UnicodeEncodeError:
self.skip("Need user encoding other than %r to test maintainer "
"name from environment" % (user_enc,))
self.make_simple_package("source")
self.build_tree_contents([("test.recipe", "# bzr-builder format 0.1 "
"deb-version 1\nsource 1\n")])
out, err = self.run_bzr("dailydeb -q test.recipe working")
new_cl_contents = ("package (1) unstable; urgency=low\n\n"
" * Auto build.\n\n"
" -- Micha\xe2\x97\x88 Sawicz ")
actual_cl_contents = self._get_file_contents(
"working/test-1/debian/changelog")
self.assertStartsWith(actual_cl_contents, new_cl_contents)
def test_cmd_dailydeb_with_invalid_version(self):
source = self.make_branch_and_tree("source")
self.build_tree(["source/a"])
self.build_tree_contents([
("source/debian/", None),
("source/debian/control",
"Source: foo\nMaintainer: maint maint@maint.org\n")
])
source.add(["a", "debian", "debian/control"])
revid = source.commit("one")
self.build_tree_contents([("test.recipe", "# bzr-builder format 0.1 "
"deb-version $\nsource 1\n"),
])
err = self.run_bzr("dailydeb -q test.recipe working", retcode=3)[1]
self.assertContainsRe(err, "bzr: ERROR: Invalid deb-version: \\$: "
"(Could not parse version: \\$|Invalid version string '\\$')\n")
def test_cmd_dailydeb_with_safe(self):
self.make_simple_package("source")
self.build_tree_contents([("test.recipe", "# bzr-builder format 0.3 "
"deb-version 1\nsource 1\nrun something bad")])
out, err = self.run_bzr("dailydeb -q test.recipe working --safe",
retcode=3)
self.assertContainsRe(err, "The 'run' instruction is forbidden.$")
def make_simple_quilt_package(self):
source = self.make_simple_package("source")
self.build_tree(["source/debian/source/"])
self.build_tree_contents([
("source/debian/source/format", "3.0 (quilt)\n")])
source.add(["debian/source", "debian/source/format"])
source.commit("set source format")
return source
def test_cmd_dailydeb_missing_orig_tarball(self):
self.make_simple_quilt_package()
self.build_tree_contents([("test.recipe", "# bzr-builder format 0.3 "
"deb-version 1-1\nsource 1\n")])
out, err = self.run_bzr(
"dailydeb -q test.recipe working",
retcode=3)
self.assertEquals("", out)
self.assertEquals(
'bzr: ERROR: Unable to find the upstream source. '
'Import it as tag upstream-1 or build with '
'--allow-fallback-to-native.\n', err)
def test_cmd_dailydeb_with_orig_tarball(self):
self.requireFeature(NotUnderFakeRootFeature)
self.make_simple_package("source")
self.make_upstream_version("0.1", [("upstream/file", "content\n")])
self.build_tree_contents([("test.recipe", "# bzr-builder format 0.3 "
"deb-version 0.1-1\nsource\n")])
out, err = self.run_bzr(
"dailydeb -q test.recipe working",
retcode=0)
self.assertPathExists("working/package_0.1.orig.tar.gz")
self.assertPathExists("working/package_0.1-1.diff.gz")
def test_cmd_dailydeb_with_pristine_orig_gz_tarball(self):
self.requireFeature(NotUnderFakeRootFeature)
self.requireFeature(PristineTarFeature)
self.make_simple_package("source")
pristine_tar_sha1 = self.make_upstream_version("0.1",
[("upstream/file", "content\n")], pristine_tar_format="gz")
self.build_tree_contents([("test.recipe", "# bzr-builder format 0.3 "
"deb-version 0.1-1\nsource\n")])
out, err = self.run_bzr("dailydeb -q test.recipe working",
retcode=0)
self.assertPathExists("working/package_0.1.orig.tar.gz")
self.assertPathExists("working/package_0.1-1.diff.gz")
self.assertEquals(
osutils.sha_file_by_name("working/package_0.1.orig.tar.gz"),
pristine_tar_sha1)
def test_cmd_dailydeb_with_pristine_orig_bz2_tarball(self):
self.requireFeature(NotUnderFakeRootFeature)
self.requireFeature(PristineTarFeature)
self.make_simple_quilt_package()
pristine_tar_sha1 = self.make_upstream_version("0.1", [
("upstream/file", "content\n"),
("upstream/a", "contents of source/a\n")],
pristine_tar_format="bz2")
self.build_tree_contents([("test.recipe", "# bzr-builder format 0.3 "
"deb-version 0.1-1\nsource\n")])
wt = workingtree.WorkingTree.open("source")
self.build_tree([("source/upstream/")])
self.build_tree_contents([
("source/upstream/file", "content\n"),
("source/upstream/a", "contents of source/a\n")])
wt.add(["upstream", "upstream/file", "upstream/a"])
out, err = self.run_bzr("dailydeb -q test.recipe working",
retcode=0)
self.assertPathExists("working/package_0.1.orig.tar.bz2")
self.assertPathExists("working/package_0.1-1.debian.tar.gz")
self.assertEquals(
osutils.sha_file_by_name("working/package_0.1.orig.tar.bz2"),
pristine_tar_sha1)
def test_cmd_dailydeb_allow_fallback_to_native_with_orig_tarball(self):
self.requireFeature(PristineTarFeature)
self.make_simple_quilt_package()
pristine_tar_sha1 = self.make_upstream_version("0.1", [
("upstream/file", "content\n"),
("upstream/a", "contents of source/a\n")],
pristine_tar_format="bz2")
self.build_tree_contents([("test.recipe", "# bzr-builder format 0.3 "
"deb-version 0.1-1\nsource\n")])
wt = workingtree.WorkingTree.open("source")
self.build_tree([("source/upstream/")])
self.build_tree_contents([
("source/upstream/file", "content\n"),
("source/upstream/a", "contents of source/a\n")])
wt.add(["upstream", "upstream/file", "upstream/a"])
out, err = self.run_bzr(
"dailydeb --allow-fallback-to-native -q test.recipe working",
retcode=0)
self.assertPathExists("working/package_0.1.orig.tar.bz2")
self.assertPathExists("working/package_0.1-1.debian.tar.gz")
self.assertEquals("3.0 (quilt)\n",
open("source/debian/source/format", "r").read())
self.assertEquals(
osutils.sha_file_by_name("working/package_0.1.orig.tar.bz2"),
pristine_tar_sha1)
def test_cmd_dailydeb_force_native(self):
self.requireFeature(NotUnderFakeRootFeature)
self.make_simple_quilt_package()
self.build_tree_contents([("test.recipe", "# bzr-builder format 0.3 "
"deb-version 1\nsource 2\n")])
out, err = self.run_bzr(
"dailydeb --allow-fallback-to-native -q test.recipe working",
retcode=0)
self.assertFileEqual("3.0 (native)\n",
"working/test-1/debian/source/format")
def test_cmd_dailydeb_force_native_empty_series(self):
self.requireFeature(NotUnderFakeRootFeature)
source = self.make_simple_quilt_package()
self.build_tree(['source/debian/patches/'])
self.build_tree_contents([
("test.recipe", "# bzr-builder format 0.3 "
"deb-version 1\nsource 3\n"),
("source/debian/patches/series", "\n")])
source.add(["debian/patches", "debian/patches/series"])
source.commit("add patches")
out, err = self.run_bzr(
"dailydeb --allow-fallback-to-native -q test.recipe working",
retcode=0)
self.assertFileEqual("3.0 (native)\n",
"working/test-1/debian/source/format")
self.assertPathDoesNotExist("working/test-1/debian/patches")
def test_cmd_dailydeb_force_native_apply_quilt(self):
self.requireFeature(NotUnderFakeRootFeature)
source = self.make_simple_quilt_package()
self.build_tree(["source/debian/patches/"])
patch = dedent(
"""diff -ur a/thefile b/thefile
--- a/thefile 2010-12-05 20:14:22.000000000 +0100
+++ b/thefile 2010-12-05 20:14:26.000000000 +0100
@@ -1 +1 @@
-old-contents
+new-contents
""")
self.build_tree_contents([
("source/thefile", "old-contents\n"),
("source/debian/patches/series", "01_foo.patch"),
("source/debian/patches/01_foo.patch", patch)])
source.add(["thefile", "debian/patches", "debian/patches/series",
"debian/patches/01_foo.patch"])
source.commit("add patch")
self.build_tree_contents([("test.recipe", "# bzr-builder format 0.3 "
"deb-version 1\nsource\n")])
out, err = self.run_bzr(
"dailydeb --allow-fallback-to-native -q test.recipe working",
retcode=0)
self.assertFileEqual("3.0 (native)\n",
"working/test-1/debian/source/format")
self.assertFileEqual("new-contents\n",
"working/test-1/thefile")
self.assertPathDoesNotExist("working/test-1/debian/patches")
def test_cmd_dailydeb_force_native_apply_quilt_failure(self):
source = self.make_simple_quilt_package()
self.build_tree(["source/debian/patches/"])
patch = dedent(
"""diff -ur a/thefile b/thefile
--- a/thefile 2010-12-05 20:14:22.000000000 +0100
+++ b/thefile 2010-12-05 20:14:26.000000000 +0100
@@ -1 +1 @@
-old-contents
+new-contents
""")
self.build_tree_contents([
("source/thefile", "contents\n"),
("source/debian/patches/series", "01_foo.patch"),
("source/debian/patches/01_foo.patch", patch)])
source.add(["thefile", "debian/patches", "debian/patches/series",
"debian/patches/01_foo.patch"])
source.commit("add patch")
self.build_tree_contents([("test.recipe", "# bzr-builder format 0.3 "
"deb-version 1-1\nsource 3\n")])
out, err = self.run_bzr(
"dailydeb --allow-fallback-to-native -q test.recipe working",
retcode=3)
self.assertContainsRe(err, "bzr: ERROR: Failed to apply quilt patches")
def test_unknown_source_format(self):
source = self.make_simple_package("source")
self.build_tree(["source/debian/source/"])
self.build_tree_contents([
("source/debian/source/format", "2.0\n")])
source.add(["debian/source", "debian/source/format"])
source.commit("set source format")
self.build_tree_contents([("test.recipe", "# bzr-builder format 0.3 "
"deb-version 1-1\nsource\n")])
out, err = self.run_bzr(
"dailydeb --allow-fallback-to-native -q test.recipe working", retcode=3)
self.assertEquals(err, "bzr: ERROR: Unknown source format 2.0\n")
bzr-builder-0.7.3/tests/test_deb_util.py 0000644 0000000 0000000 00000002064 11740346202 016433 0 ustar 0000000 0000000 # bzr-builder: a bzr plugin to construct trees based on recipes
# Copyright 2009 Canonical Ltd.
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more details.
# You should have received a copy of the GNU General Public License along
# with this program. If not, see .
from bzrlib.plugins.builder.deb_util import target_from_dput
from bzrlib.tests import (
TestCase,
)
class TestTargetFromDPut(TestCase):
def test_default_ppa(self):
self.assertEqual(('team-name', 'ppa'), target_from_dput('ppa:team-name'))
def test_named_ppa(self):
self.assertEqual(('team', 'ppa2'), target_from_dput('ppa:team/ppa2'))
bzr-builder-0.7.3/tests/test_deb_version.py 0000644 0000000 0000000 00000045066 11740346202 017154 0 ustar 0000000 0000000 # bzr-builder: a bzr plugin to constuct trees based on recipes
# Copyright 2009-2011 Canonical Ltd.
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more details.
# You should have received a copy of the GNU General Public License along
# with this program. If not, see .
import datetime
import textwrap
from bzrlib import (
errors,
)
from bzrlib.tests import (
TestCase,
TestCaseWithTransport,
)
from bzrlib.plugins.builder.deb_version import (
DebUpstreamBaseVariable,
DebUpstreamVariable,
DebVersionVariable,
SubstitutionUnavailable,
check_expanded_deb_version,
version_extract_base,
substitute_branch_vars,
substitute_time,
)
from bzrlib.plugins.builder.recipe import (
BaseRecipeBranch,
RecipeBranch,
resolve_revisions,
)
try:
from debian import changelog
except ImportError:
# In older versions of python-debian the main package was named
# debian_bundle
from debian_bundle import changelog
class ResolveRevisionsTests(TestCaseWithTransport):
def test_unchanged(self):
source =self.make_branch_and_tree("source")
revid = source.commit("one")
branch1 = BaseRecipeBranch("source", "{revno}", 0.2, revspec="1")
branch2 = BaseRecipeBranch("source", "{revno}", 0.2,
revspec="revid:%s" % revid)
self.assertEqual(False, resolve_revisions(branch1,
if_changed_from=branch2,
substitute_branch_vars=substitute_branch_vars))
self.assertEqual("source", branch1.url)
self.assertEqual(revid, branch1.revid)
self.assertEqual("1", branch1.revspec)
self.assertEqual("1", branch1.deb_version)
def test_unchanged_not_explicit(self):
source =self.make_branch_and_tree("source")
revid = source.commit("one")
branch1 = BaseRecipeBranch("source", "{revno}", 0.2)
branch2 = BaseRecipeBranch("source", "{revno}", 0.2,
revspec="revid:%s" % revid)
self.assertEqual(False, resolve_revisions(branch1,
if_changed_from=branch2,
substitute_branch_vars=substitute_branch_vars))
self.assertEqual("source", branch1.url)
self.assertEqual(revid, branch1.revid)
self.assertEqual(None, branch1.revspec)
self.assertEqual("1", branch1.deb_version)
def test_unchanged_multilevel(self):
source =self.make_branch_and_tree("source")
revid = source.commit("one")
branch1 = BaseRecipeBranch("source", "{revno}", 0.2)
branch2 = RecipeBranch("nested1", "source")
branch3 = RecipeBranch("nested2", "source")
branch2.nest_branch("bar", branch3)
branch1.nest_branch("foo", branch2)
branch4 = BaseRecipeBranch("source", "{revno}", 0.2,
revspec="revid:%s" % revid)
branch5 = RecipeBranch("nested1", "source",
revspec="revid:%s" % revid)
branch6 = RecipeBranch("nested2", "source",
revspec="revid:%s" % revid)
branch5.nest_branch("bar", branch6)
branch4.nest_branch("foo", branch5)
self.assertEqual(False, resolve_revisions(branch1,
if_changed_from=branch4,
substitute_branch_vars=substitute_branch_vars))
self.assertEqual("source", branch1.url)
self.assertEqual(revid, branch1.revid)
self.assertEqual(None, branch1.revspec)
self.assertEqual("1", branch1.deb_version)
def test_changed(self):
source =self.make_branch_and_tree("source")
revid = source.commit("one")
branch1 = BaseRecipeBranch("source", "{revno}", 0.2, revspec="1")
branch2 = BaseRecipeBranch("source", "{revno}", 0.2,
revspec="revid:foo")
self.assertEqual(True, resolve_revisions(branch1,
if_changed_from=branch2,
substitute_branch_vars=substitute_branch_vars))
self.assertEqual("source", branch1.url)
self.assertEqual(revid, branch1.revid)
self.assertEqual("1", branch1.revspec)
self.assertEqual("1", branch1.deb_version)
def test_changed_shape(self):
source =self.make_branch_and_tree("source")
revid = source.commit("one")
branch1 = BaseRecipeBranch("source", "{revno}", 0.2, revspec="1")
branch2 = BaseRecipeBranch("source", "{revno}", 0.2,
revspec="revid:%s" % revid)
branch3 = RecipeBranch("nested", "source")
branch1.nest_branch("foo", branch3)
self.assertEqual(True, resolve_revisions(branch1,
if_changed_from=branch2,
substitute_branch_vars=substitute_branch_vars))
self.assertEqual("source", branch1.url)
self.assertEqual(revid, branch1.revid)
self.assertEqual("1", branch1.revspec)
self.assertEqual("1", branch1.deb_version)
def test_changed_command(self):
source =self.make_branch_and_tree("source")
source.commit("one")
branch1 = BaseRecipeBranch("source", "{revno}", 0.2)
branch2 = BaseRecipeBranch("source", "{revno}", 0.2)
branch1.run_command("touch test1")
branch2.run_command("touch test2")
self.assertEqual(True, resolve_revisions(branch1,
if_changed_from=branch2,
substitute_branch_vars=substitute_branch_vars))
self.assertEqual("source", branch1.url)
def test_unchanged_command(self):
source =self.make_branch_and_tree("source")
source.commit("one")
branch1 = BaseRecipeBranch("source", "{revno}", 0.2)
branch2 = BaseRecipeBranch("source", "{revno}", 0.2)
branch1.run_command("touch test1")
branch2.run_command("touch test1")
self.assertEqual(False, resolve_revisions(branch1,
if_changed_from=branch2,
substitute_branch_vars=substitute_branch_vars))
self.assertEqual("source", branch1.url)
def test_substitute(self):
source =self.make_branch_and_tree("source")
revid1 = source.commit("one")
source.commit("two")
branch1 = BaseRecipeBranch("source",
"{revno}-{revno:packaging}", 0.2, revspec="1")
branch2 = RecipeBranch("packaging", "source")
branch1.nest_branch("debian", branch2)
self.assertEqual(True, resolve_revisions(branch1,
substitute_branch_vars=substitute_branch_vars))
self.assertEqual("source", branch1.url)
self.assertEqual(revid1, branch1.revid)
self.assertEqual("1", branch1.revspec)
self.assertEqual("1-2", branch1.deb_version)
def test_substitute_supports_debupstream(self):
# resolve_revisions should leave debupstream parameters alone and not
# complain.
source =self.make_branch_and_tree("source")
source.commit("one")
source.commit("two")
branch1 = BaseRecipeBranch("source", "{debupstream}-{revno}", 0.2)
resolve_revisions(branch1,
substitute_branch_vars=substitute_branch_vars)
self.assertEqual("{debupstream}-2", branch1.deb_version)
def test_subsitute_not_fully_expanded(self):
source =self.make_branch_and_tree("source")
source.commit("one")
source.commit("two")
branch1 = BaseRecipeBranch("source", "{revno:packaging}", 0.2)
resolve_revisions(branch1, substitute_branch_vars=substitute_branch_vars)
self.assertRaises(errors.BzrCommandError, check_expanded_deb_version, branch1)
def test_substitute_svn_not_svn(self):
br = self.make_branch("source")
source = br.create_checkout("checkout")
source.commit("one")
source.commit("two")
branch1 = BaseRecipeBranch("source", "foo-{svn-revno}", 0.4)
e = self.assertRaises(errors.BzrCommandError, resolve_revisions,
branch1, None, substitute_branch_vars)
self.assertTrue(str(e).startswith("unable to expand {svn-revno} "),
e)
def test_substitute_svn(self):
br = self.make_branch("source")
source = br.create_checkout("checkout")
source.commit("one")
source.commit("two",
rev_id="svn-v4:be7e6eca-30d4-0310-a8e5-ac0d63af7070:trunk:5344")
branch1 = BaseRecipeBranch("source", "foo-{svn-revno}", 0.4)
resolve_revisions(branch1, substitute_branch_vars=substitute_branch_vars)
self.assertEqual("foo-5344", branch1.deb_version)
def test_substitute_git_not_git(self):
source = self.make_branch_and_tree("source")
source.commit("one")
source.commit("two")
branch1 = BaseRecipeBranch("source", "foo-{git-commit}", 0.4)
e = self.assertRaises(errors.BzrCommandError, resolve_revisions,
branch1, None, substitute_branch_vars)
self.assertTrue(str(e).startswith("unable to expand {git-commit} "),
e)
def test_substitute_git(self):
source = self.make_branch_and_tree("source")
source.commit("one",
rev_id="git-v1:a029d7b2cc83c26a53d8b2a24fa12c340fcfac58")
branch1 = BaseRecipeBranch("source", "foo-{git-commit}", 0.4)
resolve_revisions(branch1,
substitute_branch_vars=substitute_branch_vars)
self.assertEqual("foo-a029d7b", branch1.deb_version)
def test_latest_tag(self):
source = self.make_branch_and_tree("source")
revid = source.commit("one")
source.branch.tags.set_tag("millbank", revid)
source.commit("two")
branch1 = BaseRecipeBranch("source", "foo-{latest-tag}", 0.4)
resolve_revisions(branch1,
substitute_branch_vars=substitute_branch_vars)
self.assertEqual("foo-millbank", branch1.deb_version)
def test_latest_tag_no_tag(self):
source = self.make_branch_and_tree("source")
revid = source.commit("one")
source.commit("two")
branch1 = BaseRecipeBranch("source", "foo-{latest-tag}", 0.4)
e = self.assertRaises(errors.BzrCommandError,
resolve_revisions, branch1,
substitute_branch_vars=substitute_branch_vars)
self.assertTrue(str(e).startswith("No tags set on branch None mainline"),
e)
def test_substitute_revdate(self):
br = self.make_branch("source")
source = br.create_checkout("checkout")
source.commit("one")
source.commit("two", timestamp=1307708628, timezone=0)
branch1 = BaseRecipeBranch("source", "foo-{revdate}", 0.4)
resolve_revisions(branch1,
substitute_branch_vars=substitute_branch_vars)
self.assertEqual("foo-20110610", branch1.deb_version)
def test_substitute_revtime(self):
br = self.make_branch("source")
source = br.create_checkout("checkout")
source.commit("one")
source.commit("two", timestamp=1307708628, timezone=0)
branch1 = BaseRecipeBranch("source", "foo-{revtime}", 0.4)
resolve_revisions(branch1,
substitute_branch_vars=substitute_branch_vars)
self.assertEqual("foo-201106101223", branch1.deb_version)
class DebUpstreamVariableTests(TestCase):
def write_changelog(self, version):
contents = textwrap.dedent("""
package (%s) experimental; urgency=low
* Initial release. (Closes: #XXXXXX)
-- Jelmer Vernooij Thu, 19 May 2011 10:07:41 +0100
""" % version)[1:]
return changelog.Changelog(file=contents)
def test_empty_changelog(self):
var = DebUpstreamVariable.from_changelog(None, changelog.Changelog())
self.assertRaises(SubstitutionUnavailable, var.get)
def test_version(self):
var = DebUpstreamVariable.from_changelog(None,
self.write_changelog("2.3"))
self.assertEquals("2.3", var.get())
def test_epoch(self):
# The epoch is (currently) ignored by {debupstream}.
var = DebUpstreamVariable.from_changelog(None,
self.write_changelog("2:2.3"))
self.assertEquals("2.3", var.get())
def test_base_without_snapshot(self):
var = DebUpstreamBaseVariable.from_changelog(None,
self.write_changelog("2.4"))
self.assertEquals("2.4+", var.get())
def test_base_with_svn_snapshot(self):
var = DebUpstreamBaseVariable.from_changelog(None,
self.write_changelog("2.4~svn4"))
self.assertEquals("2.4~", var.get())
def test_base_with_bzr_snapshot(self):
var = DebUpstreamBaseVariable.from_changelog(None,
self.write_changelog("2.4+bzr343"))
self.assertEquals("2.4+", var.get())
class VersionExtractBaseTests(TestCase):
def test_simple_extract(self):
self.assertEquals("2.4", version_extract_base("2.4"))
self.assertEquals("2.4+foobar", version_extract_base("2.4+foobar"))
def test_with_bzr(self):
self.assertEquals("2.4+", version_extract_base("2.4+bzr32"))
self.assertEquals("2.4~", version_extract_base("2.4~bzr32"))
def test_with_git(self):
self.assertEquals("2.4+", version_extract_base("2.4+git20101010"))
self.assertEquals("2.4~", version_extract_base("2.4~gitaabbccdd"))
def test_with_svn(self):
self.assertEquals("2.4+", version_extract_base("2.4+svn45"))
self.assertEquals("2.4~", version_extract_base("2.4~svn45"))
def test_with_dfsg(self):
self.assertEquals("2.4+", version_extract_base("2.4+bzr32+dfsg1"))
self.assertEquals("2.4~", version_extract_base("2.4~bzr32+dfsg.1"))
self.assertEquals("2.4~", version_extract_base("2.4~bzr32.dfsg.1"))
self.assertEquals("2.4~", version_extract_base("2.4~bzr32dfsg.1"))
self.assertEquals("1.6~", version_extract_base("1.6~git20120320.dfsg.1"))
class DebVersionVariableTests(TestCase):
def write_changelog(self, version):
contents = textwrap.dedent("""
package (%s) experimental; urgency=low
* Initial release. (Closes: #XXXXXX)
-- Jelmer Vernooij Thu, 19 May 2011 10:07:41 +0100
""" % version)[1:]
return changelog.Changelog(file=contents)
def test_empty_changelog(self):
var = DebVersionVariable.from_changelog(None, changelog.Changelog())
self.assertRaises(SubstitutionUnavailable, var.get)
def test_simple(self):
var = DebVersionVariable.from_changelog(
None, self.write_changelog("2.3-1"))
self.assertEquals("2.3-1", var.get())
def test_epoch(self):
var = DebVersionVariable.from_changelog(
None, self.write_changelog("4:2.3-1"))
self.assertEquals("4:2.3-1", var.get())
class RecipeBranchTests(TestCaseWithTransport):
def test_substitute_time(self):
time = datetime.datetime.utcfromtimestamp(1)
base_branch = BaseRecipeBranch("base_url", "1-{time}", 0.2)
substitute_time(base_branch, time)
self.assertEqual("1-197001010000", base_branch.deb_version)
substitute_time(base_branch, time)
self.assertEqual("1-197001010000", base_branch.deb_version)
def test_substitute_date(self):
time = datetime.datetime.utcfromtimestamp(1)
base_branch = BaseRecipeBranch("base_url", "1-{date}", 0.2)
substitute_time(base_branch, time)
self.assertEqual("1-19700101", base_branch.deb_version)
substitute_time(base_branch, time)
self.assertEqual("1-19700101", base_branch.deb_version)
def test_substitute_branch_vars(self):
base_branch = BaseRecipeBranch("base_url", "1", 0.2)
wt = self.make_branch_and_tree("br")
revid = wt.commit("acommit")
substitute_branch_vars(base_branch, None, wt.branch, revid)
self.assertEqual("1", base_branch.deb_version)
substitute_branch_vars(base_branch, None, wt.branch, revid)
self.assertEqual("1", base_branch.deb_version)
base_branch = BaseRecipeBranch("base_url", "{revno}", 0.2)
substitute_branch_vars(base_branch, None, wt.branch, revid)
self.assertEqual("1", base_branch.deb_version)
base_branch = BaseRecipeBranch("base_url", "{revno}", 0.2)
substitute_branch_vars(base_branch, "foo", wt.branch, revid)
self.assertEqual("{revno}", base_branch.deb_version)
substitute_branch_vars(base_branch, "foo", wt.branch, revid)
self.assertEqual("{revno}", base_branch.deb_version)
base_branch = BaseRecipeBranch("base_url", "{revno:foo}", 0.2)
substitute_branch_vars(base_branch, "foo", wt.branch, revid)
self.assertEqual("1", base_branch.deb_version)
def test_substitute_branch_vars_debupstream(self):
wt = self.make_branch_and_tree("br")
revid1 = wt.commit("acommit")
cl_contents = ("package (0.1-1) unstable; urgency=low\n * foo\n"
" -- maint Tue, 04 Aug 2009 "
"10:03:10 +0100\n")
self.build_tree_contents(
[("br/debian/", ), ('br/debian/changelog', cl_contents)])
wt.add(['debian', 'debian/changelog'])
revid2 = wt.commit("with changelog")
base_branch = BaseRecipeBranch("base_url", "{debupstream}", 0.4)
# No changelog file, so no substitution
substitute_branch_vars(base_branch, None, wt.branch, revid1)
self.assertEqual("{debupstream}", base_branch.deb_version)
substitute_branch_vars(base_branch, None, wt.branch, revid2)
self.assertEqual("0.1", base_branch.deb_version)
base_branch = BaseRecipeBranch("base_url", "{debupstream:tehname}", 0.4)
substitute_branch_vars(base_branch, "tehname", wt.branch, revid2)
self.assertEqual("0.1", base_branch.deb_version)
def test_substitute_branch_vars_debupstream_pre_0_4(self):
wt = self.make_branch_and_tree("br")
cl_contents = ("package (0.1-1) unstable; urgency=low\n * foo\n"
" -- maint Tue, 04 Aug 2009 "
"10:03:10 +0100\n")
self.build_tree_contents(
[("br/debian/", ), ('br/debian/changelog', cl_contents)])
wt.add(['debian', 'debian/changelog'])
revid = wt.commit("with changelog")
# In recipe format < 0.4 {debupstream} gets replaced from the resulting
# tree, not from the branch vars.
base_branch = BaseRecipeBranch("base_url", "{debupstream}", 0.2)
substitute_branch_vars(base_branch, None, wt.branch, revid)
self.assertEqual("{debupstream}", base_branch.deb_version)
bzr-builder-0.7.3/tests/test_recipe.py 0000644 0000000 0000000 00000154233 11740346202 016121 0 ustar 0000000 0000000 # bzr-builder: a bzr plugin to constuct trees based on recipes
# Copyright 2009 Canonical Ltd.
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more details.
# You should have received a copy of the GNU General Public License along
# with this program. If not, see .
import os
from bzrlib import (
errors,
transport,
workingtree,
)
from bzrlib.tests import (
TestCaseInTempDir,
TestCaseWithTransport,
)
from bzrlib.plugins.builder.recipe import (
BaseRecipeBranch,
build_tree,
ensure_basedir,
InstructionParseError,
ForbiddenInstructionError,
MERGE_INSTRUCTION,
NEST_INSTRUCTION,
NEST_PART_INSTRUCTION,
pull_or_branch,
RecipeParser,
RecipeBranch,
RecipeParseError,
RUN_INSTRUCTION,
SAFE_INSTRUCTIONS,
USAGE,
)
class RecipeParserTests(TestCaseInTempDir):
deb_version = "0.1-{revno}"
basic_header = ("# bzr-builder format 0.3 deb-version "
+ deb_version +"\n")
basic_branch = "http://foo.org/"
basic_header_and_branch = basic_header + basic_branch + "\n"
def get_recipe(self, recipe_text, **kwargs):
return RecipeParser(recipe_text).parse(**kwargs)
def assertParseError(self, line, char, problem, callable, *args,
**kwargs):
exc = self.assertRaises(RecipeParseError, callable, *args, **kwargs)
self.assertEqual(problem, exc.problem)
self.assertEqual(line, exc.line)
self.assertEqual(char, exc.char)
self.assertEqual("recipe", exc.filename)
return exc
def assertInstructionParseError(self, line, char, problem, instruction,
callable, *args, **kwargs):
exc = self.assertParseError(line, char, problem, callable, *args,
**kwargs)
self.assertIsInstance(exc, InstructionParseError),
self.assertEqual(USAGE[instruction], exc.usage)
def check_recipe_branch(self, branch, name, url, revspec=None,
num_child_branches=0, revid=None):
self.assertEqual(name, branch.name)
self.assertEqual(url, branch.url)
self.assertEqual(revspec, branch.revspec)
self.assertEqual(revid, branch.revid)
self.assertEqual(num_child_branches, len(branch.child_branches))
def check_base_recipe_branch(self, branch, url, revspec=None,
num_child_branches=0, revid=None, deb_version=deb_version):
self.check_recipe_branch(branch, None, url, revspec=revspec,
num_child_branches=num_child_branches, revid=revid)
self.assertEqual(deb_version, branch.deb_version)
def test_parses_most_basic(self):
self.get_recipe(self.basic_header_and_branch)
def test_parses_missing_deb_version(self):
header = "# bzr-builder format 0.3\n"
branch = "http://foo.org/\n"
self.get_recipe(header + branch)
def tests_rejects_non_comment_to_start(self):
self.assertParseError(1, 1, "Expecting '#', got 'b'",
self.get_recipe, "bzr-builder")
def tests_rejects_wrong_format_definition(self):
self.assertParseError(1, 3, "Expecting 'bzr-builder', "
"got 'bzr-nothing'", self.get_recipe, "# bzr-nothing")
def tests_rejects_no_format_definition(self):
self.assertParseError(1, 3, "End of line while looking for "
"'bzr-builder'", self.get_recipe, "# \n")
def tests_rejects_no_format_definition_eof(self):
self.assertParseError(1, 3, "End of line while looking for "
"'bzr-builder'", self.get_recipe, "# ")
def tests_rejects_wrong_format_version_marker(self):
self.assertParseError(1, 15, "Expecting 'format', got 'aaaa'",
self.get_recipe, "# bzr-builder aaaa")
def test_rejects_invalid_format_version(self):
self.assertParseError(1, 22, "Expecting a float, got 'foo'",
self.get_recipe, "# bzr-builder format foo")
def test_rejects_invalid_format_version2(self):
self.assertParseError(1, 22, "Expecting a float, got '1.'",
self.get_recipe, "# bzr-builder format 1.")
def test_unknown_format_version(self):
self.assertParseError(1, 22, "Unknown format: '10000'",
self.get_recipe, "# bzr-builder format 10000 deb-version 1\n")
def test_rejects_invalid_deb_version_marker(self):
self.assertParseError(1, 26, "Expecting 'deb-version', "
"got 'deb'", self.get_recipe, "# bzr-builder format 0.1 deb")
def tests_rejects_no_deb_version_value(self):
self.assertParseError(1, 37, "End of line while looking for "
"a value for 'deb-version'", self.get_recipe,
"# bzr-builder format 0.1 deb-version")
def tests_rejects_extra_text_after_deb_version(self):
self.assertParseError(1, 40, "Expecting the end of the line, "
"got 'foo'", self.get_recipe,
"# bzr-builder format 0.1 deb-version 1 foo")
def tests_rejects_indented_base_branch(self):
self.assertParseError(2, 3, "Not allowed to indent unless after "
"a 'nest' line", self.get_recipe,
self.basic_header + " http://foo.org/")
def tests_rejects_text_after_base_branch(self):
self.assertParseError(2, 19, "Expecting the end of the line, "
"got 'foo'", self.get_recipe,
self.basic_header + "http://foo.org/ 2 foo")
def tests_rejects_unknown_instruction(self):
self.assertParseError(3, 1, "Expecting 'merge', 'nest', 'nest-part' "
"or 'run', got 'cat'", self.get_recipe,
self.basic_header + "http://foo.org/\n" + "cat")
def test_rejects_merge_no_name(self):
self.assertInstructionParseError(3, 7, "End of line while looking for "
"the branch id", MERGE_INSTRUCTION, self.get_recipe,
self.basic_header_and_branch + "merge ")
def test_rejects_merge_no_name_no_space(self):
self.assertInstructionParseError(3, 6, "End of line while looking for "
"the branch id", MERGE_INSTRUCTION, self.get_recipe,
self.basic_header_and_branch + "merge")
def test_rejects_merge_no_url(self):
self.assertInstructionParseError(3, 11, "End of line while looking for"
" the branch url", MERGE_INSTRUCTION, self.get_recipe,
self.basic_header_and_branch + "merge foo ")
def test_rejects_text_at_end_of_merge_line(self):
self.assertInstructionParseError(3, 17,
"Expecting the end of the line, got 'bar'",
MERGE_INSTRUCTION, self.get_recipe,
self.basic_header_and_branch + "merge foo url 2 bar")
def test_rejects_nest_part_no_name(self):
self.assertInstructionParseError(3, 11, "End of line while looking for"
" the branch id", NEST_PART_INSTRUCTION, self.get_recipe,
self.basic_header_and_branch + "nest-part ")
def test_rejects_nest_part_no_url(self):
self.assertInstructionParseError(3, 15, "End of line while looking for"
" the branch url", NEST_PART_INSTRUCTION, self.get_recipe,
self.basic_header_and_branch + "nest-part foo ")
def test_rejects_nest_part_no_subpath(self):
self.assertInstructionParseError(3, 22, "End of line while looking for"
" the subpath to merge", NEST_PART_INSTRUCTION,
self.get_recipe,
self.basic_header_and_branch + "nest-part foo url:// ")
def test_rejects_nest_part_no_subpath_no_space(self):
self.assertInstructionParseError(3, 21, "End of line while looking for"
" the subpath to merge", NEST_PART_INSTRUCTION,
self.get_recipe,
self.basic_header_and_branch + "nest-part foo url://")
def test_rejects_nest_no_name(self):
self.assertInstructionParseError(3, 6, "End of line while looking for"
" the branch id", NEST_INSTRUCTION, self.get_recipe,
self.basic_header_and_branch + "nest ")
def test_rejects_nest_no_url(self):
self.assertInstructionParseError(3, 10,
"End of line while looking for the branch url",
NEST_INSTRUCTION, self.get_recipe,
self.basic_header_and_branch + "nest foo ")
def test_rejects_nest_no_location(self):
self.assertInstructionParseError(3, 14,
"End of line while looking for the location to nest",
NEST_INSTRUCTION, self.get_recipe,
self.basic_header_and_branch + "nest foo url ")
def test_rejects_text_at_end_of_nest_line(self):
self.assertInstructionParseError(3, 20,
"Expecting the end of the line, got 'baz'",
NEST_INSTRUCTION, self.get_recipe,
self.basic_header_and_branch + "nest foo url bar 2 baz")
def test_rejects_indent_after_first_branch(self):
self.assertParseError(3, 3, "Not allowed to indent unless after "
"a 'nest' line", self.get_recipe,
self.basic_header_and_branch + " nest foo url bar")
def test_rejects_indent_after_merge(self):
self.assertParseError(4, 3, "Not allowed to indent unless after "
"a 'nest' line", self.get_recipe,
self.basic_header_and_branch + "merge foo url\n"
+ " nest baz url bar")
def test_rejects_tab_indent(self):
self.assertParseError(4, 3, "Indents may not be done by tabs",
self.get_recipe,
self.basic_header_and_branch + "nest foo url bar\n"
+ "\t\tmerge baz url")
def test_rejects_odd_space_indent(self):
self.assertParseError(4, 2, "Indent not a multiple of two spaces",
self.get_recipe,
self.basic_header_and_branch + "nest foo url bar\n"
+ " merge baz url")
def test_rejects_four_space_indent(self):
self.assertParseError(4, 5, "Indented by more than two spaces "
"at once", self.get_recipe,
self.basic_header_and_branch + "nest foo url bar\n"
+ " merge baz url")
def test_rejects_empty_recipe(self):
self.assertParseError(3, 1, "Empty recipe", self.get_recipe,
self.basic_header)
def test_rejects_non_unique_ids(self):
self.assertParseError(4, 7, "'foo' was already used to identify "
"a branch.", self.get_recipe,
self.basic_header_and_branch + "merge foo url\n"
+ "merge foo other-url\n")
def test_rejects_run_with_no_instruction(self):
self.assertInstructionParseError(3, 4,
"End of line while looking for the command",
RUN_INSTRUCTION, self.get_recipe,
self.basic_header_and_branch + "run\n")
def test_builds_simplest_recipe(self):
base_branch = self.get_recipe(self.basic_header_and_branch)
self.check_base_recipe_branch(base_branch, "http://foo.org/")
def test_skips_comments(self):
base_branch = self.get_recipe(self.basic_header + "# comment\n"
+ "http://foo.org/\n")
self.check_base_recipe_branch(base_branch, "http://foo.org/")
def test_builds_recipe_with_merge(self):
base_branch = self.get_recipe(self.basic_header_and_branch
+ "merge bar http://bar.org")
self.check_base_recipe_branch(base_branch, "http://foo.org/",
num_child_branches=1)
child_branch, location = base_branch.child_branches[0].as_tuple()
self.assertEqual(None, location)
self.check_recipe_branch(child_branch, "bar", "http://bar.org")
def test_builds_recipe_with_nest_part(self):
base_branch = self.get_recipe(self.basic_header_and_branch
+ "nest-part bar http://bar.org some/path")
self.check_base_recipe_branch(base_branch, "http://foo.org/",
num_child_branches=1)
instruction = base_branch.child_branches[0]
self.assertEqual(None, instruction.nest_path)
self.check_recipe_branch(
instruction.recipe_branch, "bar", "http://bar.org")
self.assertEqual("some/path", instruction.subpath)
self.assertEqual(None, instruction.target_subdir)
def test_builds_recipe_with_nest_part_subdir(self):
base_branch = self.get_recipe(self.basic_header_and_branch
+ "nest-part bar http://bar.org some/path target-subdir")
self.check_base_recipe_branch(base_branch, "http://foo.org/",
num_child_branches=1)
instruction = base_branch.child_branches[0]
self.assertEqual(None, instruction.nest_path)
self.check_recipe_branch(
instruction.recipe_branch, "bar", "http://bar.org")
self.assertEqual("some/path", instruction.subpath)
self.assertEqual("target-subdir", instruction.target_subdir)
def test_builds_recipe_with_nest_part_subdir_and_revspec(self):
base_branch = self.get_recipe(self.basic_header_and_branch
+ "nest-part bar http://bar.org some/path target-subdir 1234")
self.check_base_recipe_branch(base_branch, "http://foo.org/",
num_child_branches=1)
instruction = base_branch.child_branches[0]
self.assertEqual(None, instruction.nest_path)
self.check_recipe_branch(
instruction.recipe_branch, "bar", "http://bar.org", "1234")
self.assertEqual("some/path", instruction.subpath)
self.assertEqual("target-subdir", instruction.target_subdir)
def test_builds_recipe_with_nest(self):
base_branch = self.get_recipe(self.basic_header_and_branch
+ "nest bar http://bar.org baz")
self.check_base_recipe_branch(base_branch, "http://foo.org/",
num_child_branches=1)
child_branch, location = base_branch.child_branches[0].as_tuple()
self.assertEqual("baz", location)
self.check_recipe_branch(child_branch, "bar", "http://bar.org")
def test_builds_recipe_with_nest_then_merge(self):
base_branch = self.get_recipe(self.basic_header_and_branch
+ "nest bar http://bar.org baz\nmerge zam lp:zam")
self.check_base_recipe_branch(base_branch, "http://foo.org/",
num_child_branches=2)
child_branch, location = base_branch.child_branches[0].as_tuple()
self.assertEqual("baz", location)
self.check_recipe_branch(child_branch, "bar", "http://bar.org")
child_branch, location = base_branch.child_branches[1].as_tuple()
self.assertEqual(None, location)
self.check_recipe_branch(child_branch, "zam", "lp:zam")
def test_builds_recipe_with_merge_then_nest(self):
base_branch = self.get_recipe(self.basic_header_and_branch
+ "merge zam lp:zam\nnest bar http://bar.org baz")
self.check_base_recipe_branch(base_branch, "http://foo.org/",
num_child_branches=2)
child_branch, location = base_branch.child_branches[0].as_tuple()
self.assertEqual(None, location)
self.check_recipe_branch(child_branch, "zam", "lp:zam")
child_branch, location = base_branch.child_branches[1].as_tuple()
self.assertEqual("baz", location)
self.check_recipe_branch(child_branch, "bar", "http://bar.org")
def test_builds_a_merge_in_to_a_nest(self):
base_branch = self.get_recipe(self.basic_header_and_branch
+ "nest bar http://bar.org baz\n merge zam lp:zam")
self.check_base_recipe_branch(base_branch, "http://foo.org/",
num_child_branches=1)
child_branch, location = base_branch.child_branches[0].as_tuple()
self.assertEqual("baz", location)
self.check_recipe_branch(child_branch, "bar", "http://bar.org",
num_child_branches=1)
child_branch, location = child_branch.child_branches[0].as_tuple()
self.assertEqual(None, location)
self.check_recipe_branch(child_branch, "zam", "lp:zam")
def tests_builds_nest_into_a_nest(self):
base_branch = self.get_recipe(self.basic_header_and_branch
+ "nest bar http://bar.org baz\n nest zam lp:zam zoo")
self.check_base_recipe_branch(base_branch, "http://foo.org/",
num_child_branches=1)
child_branch, location = base_branch.child_branches[0].as_tuple()
self.assertEqual("baz", location)
self.check_recipe_branch(child_branch, "bar", "http://bar.org",
num_child_branches=1)
child_branch, location = child_branch.child_branches[0].as_tuple()
self.assertEqual("zoo", location)
self.check_recipe_branch(child_branch, "zam", "lp:zam")
def tests_builds_recipe_with_revspecs(self):
base_branch = self.get_recipe(self.basic_header
+ "http://foo.org/ revid:a\n"
+ "nest bar http://bar.org baz tag:b\n"
+ "merge zam lp:zam 2")
self.check_base_recipe_branch(base_branch, "http://foo.org/",
num_child_branches=2, revspec="revid:a")
instruction = base_branch.child_branches[0]
child_branch = instruction.recipe_branch
location = instruction.nest_path
self.assertEqual("baz", location)
self.check_recipe_branch(child_branch, "bar", "http://bar.org",
revspec="tag:b")
child_branch, location = base_branch.child_branches[1].as_tuple()
self.assertEqual(None, location)
self.check_recipe_branch(child_branch, "zam", "lp:zam", revspec="2")
def test_builds_recipe_with_commands(self):
base_branch = self.get_recipe(self.basic_header
+ "http://foo.org/\n"
+ "run touch test \n")
self.check_base_recipe_branch(base_branch, "http://foo.org/",
num_child_branches=1)
child_branch, command = base_branch.child_branches[0].as_tuple()
self.assertEqual(None, child_branch)
self.assertEqual("touch test", command)
def test_accepts_blank_line_during_nest(self):
base_branch = self.get_recipe(self.basic_header_and_branch
+ "nest foo http://bar.org bar\n merge baz baz.org\n\n"
" merge zap zap.org\n")
self.check_base_recipe_branch(base_branch, self.basic_branch,
num_child_branches=1)
nested_branch, location = base_branch.child_branches[0].as_tuple()
self.assertEqual("bar", location)
self.check_recipe_branch(nested_branch, "foo", "http://bar.org",
num_child_branches=2)
child_branch, location = nested_branch.child_branches[0].as_tuple()
self.assertEqual(None, location)
self.check_recipe_branch(child_branch, "baz", "baz.org")
child_branch, location = nested_branch.child_branches[1].as_tuple()
self.assertEqual(None, location)
self.check_recipe_branch(child_branch, "zap", "zap.org")
def test_accepts_blank_line_at_start_of_nest(self):
base_branch = self.get_recipe(self.basic_header_and_branch
+ "nest foo http://bar.org bar\n\n merge baz baz.org\n")
self.check_base_recipe_branch(base_branch, self.basic_branch,
num_child_branches=1)
nested_branch, location = base_branch.child_branches[0].as_tuple()
self.assertEqual("bar", location)
self.check_recipe_branch(nested_branch, "foo", "http://bar.org",
num_child_branches=1)
child_branch, location = nested_branch.child_branches[0].as_tuple()
self.assertEqual(None, location)
self.check_recipe_branch(child_branch, "baz", "baz.org")
def test_accepts_blank_line_as_only_thing_in_nest(self):
base_branch = self.get_recipe(self.basic_header_and_branch
+ "nest foo http://bar.org bar\n\nmerge baz baz.org\n")
self.check_base_recipe_branch(base_branch, self.basic_branch,
num_child_branches=2)
nested_branch, location = base_branch.child_branches[0].as_tuple()
self.assertEqual("bar", location)
self.check_recipe_branch(nested_branch, "foo", "http://bar.org")
child_branch, location = base_branch.child_branches[1].as_tuple()
self.assertEqual(None, location)
self.check_recipe_branch(child_branch, "baz", "baz.org")
def test_accepts_comment_line_with_any_number_of_spaces(self):
base_branch = self.get_recipe(self.basic_header_and_branch
+ "nest foo http://bar.org bar\n #foo\nmerge baz baz.org\n")
self.check_base_recipe_branch(base_branch, self.basic_branch,
num_child_branches=2)
nested_branch, location = base_branch.child_branches[0].as_tuple()
self.assertEqual("bar", location)
self.check_recipe_branch(nested_branch, "foo", "http://bar.org")
child_branch, location = base_branch.child_branches[1].as_tuple()
self.assertEqual(None, location)
self.check_recipe_branch(child_branch, "baz", "baz.org")
def test_old_format_rejects_run(self):
header = ("# bzr-builder format 0.1 deb-version "
+ self.deb_version +"\n")
self.assertParseError(3, 1, "Expecting 'merge' or 'nest', got 'run'"
, self.get_recipe, header + "http://foo.org/\n"
+ "run touch test \n")
def test_old_format_rejects_nest_part(self):
header = ("# bzr-builder format 0.2 deb-version "
+ self.deb_version +"\n")
self.assertParseError(3, 1, "Expecting 'merge', 'nest' or 'run', "
"got 'nest-part'" , self.get_recipe,
header + "http://foo.org/\nnest-part packaging foo test \n")
def test_error_on_forbidden_instructions(self):
exc = self.assertParseError(3, 1, "The 'run' instruction is "
"forbidden", self.get_recipe, self.basic_header_and_branch
+ "run touch test\n",
permitted_instructions=[SAFE_INSTRUCTIONS])
self.assertTrue(isinstance(exc, ForbiddenInstructionError))
self.assertEqual("run", exc.instruction_name)
def test_error_on_duplicate_path(self):
exc = self.assertInstructionParseError(3, 15,
"The path '.' is a duplicate of the one used on line 1.",
NEST_INSTRUCTION, self.get_recipe,
self.basic_header_and_branch + "nest nest url .\n")
def test_error_on_duplicate_path_with_another_nest(self):
exc = self.assertInstructionParseError(4, 16,
"The path 'foo' is a duplicate of the one used on line 3.",
NEST_INSTRUCTION, self.get_recipe,
self.basic_header_and_branch + "nest nest url foo\n"
+ "nest nest2 url foo\n")
def test_duplicate_path_check_uses_normpath(self):
exc = self.assertInstructionParseError(3, 15,
"The path 'foo/..' is a duplicate of the one used on line 1.",
NEST_INSTRUCTION, self.get_recipe,
self.basic_header_and_branch + "nest nest url foo/..\n")
def test_error_absolute_path(self):
exc = self.assertInstructionParseError(3, 15,
"Absolute paths are not allowed: /etc/passwd",
NEST_INSTRUCTION, self.get_recipe,
self.basic_header_and_branch + "nest nest url /etc/passwd\n")
def test_error_simple_parent_dir(self):
exc = self.assertInstructionParseError(3, 15,
"Paths outside the current directory are not allowed: ../foo",
NEST_INSTRUCTION, self.get_recipe,
self.basic_header_and_branch + "nest nest url ../foo\n")
def test_error_complex_parent_dir(self):
exc = self.assertInstructionParseError(3, 15,
"Paths outside the current directory are not allowed:"
" ./foo/../..", NEST_INSTRUCTION, self.get_recipe,
self.basic_header_and_branch + "nest nest url ./foo/../..\n")
class BuildTreeTests(TestCaseWithTransport):
if not getattr(TestCaseWithTransport, "assertPathExists", None):
# Compatibility with bzr < 2.4
def assertPathExists(self, path):
self.failUnlessExists(path)
def test_ensure_basedir(self):
to_transport = transport.get_transport("a")
ensure_basedir(to_transport)
self.assertPathExists("a")
ensure_basedir(to_transport)
self.assertPathExists("a")
e = self.assertRaises(errors.BzrCommandError, ensure_basedir,
transport.get_transport("b/c"))
self.assertTrue('Parent of "' in str(e))
self.assertTrue('" does not exist.' in str(e))
def test_build_tree_single_branch(self):
source = self.make_branch_and_tree("source")
revid = source.commit("one")
base_branch = BaseRecipeBranch("source", "1", 0.2)
build_tree(base_branch, "target")
self.assertPathExists("target")
tree = workingtree.WorkingTree.open("target")
self.assertEqual(revid, tree.last_revision())
self.assertEqual(revid, base_branch.revid)
def test_build_tree_single_branch_dir_not_branch(self):
source = self.make_branch_and_tree("source")
revid = source.commit("one")
# We just create the target as a directory
os.mkdir("target")
base_branch = BaseRecipeBranch("source", "1", 0.2)
build_tree(base_branch, "target")
self.assertPathExists("target")
tree = workingtree.WorkingTree.open("target")
self.assertEqual(revid, tree.last_revision())
self.assertEqual(revid, base_branch.revid)
def test_build_tree_single_branch_existing_branch(self):
source = self.make_branch_and_tree("source")
revid = source.commit("one")
self.make_branch_and_tree("target")
base_branch = BaseRecipeBranch("source", "1", 0.2)
build_tree(base_branch, "target")
self.assertPathExists("target")
tree = workingtree.WorkingTree.open("target")
self.assertEqual(revid, tree.last_revision())
self.assertEqual(revid, base_branch.revid)
def make_source_branch(self, relpath):
"""Make a branch with one file and one commit."""
source1 = self.make_branch_and_tree(relpath)
self.build_tree([relpath + "/a"])
source1.add(["a"])
source1.commit("one")
return source1
def test_build_tree_nested(self):
source1_rev_id = self.make_source_branch("source1").last_revision()
source2_rev_id = self.make_source_branch("source2").last_revision()
base_branch = BaseRecipeBranch("source1", "1", 0.2)
nested_branch = RecipeBranch("nested", "source2")
base_branch.nest_branch("sub", nested_branch)
build_tree(base_branch, "target")
self.assertPathExists("target")
tree = workingtree.WorkingTree.open("target")
self.assertEqual([source1_rev_id], tree.get_parent_ids())
tree = workingtree.WorkingTree.open("target/sub")
self.assertEqual([source2_rev_id], tree.get_parent_ids())
self.assertEqual(source1_rev_id, base_branch.revid)
self.assertEqual(source2_rev_id, nested_branch.revid)
def test_build_tree_merged(self):
source1 = self.make_source_branch("source1")
source1_rev_id = source1.last_revision()
source2 = source1.bzrdir.sprout("source2").open_workingtree()
self.build_tree_contents([("source2/a", "other change")])
source2_rev_id = source2.commit("one")
base_branch = BaseRecipeBranch("source1", "1", 0.2)
merged_branch = RecipeBranch("merged", "source2")
base_branch.merge_branch(merged_branch)
build_tree(base_branch, "target")
self.assertPathExists("target")
tree = workingtree.WorkingTree.open("target")
last_revid = tree.last_revision()
last_revtree = tree.branch.repository.revision_tree(last_revid)
self.assertEqual([source1_rev_id, source2_rev_id],
last_revtree.get_parent_ids())
self.check_file_contents("target/a", "other change")
self.assertEqual(source1_rev_id, base_branch.revid)
self.assertEqual(source2_rev_id, merged_branch.revid)
def test_build_tree_merge_unrelated(self):
"""Make a branch with one file and one commit."""
source1 = self.make_branch_and_tree("source1")
self.build_tree_contents([("source1/a", "file a")])
source1.add(["a"])
source1_rev_id = source1.commit("one")
source2 = self.make_branch_and_tree("source2")
self.build_tree_contents([("source2/b", "file b")])
source2.add(["b"])
source2_rev_id = source2.commit("one")
base_branch = BaseRecipeBranch("source1", "1", 0.2)
merged_branch = RecipeBranch("merged", "source2")
base_branch.merge_branch(merged_branch)
build_tree(base_branch, "target")
self.assertPathExists("target")
tree = workingtree.WorkingTree.open("target")
last_revid = tree.last_revision()
last_revtree = tree.branch.repository.revision_tree(last_revid)
self.assertEqual([source1_rev_id, source2_rev_id],
last_revtree.get_parent_ids())
self.check_file_contents("target/a", "file a")
self.check_file_contents("target/b", "file b")
self.assertEqual(source1_rev_id, base_branch.revid)
self.assertEqual(source2_rev_id, merged_branch.revid)
def test_build_tree_implicit_dir(self):
# Branches nested into non-existant directories trigger creation of
# those directories.
source1 = self.make_source_branch("source1")
source2 = self.make_source_branch("source2")
source3 = self.make_source_branch("source3")
self.build_tree_contents([
("source2/file", "new file"),
("source3/yetanotherfile", "rugby")])
source2.add(["file"])
source2.commit("two")
source3.add(["yetanotherfile"])
source3.commit("three")
base_branch = BaseRecipeBranch("source1", "1", 0.4)
merged_branch_2 = RecipeBranch("merged", "source2")
# Merge source2 into path implicit/b
base_branch.nest_part_branch(merged_branch_2, ".",
target_subdir="implicit/b")
# Merge source3 into path implicit/moreimplicit/c
merged_branch_3 = RecipeBranch("merged", "source3")
base_branch.nest_part_branch(merged_branch_3, ".",
target_subdir="moreimplicit/another/c")
build_tree(base_branch, "target")
self.check_file_contents("target/implicit/b/file", "new file")
self.check_file_contents("target/moreimplicit/another/c/yetanotherfile",
"rugby")
def test_build_tree_nest_part(self):
"""A recipe can specify a merge of just part of an unrelated tree."""
source1 = self.make_source_branch("source1")
source2 = self.make_source_branch("source2")
source1.lock_read()
self.addCleanup(source1.unlock)
source1_rev_id = source1.last_revision()
# Add 'b' to source2.
self.build_tree_contents([
("source2/b", "new file"), ("source2/not-b", "other file")])
source2.add(["b", "not-b"])
source2_rev_id = source2.commit("two")
base_branch = BaseRecipeBranch("source1", "1", 0.2)
merged_branch = RecipeBranch("merged", "source2")
# Merge just 'b' from source2; 'a' is untouched.
base_branch.nest_part_branch(merged_branch, "b")
build_tree(base_branch, "target")
file_id = source1.path2id("a")
self.check_file_contents("target/a", source1.get_file_text(file_id))
self.check_file_contents("target/b", "new file")
self.assertNotInWorkingTree("not-b", "target")
self.assertEqual(source1_rev_id, base_branch.revid)
self.assertEqual(source2_rev_id, merged_branch.revid)
def test_build_tree_nest_part_explicit_target(self):
"""A recipe can specify a merge of just part of an unrelated tree into
a specific subdirectory of the target tree.
"""
source1 = self.make_branch_and_tree("source1")
self.build_tree(["source1/dir/"])
source1.add(["dir"])
source1.commit("one")
source2 = self.make_source_branch("source2")
source1.lock_read()
self.addCleanup(source1.unlock)
source1_rev_id = source1.last_revision()
# Add 'b' to source2.
self.build_tree_contents([
("source2/b", "new file"), ("source2/not-b", "other file")])
source2.add(["b", "not-b"])
source2_rev_id = source2.commit("two")
base_branch = BaseRecipeBranch("source1", "1", 0.2)
merged_branch = RecipeBranch("merged", "source2")
# Merge just 'b' from source2; 'a' is untouched.
base_branch.nest_part_branch(merged_branch, "b", "dir/b")
build_tree(base_branch, "target")
self.check_file_contents("target/dir/b", "new file")
self.assertNotInWorkingTree("dir/not-b", "target")
self.assertEqual(source1_rev_id, base_branch.revid)
self.assertEqual(source2_rev_id, merged_branch.revid)
def test_build_tree_merge_twice(self):
source1 = self.make_source_branch("source1")
source1_rev_id = source1.last_revision()
source2 = source1.bzrdir.sprout("source2").open_workingtree()
self.build_tree_contents([("source2/a", "other change")])
source2_rev_id = source2.commit("one")
source3 = source2.bzrdir.sprout("source3").open_workingtree()
self.build_tree_contents([("source3/a", "third change")])
source3_rev_id = source3.commit("one")
base_branch = BaseRecipeBranch("source1", "1", 0.2)
merged_branch1 = RecipeBranch("merged", "source2")
base_branch.merge_branch(merged_branch1)
merged_branch2 = RecipeBranch("merged2", "source3")
base_branch.merge_branch(merged_branch2)
build_tree(base_branch, "target")
self.assertPathExists("target")
tree = workingtree.WorkingTree.open("target")
last_revid = tree.last_revision()
previous_revid = tree.branch.repository.get_revision(tree.branch.last_revision()).parent_ids[0]
last_revtree = tree.branch.repository.revision_tree(last_revid)
previous_revtree = tree.branch.repository.revision_tree(previous_revid)
self.assertEqual([previous_revid, source3_rev_id],
last_revtree.get_parent_ids())
self.assertEqual([source1_rev_id, source2_rev_id],
previous_revtree.get_parent_ids())
self.check_file_contents("target/a", "third change")
self.assertEqual(source1_rev_id, base_branch.revid)
self.assertEqual(source2_rev_id, merged_branch1.revid)
self.assertEqual(source3_rev_id, merged_branch2.revid)
def test_build_tree_merged_with_conflicts(self):
source1 = self.make_source_branch("source1")
source2 = source1.bzrdir.sprout("source2").open_workingtree()
self.build_tree_contents([("source2/a", "other change\n")])
source2_rev_id = source2.commit("one")
self.build_tree_contents([("source1/a", "trunk change\n")])
source1_rev_id = source1.commit("two")
base_branch = BaseRecipeBranch("source1", "1", 0.2)
merged_branch = RecipeBranch("merged", "source2")
base_branch.merge_branch(merged_branch)
self.assertRaises(errors.BzrCommandError, build_tree,
base_branch, "target")
self.assertPathExists("target")
tree = workingtree.WorkingTree.open("target")
self.assertEqual(source1_rev_id, tree.last_revision())
self.assertEqual([source1_rev_id, source2_rev_id],
tree.get_parent_ids())
self.assertEqual(1, len(tree.conflicts()))
conflict = tree.conflicts()[0]
self.assertEqual("text conflict", conflict.typestring)
self.assertEqual("a", conflict.path)
self.check_file_contents("target/a", "<<<<<<< TREE\ntrunk change\n"
"=======\nother change\n>>>>>>> MERGE-SOURCE\n")
self.assertEqual(source1_rev_id, base_branch.revid)
self.assertEqual(source2_rev_id, merged_branch.revid)
def test_build_tree_with_revspecs(self):
source1 = self.make_source_branch("source1")
source1_rev_id = source1.last_revision()
source2 = source1.bzrdir.sprout("source2").open_workingtree()
self.build_tree_contents([("source2/a", "other change\n")])
source2_rev_id = source2.commit("one")
self.build_tree_contents([("source2/a", "unwanted change\n")])
source2.commit("one")
self.build_tree_contents([("source1/a", "unwanted trunk change\n")])
source1.commit("two")
base_branch = BaseRecipeBranch("source1", "1", 0.2, revspec="1")
merged_branch = RecipeBranch("merged", "source2", revspec="2")
base_branch.merge_branch(merged_branch)
build_tree(base_branch, "target")
self.assertPathExists("target")
tree = workingtree.WorkingTree.open("target")
last_revid = tree.last_revision()
last_revtree = tree.branch.repository.revision_tree(last_revid)
self.assertEqual([source1_rev_id, source2_rev_id],
last_revtree.get_parent_ids())
self.assertEqual(source1_rev_id, base_branch.revid)
self.assertEqual(source2_rev_id, merged_branch.revid)
def test_pull_or_branch_branch(self):
source = self.make_branch_and_tree("source")
source.lock_write()
self.addCleanup(source.unlock)
self.build_tree(["source/a"])
source.add(["a"])
rev_id = source.commit("one")
source.branch.tags.set_tag("one", rev_id)
to_transport = transport.get_transport("target")
tree_to, br_to = pull_or_branch(None, None, source.branch,
to_transport, rev_id)
self.addCleanup(tree_to.unlock)
self.addCleanup(br_to.unlock)
self.assertEqual(rev_id, tree_to.last_revision())
self.assertEqual(rev_id, br_to.last_revision())
self.assertTrue(tree_to.is_locked())
self.assertTrue(br_to.is_locked())
self.assertEqual(rev_id, br_to.tags.lookup_tag("one"))
def test_pull_or_branch_branch_in_no_trees_repo(self):
"""When in a no-trees repo we need to force a working tree"""
repo = self.make_repository(".", shared=True)
repo.set_make_working_trees(False)
source = self.make_branch_and_tree("source")
self.build_tree(["source/a"])
source.add(["a"])
rev_id = source.commit("one")
source.branch.tags.set_tag("one", rev_id)
to_transport = transport.get_transport("target")
tree_to, br_to = pull_or_branch(None, None, source.branch,
to_transport, rev_id)
self.addCleanup(tree_to.unlock)
self.addCleanup(br_to.unlock)
self.assertEqual(rev_id, tree_to.last_revision())
self.assertEqual(rev_id, br_to.last_revision())
self.assertTrue(tree_to.is_locked())
self.assertTrue(br_to.is_locked())
self.assertEqual(rev_id, br_to.tags.lookup_tag("one"))
def test_pull_or_branch_pull_with_tree(self):
source = self.make_branch_and_tree("source")
self.build_tree(["source/a"])
source.add(["a"])
first_rev_id = source.commit("one")
source.branch.tags.set_tag("one", first_rev_id)
to_transport = transport.get_transport("target")
tree_to, br_to = pull_or_branch(None, None, source.branch,
to_transport, first_rev_id)
self.addCleanup(tree_to.unlock)
self.addCleanup(br_to.unlock)
self.build_tree(["source/b"])
source.add(["b"])
rev_id = source.commit("two")
source.branch.tags.set_tag("one", rev_id)
tree_to, br_to = pull_or_branch(tree_to, br_to, source.branch,
to_transport, rev_id)
self.assertEqual(rev_id, tree_to.last_revision())
self.assertEqual(rev_id, br_to.last_revision())
self.assertTrue(tree_to.is_locked())
self.assertTrue(br_to.is_locked())
# Changed tag isn't overwritten
self.assertEqual(first_rev_id, br_to.tags.lookup_tag("one"))
def test_pull_or_branch_pull_with_no_tree(self):
source = self.make_branch_and_tree("source")
self.build_tree(["source/a"])
source.add(["a"])
first_rev_id = source.commit("one")
source.branch.tags.set_tag("one", first_rev_id)
to_transport = transport.get_transport("target")
tree_to, br_to = pull_or_branch(None, None, source.branch,
to_transport, first_rev_id)
tree_to.unlock()
tree_to.bzrdir.destroy_workingtree()
self.build_tree(["source/b"])
source.add(["b"])
rev_id = source.commit("two")
source.branch.tags.set_tag("one", rev_id)
tree_to, br_to = pull_or_branch(None, br_to, source.branch,
to_transport, rev_id)
self.addCleanup(tree_to.unlock)
self.addCleanup(br_to.unlock)
self.assertEqual(rev_id, tree_to.last_revision())
self.assertEqual(rev_id, br_to.last_revision())
self.assertTrue(tree_to.is_locked())
self.assertTrue(br_to.is_locked())
# Changed tag isn't overwritten
self.assertEqual(first_rev_id, br_to.tags.lookup_tag("one"))
def test_pull_or_branch_pull_with_conflicts(self):
source = self.make_branch_and_tree("source")
self.build_tree(["source/a"])
source.add(["a"])
first_rev_id = source.commit("one")
source.branch.tags.set_tag("one", first_rev_id)
to_transport = transport.get_transport("target")
tree_to, br_to = pull_or_branch(None, None, source.branch,
to_transport, first_rev_id)
self.build_tree(["source/b"])
self.build_tree_contents([("target/b", "other contents")])
source.add(["b"])
rev_id = source.commit("two")
source.branch.tags.set_tag("one", rev_id)
e = self.assertRaises(errors.BzrCommandError,
pull_or_branch, tree_to, br_to, source.branch,
to_transport, rev_id, accelerator_tree=source)
self.assertEqual("Conflicts... aborting.", str(e))
tree_to.unlock()
br_to.unlock()
tree_to = workingtree.WorkingTree.open("target")
br_to = tree_to.branch
self.assertEqual(rev_id, tree_to.last_revision())
self.assertEqual(rev_id, br_to.last_revision())
# Changed tag isn't overwritten
self.assertEqual(first_rev_id, br_to.tags.lookup_tag("one"))
self.assertEqual(1, len(tree_to.conflicts()))
conflict = tree_to.conflicts()[0]
self.assertEqual("duplicate", conflict.typestring)
self.assertEqual("b.moved", conflict.path)
self.assertEqual("b", conflict.conflict_path)
def test_build_tree_runs_commands(self):
source = self.make_branch_and_tree("source")
revid = source.commit("one")
base_branch = BaseRecipeBranch("source", "1", 0.2)
base_branch.run_command("touch test")
build_tree(base_branch, "target")
self.assertPathExists("target")
self.assertPathExists("target/test")
tree = workingtree.WorkingTree.open("target")
self.assertEqual(revid, tree.last_revision())
self.assertEqual(revid, base_branch.revid)
def test_error_on_merge_revspec(self):
# See bug 416950
source = self.make_branch_and_tree("source")
revid = source.commit("one")
base_branch = BaseRecipeBranch("source", "1", 0.2)
merged_branch = RecipeBranch("merged", "source", revspec="debian")
base_branch.merge_branch(merged_branch)
e = self.assertRaises(errors.InvalidRevisionSpec,
build_tree, base_branch, "target")
self.assertTrue(str(e).startswith("Requested revision: 'debian' "
"does not exist in branch: "))
self.assertTrue(str(e).endswith(". Did you not mean to specify a "
"revspec at the end of the merge line?"))
class StringifyTests(TestCaseInTempDir):
def test_missing_debversion(self):
base_branch = BaseRecipeBranch("base_url", None, 0.1)
base_branch.revid = "base_revid"
manifest = str(base_branch)
self.assertEqual("# bzr-builder format 0.1\n"
"base_url revid:base_revid\n", manifest)
def test_simple_manifest(self):
base_branch = BaseRecipeBranch("base_url", "1", 0.1)
base_branch.revid = "base_revid"
manifest = str(base_branch)
self.assertEqual("# bzr-builder format 0.1 deb-version 1\n"
"base_url revid:base_revid\n", manifest)
def test_complex_manifest(self):
base_branch = BaseRecipeBranch("base_url", "2", 0.2)
base_branch.revid = "base_revid"
nested_branch1 = RecipeBranch("nested1", "nested1_url")
nested_branch1.revid = "nested1_revid"
base_branch.nest_branch("nested", nested_branch1)
nested_branch2 = RecipeBranch("nested2", "nested2_url")
nested_branch2.revid = "nested2_revid"
nested_branch1.nest_branch("nested2", nested_branch2)
merged_branch = RecipeBranch("merged", "merged_url")
merged_branch.revid = "merged_revid"
base_branch.merge_branch(merged_branch)
manifest = str(base_branch)
self.assertEqual("# bzr-builder format 0.2 deb-version 2\n"
"base_url revid:base_revid\n"
"nest nested1 nested1_url nested revid:nested1_revid\n"
" nest nested2 nested2_url nested2 revid:nested2_revid\n"
"merge merged merged_url revid:merged_revid\n", manifest)
def test_manifest_with_command(self):
base_branch = BaseRecipeBranch("base_url", "1", 0.2)
base_branch.revid = "base_revid"
base_branch.run_command("touch test")
manifest = str(base_branch)
self.assertEqual("# bzr-builder format 0.2 deb-version 1\n"
"base_url revid:base_revid\n"
"run touch test\n", manifest)
def test_recipe_with_no_revspec(self):
base_branch = BaseRecipeBranch("base_url", "1", 0.1)
manifest = str(base_branch)
self.assertEqual("# bzr-builder format 0.1 deb-version 1\n"
"base_url\n", manifest)
def test_recipe_with_tag_revspec(self):
base_branch = BaseRecipeBranch("base_url", "1", 0.1,
revspec="tag:foo")
manifest = str(base_branch)
self.assertEqual("# bzr-builder format 0.1 deb-version 1\n"
"base_url tag:foo\n", manifest)
def test_recipe_with_child(self):
base_branch = BaseRecipeBranch("base_url", "2", 0.2)
nested_branch1 = RecipeBranch("nested1", "nested1_url",
revspec="tag:foo")
base_branch.nest_branch("nested", nested_branch1)
nested_branch2 = RecipeBranch("nested2", "nested2_url")
nested_branch1.nest_branch("nested2", nested_branch2)
merged_branch = RecipeBranch("merged", "merged_url")
base_branch.merge_branch(merged_branch)
manifest = str(base_branch)
self.assertEqual("# bzr-builder format 0.2 deb-version 2\n"
"base_url\n"
"nest nested1 nested1_url nested tag:foo\n"
" nest nested2 nested2_url nested2\n"
"merge merged merged_url\n", manifest)
def test_get_recipe_text(self):
base_branch = BaseRecipeBranch("base_url", "1", 0.1)
base_branch.revid = "base_revid"
manifest = base_branch.get_recipe_text()
self.assertEqual("# bzr-builder format 0.1 deb-version 1\n"
"base_url revid:base_revid\n", manifest)
def test_get_recipe_doesnt_raise_on_invalid_recipe(self):
base_branch = BaseRecipeBranch("base_url", "1", 0.1)
base_branch.revid = "base_revid"
nested_branch1 = RecipeBranch("nested1", "nested1_url",
revspec="tag:foo")
base_branch.nest_branch(".", nested_branch1)
manifest = base_branch.get_recipe_text()
self.assertEqual("# bzr-builder format 0.1 deb-version 1\n"
"base_url revid:base_revid\n"
"nest nested1 nested1_url . tag:foo\n", manifest)
def test_get_recipe_text_validate_True(self):
base_branch = BaseRecipeBranch("base_url", "1", 0.1)
base_branch.revid = "base_revid"
nested_branch1 = RecipeBranch("nested1", "nested1_url",
revspec="tag:foo")
base_branch.nest_branch(".", nested_branch1)
self.assertRaises(InstructionParseError,
base_branch.get_recipe_text, validate=True)
def test_str_validates(self):
base_branch = BaseRecipeBranch("base_url", "1", 0.1)
base_branch.revid = "base_revid"
nested_branch1 = RecipeBranch("nested1", "nested1_url",
revspec="tag:foo")
base_branch.nest_branch(".", nested_branch1)
self.assertRaises(InstructionParseError, str, base_branch)
def test_with_nest_part(self):
base_branch = BaseRecipeBranch("base_url", "1", 0.1)
base_branch.revid = "base_revid"
nested_branch1 = RecipeBranch("nested1", "nested1_url",
revspec="tag:foo")
base_branch.nest_part_branch(nested_branch1, "foo", "bar")
manifest = base_branch.get_recipe_text()
self.assertEqual("# bzr-builder format 0.1 deb-version 1\n"
"base_url revid:base_revid\n"
"nest-part nested1 nested1_url foo bar tag:foo\n",
manifest)
def test_with_nest_part_with_no_target_dir(self):
base_branch = BaseRecipeBranch("base_url", "1", 0.1)
base_branch.revid = "base_revid"
nested_branch1 = RecipeBranch("nested1", "nested1_url",
revspec="tag:foo")
base_branch.nest_part_branch(nested_branch1, "foo", None)
manifest = base_branch.get_recipe_text()
self.assertEqual("# bzr-builder format 0.1 deb-version 1\n"
"base_url revid:base_revid\n"
"nest-part nested1 nested1_url foo foo tag:foo\n",
manifest)
def test_with_nest_part_with_no_target_dir_no_revspec(self):
base_branch = BaseRecipeBranch("base_url", "1", 0.1)
base_branch.revid = "base_revid"
nested_branch1 = RecipeBranch("nested1", "nested1_url")
base_branch.nest_part_branch(nested_branch1, "foo", None)
manifest = base_branch.get_recipe_text()
self.assertEqual("# bzr-builder format 0.1 deb-version 1\n"
"base_url revid:base_revid\n"
"nest-part nested1 nested1_url foo\n",
manifest)
class RecipeBranchTests(TestCaseWithTransport):
def test_base_recipe_branch(self):
base_branch = BaseRecipeBranch("base_url", "1", 0.2, revspec="2")
self.assertEqual(None, base_branch.name)
self.assertEqual("base_url", base_branch.url)
self.assertEqual("1", base_branch.deb_version)
self.assertEqual("2", base_branch.revspec)
self.assertEqual(0, len(base_branch.child_branches))
self.assertEqual(None, base_branch.revid)
def test_recipe_branch(self):
branch = RecipeBranch("name", "url", revspec="2")
self.assertEqual("name", branch.name)
self.assertEqual("url", branch.url)
self.assertEqual("2", branch.revspec)
self.assertEqual(0, len(branch.child_branches))
self.assertEqual(None, branch.revid)
def test_different_shape_to(self):
branch1 = BaseRecipeBranch("base_url", "1", 0.2, revspec="2")
branch2 = BaseRecipeBranch("base_url", "1", 0.2, revspec="3")
self.assertFalse(branch1.different_shape_to(branch2))
branch2 = BaseRecipeBranch("base", "1", 0.2, revspec="2")
self.assertTrue(branch1.different_shape_to(branch2))
branch2 = BaseRecipeBranch("base_url", "2", 0.2, revspec="2")
self.assertFalse(branch1.different_shape_to(branch2))
rbranch1 = RecipeBranch("name", "other_url")
rbranch2 = RecipeBranch("name2", "other_url")
self.assertTrue(rbranch1.different_shape_to(rbranch2))
rbranch2 = RecipeBranch("name", "other_url2")
self.assertTrue(rbranch1.different_shape_to(rbranch2))
def test_list_branch_names(self):
base_branch = BaseRecipeBranch("base_url", "1", 0.2)
base_branch.merge_branch(RecipeBranch("merged", "merged_url"))
nested_branch = RecipeBranch("nested", "nested_url")
nested_branch.merge_branch(
RecipeBranch("merged_into_nested", "another_url"))
base_branch.nest_branch("subdir", nested_branch)
base_branch.merge_branch(
RecipeBranch("another_nested", "yet_another_url"))
base_branch.run_command("a command")
self.assertEqual(
["merged", "nested", "merged_into_nested", "another_nested"],
base_branch.list_branch_names())
def test_iter_all_branches(self):
base_branch = BaseRecipeBranch("base_url", "1", 0.2)
merged_branch = RecipeBranch("merged", "merged_url")
base_branch.merge_branch(merged_branch)
nested_branch = RecipeBranch("nested", "nested_url")
merge_into_nested_branch = RecipeBranch("merged_into_nested", "another_url")
nested_branch.merge_branch(merge_into_nested_branch)
base_branch.nest_branch("subdir", nested_branch)
another_nested_branch = RecipeBranch("another_nested", "yet_another_url")
base_branch.merge_branch(another_nested_branch)
base_branch.run_command("a command")
self.assertEqual([
base_branch, merged_branch, nested_branch, merge_into_nested_branch,
another_nested_branch],
list(base_branch.iter_all_branches()))
def test_iter_all_instructions(self):
base_branch = BaseRecipeBranch("base_url", "1", 0.2)
nested_branch = RecipeBranch("nested", "nested_url")
merge_into_nested_branch = RecipeBranch("merged_into_nested", "another_url")
nested_branch.merge_branch(merge_into_nested_branch)
merge_into_nested = nested_branch.child_branches[-1]
base_branch.run_command("a command")
cmd = base_branch.child_branches[-1]
base_branch.nest_branch("subdir", nested_branch)
nest = base_branch.child_branches[-1]
self.assertEqual([
cmd, nest, merge_into_nested],
list(base_branch.iter_all_instructions()))