build_package_structure.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. # Copyright 2021-2025 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. # Builds the structure to hold the package files.
  13. #
  14. # Invoked by the workflow files build-and-release-single-package.yml and build-and-release.yml.
  15. # Working directory must be '[checkout-root]'.
  16. # --------------------------------------------------------------------------------------------------
  17. import argparse
  18. import json
  19. import os
  20. import re
  21. import shutil
  22. from pathlib import Path
  23. from common import Package, Version
  24. # Base build directory name
  25. DEST_ROOT = "build_"
  26. # Files to be copied from taipy/<package> to build directory
  27. BUILD_CP_FILES = ["README.md", "setup.py"]
  28. # Files to be moved from taipy/<package> to build directory
  29. BUILD_MV_FILES = ["LICENSE", "package_desc.md", "pyproject.toml"]
  30. # Items to skip while copying directory structure
  31. SKIP_ITEMS = {
  32. "taipy": [
  33. "build_taipy",
  34. "doc",
  35. "frontend",
  36. "tests",
  37. "tools",
  38. ".git",
  39. ".github",
  40. ".pytest_cache",
  41. "node_modules",
  42. ],
  43. "taipy-gui": [
  44. "node_modules",
  45. ],
  46. }
  47. # Regexp identifying subpackage directories in taipy hierarchy
  48. packages = "|".join(Package.NAMES)
  49. SUB_PACKAGE_DIR_PATTERN = re.compile(rf"taipy/(?:{packages})")
  50. # Filters files not to be copied
  51. def skip_path(path: str, package: Package, parent: str) -> bool:
  52. path = path.replace("\\", "/")
  53. if path.startswith("./"):
  54. path = path[2:]
  55. # Specific items per package
  56. if (skip_items := SKIP_ITEMS.get(package.short_name, None)) and path in skip_items:
  57. return True
  58. # Taipy sub-package directories
  59. if package.name == "taipy" and SUB_PACKAGE_DIR_PATTERN.fullmatch(path):
  60. return True
  61. # Others
  62. if path.endswith("__pycache__") or path.startswith("build_"):
  63. return True
  64. return False
  65. def recursive_copy(package: Package, source, dest, *, parent: str = "", skip_root: bool = False):
  66. dest_path = dest if skip_root else os.path.join(dest, os.path.basename(source))
  67. if not skip_root:
  68. os.makedirs(dest_path, exist_ok=True)
  69. for item in os.listdir(source):
  70. src_item = os.path.join(source, item)
  71. dest_item = os.path.join(dest_path, item)
  72. if not skip_path(src_item, package, parent):
  73. if os.path.isfile(src_item):
  74. shutil.copy2(src_item, dest_item)
  75. elif os.path.isdir(src_item):
  76. if (s := src_item.replace("\\", "/")).startswith("./"):
  77. s = s[2:]
  78. recursive_copy(package, src_item, dest_path, parent=s)
  79. def main():
  80. parser = argparse.ArgumentParser(
  81. description="Creates the directory structure to build a Taipy package.",
  82. formatter_class=argparse.RawTextHelpFormatter,
  83. )
  84. parser.add_argument(
  85. "package",
  86. type=Package.check_argument,
  87. action="store",
  88. help="""The name of the package to setup the build version for.
  89. This must be the short name of a Taipy package (common, core...) or 'taipy'.
  90. """,
  91. )
  92. parser.add_argument("version", type=Version.check_argument, action="store", help="Version of the package to build.")
  93. args = parser.parse_args()
  94. package = Package(args.package)
  95. if package.name == "taipy":
  96. # Check that gui_core bundle was built
  97. if not os.path.exists("taipy/gui_core/lib/taipy-gui-core.js"):
  98. raise SystemError("Taipy GUI-Core bundle was not built")
  99. elif package.name == "gui":
  100. # Check that gui bundle was built
  101. if not os.path.exists("taipy/gui/webapp/taipy-gui.js"):
  102. raise SystemError("Taipy GUI bundle was not built")
  103. # Create 'build_<package>' target directory and its subdirectory 'taipy' if needed
  104. build_dir = Path(DEST_ROOT + package.short_name)
  105. if build_dir.exists():
  106. print(f"Removing legacy directory '{build_dir}'") # noqa: T201
  107. shutil.rmtree(build_dir)
  108. dest_dir = build_dir
  109. if package.name != "taipy":
  110. dest_dir = build_dir / "taipy"
  111. dest_dir.mkdir(parents=True, exist_ok=True)
  112. # Copy the package build files from taipy[/package] to build_<package>/taipy
  113. recursive_copy(package, "." if package.name == "taipy" else package.package_dir, dest_dir)
  114. # This is needed for local builds (i.e. not in a Github workflow)
  115. if package.name == "taipy":
  116. # Needs the frontend build scripts
  117. tools_dir = build_dir / "tools" / "frontend"
  118. tools_dir.mkdir(parents=True, exist_ok=True)
  119. shutil.copy2("tools/frontend/bundle_build.py", tools_dir)
  120. # Copy the build files from tools/packages/taipy to build_taipy
  121. recursive_copy(package, Path("tools") / "packages" / "taipy", build_dir, skip_root=True)
  122. else:
  123. build_package_dir = build_dir / package.package_dir
  124. # Copy build files from package to build dir
  125. for filename in BUILD_CP_FILES:
  126. shutil.copy2(build_package_dir / filename, build_dir)
  127. # Move build files from package to build dir
  128. for filename in BUILD_MV_FILES:
  129. shutil.move(build_package_dir / filename, build_dir)
  130. # Copy the build files from tools/packages/taipy-<package> to build_<package>
  131. recursive_copy(package, Path("tools") / "packages" / f"taipy-{package.short_name}", build_dir, skip_root=True)
  132. # Check that versions were set in setup.requirements.txt
  133. with open(build_dir / "setup.requirements.txt") as requirements_file:
  134. for line in requirements_file:
  135. if match := re.fullmatch(r"(taipy\-\w+)(.*)", line.strip()):
  136. if not match[2]: # Version not updated
  137. print(f"setup.requirements.txt not up-to-date in 'tools/packages/{package.short_name}'.") # noqa: T201
  138. raise SystemError(f"Version for dependency on {match[1]} is missing.")
  139. # Update package's version.json
  140. with open(build_dir / package.package_dir / "version.json", "w") as version_file:
  141. json.dump(args.version.to_dict(), version_file)
  142. # Copy topmost __init__
  143. if package.name != "taipy":
  144. shutil.copy2(Path("taipy") / "__init__.py", dest_dir)
  145. if __name__ == "__main__":
  146. main()