Merge lp:~lifeless/bzr-builder/blocking into lp:~james-w/bzr-builder/trunk-old

Proposed by Robert Collins
Status: Merged
Merge reported by: James Westby
Merged at revision: not available
Proposed branch: lp:~lifeless/bzr-builder/blocking
Merge into: lp:~james-w/bzr-builder/trunk-old
Diff against target: 1297 lines (+603/-227)
9 files modified
TODO (+0/-3)
__init__.py (+182/-98)
ppa.py (+124/-0)
recipe.py (+139/-79)
setup.py (+13/-12)
tests/__init__.py (+4/-3)
tests/test_blackbox.py (+47/-9)
tests/test_ppa.py (+31/-0)
tests/test_recipe.py (+63/-23)
To merge this branch: bzr merge lp:~lifeless/bzr-builder/blocking
Reviewer Review Type Date Requested Status
James Westby Needs Resubmitting
Review via email: mp+13826@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Robert Collins (lifeless) wrote :

This adds the ability to block on a build till its done - useful for integration with test builders.

Revision history for this message
James Westby (james-w) wrote :
Download full text (8.4 KiB)

12 === modified file '__init__.py'
13 --- __init__.py 2009-09-25 19:50:30 +0000
14 +++ __init__.py 2009-10-23 07:30:27 +0000
15 @@ -249,17 +249,25 @@
16 Option("key-id", type=str, short_name="k",
17 help="Sign the packages with the specified GnuPG key, "
18 "must be specified if you use --dput."),
19 + Option("watch-ppa", help="Watch the PPA the package was "
20 + "dput to and exit with 0 only if it builds and "
21 + "publishes successfully."),

--dput is planned to become a list option, at the request of Neil
who wants to put the package in multiple PPAs. How would this work
if there were multiple PPAs to watch?

Any chance we could make that change first? (bug 433454)

Is the default for an Option action=store_true?

37 + elif dput:
38 + target_from_dput(dput)

Is that just a check that the dput target is valid?

For the reasons given below I'm not sure I like the
restriction on every use, as this will break some people's use.
I assume it is there as a "fail fast" check?

46 + if watch_ppa:
47 + from bzrlib.plugins.builder.ppa import watch
48 + target = target_from_dput(dput)
49 + return watch(target, self.package, base_branch.deb_version)

Has self.package always been set to something reliable here?

Nice API, but it probably needs to take a list of PPAs given the above?

Also, it would be nice to parallelise the watches if you are uploading
a bunch (cron usecase, rather than hook). We don't have code to use this
but would it work to extend the API in that direction so that I could
add that feature easily once we do (planned soon)?

52 def _add_changelog_entry(self, base_branch, basedir, distribution=None,
53 @@ -330,6 +342,7 @@
54 "specified.")
55 if distribution is None:
56 distribution = "jaunty"
57 + self.package = package

Ah, so I assume this is the self.package thing I asked about above
answered?

65 +def target_from_dput(dput):
66 + """Convert a dput specification to a LP API specification.
67 +
68 + :param dput: A dput command spec like ppa:team-name.
69 + :return: A LP API target like team-name/ppa.
70 + """
71 + if not dput.startswith('ppa:'):
72 + raise errors.BzrCommandError('not a ppa %s' % dput)

I don't like that error.

I don't mind only supporting ppa: targets, but there will be some people
that have defined a specific target like my-ppa (or even 'ppa'). Having
the error be clear about that, and perhaps even explaining how to use
ppa: would be great.

73 + base, _, suffix = dput[4:].partition('/')

I'd prefer "len('ppa:')" than '4'.

74 + if not suffix:
75 + suffix = 'ppa'
76 + return base + '/' + suffix

I'm not entirely sure about the scheme used for ppas, but I'll
assume this is correct as you have presumably used it :-)

83 === added file 'ppa.py'
84 --- ppa.py 1970-01-01 00:00:00 +0000
85 +++ ppa.py 2009-10-23 07:30:27 +0000
103 +from optparse import OptionParser
104 +import os
105 +import sys
106 +import time
107 +
108 +from launch...

Read more...

review: Needs Resubmitting
lp:~lifeless/bzr-builder/blocking updated
54. By James Westby

EMAIL is the correct env var for the email address, not MAIL

Revision history for this message
Robert Collins (lifeless) wrote :
Download full text (11.0 KiB)

On Fri, 2009-10-23 at 10:48 +0000, James Westby wrote:
> Review: Resubmit
>
> 12 === modified file '__init__.py'
> 13 --- __init__.py 2009-09-25 19:50:30 +0000
> 14 +++ __init__.py 2009-10-23 07:30:27 +0000
> 15 @@ -249,17 +249,25 @@
> 16 Option("key-id", type=str, short_name="k",
> 17 help="Sign the packages with the specified GnuPG key, "
> 18 "must be specified if you use --dput."),
> 19 + Option("watch-ppa", help="Watch the PPA the package was "
> 20 + "dput to and exit with 0 only if it builds and "
> 21 + "publishes successfully."),
>
> --dput is planned to become a list option, at the request of Neil
> who wants to put the package in multiple PPAs. How would this work
> if there were multiple PPAs to watch?
>
> Any chance we could make that change first? (bug 433454)

Well, this is written, and that isn't. By written I mean "I'm using
it" :).

I'd either watch all the PPA's, serially, or make the options
incompatible.

> Is the default for an Option action=store_true?

Yes.

> 37 + elif dput:
> 38 + target_from_dput(dput)
>
> Is that just a check that the dput target is valid?

valid-to-watch, yes - so that we don't upload something we can't watch.
It can be bypassed of course, IFF we are not going to try to watch.

> 46 + if watch_ppa:
> 47 + from bzrlib.plugins.builder.ppa import watch
> 48 + target = target_from_dput(dput)
> 49 + return watch(target, self.package, base_branch.deb_version)
>
> Has self.package always been set to something reliable here?

Yes, as long as we've made a changelog entry.

> Nice API, but it probably needs to take a list of PPAs given the above?

I don't think so - a loop at this level is fine.

> Also, it would be nice to parallelise the watches if you are uploading
> a bunch (cron usecase, rather than hook). We don't have code to use this
> but would it work to extend the API in that direction so that I could
> add that feature easily once we do (planned soon)?

Personally, I'd loop, unless you really need 'fail as soon as any FTB.
If you need that then yes, you need to push the triple down into the
watch routine and split that up more.

> 65 +def target_from_dput(dput):
> 66 + """Convert a dput specification to a LP API specification.
> 67 +
> 68 + :param dput: A dput command spec like ppa:team-name.
> 69 + :return: A LP API target like team-name/ppa.
> 70 + """
> 71 + if not dput.startswith('ppa:'):
> 72 + raise errors.BzrCommandError('not a ppa %s' % dput)
>
> I don't like that error.
>
> I don't mind only supporting ppa: targets, but there will be some people
> that have defined a specific target like my-ppa (or even 'ppa'). Having
> the error be clear about that, and perhaps even explaining how to use
> ppa: would be great.

Feel free to make the text longer when you merge :) [round trips cost,
so we should only round trip on merge changes when the submitter has to
make changes anyhow].

> 73 + base, _, suffix = dput[4:].partition('/')
>
> I'd prefer "len('ppa:')" than '4'.

I'd put a co...

Revision history for this message
Robert Collins (lifeless) wrote :

On Fri, 2009-10-23 at 20:09 +0000, Robert Collins wrote:

> The only things I saw that I would consider a must are changing the
> early check on dput parsing, the better error message for 'not a ppa',
> and adding a timeout on not finding source records - everything else
> will communicate what is going on to the user adequately and either
> won't be made appreciably better, or could be made worse by changing.
> (For instance, changing the bare except will cause any number of unknown
> exceptions to float past - and if I had a test harness where I could
> diagnose those, I would be delighted to change it, but I don't, so other
> than making it 'except Exception:' [happy to do that]) I'd rather not
> touch it.

Done these things.

-Rob

Revision history for this message
James Westby (james-w) wrote :

