Merge lp:~jelmer/bzr-builder/lp-urls into lp:bzr-builder
- lp-urls
- Merge into trunk
Proposed by
Jelmer Vernooij
Status: | Merged |
---|---|
Approved by: | James Westby |
Approved revision: | 128 |
Merged at revision: | 126 |
Proposed branch: | lp:~jelmer/bzr-builder/lp-urls |
Merge into: | lp:bzr-builder |
Diff against target: |
342 lines (+108/-63) 3 files modified
cmds.py (+105/-52) ppa.py (+2/-10) tests/test_blackbox.py (+1/-1) |
To merge this branch: | bzr merge lp:~jelmer/bzr-builder/lp-urls |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
James Westby | Approve | ||
Review via email: mp+61446@code.launchpad.net |
Commit message
Description of the change
Support building directly from a Launchpad recipe URL.
This means that things like:
bzr dailydeb https:/
now do the right thing
To post a comment you must log in.
Revision history for this message
Jelmer Vernooij (jelmer) wrote : | # |
On Wed, 2011-05-18 at 18:14 +0000, James Westby wrote:
> Review: Approve
> 78 + if safe:
> 79 + permitted_
> 80 + else:
> 81 + permitted_
>
> Would you move that above the opening of the file, so that it is outside
> of the try block, so that block is clearly focused on the code that
> might reasonably fail?
Done.
> Thanks for the cool feature.
Thanks!
Cheers,
Jelmer
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'cmds.py' |
2 | --- cmds.py 2011-05-18 16:12:22 +0000 |
3 | +++ cmds.py 2011-05-18 17:14:21 +0000 |
4 | @@ -15,6 +15,7 @@ |
5 | |
6 | """Subcommands provided by bzr-builder.""" |
7 | |
8 | +from StringIO import StringIO |
9 | import datetime |
10 | from email import utils |
11 | import errno |
12 | @@ -35,8 +36,10 @@ |
13 | |
14 | from bzrlib import ( |
15 | errors, |
16 | + lazy_regex, |
17 | trace, |
18 | - transport, |
19 | + transport as _mod_transport, |
20 | + urlutils, |
21 | ) |
22 | from bzrlib.commands import Command |
23 | from bzrlib.option import Option |
24 | @@ -59,54 +62,53 @@ |
25 | pass |
26 | |
27 | |
28 | -def write_manifest_to_path(path, base_branch): |
29 | +def write_manifest_to_transport(location, base_branch): |
30 | """Write a manifest to disk. |
31 | |
32 | - :param path: Path to write to |
33 | + :param location: Location to write to |
34 | :param base_branch: Recipe base branch |
35 | """ |
36 | - parent_dir = os.path.dirname(path) |
37 | - if parent_dir != '' and not os.path.exists(parent_dir): |
38 | - os.makedirs(parent_dir) |
39 | - manifest_f = open(path, 'wb') |
40 | - try: |
41 | - manifest_f.write(str(base_branch)) |
42 | - finally: |
43 | - manifest_f.close() |
44 | - |
45 | - |
46 | -def get_branch_from_recipe_file(recipe_file, safe=False): |
47 | + child_transport = _mod_transport.get_transport(location) |
48 | + base_transport = child_transport.clone('..') |
49 | + base_transport.create_prefix() |
50 | + basename = base_transport.relpath(child_transport.base) |
51 | + base_transport.put_bytes(basename, str(base_branch)) |
52 | + |
53 | + |
54 | +def get_branch_from_recipe_location(recipe_location, safe=False, |
55 | + possible_transports=None): |
56 | """Return the base branch for the specified recipe. |
57 | |
58 | - :param recipe_file: The URL of the recipe file to retrieve. |
59 | + :param recipe_location: The URL of the recipe file to retrieve. |
60 | :param safe: if True, reject recipes that would cause arbitrary code |
61 | execution. |
62 | """ |
63 | - recipe_transport = transport.get_transport(os.path.dirname(recipe_file)) |
64 | try: |
65 | - recipe_contents = recipe_transport.get_bytes( |
66 | - os.path.basename(recipe_file)) |
67 | + (basename, f) = get_recipe_from_location(recipe_location, possible_transports) |
68 | except errors.NoSuchFile: |
69 | raise errors.BzrCommandError("Specified recipe does not exist: " |
70 | - "%s" % recipe_file) |
71 | - if safe: |
72 | - permitted_instructions = SAFE_INSTRUCTIONS |
73 | - else: |
74 | - permitted_instructions = None |
75 | - parser = RecipeParser(recipe_contents, filename=recipe_file) |
76 | + "%s" % recipe_location) |
77 | + try: |
78 | + if safe: |
79 | + permitted_instructions = SAFE_INSTRUCTIONS |
80 | + else: |
81 | + permitted_instructions = None |
82 | + parser = RecipeParser(f, filename=recipe_location) |
83 | + finally: |
84 | + f.close() |
85 | return parser.parse(permitted_instructions=permitted_instructions) |
86 | |
87 | |
88 | -def get_old_recipe(if_changed_from): |
89 | - old_manifest_transport = transport.get_transport(os.path.dirname( |
90 | - if_changed_from)) |
91 | +def get_old_recipe(if_changed_from, possible_transports=None): |
92 | try: |
93 | - old_manifest_contents = old_manifest_transport.get_bytes( |
94 | - os.path.basename(if_changed_from)) |
95 | + (basename, f) = get_recipe_from_location(if_changed_from, possible_transports) |
96 | except errors.NoSuchFile: |
97 | return None |
98 | - old_recipe = RecipeParser(old_manifest_contents, |
99 | - filename=if_changed_from).parse() |
100 | + try: |
101 | + old_recipe = RecipeParser(f, |
102 | + filename=if_changed_from).parse() |
103 | + finally: |
104 | + f.close() |
105 | return old_recipe |
106 | |
107 | |
108 | @@ -395,6 +397,53 @@ |
109 | "install the devscripts package.") |
110 | |
111 | |
112 | +launchpad_recipe_re = lazy_regex.lazy_compile( |
113 | + r'^https://code.launchpad.net/~(.*)/\+recipe/(.*)$') |
114 | + |
115 | + |
116 | +def get_recipe_from_launchpad(username, recipe_name, location): |
117 | + """Load a recipe from Launchpad. |
118 | + |
119 | + :param username: The launchpad user name |
120 | + :param recipe_name: Recipe name |
121 | + :param location: Original location (used for error reporting) |
122 | + :return: Text of the recipe |
123 | + """ |
124 | + from launchpadlib.launchpad import Launchpad |
125 | + lp = Launchpad.login_with("bzr-builder", "production") |
126 | + try: |
127 | + person = lp.people[username] |
128 | + except KeyError: |
129 | + raise errors.NoSuchFile(location, |
130 | + "No such Launchpad user %s" % username) |
131 | + recipe = person.getRecipe(name=recipe_name) |
132 | + if recipe is None: |
133 | + raise errors.NoSuchFile(location, |
134 | + "Launchpad user %s has no recipe %s" % ( |
135 | + username, recipe_name)) |
136 | + return recipe.recipe_text |
137 | + |
138 | + |
139 | +def get_recipe_from_location(location, possible_transports=None): |
140 | + """Open a recipe as a file-like object from a URL. |
141 | + |
142 | + :param location: The recipe location |
143 | + :param possible_transports: Possible transports to use |
144 | + :return: Tuple with basename and file-like object |
145 | + """ |
146 | + m = launchpad_recipe_re.match(location) |
147 | + if m: |
148 | + (username, recipe_name) = m.groups() |
149 | + text = get_recipe_from_launchpad(username, recipe_name, |
150 | + location) |
151 | + return (recipe_name, StringIO(text)) |
152 | + child_transport = _mod_transport.get_transport(location, |
153 | + possible_transports=possible_transports) |
154 | + recipe_transport = child_transport.clone('..') |
155 | + basename = recipe_transport.relpath(child_transport.base) |
156 | + return basename, recipe_transport.get(basename) |
157 | + |
158 | + |
159 | class cmd_build(Command): |
160 | """Build a tree based on a 'recipe'. |
161 | |
162 | @@ -402,7 +451,7 @@ |
163 | |
164 | See "bzr help builder" for more information on what a recipe is. |
165 | """ |
166 | - takes_args = ["recipe_file", "working_directory"] |
167 | + takes_args = ["recipe_location", "working_directory"] |
168 | takes_options = [ |
169 | Option('manifest', type=str, argname="path", |
170 | help="Path to write the manifest to."), |
171 | @@ -411,24 +460,25 @@ |
172 | "to that specified in the specified manifest."), |
173 | ] |
174 | |
175 | - def _get_prepared_branch_from_recipe(self, recipe_file, |
176 | - if_changed_from=None, safe=False): |
177 | + def _get_prepared_branch_from_recipe(self, recipe_location, |
178 | + if_changed_from=None, safe=False, possible_transports=None): |
179 | """Common code to prepare a branch and do substitutions. |
180 | |
181 | - :param recipe_file: a path to a recipe file to work from. |
182 | - :param if_changed_from: an optional path to a manifest to |
183 | + :param recipe_location: a path to a recipe file to work from. |
184 | + :param if_changed_from: an optional location of a manifest to |
185 | compare the recipe against. |
186 | :param safe: if True, reject recipes that would cause arbitrary code |
187 | execution. |
188 | :return: A tuple with (retcode, base_branch). If retcode is None |
189 | then the command execution should continue. |
190 | """ |
191 | - base_branch = get_branch_from_recipe_file(recipe_file, safe=safe) |
192 | + base_branch = get_branch_from_recipe_location(recipe_location, safe=safe, |
193 | + possible_transports=possible_transports) |
194 | time = datetime.datetime.utcnow() |
195 | base_branch.substitute_time(time) |
196 | old_recipe = None |
197 | if if_changed_from is not None: |
198 | - old_recipe = get_old_recipe(if_changed_from) |
199 | + old_recipe = get_old_recipe(if_changed_from, possible_transports) |
200 | # Save the unsubstituted version for dailydeb. |
201 | self._template_version = base_branch.deb_version |
202 | changed = resolve_revisions(base_branch, if_changed_from=old_recipe) |
203 | @@ -437,16 +487,17 @@ |
204 | return 0, base_branch |
205 | return None, base_branch |
206 | |
207 | - def run(self, recipe_file, working_directory, manifest=None, |
208 | + def run(self, recipe_location, working_directory, manifest=None, |
209 | if_changed_from=None): |
210 | - result, base_branch = self._get_prepared_branch_from_recipe(recipe_file, |
211 | - if_changed_from=if_changed_from) |
212 | + possible_transports = [] |
213 | + result, base_branch = self._get_prepared_branch_from_recipe(recipe_location, |
214 | + if_changed_from=if_changed_from, possible_transports=possible_transports) |
215 | if result is not None: |
216 | return result |
217 | manifest_path = manifest or os.path.join(working_directory, |
218 | "bzr-builder.manifest") |
219 | build_tree(base_branch, working_directory) |
220 | - write_manifest_to_path(manifest_path, base_branch) |
221 | + write_manifest_to_transport(manifest_path, base_branch) |
222 | |
223 | |
224 | def debian_source_package_name(control_path): |
225 | @@ -497,9 +548,9 @@ |
226 | " arbitrary code execution."), |
227 | ] |
228 | |
229 | - takes_args = ["recipe_file", "working_basedir?"] |
230 | + takes_args = ["recipe_location", "working_basedir?"] |
231 | |
232 | - def run(self, recipe_file, working_basedir=None, manifest=None, |
233 | + def run(self, recipe_location, working_basedir=None, manifest=None, |
234 | if_changed_from=None, package=None, distribution=None, |
235 | dput=None, key_id=None, no_build=None, watch_ppa=False, |
236 | append_version=None, safe=False): |
237 | @@ -515,8 +566,10 @@ |
238 | # Check we can calculate a PPA url. |
239 | target_from_dput(dput) |
240 | |
241 | - result, base_branch = self._get_prepared_branch_from_recipe(recipe_file, |
242 | - if_changed_from=if_changed_from, safe=safe) |
243 | + possible_transports = [] |
244 | + result, base_branch = self._get_prepared_branch_from_recipe(recipe_location, |
245 | + if_changed_from=if_changed_from, safe=safe, |
246 | + possible_transports=possible_transports) |
247 | if result is not None: |
248 | return result |
249 | if working_basedir is None: |
250 | @@ -526,7 +579,7 @@ |
251 | temp_dir = None |
252 | if not os.path.exists(working_basedir): |
253 | os.makedirs(working_basedir) |
254 | - package_name = self._calculate_package_name(recipe_file, package) |
255 | + package_name = self._calculate_package_name(recipe_location, package) |
256 | working_directory = os.path.join(working_basedir, |
257 | "%s-%s" % (package_name, self._template_version)) |
258 | try: |
259 | @@ -544,7 +597,7 @@ |
260 | raise errors.BzrCommandError("Missing debian/control file to " |
261 | "read package name from.") |
262 | package = debian_source_package_name(control_path) |
263 | - write_manifest_to_path(manifest_path, base_branch) |
264 | + write_manifest_to_transport(manifest_path, base_branch) |
265 | # Add changelog also substitutes {debupstream}. |
266 | add_changelog_entry(base_branch, working_directory, |
267 | distribution=distribution, package=package, |
268 | @@ -556,7 +609,7 @@ |
269 | os.rename(working_directory, package_dir) |
270 | if no_build: |
271 | if manifest is not None: |
272 | - write_manifest_to_path(manifest, base_branch) |
273 | + write_manifest_to_transport(manifest, base_branch) |
274 | return 0 |
275 | try: |
276 | build_source_package(package_dir) |
277 | @@ -570,7 +623,7 @@ |
278 | os.rename(package_dir, working_directory) |
279 | # Note that this may write a second manifest. |
280 | if manifest is not None: |
281 | - write_manifest_to_path(manifest, base_branch) |
282 | + write_manifest_to_transport(manifest, base_branch) |
283 | finally: |
284 | if temp_dir is not None: |
285 | shutil.rmtree(temp_dir) |
286 | @@ -580,9 +633,9 @@ |
287 | if not watch(owner, archive, package_name, base_branch.deb_version): |
288 | return 2 |
289 | |
290 | - def _calculate_package_name(self, recipe_file, package): |
291 | + def _calculate_package_name(self, recipe_location, package): |
292 | """Calculate the directory name that should be used while debuilding.""" |
293 | - recipe_name = os.path.basename(recipe_file) |
294 | + recipe_name = urlutils.basename(recipe_location) |
295 | if recipe_name.endswith(".recipe"): |
296 | recipe_name = recipe_name[:-len(".recipe")] |
297 | return package or recipe_name |
298 | |
299 | === modified file 'ppa.py' |
300 | --- ppa.py 2011-03-28 10:31:13 +0000 |
301 | +++ ppa.py 2011-05-18 17:14:21 +0000 |
302 | @@ -14,25 +14,17 @@ |
303 | # You should have received a copy of the GNU General Public License along |
304 | # with this program. If not, see <http://www.gnu.org/licenses/>. |
305 | |
306 | -import os |
307 | import time |
308 | |
309 | - |
310 | -from launchpadlib.launchpad import ( |
311 | - Launchpad, |
312 | - EDGE_SERVICE_ROOT, |
313 | - ) |
314 | -from launchpadlib.credentials import Credentials |
315 | - |
316 | from bzrlib import ( |
317 | errors, |
318 | trace, |
319 | ) |
320 | +from launchpadlib.launchpad import Launchpad |
321 | |
322 | |
323 | def get_lp(): |
324 | - oauth_file = os.path.expanduser('~/.cache/launchpadlib/bzr-builder') |
325 | - return Launchpad.login_with('bzr-builder', 'production', credentials_file=oauth_file) |
326 | + return Launchpad.login_with('bzr-builder', 'production') |
327 | |
328 | |
329 | def watch(owner_name, archive_name, package_name, version): |
330 | |
331 | === modified file 'tests/test_blackbox.py' |
332 | --- tests/test_blackbox.py 2011-05-18 16:12:22 +0000 |
333 | +++ tests/test_blackbox.py 2011-05-18 17:14:21 +0000 |
334 | @@ -41,7 +41,7 @@ |
335 | def test_cmd_builder_requires_recipe_file_argument(self): |
336 | err = self.run_bzr("build", retcode=3)[1] |
337 | self.assertEqual("bzr: ERROR: command 'build' requires argument " |
338 | - "RECIPE_FILE\n", err) |
339 | + "RECIPE_LOCATION\n", err) |
340 | |
341 | def test_cmd_builder_requires_working_dir_argument(self): |
342 | err = self.run_bzr("build recipe", retcode=3)[1] |
78 + if safe: instructions = SAFE_INSTRUCTIONS instructions = None
79 + permitted_
80 + else:
81 + permitted_
Would you move that above the opening of the file, so that it is outside
of the try block, so that block is clearly focused on the code that
might reasonably fail?
Thanks for the cool feature.
James