PageRenderTime 46ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/contrib/go/src/python/pants/contrib/go/tasks/go_task.py

https://gitlab.com/Ivy001/pants
Python | 168 lines | 144 code | 15 blank | 9 comment | 0 complexity | 533548da3bcdca07d1e4077bd902c6c3 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 json
  7. import re
  8. import subprocess
  9. from collections import namedtuple
  10. from pants.base.workunit import WorkUnit, WorkUnitLabel
  11. from pants.task.task import Task
  12. from pants.util.memo import memoized_method, memoized_property
  13. from twitter.common.collections.orderedset import OrderedSet
  14. from pants.contrib.go.subsystems.go_distribution import GoDistribution
  15. from pants.contrib.go.targets.go_binary import GoBinary
  16. from pants.contrib.go.targets.go_library import GoLibrary
  17. from pants.contrib.go.targets.go_local_source import GoLocalSource
  18. from pants.contrib.go.targets.go_remote_library import GoRemoteLibrary
  19. from pants.contrib.go.targets.go_target import GoTarget
  20. class GoTask(Task):
  21. @classmethod
  22. def global_subsystems(cls):
  23. return super(GoTask, cls).global_subsystems() + (GoDistribution.Factory,)
  24. @staticmethod
  25. def is_binary(target):
  26. return isinstance(target, GoBinary)
  27. @staticmethod
  28. def is_local_lib(target):
  29. return isinstance(target, GoLibrary)
  30. @staticmethod
  31. def is_remote_lib(target):
  32. return isinstance(target, GoRemoteLibrary)
  33. @staticmethod
  34. def is_local_src(target):
  35. return isinstance(target, GoLocalSource)
  36. @staticmethod
  37. def is_go(target):
  38. return isinstance(target, GoTarget)
  39. @memoized_property
  40. def go_dist(self):
  41. return GoDistribution.Factory.global_instance().create()
  42. @memoized_property
  43. def import_oracle(self):
  44. """Return an import oracle that can help look up and categorize imports.
  45. :rtype: :class:`ImportOracle`
  46. """
  47. return ImportOracle(go_dist=self.go_dist, workunit_factory=self.context.new_workunit)
  48. @memoized_property
  49. def goos_goarch(self):
  50. """Return concatenated $GOOS and $GOARCH environment variables, separated by an underscore.
  51. Useful for locating where the Go compiler is placing binaries ("$GOPATH/pkg/$GOOS_$GOARCH").
  52. :rtype: string
  53. """
  54. return '{goos}_{goarch}'.format(goos=self._lookup_go_env_var('GOOS'),
  55. goarch=self._lookup_go_env_var('GOARCH'))
  56. def _lookup_go_env_var(self, var):
  57. return self.go_dist.create_go_cmd('env', args=[var]).check_output().strip()
  58. class ImportOracle(object):
  59. """Answers questions about Go imports."""
  60. class ListDepsError(Exception):
  61. """Indicates a problem listing import paths for one or more packages."""
  62. def __init__(self, go_dist, workunit_factory):
  63. self._go_dist = go_dist
  64. self._workunit_factory = workunit_factory
  65. @memoized_property
  66. def go_stdlib(self):
  67. """Return the set of all Go standard library import paths.
  68. :rtype: frozenset of string
  69. """
  70. out = self._go_dist.create_go_cmd('list', args=['std']).check_output()
  71. return frozenset(out.strip().split())
  72. # This simple regex mirrors the behavior of the relevant go code in practice (see
  73. # repoRootForImportDynamic and surrounding code in
  74. # https://github.com/golang/go/blob/7bc40ffb05d8813bf9b41a331b45d37216f9e747/src/cmd/go/vcs.go).
  75. _remote_import_re = re.compile('[^.]+(?:\.[^.]+)+\/')
  76. def is_remote_import(self, import_path):
  77. """Whether the specified import_path denotes a remote import."""
  78. return self._remote_import_re.match(import_path) is not None
  79. def is_go_internal_import(self, import_path):
  80. """Return `True` if the given import path will be satisfied directly by the Go distribution.
  81. For example, both the go standard library ("archive/tar", "bufio", "fmt", etc.) and "C" imports
  82. are satisfiable by a Go distribution via linking of internal Go code and external c standard
  83. library code respectively.
  84. :rtype: bool
  85. """
  86. # The "C" package is a psuedo-package that links through to the c stdlib, see:
  87. # http://blog.golang.org/c-go-cgo
  88. return import_path == 'C' or import_path in self.go_stdlib
  89. class ImportListing(namedtuple('ImportListing', ['pkg_name',
  90. 'imports',
  91. 'test_imports',
  92. 'x_test_imports'])):
  93. """Represents all the imports of a given package."""
  94. @property
  95. def all_imports(self):
  96. """Return all imports for this package, including any test imports.
  97. :rtype: list of string
  98. """
  99. return list(OrderedSet(self.imports + self.test_imports + self.x_test_imports))
  100. @memoized_method
  101. def list_imports(self, pkg, gopath=None):
  102. """Return a listing of the dependencies of the given package.
  103. :param string pkg: The package whose files to list all dependencies of.
  104. :param string gopath: An optional $GOPATH which points to a Go workspace containing `pkg`.
  105. :returns: The import listing for `pkg` that represents all its dependencies.
  106. :rtype: :class:`ImportOracle.ImportListing`
  107. :raises: :class:`ImportOracle.ListDepsError` if there was a problem listing the dependencies
  108. of `pkg`.
  109. """
  110. go_cmd = self._go_dist.create_go_cmd('list', args=['-json', pkg], gopath=gopath)
  111. with self._workunit_factory(pkg, cmd=str(go_cmd), labels=[WorkUnitLabel.TOOL]) as workunit:
  112. # TODO(John Sirois): It would be nice to be able to tee the stdout to the workunit to we have
  113. # a capture of the json available for inspection in the server console.
  114. process = go_cmd.spawn(stdout=subprocess.PIPE, stderr=workunit.output('stderr'))
  115. out, _ = process.communicate()
  116. returncode = process.returncode
  117. workunit.set_outcome(WorkUnit.SUCCESS if returncode == 0 else WorkUnit.FAILURE)
  118. if returncode != 0:
  119. raise self.ListDepsError('Problem listing imports for {}: {} failed with exit code {}'
  120. .format(pkg, go_cmd, returncode))
  121. data = json.loads(out)
  122. # XTestImports are for black box tests. These test files live inside the package dir but
  123. # declare a different package and thus can only access the public members of the package's
  124. # production code. This style of test necessarily means the test file will import the main
  125. # package. For pants, this would lead to a cyclic self-dependency, so we omit the main
  126. # package as implicitly included as its own dependency.
  127. x_test_imports = [i for i in data.get('XTestImports', []) if i != pkg]
  128. return self.ImportListing(pkg_name=data.get('Name'),
  129. imports=data.get('Imports', []),
  130. test_imports=data.get('TestImports', []),
  131. x_test_imports=x_test_imports)