sphinx_doc_update.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. #!/usr/bin/env python3
  2. # ##### BEGIN GPL LICENSE BLOCK #####
  3. #
  4. # This program is free software; you can redistribute it and/or
  5. # modify it under the terms of the GNU General Public License
  6. # as published by the Free Software Foundation; either version 2
  7. # of the License, or (at your option) any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU General Public License
  15. # along with this program; if not, write to the Free Software Foundation,
  16. # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  17. #
  18. # ##### END GPL LICENSE BLOCK #####
  19. # <pep8 compliant>
  20. """
  21. This is a helper script to generate Blender Python API documentation (using Sphinx), and update server data using rsync.
  22. You'll need to specify your user login and password, obviously.
  23. Example usage:
  24. ./sphinx_doc_update.py --mirror ../../../docs/remote_api_backup/ --source ../.. --blender ../../../build_cmake/bin/blender --user foobar --password barfoo
  25. """
  26. import os
  27. import shutil
  28. import subprocess
  29. import sys
  30. import tempfile
  31. import zipfile
  32. DEFAULT_RSYNC_SERVER = "docs.blender.org"
  33. DEFAULT_RSYNC_ROOT = "/api/"
  34. DEFAULT_SYMLINK_ROOT = "/data/www/vhosts/docs.blender.org/api"
  35. def argparse_create():
  36. import argparse
  37. global __doc__
  38. # When --help or no args are given, print this help
  39. usage_text = __doc__
  40. parser = argparse.ArgumentParser(description=usage_text,
  41. formatter_class=argparse.RawDescriptionHelpFormatter)
  42. parser.add_argument(
  43. "--mirror", dest="mirror_dir",
  44. metavar='PATH', required=True,
  45. help="Path to local rsync mirror of api doc server")
  46. parser.add_argument(
  47. "--source", dest="source_dir",
  48. metavar='PATH', required=True,
  49. help="Path to Blender git repository")
  50. parser.add_argument(
  51. "--blender", dest="blender",
  52. metavar='PATH', required=True,
  53. help="Path to Blender executable")
  54. parser.add_argument(
  55. "--rsync-server", dest="rsync_server", default=DEFAULT_RSYNC_SERVER,
  56. metavar='RSYNCSERVER', type=str, required=False,
  57. help=("rsync server address"))
  58. parser.add_argument(
  59. "--rsync-root", dest="rsync_root", default=DEFAULT_RSYNC_ROOT,
  60. metavar='RSYNCROOT', type=str, required=False,
  61. help=("Root path of API doc on rsync server"))
  62. parser.add_argument(
  63. "--user", dest="user",
  64. metavar='USER', type=str, required=True,
  65. help=("User to login on rsync server"))
  66. parser.add_argument(
  67. "--password", dest="password",
  68. metavar='PASSWORD', type=str, required=True,
  69. help=("Password to login on rsync server"))
  70. return parser
  71. def main():
  72. # ----------
  73. # Parse Args
  74. args = argparse_create().parse_args()
  75. rsync_base = "rsync://%s@%s:%s" % (args.user, args.rsync_server, args.rsync_root)
  76. blenver = blenver_zip = ""
  77. api_name = ""
  78. branch = ""
  79. is_release = False
  80. # I) Update local mirror using rsync.
  81. rsync_mirror_cmd = ("rsync", "--delete-after", "-avzz", rsync_base, args.mirror_dir)
  82. subprocess.run(rsync_mirror_cmd, env=dict(os.environ, RSYNC_PASSWORD=args.password))
  83. with tempfile.TemporaryDirectory() as tmp_dir:
  84. # II) Generate doc source in temp dir.
  85. doc_gen_cmd = (
  86. args.blender, "--background", "-noaudio", "--factory-startup", "--python-exit-code", "1",
  87. "--python", "%s/doc/python_api/sphinx_doc_gen.py" % args.source_dir, "--",
  88. "--output", tmp_dir
  89. )
  90. subprocess.run(doc_gen_cmd)
  91. # III) Get Blender version info.
  92. getver_file = os.path.join(tmp_dir, "blendver.txt")
  93. getver_script = (
  94. "import sys, bpy\n"
  95. "with open(sys.argv[-1], 'w') as f:\n"
  96. " is_release = bpy.app.version_cycle in {'rc', 'release'}\n"
  97. " branch = bpy.app.build_branch.split()[0].decode()\n"
  98. " f.write('%d\\n' % is_release)\n"
  99. " f.write('%s\\n' % branch)\n"
  100. " f.write('%d.%d%s\\n' % (bpy.app.version[0], bpy.app.version[1], bpy.app.version_char)\n"
  101. " if is_release else '%s\\n' % branch)\n"
  102. " f.write('%d_%d%s_release' % (bpy.app.version[0], bpy.app.version[1], bpy.app.version_char)\n"
  103. " if is_release else '%d_%d_%d' % bpy.app.version)\n"
  104. )
  105. get_ver_cmd = (args.blender, "--background", "-noaudio", "--factory-startup", "--python-exit-code", "1",
  106. "--python-expr", getver_script, "--", getver_file)
  107. subprocess.run(get_ver_cmd)
  108. with open(getver_file) as f:
  109. is_release, branch, blenver, blenver_zip = f.read().split("\n")
  110. is_release = bool(int(is_release))
  111. os.remove(getver_file)
  112. # IV) Build doc.
  113. curr_dir = os.getcwd()
  114. os.chdir(tmp_dir)
  115. sphinx_cmd = ("sphinx-build", "-b", "html", "sphinx-in", "sphinx-out")
  116. subprocess.run(sphinx_cmd)
  117. shutil.rmtree(os.path.join("sphinx-out", ".doctrees"))
  118. os.chdir(curr_dir)
  119. # V) Cleanup existing matching dir in server mirror (if any), and copy new doc.
  120. api_name = blenver
  121. api_dir = os.path.join(args.mirror_dir, api_name)
  122. if os.path.exists(api_dir):
  123. shutil.rmtree(api_dir)
  124. os.rename(os.path.join(tmp_dir, "sphinx-out"), api_dir)
  125. # VI) Create zip archive.
  126. zip_name = "blender_python_reference_%s" % blenver_zip # We can't use 'release' postfix here...
  127. zip_path = os.path.join(args.mirror_dir, zip_name)
  128. with zipfile.ZipFile(zip_path, 'w') as zf:
  129. for dirname, _, filenames in os.walk(api_dir):
  130. for filename in filenames:
  131. filepath = os.path.join(dirname, filename)
  132. zip_filepath = os.path.join(zip_name, os.path.relpath(filepath, api_dir))
  133. zf.write(filepath, arcname=zip_filepath)
  134. os.rename(zip_path, os.path.join(api_dir, "%s.zip" % zip_name))
  135. # VII) Create symlinks and html redirects.
  136. if is_release:
  137. symlink = os.path.join(args.mirror_dir, "current")
  138. os.remove(symlink)
  139. os.symlink("./%s" % api_name, symlink)
  140. with open(os.path.join(args.mirror_dir, "250PythonDoc/index.html"), 'w') as f:
  141. f.write("<html><head><title>Redirecting...</title><meta http-equiv=\"REFRESH\""
  142. "content=\"0;url=../%s/\"></head><body>Redirecting...</body></html>" % api_name)
  143. elif branch == "master":
  144. with open(os.path.join(args.mirror_dir, "blender_python_api/index.html"), 'w') as f:
  145. f.write("<html><head><title>Redirecting...</title><meta http-equiv=\"REFRESH\""
  146. "content=\"0;url=../%s/\"></head><body>Redirecting...</body></html>" % api_name)
  147. # VIII) Upload (first do a dry-run so user can ensure everything is OK).
  148. print("Doc generated in local mirror %s, please check it before uploading "
  149. "(hit [Enter] to continue, [Ctrl-C] to exit):" % api_dir)
  150. sys.stdin.read(1)
  151. rsync_mirror_cmd = ("rsync", "--dry-run", "--delete-after", "-avzz", args.mirror_dir, rsync_base)
  152. subprocess.run(rsync_mirror_cmd, env=dict(os.environ, RSYNC_PASSWORD=args.password))
  153. print("Rsync upload simulated, please check every thing is OK (hit [Enter] to continue, [Ctrl-C] to exit):")
  154. sys.stdin.read(1)
  155. rsync_mirror_cmd = ("rsync", "--delete-after", "-avzz", args.mirror_dir, rsync_base)
  156. subprocess.run(rsync_mirror_cmd, env=dict(os.environ, RSYNC_PASSWORD=args.password))
  157. if __name__ == "__main__":
  158. main()