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