Browse Source

"shore bump" can now replace inter-dependency requirements within a mono-versioned monorepo (close #20)

master
Niklas Rosenstein 1 year ago
parent
commit
522c04e0c0
Signed by: NiklasRosenstein GPG Key ID: 06D269B33D25F6C6
4 changed files with 80 additions and 5 deletions
  1. + 19
    - 1
      src/shore/__main__.py
  2. + 18
    - 2
      src/shore/model.py
  3. + 37
    - 1
      src/shore/plugins/core.py
  4. + 6
    - 1
      src/shore/util/version.py

+ 19
- 1
src/shore/__main__.py

@ -30,7 +30,8 @@ from shore.core.plugins import (
IPackagePlugin,
VersionRef,
write_to_disk)
from shore.model import Monorepo, ObjectCache, Package
from shore.model import Monorepo, ObjectCache, Package, VersionSelector
from shore.plugins.core import get_monorepo_interdependency_version_refs
from shore.util import git as _git
from shore.util.classifiers import get_classifiers
from shore.util.license import get_license_metadata, wrap_license_text
@ -479,6 +480,23 @@ def bump(**args):
with open(ref.filename, 'w') as fp:
fp.write(contents)
# For monorepos using mono-versioning, we may need to bump cross-package references.
if isinstance(subject, Monorepo) and subject.mono_versioning:
version_sel_refs = list(get_monorepo_interdependency_version_refs(subject, new_version))
logger.info('bumping %d monorepo inter-dependency requirement(s)', len(version_sel_refs))
for group_key, refs in Stream.groupby(version_sel_refs, lambda r: r.filename, collect=list):
logger.info(' %s:', os.path.relpath(group_key))
with open(group_key) as fp:
content = fp.read()
offset = 0
for ref in refs:
logger.info(' %s %s -> %s', ref.package, ref.sel, ref.new_sel)
content = content[:ref.start - offset] + ref.new_sel + content[ref.end - offset:]
offset += len(ref.sel) - len(ref.new_sel)
if not args['dry']:
with open(group_key, 'w') as fp:
fp.write(content)
if args['tag']:
if any(f.mode == 'A' for f in _git.porcelain()):
logger.error('cannot tag with non-empty staging area')

+ 18
- 2
src/shore/model.py

@ -34,8 +34,8 @@ from shore.core.plugins import (
PluginNotFound)
from shore.mapper import mapper
from shore.util.ast import load_module_members
from shore.util.version import Version
from typing import Any, Callable, Dict, Iterable, Optional, List, Type
from shore.util.version import bump_version, Version
from typing import Any, Callable, Dict, Iterable, Optional, List, Type, Union
import ast
import collections
import copy
@ -101,6 +101,22 @@ class VersionSelector(object):
return '>={},<{}'.format(match.group(1), '.'.join(max_version))
return re.sub(regex, sub, self._string)
def is_semver_selector(self) -> bool:
return self._string and self._string[0] in '^~' and ',' not in self._string
def matches(self, version: Union[Version, str]) -> bool:
if not self.is_semver_selector():
# TODO (@NiklasRosenstein): Match setuptools version selectors.
return False
min_version = Version(self._string[1:])
if self._string[0] == '^':
max_version = bump_version(min_version, 'major')
elif self._string[0] == '~':
max_version = bump_version(min_version, 'minor')
else:
raise RuntimeError('invalid semver selector string {!r}'.format(self._string))
return min_version <= Version(version) < max_version
VersionSelector.ANY = VersionSelector('*')

+ 37
- 1
src/shore/plugins/core.py

@ -20,14 +20,20 @@
# IN THE SOFTWARE.
from shore.core.plugins import CheckResult, FileToRender, IPackagePlugin, IMonorepoPlugin, VersionRef
from shore.model import BaseObject, Monorepo, Package
from shore.model import BaseObject, Monorepo, Package, VersionSelector
from shore.plugins._util import find_readme_file
from shore.util.classifiers import get_classifiers
from shore.util.version import Version
from nr.interface import implements, override
from typing import Iterable, Optional
import collections
import logging
import os
import re
logger = logging.getLogger(__name__)
VersionSelectorRef = collections.namedtuple('VersionSelectorRef', 'filename,start,end,package,sel,new_sel')
@implements(IPackagePlugin, IMonorepoPlugin)
class CorePlugin:
@ -126,3 +132,33 @@ class CorePlugin:
if match:
return VersionRef(filename, match.start(1), match.end(1), match.group(1))
return None
def get_monorepo_interdependency_version_refs(monorepo: Monorepo, new_version: Version) -> Iterable[VersionSelectorRef]:
"""
Generates #VersionSelectorRef#s for every dependency between packages in *monorepo*
that needs to be updated.
"""
regex = re.compile(r'^\s*- +([A-z0-9\.\-_]+) *([^\n:]+)?$', re.M)
packages = list(monorepo.get_packages())
package_names = set(p.modulename or p.name for p in packages)
for package in packages:
with open(package.filename) as fp:
content = fp.read()
for match in regex.finditer(content):
package_name, version_selector = match.groups()
if version_selector:
version_selector = VersionSelector(version_selector)
new_version_selector = None
if package_name in package_names and version_selector:
if not version_selector.matches(new_version):
if version_selector.is_semver_selector():
new_version_selector = VersionSelector(str(version_selector)[0] + str(new_version))
else:
logger.warning('%s: %s %s does not match version %s of monorepo %s and cannot be automatically bumped.',
package.filename, name, version_selector, new_version, subject.name)
if new_version_selector:
yield VersionSelectorRef(package.filename, match.start(2), match.end(2),
package_name, str(version_selector), str(new_version_selector))

+ 6
- 1
src/shore/util/version.py

@ -20,6 +20,7 @@
# IN THE SOFTWARE.
from packaging.version import Version as _Version
from typing import Union
import re
@ -28,7 +29,11 @@ class Version(_Version):
commit-distance and commit SHA suffix in the format of `-X-gY` (where
X is the distance and Y is the lowercase 7-character SHA sum). """
def __init__(self, s: str):
def __init__(self, s: Union['Version', str]):
if isinstance(s, Version):
s = str(s)
elif not isinstance(s, str):
raise TypeError('expected Version or str, got {}'.format(type(s).__name__))
match = re.match(r'(.*)-(\d+)-g([0-9a-f]{7})', s)
if match:
s = match.group(1)

Loading…
Cancel
Save