Update py API doc generation tools to comply to new name scheme on server.
[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 = "docs.blender.org"
45 DEFAULT_RSYNC_ROOT = "/api/"
46 DEFAULT_SYMLINK_ROOT = "/data/www/vhosts/docs.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     blenver = blenver_zip = ""
100     api_name = ""
101     is_release = False
102
103     # I) Update local mirror using rsync.
104     rsync_mirror_cmd = ("rsync", "--delete-after", "-avzz", rsync_base, args.mirror_dir)
105     subprocess.run(rsync_mirror_cmd, env=dict(os.environ, RSYNC_PASSWORD=args.password))
106
107     with tempfile.TemporaryDirectory() as tmp_dir:
108         # II) Generate doc source in temp dir.
109         doc_gen_cmd = (args.blender, "--background", "-noaudio", "--factory-startup", "--python-exit-code", "1",
110                        "--python", "%s/doc/python_api/sphinx_doc_gen.py" % args.source_dir, "--",
111                        "--output", tmp_dir)
112         subprocess.run(doc_gen_cmd)
113
114         # III) Get Blender version info.
115         getver_file = os.path.join(tmp_dir, "blendver.txt")
116         getver_script = (""
117             "import sys, bpy\n"
118             "with open(sys.argv[-1], 'w') as f:\n"
119             "    is_release = bpy.app.version_cycle in {'rc', 'release'}\n"
120             "    branch = bpy.app.build_branch.split()[0].decode()\n"
121             "    f.write('%d\\n' % is_release)\n"
122             "    f.write('%d.%d%s\\n' % (bpy.app.version[0], bpy.app.version[1], bpy.app.version_char)\n"
123             "            if is_release else '%s\\n' % branch)\n"
124             "    f.write('%d_%d%s_release\\n' % (bpy.app.version[0], bpy.app.version[1], bpy.app.version_char)\n"
125             "            if is_release else '%d_%d_%d' % bpy.app.version)\n")
126         get_ver_cmd = (args.blender, "--background", "-noaudio", "--factory-startup", "--python-exit-code", "1",
127                        "--python-expr", getver_script, "--", getver_file)
128         subprocess.run(get_ver_cmd)
129         with open(getver_file) as f:
130             is_release, blenver, blenver_zip = f.read().split("\n")
131             is_release = bool(int(is_release))
132         os.remove(getver_file)
133
134         # IV) Build doc.
135         curr_dir = os.getcwd()
136         os.chdir(tmp_dir)
137         sphinx_cmd = ("sphinx-build", "-b", "html", "sphinx-in", "sphinx-out")
138         subprocess.run(sphinx_cmd)
139         shutil.rmtree(os.path.join("sphinx-out", ".doctrees"))
140         os.chdir(curr_dir)
141
142         # V) Cleanup existing matching dir in server mirror (if any), and copy new doc.
143         api_name = blenver
144         api_dir = os.path.join(args.mirror_dir, api_name)
145         if os.path.exists(api_dir):
146             shutil.rmtree(api_dir)
147         os.rename(os.path.join(tmp_dir, "sphinx-out"), api_dir)
148
149     # VI) Create zip archive.
150     zip_name = "blender_python_reference_%s" % blenver_zip  # We can't use 'release' postfix here...
151     zip_path = os.path.join(args.mirror_dir, zip_name)
152     with zipfile.ZipFile(zip_path, 'w') as zf:
153         for dirname, _, filenames in os.walk(api_dir):
154             for filename in filenames:
155                 filepath = os.path.join(dirname, filename)
156                 zip_filepath = os.path.join(zip_name, os.path.relpath(filepath, api_dir))
157                 zf.write(filepath, arcname=zip_filepath)
158     os.rename(zip_path, os.path.join(api_dir, "%s.zip" % zip_name))
159
160     # VII) Create symlinks and html redirects.
161     os.symlink("./contents.html", os.path.join(api_dir, "index.html"))
162     if is_release:
163         symlink = os.path.join(args.mirror_dir, "current")
164         os.remove(symlink)
165         os.symlink("./%s" % api_name, symlink)
166         with open(os.path.join(args.mirror_dir, "250PythonDoc/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     else:
170         with open(os.path.join(args.mirror_dir, "blender_python_api/index.html"), 'w') as f:
171             f.write("<html><head><title>Redirecting...</title><meta http-equiv=\"REFRESH\""
172                     "content=\"0;url=../%s/\"></head><body>Redirecting...</body></html>" % api_name)
173
174     # VIII) Upload (first do a dry-run so user can ensure everything is OK).
175     print("Doc generated in local mirror %s, please check it before uploading "
176           "(hit [Enter] to continue, [Ctrl-C] to exit):" % api_dir)
177     sys.stdin.read(1)
178
179     rsync_mirror_cmd = ("rsync", "--dry-run", "--delete-after", "-avzz", args.mirror_dir, rsync_base)
180     subprocess.run(rsync_mirror_cmd, env=dict(os.environ, RSYNC_PASSWORD=args.password))
181
182     print("Rsync upload simulated, please check every thing is OK (hit [Enter] to continue, [Ctrl-C] to exit):")
183     sys.stdin.read(1)
184
185     rsync_mirror_cmd = ("rsync", "--delete-after", "-avzz", args.mirror_dir, rsync_base)
186     subprocess.run(rsync_mirror_cmd, env=dict(os.environ, RSYNC_PASSWORD=args.password))
187
188
189 if __name__ == "__main__":
190     main()