fetch_latest_versions.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. # Copyright 2021-2024 Avaiga Private Limited
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
  4. # the License. You may obtain a copy of the License at
  5. #
  6. # http://www.apache.org/licenses/LICENSE-2.0
  7. #
  8. # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
  9. # an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
  10. # specific language governing permissions and limitations under the License.
  11. # --------------------------------------------------------------------------------------------------
  12. # Returns the latest released versions for every Taipy package that is compatible with the target
  13. # version (major and minor numbers match).
  14. # The target package's version is set to the target version.
  15. #
  16. # Invoked from the workflow in build-and-release-single-package.yml.
  17. #
  18. # Outputs a line for each package (including 'taipy"):
  19. # <package_short_name>_VERSION=<package_version>
  20. # If a dev release is requested, a similar line is issued indicating the next dev version number:
  21. # NEXT_<package_short_name>_VERSION=<package_version>
  22. # An additional line is added containing the latest release for 'taipy', no matter what the target
  23. # version is. This version is extracted so that it has no extension:
  24. # LATEST_TAIPY_VERSION=<package_version>
  25. # --------------------------------------------------------------------------------------------------
  26. import sys
  27. import requests
  28. from common import PACKAGES, Package, Version, fetch_github_releases, fetch_latest_github_taipy_releases
  29. def usage() -> None:
  30. print(f"Usage: {sys.argv[0]} <package> <version> <dev_version> <deps> [<gh_path>]") # noqa: T201
  31. print(" <package> must be a Taipy package name.") # noqa: T201
  32. print(" <version> is the target version for *package*. It must of the form: <Maj>.<Min>.<Tech>[.dev*].") # noqa: T201
  33. print(" <release_type> must be one of 'dev' or 'production'.") # noqa: T201
  34. print(" <deps> must be 'Pypi' or 'GitHub', indicating where to find Taipy package dependencies.") # noqa: T201
  35. print(" <gh_path>: The path of GitHub repository (owner/repo), used if <deps> is 'GitHub'.") # noqa: T201
  36. def fetch_latest_github_releases(
  37. package: Package, version: Version, dev: bool, all_releases: dict[Package, list[Version]]
  38. ) -> dict[Package, Version]:
  39. """Find the latest release version for each package, in the GitHub releases.
  40. All release versions are retrieved from GitHub, and we keep the ones that have a version that
  41. is compatible with *version*.
  42. "dev" releases are kept only if *dev* is True.
  43. Arguments:
  44. package: The package that we want to force *version* for.
  45. version: The incoming version of package *package*.
  46. dev: True if we're targeting a dev release, False for a production release.
  47. gh_path: The "OWNER/REPO" string at the beginning of the working repository.
  48. If not provided, it is computed at runtime from the current Git branch remote URL.
  49. Return:
  50. A dictionary make of [package, version] pairs where the *package* package's version is set
  51. to *version*.
  52. """
  53. # For each package, pick the latest that *version* is compatible with
  54. all_package_names = PACKAGES + ["taipy"]
  55. releases = {}
  56. for pkg_name in all_package_names:
  57. a_package = Package(pkg_name)
  58. if versions := all_releases.get(a_package):
  59. for a_version in versions:
  60. if a_version.ext and (not dev or not a_version.validate_extension("dev")):
  61. continue
  62. if version.is_compatible(a_version):
  63. releases[pkg_name] = a_version
  64. break
  65. # Fill in missing versions
  66. releases[package.short_name] = version
  67. for p in all_package_names:
  68. if p not in releases:
  69. releases[p] = Version.UNKNOWN
  70. return {Package(p): v for p, v in releases.items()}
  71. def fetch_latest_pypi_releases(package: Package, version: Version, dev: bool) -> dict[Package, Version]:
  72. """Find the latest release version for each package, in the Pypi releases.
  73. All release versions are retrieved from Pypi, and we keep the ones that have a version that
  74. is compatible with *version*.
  75. "dev" releases are kept only if *dev* is True.
  76. Return:
  77. A dictionary make of [package, version] pairs where the *package* package's version is set
  78. to *version*.
  79. """
  80. def retrieve_package_version(sub_pkg: Package, dev: bool) -> Version:
  81. """Returns the latest release version for *sub_pkg* on Pypi that is compatible with *version*."""
  82. url = f"https://pypi.org/pypi/{sub_pkg.name}/json"
  83. response = requests.get(url)
  84. resp_json = response.json()
  85. # All release versions for the <sub_pkg> package
  86. versions = list(resp_json["releases"].keys())
  87. if versions:
  88. versions.reverse() # More recent release is last
  89. # Find first that <version> would be compatible with
  90. for v in versions:
  91. check_version = Version.from_string(v)
  92. # Drop all version with extension if not dev
  93. # Keep 'dev' extensions if dev
  94. if check_version.ext and (not dev or not check_version.validate_extension("dev")):
  95. continue
  96. if version.is_compatible(check_version):
  97. return check_version
  98. return Version.UNKNOWN
  99. releases = {pkg: retrieve_package_version(pkg, dev) for pkg in [Package(p) for p in PACKAGES]}
  100. releases[package] = version
  101. return releases
  102. if __name__ == "__main__":
  103. if len(sys.argv) < 5:
  104. usage()
  105. raise ValueError("Missing arguments.")
  106. package = Package(sys.argv[1])
  107. version = Version.from_string(sys.argv[2])
  108. is_dev_version = sys.argv[3] == "dev"
  109. if is_dev_version and (version.ext is None or not version.validate_extension("dev")):
  110. raise ValueError("Version extension does not contain 'dev'.")
  111. pypi_deps = sys.argv[4] == "Pypi" or sys.argv[4] == "true" # true to keep pre-4.0.3 compatibility
  112. gh_path = sys.argv[5] if len(sys.argv) > 5 else None
  113. # Retrieve all available Github releases for all packages
  114. all_releases = fetch_github_releases(gh_path)
  115. # Compute the latest versions compatible with *version*
  116. versions = (
  117. fetch_latest_pypi_releases(package, version, is_dev_version)
  118. if pypi_deps
  119. else fetch_latest_github_releases(package, version, is_dev_version, all_releases)
  120. )
  121. # Print them out
  122. for p, v in versions.items():
  123. print(f"{p.short_name}_VERSION={v}") # noqa: T201
  124. # Print out the latest 'taipy' version that has no extension
  125. print(f"LATEST_TAIPY_VERSION={fetch_latest_github_taipy_releases(all_releases)}") # noqa: T201