check PV of virtual ebuilds

We want people to follow the guidelines for the right PV value to use
when creating virtuals that boards will be able to override.

BUG=chromium:340036
TEST=`./pre-upload_unittest.py` passes
TEST=uploaded a few CLs with bad PVs & good PVs and checked behavior

Change-Id: Ie514a6b316465939498593c22fb7ca5f74972f01
Reviewed-on: https://chromium-review.googlesource.com/184623
Reviewed-by: David James <davidjames@chromium.org>
Tested-by: Mike Frysinger <vapier@chromium.org>
diff --git a/pre-upload.py b/pre-upload.py
index 0cc52f1..6f38310 100755
--- a/pre-upload.py
+++ b/pre-upload.py
@@ -617,6 +617,64 @@
         return HookFailure(e.message, [ebuild])
 
 
+def _check_ebuild_virtual_pv(project, commit):
+  """Enforce the virtual PV policies."""
+  # If this is the portage-stable overlay, then ignore the check.
+  # We want to import virtuals as-is from upstream Gentoo.
+  whitelist = (
+      'chromiumos/overlays/portage-stable',
+  )
+  if project in whitelist:
+    return None
+
+  # We assume the repo name is the same as the dir name on disk.
+  # It would be dumb to not have them match though.
+  project = os.path.basename(project)
+
+  is_variant = lambda x: x.startswith('overlay-variant-')
+  is_board = lambda x: x.startswith('overlay-')
+  is_private = lambda x: x.endswith('-private')
+
+  get_pv = re.compile(r'(.*?)virtual/([^/]+)/\2-([^/]*)\.ebuild$')
+
+  ebuilds_re = [r'\.ebuild$']
+  ebuilds = _filter_files(_get_affected_files(commit, relative=True),
+                          ebuilds_re)
+  bad_ebuilds = []
+
+  for ebuild in ebuilds:
+    m = get_pv.match(ebuild)
+    if m:
+      overlay = m.group(1)
+      if not overlay or not is_board(overlay):
+        overlay = project
+
+      pv = m.group(3).split('-', 1)[0]
+
+      if is_private(overlay):
+        want_pv = '3.5' if is_variant(overlay) else '3'
+      elif is_board(overlay):
+        want_pv = '2.5' if is_variant(overlay) else '2'
+      else:
+        want_pv = '1'
+
+      if pv != want_pv:
+        bad_ebuilds.append((ebuild, pv, want_pv))
+
+  if bad_ebuilds:
+    # pylint: disable=C0301
+    url = 'http://dev.chromium.org/chromium-os/how-tos-and-troubleshooting/portage-build-faq#TOC-Virtuals-and-central-management'
+    # pylint: enable=C0301
+    return HookFailure(
+        'These virtuals have incorrect package versions (PVs). Please adjust:\n'
+        '\t%s\n'
+        'If this is an upstream Gentoo virtual, then you may ignore this\n'
+        'check (and re-run w/--no-verify). Otherwise, please see this\n'
+        'page for more details:\n%s\n' %
+        ('\n\t'.join(['%s:\n\t\tPV is %s but should be %s' % x
+                      for x in bad_ebuilds]), url))
+
+
 def _check_change_has_proper_changeid(_project, commit):
   """Verify that Change-ID is present in last paragraph of commit message."""
   desc = _get_commit_desc(commit)
@@ -779,6 +837,7 @@
     _check_change_has_proper_changeid,
     _check_ebuild_eapi,
     _check_ebuild_licenses,
+    _check_ebuild_virtual_pv,
     _check_no_stray_whitespace,
     _check_no_long_lines,
     _check_license,
diff --git a/pre-upload_unittest.py b/pre-upload_unittest.py
index 57633e6..8120c69 100755
--- a/pre-upload_unittest.py
+++ b/pre-upload_unittest.py
@@ -207,5 +207,80 @@
     self.assertEqual(ret, None)
 
 
+class CheckEbuildVirtualPv(cros_test_lib.MockTestCase):
+  """Tests for _check_ebuild_virtual_pv."""
+
+  PORTAGE_STABLE = 'chromiumos/overlays/portage-stable'
+  CHROMIUMOS_OVERLAY = 'chromiumos/overlays/chromiumos'
+  BOARD_OVERLAY = 'chromiumos/overlays/board-overlays'
+  PRIVATE_OVERLAY = 'chromeos/overlays/overlay-link-private'
+  PRIVATE_VARIANT_OVERLAY = ('chromeos/overlays/'
+                             'overlay-variant-daisy-spring-private')
+
+  def setUp(self):
+    self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
+
+  def testNoVirtuals(self):
+    """Skip non virtual packages."""
+    self.file_mock.return_value = ['some/package/package-3.ebuild']
+    ret = pre_upload._check_ebuild_virtual_pv('overlay', 'H')
+    self.assertEqual(ret, None)
+
+  def testCommonVirtuals(self):
+    """Non-board overlays should use PV=1."""
+    template = 'virtual/foo/foo-%s.ebuild'
+    self.file_mock.return_value = [template % '1']
+    ret = pre_upload._check_ebuild_virtual_pv(self.CHROMIUMOS_OVERLAY, 'H')
+    self.assertEqual(ret, None)
+
+    self.file_mock.return_value = [template % '2']
+    ret = pre_upload._check_ebuild_virtual_pv(self.CHROMIUMOS_OVERLAY, 'H')
+    self.assertTrue(isinstance (ret, errors.HookFailure))
+
+  def testPublicBoardVirtuals(self):
+    """Public board overlays should use PV=2."""
+    template = 'overlay-lumpy/virtual/foo/foo-%s.ebuild'
+    self.file_mock.return_value = [template % '2']
+    ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
+    self.assertEqual(ret, None)
+
+    self.file_mock.return_value = [template % '2.5']
+    ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
+    self.assertTrue(isinstance (ret, errors.HookFailure))
+
+  def testPublicBoardVariantVirtuals(self):
+    """Public board variant overlays should use PV=2.5."""
+    template = 'overlay-variant-lumpy-foo/virtual/foo/foo-%s.ebuild'
+    self.file_mock.return_value = [template % '2.5']
+    ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
+    self.assertEqual(ret, None)
+
+    self.file_mock.return_value = [template % '3']
+    ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
+    self.assertTrue(isinstance (ret, errors.HookFailure))
+
+  def testPrivateBoardVirtuals(self):
+    """Private board overlays should use PV=3."""
+    template = 'virtual/foo/foo-%s.ebuild'
+    self.file_mock.return_value = [template % '3']
+    ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_OVERLAY, 'H')
+    self.assertEqual(ret, None)
+
+    self.file_mock.return_value = [template % '3.5']
+    ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_OVERLAY, 'H')
+    self.assertTrue(isinstance (ret, errors.HookFailure))
+
+  def testPrivateBoardVariantVirtuals(self):
+    """Private board variant overlays should use PV=3.5."""
+    template = 'virtual/foo/foo-%s.ebuild'
+    self.file_mock.return_value = [template % '3.5']
+    ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_VARIANT_OVERLAY, 'H')
+    self.assertEqual(ret, None)
+
+    self.file_mock.return_value = [template % '4']
+    ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_VARIANT_OVERLAY, 'H')
+    self.assertTrue(isinstance (ret, errors.HookFailure))
+
+
 if __name__ == '__main__':
   cros_test_lib.main()