On Fri Oct 23 20:09:09 UTC 2009 Robert Collins wrote:
> I'd appreciate it if you can go over this mail and categorise 'must' vs
> 'should' - most of the support code for LP API's is cargo culted and
> repeated in other lp tools - really it should be factored into
> launchpadlib itself, so I don't think polishing it here is a good use of
> anyones time.

I consider most of them to be a 'must', as you are asking me to maintain
the code. I wouldn't block on supporting code that wasn't written yet though.

If you don't want to make the changes then please say so and I will happily
do the work myself when I have some time.

Thanks,

James

Revision history for this message
Robert Collins (lifeless) wrote :

I've got this in production now, and had to make further changes so its using trace.note now.

lp:~lifeless/bzr-builder/blocking updated
55. By James Westby

Merge some doc changes to improve formatting. Thanks Ian.

56. By James Westby

Typo fix from Michael, thanks.

57. By James Westby

Newer bzr requires declaration that some paths will be accessed.

If we are using the newer bzr then declare that the root will be
accessed in the blackbox tests. This allows them to run, but bypasses
the improved isolation.

58. By James Westby

Add {debupstream} variable that expands to the upstream version from d/c.

debian/changelog is read once the tree is built and the upstream part of
the latest version number is placed in to this variable. Thanks Rob.

59. By James Westby

Refactor to use objects and polymorphism rather than tuples and if blocks.

Thanks Andrew.

60. By James Westby

Some minor code cleanups. Thanks Ian.

61. By James Westby

pyflakes cleanup. Thanks Michael.

62. By James Westby

Move calculate_package_dir to be a function rather than a private method.

63. By James Westby

Allow add_changelog_entry to take user data to override environment.

64. By James Westby

Add a --no-build option that doesn't actually build the source package.

This is for the benefit of Launchpad, but may be useful for other scripts too.

65. By James Westby

If --no-build is passed still write any --manifest

66. By James Westby

Prepare 0.2.

67. By James Westby

Make build_manifest into __str__ and make it work for recipes too.

68. By James Westby

Allow child branches to be stringifyed without being built.

69. By James Westby

Guard setup in setup.py so that it can be imported.

70. By James Westby

