Merge ~cjwatson/launchpad-buildd:ttb-git into launchpad-buildd:master

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: 63b920feffbe405e69997b9475b83fb0bb54cc07
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~cjwatson/launchpad-buildd:ttb-git
Merge into: launchpad-buildd:master
Diff against target: 496 lines (+209/-115)
5 files modified
debian/changelog (+2/-0)
lpbuildd/target/generate_translation_templates.py (+24/-39)
lpbuildd/target/tests/test_generate_translation_templates.py (+166/-71)
lpbuildd/tests/test_translationtemplatesbuildmanager.py (+1/-1)
lpbuildd/translationtemplates.py (+16/-4)
Reviewer Review Type Date Requested Status
Ioana Lasc (community) Approve
Review via email: mp+383063@code.launchpad.net

Commit message

Convert translation templates builds to the VCS mixin

Description of the change

This adds git support. There'll be a fair bit of work needed on the Launchpad side before this is useful, but we might as well get started here.

This is essentially the same as https://code.launchpad.net/~cjwatson/launchpad-buildd/ttb-git/+merge/341389, converted to git and rebased on master.

To post a comment you must log in.
Revision history for this message
Ioana Lasc (ilasc) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/debian/changelog b/debian/changelog
2index b6abb76..867b35e 100644
3--- a/debian/changelog
4+++ b/debian/changelog
5@@ -19,6 +19,8 @@ launchpad-buildd (190) UNRELEASED; urgency=medium
6 * Only include mock in tests_require for Python 2.
7 * Fix snap proxy testing for Python 3.
8 * Rename [slave] configuration section to [builder].
9+ * Convert translation templates builds to the VCS mixin, thereby adding
10+ git support.
11
12 -- Colin Watson <cjwatson@ubuntu.com> Tue, 28 Apr 2020 10:19:27 +0100
13
14diff --git a/lpbuildd/target/generate_translation_templates.py b/lpbuildd/target/generate_translation_templates.py
15index 3e44af7..1e708ba 100644
16--- a/lpbuildd/target/generate_translation_templates.py
17+++ b/lpbuildd/target/generate_translation_templates.py
18@@ -10,6 +10,7 @@ import os.path
19
20 from lpbuildd.pottery import intltool
21 from lpbuildd.target.operation import Operation
22+from lpbuildd.target.vcs import VCSOperationMixin
23
24
25 logger = logging.getLogger(__name__)
26@@ -19,7 +20,7 @@ RETCODE_FAILURE_INSTALL = 200
27 RETCODE_FAILURE_BUILD = 201
28
29
30-class GenerateTranslationTemplates(Operation):
31+class GenerateTranslationTemplates(VCSOperationMixin, Operation):
32 """Script to generate translation templates from a branch."""
33
34 description = "Generate templates for a branch."
35@@ -28,42 +29,24 @@ class GenerateTranslationTemplates(Operation):
36 def add_arguments(cls, parser):
37 super(GenerateTranslationTemplates, cls).add_arguments(parser)
38 parser.add_argument(
39- "branch_spec", help=(
40- "A branch URL or the path of a local branch. URLs are "
41- "recognised by the occurrence of ':'. In the case of a URL, "
42- "this will make up a path for the branch and check out the "
43- "branch to there."))
44- parser.add_argument(
45 "result_name",
46- help="The name of the result tarball. Should end in '.tar.gz'.")
47+ help="the name of the result tarball; should end in '.tar.gz'")
48
49 def __init__(self, args, parser):
50 super(GenerateTranslationTemplates, self).__init__(args, parser)
51 self.work_dir = os.environ["HOME"]
52+ self.branch_dir = os.path.join(self.work_dir, "source-tree")
53+
54+ def install(self):
55+ logger.info("Installing dependencies...")
56+ deps = ["intltool"]
57+ deps.extend(self.vcs_deps)
58+ self.backend.run(["apt-get", "-y", "install"] + deps)
59
60- def _getBranch(self):
61- """Set `self.branch_dir`, and check out branch if needed."""
62- if ':' in self.args.branch_spec:
63- # This is a branch URL. Check out the branch.
64- self.branch_dir = os.path.join(self.work_dir, 'source-tree')
65- logger.info("Getting remote branch %s..." % self.args.branch_spec)
66- self._checkout(self.args.branch_spec)
67- else:
68- # This is a local filesystem path. Use the branch in-place.
69- logger.info("Using local branch %s..." % self.args.branch_spec)
70- self.branch_dir = self.args.branch_spec
71-
72- def _checkout(self, branch_url):
73- """Check out a source branch to generate from.
74-
75- The branch is checked out to the location specified by
76- `self.branch_dir`.
77- """
78- logger.info(
79- "Exporting branch %s to %s..." % (branch_url, self.branch_dir))
80- self.backend.run(
81- ["bzr", "export", "-q", "-d", branch_url, self.branch_dir])
82- logger.info("Exporting branch done.")
83+ def fetch(self, quiet=False):
84+ logger.info("Fetching %s...", self.vcs_description)
85+ self.vcs_fetch(
86+ os.path.basename(self.branch_dir), cwd=self.work_dir, quiet=quiet)
87
88 def _makeTarball(self, files):
89 """Put the given files into a tarball in the working directory."""
90@@ -78,21 +61,23 @@ class GenerateTranslationTemplates(Operation):
91 self.backend.run(cmd)
92 logger.info("Tarball generated.")
93
94+ def generate(self):
95+ logger.info("Generating templates...")
96+ pots = intltool.generate_pots(self.backend, self.branch_dir)
97+ logger.info("Generated %d templates." % len(pots))
98+ if len(pots) > 0:
99+ self._makeTarball(pots)
100+
101 def run(self):
102 """Do It. Generate templates."""
103 try:
104- self.backend.run(["apt-get", "-y", "install", "bzr", "intltool"])
105+ self.install()
106 except Exception:
107 logger.exception("Install failed")
108 return RETCODE_FAILURE_INSTALL
109 try:
110- logger.info(
111- "Generating templates for %s." % self.args.branch_spec)
112- self._getBranch()
113- pots = intltool.generate_pots(self.backend, self.branch_dir)
114- logger.info("Generated %d templates." % len(pots))
115- if len(pots) > 0:
116- self._makeTarball(pots)
117+ self.fetch()
118+ self.generate()
119 except Exception:
120 logger.exception("Build failed")
121 return RETCODE_FAILURE_BUILD
122diff --git a/lpbuildd/target/tests/test_generate_translation_templates.py b/lpbuildd/target/tests/test_generate_translation_templates.py
123index 16f6848..864e595 100644
124--- a/lpbuildd/target/tests/test_generate_translation_templates.py
125+++ b/lpbuildd/target/tests/test_generate_translation_templates.py
126@@ -15,23 +15,37 @@ from fixtures import (
127 from testtools import TestCase
128 from testtools.matchers import (
129 ContainsDict,
130- EndsWith,
131 Equals,
132+ Is,
133+ MatchesDict,
134 MatchesListwise,
135 MatchesSetwise,
136 )
137
138 from lpbuildd.target.cli import parse_args
139-from lpbuildd.tests.fakebuilder import FakeMethod
140
141
142-class MatchesCall(MatchesListwise):
143+class RanCommand(MatchesListwise):
144
145- def __init__(self, *args, **kwargs):
146- super(MatchesCall, self).__init__([
147- Equals(args),
148- ContainsDict(
149- {name: Equals(value) for name, value in kwargs.items()})])
150+ def __init__(self, args, get_output=None, echo=None, cwd=None, **env):
151+ kwargs_matcher = {}
152+ if get_output is not None:
153+ kwargs_matcher["get_output"] = Is(get_output)
154+ if echo is not None:
155+ kwargs_matcher["echo"] = Is(echo)
156+ if cwd:
157+ kwargs_matcher["cwd"] = Equals(cwd)
158+ if env:
159+ kwargs_matcher["env"] = MatchesDict(
160+ {key: Equals(value) for key, value in env.items()})
161+ super(RanCommand, self).__init__(
162+ [Equals((args,)), ContainsDict(kwargs_matcher)])
163+
164+
165+class RanAptGet(RanCommand):
166+
167+ def __init__(self, *args):
168+ super(RanAptGet, self).__init__(["apt-get", "-y"] + list(args))
169
170
171 class TestGenerateTranslationTemplates(TestCase):
172@@ -45,82 +59,125 @@ class TestGenerateTranslationTemplates(TestCase):
173 self.useFixture(EnvironmentVariable("HOME", self.home_dir))
174 self.logger = self.useFixture(FakeLogger())
175
176- def test_getBranch_url(self):
177- # If passed a branch URL, the template generation script will
178- # check out that branch into a directory called "source-tree."
179+ def make_branch_contents(self, content_map):
180+ """Create a directory with the contents of a working branch.
181+
182+ :param content_map: A dict mapping file names to file contents.
183+ Each of these files with their contents will be written to the
184+ branch. Currently only supports writing files at the root
185+ directory of the branch.
186+ """
187+ branch_path = self.useFixture(TempDir()).path
188+ for name, contents in content_map.items():
189+ with open(os.path.join(branch_path, name), 'wb') as f:
190+ f.write(contents)
191+ return branch_path
192+
193+ def make_bzr_branch(self, branch_path):
194+ """Make a bzr branch from an existing directory."""
195+ bzr_home = self.useFixture(TempDir()).path
196+ self.useFixture(EnvironmentVariable("BZR_HOME", bzr_home))
197+ self.useFixture(EnvironmentVariable("BZR_EMAIL"))
198+ self.useFixture(EnvironmentVariable("EMAIL"))
199+
200+ subprocess.check_call(["bzr", "init", "-q"], cwd=branch_path)
201+ subprocess.check_call(["bzr", "add", "-q"], cwd=branch_path)
202+ committer_id = "Committer <committer@example.com>"
203+ with EnvironmentVariable("BZR_EMAIL", committer_id):
204+ subprocess.check_call(
205+ ["bzr", "commit", "-q", "-m", "Populating branch."],
206+ cwd=branch_path)
207+
208+ def make_git_branch(self, branch_path):
209+ subprocess.check_call(["git", "init", "-q"], cwd=branch_path)
210+ subprocess.check_call(
211+ ["git", "config", "user.name", "Committer"], cwd=branch_path)
212+ subprocess.check_call(
213+ ["git", "config", "user.email", "committer@example.com"],
214+ cwd=branch_path)
215+ subprocess.check_call(["git", "add", "."], cwd=branch_path)
216+ subprocess.check_call(
217+ ["git", "commit", "-q", "--allow-empty",
218+ "-m", "Populating branch"],
219+ cwd=branch_path)
220+
221+ def test_install_bzr(self):
222 args = [
223 "generate-translation-templates",
224 "--backend=fake", "--series=xenial", "--arch=amd64", "1",
225- "lp://~my/translation/branch", self.result_name,
226+ "--branch", "lp:foo", self.result_name,
227 ]
228 generator = parse_args(args=args).operation
229- generator._checkout = FakeMethod()
230- generator._getBranch()
231-
232- self.assertEqual(1, generator._checkout.call_count)
233- self.assertThat(generator.branch_dir, EndsWith("source-tree"))
234+ generator.install()
235+ self.assertThat(generator.backend.run.calls, MatchesListwise([
236+ RanAptGet("install", "intltool", "bzr"),
237+ ]))
238
239- def test_getBranch_dir(self):
240- # If passed a branch directory, the template generation script
241- # works directly in that directory.
242- branch_dir = "/home/me/branch"
243+ def test_install_git(self):
244 args = [
245 "generate-translation-templates",
246 "--backend=fake", "--series=xenial", "--arch=amd64", "1",
247- branch_dir, self.result_name,
248+ "--git-repository", "lp:foo", self.result_name,
249 ]
250 generator = parse_args(args=args).operation
251- generator._checkout = FakeMethod()
252- generator._getBranch()
253-
254- self.assertEqual(0, generator._checkout.call_count)
255- self.assertEqual(branch_dir, generator.branch_dir)
256+ generator.install()
257+ self.assertThat(generator.backend.run.calls, MatchesListwise([
258+ RanAptGet("install", "intltool", "git"),
259+ ]))
260
261- def _createBranch(self, content_map=None):
262- """Create a working branch.
263+ def test_fetch_bzr(self):
264+ # fetch can retrieve branch contents from a Bazaar branch.
265+ marker_text = "Ceci n'est pas cet branch."
266+ branch_path = self.make_branch_contents({'marker.txt': marker_text})
267+ self.make_bzr_branch(branch_path)
268
269- :param content_map: optional dict mapping file names to file
270- contents. Each of these files with their contents will be
271- written to the branch. Currently only supports writing files at
272- the root directory of the branch.
273+ args = [
274+ "generate-translation-templates",
275+ "--backend=uncontained", "--series=xenial", "--arch=amd64", "1",
276+ "--branch", branch_path, self.result_name,
277+ ]
278+ generator = parse_args(args=args).operation
279+ generator.fetch(quiet=True)
280
281- :return: the URL of a fresh bzr branch.
282- """
283- branch_path = self.useFixture(TempDir()).path
284- branch_url = 'file://' + branch_path
285- subprocess.check_call(['bzr', 'init', '-q'], cwd=branch_path)
286+ marker_path = os.path.join(generator.branch_dir, 'marker.txt')
287+ with open(marker_path) as marker_file:
288+ self.assertEqual(marker_text, marker_file.read())
289
290- if content_map is not None:
291- for name, contents in content_map.items():
292- with open(os.path.join(branch_path, name), 'wb') as f:
293- f.write(contents)
294- subprocess.check_call(
295- ['bzr', 'add', '-q'] + list(content_map), cwd=branch_path)
296- committer_id = 'Committer <committer@example.com>'
297- with EnvironmentVariable('BZR_EMAIL', committer_id):
298- subprocess.check_call(
299- ['bzr', 'commit', '-q', '-m', 'Populating branch.'],
300- cwd=branch_path)
301+ def test_fetch_git(self):
302+ # fetch can retrieve branch contents from a Git repository.
303+ marker_text = b"Ceci n'est pas cet branch."
304+ branch_path = self.make_branch_contents({'marker.txt': marker_text})
305+ self.make_git_branch(branch_path)
306
307- return branch_url
308+ args = [
309+ "generate-translation-templates",
310+ "--backend=uncontained", "--series=xenial", "--arch=amd64", "1",
311+ "--git-repository", branch_path, self.result_name,
312+ ]
313+ generator = parse_args(args=args).operation
314+ generator.fetch(quiet=True)
315
316- def test_getBranch_bzr(self):
317- # _getBranch can retrieve branch contents from a branch URL.
318- bzr_home = self.useFixture(TempDir()).path
319- self.useFixture(EnvironmentVariable('BZR_HOME', bzr_home))
320- self.useFixture(EnvironmentVariable('BZR_EMAIL'))
321- self.useFixture(EnvironmentVariable('EMAIL'))
322+ marker_path = os.path.join(generator.branch_dir, 'marker.txt')
323+ with open(marker_path, "rb") as marker_file:
324+ self.assertEqual(marker_text, marker_file.read())
325
326+ def test_fetch_git_with_path(self):
327+ # fetch can retrieve branch contents from a Git repository and
328+ # branch name.
329 marker_text = b"Ceci n'est pas cet branch."
330- branch_url = self._createBranch({'marker.txt': marker_text})
331+ branch_path = self.make_branch_contents({'marker.txt': marker_text})
332+ self.make_git_branch(branch_path)
333+ subprocess.call(
334+ ["git", "branch", "-m", "master", "next"], cwd=branch_path)
335
336 args = [
337 "generate-translation-templates",
338 "--backend=uncontained", "--series=xenial", "--arch=amd64", "1",
339- branch_url, self.result_name,
340+ "--git-repository", branch_path, "--git-path", "next",
341+ self.result_name,
342 ]
343 generator = parse_args(args=args).operation
344- generator._getBranch()
345+ generator.fetch(quiet=True)
346
347 marker_path = os.path.join(generator.branch_dir, 'marker.txt')
348 with open(marker_path, "rb") as marker_file:
349@@ -136,22 +193,23 @@ class TestGenerateTranslationTemplates(TestCase):
350 potnames = [
351 member.name
352 for member in tar.getmembers() if not member.isdir()]
353+ self.make_bzr_branch(branchdir)
354
355 args = [
356 "generate-translation-templates",
357 "--backend=uncontained", "--series=xenial", "--arch=amd64", "1",
358- branchdir, self.result_name,
359+ "--branch", branchdir, self.result_name,
360 ]
361 generator = parse_args(args=args).operation
362- generator._getBranch()
363+ generator.fetch(quiet=True)
364 generator._makeTarball(potnames)
365 result_path = os.path.join(self.home_dir, self.result_name)
366 with tarfile.open(result_path, 'r|*') as tar:
367 tarnames = tar.getnames()
368 self.assertThat(tarnames, MatchesSetwise(*(map(Equals, potnames))))
369
370- def test_run(self):
371- # Install dependencies and generate a templates tarball.
372+ def test_run_bzr(self):
373+ # Install dependencies and generate a templates tarball from Bazaar.
374 branch_url = "lp:~my/branch"
375 branch_dir = os.path.join(self.home_dir, "source-tree")
376 po_dir = os.path.join(branch_dir, "po")
377@@ -160,7 +218,40 @@ class TestGenerateTranslationTemplates(TestCase):
378 args = [
379 "generate-translation-templates",
380 "--backend=fake", "--series=xenial", "--arch=amd64", "1",
381- branch_url, self.result_name,
382+ "--branch", branch_url, self.result_name,
383+ ]
384+ generator = parse_args(args=args).operation
385+ generator.backend.add_file(os.path.join(po_dir, "POTFILES.in"), "")
386+ generator.backend.add_file(
387+ os.path.join(po_dir, "Makevars"), "DOMAIN = test\n")
388+ generator.run()
389+ self.assertThat(generator.backend.run.calls, MatchesListwise([
390+ RanAptGet("install", "intltool", "bzr"),
391+ RanCommand(
392+ ["bzr", "branch", "lp:~my/branch", "source-tree"],
393+ cwd=self.home_dir, LANG="C.UTF-8", SHELL="/bin/sh"),
394+ RanCommand(
395+ ["rm", "-f",
396+ os.path.join(po_dir, "missing"),
397+ os.path.join(po_dir, "notexist")]),
398+ RanCommand(["/usr/bin/intltool-update", "-m"], cwd=po_dir),
399+ RanCommand(
400+ ["/usr/bin/intltool-update", "-p", "-g", "test"], cwd=po_dir),
401+ RanCommand(
402+ ["tar", "-C", branch_dir, "-czf", result_path, "po/test.pot"]),
403+ ]))
404+
405+ def test_run_git(self):
406+ # Install dependencies and generate a templates tarball from Git.
407+ repository_url = "lp:~my/repository"
408+ branch_dir = os.path.join(self.home_dir, "source-tree")
409+ po_dir = os.path.join(branch_dir, "po")
410+ result_path = os.path.join(self.home_dir, self.result_name)
411+
412+ args = [
413+ "generate-translation-templates",
414+ "--backend=fake", "--series=xenial", "--arch=amd64", "1",
415+ "--git-repository", repository_url, self.result_name,
416 ]
417 generator = parse_args(args=args).operation
418 generator.backend.add_file(os.path.join(po_dir, "POTFILES.in"), b"")
419@@ -168,16 +259,20 @@ class TestGenerateTranslationTemplates(TestCase):
420 os.path.join(po_dir, "Makevars"), b"DOMAIN = test\n")
421 generator.run()
422 self.assertThat(generator.backend.run.calls, MatchesListwise([
423- MatchesCall(["apt-get", "-y", "install", "bzr", "intltool"]),
424- MatchesCall(
425- ["bzr", "export", "-q", "-d", "lp:~my/branch", branch_dir]),
426- MatchesCall(
427+ RanAptGet("install", "intltool", "git"),
428+ RanCommand(
429+ ["git", "clone", "lp:~my/repository", "source-tree"],
430+ cwd=self.home_dir, LANG="C.UTF-8", SHELL="/bin/sh"),
431+ RanCommand(
432+ ["git", "submodule", "update", "--init", "--recursive"],
433+ cwd=branch_dir, LANG="C.UTF-8", SHELL="/bin/sh"),
434+ RanCommand(
435 ["rm", "-f",
436 os.path.join(po_dir, "missing"),
437 os.path.join(po_dir, "notexist")]),
438- MatchesCall(["/usr/bin/intltool-update", "-m"], cwd=po_dir),
439- MatchesCall(
440+ RanCommand(["/usr/bin/intltool-update", "-m"], cwd=po_dir),
441+ RanCommand(
442 ["/usr/bin/intltool-update", "-p", "-g", "test"], cwd=po_dir),
443- MatchesCall(
444+ RanCommand(
445 ["tar", "-C", branch_dir, "-czf", result_path, "po/test.pot"]),
446 ]))
447diff --git a/lpbuildd/tests/test_translationtemplatesbuildmanager.py b/lpbuildd/tests/test_translationtemplatesbuildmanager.py
448index a47e5f8..196f4d8 100644
449--- a/lpbuildd/tests/test_translationtemplatesbuildmanager.py
450+++ b/lpbuildd/tests/test_translationtemplatesbuildmanager.py
451@@ -88,7 +88,7 @@ class TestTranslationTemplatesBuildManagerIteration(TestCase):
452 'generate-translation-templates',
453 '--backend=chroot', '--series=xenial', '--arch=i386',
454 self.buildid,
455- url, 'resultarchive',
456+ '--branch', url, 'resultarchive',
457 ]
458 self.assertEqual(expected_command, self.buildmanager.commands[-1])
459 self.assertEqual(
460diff --git a/lpbuildd/translationtemplates.py b/lpbuildd/translationtemplates.py
461index 2e778c5..36f4335 100644
462--- a/lpbuildd/translationtemplates.py
463+++ b/lpbuildd/translationtemplates.py
464@@ -37,16 +37,28 @@ class TranslationTemplatesBuildManager(DebianBuildManager):
465
466 def initiate(self, files, chroot, extra_args):
467 """See `BuildManager`."""
468- self._branch_url = extra_args['branch_url']
469+ self.branch = extra_args.get('branch')
470+ # XXX cjwatson 2017-11-10: Backward-compatibility; remove once the
471+ # manager passes branch instead.
472+ if self.branch is None:
473+ self.branch = extra_args['branch_url']
474+ self.git_repository = extra_args.get("git_repository")
475+ self.git_path = extra_args.get("git_path")
476
477 super(TranslationTemplatesBuildManager, self).initiate(
478 files, chroot, extra_args)
479
480 def doGenerate(self):
481 """Generate templates."""
482- self.runTargetSubProcess(
483- "generate-translation-templates",
484- self._branch_url, self._resultname)
485+ args = []
486+ if self.branch is not None:
487+ args.extend(["--branch", self.branch])
488+ if self.git_repository is not None:
489+ args.extend(["--git-repository", self.git_repository])
490+ if self.git_path is not None:
491+ args.extend(["--git-path", self.git_path])
492+ args.append(self._resultname)
493+ self.runTargetSubProcess("generate-translation-templates", *args)
494
495 # Satisfy DebianPackageManager's needs without having a misleading
496 # method name here.

Subscribers

People subscribed via source and target branches