mz_version.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. # Copyright Materialize, Inc. and contributors. All rights reserved.
  2. #
  3. # Use of this software is governed by the Business Source License
  4. # included in the LICENSE file at the root of this repository.
  5. #
  6. # As of the Change Date specified in that file, in accordance with
  7. # the Business Source License, use of this software will be governed
  8. # by the Apache License, Version 2.0.
  9. """Version types"""
  10. from __future__ import annotations
  11. import json
  12. import subprocess
  13. from typing import TypeVar
  14. try:
  15. from semver.version import Version
  16. except ImportError:
  17. from semver import VersionInfo as Version # type: ignore
  18. T = TypeVar("T", bound="TypedVersionBase")
  19. class TypedVersionBase(Version):
  20. """Typed version, can be parsed from version string"""
  21. @classmethod
  22. def get_prefix(cls) -> str:
  23. raise NotImplementedError(f"Not implemented in {cls}")
  24. @classmethod
  25. def create(
  26. cls: type[T], major: int, minor: int, patch: int, prerelease: str | None = None
  27. ) -> T:
  28. prerelease_suffix = f"-{prerelease}" if prerelease is not None else ""
  29. return cls.parse(
  30. f"{cls.get_prefix()}{major}.{minor}.{patch}{prerelease_suffix}"
  31. )
  32. @classmethod
  33. def parse_without_prefix(
  34. cls: type[T], version_without_prefix: str, drop_dev_suffix: bool = False
  35. ) -> T:
  36. version = f"{cls.get_prefix()}{version_without_prefix}"
  37. return cls.parse(version, drop_dev_suffix=drop_dev_suffix)
  38. @classmethod
  39. def parse(cls: type[T], version: str, drop_dev_suffix: bool = False) -> T:
  40. """Parses a version string with prefix, for example: v0.45.0-dev (f01773cb1) or v0.115.0-dev.0"""
  41. expected_prefix = cls.get_prefix()
  42. if not version.startswith(expected_prefix):
  43. raise ValueError(
  44. f"Invalid version string '{version}', expected prefix '{expected_prefix}'"
  45. )
  46. version = version.removeprefix(expected_prefix)
  47. if " " in version:
  48. version, git_hash = version.split(" ")
  49. if not git_hash[0] == "(" or not git_hash[-1] == ")":
  50. raise ValueError(f"Invalid mz version string: {version}")
  51. # Hash ignored
  52. if drop_dev_suffix:
  53. version, _, _ = version.partition("-dev")
  54. return super().parse(version)
  55. @classmethod
  56. def try_parse(
  57. cls: type[T], version: str, drop_dev_suffix: bool = False
  58. ) -> T | None:
  59. """Parses a version string but returns empty if that fails"""
  60. try:
  61. return cls.parse(version, drop_dev_suffix=drop_dev_suffix)
  62. except ValueError:
  63. return None
  64. @classmethod
  65. def is_valid_version_string(cls, version: str) -> bool:
  66. return cls.try_parse(version) is not None
  67. def str_without_prefix(self) -> str:
  68. return super().__str__()
  69. def __str__(self) -> str:
  70. return f"{self.get_prefix()}{self.str_without_prefix()}"
  71. def is_dev_version(self) -> bool:
  72. return self.prerelease is not None
  73. class MzVersion(TypedVersionBase):
  74. """Version of Materialize, can be parsed from version string, SQL, cargo"""
  75. @classmethod
  76. def get_prefix(cls) -> str:
  77. return "v"
  78. @classmethod
  79. def parse_mz(cls: type[T], version: str, drop_dev_suffix: bool = False) -> T:
  80. """Parses a version string with prefix, for example: v0.45.0-dev (f01773cb1) or v0.115.0-dev.0"""
  81. return cls.parse(version=version, drop_dev_suffix=drop_dev_suffix)
  82. @classmethod
  83. def parse_cargo(cls) -> MzVersion:
  84. """Uses the cargo mz-environmentd package info to get the version of current source code state"""
  85. metadata = json.loads(
  86. subprocess.check_output(
  87. ["cargo", "metadata", "--no-deps", "--format-version=1"]
  88. )
  89. )
  90. for package in metadata["packages"]:
  91. if package["name"] == "mz-environmentd":
  92. return cls.parse_without_prefix(package["version"])
  93. else:
  94. raise ValueError("No mz-environmentd version found in cargo metadata")
  95. class MzCliVersion(TypedVersionBase):
  96. """Version of Materialize APT"""
  97. @classmethod
  98. def get_prefix(cls) -> str:
  99. return "mz-v"
  100. class MzLspServerVersion(TypedVersionBase):
  101. """Version of Materialize LSP Server"""
  102. @classmethod
  103. def get_prefix(cls) -> str:
  104. return "mz-lsp-server-v"
  105. class MzDebugVersion(TypedVersionBase):
  106. """Version of Materialize Debug Tool"""
  107. @classmethod
  108. def get_prefix(cls) -> str:
  109. return "mz-debug-v"