Merge lp:~lifeless/bzr-builder/blocking into lp:~james-w/bzr-builder/trunk-old
- blocking
- Merge into trunk-old
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
James Westby | Needs Resubmitting | ||
Review via email: mp+13826@code.launchpad.net |
Commit message
Description of the change
Robert Collins (lifeless) wrote : | # |
James Westby (james-w) wrote : | # |
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_
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.
48 + target = target_
49 + return watch(target, self.package, base_branch.
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_
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_
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
72 + raise errors.
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:
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...
- 54. By James Westby
-
EMAIL is the correct env var for the email address, not MAIL
Robert Collins (lifeless) wrote : | # |
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_
>
> 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.
> 48 + target = target_
> 49 + return watch(target, self.package, base_branch.
>
> 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_
> 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
> 72 + raise errors.
>
> 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:
>
> I'd prefer "len('ppa:')" than '4'.
I'd put a co...
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
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
Robert Collins (lifeless) wrote : | # |
I've got this in production now, and had to make further changes so its using trace.note now.
- 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
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 |
This adds the ability to block on a build till its done - useful for integration with test builders.