You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

136 lines
4.6KB

  1. import logging
  2. import re
  3. from typing import Optional, TypeAlias
  4. from xml.etree import ElementTree
  5. from xmlutils import pom_namespace as ns, find_tag_text
  6. logger = logging.getLogger(__name__)
  7. Properties: TypeAlias = dict[str, Optional[str]]
  8. class PropertyMissing(Exception):
  9. def __init__(self, prop: str):
  10. self.prop = prop
  11. class PackageError(Exception):
  12. pass
  13. class PackagePOM:
  14. name: str
  15. is_bom: bool
  16. parent: Optional[str]
  17. group_id: Optional[str]
  18. artifact_id: Optional[str]
  19. version: Optional[str]
  20. properties: Properties
  21. _raw_root: ElementTree.Element
  22. def __init__(self, name: str, pom: str):
  23. logger.debug(f'{name}: Parsing POM')
  24. self.name = name
  25. self._raw_root = ElementTree.fromstring(pom)
  26. self.group_id, self.artifact_id, self.version = name.split(':')
  27. if self.group_id is None or self.artifact_id is None or self.version is None:
  28. logger.warning(
  29. f'{name}: One of groupId={self.group_id}, artifactId={self.artifact_id}, version={self.version} was '
  30. f'None. This can cause issues with dependency resolution'
  31. )
  32. self.parent = None
  33. if (parent_tag := self._raw_root.find('parent', ns)) is not None:
  34. parent_group = find_tag_text(parent_tag, 'groupId')
  35. parent_artifact = find_tag_text(parent_tag, 'artifactId')
  36. parent_version = find_tag_text(parent_tag, 'version')
  37. if parent_group is None or parent_artifact is None or parent_version is None:
  38. raise PackageError(f'Invalid parent {parent_group}:{parent_artifact}:{parent_version}')
  39. else:
  40. self.parent = f'{parent_group}:{parent_artifact}:{parent_version}'
  41. if (packaging := self._raw_root.find('packaging', ns)) is not None:
  42. self.is_bom = packaging == 'pom'
  43. else:
  44. self.is_bom = False
  45. self.set_properties({})
  46. @property
  47. def dependency_management(self) -> list[str]:
  48. return [
  49. self._package_from_xml_dep(dep)
  50. for dep in self._raw_root.find('dependencyManagement/dependencies', ns) or []
  51. ]
  52. def set_properties(self, parent_properties: Optional[Properties]):
  53. logger.debug(f'{self.name}: Parsing properties')
  54. props: Properties = parent_properties or {}
  55. for prop_tag in self._raw_root.findall('.//properties/*', ns):
  56. prop = prop_tag.tag.replace(f'{{{ns[""]}}}', '')
  57. value = prop_tag.text if prop_tag.text is not None else ''
  58. logger.debug(f'{self.name}: Setting prop {prop}={value}')
  59. props[prop] = value
  60. changed = True
  61. while changed:
  62. changed = False
  63. for prop, value in props.items():
  64. new_value = self._prop_replace(value, props, True)
  65. if new_value != value:
  66. changed = True
  67. logger.debug(f'{self.name}: Setting prop {prop}={new_value}')
  68. props[prop] = new_value
  69. self.properties = props
  70. def _prop_replace(self, text, props: Optional[Properties] = None, quiet: bool = False) -> str:
  71. def lookup_prop(match) -> str:
  72. prop = match.group(1)
  73. if prop == 'project.groupId':
  74. value = str(self.group_id)
  75. elif prop == 'project.artifactId':
  76. value = str(self.artifact_id)
  77. elif prop == 'project.version':
  78. value = str(self.version)
  79. elif prop.startswith('project.build') or prop.startswith('env.') or prop.startswith('maven.'):
  80. value = None
  81. elif prop in ['project.basedir', 'basedir', 'user.home', 'debug.port']:
  82. value = None
  83. else:
  84. try:
  85. value = props[prop] if props is not None else self.properties[prop]
  86. except KeyError:
  87. value = None
  88. if value is None and not quiet:
  89. raise PropertyMissing(prop)
  90. else:
  91. logger.debug(f'{self.name}: Replacing property {prop} with {value}')
  92. return value
  93. return re.sub(
  94. r'\$\{([^}]*)}',
  95. lookup_prop,
  96. text,
  97. )
  98. def _package_from_xml_dep(self, dep: ElementTree.Element) -> str:
  99. def prop_replace_tag(tag) -> str:
  100. return self._prop_replace(
  101. elem.text or '' if (elem := dep.find(tag, ns)) is not None else '',
  102. )
  103. return f"{prop_replace_tag('groupId')}:{prop_replace_tag('artifactId')}:{prop_replace_tag('version')}"