Merge lp:~jelmer/bzr-builder/lp-urls into lp:bzr-builder

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
Reviewer Review Type Date Requested Status
James Westby Approve
Review via email: mp+61446@code.launchpad.net

Description of the change

Support building directly from a Launchpad recipe URL.

This means that things like:

bzr dailydeb https://code.launchpad.net/~bzr/+recipe/bzr-stats-daily

now do the right thing

To post a comment you must log in.
Revision history for this message
James Westby (james-w) wrote :

78 + if safe:
79 + permitted_instructions = SAFE_INSTRUCTIONS
80 + else:
81 + permitted_instructions = None

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

review: Approve
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_instructions = SAFE_INSTRUCTIONS
> 80 + else:
> 81 + permitted_instructions = None
>
> 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]

Subscribers

People subscribed via source and target branches