Extensions: support system repositories & BLENDER_SYSTEM_EXTENSIONS

Support for "System" extensions as an alternative to the current
"User" extensions repository.

The purpose of this change is to support bundling extensions for
offline work or in environments where users setting up thier own
extensions isn't desirable, see #122512.

Details:

The default "System" repository on Linux will for example use:
- `/usr/share/blender/4.2/extensions/{system}` For system installs.
- `./4.2/extensions/{system}` For portable installs.

- Blender's default startup now has a "System" repository
  which users or administrators may populate.

- Repositories can select between User/System paths,
  setting a custom path overrides overrides this setting.

- Add "BLENDER_SYSTEM_EXTENSIONS" (matching "BLENDER_LOCAL_EXTENSIONS").

Ref !122832
This commit is contained in:
Campbell Barton 2024-06-07 11:36:20 +10:00
parent f423ec8848
commit dc9430c480
13 changed files with 115 additions and 7 deletions

@ -0,0 +1,7 @@
System Extensions
Extensions extracted into this directory will be available from the
default "System" repository.
This allows extensions to be bundled with Blender outside of
user repositories.

@ -2211,6 +2211,10 @@ class USERPREF_PT_extensions_repos(Panel):
# valid UTF-8 which will raise a Python exception when passed in as text. # valid UTF-8 which will raise a Python exception when passed in as text.
sub.prop(active_repo, "directory", text="") sub.prop(active_repo, "directory", text="")
row = layout_panel.row()
row.active = not use_custom_directory
row.prop(active_repo, "source")
if active_repo.use_remote_url: if active_repo.use_remote_url:
row = layout_panel.row(align=True, heading="Authentication") row = layout_panel.row(align=True, heading="Authentication")
row.prop(active_repo, "use_access_token") row.prop(active_repo, "use_access_token")

@ -168,7 +168,8 @@ enum {
/* system */ /* system */
BLENDER_SYSTEM_DATAFILES = 52, BLENDER_SYSTEM_DATAFILES = 52,
BLENDER_SYSTEM_SCRIPTS = 53, BLENDER_SYSTEM_SCRIPTS = 53,
BLENDER_SYSTEM_PYTHON = 54, BLENDER_SYSTEM_EXTENSIONS = 54,
BLENDER_SYSTEM_PYTHON = 55,
}; };
/** For #BKE_appdir_folder_id_version only. */ /** For #BKE_appdir_folder_id_version only. */