Merge --watch-ppa support. Thanks Rob.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'TODO'
2--- TODO 2009-04-15 14:10:19 +0000
3+++ TODO 2010-04-08 03:00:43 +0000
4@@ -1,6 +1,3 @@
5-- Implement building the source package from the tree, including adding
6- the changelog entry with the desired version.
7-- Implement uploading to a PPA.
8 - Documentation.
9 - Work out how to get tarballs for non-native packages if we want that.
10 - Decide on error on existing target directory vs. re-use and pull changes.
11
12=== modified file '__init__.py'
13--- __init__.py 2009-11-23 16:42:00 +0000
14+++ __init__.py 2010-04-08 03:00:43 +0000
15@@ -107,6 +107,10 @@
16 * {revno} will be the revno of the base branch (the first specified).
17 * {revno:<branch name>} will be substituted with the revno for the
18 branch named <branch name> in the recipe.
19+ * {debupstream} will be replaced by the upstream portion of the version
20+ number taken from debian/changelog in the final tree. If when the
21+ tree is built the top of debian/changelog has a version number of
22+ "1.0-1" then this would evaluate to "1.0".
23
24 Format versions:
25
26@@ -144,20 +148,24 @@
27 from bzrlib.option import Option
28
29 from bzrlib.plugins.builder.recipe import (
30- build_manifest,
31 build_tree,
32+ DEBUPSTREAM_VAR,
33 RecipeParser,
34 resolve_revisions,
35 )
36
37
38+# The default distribution used by add_changelog_entry()
39+DEFAULT_UBUNTU_DISTRIBUTION = "lucid"
40+
41+
42 def write_manifest_to_path(path, base_branch):
43 parent_dir = os.path.dirname(path)
44 if parent_dir != '' and not os.path.exists(parent_dir):
45 os.makedirs(parent_dir)
46 manifest_f = open(path, 'wb')
47 try:
48- manifest_f.write(build_manifest(base_branch))
49+ manifest_f.write(str(base_branch))
50 finally:
51 manifest_f.close()
52
53@@ -188,8 +196,7 @@
54
55
56 def get_maintainer():
57- """
58- Create maintainer string using the same algorithm as in dch
59+ """Create maintainer string using the same algorithm as in dch.
60 """
61 env = os.environ
62 regex = re.compile(r"^(.*)\s+<(.*)>$")
63@@ -218,7 +225,7 @@
64 # Use password database if no data in environment variables
65 try:
66 maintainer = re.sub(r',.*', '', pwd.getpwuid(os.getuid()).pw_gecos)
67- except KeyError, AttributeError:
68+ except (KeyError, AttributeError):
69 # TBD: Use last changelog entry value
70 maintainer = "bzr-builder"
71
72@@ -254,7 +261,7 @@
73
74
75 def add_changelog_entry(base_branch, basedir, distribution=None,
76- package=None):
77+ package=None, author_name=None, author_email=None):
78 debian_dir = os.path.join(basedir, "debian")
79 if not os.path.exists(debian_dir):
80 os.makedirs(debian_dir)
81@@ -272,16 +279,25 @@
82 distribution = cl._blocks[0].distributions.split()[0]
83 if package is None:
84 package = cl._blocks[0].package
85+ if DEBUPSTREAM_VAR in base_branch.deb_version:
86+ cl_version = cl._blocks[0].version
87+ base_branch.substitute_debupstream(cl_version)
88 else:
89 if package is None:
90 raise errors.BzrCommandError("No previous changelog to "
91 "take the package name from, and --package not "
92 "specified.")
93+ if DEBUPSTREAM_VAR in base_branch.deb_version:
94+ raise errors.BzrCommandError("No previous changelog to "
95+ "take the upstream version from as %s was "
96+ "used." % DEBUPSTREAM_VAR)
97 if distribution is None:
98- distribution = "jaunty"
99+ distribution = DEFAULT_UBUNTU_DISTRIBUTION
100 # Use debian packaging environment variables
101 # or default values if they don't exist
102- author = "%s <%s>" % get_maintainer()
103+ if author_name is None or author_email is None:
104+ author_name, author_email = get_maintainer()
105+ author = "%s <%s>" % (author_name, author_email)
106
107 date = utils.formatdate(localtime=True)
108 cl.new_block(package=package, version=base_branch.deb_version,
109@@ -295,74 +311,54 @@
110 cl_f.close()
111
112
113+def calculate_package_dir(base_branch, package_name, working_basedir):
114+ """Calculate the directory name that should be used while debuilding."""
115+ version = base_branch.deb_version
116+ if "-" in version:
117+ version = version[:version.rindex("-")]
118+ package_basedir = "%s-%s" % (package_name, version)
119+ package_dir = os.path.join(working_basedir, package_basedir)
120+ return package_dir
121+
122+
123+def _run_command(command, basedir, msg, error_msg):
124+ """ Run a command in a subprocess.
125+
126+ :param command: list with command and parameters
127+ :param msg: message to display to the user
128+ :param error_msg: message to display if something fails.
129+ """
130+ trace.note(msg)
131+ proc = subprocess.Popen(command, cwd=basedir,
132+ stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
133+ stdin=subprocess.PIPE)
134+ proc.stdin.close()
135+ retcode = proc.wait()
136+ if retcode != 0:
137+ output = proc.stdout.read()
138+ raise errors.BzrCommandError("%s: %s" % (error_msg, output))
139+
140+
141 def build_source_package(basedir):
142- trace.note("Building the source package")
143 command = ["/usr/bin/debuild", "--no-tgz-check", "-i", "-I", "-S",
144 "-uc", "-us"]
145- proc = subprocess.Popen(command, cwd=basedir,
146- stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
147- stdin=subprocess.PIPE)
148- proc.stdin.close()
149- retcode = proc.wait()
150- if retcode != 0:
151- output = proc.stdout.read()
152- raise errors.BzrCommandError("Failed to build the source package: "
153- "%s" % output)
154+ _run_command(command, basedir,
155+ "Building the source package",
156+ "Failed to build the source package")
157
158
159 def sign_source_package(basedir, key_id):
160- trace.note("Signing the source package")
161 command = ["/usr/bin/debsign", "-S", "-k%s" % key_id]
162- proc = subprocess.Popen(command, cwd=basedir,
163- stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
164- stdin=subprocess.PIPE)
165- proc.stdin.close()
166- retcode = proc.wait()
167- if retcode != 0:
168- output = proc.stdout.read()
169- raise errors.BzrCommandError("Signing the package failed: "
170- "%s" % output)
171+ _run_command(command, basedir,
172+ "Signing the source package",
173+ "Signing the package failed")
174
175
176 def dput_source_package(basedir, target):
177- trace.note("Uploading the source package")
178 command = ["/usr/bin/debrelease", "-S", "--dput", target]
179- proc = subprocess.Popen(command, cwd=basedir,
180- stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
181- stdin=subprocess.PIPE)
182- proc.stdin.close()
183- retcode = proc.wait()
184- if retcode != 0:
185- output = proc.stdout.read()
186- raise errors.BzrCommandError("Uploading the package failed: "
187- "%s" % output)
188-
189-
190-def get_base_branch(recipe_file, if_changed_from=None):
191- base_branch = get_branch_from_recipe_file(recipe_file)
192- time = datetime.datetime.utcnow()
193- base_branch.substitute_time(time)
194- old_recipe = None
195- if if_changed_from is not None:
196- old_recipe = get_old_recipe(if_changed_from)
197- changed = resolve_revisions(base_branch, if_changed_from=old_recipe)
198- if not changed:
199- return None
200- return base_branch
201-
202-
203-def build_one_recipe(recipe_file, working_directory, manifest=None,
204- if_changed_from=None):
205- base_branch = get_base_branch(recipe_file, if_changed_from=if_changed_from)
206- if base_branch is None:
207- trace.note("Unchanged")
208- return 0
209- build_tree(base_branch, working_directory)
210- if manifest is not None:
211- write_manifest_to_path(manifest, base_branch)
212- else:
213- write_manifest_to_path(os.path.join(working_directory,
214- "bzr-builder.manifest"), base_branch)
215+ _run_command(command, basedir,
216+ "Uploading the source package",
217+ "Uploading the package failed")
218
219
220 class cmd_build(Command):
221@@ -381,10 +377,40 @@
222 "to that specified in the specified manifest."),
223 ]
224
225+ def _get_prepared_branch_from_recipe(self, recipe_file,
226+ if_changed_from=None):
227+ """Common code to prepare a branch and do substitutions.
228+
229+ :param recipe_file: a path to a recipe file to work from.
230+ :param if_changed_from: an optional path to a manifest to
231+ compare the recipe against.
232+ :return: A tuple with (retcode, base_branch). If retcode is None
233+ then the command execution should continue.
234+ """
235+ base_branch = get_branch_from_recipe_file(recipe_file)
236+ time = datetime.datetime.utcnow()
237+ base_branch.substitute_time(time)
238+ old_recipe = None
239+ if if_changed_from is not None:
240+ old_recipe = get_old_recipe(if_changed_from)
241+ # Save the unsubstituted version for dailydeb.
242+ self._template_version = base_branch.deb_version
243+ changed = resolve_revisions(base_branch, if_changed_from=old_recipe)
244+ if not changed:
245+ trace.note("Unchanged")
246+ return 0, base_branch
247+ return None, base_branch
248+
249 def run(self, recipe_file, working_directory, manifest=None,
250 if_changed_from=None):
251- return build_one_recipe(recipe_file, working_directory,
252- manifest=manifest, if_changed_from=if_changed_from)
253+ result, base_branch = self._get_prepared_branch_from_recipe(recipe_file,
254+ if_changed_from=if_changed_from)
255+ if result is not None:
256+ return result
257+ manifest_path = manifest or os.path.join(working_directory,
258+ "bzr-builder.manifest")
259+ build_tree(base_branch, working_directory)
260+ write_manifest_to_path(manifest_path, base_branch)
261
262
263 register_command(cmd_build)
264@@ -414,60 +440,118 @@
265 help="dput the built package to the specified "
266 "dput target."),
267 Option("key-id", type=str, short_name="k",
268- help="Sign the packages with the specified GnuPG key, "
269- "must be specified if you use --dput."),
270+ help="Sign the packages with the specified GnuPG key. "
271+ "Must be specified if you use --dput."),
272+ Option("no-build",
273+ help="Just ready the source package and don't "
274+ "actually build it."),
275+ Option("watch-ppa", help="Watch the PPA the package was "
276+ "dput to and exit with 0 only if it builds and "
277+ "publishes successfully."),
278 ]
279
280- takes_args = ["recipe_file", "working_directory?"]
281+ takes_args = ["recipe_file", "working_basedir?"]
282
283- def run(self, recipe_file, working_directory=None, manifest=None,
284+ def run(self, recipe_file, working_basedir=None, manifest=None,
285 if_changed_from=None, package=None, distribution=None,
286- dput=None, key_id=None):
287+ dput=None, key_id=None, no_build=None, watch_ppa=False):
288
289 if dput is not None and key_id is None:
290 raise errors.BzrCommandError("You must specify --key-id if you "
291 "specify --dput.")
292+ if watch_ppa:
293+ if not dput:
294+ raise errors.BzrCommandError(
295+ "cannot watch a ppa without doing dput.")
296+ else:
297+ # Check we can calculate a PPA url.
298+ target_from_dput(dput)
299
300- base_branch = get_base_branch(recipe_file, if_changed_from=if_changed_from)
301- if base_branch is None:
302- trace.note("Unchanged")
303- return 0
304- recipe_name = os.path.basename(recipe_file)
305- if recipe_name.endswith(".recipe"):
306- recipe_name = recipe_name[:-len(".recipe")]
307- version = base_branch.deb_version
308- if "-" in version:
309- version = version[:version.rindex("-")]
310- package_basedir = "%s-%s" % (package or recipe_name, version)
311- if working_directory is None:
312+ result, base_branch = self._get_prepared_branch_from_recipe(recipe_file,
313+ if_changed_from=if_changed_from)
314+ if result is not None:
315+ return result
316+ if working_basedir is None:
317 temp_dir = tempfile.mkdtemp(prefix="bzr-builder-")
318- working_directory = temp_dir
319+ working_basedir = temp_dir
320 else:
321 temp_dir = None
322- if not os.path.exists(working_directory):
323- os.makedirs(working_directory)
324+ if not os.path.exists(working_basedir):
325+ os.makedirs(working_basedir)
326+ package_name = self._calculate_package_name(recipe_file, package)
327+ working_directory = os.path.join(working_basedir,
328+ "%s-%s" % (package_name, self._template_version))
329 try:
330- package_dir = os.path.join(working_directory, package_basedir)
331- build_tree(base_branch, package_dir)
332- write_manifest_to_path(os.path.join(package_dir, "debian",
333- "bzr-builder.manifest"), base_branch)
334- add_changelog_entry(base_branch, package_dir,
335- distribution=distribution, package=package)
336- build_source_package(package_dir)
337- if key_id is not None:
338- sign_source_package(package_dir, key_id)
339- if dput is not None:
340- dput_source_package(package_dir, dput)
341+ # we want to use a consistent package_dir always to support
342+ # updates in place, but debuild etc want PACKAGE-UPSTREAMVERSION
343+ # on disk, so we build_tree with the unsubstituted version number
344+ # and do a final rename-to step before calling into debian build
345+ # tools. We then rename the working dir back.
346+ manifest_path = os.path.join(working_directory, "debian",
347+ "bzr-builder.manifest")
348+ build_tree(base_branch, working_directory)
349+ write_manifest_to_path(manifest_path, base_branch)
350+ # Add changelog also substitutes {debupstream}.
351+ add_changelog_entry(base_branch, working_directory,
352+ distribution=distribution, package=package)
353+ package_dir = calculate_package_dir(base_branch,
354+ package_name, working_basedir)
355+ # working_directory -> package_dir: after this debian stuff works.
356+ os.rename(working_directory, package_dir)
357+ if no_build:
358+ if manifest is not None:
359+ write_manifest_to_path(manifest, base_branch)
360+ return 0
361+ try:
362+ build_source_package(package_dir)
363+ if key_id is not None:
364+ sign_source_package(package_dir, key_id)
365+ if dput is not None:
366+ dput_source_package(package_dir, dput)
367+ finally:
368+ # package_dir -> working_directory
369+ # FIXME: may fail in error unwind, masking the original exception.
370+ os.rename(package_dir, working_directory)
371+ # Note that this may write a second manifest.
372 if manifest is not None:
373 write_manifest_to_path(manifest, base_branch)
374 finally:
375 if temp_dir is not None:
376 shutil.rmtree(temp_dir)
377+ if watch_ppa:
378+ from bzrlib.plugins.builder.ppa import watch
379+ target = target_from_dput(dput)
380+ if not watch(target, self.package, base_branch.deb_version):
381+ return 2
382+
383+ def _calculate_package_name(self, recipe_file, package):
384+ """Calculate the directory name that should be used while debuilding."""
385+ recipe_name = os.path.basename(recipe_file)
386+ if recipe_name.endswith(".recipe"):
387+ recipe_name = recipe_name[:-len(".recipe")]
388+ return package or recipe_name
389
390
391 register_command(cmd_dailydeb)
392
393
394+def target_from_dput(dput):
395+ """Convert a dput specification to a LP API specification.
396+
397+ :param dput: A dput command spec like ppa:team-name.
398+ :return: A LP API target like team-name/ppa.
399+ """
400+ ppa_prefix = 'ppa:'
401+ if not dput.startswith(ppa_prefix):
402+ raise errors.BzrCommandError('%r does not appear to be a PPA. '
403+ 'A dput target like \'%suser[/name]\' must be used.'
404+ % (dput, ppa_prefix))
405+ base, _, suffix = dput[len(ppa_prefix):].partition('/')
406+ if not suffix:
407+ suffix = 'ppa'
408+ return base + '/' + suffix
409+
410+
411 def test_suite():
412 from unittest import TestSuite
413 from bzrlib.plugins.builder import tests
414
415=== added file 'ppa.py'
416--- ppa.py 1970-01-01 00:00:00 +0000
417+++ ppa.py 2010-04-08 03:00:43 +0000
418@@ -0,0 +1,124 @@
419+# ppa support for bzr builder.
420+#
421+# Copyright: Canonical Ltd. (C) 2009
422+#
423+# This program is free software: you can redistribute it and/or modify it
424+# under the terms of the GNU General Public License version 3, as published
425+# by the Free Software Foundation.
426+
427+# This program is distributed in the hope that it will be useful, but
428+# WITHOUT ANY WARRANTY; without even the implied warranties of
429+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
430+# PURPOSE. See the GNU General Public License for more details.
431+
432+# You should have received a copy of the GNU General Public License along
433+# with this program. If not, see <http://www.gnu.org/licenses/>.
434+
435+import os
436+import time
437+
438+
439+from launchpadlib.launchpad import (
440+ Launchpad,
441+ EDGE_SERVICE_ROOT,
442+ )
443+from launchpadlib.credentials import Credentials
444+
445+from bzrlib import (
446+ errors,
447+ trace,
448+ )
449+
450+
451+def get_lp():
452+ credentials = Credentials()
453+ oauth_file = os.path.expanduser('~/.cache/launchpadlib/bzr-builder')
454+ if os.path.exists(oauth_file):
455+ f = open(oauth_file)
456+ try:
457+ credentials.load(f)
458+ finally:
459+ f.close()
460+ launchpad = Launchpad(credentials, EDGE_SERVICE_ROOT)
461+ else:
462+ launchpad = Launchpad.get_token_and_login('bzr-builder',
463+ EDGE_SERVICE_ROOT)
464+ f = open(oauth_file, 'wb')
465+ try:
466+ launchpad.credentials.save(f)
467+ finally:
468+ f.close()
469+ return launchpad
470+
471+
472+def watch(owner_name, archive_name, package_name, version):
473+ """Watch a package build.
474+
475+ :return: True once the package built and published, or False if it fails
476+ or there is a timeout waiting.
477+ """
478+ version = str(version)
479+ trace.note("Logging into Launchpad")
480+
481+ launchpad = get_lp()
482+ owner = launchpad.people[owner_name]
483+ archive = owner.getPPAByName(name=archive_name)
484+ end_states = ['FAILEDTOBUILD', 'FULLYBUILT']
485+ important_arches = ['amd64', 'i386', 'lpia', 'armel']
486+ trace.note("Waiting for version %s of %s to build." % (version, package_name))
487+ start = time.time()
488+ while True:
489+ sourceRecords = list(archive.getPublishedSources(
490+ source_name=package_name, version=version))
491+ if not sourceRecords:
492+ if time.time() - 900 > start:
493+ # Over 15 minutes and no source yet, upload FAIL.
494+ raise errors.BzrCommandError("No source record in %s/%s for "
495+ "package %s=%s after 15 minutes." % (owner_name,
496+ archive_name, package_name, version))
497+ return False
498+ trace.note("Source not available yet - waiting.")
499+ time.sleep(60)
500+ continue
501+ pkg = sourceRecords[0]
502+ if pkg.status.lower() not in ('published', 'pending'):
503+ trace.note("Package status: %s" % (pkg.status,))
504+ time.sleep(60)
505+ continue
506+ # FIXME: LP should export this as an attribute.
507+ source_id = pkg.self_link.rsplit('/', 1)[1]
508+ buildSummaries = archive.getBuildSummariesForSourceIds(
509+ source_ids=[source_id])[source_id]
510+ if buildSummaries['status'] in end_states:
511+ break
512+ if buildSummaries['status'] == 'NEEDSBUILD':
513+ # We ignore non-virtual PPA architectures that are sparsely
514+ # supplied with buildds.
515+ missing = []
516+ for build in buildSummaries['builds']:
517+ arch = build['arch_tag']
518+ if arch in important_arches:
519+ missing.append(arch)
520+ if not missing:
521+ break
522+ extra = ' on ' + ', '.join(missing)
523+ else:
524+ extra = ''
525+ trace.note("%s is still in %s%s" % (pkg.display_name,
526+ buildSummaries['status'], extra))
527+ time.sleep(60)
528+ trace.note("%s is now %s" % (pkg.display_name, buildSummaries['status']))
529+ result = True
530+ if pkg.status.lower() != 'published':
531+ result = False # should this perhaps keep waiting?
532+ if buildSummaries['status'] != 'FULLYBUILT':
533+ if buildSummaries['status'] == 'NEEDSBUILD':
534+ # We're stopping early cause the important_arches are built.
535+ builds = pkg.getBuilds()
536+ for build in builds:
537+ if build.arch_tag in important_arches:
538+ if build.buildstate != 'Successfully built':
539+ result = False
540+ else:
541+ result = False
542+ return result
543
544=== modified file 'recipe.py'
545--- recipe.py 2009-08-18 21:16:17 +0000
546+++ recipe.py 2010-04-08 03:00:43 +0000
547@@ -38,6 +38,13 @@
548 NEST_INSTRUCTION = "nest"
549 RUN_INSTRUCTION = "run"
550
551+TIME_VAR = "{time}"
552+REVNO_VAR = "{revno}"
553+REVNO_PARAM_VAR = "{revno:%s}"
554+DEBUPSTREAM_VAR = "{debupstream}"
555+
556+ok_to_preserve = [DEBUPSTREAM_VAR]
557+
558
559 class CommandFailedError(errors.BzrError):
560
561@@ -113,10 +120,10 @@
562 # We do a "pull"
563 if tree_to is not None:
564 # FIXME: should these pulls overwrite?
565- result = tree_to.pull(br_from, stop_revision=revision_id,
566+ tree_to.pull(br_from, stop_revision=revision_id,
567 possible_transports=possible_transports)
568 else:
569- result = br_to.pull(br_from, stop_revision=revision_id,
570+ br_to.pull(br_from, stop_revision=revision_id,
571 possible_transports=possible_transports)
572 tree_to = br_to.bzrdir.create_workingtree()
573 # Ugh, we have to assume that the caller replaces their reference
574@@ -226,7 +233,7 @@
575 def get_revno():
576 try:
577 revno = br_from.revision_id_to_revno(revision_id)
578- return "%s" % revno
579+ return str(revno)
580 except errors.NoSuchRevision:
581 # We need to load and use the full revno map after all
582 result = br_from.get_revision_id_to_revno_map().get(
583@@ -247,11 +254,11 @@
584 changed_revision_id = br_from.last_revision()
585 if revision_id != changed_revision_id:
586 changed = True
587- for index, (child_branch, nest_location) in \
588- enumerate(new_branch.child_branches):
589+ for index, instruction in enumerate(new_branch.child_branches):
590+ child_branch = instruction.recipe_branch
591 if_changed_child = None
592 if if_changed_from is not None:
593- if_changed_child = if_changed_from.child_branches[index][0]
594+ if_changed_child = if_changed_from.child_branches[index].recipe_branch
595 if child_branch is not None:
596 child_changed = _resolve_revisions_recurse(child_branch,
597 substitute_revno,
598@@ -288,7 +295,10 @@
599 if_changed_from=if_changed_from_revisions)
600 if not changed:
601 changed = changed_revisions
602- if "{" in base_branch.deb_version:
603+ checked_version = base_branch.deb_version
604+ for token in ok_to_preserve:
605+ checked_version = checked_version.replace(token, "")
606+ if "{" in checked_version:
607 raise errors.BzrCommandError("deb-version not fully "
608 "expanded: %s" % base_branch.deb_version)
609 if if_changed_from is not None and not changed:
610@@ -323,22 +333,8 @@
611 try:
612 tree_to, br_to = update_branch(base_branch, tree_to, br_to,
613 to_transport)
614- for child_branch, nest_location in base_branch.child_branches:
615- if child_branch is None:
616- # it's a command
617- proc = subprocess.Popen(nest_location, cwd=target_path,
618- preexec_fn=subprocess_setup, shell=True,
619- stdin=subprocess.PIPE)
620- proc.communicate()
621- if proc.returncode != 0:
622- raise CommandFailedError(nest_location)
623- elif nest_location is not None:
624- # FIXME: pass possible_transports around
625- build_tree(child_branch,
626- target_path=os.path.join(target_path,
627- nest_location))
628- else:
629- merge_branch(child_branch, tree_to, br_to)
630+ for instruction in base_branch.child_branches:
631+ instruction.apply(target_path, tree_to, br_to)
632 finally:
633 # Is this ok if tree_to is created by pull_or_branch?
634 if br_to is not None:
635@@ -348,42 +344,48 @@
636 tree_to.unlock()
637
638
639-def _add_child_branches_to_manifest(child_branches, indent_level):
640- manifest = ""
641- for child_branch, nest_location in child_branches:
642- if child_branch is None:
643- manifest += "%s%s %s\n" % (" " * indent_level, RUN_INSTRUCTION,
644- nest_location)
645- else:
646- assert child_branch.revid is not None, "Branch hasn't been built"
647- if nest_location is not None:
648- manifest += "%s%s %s %s %s revid:%s\n" % \
649- (" " * indent_level, NEST_INSTRUCTION,
650- child_branch.name,
651- child_branch.url, nest_location,
652- child_branch.revid)
653- manifest += _add_child_branches_to_manifest(
654- child_branch.child_branches, indent_level+1)
655- else:
656- manifest += "%s%s %s %s revid:%s\n" % \
657- (" " * indent_level, MERGE_INSTRUCTION,
658- child_branch.name,
659- child_branch.url, child_branch.revid)
660- return manifest
661-
662-
663-def build_manifest(base_branch):
664- manifest = "# bzr-builder format %s deb-version " % str(base_branch.format)
665- # TODO: should we store the expanded version that was used?
666- manifest += "%s\n" % (base_branch.deb_version,)
667- assert base_branch.revid is not None, "Branch hasn't been built"
668- manifest += "%s revid:%s\n" % (base_branch.url, base_branch.revid)
669- manifest += _add_child_branches_to_manifest(base_branch.child_branches, 0)
670- # Sanity check.
671- # TODO: write a function that compares the result of this parse with
672- # the branch that we built it from.
673- RecipeParser(manifest).parse()
674- return manifest
675+class ChildBranch(object):
676+ """A child branch in a recipe.
677+
678+ If the nest path is not None it is the path relative to the recipe branch
679+ where the child branch should be placed. If it is None then the child
680+ branch should be merged instead of nested.
681+ """
682+
683+ def __init__(self, recipe_branch, nest_path=None):
684+ self.recipe_branch = recipe_branch
685+ self.nest_path = nest_path
686+
687+ def apply(self, target_path, tree_to, br_to):
688+ raise NotImplementedError(self.apply)
689+
690+ def as_tuple(self):
691+ return (self.recipe_branch, self.nest_path)
692+
693+
694+class CommandInstruction(ChildBranch):
695+
696+ def apply(self, target_path, tree_to, br_to):
697+ # it's a command
698+ proc = subprocess.Popen(self.nest_path, cwd=target_path,
699+ preexec_fn=subprocess_setup, shell=True, stdin=subprocess.PIPE)
700+ proc.communicate()
701+ if proc.returncode != 0:
702+ raise CommandFailedError(self.nest_path)
703+
704+
705+class MergeInstruction(ChildBranch):
706+
707+ def apply(self, target_path, tree_to, br_to):
708+ merge_branch(self.recipe_branch, tree_to, br_to)
709+
710+
711+class NestInstruction(ChildBranch):
712+
713+ def apply(self, target_path, tree_to, br_to):
714+ # FIXME: pass possible_transports around
715+ build_tree(self.recipe_branch,
716+ target_path=os.path.join(target_path, self.nest_path))
717
718
719 class RecipeBranch(object):
720@@ -393,11 +395,7 @@
721 root branch), and optionally child branches that are either merged
722 or nested.
723
724- The child_branches attribute is a list of tuples of (RecipeBranch,
725- relative path), where if the relative branch is not None it is the
726- path relative to this branch where the child branch should be placed.
727- If it is None then the child branch should be merged instead.
728-
729+ The child_branches attribute is a list of tuples of ChildBranch objects.
730 The revid attribute records the revid that the url and revspec resolved
731 to when the RecipeBranch was built, or None if it has not been built.
732 """
733@@ -421,7 +419,7 @@
734
735 :param branch: the RecipeBranch to merge.
736 """
737- self.child_branches.append((branch, None))
738+ self.child_branches.append(MergeInstruction(branch))
739
740 def nest_branch(self, location, branch):
741 """Nest a child branch in to this one.
742@@ -429,16 +427,16 @@
743 :param location: the relative path at which this branch should be nested.
744 :param branch: the RecipeBranch to nest.
745 """
746- assert location not in [b[1] for b in self.child_branches],\
747+ assert location not in [b.nest_path for b in self.child_branches],\
748 "%s already has branch nested there" % location
749- self.child_branches.append((branch, location))
750+ self.child_branches.append(NestInstruction(branch, location))
751
752 def run_command(self, command):
753 """Set a command to be run.
754
755 :param command: the command to be run
756 """
757- self.child_branches.append((None, command))
758+ self.child_branches.append(CommandInstruction(None, command))
759
760 def different_shape_to(self, other_branch):
761 """Tests whether the name, url and child_branches are the same"""
762@@ -448,16 +446,18 @@
763 return True
764 if len(self.child_branches) != len(other_branch.child_branches):
765 return True
766- for index, (child_branch, nest_location) in \
767- enumerate(self.child_branches):
768- other_child, other_nest_location = \
769- other_branch.child_branches[index]
770+ for index, instruction in enumerate(self.child_branches):
771+ child_branch = instruction.recipe_branch
772+ nest_location = instruction.nest_path
773+ other_instruction = other_branch.child_branches[index]
774+ other_child_branch = other_instruction.recipe_branch
775+ other_nest_location = other_instruction.nest_path
776 if nest_location != other_nest_location:
777 return True
778- if ((child_branch is None and other_child is not None)
779- or (child_branch is not None and other_child is None)):
780+ if ((child_branch is None and other_child_branch is not None)
781+ or (child_branch is not None and other_child_branch is None)):
782 return True
783- if child_branch.different_shape_to(other_child):
784+ if child_branch.different_shape_to(other_child_branch):
785 return True
786 return False
787
788@@ -486,9 +486,9 @@
789 needed.
790 """
791 if branch_name is None:
792- subst_string = "{revno}"
793+ subst_string = REVNO_VAR
794 else:
795- subst_string = "{revno:%s}" % branch_name
796+ subst_string = REVNO_PARAM_VAR % branch_name
797 if subst_string in self.deb_version:
798 revno = get_revno_cb()
799 if revno is None:
800@@ -502,10 +502,70 @@
801
802 :param time: a datetime.datetime with the desired time.
803 """
804- if "{time}" in self.deb_version:
805- self.deb_version = self.deb_version.replace("{time}",
806+ if TIME_VAR in self.deb_version:
807+ self.deb_version = self.deb_version.replace(TIME_VAR,
808 time.strftime("%Y%m%d%H%M"))
809
810+ def substitute_debupstream(self, version):
811+ """Substitute {debupstream} in to deb_version if needed.
812+
813+ :param version: the Version object to take the upstream version
814+ from.
815+ """
816+ if DEBUPSTREAM_VAR in self.deb_version:
817+ # Should we include the epoch?
818+ self.deb_version = self.deb_version.replace(DEBUPSTREAM_VAR,
819+ version.upstream_version)
820+
821+ def _add_child_branches_to_manifest(self, child_branches, indent_level):
822+ manifest = ""
823+ for instruction in child_branches:
824+ child_branch = instruction.recipe_branch
825+ nest_location = instruction.nest_path
826+ if child_branch is None:
827+ manifest += "%s%s %s\n" % (" " * indent_level, RUN_INSTRUCTION,
828+ nest_location)
829+ else:
830+ if child_branch.revid is not None:
831+ revid_part = " revid:%s" % child_branch.revid
832+ elif child_branch.revspec is not None:
833+ revid_part = " %s" % child_branch.revspec
834+ else:
835+ revid_part = ""
836+ if nest_location is not None:
837+ manifest += "%s%s %s %s %s%s\n" % \
838+ (" " * indent_level, NEST_INSTRUCTION,
839+ child_branch.name,
840+ child_branch.url, nest_location,
841+ revid_part)
842+ manifest += self._add_child_branches_to_manifest(
843+ child_branch.child_branches, indent_level+1)
844+ else:
845+ manifest += "%s%s %s %s%s\n" % \
846+ (" " * indent_level, MERGE_INSTRUCTION,
847+ child_branch.name,
848+ child_branch.url, revid_part)
849+ return manifest
850+
851+
852+ def __str__(self):
853+ manifest = "# bzr-builder format %s deb-version " % str(self.format)
854+ # TODO: should we store the expanded version that was used?
855+ manifest += "%s\n" % (self.deb_version,)
856+ if self.revid is not None:
857+ manifest += "%s revid:%s\n" % (self.url, self.revid)
858+ elif self.revspec is not None:
859+ manifest += "%s %s\n" % (self.url, self.revspec)
860+ else:
861+ manifest += "%s\n" % (self.url,)
862+ manifest += self._add_child_branches_to_manifest(self.child_branches,
863+ 0)
864+ # Sanity check.
865+ # TODO: write a function that compares the result of this parse with
866+ # the branch that we built it from.
867+ RecipeParser(manifest).parse()
868+ return manifest
869+
870
871 class RecipeParseError(errors.BzrError):
872 _fmt = "Error parsing %(filename)s:%(line)s:%(char)s: %(problem)s."
873
874=== modified file 'setup.py'
875--- setup.py 2009-08-25 11:43:42 +0000
876+++ setup.py 2010-04-08 03:00:43 +0000
877@@ -2,15 +2,16 @@
878
879 from distutils.core import setup
880
881-setup(name="bzr-builder",
882- version="0.1",
883- description="Turn a recipe in to a bzr branch",
884- author="James Westby",
885- author_email="james.westby@canonical.com",
886- license="GNU GPL v3",
887- url="http://launchpad.net/bzr-builder",
888- packages=['bzrlib.plugins.builder',
889- 'bzrlib.plugins.builder.tests',
890- ],
891- package_dir={'bzrlib.plugins.builder': '.'},
892- )
893+if __name__ == '__main__':
894+ setup(name="bzr-builder",
895+ version="0.2",
896+ description="Turn a recipe in to a bzr branch",
897+ author="James Westby",
898+ author_email="james.westby@canonical.com",
899+ license="GNU GPL v3",
900+ url="http://launchpad.net/bzr-builder",
901+ packages=['bzrlib.plugins.builder',
902+ 'bzrlib.plugins.builder.tests',
903+ ],
904+ package_dir={'bzrlib.plugins.builder': '.'},
905+ )
906
907=== modified file 'tests/__init__.py'
908--- tests/__init__.py 2009-04-16 20:23:39 +0000
909+++ tests/__init__.py 2010-04-08 03:00:43 +0000
910@@ -20,9 +20,10 @@
911 loader = TestUtil.TestLoader()
912 suite = TestSuite()
913 testmod_names = [
914- 'test_blackbox',
915- 'test_recipe',
916+ 'blackbox',
917+ 'ppa',
918+ 'recipe',
919 ]
920- suite.addTest(loader.loadTestsFromModuleNames(["%s.%s" % (__name__, i)
921+ suite.addTest(loader.loadTestsFromModuleNames(["%s.test_%s" % (__name__, i)
922 for i in testmod_names]))
923 return suite
924
925=== modified file 'tests/test_blackbox.py'
926--- tests/test_blackbox.py 2009-09-21 18:07:53 +0000
927+++ tests/test_blackbox.py 2010-04-08 03:00:43 +0000
928@@ -83,7 +83,7 @@
929 source = self.make_branch_and_tree("source")
930 self.build_tree(["source/a"])
931 source.add(["a"])
932- revid = source.commit("one")
933+ source.commit("one")
934 out, err = self.run_bzr("build recipe working "
935 "--if-changed-from manifest")
936
937@@ -149,12 +149,14 @@
938 cl_f = open(cl_path)
939 try:
940 line = cl_f.readline()
941- self.assertEqual("foo (1) jaunty; urgency=low\n", line)
942+ self.assertEqual("foo (1) lucid; urgency=low\n", line)
943 finally:
944 cl_f.close()
945
946 def test_cmd_dailydeb_no_work_dir(self):
947 #TODO: define a test feature for debuild and require it here.
948+ if getattr(self, "permit_dir", None) is not None:
949+ self.permit_dir('/') # Allow the made working dir to be accessed.
950 source = self.make_branch_and_tree("source")
951 self.build_tree(["source/a", "source/debian/"])
952 self.build_tree_contents([("source/debian/rules",
953@@ -162,7 +164,7 @@
954 ("source/debian/control",
955 "Source: foo\nMaintainer: maint maint@maint.org\n")])
956 source.add(["a", "debian/", "debian/rules", "debian/control"])
957- revid = source.commit("one")
958+ source.commit("one")
959 self.build_tree_contents([("test.recipe", "# bzr-builder format 0.1 "
960 "deb-version 1\nsource 1\n")])
961 out, err = self.run_bzr("dailydeb test.recipe "
962@@ -170,6 +172,8 @@
963
964 def test_cmd_dailydeb_if_changed_from_non_existant(self):
965 #TODO: define a test feature for debuild and require it here.
966+ if getattr(self, "permit_dir", None) is not None:
967+ self.permit_dir('/') # Allow the made working dir to be accessed.
968 source = self.make_branch_and_tree("source")
969 self.build_tree(["source/a", "source/debian/"])
970 self.build_tree_contents([("source/debian/rules",
971@@ -177,14 +181,13 @@
972 ("source/debian/control",
973 "Source: foo\nMaintainer: maint maint@maint.org\n")])
974 source.add(["a", "debian/", "debian/rules", "debian/control"])
975- revid = source.commit("one")
976+ source.commit("one")
977 self.build_tree_contents([("test.recipe", "# bzr-builder format 0.1 "
978 "deb-version 1\nsource 1\n")])
979 out, err = self.run_bzr("dailydeb test.recipe "
980 "--manifest manifest --package foo --if-changed-from bar")
981
982- def test_cmd_dailydeb_with_package_from_changelog(self):
983- #TODO: define a test feature for debuild and require it here.
984+ def make_simple_package(self):
985 source = self.make_branch_and_tree("source")
986 self.build_tree(["source/a", "source/debian/"])
987 cl_contents = ("package (0.1-1) unstable; urgency=low\n * foo\n"
988@@ -197,7 +200,29 @@
989 ("source/debian/changelog", cl_contents)])
990 source.add(["a", "debian/", "debian/rules", "debian/control",
991 "debian/changelog"])
992- revid = source.commit("one")
993+ source.commit("one")
994+ return source
995+
996+ def test_cmd_dailydeb_no_build(self):
997+ self.make_simple_package()
998+ self.build_tree_contents([("test.recipe", "# bzr-builder format 0.1 "
999+ "deb-version 1\nsource 1\n")])
1000+ out, err = self.run_bzr("dailydeb test.recipe "
1001+ "--manifest manifest --no-build working")
1002+ new_cl_contents = ("package (1) unstable; urgency=low\n\n"
1003+ " * Auto build.\n\n -- M. Maintainer <maint@maint.org> ")
1004+ f = open("working/test-1/debian/changelog")
1005+ try:
1006+ actual_cl_contents = f.read()
1007+ finally:
1008+ f.close()
1009+ self.assertStartsWith(actual_cl_contents, new_cl_contents)
1010+ for fn in os.listdir("working"):
1011+ self.assertFalse(fn.endswith(".changes"))
1012+
1013+ def test_cmd_dailydeb_with_package_from_changelog(self):
1014+ #TODO: define a test feature for debuild and require it here.
1015+ self.make_simple_package()
1016 self.build_tree_contents([("test.recipe", "# bzr-builder format 0.1 "
1017 "deb-version 1\nsource 1\n")])
1018 out, err = self.run_bzr("dailydeb test.recipe "
1019@@ -209,5 +234,18 @@
1020 actual_cl_contents = f.read()
1021 finally:
1022 f.close()
1023- self.assertEqual(new_cl_contents,
1024- actual_cl_contents[:len(new_cl_contents)])
1025+ self.assertStartsWith(actual_cl_contents, new_cl_contents)
1026+
1027+ def test_cmd_dailydeb_with_upstream_version_from_changelog(self):
1028+ self.make_simple_package()
1029+ self.build_tree_contents([("test.recipe", "# bzr-builder format 0.1 "
1030+ "deb-version {debupstream}-2\nsource 1\n")])
1031+ out, err = self.run_bzr("dailydeb test.recipe working")
1032+ new_cl_contents = ("package (0.1-2) unstable; urgency=low\n\n"
1033+ " * Auto build.\n\n -- M. Maintainer <maint@maint.org> ")
1034+ f = open("working/test-{debupstream}-2/debian/changelog")
1035+ try:
1036+ actual_cl_contents = f.read()
1037+ finally:
1038+ f.close()
1039+ self.assertStartsWith(actual_cl_contents, new_cl_contents)
1040
1041=== added file 'tests/test_ppa.py'
1042--- tests/test_ppa.py 1970-01-01 00:00:00 +0000
1043+++ tests/test_ppa.py 2010-04-08 03:00:43 +0000
1044@@ -0,0 +1,31 @@
1045+# bzr-builder: a bzr plugin to construct trees based on recipes
1046+# Copyright 2009 Canonical Ltd.
1047+
1048+# This program is free software: you can redistribute it and/or modify it
1049+# under the terms of the GNU General Public License version 3, as published
1050+# by the Free Software Foundation.
1051+
1052+# This program is distributed in the hope that it will be useful, but
1053+# WITHOUT ANY WARRANTY; without even the implied warranties of
1054+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1055+# PURPOSE. See the GNU General Public License for more details.
1056+
1057+# You should have received a copy of the GNU General Public License along
1058+# with this program. If not, see <http://www.gnu.org/licenses/>.
1059+
1060+import os
1061+
1062+from bzrlib import workingtree
1063+from bzrlib.plugins.builder import target_from_dput
1064+from bzrlib.tests import (
1065+ TestCase,
1066+ )
1067+
1068+
1069+class TestTargetFromDPut(TestCase):
1070+
1071+ def test_default_ppa(self):
1072+ self.assertEqual('team-name/ppa', target_from_dput('ppa:team-name'))
1073+
1074+ def test_named_ppa(self):
1075+ self.assertEqual('team/ppa2', target_from_dput('ppa:team/ppa2'))
1076
1077=== modified file 'tests/test_recipe.py'
1078--- tests/test_recipe.py 2009-08-18 21:25:32 +0000
1079+++ tests/test_recipe.py 2010-04-08 03:00:43 +0000
1080@@ -27,7 +27,6 @@
1081 )
1082 from bzrlib.plugins.builder.recipe import (
1083 BaseRecipeBranch,
1084- build_manifest,
1085 build_tree,
1086 ensure_basedir,
1087 pull_or_branch,
1088@@ -216,7 +215,7 @@
1089 + "merge bar http://bar.org")
1090 self.check_base_recipe_branch(base_branch, "http://foo.org/",
1091 num_child_branches=1)
1092- child_branch, location = base_branch.child_branches[0]
1093+ child_branch, location = base_branch.child_branches[0].as_tuple()
1094 self.assertEqual(None, location)
1095 self.check_recipe_branch(child_branch, "bar", "http://bar.org")
1096
1097@@ -225,7 +224,7 @@
1098 + "nest bar http://bar.org baz")
1099 self.check_base_recipe_branch(base_branch, "http://foo.org/",
1100 num_child_branches=1)
1101- child_branch, location = base_branch.child_branches[0]
1102+ child_branch, location = base_branch.child_branches[0].as_tuple()
1103 self.assertEqual("baz", location)
1104 self.check_recipe_branch(child_branch, "bar", "http://bar.org")
1105
1106@@ -234,10 +233,10 @@
1107 + "nest bar http://bar.org baz\nmerge zam lp:zam")
1108 self.check_base_recipe_branch(base_branch, "http://foo.org/",
1109 num_child_branches=2)
1110- child_branch, location = base_branch.child_branches[0]
1111+ child_branch, location = base_branch.child_branches[0].as_tuple()
1112 self.assertEqual("baz", location)
1113 self.check_recipe_branch(child_branch, "bar", "http://bar.org")
1114- child_branch, location = base_branch.child_branches[1]
1115+ child_branch, location = base_branch.child_branches[1].as_tuple()
1116 self.assertEqual(None, location)
1117 self.check_recipe_branch(child_branch, "zam", "lp:zam")
1118
1119@@ -246,10 +245,10 @@
1120 + "merge zam lp:zam\nnest bar http://bar.org baz")
1121 self.check_base_recipe_branch(base_branch, "http://foo.org/",
1122 num_child_branches=2)
1123- child_branch, location = base_branch.child_branches[0]
1124+ child_branch, location = base_branch.child_branches[0].as_tuple()
1125 self.assertEqual(None, location)
1126 self.check_recipe_branch(child_branch, "zam", "lp:zam")
1127- child_branch, location = base_branch.child_branches[1]
1128+ child_branch, location = base_branch.child_branches[1].as_tuple()
1129 self.assertEqual("baz", location)
1130 self.check_recipe_branch(child_branch, "bar", "http://bar.org")
1131
1132@@ -258,11 +257,11 @@
1133 + "nest bar http://bar.org baz\n merge zam lp:zam")
1134 self.check_base_recipe_branch(base_branch, "http://foo.org/",
1135 num_child_branches=1)
1136- child_branch, location = base_branch.child_branches[0]
1137+ child_branch, location = base_branch.child_branches[0].as_tuple()
1138 self.assertEqual("baz", location)
1139 self.check_recipe_branch(child_branch, "bar", "http://bar.org",
1140 num_child_branches=1)
1141- child_branch, location = child_branch.child_branches[0]
1142+ child_branch, location = child_branch.child_branches[0].as_tuple()
1143 self.assertEqual(None, location)
1144 self.check_recipe_branch(child_branch, "zam", "lp:zam")
1145
1146@@ -271,11 +270,11 @@
1147 + "nest bar http://bar.org baz\n nest zam lp:zam zoo")
1148 self.check_base_recipe_branch(base_branch, "http://foo.org/",
1149 num_child_branches=1)
1150- child_branch, location = base_branch.child_branches[0]
1151+ child_branch, location = base_branch.child_branches[0].as_tuple()
1152 self.assertEqual("baz", location)
1153 self.check_recipe_branch(child_branch, "bar", "http://bar.org",
1154 num_child_branches=1)
1155- child_branch, location = child_branch.child_branches[0]
1156+ child_branch, location = child_branch.child_branches[0].as_tuple()
1157 self.assertEqual("zoo", location)
1158 self.check_recipe_branch(child_branch, "zam", "lp:zam")
1159
1160@@ -286,11 +285,13 @@
1161 + "merge zam lp:zam 2")
1162 self.check_base_recipe_branch(base_branch, "http://foo.org/",
1163 num_child_branches=2, revspec="revid:a")
1164- child_branch, location = base_branch.child_branches[0]
1165+ instruction = base_branch.child_branches[0]
1166+ child_branch = instruction.recipe_branch
1167+ location = instruction.nest_path
1168 self.assertEqual("baz", location)
1169 self.check_recipe_branch(child_branch, "bar", "http://bar.org",
1170 revspec="tag:b")
1171- child_branch, location = base_branch.child_branches[1]
1172+ child_branch, location = base_branch.child_branches[1].as_tuple()
1173 self.assertEqual(None, location)
1174 self.check_recipe_branch(child_branch, "zam", "lp:zam", revspec="2")
1175
1176@@ -300,7 +301,7 @@
1177 + "run touch test \n")
1178 self.check_base_recipe_branch(base_branch, "http://foo.org/",
1179 num_child_branches=1)
1180- child_branch, command = base_branch.child_branches[0]
1181+ child_branch, command = base_branch.child_branches[0].as_tuple()
1182 self.assertEqual(None, child_branch)
1183 self.assertEqual("touch test", command)
1184
1185@@ -350,7 +351,7 @@
1186 def test_build_tree_single_branch_existing_branch(self):
1187 source = self.make_branch_and_tree("source")
1188 revid = source.commit("one")
1189- target = self.make_branch_and_tree("target")
1190+ self.make_branch_and_tree("target")
1191 base_branch = BaseRecipeBranch("source", "1", 0.2)
1192 build_tree(base_branch, "target")
1193 self.failUnlessExists("target")
1194@@ -446,7 +447,7 @@
1195 base_branch = BaseRecipeBranch("source1", "1", 0.2)
1196 merged_branch = RecipeBranch("merged", "source2")
1197 base_branch.merge_branch(merged_branch)
1198- e = self.assertRaises(errors.BzrCommandError, build_tree,
1199+ self.assertRaises(errors.BzrCommandError, build_tree,
1200 base_branch, "target")
1201 self.failUnlessExists("target")
1202 tree = workingtree.WorkingTree.open("target")
1203@@ -702,7 +703,7 @@
1204
1205 def test_changed_command(self):
1206 source =self.make_branch_and_tree("source")
1207- revid = source.commit("one")
1208+ source.commit("one")
1209 branch1 = BaseRecipeBranch("source", "{revno}", 0.2)
1210 branch2 = BaseRecipeBranch("source", "{revno}", 0.2)
1211 branch1.run_command("touch test1")
1212@@ -714,7 +715,7 @@
1213 def test_substitute(self):
1214 source =self.make_branch_and_tree("source")
1215 revid1 = source.commit("one")
1216- revid2 = source.commit("two")
1217+ source.commit("two")
1218 branch1 = BaseRecipeBranch("source",
1219 "{revno}-{revno:packaging}", 0.2, revspec="1")
1220 branch2 = RecipeBranch("packaging", "source")
1221@@ -725,13 +726,23 @@
1222 self.assertEqual("1", branch1.revspec)
1223 self.assertEqual("1-2", branch1.deb_version)
1224
1225-
1226-class BuildManifestTests(TestCaseInTempDir):
1227+ def test_substitute_supports_debupstream(self):
1228+ # resolve_revisions should leave debupstream parameters alone and not
1229+ # complain.
1230+ source =self.make_branch_and_tree("source")
1231+ source.commit("one")
1232+ source.commit("two")
1233+ branch1 = BaseRecipeBranch("source", "{debupstream}-{revno}", 0.2)
1234+ resolve_revisions(branch1)
1235+ self.assertEqual("{debupstream}-2", branch1.deb_version)
1236+
1237+
1238+class StringifyTests(TestCaseInTempDir):
1239
1240 def test_simple_manifest(self):
1241 base_branch = BaseRecipeBranch("base_url", "1", 0.1)
1242 base_branch.revid = "base_revid"
1243- manifest = build_manifest(base_branch)
1244+ manifest = str(base_branch)
1245 self.assertEqual("# bzr-builder format 0.1 deb-version 1\n"
1246 "base_url revid:base_revid\n", manifest)
1247
1248@@ -747,7 +758,7 @@
1249 merged_branch = RecipeBranch("merged", "merged_url")
1250 merged_branch.revid = "merged_revid"
1251 base_branch.merge_branch(merged_branch)
1252- manifest = build_manifest(base_branch)
1253+ manifest = str(base_branch)
1254 self.assertEqual("# bzr-builder format 0.2 deb-version 2\n"
1255 "base_url revid:base_revid\n"
1256 "nest nested1 nested1_url nested revid:nested1_revid\n"
1257@@ -758,11 +769,40 @@
1258 base_branch = BaseRecipeBranch("base_url", "1", 0.2)
1259 base_branch.revid = "base_revid"
1260 base_branch.run_command("touch test")
1261- manifest = build_manifest(base_branch)
1262+ manifest = str(base_branch)
1263 self.assertEqual("# bzr-builder format 0.2 deb-version 1\n"
1264 "base_url revid:base_revid\n"
1265 "run touch test\n", manifest)
1266
1267+ def test_recipe_with_no_revspec(self):
1268+ base_branch = BaseRecipeBranch("base_url", "1", 0.1)
1269+ manifest = str(base_branch)
1270+ self.assertEqual("# bzr-builder format 0.1 deb-version 1\n"
1271+ "base_url\n", manifest)
1272+
1273+ def test_recipe_with_tag_revspec(self):
1274+ base_branch = BaseRecipeBranch("base_url", "1", 0.1,
1275+ revspec="tag:foo")
1276+ manifest = str(base_branch)
1277+ self.assertEqual("# bzr-builder format 0.1 deb-version 1\n"
1278+ "base_url tag:foo\n", manifest)
1279+
1280+ def test_recipe_with_child(self):
1281+ base_branch = BaseRecipeBranch("base_url", "2", 0.2)
1282+ nested_branch1 = RecipeBranch("nested1", "nested1_url",
1283+ revspec="tag:foo")
1284+ base_branch.nest_branch("nested", nested_branch1)
1285+ nested_branch2 = RecipeBranch("nested2", "nested2_url")
1286+ nested_branch1.nest_branch("nested2", nested_branch2)
1287+ merged_branch = RecipeBranch("merged", "merged_url")
1288+ base_branch.merge_branch(merged_branch)
1289+ manifest = str(base_branch)
1290+ self.assertEqual("# bzr-builder format 0.2 deb-version 2\n"
1291+ "base_url\n"
1292+ "nest nested1 nested1_url nested tag:foo\n"
1293+ " nest nested2 nested2_url nested2\n"
1294+ "merge merged merged_url\n", manifest)
1295+
1296
1297 class RecipeBranchTests(TestCaseInTempDir):
1298

Subscribers

People subscribed via source and target branches