forked from bartvdbraak/blender
e5b98f7a2f
updating the doc. I'm also including the latest API dump with it. This helps to keep track of the revisions to update and the next person updating doesnt need to build an old Blender for that (as I just had to). I updated the sphinx_changelog_gen.py with this info as well. I'll update changelog.rst manually next. I'm not so sure how to indicate that the properties were replaced instead of only removed.
368 lines
13 KiB
Python
368 lines
13 KiB
Python
# ##### BEGIN GPL LICENSE BLOCK #####
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software Foundation,
|
|
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
#
|
|
# ##### END GPL LICENSE BLOCK #####
|
|
|
|
# <pep8 compliant>
|
|
|
|
"""
|
|
Dump the python API into a text file so we can generate changelogs.
|
|
|
|
output from this tool should be added into "doc/python_api/rst/change_log.rst"
|
|
|
|
# dump api blender_version.py in CWD
|
|
blender --background --python doc/python_api/sphinx_changelog_gen.py -- --dump
|
|
|
|
# create changelog
|
|
blender --background --python doc/python_api/sphinx_changelog_gen.py -- \
|
|
--api_from blender_2_56_1.py \
|
|
--api_to blender_2_57_0.py \
|
|
--api_out changes.rst
|
|
|
|
|
|
# Api comparison can also run without blender
|
|
python doc/python_api/sphinx_changelog_gen.py \
|
|
--api_from blender_api_2_56_6.py \
|
|
--api_to blender_api_2_57.py \
|
|
--api_out changes.rst
|
|
|
|
# Save the latest API dump in this folder, renaming it with its revision.
|
|
# This way the next person updating it doesn't need to build an old Blender only for that
|
|
|
|
"""
|
|
|
|
# format
|
|
'''
|
|
{"module.name":
|
|
{"parent.class":
|
|
{"basic_type", "member_name": ("Name", type, range, length, default, descr, f_args, f_arg_types, f_ret_types)}, ...
|
|
}, ...
|
|
}
|
|
'''
|
|
|
|
api_names = "basic_type" "name", "type", "range", "length", "default", "descr", "f_args", "f_arg_types", "f_ret_types"
|
|
|
|
API_BASIC_TYPE = 0
|
|
API_F_ARGS = 7
|
|
|
|
|
|
def api_dunp_fname():
|
|
import bpy
|
|
return "blender_api_%s.py" % "_".join([str(i) for i in bpy.app.version])
|
|
|
|
|
|
def api_dump():
|
|
dump = {}
|
|
dump_module = dump["bpy.types"] = {}
|
|
|
|
import rna_info
|
|
import inspect
|
|
|
|
struct = rna_info.BuildRNAInfo()[0]
|
|
for struct_id, strict_info in sorted(struct.items()):
|
|
|
|
struct_id_str = strict_info.identifier
|
|
|
|
if rna_info.rna_id_ignore(struct_id_str):
|
|
continue
|
|
|
|
for base in strict_info.get_bases():
|
|
struct_id_str = base.identifier + "." + struct_id_str
|
|
|
|
dump_class = dump_module[struct_id_str] = {}
|
|
|
|
props = [(prop.identifier, prop) for prop in strict_info.properties]
|
|
for prop_id, prop in sorted(props):
|
|
# if prop.type == 'boolean':
|
|
# continue
|
|
prop_type = prop.type
|
|
prop_length = prop.array_length
|
|
prop_range = round(prop.min, 4), round(prop.max, 4)
|
|
prop_default = prop.default
|
|
if type(prop_default) is float:
|
|
prop_default = round(prop_default, 4)
|
|
|
|
if prop_range[0] == -1 and prop_range[1] == -1:
|
|
prop_range = None
|
|
|
|
dump_class[prop_id] = (
|
|
"prop_rna", # basic_type
|
|
prop.name, # name
|
|
prop_type, # type
|
|
prop_range, # range
|
|
prop_length, # length
|
|
prop.default, # default
|
|
prop.description, # descr
|
|
Ellipsis, # f_args
|
|
Ellipsis, # f_arg_types
|
|
Ellipsis, # f_ret_types
|
|
)
|
|
del props
|
|
|
|
# python props, tricky since we dont know much about them.
|
|
for prop_id, attr in strict_info.get_py_properties():
|
|
|
|
dump_class[prop_id] = (
|
|
"prop_py", # basic_type
|
|
Ellipsis, # name
|
|
Ellipsis, # type
|
|
Ellipsis, # range
|
|
Ellipsis, # length
|
|
Ellipsis, # default
|
|
attr.__doc__, # descr
|
|
Ellipsis, # f_args
|
|
Ellipsis, # f_arg_types
|
|
Ellipsis, # f_ret_types
|
|
)
|
|
|
|
# kludge func -> props
|
|
funcs = [(func.identifier, func) for func in strict_info.functions]
|
|
for func_id, func in funcs:
|
|
|
|
func_ret_types = tuple([prop.type for prop in func.return_values])
|
|
func_args_ids = tuple([prop.identifier for prop in func.args])
|
|
func_args_type = tuple([prop.type for prop in func.args])
|
|
|
|
dump_class[func_id] = (
|
|
"func_rna", # basic_type
|
|
Ellipsis, # name
|
|
Ellipsis, # type
|
|
Ellipsis, # range
|
|
Ellipsis, # length
|
|
Ellipsis, # default
|
|
func.description, # descr
|
|
func_args_ids, # f_args
|
|
func_args_type, # f_arg_types
|
|
func_ret_types, # f_ret_types
|
|
)
|
|
del funcs
|
|
|
|
# kludge func -> props
|
|
funcs = strict_info.get_py_functions()
|
|
for func_id, attr in funcs:
|
|
# arg_str = inspect.formatargspec(*inspect.getargspec(py_func))
|
|
|
|
func_args_ids = tuple(inspect.getargspec(attr).args)
|
|
|
|
dump_class[func_id] = (
|
|
"func_py", # basic_type
|
|
Ellipsis, # name
|
|
Ellipsis, # type
|
|
Ellipsis, # range
|
|
Ellipsis, # length
|
|
Ellipsis, # default
|
|
attr.__doc__, # descr
|
|
func_args_ids, # f_args
|
|
Ellipsis, # f_arg_types
|
|
Ellipsis, # f_ret_types
|
|
)
|
|
del funcs
|
|
|
|
import pprint
|
|
|
|
filename = api_dunp_fname()
|
|
filehandle = open(filename, 'w')
|
|
tot = filehandle.write(pprint.pformat(dump, width=1))
|
|
filehandle.close()
|
|
print("%s, %d bytes written" % (filename, tot))
|
|
|
|
|
|
def compare_props(a, b, fuzz=0.75):
|
|
|
|
# must be same basic_type, function != property
|
|
if a[0] != b[0]:
|
|
return False
|
|
|
|
tot = 0
|
|
totlen = 0
|
|
for i in range(1, len(a)):
|
|
if not (Ellipsis is a[i] is b[i]):
|
|
tot += (a[i] == b[i])
|
|
totlen += 1
|
|
|
|
return ((tot / totlen) >= fuzz)
|
|
|
|
|
|
def api_changelog(api_from, api_to, api_out):
|
|
|
|
file_handle = open(api_from, 'r')
|
|
dict_from = eval(file_handle.read())
|
|
file_handle.close()
|
|
|
|
file_handle = open(api_to, 'r')
|
|
dict_to = eval(file_handle.read())
|
|
file_handle.close()
|
|
|
|
api_changes = []
|
|
|
|
# first work out what moved
|
|
for mod_id, mod_data in dict_to.items():
|
|
mod_data_other = dict_from[mod_id]
|
|
for class_id, class_data in mod_data.items():
|
|
class_data_other = mod_data_other.get(class_id)
|
|
if class_data_other is None:
|
|
# TODO, document new structs
|
|
continue
|
|
|
|
# find the props which are not in either
|
|
set_props_new = set(class_data.keys())
|
|
set_props_other = set(class_data_other.keys())
|
|
set_props_shared = set_props_new & set_props_other
|
|
|
|
props_moved = []
|
|
props_new = []
|
|
props_old = []
|
|
func_args = []
|
|
|
|
set_props_old = set_props_other - set_props_shared
|
|
set_props_new = set_props_new - set_props_shared
|
|
|
|
# first find settings which have been moved old -> new
|
|
for prop_id_old in set_props_old.copy():
|
|
prop_data_other = class_data_other[prop_id_old]
|
|
for prop_id_new in set_props_new.copy():
|
|
prop_data = class_data[prop_id_new]
|
|
if compare_props(prop_data_other, prop_data):
|
|
props_moved.append((prop_id_old, prop_id_new))
|
|
|
|
# remove
|
|
if prop_id_old in set_props_old:
|
|
set_props_old.remove(prop_id_old)
|
|
set_props_new.remove(prop_id_new)
|
|
|
|
# func args
|
|
for prop_id in set_props_shared:
|
|
prop_data = class_data[prop_id]
|
|
prop_data_other = class_data_other[prop_id]
|
|
if prop_data[API_BASIC_TYPE] == prop_data_other[API_BASIC_TYPE]:
|
|
if prop_data[API_BASIC_TYPE].startswith("func"):
|
|
args_new = prop_data[API_F_ARGS]
|
|
args_old = prop_data_other[API_F_ARGS]
|
|
|
|
if args_new != args_old:
|
|
func_args.append((prop_id, args_old, args_new))
|
|
|
|
if props_moved or set_props_new or set_props_old or func_args:
|
|
props_moved.sort()
|
|
props_new[:] = sorted(set_props_new)
|
|
props_old[:] = sorted(set_props_old)
|
|
func_args.sort()
|
|
|
|
api_changes.append((mod_id, class_id, props_moved, props_new, props_old, func_args))
|
|
|
|
# also document function argument changes
|
|
|
|
fout = open(api_out, 'w')
|
|
fw = fout.write
|
|
# print(api_changes)
|
|
|
|
# :class:`bpy_struct.id_data`
|
|
|
|
def write_title(title, title_char):
|
|
fw("%s\n%s\n\n" % (title, title_char * len(title)))
|
|
|
|
for mod_id, class_id, props_moved, props_new, props_old, func_args in api_changes:
|
|
class_name = class_id.split(".")[-1]
|
|
title = mod_id + "." + class_name
|
|
write_title(title, "-")
|
|
|
|
if props_new:
|
|
write_title("Added", "^")
|
|
for prop_id in props_new:
|
|
fw("* :class:`%s.%s.%s`\n" % (mod_id, class_name, prop_id))
|
|
fw("\n")
|
|
|
|
if props_old:
|
|
write_title("Removed", "^")
|
|
for prop_id in props_old:
|
|
fw("* **%s**\n" % prop_id) # cant link to remvoed docs
|
|
fw("\n")
|
|
|
|
if props_moved:
|
|
write_title("Renamed", "^")
|
|
for prop_id_old, prop_id in props_moved:
|
|
fw("* **%s** -> :class:`%s.%s.%s`\n" % (prop_id_old, mod_id, class_name, prop_id))
|
|
fw("\n")
|
|
|
|
if func_args:
|
|
write_title("Function Arguments", "^")
|
|
for func_id, args_old, args_new in func_args:
|
|
args_new = ", ".join(args_new)
|
|
args_old = ", ".join(args_old)
|
|
fw("* :class:`%s.%s.%s` (%s), *was (%s)*\n" % (mod_id, class_name, prop_id, args_new, args_old))
|
|
fw("\n")
|
|
|
|
fout.close()
|
|
|
|
|
|
def main():
|
|
import sys
|
|
import os
|
|
|
|
try:
|
|
import argparse
|
|
except:
|
|
print("Old Blender, just dumping")
|
|
api_dump()
|
|
return
|
|
|
|
argv = sys.argv
|
|
|
|
if "--" not in argv:
|
|
argv = [] # as if no args are passed
|
|
else:
|
|
argv = argv[argv.index("--") + 1:] # get all args after "--"
|
|
|
|
# When --help or no args are given, print this help
|
|
usage_text = "Run blender in background mode with this script: "
|
|
"blender --background --python %s -- [options]" % os.path.basename(__file__)
|
|
|
|
epilog = "Run this before releases"
|
|
|
|
parser = argparse.ArgumentParser(description=usage_text, epilog=epilog)
|
|
|
|
parser.add_argument("--dump", dest="dump", action='store_true',
|
|
help="When set the api will be dumped into blender_version.py")
|
|
|
|
parser.add_argument("--api_from", dest="api_from", metavar='FILE',
|
|
help="File to compare from (previous version)")
|
|
parser.add_argument("--api_to", dest="api_to", metavar='FILE',
|
|
help="File to compare from (current)")
|
|
parser.add_argument("--api_out", dest="api_out", metavar='FILE',
|
|
help="Output sphinx changelog")
|
|
|
|
args = parser.parse_args(argv) # In this example we wont use the args
|
|
|
|
if not argv:
|
|
parser.print_help()
|
|
return
|
|
|
|
if args.dump:
|
|
api_dump()
|
|
else:
|
|
if args.api_from and args.api_to and args.api_out:
|
|
api_changelog(args.api_from, args.api_to, args.api_out)
|
|
else:
|
|
print("Error: --api_from/api_to/api_out args needed")
|
|
parser.print_help()
|
|
return
|
|
|
|
print("batch job finished, exiting")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|