@ -29,7 +29,7 @@ extern "C" {
/* Blender file format version. */ /* Blender file format version. */
#define BLENDER_FILE_VERSION BLENDER_VERSION #define BLENDER_FILE_VERSION BLENDER_VERSION
#define BLENDER_FILE_SUBVERSION 55 #define BLENDER_FILE_SUBVERSION 56
/* Minimum Blender version that supports reading file written with the current /* Minimum Blender version that supports reading file written with the current
* version. Older Blender versions will test this and cancel loading the file, showing a warning to * version. Older Blender versions will test this and cancel loading the file, showing a warning to

@ -90,6 +90,7 @@ bUserExtensionRepo *BKE_preferences_extension_repo_add(UserDef *userdef,
void BKE_preferences_extension_repo_remove(UserDef *userdef, bUserExtensionRepo *repo); void BKE_preferences_extension_repo_remove(UserDef *userdef, bUserExtensionRepo *repo);
bUserExtensionRepo *BKE_preferences_extension_repo_add_default_remote(UserDef *userdef); bUserExtensionRepo *BKE_preferences_extension_repo_add_default_remote(UserDef *userdef);
bUserExtensionRepo *BKE_preferences_extension_repo_add_default_user(UserDef *userdef); bUserExtensionRepo *BKE_preferences_extension_repo_add_default_user(UserDef *userdef);
bUserExtensionRepo *BKE_preferences_extension_repo_add_default_system(UserDef *userdef);
/** Create all default repositories, only use when repositories are empty. */ /** Create all default repositories, only use when repositories are empty. */
void BKE_preferences_extension_repo_add_defaults_all(UserDef *userdef); void BKE_preferences_extension_repo_add_defaults_all(UserDef *userdef);

@ -656,6 +656,18 @@ bool BKE_appdir_folder_id_ex(const int folder_id,
} }
return false; return false;
case BLENDER_SYSTEM_EXTENSIONS:
if (get_path_environment(path, path_maxncpy, subfolder, "BLENDER_SYSTEM_EXTENSIONS")) {
break;
}
if (get_path_system(path, path_maxncpy, "extensions", subfolder)) {
break;
}
if (get_path_local(path, path_maxncpy, "extensions", subfolder)) {
break;
}
return false;
case BLENDER_SYSTEM_PYTHON: case BLENDER_SYSTEM_PYTHON:
if (get_path_environment(path, path_maxncpy, subfolder, "BLENDER_SYSTEM_PYTHON")) { if (get_path_environment(path, path_maxncpy, subfolder, "BLENDER_SYSTEM_PYTHON")) {
break; break;

@ -216,11 +216,19 @@ bUserExtensionRepo *BKE_preferences_extension_repo_add_default_user(UserDef *use
return repo; return repo;
} }
bUserExtensionRepo *BKE_preferences_extension_repo_add_default_system(UserDef *userdef)
{
bUserExtensionRepo *repo = BKE_preferences_extension_repo_add(userdef, "System", "system", "");
repo->source = USER_EXTENSION_REPO_SOURCE_SYSTEM;
return repo;
}
void BKE_preferences_extension_repo_add_defaults_all(UserDef *userdef) void BKE_preferences_extension_repo_add_defaults_all(UserDef *userdef)
{ {
BLI_assert(BLI_listbase_is_empty(&userdef->extension_repos)); BLI_assert(BLI_listbase_is_empty(&userdef->extension_repos));
BKE_preferences_extension_repo_add_default_remote(userdef); BKE_preferences_extension_repo_add_default_remote(userdef);
BKE_preferences_extension_repo_add_default_user(userdef); BKE_preferences_extension_repo_add_default_user(userdef);
BKE_preferences_extension_repo_add_default_system(userdef);
} }
void BKE_preferences_extension_repo_name_set(UserDef *userdef, void BKE_preferences_extension_repo_name_set(UserDef *userdef,
@ -269,8 +277,19 @@ size_t BKE_preferences_extension_repo_dirpath_get(const bUserExtensionRepo *repo
return BLI_strncpy_rlen(dirpath, repo->custom_dirpath, dirpath_maxncpy); return BLI_strncpy_rlen(dirpath, repo->custom_dirpath, dirpath_maxncpy);
} }
std::optional<std::string> path = BKE_appdir_folder_id_user_notest(BLENDER_USER_EXTENSIONS, std::optional<std::string> path = std::nullopt;
nullptr);
switch (repo->source) {
case USER_EXTENSION_REPO_SOURCE_SYSTEM: {
path = BKE_appdir_folder_id(BLENDER_SYSTEM_EXTENSIONS, nullptr);
break;
}
default: { /* #USER_EXTENSION_REPO_SOURCE_USER. */
path = BKE_appdir_folder_id_user_notest(BLENDER_USER_EXTENSIONS, nullptr);
break;
}
}
/* Highly unlikely to fail as the directory doesn't have to exist. */ /* Highly unlikely to fail as the directory doesn't have to exist. */
if (!path) { if (!path) {
dirpath[0] = '\0'; dirpath[0] = '\0';

@ -973,6 +973,10 @@ void blo_do_versions_userdef(UserDef *userdef)
userdef->sequencer_editor_flag |= USER_SEQ_ED_SIMPLE_TWEAKING; userdef->sequencer_editor_flag |= USER_SEQ_ED_SIMPLE_TWEAKING;
} }
if (!USER_VERSION_ATLEAST(402, 56)) {
BKE_preferences_extension_repo_add_default_system(userdef);
}
/** /**
* Always bump subversion in BKE_blender_version.h when adding versioning * Always bump subversion in BKE_blender_version.h when adding versioning
* code here, and wrap it inside a USER_VERSION_ATLEAST check. * code here, and wrap it inside a USER_VERSION_ATLEAST check.

@ -649,8 +649,11 @@ typedef struct bUserExtensionRepo {
char custom_dirpath[1024]; /* FILE_MAX */ char custom_dirpath[1024]; /* FILE_MAX */
char remote_url[1024]; /* FILE_MAX */ char remote_url[1024]; /* FILE_MAX */
int flag; uint8_t flag;
char _pad0[4]; /** The source location when the custom directory isn't used (#eUserExtensionRepo_Source).*/
uint8_t source;
char _pad0[6];
} bUserExtensionRepo; } bUserExtensionRepo;
typedef enum eUserExtensionRepo_Flag { typedef enum eUserExtensionRepo_Flag {
@ -663,6 +666,15 @@ typedef enum eUserExtensionRepo_Flag {
USER_EXTENSION_REPO_FLAG_USE_ACCESS_TOKEN = 1 << 5, USER_EXTENSION_REPO_FLAG_USE_ACCESS_TOKEN = 1 << 5,
} eUserExtensionRepo_Flag; } eUserExtensionRepo_Flag;
/**
* The source to use (User or System), only valid when the
* #USER_EXTENSION_REPO_FLAG_USE_CUSTOM_DIRECTORY flag isn't set.
*/
typedef enum eUserExtensionRepo_Source {
USER_EXTENSION_REPO_SOURCE_USER = 0,
USER_EXTENSION_REPO_SOURCE_SYSTEM = 1,
} eUserExtensionRepo_Source;
typedef struct SolidLight { typedef struct SolidLight {
int flag; int flag;
float smooth; float smooth;

@ -439,6 +439,15 @@ static void rna_userdef_extension_repo_use_remote_url_set(PointerRNA *ptr, bool
ptr, value, USER_EXTENSION_REPO_FLAG_USE_REMOTE_URL); ptr, value, USER_EXTENSION_REPO_FLAG_USE_REMOTE_URL);
} }
static void rna_userdef_extension_repo_source_set(PointerRNA *ptr, int value)
{
Main *bmain = G.main;
bUserExtensionRepo *repo = (bUserExtensionRepo *)ptr->data;
BKE_callback_exec_null(bmain, BKE_CB_EVT_EXTENSION_REPOS_UPDATE_PRE);
repo->source = value;
BKE_callback_exec_null(bmain, BKE_CB_EVT_EXTENSION_REPOS_UPDATE_POST);
}
static void rna_userdef_script_autoexec_update(Main * /*bmain*/, static void rna_userdef_script_autoexec_update(Main * /*bmain*/,
Scene * /*scene*/, Scene * /*scene*/,
PointerRNA *ptr) PointerRNA *ptr)
@ -6750,6 +6759,20 @@ static void rna_def_userdef_filepaths_extension_repo(BlenderRNA *brna)
StructRNA *srna; StructRNA *srna;
PropertyRNA *prop; PropertyRNA *prop;
static const EnumPropertyItem source_type_items[] = {
{USER_EXTENSION_REPO_SOURCE_USER,
"USER",
0,
"User",
"Repository managed by the user, stored in user directories"},
{USER_EXTENSION_REPO_SOURCE_SYSTEM,
"SYSTEM",
0,
"System",
"Read-only repository provided by the system"},
{0, nullptr, 0, nullptr, nullptr},
};
srna = RNA_def_struct(brna, "UserExtensionRepo", nullptr); srna = RNA_def_struct(brna, "UserExtensionRepo", nullptr);
RNA_def_struct_sdna(srna, "bUserExtensionRepo"); RNA_def_struct_sdna(srna, "bUserExtensionRepo");
RNA_def_struct_ui_text( RNA_def_struct_ui_text(
@ -6801,6 +6824,14 @@ static void rna_def_userdef_filepaths_extension_repo(BlenderRNA *brna)
"rna_userdef_extension_repo_access_token_length", "rna_userdef_extension_repo_access_token_length",
"rna_userdef_extension_repo_access_token_set"); "rna_userdef_extension_repo_access_token_set");
prop = RNA_def_property(srna, "source", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, source_type_items);
RNA_def_property_enum_funcs(prop, nullptr, "rna_userdef_extension_repo_source_set", nullptr);
RNA_def_property_ui_text(
prop,
"Source",
"Select if the repository is in a user managed or system provided directory");
/* NOTE(@ideasman42): this is intended to be used by a package manger component /* NOTE(@ideasman42): this is intended to be used by a package manger component
* which is not yet integrated. */ * which is not yet integrated. */
prop = RNA_def_property(srna, "use_cache", PROP_BOOLEAN, PROP_NONE); prop = RNA_def_property(srna, "use_cache", PROP_BOOLEAN, PROP_NONE);

