Browse Source

Add a dataclass for package

main
Sindre Stephansen 2 years ago
parent
commit
66e8cba3e7
Signed by: sindre <sindre@sindrestephansen.com> GPG Key ID: B06FC67D17A46ADE
5 changed files with 78 additions and 60 deletions
  1. +37
    -22
      sync/src/config.py
  2. +5
    -2
      sync/src/gradle.py
  3. +15
    -11
      sync/src/maven/fetch.py
  4. +3
    -2
      sync/src/maven/packages.py
  5. +18
    -23
      sync/src/pom.py

+ 37
- 22
sync/src/config.py View File

@@ -8,13 +8,23 @@ from pathlib import Path
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)




@dataclass
class Package:
group_id: str
artifact_id: str
version: str

def __str__(self):
return f'{self.group_id}:{self.artifact_id}:{self.version}'


@dataclass @dataclass
class Configuration: class Configuration:
name: str name: str
kotlin_version: str kotlin_version: str
gradle_version: Optional[str] gradle_version: Optional[str]
plugins: dict[str, str] plugins: dict[str, str]
packages: list[str]
packages: list[Package]




@dataclass @dataclass
@@ -23,39 +33,44 @@ class Config:
mirrors: list[str] mirrors: list[str]




def handle_packages(section) -> list[str]:
def handle_packages(section) -> list[Package]:
ignore = ['_versions'] ignore = ['_versions']
result = []
result: list[Package] = []


for entry in section: for entry in section:
if entry not in ignore: if entry not in ignore:
if ':' in entry: if ':' in entry:
versions = section[entry]
if isinstance(versions, str):
result.append(f'{entry}:{versions}')
elif isinstance(versions, list):
for version in versions:
result.append(f'{entry}:{version}')
try:
group_id, artifact_id = entry.split(':')
except ValueError:
logger.exception(f'Illegal package identifier "{entry}". Should be on the format "groupId:artifactId"')
continue

value = section[entry]
if isinstance(value, str):
result.append(Package(group_id, artifact_id, value))
elif isinstance(value, list):
for version in value:
result.append(Package(group_id, artifact_id, version))
else: else:
logger.warning(f'Invalid version "{versions}" for "{entry}". Should be a string or list.')
logger.warning(f'Invalid version "{value}" for "{entry}". Should be a string or list.')
elif isinstance(section[entry], dict): elif isinstance(section[entry], dict):
group_id = entry
group_section = section[entry] group_section = section[entry]
default_versions = group_section.get('_versions', []) default_versions = group_section.get('_versions', [])


for artifact in group_section:
if artifact not in ignore:
versions = group_section[artifact]

if not versions and default_versions:
versions = default_versions
for artifact_id, value in group_section.items():
if artifact_id not in ignore:
if not value and default_versions:
value = default_versions


if isinstance(versions, str):
result.append(f'{entry}:{artifact}:{versions}')
elif isinstance(versions, list) and versions:
for version in versions:
result.append(f'{entry}:{artifact}:{version}')
if isinstance(value, str):
result.append(Package(group_id, artifact_id, value))
elif isinstance(value, list):
for version in value:
result.append(Package(group_id, artifact_id, version))
else: else:
logger.warning(f'Invalid versions "{versions}" for "{entry}:{artifact}"')
logger.warning(f'Invalid versions "{value}" for "{group_id}:{artifact_id}"')
else: else:
logger.warning(f'Invalid package spec "{entry}". Should be a full spec or a group ID') logger.warning(f'Invalid package spec "{entry}". Should be a full spec or a group ID')




+ 5
- 2
sync/src/gradle.py View File

@@ -1,3 +1,6 @@
from config import Package


