From 3de916ca25443335c224ffbd46c707317810f7e8 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Tue, 27 May 2025 20:18:36 +1000 Subject: [PATCH] RNA: support for marking properties as deprecated Deprecation meta-data support for RNA properties. - Properties may have a deprecated note and version. - Warnings shown when these are accessed from Python. - A note is included in the generated documentation. Support for marking functions as deprecated can be added in the future. Ref !139487 --- doc/python_api/sphinx_doc_gen.py | 26 +++++++ scripts/modules/rna_info.py | 10 +++ source/blender/makesrna/RNA_access.hh | 2 + source/blender/makesrna/RNA_define.hh | 6 ++ source/blender/makesrna/RNA_types.hh | 19 +++++ source/blender/makesrna/intern/makesrna.cc | 21 +++++- source/blender/makesrna/intern/rna_access.cc | 5 ++ source/blender/makesrna/intern/rna_define.cc | 47 +++++++++++++ .../makesrna/intern/rna_internal_types.hh | 3 + source/blender/makesrna/intern/rna_rna.cc | 70 +++++++++++++++++++ source/blender/python/intern/bpy_rna.cc | 22 ++++++ 11 files changed, 230 insertions(+), 1 deletion(-) diff --git a/doc/python_api/sphinx_doc_gen.py b/doc/python_api/sphinx_doc_gen.py index f29d87255ef..3840061a9a9 100644 --- a/doc/python_api/sphinx_doc_gen.py +++ b/doc/python_api/sphinx_doc_gen.py @@ -1338,6 +1338,8 @@ def pycontext2sphinx(basepath): fw(".. data:: {:s}\n\n".format(prop.identifier)) if prop.description: fw(" {:s}\n\n".format(prop.description)) + if (deprecated := prop.deprecated) is not None: + fw(pyrna_deprecated_directive(" ", deprecated)) # Special exception, can't use generic code here for enums. if prop.type == "enum": @@ -1449,6 +1451,23 @@ def pyrna_enum2sphinx(prop, use_empty_descriptions=False): return "" +def pyrna_deprecated_directive(ident, deprecated): + note, version, removal_version = deprecated + + # Show a short 2 number version where possible to reduce noise. + version_str = "{:d}.{:d}.{:d}".format(*version).removesuffix(".0") + removal_version_str = "{:d}.{:d}.{:d}".format(*removal_version).removesuffix(".0") + + return ( + "{:s}.. deprecated:: {:s} removal planned in version {:s}\n" + "\n" + "{:s} {:s}\n" + ).format( + ident, version_str, removal_version_str, + ident, note, + ) + + def pyrna2sphinx(basepath): """ ``bpy.types`` and ``bpy.ops``. @@ -1640,6 +1659,9 @@ def pyrna2sphinx(basepath): if prop.description: write_indented_lines(" ", fw, prop.description, False) fw("\n") + if (deprecated := prop.deprecated) is not None: + fw(pyrna_deprecated_directive(" ", deprecated)) + fw("\n") # Special exception, can't use generic code here for enums. if prop.type == "enum": @@ -1719,6 +1741,10 @@ def pyrna2sphinx(basepath): prop.identifier, ", ".join((val for val in (descr, type_descr) if val)) )) + if (deprecated := prop.deprecated) is not None: + fw(pyrna_deprecated_directive(" ", deprecated)) + fw("\n") + fw(" :rtype: ({:s})\n".format(", ".join(type_descrs))) write_example_ref(" ", fw, struct_module_name + "." + struct_id + "." + func.identifier) diff --git a/scripts/modules/rna_info.py b/scripts/modules/rna_info.py index e71f8f22ee5..79f948e2995 100644 --- a/scripts/modules/rna_info.py +++ b/scripts/modules/rna_info.py @@ -279,6 +279,7 @@ class InfoPropertyRNA: "is_never_none", "is_path_supports_blend_relative", "is_path_supports_templates", + "deprecated", ) global_lookup = {} @@ -306,6 +307,15 @@ class InfoPropertyRNA: self.is_path_supports_blend_relative = rna_prop.is_path_supports_blend_relative self.is_path_supports_templates = rna_prop.is_path_supports_templates + if rna_prop.is_deprecated: + self.deprecated = ( + rna_prop.deprecated_note, + tuple(rna_prop.deprecated_version), + tuple(rna_prop.deprecated_removal_version), + ) + else: + self.deprecated = None + self.type = rna_prop.type.lower() fixed_type = getattr(rna_prop, "fixed_type", "") if fixed_type: diff --git a/source/blender/makesrna/RNA_access.hh b/source/blender/makesrna/RNA_access.hh index 93dd180a313..64e3ddb1e7e 100644 --- a/source/blender/makesrna/RNA_access.hh +++ b/source/blender/makesrna/RNA_access.hh @@ -239,6 +239,8 @@ bool RNA_struct_bl_idname_ok_or_report(ReportList *reports, const char *RNA_property_identifier(const PropertyRNA *prop); const char *RNA_property_description(PropertyRNA *prop); +const DeprecatedRNA *RNA_property_deprecated(const PropertyRNA *prop); + PropertyType RNA_property_type(PropertyRNA *prop); PropertySubType RNA_property_subtype(PropertyRNA *prop); PropertyUnit RNA_property_unit(PropertyRNA *prop); diff --git a/source/blender/makesrna/RNA_define.hh b/source/blender/makesrna/RNA_define.hh index 8ea304a8406..9a675158684 100644 --- a/source/blender/makesrna/RNA_define.hh +++ b/source/blender/makesrna/RNA_define.hh @@ -450,6 +450,12 @@ void RNA_def_property_enum_default(PropertyRNA *prop, int value); void RNA_def_property_string_default(PropertyRNA *prop, const char *value); void RNA_def_property_ui_text(PropertyRNA *prop, const char *name, const char *description); + +void RNA_def_property_deprecated(PropertyRNA *prop, + const char *note, + short version, + short removal_version); + /** * The values hare are a little confusing: * diff --git a/source/blender/makesrna/RNA_types.hh b/source/blender/makesrna/RNA_types.hh index 6fa28387fa5..cba4a1d0db0 100644 --- a/source/blender/makesrna/RNA_types.hh +++ b/source/blender/makesrna/RNA_types.hh @@ -938,6 +938,25 @@ struct ExtensionRNA { StructFreeFunc free; }; +/** + * Information about deprecated properties. + * + * Used by the API documentation and Python API to print warnings + * when accessing a deprecated property. + */ +struct DeprecatedRNA { + /** Single line deprecation message, suggest alternatives where possible. */ + const char *note; + /** The released version this was deprecated. */ + short version; + /** + * The version this will be removed. + * The value represents major, minor versions (sub-version isn't supported). + * Compatible with #Main::versionfile (e.g. `502` for `v5.2`). + */ + short removal_version; +}; + /* Primitive types. */ struct PrimitiveStringRNA { diff --git a/source/blender/makesrna/intern/makesrna.cc b/source/blender/makesrna/intern/makesrna.cc index 4dd23b4d1c4..719898b821b 100644 --- a/source/blender/makesrna/intern/makesrna.cc +++ b/source/blender/makesrna/intern/makesrna.cc @@ -4130,6 +4130,17 @@ static void rna_generate_property(FILE *f, StructRNA *srna, const char *nest, Pr freenest = true; } + if (prop->deprecated) { + fprintf(f, + "static const DeprecatedRNA rna_%s%s_%s_deprecated = {\n\t", + srna->identifier, + strnest, + prop->identifier); + rna_print_c_string(f, prop->deprecated->note); + fprintf(f, ",\n\t%d, %d,\n", prop->deprecated->version, prop->deprecated->removal_version); + fprintf(f, "};\n\n"); + } + switch (prop->type) { case PROP_ENUM: { EnumPropertyRNA *eprop = (EnumPropertyRNA *)prop; @@ -4369,7 +4380,15 @@ static void rna_generate_property(FILE *f, StructRNA *srna, const char *nest, Pr fprintf(f, ",\n\t"); fprintf(f, "%d, ", prop->icon); rna_print_c_string(f, prop->translation_context); - fprintf(f, ",\n"); + fprintf(f, ",\n\t"); + + if (prop->deprecated) { + fprintf(f, "&rna_%s%s_%s_deprecated,", srna->identifier, strnest, prop->identifier); + } + else { + fprintf(f, "nullptr,\n"); + } + fprintf(f, "\t%s, PropertySubType(int(%s) | int(%s)), %s, %u, {%u, %u, %u}, %u,\n", RNA_property_typename(prop->type), diff --git a/source/blender/makesrna/intern/rna_access.cc b/source/blender/makesrna/intern/rna_access.cc index 681cb413c04..b23a183ccac 100644 --- a/source/blender/makesrna/intern/rna_access.cc +++ b/source/blender/makesrna/intern/rna_access.cc @@ -1237,6 +1237,11 @@ const char *RNA_property_description(PropertyRNA *prop) return TIP_(rna_ensure_property_description(prop)); } +const DeprecatedRNA *RNA_property_deprecated(const PropertyRNA *prop) +{ + return prop->deprecated; +} + PropertyType RNA_property_type(PropertyRNA *prop) { return rna_ensure_property(prop)->type; diff --git a/source/blender/makesrna/intern/rna_define.cc b/source/blender/makesrna/intern/rna_define.cc index 8f0e44f1bad..69e1251fef3 100644 --- a/source/blender/makesrna/intern/rna_define.cc +++ b/source/blender/makesrna/intern/rna_define.cc @@ -28,6 +28,8 @@ #include "BLI_math_bits.h" #include "BLI_string.h" +#include "BKE_blender_version.h" /* For #BLENDER_VERSION deprecation warnings. */ + #include "BLT_translation.hh" #include "UI_interface.hh" /* For things like UI_PRECISION_FLOAT_MAX... */ @@ -1477,6 +1479,7 @@ PropertyRNA *RNA_def_property(StructOrFunctionRNA *cont_, prop->subtype = PropertySubType(subtype); prop->name = identifier; prop->description = ""; + prop->deprecated = nullptr; prop->translation_context = BLT_I18NCONTEXT_DEFAULT_BPYRNA; /* a priori not raw editable */ prop->rawtype = RawPropertyType(-1); @@ -1749,6 +1752,47 @@ void RNA_def_property_ui_text(PropertyRNA *prop, const char *name, const char *d prop->description = description; } +void RNA_def_property_deprecated(PropertyRNA *prop, + const char *note, + const short version, + const short removal_version) +{ + if (!DefRNA.preprocess) { + fprintf(stderr, "%s: \"%s\": only during preprocessing.", __func__, prop->identifier); + return; + } + +#ifndef RNA_RUNTIME + StructRNA *srna = DefRNA.laststruct; + BLI_assert(prop->deprecated == nullptr); + BLI_assert(note != nullptr); + BLI_assert(version > 0); + BLI_assert(removal_version > version); + + /* This message is to alert developers of deprecation + * without breaking the build after a version bump. */ + if (removal_version <= BLENDER_VERSION) { + fprintf(stderr, + "\nWARNING: \"%s.%s\" deprecation starting at %d.%d marks this property to be removed " + "in the current Blender version!\n\n", + srna->identifier, + prop->identifier, + version / 100, + version % 100); + } + + DeprecatedRNA *deprecated = static_cast(rna_calloc(sizeof(DeprecatedRNA))); + deprecated->note = note; + deprecated->version = version; + deprecated->removal_version = removal_version; + prop->deprecated = deprecated; +#else + (void)note; + (void)version; + (void)removal_version; +#endif +} + void RNA_def_property_ui_icon(PropertyRNA *prop, int icon, int consecutive) { prop->icon = icon; @@ -5082,6 +5126,9 @@ void RNA_def_property_free_pointers(PropertyRNA *prop) if (prop->py_data) { MEM_freeN(prop->py_data); } + if (prop->deprecated) { + MEM_freeN(prop->deprecated); + } switch (prop->type) { case PROP_BOOLEAN: { diff --git a/source/blender/makesrna/intern/rna_internal_types.hh b/source/blender/makesrna/intern/rna_internal_types.hh index 8427fb9b2cf..162841bd362 100644 --- a/source/blender/makesrna/intern/rna_internal_types.hh +++ b/source/blender/makesrna/intern/rna_internal_types.hh @@ -368,6 +368,9 @@ struct PropertyRNA { /** Context for translation. */ const char *translation_context; + /** Optional deprecation information. */ + const DeprecatedRNA *deprecated; + /** Property type as it appears to the outside. */ PropertyType type; /** Subtype, 'interpretation' of the property. */ diff --git a/source/blender/makesrna/intern/rna_rna.cc b/source/blender/makesrna/intern/rna_rna.cc index f706dbb4fa9..9b324063def 100644 --- a/source/blender/makesrna/intern/rna_rna.cc +++ b/source/blender/makesrna/intern/rna_rna.cc @@ -886,6 +886,50 @@ static bool rna_Property_is_runtime_get(PointerRNA *ptr) return RNA_property_is_runtime(prop); } +static bool rna_Property_is_deprecated_get(PointerRNA *ptr) +{ + const PropertyRNA *prop = (const PropertyRNA *)ptr->data; + return RNA_property_deprecated(prop) != nullptr; +} + +static int rna_Property_deprecated_note_length(PointerRNA *ptr) +{ + const PropertyRNA *prop = (const PropertyRNA *)ptr->data; + if (const DeprecatedRNA *deprecated = RNA_property_deprecated(prop)) { + return strlen(deprecated->note); + } + return 0; +} + +static void rna_Property_deprecated_note_get(PointerRNA *ptr, char *value) +{ + const PropertyRNA *prop = (const PropertyRNA *)ptr->data; + if (const DeprecatedRNA *deprecated = RNA_property_deprecated(prop)) { + strcpy(value, deprecated->note); + } +} + +static void rna_Property_deprecated_version_get(PointerRNA *ptr, int *value) +{ + const PropertyRNA *prop = (const PropertyRNA *)ptr->data; + short version = 0; + if (const DeprecatedRNA *deprecated = RNA_property_deprecated(prop)) { + version = deprecated->version; + } + ARRAY_SET_ITEMS(value, version / 100, version % 100, 0); +} + +static void rna_Property_deprecated_removal_version_get(PointerRNA *ptr, int *value) +{ + const PropertyRNA *prop = (const PropertyRNA *)ptr->data; + short version = 0; + if (const DeprecatedRNA *deprecated = RNA_property_deprecated(prop)) { + version = deprecated->removal_version; + } + + ARRAY_SET_ITEMS(value, version / 100, version % 100, 0); +} + static bool rna_BoolProperty_default_get(PointerRNA *ptr) { PropertyRNA *prop = (PropertyRNA *)ptr->data; @@ -3338,6 +3382,32 @@ static void rna_def_property(BlenderRNA *brna) "Property is a path which supports the \"{variable_name}\" variable expression syntax, " "which substitutes the value of the referenced variable in place of the expression"); + prop = RNA_def_property(srna, "is_deprecated", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_boolean_funcs(prop, "rna_Property_is_deprecated_get", nullptr); + RNA_def_property_ui_text(prop, "Deprecated", "The property is deprecated"); + + prop = RNA_def_property(srna, "deprecated_note", PROP_STRING, PROP_NONE); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_string_funcs( + prop, "rna_Property_deprecated_note_get", "rna_Property_deprecated_note_length", nullptr); + RNA_def_property_ui_text(prop, "Deprecated Note", "A note regarding deprecation"); + + prop = RNA_def_property(srna, "deprecated_version", PROP_INT, PROP_NONE); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + /* Use 3 values to match `bpy.app.version`. */ + RNA_def_property_array(prop, 3); + RNA_def_property_ui_text(prop, "Deprecated Version", "The Blender version this was deprecated"); + RNA_def_property_int_funcs(prop, "rna_Property_deprecated_version_get", nullptr, nullptr); + + prop = RNA_def_property(srna, "deprecated_removal_version", PROP_INT, PROP_NONE); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_array(prop, 3); + RNA_def_property_ui_text( + prop, "Deprecated Removal Version", "The Blender version this is expected to be removed"); + RNA_def_property_int_funcs( + prop, "rna_Property_deprecated_removal_version_get", nullptr, nullptr); + prop = RNA_def_property(srna, "tags", PROP_ENUM, PROP_NONE); RNA_def_property_clear_flag(prop, PROP_EDITABLE); RNA_def_property_enum_items(prop, dummy_prop_tags); diff --git a/source/blender/python/intern/bpy_rna.cc b/source/blender/python/intern/bpy_rna.cc index 88bc8b5ac35..8a7a0e87e43 100644 --- a/source/blender/python/intern/bpy_rna.cc +++ b/source/blender/python/intern/bpy_rna.cc @@ -183,6 +183,20 @@ void pyrna_invalidate(BPy_DummyPointerRNA *self) self->ptr->invalidate(); } +static void pyrna_prop_warn_deprecated(const PointerRNA *ptr, + const PropertyRNA *prop, + const DeprecatedRNA *deprecated) +{ + PyErr_WarnFormat(PyExc_DeprecationWarning, + 1, + "'%s.%s' is expected to be removed in Blender %d.%d", + RNA_struct_identifier(ptr->type), + RNA_property_identifier(prop), + deprecated->removal_version / 100, + deprecated->removal_version % 100, + deprecated->note); +} + #ifdef USE_PYRNA_INVALIDATE_GC # define FROM_GC(g) ((PyObject *)(((PyGC_Head *)g) + 1)) @@ -1436,6 +1450,10 @@ PyObject *pyrna_prop_to_py(PointerRNA *ptr, PropertyRNA *prop) PyObject *ret; const int type = RNA_property_type(prop); + if (const DeprecatedRNA *deprecated = RNA_property_deprecated(prop)) { + pyrna_prop_warn_deprecated(ptr, prop, deprecated); + } + if (RNA_property_array_check(prop)) { return pyrna_py_from_array(ptr, prop); } @@ -1607,6 +1625,10 @@ static int pyrna_py_to_prop( /* XXX hard limits should be checked here. */ const int type = RNA_property_type(prop); + if (const DeprecatedRNA *deprecated = RNA_property_deprecated(prop)) { + pyrna_prop_warn_deprecated(ptr, prop, deprecated); + } + if (RNA_property_array_check(prop)) { /* Done getting the length. */ if (pyrna_py_to_array(ptr, prop, static_cast(data), value, error_prefix) == -1) {