← Back to team overview

launchpad-reviewers team mailing list archive

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

 

Colin Watson has proposed merging ~cjwatson/launchpad-buildd:ttb-git into launchpad-buildd:master.

Commit message:
Convert translation templates builds to the VCS mixin

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad-buildd/+git/launchpad-buildd/+merge/383063

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.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad-buildd:ttb-git into launchpad-buildd:master.
diff --git a/debian/changelog b/debian/changelog
index e549316..4b68c92 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,6 +1,8 @@
 launchpad-buildd (190) UNRELEASED; urgency=medium
 
   * Switch to git; add Vcs-* fields.
+  * Convert translation templates builds to the VCS mixin, thereby adding
+    git support.
 
  -- Colin Watson <cjwatson@xxxxxxxxxx>  Tue, 28 Apr 2020 10:19:27 +0100
 
diff --git a/lpbuildd/target/generate_translation_templates.py b/lpbuildd/target/generate_translation_templates.py
index 3e44af7..1e708ba 100644
--- a/lpbuildd/target/generate_translation_templates.py
+++ b/lpbuildd/target/generate_translation_templates.py
@@ -10,6 +10,7 @@ import os.path
 
 from lpbuildd.pottery import intltool
 from lpbuildd.target.operation import Operation
+from lpbuildd.target.vcs import VCSOperationMixin
 
 
 logger = logging.getLogger(__name__)
@@ -19,7 +20,7 @@ RETCODE_FAILURE_INSTALL = 200
 RETCODE_FAILURE_BUILD = 201
 
 
-class GenerateTranslationTemplates(Operation):
+class GenerateTranslationTemplates(VCSOperationMixin, Operation):
     """Script to generate translation templates from a branch."""
 
     description = "Generate templates for a branch."
@@ -28,42 +29,24 @@ class GenerateTranslationTemplates(Operation):
     def add_arguments(cls, parser):
         super(GenerateTranslationTemplates, cls).add_arguments(parser)
         parser.add_argument(
-            "branch_spec", help=(
-                "A branch URL or the path of a local branch.  URLs are "
-                "recognised by the occurrence of ':'.  In the case of a URL, "
-                "this will make up a path for the branch and check out the "
-                "branch to there."))
-        parser.add_argument(
             "result_name",
-            help="The name of the result tarball.  Should end in '.tar.gz'.")
+            help="the name of the result tarball; should end in '.tar.gz'")
 
     def __init__(self, args, parser):
         super(GenerateTranslationTemplates, self).__init__(args, parser)
         self.work_dir = os.environ["HOME"]
+        self.branch_dir = os.path.join(self.work_dir, "source-tree")
+
+    def install(self):
+        logger.info("Installing dependencies...")
+        deps = ["intltool"]
+        deps.extend(self.vcs_deps)
+        self.backend.run(["apt-get", "-y", "install"] + deps)
 
-    def _getBranch(self):
-        """Set `self.branch_dir`, and check out branch if needed."""
-        if ':' in self.args.branch_spec:
-            # This is a branch URL.  Check out the branch.
-            self.branch_dir = os.path.join(self.work_dir, 'source-tree')
-            logger.info("Getting remote branch %s..." % self.args.branch_spec)
-            self._checkout(self.args.branch_spec)
-        else:
-            # This is a local filesystem path.  Use the branch in-place.
-            logger.info("Using local branch %s..." % self.args.branch_spec)
-            self.branch_dir = self.args.branch_spec
-
-    def _checkout(self, branch_url):
-        """Check out a source branch to generate from.
-
-        The branch is checked out to the location specified by
-        `self.branch_dir`.
-        """
-        logger.info(
-            "Exporting branch %s to %s..." % (branch_url, self.branch_dir))
-        self.backend.run(
-            ["bzr", "export", "-q", "-d", branch_url, self.branch_dir])
-        logger.info("Exporting branch done.")
+    def fetch(self, quiet=False):
+        logger.info("Fetching %s...", self.vcs_description)
+        self.vcs_fetch(
+            os.path.basename(self.branch_dir), cwd=self.work_dir, quiet=quiet)
 
     def _makeTarball(self, files):
         """Put the given files into a tarball in the working directory."""
@@ -78,21 +61,23 @@ class GenerateTranslationTemplates(Operation):
         self.backend.run(cmd)
         logger.info("Tarball generated.")
 
