Преглед на файлове

Add support for resolving latest or stable version of packages

main
Sindre Stephansen преди 2 години
родител
ревизия
15aa4ea177
Signed by: sindre <sindre@sindrestephansen.com> GPG Key ID: B06FC67D17A46ADE
променени са 10 файла, в които са добавени 137 реда и са изтрити 14 реда
  1. +2
    -2
      package-list.yaml
  2. +3
    -0
      sync/src/config.py
  3. +3
    -1
      sync/src/gradle.py
  4. +2
    -2
      sync/src/main.py
  5. +7
    -0
      sync/src/maven/fetch.py
  6. +58
    -0
      sync/src/maven/metadata.py
  7. +21
    -2
      sync/src/maven/packages.py
  8. +27
    -0
      sync/src/maven/version.py
  9. +10
    -4
      sync/src/pom.py
  10. +4
    -3
      sync/src/xmlutils.py

+ 2
- 2
package-list.yaml Целия файл

@@ -33,7 +33,7 @@ configurations:
packages: packages:
org.jetbrains.kotlinx:kotlinx-datetime: "0.4.0" org.jetbrains.kotlinx:kotlinx-datetime: "0.4.0"
com.expediagroup: com.expediagroup:
_versions: ["7.0.0-alpha.6", "6.5.2"]
graphql-kotlin-ktor-server: ["7.0.0-alpha.6"]
_versions: ["latest", "stable"]
graphql-kotlin-ktor-server: ["latest"]
graphql-kotlin-client: [] graphql-kotlin-client: []
graphql-kotlin-client-generator: [] graphql-kotlin-client-generator: []

+ 3
- 0
sync/src/config.py Целия файл

@@ -17,6 +17,9 @@ class Package:
def __str__(self): def __str__(self):
return f'{self.group_id}:{self.artifact_id}:{self.version}' return f'{self.group_id}:{self.artifact_id}:{self.version}'


def __hash__(self):
return hash(str(self))



@dataclass @dataclass
class Configuration: class Configuration:


+ 3
- 1
sync/src/gradle.py Целия файл

@@ -1,3 +1,5 @@
from typing import Iterable

from config import Package from config import Package