@ -266,7 +266,7 @@ PyDoc_STRVAR(
"\n" "\n"
" Return a system resource path.\n" " Return a system resource path.\n"
"\n" "\n"
" :arg type: string in ['DATAFILES', 'SCRIPTS', 'PYTHON'].\n" " :arg type: string in ['DATAFILES', 'SCRIPTS', 'EXTENSIONS', 'PYTHON'].\n"
" :type type: string\n" " :type type: string\n"
" :arg path: Optional subdirectory.\n" " :arg path: Optional subdirectory.\n"
" :type path: string or bytes\n"); " :type path: string or bytes\n");
@ -275,6 +275,7 @@ static PyObject *bpy_system_resource(PyObject * /*self*/, PyObject *args, PyObje
const PyC_StringEnumItems type_items[] = { const PyC_StringEnumItems type_items[] = {
{BLENDER_SYSTEM_DATAFILES, "DATAFILES"}, {BLENDER_SYSTEM_DATAFILES, "DATAFILES"},
{BLENDER_SYSTEM_SCRIPTS, "SCRIPTS"}, {BLENDER_SYSTEM_SCRIPTS, "SCRIPTS"},
{BLENDER_SYSTEM_EXTENSIONS, "EXTENSIONS"},
{BLENDER_SYSTEM_PYTHON, "PYTHON"}, {BLENDER_SYSTEM_PYTHON, "PYTHON"},
{0, nullptr}, {0, nullptr},
}; };

@ -1659,6 +1659,13 @@ if(DEFINED TARGETDIR_TEXT)
) )
endif() endif()
# Create a system extensions directory (users or administrators may populate this).
# This only contains a `readme.txt` explaining it's purpose.
install(
DIRECTORY ${CMAKE_SOURCE_DIR}/release/extensions
DESTINATION ${TARGETDIR_VER}
)
# Install more files specified elsewhere. # Install more files specified elsewhere.
delayed_do_install(${TARGETDIR_VER}) delayed_do_install(${TARGETDIR_VER})

