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