@@ -16,7 +18,7 @@ pluginManagement {
""" """




def create_gradle_build(kotlin_version: str, plugins: dict[str, str], packages: list[Package], repo: str) -> str:
def create_gradle_build(kotlin_version: str, plugins: dict[str, str], packages: Iterable[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}"' + """


+ 2
- 2
sync/src/main.py Целия файл

@@ -11,11 +11,11 @@ logger = logging.getLogger(__name__)




async def resolve_kotlin(ident: str, configuration: Configuration, output_dir: Path, mirrors: list[str], gradle_repo: str): async def resolve_kotlin(ident: str, configuration: Configuration, output_dir: Path, mirrors: list[str], gradle_repo: str):
resolved_packages = [
resolved_packages = set([
resolved resolved
for package in configuration.packages for package in configuration.packages
for resolved in await get_effective_packages(package, mirrors) for resolved in await get_effective_packages(package, mirrors)
]
])


gradle_path = configuration.gradle_version or "default" gradle_path = configuration.gradle_version or "default"
path = output_dir / f"{gradle_path}/{ident}-kotlin-{configuration.kotlin_version}" path = output_dir / f"{gradle_path}/{ident}-kotlin-{configuration.kotlin_version}"


+ 7
- 0
sync/src/maven/fetch.py Целия файл

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


from config import Package from config import Package
from maven.metadata import MavenMetadata
from pom import PackagePOM from pom import PackagePOM


logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -17,6 +18,12 @@ def group_url(group_id: str) -> str:
return f'{group_id.replace(".", "/")}' return f'{group_id.replace(".", "/")}'




async def fetch_metadata(package: Package, mirrors: Iterable[str]) -> Optional[MavenMetadata]:
url = f'{group_url(package.group_id)}/{package.artifact_id}/maven-metadata.xml'
metadata = await fetch_maven_file(url, mirrors)
return MavenMetadata(package, metadata) if metadata else None


async def fetch_pom(package: Package, mirrors: Iterable[str]) -> Optional[PackagePOM]: 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' 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) pom = await fetch_maven_file(url, mirrors)


+ 58
- 0
sync/src/maven/metadata.py Целия файл

@@ -0,0 +1,58 @@
import logging
from dataclasses import dataclass
from xml.etree import ElementTree

from config import Package
from xmlutils import find_tag_text

logger = logging.getLogger(__name__)


class MetadataError(Exception):
pass


@dataclass
class MavenMetadata:
latest: str
release: str
stable: str
versions: list[str]

def __init__(self, package: Package, xml: str):
root = ElementTree.fromstring(xml)

versioning = root.find('versioning')
if versioning is None:
logger.error(f'Maven metadata for {package} does not contain "<versioning>"')
raise MetadataError(str(package))

versions_tag = versioning.find('versions')
if versions_tag is None:
logger.error(f'Maven metadata for {package} does not contain any versions')
raise MetadataError(str(package))

self.versions = [v.text for v in versions_tag if v is not None and v.text]
self.versions.reverse()

if (latest := find_tag_text(versioning, 'latest')) is not None:
self.latest = latest
else:
logger.error(f'Maven metadata for {package} does not contain <latest>')
raise MetadataError(str(package))

if (release := find_tag_text(versioning, 'release')) is not None:
self.release = release
else:
logger.error(f'Maven metadata for {package} does not contain <release>')
raise MetadataError(str(package))

if '-' in self.release:
release_index = self.versions.index(self.release)
stable_list = [v for v in self.versions[release_index:] if '-' not in v]
try:
self.stable = stable_list[0]
except IndexError:
self.stable = self.release
else:
self.stable = self.release

+ 21
- 2
sync/src/maven/packages.py Целия файл

@@ -1,8 +1,10 @@
import dataclasses
import logging import logging
from typing import Optional, Iterable from typing import Optional, Iterable


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


logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -15,13 +17,30 @@ async def get_effective_packages(package: Package, mirrors: Iterable[str]) -> li
For most packages, this just returns the package name. However, for BOMs, the full list of packages included For most packages, this just returns the package name. However, for BOMs, the full list of packages included
in the BOM is returned (including the BOM itself). This is because Gradle doesn't fetch all packages of a BOM. in the BOM is returned (including the BOM itself). This is because Gradle doesn't fetch all packages of a BOM.


If the version is 'latest' or 'stable', the maven metadata is queried. For 'latest', the <latest> tag is used.
For 'stable' the <release> tag is used. If the <release> tag contains a minus (-) the version is interpreted as
an alpha, beta or RC, and the newest version without a minus will also be fetched.

This requires querying Maven and parsing the POM to check if the package is a BOM. This requires querying Maven and parsing the POM to check if the package is a BOM.
""" """
versions = [
dataclasses.replace(package, version=version)
for version in await resolve_version(package, mirrors)
]

deps = [
dep
for v in versions
for dep in await get_dependencies(v, mirrors)
]

return versions + deps


async def get_dependencies(package: Package, mirrors: Iterable[str]) -> list[Package]:
packages = [] packages = []


if pom := await fetch_pom(package, mirrors): if pom := await fetch_pom(package, mirrors):
packages.append(package)

if pom.is_bom: if pom.is_bom:
try: try:
packages.extend(pom.dependency_management) packages.extend(pom.dependency_management)


+ 27
- 0
sync/src/maven/version.py Целия файл

@@ -0,0 +1,27 @@
import logging
from typing import Iterable

from config import Package
from maven.fetch import fetch_metadata

logger = logging.getLogger(__name__)


async def resolve_version(package: Package, mirrors: Iterable[str]) -> list[str]:
versions = []

if package.version in ['latest', 'stable']:
if metadata := await fetch_metadata(package, mirrors):
if package.version == 'latest':
versions.append(metadata.latest)
elif package.version == 'stable':
versions.append(metadata.release)

if metadata.stable != metadata.release:
versions.append(metadata.stable)
else:
logger.error(f'{package}: Could not find package metadata')
else:
versions.append(package.version)

return versions

+ 10
- 4
sync/src/pom.py Целия файл

@@ -4,12 +4,18 @@ from typing import Optional, TypeAlias
from xml.etree import ElementTree from xml.etree import ElementTree


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


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


Properties: TypeAlias = dict[str, str] Properties: TypeAlias = dict[str, str]


ns = {'': 'http://maven.apache.org/POM/4.0.0'}


def pom_find_tag_text(parent: ElementTree.Element, tag: str) -> str | None:
return find_tag_text(parent, tag, ns)



class PropertyMissing(Exception): class PropertyMissing(Exception):
def __init__(self, prop: str): def __init__(self, prop: str):
@@ -38,9 +44,9 @@ class PackagePOM:


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_artifact = find_tag_text(parent_tag, 'artifactId')
parent_version = find_tag_text(parent_tag, 'version')
parent_group = pom_find_tag_text(parent_tag, 'groupId')
parent_artifact = pom_find_tag_text(parent_tag, 'artifactId')
parent_version = pom_find_tag_text(parent_tag, 'version')


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}')


+ 4
- 3
sync/src/xmlutils.py Целия файл

@@ -1,6 +1,7 @@
pom_namespace = {'': 'http://maven.apache.org/POM/4.0.0'}
from typing import Optional
from xml.etree import ElementTree




def find_tag_text(parent, tag) -> str | None:
elem = parent.find(tag, pom_namespace)
def find_tag_text(parent: ElementTree.Element, tag: str, namespace: Optional[dict[str, str]] = None) -> str | None:
elem = parent.find(tag, namespace)
return elem.text if elem is not None else None return elem.text if elem is not None else None

Loading…
Отказ
Запис