@ -853,6 +853,7 @@ static void print_help(bArgs *ba, bool all)
PRINT("\n"); PRINT("\n");
PRINT(" $BLENDER_SYSTEM_RESOURCES Replace default directory of all bundled resource files.\n"); PRINT(" $BLENDER_SYSTEM_RESOURCES Replace default directory of all bundled resource files.\n");
PRINT(" $BLENDER_SYSTEM_SCRIPTS Directory to add more bundled scripts.\n"); PRINT(" $BLENDER_SYSTEM_SCRIPTS Directory to add more bundled scripts.\n");
PRINT(" $BLENDER_SYSTEM_EXTENSIONS Directory for system extensions repository.\n");
PRINT(" $BLENDER_SYSTEM_DATAFILES Directory to replace bundled datafiles.\n"); PRINT(" $BLENDER_SYSTEM_DATAFILES Directory to replace bundled datafiles.\n");
PRINT(" $BLENDER_SYSTEM_PYTHON Directory to replace bundled Python libraries.\n"); PRINT(" $BLENDER_SYSTEM_PYTHON Directory to replace bundled Python libraries.\n");
@ -1533,6 +1534,9 @@ static const char arg_handle_env_system_set_doc_scripts[] =
static const char arg_handle_env_system_set_doc_python[] = static const char arg_handle_env_system_set_doc_python[] =
"\n\t" "\n\t"
"Set the " STRINGIFY_ARG(BLENDER_SYSTEM_PYTHON) " environment variable."; "Set the " STRINGIFY_ARG(BLENDER_SYSTEM_PYTHON) " environment variable.";
static const char arg_handle_env_system_set_doc_extensions[] =
"\n\t"
"Set the " STRINGIFY_ARG(BLENDER_SYSTEM_EXTENSIONS) " environment variable.";
static int arg_handle_env_system_set(int argc, const char **argv, void * /*data*/) static int arg_handle_env_system_set(int argc, const char **argv, void * /*data*/)
{ {
@ -2542,6 +2546,11 @@ void main_args_setup(bContext *C, bArgs *ba, bool all)
ba, nullptr, "--env-system-scripts", CB_EX(arg_handle_env_system_set, scripts), nullptr); ba, nullptr, "--env-system-scripts", CB_EX(arg_handle_env_system_set, scripts), nullptr);
BLI_args_add( BLI_args_add(
ba, nullptr, "--env-system-python", CB_EX(arg_handle_env_system_set, python), nullptr); ba, nullptr, "--env-system-python", CB_EX(arg_handle_env_system_set, python), nullptr);
BLI_args_add(ba,
nullptr,
"--env-system-extensions",
CB_EX(arg_handle_env_system_set, extensions),
nullptr);
BLI_args_add(ba, "-t", "--threads", CB(arg_handle_threads_set), nullptr); BLI_args_add(ba, "-t", "--threads", CB(arg_handle_threads_set), nullptr);