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
This commit is contained in:
Campbell Barton
2025-05-27 20:18:36 +10:00
parent c4ba04428c
commit 3de916ca25
11 changed files with 230 additions and 1 deletions

View File

@@ -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)

View File

@@ -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:

View File

@@ -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);

View File

@@ -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:
*

View File

@@ -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 {

View File

@@ -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),

View File

@@ -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;

View File

@@ -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<DeprecatedRNA *>(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: {

View File

@@ -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. */

View File

@@ -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);

View File

@@ -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<char *>(data), value, error_prefix) == -1) {