+    def generate(self):
+        logger.info("Generating templates...")
+        pots = intltool.generate_pots(self.backend, self.branch_dir)
+        logger.info("Generated %d templates." % len(pots))
+        if len(pots) > 0:
+            self._makeTarball(pots)
+
     def run(self):
         """Do It.  Generate templates."""
         try:
-            self.backend.run(["apt-get", "-y", "install", "bzr", "intltool"])
+            self.install()
         except Exception:
             logger.exception("Install failed")
             return RETCODE_FAILURE_INSTALL
         try:
-            logger.info(
-                "Generating templates for %s." % self.args.branch_spec)
-            self._getBranch()
-            pots = intltool.generate_pots(self.backend, self.branch_dir)
-            logger.info("Generated %d templates." % len(pots))
-            if len(pots) > 0:
-                self._makeTarball(pots)
+            self.fetch()
+            self.generate()
         except Exception:
             logger.exception("Build failed")
             return RETCODE_FAILURE_BUILD
diff --git a/lpbuildd/target/tests/test_generate_translation_templates.py b/lpbuildd/target/tests/test_generate_translation_templates.py
index da7f196..ebf0b90 100644
--- a/lpbuildd/target/tests/test_generate_translation_templates.py
+++ b/lpbuildd/target/tests/test_generate_translation_templates.py
@@ -15,23 +15,37 @@ from fixtures import (
 from testtools import TestCase
 from testtools.matchers import (
     ContainsDict,
-    EndsWith,
     Equals,
+    Is,
+    MatchesDict,
     MatchesListwise,
     MatchesSetwise,
     )
 
 from lpbuildd.target.cli import parse_args
-from lpbuildd.tests.fakebuilder import FakeMethod
 
 
-class MatchesCall(MatchesListwise):
+class RanCommand(MatchesListwise):
 
-    def __init__(self, *args, **kwargs):
-        super(MatchesCall, self).__init__([
-            Equals(args),
-            ContainsDict(
-                {name: Equals(value) for name, value in kwargs.items()})])
+    def __init__(self, args, get_output=None, echo=None, cwd=None, **env):
+        kwargs_matcher = {}
+        if get_output is not None:
+            kwargs_matcher["get_output"] = Is(get_output)
+        if echo is not None:
+            kwargs_matcher["echo"] = Is(echo)
+        if cwd:
+            kwargs_matcher["cwd"] = Equals(cwd)
+        if env:
+            kwargs_matcher["env"] = MatchesDict(
+                {key: Equals(value) for key, value in env.items()})
+        super(RanCommand, self).__init__(
+            [Equals((args,)), ContainsDict(kwargs_matcher)])
+
+
+class RanAptGet(RanCommand):
+
+    def __init__(self, *args):
+        super(RanAptGet, self).__init__(["apt-get", "-y"] + list(args))
 
 
 class TestGenerateTranslationTemplates(TestCase):
@@ -45,82 +59,125 @@ class TestGenerateTranslationTemplates(TestCase):
         self.useFixture(EnvironmentVariable("HOME", self.home_dir))
         self.logger = self.useFixture(FakeLogger())
 
-    def test_getBranch_url(self):
-        # If passed a branch URL, the template generation script will
-        # check out that branch into a directory called "source-tree."
+    def make_branch_contents(self, content_map):
+        """Create a directory with the contents of a working branch.
+
+        :param content_map: A dict mapping file names to file contents.
+            Each of these files with their contents will be written to the
+            branch.  Currently only supports writing files at the root
+            directory of the branch.
+        """
+        branch_path = self.useFixture(TempDir()).path
+        for name, contents in content_map.items():
+            with open(os.path.join(branch_path, name), 'wb') as f:
+                f.write(contents)
+        return branch_path
+
+    def make_bzr_branch(self, branch_path):
+        """Make a bzr branch from an existing directory."""
+        bzr_home = self.useFixture(TempDir()).path
+        self.useFixture(EnvironmentVariable("BZR_HOME", bzr_home))
+        self.useFixture(EnvironmentVariable("BZR_EMAIL"))
+        self.useFixture(EnvironmentVariable("EMAIL"))
+
+        subprocess.check_call(["bzr", "init", "-q"], cwd=branch_path)
+        subprocess.check_call(["bzr", "add", "-q"], cwd=branch_path)
+        committer_id = "Committer <committer@xxxxxxxxxxx>"
+        with EnvironmentVariable("BZR_EMAIL", committer_id):
+            subprocess.check_call(
+                ["bzr", "commit", "-q", "-m", "Populating branch."],
+                cwd=branch_path)
+
+    def make_git_branch(self, branch_path):
+        subprocess.check_call(["git", "init", "-q"], cwd=branch_path)
+        subprocess.check_call(
+            ["git", "config", "user.name", "Committer"], cwd=branch_path)
+        subprocess.check_call(
+            ["git", "config", "user.email", "committer@xxxxxxxxxxx"],
+            cwd=branch_path)
+        subprocess.check_call(["git", "add", "."], cwd=branch_path)
+        subprocess.check_call(
+            ["git", "commit", "-q", "--allow-empty",
+             "-m", "Populating branch"],
+            cwd=branch_path)
+
+    def test_install_bzr(self):
         args = [
             "generate-translation-templates",
             "--backend=fake", "--series=xenial", "--arch=amd64", "1",
-            "lp://~my/translation/branch", self.result_name,
+            "--branch", "lp:foo", self.result_name,
             ]
         generator = parse_args(args=args).operation
-        generator._checkout = FakeMethod()
-        generator._getBranch()
-
-        self.assertEqual(1, generator._checkout.call_count)
-        self.assertThat(generator.branch_dir, EndsWith("source-tree"))
+        generator.install()
+        self.assertThat(generator.backend.run.calls, MatchesListwise([
+            RanAptGet("install", "intltool", "bzr"),
+            ]))
 
-    def test_getBranch_dir(self):
-        # If passed a branch directory, the template generation script
-        # works directly in that directory.
-        branch_dir = "/home/me/branch"
+    def test_install_git(self):
         args = [
             "generate-translation-templates",
             "--backend=fake", "--series=xenial", "--arch=amd64", "1",
-            branch_dir, self.result_name,
+            "--git-repository", "lp:foo", self.result_name,
             ]
         generator = parse_args(args=args).operation
-        generator._checkout = FakeMethod()
-        generator._getBranch()
-
-        self.assertEqual(0, generator._checkout.call_count)
-        self.assertEqual(branch_dir, generator.branch_dir)
+        generator.install()
+        self.assertThat(generator.backend.run.calls, MatchesListwise([
+            RanAptGet("install", "intltool", "git"),
+            ]))
 
-    def _createBranch(self, content_map=None):
-        """Create a working branch.
+    def test_fetch_bzr(self):
+        # fetch can retrieve branch contents from a Bazaar branch.
+        marker_text = "Ceci n'est pas cet branch."
+        branch_path = self.make_branch_contents({'marker.txt': marker_text})
+        self.make_bzr_branch(branch_path)
 
-        :param content_map: optional dict mapping file names to file
-            contents.  Each of these files with their contents will be
-            written to the branch.  Currently only supports writing files at
-            the root directory of the branch.
+        args = [
+            "generate-translation-templates",
+            "--backend=uncontained", "--series=xenial", "--arch=amd64", "1",
+            "--branch", branch_path, self.result_name,
+            ]
+        generator = parse_args(args=args).operation
+        generator.fetch(quiet=True)
 
-        :return: the URL of a fresh bzr branch.
-        """
-        branch_path = self.useFixture(TempDir()).path
-        branch_url = 'file://' + branch_path
-        subprocess.check_call(['bzr', 'init', '-q'], cwd=branch_path)
+        marker_path = os.path.join(generator.branch_dir, 'marker.txt')
+        with open(marker_path) as marker_file:
+            self.assertEqual(marker_text, marker_file.read())
 
-        if content_map is not None:
-            for name, contents in content_map.items():
-                with open(os.path.join(branch_path, name), 'wb') as f:
-                    f.write(contents)
-            subprocess.check_call(
-                ['bzr', 'add', '-q'] + list(content_map), cwd=branch_path)
-            committer_id = 'Committer <committer@xxxxxxxxxxx>'
-            with EnvironmentVariable('BZR_EMAIL', committer_id):
-                subprocess.check_call(
-                    ['bzr', 'commit', '-q', '-m', 'Populating branch.'],
-                    cwd=branch_path)
+    def test_fetch_git(self):
+        # fetch can retrieve branch contents from a Git repository.
+        marker_text = "Ceci n'est pas cet branch."
+        branch_path = self.make_branch_contents({'marker.txt': marker_text})
+        self.make_git_branch(branch_path)
 
-        return branch_url
+        args = [
+            "generate-translation-templates",
+            "--backend=uncontained", "--series=xenial", "--arch=amd64", "1",
+            "--git-repository", branch_path, self.result_name,
+            ]
+        generator = parse_args(args=args).operation
+        generator.fetch(quiet=True)
 
-    def test_getBranch_bzr(self):
-        # _getBranch can retrieve branch contents from a branch URL.
-        bzr_home = self.useFixture(TempDir()).path
-        self.useFixture(EnvironmentVariable('BZR_HOME', bzr_home))
-        self.useFixture(EnvironmentVariable('BZR_EMAIL'))
-        self.useFixture(EnvironmentVariable('EMAIL'))
+        marker_path = os.path.join(generator.branch_dir, 'marker.txt')
+        with open(marker_path) as marker_file:
+            self.assertEqual(marker_text, marker_file.read())
 
+    def test_fetch_git_with_path(self):
+        # fetch can retrieve branch contents from a Git repository and
+        # branch name.
         marker_text = "Ceci n'est pas cet branch."
-        branch_url = self._createBranch({'marker.txt': marker_text})
+        branch_path = self.make_branch_contents({'marker.txt': marker_text})
+        self.make_git_branch(branch_path)
+        subprocess.call(
+            ["git", "branch", "-m", "master", "next"], cwd=branch_path)
 
         args = [
             "generate-translation-templates",
             "--backend=uncontained", "--series=xenial", "--arch=amd64", "1",
-            branch_url, self.result_name,
+            "--git-repository", branch_path, "--git-path", "next",
+            self.result_name,
             ]
         generator = parse_args(args=args).operation
-        generator._getBranch()
+        generator.fetch(quiet=True)
 
         marker_path = os.path.join(generator.branch_dir, 'marker.txt')
         with open(marker_path) as marker_file:
@@ -136,22 +193,23 @@ class TestGenerateTranslationTemplates(TestCase):
             potnames = [
                 member.name
                 for member in tar.getmembers() if not member.isdir()]
+        self.make_bzr_branch(branchdir)
 
         args = [
             "generate-translation-templates",
             "--backend=uncontained", "--series=xenial", "--arch=amd64", "1",
-            branchdir, self.result_name,
+            "--branch", branchdir, self.result_name,
             ]
         generator = parse_args(args=args).operation
-        generator._getBranch()
+        generator.fetch(quiet=True)
         generator._makeTarball(potnames)
         result_path = os.path.join(self.home_dir, self.result_name)
         with tarfile.open(result_path, 'r|*') as tar:
             tarnames = tar.getnames()
         self.assertThat(tarnames, MatchesSetwise(*(map(Equals, potnames))))
 
-    def test_run(self):
-        # Install dependencies and generate a templates tarball.
+    def test_run_bzr(self):
+        # Install dependencies and generate a templates tarball from Bazaar.
         branch_url = "lp:~my/branch"
         branch_dir = os.path.join(self.home_dir, "source-tree")
         po_dir = os.path.join(branch_dir, "po")
@@ -160,7 +218,40 @@ class TestGenerateTranslationTemplates(TestCase):
         args = [
             "generate-translation-templates",
             "--backend=fake", "--series=xenial", "--arch=amd64", "1",
-            branch_url, self.result_name,
+            "--branch", branch_url, self.result_name,
+            ]
+        generator = parse_args(args=args).operation
+        generator.backend.add_file(os.path.join(po_dir, "POTFILES.in"), "")
+        generator.backend.add_file(
+            os.path.join(po_dir, "Makevars"), "DOMAIN = test\n")
+        generator.run()
+        self.assertThat(generator.backend.run.calls, MatchesListwise([
+            RanAptGet("install", "intltool", "bzr"),
+            RanCommand(
+                ["bzr", "branch", "lp:~my/branch", "source-tree"],
+                cwd=self.home_dir, LANG="C.UTF-8", SHELL="/bin/sh"),
+            RanCommand(
+                ["rm", "-f",
+                 os.path.join(po_dir, "missing"),
+                 os.path.join(po_dir, "notexist")]),
+            RanCommand(["/usr/bin/intltool-update", "-m"], cwd=po_dir),
+            RanCommand(
+                ["/usr/bin/intltool-update", "-p", "-g", "test"], cwd=po_dir),
+            RanCommand(
+                ["tar", "-C", branch_dir, "-czf", result_path, "po/test.pot"]),
+            ]))
+
+    def test_run_git(self):
+        # Install dependencies and generate a templates tarball from Git.
+        repository_url = "lp:~my/repository"
+        branch_dir = os.path.join(self.home_dir, "source-tree")
+        po_dir = os.path.join(branch_dir, "po")
+        result_path = os.path.join(self.home_dir, self.result_name)
+
+        args = [
+            "generate-translation-templates",
+            "--backend=fake", "--series=xenial", "--arch=amd64", "1",
+            "--git-repository", repository_url, self.result_name,
             ]
         generator = parse_args(args=args).operation
         generator.backend.add_file(os.path.join(po_dir, "POTFILES.in"), "")
@@ -168,16 +259,20 @@ class TestGenerateTranslationTemplates(TestCase):
             os.path.join(po_dir, "Makevars"), "DOMAIN = test\n")
         generator.run()
         self.assertThat(generator.backend.run.calls, MatchesListwise([
-            MatchesCall(["apt-get", "-y", "install", "bzr", "intltool"]),
-            MatchesCall(
-                ["bzr", "export", "-q", "-d", "lp:~my/branch", branch_dir]),
-            MatchesCall(
+            RanAptGet("install", "intltool", "git"),
+            RanCommand(
+                ["git", "clone", "lp:~my/repository", "source-tree"],
+                cwd=self.home_dir, LANG="C.UTF-8", SHELL="/bin/sh"),
+            RanCommand(
+                ["git", "submodule", "update", "--init", "--recursive"],
+                cwd=branch_dir, LANG="C.UTF-8", SHELL="/bin/sh"),
+            RanCommand(
                 ["rm", "-f",
                  os.path.join(po_dir, "missing"),
                  os.path.join(po_dir, "notexist")]),
-            MatchesCall(["/usr/bin/intltool-update", "-m"], cwd=po_dir),
-            MatchesCall(
+            RanCommand(["/usr/bin/intltool-update", "-m"], cwd=po_dir),
+            RanCommand(
                 ["/usr/bin/intltool-update", "-p", "-g", "test"], cwd=po_dir),
-            MatchesCall(
+            RanCommand(
                 ["tar", "-C", branch_dir, "-czf", result_path, "po/test.pot"]),
             ]))
diff --git a/lpbuildd/tests/test_translationtemplatesbuildmanager.py b/lpbuildd/tests/test_translationtemplatesbuildmanager.py
index a47e5f8..196f4d8 100644
--- a/lpbuildd/tests/test_translationtemplatesbuildmanager.py
+++ b/lpbuildd/tests/test_translationtemplatesbuildmanager.py
@@ -88,7 +88,7 @@ class TestTranslationTemplatesBuildManagerIteration(TestCase):
             'generate-translation-templates',
             '--backend=chroot', '--series=xenial', '--arch=i386',
             self.buildid,
-            url, 'resultarchive',
+            '--branch', url, 'resultarchive',
             ]
         self.assertEqual(expected_command, self.buildmanager.commands[-1])
         self.assertEqual(
diff --git a/lpbuildd/translationtemplates.py b/lpbuildd/translationtemplates.py
index 2e778c5..36f4335 100644
--- a/lpbuildd/translationtemplates.py
+++ b/lpbuildd/translationtemplates.py
@@ -37,16 +37,28 @@ class TranslationTemplatesBuildManager(DebianBuildManager):
 
     def initiate(self, files, chroot, extra_args):
         """See `BuildManager`."""
-        self._branch_url = extra_args['branch_url']
+        self.branch = extra_args.get('branch')
+        # XXX cjwatson 2017-11-10: Backward-compatibility; remove once the
+        # manager passes branch instead.
+        if self.branch is None:
+            self.branch = extra_args['branch_url']
+        self.git_repository = extra_args.get("git_repository")
+        self.git_path = extra_args.get("git_path")
 
         super(TranslationTemplatesBuildManager, self).initiate(
             files, chroot, extra_args)
 
     def doGenerate(self):
         """Generate templates."""
-        self.runTargetSubProcess(
-            "generate-translation-templates",
-            self._branch_url, self._resultname)
+        args = []
+        if self.branch is not None:
+            args.extend(["--branch", self.branch])
+        if self.git_repository is not None:
+            args.extend(["--git-repository", self.git_repository])
+        if self.git_path is not None:
+            args.extend(["--git-path", self.git_path])
+        args.append(self._resultname)
+        self.runTargetSubProcess("generate-translation-templates", *args)
 
     # Satisfy DebianPackageManager's needs without having a misleading
     # method name here.