Example #1
0
 def _get_parsed_pom_data(self, generation_context):
   self.deps_from_pom = DepsFromPom(PomUtils.pom_provides_target(rootdir=self.root_directory),
     rootdir=self.root_directory,
     exclude_project_targets=generation_context.exclude_project_targets
   )
   self.wire_info = WireInfo.from_pom(self.path, self.root_directory)
   self.signed_jar_info = SignedJarInfo.from_pom(self.path, self.root_directory)
   self.java_options_info = JavaOptionsInfo.from_pom(self.path, self.root_directory)
   self.shading_info = ShadingInfo.from_pom(self.path, self.root_directory)
   self.jooq_info = JooqInfo.from_pom(self.path, self.root_directory)
Example #2
0
def main(sourceFileName):
    start_ms = int(round(time.time() * 1000))
    pants_refs = DepsFromPom(
        PomUtils.pom_provides_target()).get(sourceFileName)
    elapsed_ms = int(round(time.time() * 1000)) - start_ms
    for pants_ref in pants_refs:
        print("      {ref}".format(ref=pants_ref))
    print
    print("Parsed %d pom.xml files in %dms." %
          (PomContentHandler.num_invocations(), elapsed_ms))
Example #3
0
class PomFile(object):
  """Information holder for relevant details of a module's pom.xml."""

  class ParsingError(Exception):
    """Error parsing pom.xml."""

  def __init__(self, pom_file_path, root_directory=None, generation_context=None):
    if generation_context is None:
      generation_context = GenerationContext()
    generation_context.pom_file_cache[(pom_file_path, root_directory)] = self
    self.context = generation_context
    self.path = pom_file_path
    self.root_directory = root_directory
    # Get deps_from_pom, wire_info, etc.
    self._get_parsed_pom_data(generation_context)
    # These dependency lists are initially populated by the pom.xml's data, then updated by
    # build_components as they generated and inject their own dependencies.
    self.lib_deps = []
    self.test_deps = []
    self.lib_jar_deps = []
    self.test_jar_deps = []
    self.resources = []
    self.test_resources = []
    self._initialize_dependency_lists()
    # Names of targets generated for project-level BUILD file.
    self.project_target_names = set()
    # Update our properties dict with any 'special' properties (things that are conditional on
    # sys.platform, etc).
    self._properties = {}
    self._update_properties()
    self._java_options = None
    self._parents = None
    self._shading_rules = None
    self._config_tree = None

  @classmethod
  def find(cls, pom_file_path, root_directory=None, generation_context=None):
    key = (pom_file_path, root_directory)
    if not generation_context or key not in generation_context.pom_file_cache:
      try:
        return cls(pom_file_path, root_directory, generation_context)
      except Exception as e:
        if not os.path.exists(pom_file_path):
          warning = '\nThis probably occurred because that pom file does not actually exist.'
        else:
          warning = '\nThe structure of that pom file is probably illegal or unconventional.'
        raise cls.ParsingError('Exception trying to parse {path}: {error}\n{warn}'.format(
          path=pom_file_path,
          error=str(e),
          warn=warning,
        )), None, sys.exc_info()[2]
    return generation_context.pom_file_cache[key]

  def _get_parsed_pom_data(self, generation_context):
    self.deps_from_pom = DepsFromPom(PomUtils.pom_provides_target(rootdir=self.root_directory),
      rootdir=self.root_directory,
      exclude_project_targets=generation_context.exclude_project_targets
    )
    self.wire_info = WireInfo.from_pom(self.path, self.root_directory)
    self.signed_jar_info = SignedJarInfo.from_pom(self.path, self.root_directory)
    self.java_options_info = JavaOptionsInfo.from_pom(self.path, self.root_directory)
    self.shading_info = ShadingInfo.from_pom(self.path, self.root_directory)
    self.jooq_info = JooqInfo.from_pom(self.path, self.root_directory)

  def _update_properties(self):
    self._properties.update(self.deps_from_pom.properties)
    self._properties.update(SpecialPropertiesInfo.from_pom(self.path,
                                                           self.root_directory).properties)
    # Magic maven symbols.
    basedir = os.path.abspath(self.directory)
    self._properties['basedir'] = "' + symbols.module_directory + '"
    self._properties['project.basedir'] = "' + symbols.module_directory + '"
    self._properties['project.baseUri'] = "' + symbols.module_uri + '"
    self._properties['project.build.directory'] = "' + symbols.module_target_directory + '"
    self._properties['maven.build.timestamp'] = "' + symbols.build_timestamp + '"
    self._properties['user.name'] = "' + symbols.user_name + '"

  def _initialize_dependency_lists(self):
    aggregate_lib_deps, aggregate_test_deps = self.deps_from_pom.get(self.path)
    lib_deps, test_deps, lib_jar_deps, test_jar_deps = [], [], [], []
    for dep in aggregate_lib_deps:
      if not (dep.startswith('jar(') or dep.startswith('sjar(')):
        lib_deps.append(dep)
      else:
        lib_jar_deps.append(dep)
    for dep in aggregate_test_deps:
      if not (dep.startswith('jar(') or dep.startswith('sjar(')):
        test_deps.append(dep)
      else:
        test_jar_deps.append(dep)
    self.lib_deps.extend(lib_deps)
    self.test_deps.extend(test_deps)
    self.lib_jar_deps.extend(lib_jar_deps)
    self.test_jar_deps.extend(test_jar_deps)

  def walk_pom_parents(self):
    """Returns an iterator of PomFile objects in the form self, self.parent, etc."""
    if not self._parents:
      all_parents = []
      pom = self
      while pom:
        all_parents.append(pom)
        pom = pom.parent
      self._parents = all_parents
    return self._parents

  @property
  def parent(self):
    if self.deps_from_pom.parent:
      parent_root = self.deps_from_pom.parent.root_directory
      parent_path = self.deps_from_pom.parent.source_file_name
      check_path = os.path.join(parent_root, parent_path) if parent_root else parent_path
      if not os.path.exists(check_path):
        raise self.ParsingError('Error parsing {pom}: parent pom does not exist at: {parent}.'
                                .format(pom=self.path, parent=check_path))
      return PomFile.find(parent_path, parent_root, generation_context=self.context)
    return None

  def _signed_jar_infos(self):
    for pom in self.walk_pom_parents():
      yield pom.signed_jar_info

  def _update_manifest_entry(self, entries, key, value):
    if key == 'Class-Path':
      paths = value.split(' ')
      for i, path in enumerate(paths):
        if os.path.dirname(path) == 'lib-signed':
          paths[i] = os.path.join('{}-signed-jars'.format(self.deps_from_pom.artifact_id),
                                  os.path.basename(path))
      if key in entries:
        for path in entries[key].split(' '):
          if path not in paths:
            paths.append(path)
      value = ' '.join(paths)
      entries[key] = value
    elif key not in entries or not entries[key]:
      entries[key] = value

  def _dedup_compile_args(self, args):
    # We try to de-dup compile arguments that don't make sense to duplicate.

    def strategy_replace(old_arg, new_arg):
      return None, new_arg

    def strategy_keep_unique(old_arg, new_arg):
      if old_arg == new_arg:
        return None, new_arg
      return old_arg, new_arg

    duplication_strategies = {
      'bootclasspath': strategy_replace,
      'lint': strategy_keep_unique,
    }

    def parse_arg(arg):
      """Return (name, value) tuple."""
      colon = arg.find(':')
      if colon > 0:
        return arg[2:colon], arg[colon+1:]
      return arg, ''

    new_args = []
    for arg in args:
      if not arg.startswith('-X'):
        new_args.append(arg)
        continue
      name, value = parse_arg(arg)
      found_duplicate = False
      for i,a in enumerate(new_args):
        if a.startswith('-X{}:'.format(name)):
          # Duplicate detected.
          found_duplicate = True
          break
      if found_duplicate:
        if name in duplication_strategies:
          old_arg, new_arg = duplication_strategies[name](a, arg)
          if old_arg is None:
            new_args.pop(i)
          if new_arg is not None:
            new_args.append(new_arg)
      else:
        new_args.append(arg)
    return new_args

  def _correct_prefixes(self, args):
    correct = []
    for arg in args:
      if arg.startswith('-C'):
        correct.append(arg)
      else:
        correct.append('-C{}'.format(arg))
    return correct

  @property
  def java_options(self):
    if not self._java_options:
      total = JavaOptionsInfo()
      total.source_level = self.java_options_info.source_level
      total.target_level = self.java_options_info.target_level
      total.compile_args = self.java_options_info.compile_args
      total.test_env_vars = dict(self.java_options_info.test_env_vars)
      total.test_jvm_args = list(self.java_options_info.test_jvm_args)

      pom = self.parent
      if pom:
        total.source_level = total.source_level or pom.java_options.source_level
        total.target_level = total.target_level or pom.java_options.target_level
        total.compile_args = pom.java_options.compile_args + total.compile_args
        total.test_env_vars.update(pom.java_options.test_env_vars)
        total.test_jvm_args.extend(pom.java_options.test_jvm_args)

      total.compile_args = self._dedup_compile_args(total.compile_args)
      total.compile_args = self._correct_prefixes(total.compile_args)
      self._java_options = total
    return self._java_options

  @classmethod
  def _merge_jooq_config(cls, one, two):
    merge_set = {'jdbc', 'properties', 'generator', 'database', 'generate', 'target'}
    merged = copy.deepcopy(one)
    frontier = [(merged, '.', child) for child in two]
    while frontier:
      parent, path, node = frontier.pop(0)
      partner = parent.find('./{}'.format(node.tag))
      stripped_tag = node.tag
      if '}' in node.tag:
        stripped_tag = node.tag[node.tag.find('}')+1:]
      if partner is None:
        # Append.
        parent.append(node)
      elif stripped_tag in merge_set:
        # Merge children.
        frontier.extend((partner, '?', kid) for kid in node)
      else:
        # Replace.
        parent.remove(partner)
        parent.append(copy.deepcopy(node))
    return merged

  @classmethod
  def _inject_jooq_schema_exclusion(cls, tree):
    table_name = 'SCHEMA_VERSION'
    prefix = tree.tag[:tree.tag.rfind('}')+1]
    node = tree.find('./{0}generator/{0}database/{0}excludes'.format(prefix))
    database = tree.find('./{0}generator/{0}database'.format(prefix))
    if node is not None:
      if node.text is None or not node.text.strip():
        node.text = table_name
      elif table_name not in node.text.upper():
        node.text += ' | {}'.format(table_name)
    elif database is not None:
      database.append(ElementTree.fromstring('<excludes>{}</excludes>'.format(table_name)))

  @property
  def jooq_config_tree(self):
    if self._config_tree is None:
      tree = None
      for pom in reversed(self.walk_pom_parents()):
        if tree is None:
          tree = pom.jooq_info.config_tree
          continue
        if pom.jooq_info.config_tree is None:
          continue
        tree = self._merge_jooq_config(tree, pom.jooq_info.config_tree)
      self._inject_jooq_schema_exclusion(tree)
      self._config_tree = tree
    return self._config_tree

  @property
  def jooq_config(self):
    return ElementTree.tostring(self.jooq_config_tree)

  @property
  def manifest_entries(self):
    entries = {}
    for info in self._signed_jar_infos():
      for key, value in info.manifest_entries.items():
        self._update_manifest_entry(entries, key, value)
    return entries

  @property
  def shading_rules(self):
    if self._shading_rules is None:
      self._shading_rules = []
      for pom in reversed(self.walk_pom_parents()):
        self._shading_rules.extend(pom.shading_info.rules)
    return self._shading_rules

  @property
  def signed_jars_deploy_excludes(self):
    exclude_sets = (set(info.excludes) for info in self._signed_jar_infos())
    all_excludes = reduce(set.union, exclude_sets, set())
    # TODO(gm): Pant's exclude() only supports the 'org' and 'name' parameters, not 'type_' etc.
    return sorted(artifact for artifact in all_excludes if len(artifact) == 2)

  @property
  def signed_jars_formatted_excludes(self):
    formatted = []
    for artifact in self.signed_jars_deploy_excludes:
      org, name = artifact
      if '*' in org:
        # TODO(gm): Pants's exclude() currently doesn't support wildcards in organizations.
        continue
      if name == '*':
        # TODO(gm): Wildcards in name only work if the whole name is a wildcard.
        formatted.append("exclude(org='{}')".format(org))
      elif '*' not in name:
        formatted.append("exclude(org='{}', name='{}')".format(org, name))
    return formatted

  @property
  def signed_jars_artifact_ids(self):
    return reduce(set.union, [set(info.signed_jars) for info in self._signed_jar_infos()], set())

  @property
  def signed_jars_dependencies(self):
    signed_jars = self.signed_jars_artifact_ids
    return ['3rdparty:{}'.format('.'.join(artifact))
            for artifact in self.signed_jars_deploy_excludes if artifact[1] in signed_jars]

  @property
  def signed_jars_strip_version(self):
    return any(info.strip_version for info in self._signed_jar_infos())

  @property
  def directory(self):
    return os.path.normpath(os.path.dirname(self.path))

  @property
  def default_target_name(self):
    return os.path.basename(self.directory)

  @property
  def properties(self):
    props = {}
    for pom in reversed(self.walk_pom_parents()):
      props.update(pom._properties)
    return props

  @property
  def mainclass(self):
    return self.deps_from_pom.get_property('project.mainclass')

  @property
  def artifact_id(self):
    return self.deps_from_pom.artifact_id