def create_gradle_settings(repo: str) -> str: def create_gradle_settings(repo: str) -> str:
return """// Generated, do not edit return """// Generated, do not edit
rootProject.name = "gradle sync job" rootProject.name = "gradle sync job"
@@ -13,7 +16,7 @@ pluginManagement {
""" """




def create_gradle_build(kotlin_version: str, plugins: dict[str, str], packages: list[str], repo: str) -> str:
def create_gradle_build(kotlin_version: str, plugins: dict[str, str], packages: list[Package], repo: str) -> str:
return """// Generated, do not edit return """// Generated, do not edit
plugins { plugins {
""" + f'kotlin("jvm") version "{kotlin_version}"' + """ """ + f'kotlin("jvm") version "{kotlin_version}"' + """
@@ -29,7 +32,7 @@ repositories {
} }


val deps = listOf<String>( val deps = listOf<String>(
""" + ',\n '.join(f'"{dep}"' for dep in sorted(packages)) + """
""" + ',\n '.join(f'"{dep}"' for dep in sorted([str(p) for p in packages])) + """
).flatMap { ).flatMap {
listOf(it, it + ":sources", it + ":javadoc") listOf(it, it + ":sources", it + ":javadoc")
}.map { }.map {


+ 15
- 11
sync/src/maven/fetch.py View File

@@ -3,6 +3,7 @@ import asyncio
from aiohttp import ClientSession from aiohttp import ClientSession
from typing import Optional, Iterable from typing import Optional, Iterable


from config import Package
from pom import PackagePOM from pom import PackagePOM


logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -12,8 +13,13 @@ class TooManyRequestsException(Exception):
pass pass




async def fetch_pom(package: str, mirrors: Iterable[str]) -> Optional[PackagePOM]:
pom = await fetch_maven_file(package, 'pom', mirrors)
def group_url(group_id: str) -> str:
return f'{group_id.replace(".", "/")}'


async def fetch_pom(package: Package, mirrors: Iterable[str]) -> Optional[PackagePOM]:
url = f'{group_url(package.group_id)}/{package.artifact_id}/{package.version}/{package.artifact_id}-{package.version}.pom'
pom = await fetch_maven_file(url, mirrors)
return PackagePOM(package, pom) if pom else None return PackagePOM(package, pom) if pom else None




@@ -27,10 +33,8 @@ async def fetch_from_mirror(session: ClientSession, url: str) -> str | int:
return response.status return response.status




async def fetch_maven_file(package: str, extension: str, mirrors: Iterable[str]) -> Optional[str]:
group_id, artifact_id, version = package.split(':')
url = f'{group_id.replace(".", "/")}/{artifact_id}/{version}/{artifact_id}-{version}.{extension}'
logger.debug(f'{package}: Downloading {extension} from {url}')
async def fetch_maven_file(url: str, mirrors: Iterable[str]) -> Optional[str]:
logger.debug(f'Downloading {url}')


async with ClientSession() as session: async with ClientSession() as session:
# Retry up to 5 times # Retry up to 5 times
@@ -46,20 +50,20 @@ async def fetch_maven_file(package: str, extension: str, mirrors: Iterable[str])
result = await fetch_from_mirror(session, f'{mirror}/{url}') result = await fetch_from_mirror(session, f'{mirror}/{url}')


if isinstance(result, str): if isinstance(result, str):
logger.debug(f'{package}: {extension} downloaded')
logger.debug(f'{url} downloaded')
return result return result
else: else:
logger.debug(f'{package}: HTTP error {result} from mirror {mirror}')
logger.warning(f'HTTP error {result} from {mirror}/{url}. Trying next mirror.')
except TooManyRequestsException: except TooManyRequestsException:
logger.info(f'{package}: Received Too Many Requests error. Trying other mirror.')
logger.info(f'Received Too Many Requests error from {mirror}/{url}. Trying other mirror.')
retry_mirrors.append(mirror) retry_mirrors.append(mirror)


if retry_mirrors: if retry_mirrors:
logger.info(f'{package}: Backing off, then trying again')
logger.info(f"Could not find {url}, but some mirrors didn't respond. Backing off, then trying again")
mirrors = retry_mirrors mirrors = retry_mirrors
await asyncio.sleep(0.1) await asyncio.sleep(0.1)
else: else:
break break


logger.warning(f'{package}: File download of {extension} failed for all mirrors')
logger.error(f'Download of {url} failed for all mirrors')
return None return None

+ 3
- 2
sync/src/maven/packages.py View File

@@ -1,13 +1,14 @@
import logging import logging
from typing import Optional, Iterable from typing import Optional, Iterable


from config import Package
from maven.fetch import fetch_pom from maven.fetch import fetch_pom
from pom import PropertyMissing, Properties from pom import PropertyMissing, Properties


logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)




async def get_effective_packages(package: str, mirrors: Iterable[str]) -> list[str]:
async def get_effective_packages(package: Package, mirrors: Iterable[str]) -> list[Package]:
""" """
Get a list of packages that is required for Gradle to fetch this package. Get a list of packages that is required for Gradle to fetch this package.


@@ -42,7 +43,7 @@ async def get_effective_packages(package: str, mirrors: Iterable[str]) -> list[s
return packages return packages




async def get_parent_props(parent: Optional[str], mirrors: Iterable[str]) -> Properties:
async def get_parent_props(parent: Optional[Package], mirrors: Iterable[str]) -> Properties:
if parent: if parent:
if pom := await fetch_pom(parent, mirrors): if pom := await fetch_pom(parent, mirrors):
pom.set_properties(await get_parent_props(pom.parent, mirrors)) pom.set_properties(await get_parent_props(pom.parent, mirrors))


+ 18
- 23
sync/src/pom.py View File

@@ -3,6 +3,7 @@ import re
from typing import Optional, TypeAlias from typing import Optional, TypeAlias
from xml.etree import ElementTree from xml.etree import ElementTree


from config import Package
from xmlutils import pom_namespace as ns, find_tag_text from xmlutils import pom_namespace as ns, find_tag_text


logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -22,30 +23,20 @@ class PackageError(Exception):
class PackagePOM: class PackagePOM:
name: str name: str
is_bom: bool is_bom: bool
parent: Optional[str]
group_id: Optional[str]
artifact_id: Optional[str]
version: Optional[str]
package: Package
parent: Optional[Package]
properties: Properties properties: Properties


_raw_root: ElementTree.Element _raw_root: ElementTree.Element


def __init__(self, name: str, pom: str):
logger.debug(f'{name}: Parsing POM')
self.name = name
def __init__(self, package: Package, pom: str):
self.package = package
self.name = str(package)
logger.debug(f'{self.name}: Parsing POM')


self._raw_root = ElementTree.fromstring(pom) self._raw_root = ElementTree.fromstring(pom)


self.group_id, self.artifact_id, self.version = name.split(':')

if self.group_id is None or self.artifact_id is None or self.version is None:
logger.warning(
f'{name}: One of groupId={self.group_id}, artifactId={self.artifact_id}, version={self.version} was '
f'None. This can cause issues with dependency resolution'
)

self.parent = None self.parent = None

if (parent_tag := self._raw_root.find('parent', ns)) is not None: if (parent_tag := self._raw_root.find('parent', ns)) is not None:
parent_group = find_tag_text(parent_tag, 'groupId') parent_group = find_tag_text(parent_tag, 'groupId')
parent_artifact = find_tag_text(parent_tag, 'artifactId') parent_artifact = find_tag_text(parent_tag, 'artifactId')
@@ -54,7 +45,7 @@ class PackagePOM:
if parent_group is None or parent_artifact is None or parent_version is None: if parent_group is None or parent_artifact is None or parent_version is None:
raise PackageError(f'Invalid parent {parent_group}:{parent_artifact}:{parent_version}') raise PackageError(f'Invalid parent {parent_group}:{parent_artifact}:{parent_version}')
else: else:
self.parent = f'{parent_group}:{parent_artifact}:{parent_version}'
self.parent = Package(parent_group, parent_artifact, parent_version)


if (packaging := self._raw_root.find('packaging', ns)) is not None: if (packaging := self._raw_root.find('packaging', ns)) is not None:
self.is_bom = packaging == 'pom' self.is_bom = packaging == 'pom'
@@ -64,7 +55,7 @@ class PackagePOM:
self.set_properties({}) self.set_properties({})


@property @property
def dependency_management(self) -> list[str]:
def dependency_management(self) -> list[Package]:
return [ return [
self._package_from_xml_dep(dep) self._package_from_xml_dep(dep)
for dep in self._raw_root.find('dependencyManagement/dependencies', ns) or [] for dep in self._raw_root.find('dependencyManagement/dependencies', ns) or []
@@ -99,11 +90,11 @@ class PackagePOM:
prop = match.group(1) prop = match.group(1)


if prop == 'project.groupId': if prop == 'project.groupId':
value = str(self.group_id)
value = str(self.package.group_id)
elif prop == 'project.artifactId': elif prop == 'project.artifactId':
value = str(self.artifact_id)
value = str(self.package.artifact_id)
elif prop == 'project.version': elif prop == 'project.version':
value = str(self.version)
value = str(self.package.version)
elif prop.startswith('project.build') or prop.startswith('env.') or prop.startswith('maven.'): elif prop.startswith('project.build') or prop.startswith('env.') or prop.startswith('maven.'):
value = None value = None
elif prop in ['project.basedir', 'basedir', 'user.home', 'debug.port']: elif prop in ['project.basedir', 'basedir', 'user.home', 'debug.port']:
@@ -126,10 +117,14 @@ class PackagePOM:
text, text,
) )


def _package_from_xml_dep(self, dep: ElementTree.Element) -> str:
def _package_from_xml_dep(self, dep: ElementTree.Element) -> Package:
def prop_replace_tag(tag) -> str: def prop_replace_tag(tag) -> str:
return self._prop_replace( return self._prop_replace(
elem.text or '' if (elem := dep.find(tag, ns)) is not None else '', elem.text or '' if (elem := dep.find(tag, ns)) is not None else '',
) )


return f"{prop_replace_tag('groupId')}:{prop_replace_tag('artifactId')}:{prop_replace_tag('version')}"
return Package(
prop_replace_tag('groupId'),
prop_replace_tag('artifactId'),
prop_replace_tag('version'),
)

Loading…
Cancel
Save