PageRenderTime 27ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/pants-plugins/src/python/internal_backend/utilities/register.py

https://gitlab.com/Ivy001/pants
Python | 227 lines | 196 code | 13 blank | 18 comment | 3 complexity | 55e14ef95684ad8d8618eb47189ceabf MD5 | raw file
  1. # coding=utf-8
  2. # Copyright 2015 Pants project contributors (see CONTRIBUTORS.md).
  3. # Licensed under the Apache License, Version 2.0 (see LICENSE).
  4. from __future__ import (absolute_import, division, generators, nested_scopes, print_function,
  5. unicode_literals, with_statement)
  6. import os
  7. from twitter.common.collections import OrderedSet
  8. from pants.backend.python.python_artifact import PythonArtifact
  9. from pants.backend.python.targets.python_library import PythonLibrary
  10. from pants.base.build_environment import get_buildroot
  11. from pants.base.exceptions import TargetDefinitionException
  12. from pants.build_graph.build_file_aliases import BuildFileAliases
  13. from pants.subsystem.subsystem import Subsystem
  14. from pants.version import PANTS_SEMVER, VERSION
  15. def _read_contents(path):
  16. with open(os.path.join(get_buildroot(), path), 'rb') as fp:
  17. return fp.read()
  18. def pants_setup_py(name, description, additional_classifiers=None, **kwargs):
  19. """Creates the setup_py for a pants artifact.
  20. :param str name: The name of the package.
  21. :param str description: A brief description of what the package provides.
  22. :param list additional_classifiers: Any additional trove classifiers that apply to the package,
  23. see: https://pypi.python.org/pypi?%3Aaction=list_classifiers
  24. :param kwargs: Any additional keyword arguments to be passed to `setuptools.setup
  25. <https://pythonhosted.org/setuptools/setuptools.html>`_.
  26. :returns: A setup_py suitable for building and publishing pants components.
  27. """
  28. if not name.startswith('pantsbuild.pants'):
  29. raise ValueError("Pants distribution package names must start with 'pantsbuild.pants', "
  30. "given {}".format(name))
  31. standard_classifiers = [
  32. 'Intended Audience :: Developers',
  33. 'License :: OSI Approved :: Apache Software License',
  34. # We know for a fact these OSs work but, for example, know Windows
  35. # does not work yet. Take the conservative approach and only list OSs
  36. # we know pants works with for now.
  37. 'Operating System :: MacOS :: MacOS X',
  38. 'Operating System :: POSIX :: Linux',
  39. 'Programming Language :: Python',
  40. 'Topic :: Software Development :: Build Tools']
  41. classifiers = OrderedSet(standard_classifiers + (additional_classifiers or []))
  42. notes = PantsReleases.global_instance().notes_for_version(PANTS_SEMVER)
  43. return PythonArtifact(
  44. name=name,
  45. version=VERSION,
  46. description=description,
  47. long_description=(_read_contents('src/python/pants/ABOUT.rst') + notes),
  48. url='https://github.com/pantsbuild/pants',
  49. license='Apache License, Version 2.0',
  50. zip_safe=True,
  51. classifiers=list(classifiers),
  52. **kwargs)
  53. def contrib_setup_py(name, description, additional_classifiers=None, **kwargs):
  54. """Creates the setup_py for a pants contrib plugin artifact.
  55. :param str name: The name of the package; must start with 'pantsbuild.pants.contrib.'.
  56. :param str description: A brief description of what the plugin provides.
  57. :param list additional_classifiers: Any additional trove classifiers that apply to the plugin,
  58. see: https://pypi.python.org/pypi?%3Aaction=list_classifiers
  59. :param kwargs: Any additional keyword arguments to be passed to `setuptools.setup
  60. <https://pythonhosted.org/setuptools/setuptools.html>`_.
  61. :returns: A setup_py suitable for building and publishing pants components.
  62. """
  63. if not name.startswith('pantsbuild.pants.contrib.'):
  64. raise ValueError("Contrib plugin package names must start with 'pantsbuild.pants.contrib.', "
  65. "given {}".format(name))
  66. return pants_setup_py(name,
  67. description,
  68. additional_classifiers=additional_classifiers,
  69. namespace_packages=['pants', 'pants.contrib'],
  70. **kwargs)
  71. class PantsReleases(Subsystem):
  72. """A subsystem to hold per-pants-release configuration."""
  73. options_scope = 'pants-releases'
  74. @classmethod
  75. def register_options(cls, register):
  76. super(PantsReleases, cls).register_options(register)
  77. register('--branch-notes', type=dict,
  78. help='A dict from branch name to release notes rst-file location.')
  79. @property
  80. def _branch_notes(self):
  81. return self.get_options().branch_notes
  82. @classmethod
  83. def _branch_name(cls, version):
  84. """Defines a mapping between versions and branches.
  85. In particular, `-dev` suffixed releases always live on master. Any other (modern) release
  86. lives in a branch.
  87. """
  88. suffix = version.public[len(version.base_version):]
  89. components = version.base_version.split('.') + [suffix]
  90. if suffix == '' or suffix.startswith('rc'):
  91. # An un-suffixed, or suffixed-with-rc version is a release from a stable branch.
  92. return '{}.{}.x'.format(*components[:2])
  93. elif suffix.startswith('.dev'):
  94. # Suffixed `dev` release version in master.
  95. return 'master'
  96. else:
  97. raise ValueError('Unparseable pants version number: {}'.format(version))
  98. def notes_for_version(self, version):
  99. """Given the parsed Version of pants, return its release notes.
  100. TODO: This method should parse out the specific version from the resulting file:
  101. see https://github.com/pantsbuild/pants/issues/1708
  102. """
  103. branch_name = self._branch_name(version)
  104. branch_notes_file = self._branch_notes.get(branch_name, None)
  105. if branch_notes_file is None:
  106. raise ValueError(
  107. 'Version {} lives in branch {}, which is not configured in {}.'.format(
  108. version, branch_name, self._branch_notes))
  109. return _read_contents(branch_notes_file)
  110. class PantsPlugin(PythonLibrary):
  111. """A pants plugin published by pantsbuild."""
  112. @classmethod
  113. def create_setup_py(cls, name, description, additional_classifiers=None):
  114. return pants_setup_py(name,
  115. description,
  116. additional_classifiers=additional_classifiers,
  117. namespace_packages=['pants', 'pants.backend'])
  118. def __init__(self,
  119. address=None,
  120. payload=None,
  121. distribution_name=None,
  122. description=None,
  123. additional_classifiers=None,
  124. build_file_aliases=False,
  125. global_subsystems=False,
  126. register_goals=False,
  127. **kwargs):
  128. """
  129. :param str distribution_name: The name of the plugin package; must start with
  130. 'pantsbuild.pants.'.
  131. :param str description: A brief description of what the plugin provides.
  132. :param list additional_classifiers: Any additional trove classifiers that apply to the plugin,
  133. see: https://pypi.python.org/pypi?%3Aaction=list_classifiers
  134. :param bool build_file_aliases: If `True`, register.py:build_file_aliases must be defined and
  135. registers the 'build_file_aliases' 'pantsbuild.plugin'
  136. entrypoint.
  137. :param bool global_subsystems: If `True`, register.py:global_subsystems must be defined and
  138. registers the 'global_subsystems' 'pantsbuild.plugin' entrypoint.
  139. :param bool register_goals: If `True`, register.py:register_goals must be defined and
  140. registers the 'register_goals' 'pantsbuild.plugin' entrypoint.
  141. """
  142. if not distribution_name.startswith('pantsbuild.pants.'):
  143. raise ValueError("Pants plugin package distribution names must start with "
  144. "'pantsbuild.pants.', given {}".format(distribution_name))
  145. if not os.path.exists(os.path.join(get_buildroot(), address.spec_path, 'register.py')):
  146. raise TargetDefinitionException(address.spec_path,
  147. 'A PantsPlugin target must have a register.py file in the '
  148. 'same directory.')
  149. setup_py = self.create_setup_py(distribution_name,
  150. description,
  151. additional_classifiers=additional_classifiers)
  152. super(PantsPlugin, self).__init__(address,
  153. payload,
  154. sources=['register.py'],
  155. provides=setup_py,
  156. **kwargs)
  157. if build_file_aliases or register_goals or global_subsystems:
  158. module = os.path.relpath(address.spec_path, self.target_base).replace(os.sep, '.')
  159. entrypoints = []
  160. if build_file_aliases:
  161. entrypoints.append('build_file_aliases = {}.register:build_file_aliases'.format(module))
  162. if register_goals:
  163. entrypoints.append('register_goals = {}.register:register_goals'.format(module))
  164. if global_subsystems:
  165. entrypoints.append('global_subsystems = {}.register:global_subsystems'.format(module))
  166. entry_points = {'pantsbuild.plugin': entrypoints}
  167. setup_py.setup_py_keywords['entry_points'] = entry_points
  168. self.mark_invalidation_hash_dirty() # To pickup the PythonArtifact (setup_py) changes.
  169. class ContribPlugin(PantsPlugin):
  170. """A contributed pants plugin published by pantsbuild."""
  171. @classmethod
  172. def create_setup_py(cls, name, description, additional_classifiers=None):
  173. return contrib_setup_py(name, description, additional_classifiers=additional_classifiers)
  174. def global_subsystems():
  175. return {PantsReleases}
  176. def build_file_aliases():
  177. return BuildFileAliases(
  178. objects={
  179. 'pants_setup_py': pants_setup_py,
  180. 'contrib_setup_py': contrib_setup_py
  181. },
  182. targets={
  183. 'pants_plugin': PantsPlugin,
  184. 'contrib_plugin': ContribPlugin
  185. }
  186. )