build_package_structure.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  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. # 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 os
  18. import re
  19. import shutil
  20. import sys
  21. from pathlib import Path
  22. from common import PACKAGES, Package
  23. # Base build directory name
  24. DEST_ROOT = "build_"
  25. # Files to be copied from taipy/<package> to build directory
  26. BUILD_CP_FILES = ["README.md", "setup.py"]
  27. # Files to be moved from taipy/<package> to build directory
  28. BUILD_MV_FILES = ["LICENSE", "package_desc.md", "pyproject.toml"]
  29. # Items to skip while copying directory structure
  30. SKIP_ITEMS = {
  31. "taipy": [
  32. "build_taipy",
  33. "doc",
  34. "frontend",
  35. "readme_img",
  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(PACKAGES)
  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 usage() -> None:
  66. print(f"Usage: {sys.argv[0]} <package>") # noqa: T201
  67. packages = "', '".join(PACKAGES)
  68. print(f" <package> must be one of '{packages}', or 'taipy'.") # noqa: T201
  69. if __name__ == "__main__":
  70. if len(sys.argv) < 2:
  71. usage()
  72. raise ValueError("Missing arguments.")
  73. package = Package(sys.argv[1])
  74. if package.name == "taipy":
  75. # Check that gui_core bundle was built
  76. if not os.path.exists("taipy/gui_core/lib/taipy-gui-core.js"):
  77. raise SystemError("Taipy GUI-Core bundle was not built")
  78. elif package.name == "gui":
  79. # Check that gui bundle was built
  80. if not os.path.exists("taipy/gui/webapp/taipy-gui.js"):
  81. raise SystemError("Taipy GUI bundle was not built")
  82. # Create 'build_<package>' target directory and its subdirectory 'taipy' if needed
  83. build_dir = Path(DEST_ROOT + package.short_name)
  84. if build_dir.exists():
  85. print(f"Removing legacy directory '{build_dir}'") # noqa: T201
  86. shutil.rmtree(build_dir)
  87. dest_dir = build_dir
  88. if package.name != "taipy":
  89. dest_dir = build_dir / "taipy"
  90. dest_dir.mkdir(parents=True, exist_ok=True)
  91. def recursive_copy(source, dest, *, parent: str = "", skip_root: bool = False):
  92. dest_path = dest if skip_root else os.path.join(dest, os.path.basename(source))
  93. if not skip_root:
  94. os.makedirs(dest_path, exist_ok=True)
  95. for item in os.listdir(source):
  96. src_item = os.path.join(source, item)
  97. dest_item = os.path.join(dest_path, item)
  98. if not skip_path(src_item, package, parent):
  99. if os.path.isfile(src_item):
  100. shutil.copy2(src_item, dest_item)
  101. elif os.path.isdir(src_item):
  102. if (s := src_item.replace("\\", "/")).startswith("./"):
  103. s = s[2:]
  104. recursive_copy(src_item, dest_path, parent=s)
  105. # Copy the package build files from taipy[/package] to build_<package>/taipy
  106. recursive_copy("." if package.name == "taipy" else package.package_dir, dest_dir)
  107. # This is needed for local builds (i.e. not in a Github workflow)
  108. if package.name == "taipy":
  109. # Needs the frontend build scripts
  110. tools_dir = build_dir / "tools" / "frontend"
  111. tools_dir.mkdir(parents=True, exist_ok=True)
  112. shutil.copy2("tools/frontend/bundle_build.py", tools_dir)
  113. # Copy the build files from tools/packages/taipy to build_taipy
  114. recursive_copy(Path("tools") / "packages" / "taipy", build_dir, skip_root=True)
  115. else:
  116. build_package_dir = build_dir / package.package_dir
  117. # Copy build files from package to build dir
  118. for filename in BUILD_CP_FILES:
  119. shutil.copy2(build_package_dir / filename, build_dir)
  120. # Move build files from package to build dir
  121. for filename in BUILD_MV_FILES:
  122. shutil.move(build_package_dir / filename, build_dir)
  123. # Copy the build files from tools/packages/taipy-<package> to build_<package>
  124. recursive_copy(Path("tools") / "packages" / f"taipy-{package.short_name}", build_dir, skip_root=True)
  125. # Check that versions were set in setup.requirements.txt
  126. with open(build_dir / "setup.requirements.txt") as requirements_file:
  127. for line in requirements_file:
  128. if match := re.fullmatch(r"(taipy\-\w+)(.*)", line.strip()):
  129. if not match[2]: # Version not updated
  130. print(f"setup.requirements.txt not up-to-date in 'tools/packages/{package.short_name}'.") # noqa: T201
  131. raise SystemError(f"Version for dependency on {match[1]} is missing.")
  132. # Copy topmost __init__
  133. if package.name != "taipy":
  134. shutil.copy2(Path("taipy") / "__init__.py", dest_dir)