Compare commits
12 Commits
74c282f532
...
4195e9e1a7
Author | SHA1 | Date | |
---|---|---|---|
|
4195e9e1a7 | ||
|
cdb61375fe | ||
|
386d6f098a | ||
|
e52783033d | ||
|
9bbf269fe3 | ||
|
0fbb83ef85 | ||
|
817efe4aef | ||
|
05feac04fa | ||
|
bae1d3311f | ||
|
ac6e216b26 | ||
|
74f8ac15f5 | ||
|
f3630777f6 |
@ -212,18 +212,10 @@ static bool malloc_debug_memset = false;
|
||||
/* implementation */
|
||||
/* --------------------------------------------------------------------- */
|
||||
|
||||
#ifdef __GNUC__
|
||||
__attribute__((format(printf, 1, 2)))
|
||||
#endif
|
||||
static void
|
||||
print_error(const char *str, ...)
|
||||
static void print_error(const char *message, va_list str_format_args)
|
||||
{
|
||||
char buf[1024];
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, str);
|
||||
vsnprintf(buf, sizeof(buf), str, ap);
|
||||
va_end(ap);
|
||||
char buf[512];
|
||||
vsnprintf(buf, sizeof(buf), message, str_format_args);
|
||||
buf[sizeof(buf) - 1] = '\0';
|
||||
|
||||
if (error_callback) {
|
||||
@ -234,6 +226,50 @@ print_error(const char *str, ...)
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef __GNUC__
|
||||
__attribute__((format(printf, 1, 2)))
|
||||
#endif
|
||||
static void
|
||||
print_error(const char *message, ...)
|
||||
{
|
||||
va_list str_format_args;
|
||||
va_start(str_format_args, message);
|
||||
print_error(message, str_format_args);
|
||||
va_end(str_format_args);
|
||||
}
|
||||
|
||||
#ifdef __GNUC__
|
||||
__attribute__((format(printf, 2, 3)))
|
||||
#endif
|
||||
static void
|
||||
report_error_on_address(const void *vmemh, const char *message, ...)
|
||||
{
|
||||
va_list str_format_args;
|
||||
|
||||
va_start(str_format_args, message);
|
||||
print_error(message, str_format_args);
|
||||
va_end(str_format_args);
|
||||
|
||||
if (vmemh == nullptr) {
|
||||
MEM_trigger_error_on_memory_block(nullptr, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
const MemHead *memh = static_cast<const MemHead *>(vmemh);
|
||||
memh--;
|
||||
size_t len = memh->len;
|
||||
|
||||
const void *address = memh;
|
||||
size_t size = len + sizeof(*memh) + sizeof(MemTail);
|
||||
if (UNLIKELY(memh->alignment > 0)) {
|
||||
const MemHeadAligned *memh_aligned = memh;
|
||||
address = MEMHEAD_REAL_PTR(memh_aligned);
|
||||
size = len + sizeof(*memh_aligned) + MEMHEAD_ALIGN_PADDING(memh_aligned->alignment) +
|
||||
sizeof(MemTail);
|
||||
}
|
||||
MEM_trigger_error_on_memory_block(address, size);
|
||||
}
|
||||
|
||||
static pthread_mutex_t thread_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
static void mem_lock_thread()
|
||||
@ -290,12 +326,9 @@ void *MEM_guarded_dupallocN(const void *vmemh)
|
||||
memh--;
|
||||
|
||||
if ((memh->flag & MEMHEAD_FLAG_FROM_CPP_NEW) != 0) {
|
||||
print_error(
|
||||
"Attempt to use C-style MEM_dupallocN on a pointer created with CPP-style MEM_new or "
|
||||
"new\n");
|
||||
#ifdef WITH_ASSERT_ABORT
|
||||
abort();
|
||||
#endif
|
||||
report_error_on_address(vmemh,
|
||||
"Attempt to use C-style MEM_dupallocN on a pointer created with "
|
||||
"CPP-style MEM_new or new\n");
|
||||
}
|
||||
|
||||
#ifndef DEBUG_MEMDUPLINAME
|
||||
@ -353,12 +386,9 @@ void *MEM_guarded_reallocN_id(void *vmemh, size_t len, const char *str)
|
||||
memh--;
|
||||
|
||||
if ((memh->flag & MEMHEAD_FLAG_FROM_CPP_NEW) != 0) {
|
||||
print_error(
|
||||
"Attempt to use C-style MEM_reallocN on a pointer created with CPP-style MEM_new or "
|
||||
"new\n");
|
||||
#ifdef WITH_ASSERT_ABORT
|
||||
abort();
|
||||
#endif
|
||||
report_error_on_address(vmemh,
|
||||
"Attempt to use C-style MEM_reallocN on a pointer created with "
|
||||
"CPP-style MEM_new or new\n");
|
||||
}
|
||||
|
||||
if (LIKELY(memh->alignment == 0)) {
|
||||
@ -398,12 +428,9 @@ void *MEM_guarded_recallocN_id(void *vmemh, size_t len, const char *str)
|
||||
memh--;
|
||||
|
||||
if ((memh->flag & MEMHEAD_FLAG_FROM_CPP_NEW) != 0) {
|
||||
print_error(
|
||||
"Attempt to use C-style MEM_recallocN on a pointer created with CPP-style MEM_new or "
|
||||
"new\n");
|
||||
#ifdef WITH_ASSERT_ABORT
|
||||
abort();
|
||||
#endif
|
||||
report_error_on_address(vmemh,
|
||||
"Attempt to use C-style MEM_recallocN on a pointer created with "
|
||||
"CPP-style MEM_new or new\n");
|
||||
}
|
||||
|
||||
if (LIKELY(memh->alignment == 0)) {
|
||||
@ -1012,11 +1039,9 @@ void MEM_guarded_freeN(void *vmemh, const AllocationType allocation_type)
|
||||
if (allocation_type != AllocationType::NEW_DELETE &&
|
||||
(memh->flag & MEMHEAD_FLAG_FROM_CPP_NEW) != 0)
|
||||
{
|
||||
print_error(
|
||||
report_error_on_address(
|
||||
vmemh,
|
||||
"Attempt to use C-style MEM_freeN on a pointer created with CPP-style MEM_new or new\n");
|
||||
#ifdef WITH_ASSERT_ABORT
|
||||
abort();
|
||||
#endif
|
||||
}
|
||||
|
||||
if (memh->tag1 == MEMFREE && memh->tag2 == MEMFREE) {
|
||||
|
@ -9,6 +9,27 @@
|
||||
* \ingroup intern_mem
|
||||
*/
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
/* BEGIN copied from BLI_asan.h */
|
||||
|
||||
/* Clang defines this. */
|
||||
#ifndef __has_feature
|
||||
# define __has_feature(x) 0
|
||||
#endif
|
||||
|
||||
#if (defined(__SANITIZE_ADDRESS__) || __has_feature(address_sanitizer)) && \
|
||||
(!defined(_MSC_VER) || _MSC_VER > 1929) /* MSVC 2019 and below doesn't ship ASAN headers. */
|
||||
# include "sanitizer/asan_interface.h"
|
||||
# define WITH_ASAN
|
||||
#else
|
||||
/* Ensure return value is used. Just using UNUSED_VARS results in a warning. */
|
||||
# define ASAN_POISON_MEMORY_REGION(addr, size) (void)(0 && ((size) != 0 && (addr) != NULL))
|
||||
# define ASAN_UNPOISON_MEMORY_REGION(addr, size) (void)(0 && ((size) != 0 && (addr) != NULL))
|
||||
#endif
|
||||
|
||||
/* END copied from BLI_asan.h */
|
||||
|
||||
MEM_INLINE bool MEM_size_safe_multiply(size_t a, size_t b, size_t *result)
|
||||
{
|
||||
/* A size_t with its high-half bits all set to 1. */
|
||||
@ -27,3 +48,42 @@ MEM_INLINE bool MEM_size_safe_multiply(size_t a, size_t b, size_t *result)
|
||||
*/
|
||||
return ((high_bits & (a | b)) == 0 || (*result / b == a));
|
||||
}
|
||||
|
||||
/**
|
||||
* Util to trigger an error for the given memory block.
|
||||
*
|
||||
* When ASAN is available, it will poison the memory block before accessing it, to trigger a
|
||||
* detailed ASAN report. Otherwise, it will abort if aborting on assert is set.
|
||||
*/
|
||||
#ifdef WITH_ASAN
|
||||
MEM_INLINE void MEM_trigger_error_on_memory_block(const void *address, const size_t size)
|
||||
{
|
||||
if (address == nullptr) {
|
||||
# ifdef WITH_ASSERT_ABORT
|
||||
abort();
|
||||
# endif
|
||||
return;
|
||||
}
|
||||
|
||||
/* Trigger ASAN error by poisoning the memory and accessing it. */
|
||||
ASAN_POISON_MEMORY_REGION(address, size);
|
||||
char *buffer = const_cast<char *>(static_cast<const char *>(address));
|
||||
const char c = *buffer;
|
||||
*buffer &= 255;
|
||||
*buffer = c;
|
||||
|
||||
/* In case ASAN is set to not terminate on error, but abort on assert is requested. */
|
||||
# ifdef WITH_ASSERT_ABORT
|
||||
abort();
|
||||
# endif
|
||||
ASAN_UNPOISON_MEMORY_REGION(address, size);
|
||||
}
|
||||
#else
|
||||
MEM_INLINE void MEM_trigger_error_on_memory_block(const void * /* address */,
|
||||
const size_t /* size */)
|
||||
{
|
||||
# ifdef WITH_ASSERT_ABORT
|
||||
abort();
|
||||
# endif
|
||||
}
|
||||
#endif
|
||||
|
@ -69,21 +69,6 @@ size_t malloc_usable_size(void *ptr);
|
||||
# define MEM_INLINE static inline
|
||||
#endif
|
||||
|
||||
/* BEGIN copied from BLI_asan.h */
|
||||
|
||||
/* Clang defines this. */
|
||||
#ifndef __has_feature
|
||||
# define __has_feature(x) 0
|
||||
#endif
|
||||
|
||||
#if (defined(__SANITIZE_ADDRESS__) || __has_feature(address_sanitizer)) && \
|
||||
(!defined(_MSC_VER) || _MSC_VER > 1929) /* MSVC 2019 and below doesn't ship ASAN headers. */
|
||||
# include "sanitizer/asan_interface.h"
|
||||
# define WITH_ASAN
|
||||
#endif
|
||||
|
||||
/* END copied from BLI_asan.h */
|
||||
|
||||
#define IS_POW2(a) (((a) & ((a)-1)) == 0)
|
||||
|
||||
/* Extra padding which needs to be applied on MemHead to make it aligned. */
|
||||
|
@ -74,18 +74,10 @@ enum {
|
||||
#define MEMHEAD_IS_FROM_CPP_NEW(memhead) ((memhead)->len & size_t(MEMHEAD_FLAG_FROM_CPP_NEW))
|
||||
#define MEMHEAD_LEN(memhead) ((memhead)->len & ~size_t(MEMHEAD_FLAG_MASK))
|
||||
|
||||
#ifdef __GNUC__
|
||||
__attribute__((format(printf, 1, 2)))
|
||||
#endif
|
||||
static void
|
||||
print_error(const char *str, ...)
|
||||
static void print_error(const char *message, va_list str_format_args)
|
||||
{
|
||||
char buf[512];
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, str);
|
||||
vsnprintf(buf, sizeof(buf), str, ap);
|
||||
va_end(ap);
|
||||
vsnprintf(buf, sizeof(buf), message, str_format_args);
|
||||
buf[sizeof(buf) - 1] = '\0';
|
||||
|
||||
if (error_callback) {
|
||||
@ -93,6 +85,48 @@ print_error(const char *str, ...)
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef __GNUC__
|
||||
__attribute__((format(printf, 1, 2)))
|
||||
#endif
|
||||
static void
|
||||
print_error(const char *message, ...)
|
||||
{
|
||||
va_list str_format_args;
|
||||
va_start(str_format_args, message);
|
||||
print_error(message, str_format_args);
|
||||
va_end(str_format_args);
|
||||
}
|
||||
|
||||
#ifdef __GNUC__
|
||||
__attribute__((format(printf, 2, 3)))
|
||||
#endif
|
||||
static void
|
||||
report_error_on_address(const void *vmemh, const char *message, ...)
|
||||
{
|
||||
va_list str_format_args;
|
||||
|
||||
va_start(str_format_args, message);
|
||||
print_error(message, str_format_args);
|
||||
va_end(str_format_args);
|
||||
|
||||
if (vmemh == nullptr) {
|
||||
MEM_trigger_error_on_memory_block(nullptr, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
MemHead *memh = MEMHEAD_FROM_PTR(vmemh);
|
||||
size_t len = MEMHEAD_LEN(memh);
|
||||
|
||||
void *address = memh;
|
||||
size_t size = len + sizeof(*memh);
|
||||
if (UNLIKELY(MEMHEAD_IS_ALIGNED(memh))) {
|
||||
MemHeadAligned *memh_aligned = MEMHEAD_ALIGNED_FROM_PTR(vmemh);
|
||||
address = MEMHEAD_REAL_PTR(memh_aligned);
|
||||
size = len + sizeof(*memh_aligned) + MEMHEAD_ALIGN_PADDING(memh_aligned->alignment);
|
||||
}
|
||||
MEM_trigger_error_on_memory_block(address, size);
|
||||
}
|
||||
|
||||
size_t MEM_lockfree_allocN_len(const void *vmemh)
|
||||
{
|
||||
if (LIKELY(vmemh)) {
|
||||
@ -109,10 +143,7 @@ void MEM_lockfree_freeN(void *vmemh, AllocationType allocation_type)
|
||||
}
|
||||
|
||||
if (UNLIKELY(vmemh == nullptr)) {
|
||||
print_error("Attempt to free nullptr pointer\n");
|
||||
#ifdef WITH_ASSERT_ABORT
|
||||
abort();
|
||||
#endif
|
||||
report_error_on_address(vmemh, "Attempt to free nullptr pointer\n");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -120,11 +151,9 @@ void MEM_lockfree_freeN(void *vmemh, AllocationType allocation_type)
|
||||
size_t len = MEMHEAD_LEN(memh);
|
||||
|
||||
if (allocation_type != AllocationType::NEW_DELETE && MEMHEAD_IS_FROM_CPP_NEW(memh)) {
|
||||
print_error(
|
||||
report_error_on_address(
|
||||
vmemh,
|
||||
"Attempt to use C-style MEM_freeN on a pointer created with CPP-style MEM_new or new\n");
|
||||
#ifdef WITH_ASSERT_ABORT
|
||||
abort();
|
||||
#endif
|
||||
}
|
||||
|
||||
memory_usage_block_free(len);
|
||||
@ -149,12 +178,9 @@ void *MEM_lockfree_dupallocN(const void *vmemh)
|
||||
const size_t prev_size = MEM_lockfree_allocN_len(vmemh);
|
||||
|
||||
if (MEMHEAD_IS_FROM_CPP_NEW(memh)) {
|
||||
print_error(
|
||||
"Attempt to use C-style MEM_dupallocN on a pointer created with CPP-style MEM_new or "
|
||||
"new\n");
|
||||
#ifdef WITH_ASSERT_ABORT
|
||||
abort();
|
||||
#endif
|
||||
report_error_on_address(vmemh,
|
||||
"Attempt to use C-style MEM_dupallocN on a pointer created with "
|
||||
"CPP-style MEM_new or new\n");
|
||||
}
|
||||
|
||||
if (UNLIKELY(MEMHEAD_IS_ALIGNED(memh))) {
|
||||
@ -179,12 +205,9 @@ void *MEM_lockfree_reallocN_id(void *vmemh, size_t len, const char *str)
|
||||
const size_t old_len = MEM_lockfree_allocN_len(vmemh);
|
||||
|
||||
if (MEMHEAD_IS_FROM_CPP_NEW(memh)) {
|
||||
print_error(
|
||||
"Attempt to use C-style MEM_reallocN on a pointer created with CPP-style MEM_new or "
|
||||
"new\n");
|
||||
#ifdef WITH_ASSERT_ABORT
|
||||
abort();
|
||||
#endif
|
||||
report_error_on_address(vmemh,
|
||||
"Attempt to use C-style MEM_reallocN on a pointer created with "
|
||||
"CPP-style MEM_new or new\n");
|
||||
}
|
||||
|
||||
if (LIKELY(!MEMHEAD_IS_ALIGNED(memh))) {
|
||||
@ -225,12 +248,9 @@ void *MEM_lockfree_recallocN_id(void *vmemh, size_t len, const char *str)
|
||||
const size_t old_len = MEM_lockfree_allocN_len(vmemh);
|
||||
|
||||
if (MEMHEAD_IS_FROM_CPP_NEW(memh)) {
|
||||
print_error(
|
||||
"Attempt to use C-style MEM_recallocN on a pointer created with CPP-style MEM_new or "
|
||||
"new\n");
|
||||
#ifdef WITH_ASSERT_ABORT
|
||||
abort();
|
||||
#endif
|
||||
report_error_on_address(vmemh,
|
||||
"Attempt to use C-style MEM_recallocN on a pointer created with "
|
||||
"CPP-style MEM_new or new\n");
|
||||
}
|
||||
|
||||
if (LIKELY(!MEMHEAD_IS_ALIGNED(memh))) {
|
||||
|
@ -72,7 +72,8 @@ watch_check_ruff:
|
||||
check_pylint:
|
||||
@cd "$(BASE_DIR)" && \
|
||||
pylint $(PY_FILES) \
|
||||
--disable=C0103,C0111,C0201,C0301,C0302,C0415,R1702,R1705,R0902,R0903,R0913,E0611,E0401,I1101,R0801,C0209,W0511,W0718,W0719,C0413,R0911,R0912,R0914,R0915 \
|
||||
--enable=useless-suppression \
|
||||
--disable=C0103,C0111,C0201,C0301,C0302,C0415,R0401,R1702,R1705,R0902,R0903,R0913,E0611,E0401,I1101,R0801,C0209,W0511,W0718,W0719,C0413,R0911,R0912,R0914,R0915 \
|
||||
--msg-template='{abspath}:{line}:{column}: {msg_id}: {msg} ({symbol})'
|
||||
watch_check_pylint:
|
||||
@cd "$(BASE_DIR)" && \
|
||||
|
@ -400,7 +400,6 @@ def monkeypatch_extenions_repos_update_pre_impl():
|
||||
|
||||
def monkeypatch_extenions_repos_update_post_impl():
|
||||
import os
|
||||
# pylint: disable-next=redefined-outer-name
|
||||
from . import bl_extension_ops
|
||||
|
||||
repo_cache_store = repo_cache_store_ensure()
|
||||
@ -437,7 +436,7 @@ def monkeypatch_extensions_repos_update_pre(*_):
|
||||
except Exception as ex:
|
||||
print_debug("ERROR", str(ex))
|
||||
try:
|
||||
monkeypatch_extensions_repos_update_pre._fn_orig()
|
||||
monkeypatch_extensions_repos_update_pre.fn_orig()
|
||||
except Exception as ex:
|
||||
print_debug("ERROR", str(ex))
|
||||
|
||||
@ -446,7 +445,7 @@ def monkeypatch_extensions_repos_update_pre(*_):
|
||||
def monkeypatch_extenions_repos_update_post(*_):
|
||||
print_debug("POST:")
|
||||
try:
|
||||
monkeypatch_extenions_repos_update_post._fn_orig()
|
||||
monkeypatch_extenions_repos_update_post.fn_orig()
|
||||
except Exception as ex:
|
||||
print_debug("ERROR", str(ex))
|
||||
try:
|
||||
@ -458,40 +457,50 @@ def monkeypatch_extenions_repos_update_post(*_):
|
||||
def monkeypatch_install():
|
||||
import addon_utils
|
||||
|
||||
# pylint: disable-next=protected-access
|
||||
handlers = bpy.app.handlers._extension_repos_update_pre
|
||||
# pylint: disable-next=protected-access
|
||||
fn_orig = addon_utils._initialize_extension_repos_pre
|
||||
|
||||
fn_override = monkeypatch_extensions_repos_update_pre
|
||||
for i, fn in enumerate(handlers):
|
||||
if fn is fn_orig:
|
||||
handlers[i] = fn_override
|
||||
fn_override._fn_orig = fn_orig
|
||||
fn_override.fn_orig = fn_orig
|
||||
break
|
||||
|
||||
# pylint: disable-next=protected-access
|
||||
handlers = bpy.app.handlers._extension_repos_update_post
|
||||
# pylint: disable-next=protected-access
|
||||
fn_orig = addon_utils._initialize_extension_repos_post
|
||||
|
||||
fn_override = monkeypatch_extenions_repos_update_post
|
||||
for i, fn in enumerate(handlers):
|
||||
if fn is fn_orig:
|
||||
handlers[i] = fn_override
|
||||
fn_override._fn_orig = fn_orig
|
||||
fn_override.fn_orig = fn_orig
|
||||
break
|
||||
|
||||
|
||||
def monkeypatch_uninstall():
|
||||
# pylint: disable-next=protected-access
|
||||
handlers = bpy.app.handlers._extension_repos_update_pre
|
||||
|
||||
fn_override = monkeypatch_extensions_repos_update_pre
|
||||
for i, fn in enumerate(handlers):
|
||||
if fn is fn_override:
|
||||
handlers[i] = fn_override._fn_orig
|
||||
del fn_override._fn_orig
|
||||
handlers[i] = fn_override.fn_orig
|
||||
del fn_override.fn_orig
|
||||
break
|
||||
|
||||
# pylint: disable-next=protected-access
|
||||
handlers = bpy.app.handlers._extension_repos_update_post
|
||||
|
||||
fn_override = monkeypatch_extenions_repos_update_post
|
||||
for i, fn in enumerate(handlers):
|
||||
if fn is fn_override:
|
||||
handlers[i] = fn_override._fn_orig
|
||||
del fn_override._fn_orig
|
||||
handlers[i] = fn_override.fn_orig
|
||||
del fn_override.fn_orig
|
||||
break
|
||||
|
||||
|
||||
@ -636,9 +645,11 @@ def register():
|
||||
from bl_ui.space_userpref import USERPREF_MT_interface_theme_presets
|
||||
USERPREF_MT_interface_theme_presets.append(theme_preset_draw)
|
||||
|
||||
# pylint: disable-next=protected-access
|
||||
handlers = bpy.app.handlers._extension_repos_sync
|
||||
handlers.append(extenion_repos_sync)
|
||||
|
||||
# pylint: disable-next=protected-access
|
||||
handlers = bpy.app.handlers._extension_repos_files_clear
|
||||
handlers.append(extenion_repos_files_clear)
|
||||
|
||||
@ -676,10 +687,12 @@ def unregister():
|
||||
from bl_ui.space_userpref import USERPREF_MT_interface_theme_presets
|
||||
USERPREF_MT_interface_theme_presets.remove(theme_preset_draw)
|
||||
|
||||
# pylint: disable-next=protected-access
|
||||
handlers = bpy.app.handlers._extension_repos_sync
|
||||
if extenion_repos_sync in handlers:
|
||||
handlers.remove(extenion_repos_sync)
|
||||
|
||||
# pylint: disable-next=protected-access
|
||||
handlers = bpy.app.handlers._extension_repos_files_clear
|
||||
if extenion_repos_files_clear in handlers:
|
||||
handlers.remove(extenion_repos_files_clear)
|
||||
|
@ -404,7 +404,7 @@ def repo_iter_valid_only(context, *, exclude_remote, exclude_system):
|
||||
if (not repo_item.use_remote_url) and (repo_item.source == 'SYSTEM'):
|
||||
continue
|
||||
# Ignore repositories that have invalid settings.
|
||||
directory, remote_url = repo_paths_or_none(repo_item)
|
||||
directory, _remote_url = repo_paths_or_none(repo_item)
|
||||
if directory is None:
|
||||
continue
|
||||
yield repo_item
|
||||
@ -494,16 +494,17 @@ def repo_cache_store_refresh_from_prefs(repo_cache_store, include_disabled=False
|
||||
return repos
|
||||
|
||||
|
||||
def _preferences_ensure_disabled(*, repo_item, pkg_id_sequence, default_set):
|
||||
def _preferences_ensure_disabled(
|
||||
*,
|
||||
repo_item, # `RepoItem`
|
||||
pkg_id_sequence, # `List[str]`
|
||||
default_set, # `bool`
|
||||
error_fn, # `Callable[[Exception], None]`
|
||||
): # `-> Dict[str, Tuple[boo, bool]]`
|
||||
import sys
|
||||
import addon_utils
|
||||
|
||||
result = {}
|
||||
errors = []
|
||||
|
||||
def handle_error(ex):
|
||||
print("Error:", ex)
|
||||
errors.append(str(ex))
|
||||
|
||||
modules_clear = []
|
||||
|
||||
@ -530,7 +531,7 @@ def _preferences_ensure_disabled(*, repo_item, pkg_id_sequence, default_set):
|
||||
if not hasattr(repo_module, pkg_id):
|
||||
print("Repo module \"{:s}.{:s}\" not a sub-module!".format(".".join(module_base_elem), pkg_id))
|
||||
|
||||
addon_utils.disable(addon_module_name, default_set=default_set, handle_error=handle_error)
|
||||
addon_utils.disable(addon_module_name, default_set=default_set, handle_error=error_fn)
|
||||
|
||||
modules_clear.append(pkg_id)
|
||||
|
||||
@ -569,11 +570,12 @@ def _preferences_ensure_disabled(*, repo_item, pkg_id_sequence, default_set):
|
||||
continue
|
||||
delattr(repo_module, pkg_id)
|
||||
|
||||
return result, errors
|
||||
return result
|
||||
|
||||
|
||||
def _preferences_ensure_enabled(*, repo_item, pkg_id_sequence, result, handle_error):
|
||||
import addon_utils
|
||||
_ = repo_item, pkg_id_sequence
|
||||
for addon_module_name, (loaded_default, loaded_state) in result.items():
|
||||
# The module was not loaded, so no need to restore it.
|
||||
if not loaded_state:
|
||||
@ -639,6 +641,7 @@ def _preferences_ui_redraw():
|
||||
def _preferences_ui_refresh_addons():
|
||||
import addon_utils
|
||||
# TODO: make a public method.
|
||||
# pylint: disable-next=protected-access
|
||||
addon_utils.modules._is_first = True
|
||||
|
||||
|
||||
@ -928,6 +931,7 @@ def _extensions_repo_sync_wheels(repo_cache_store, extensions_enabled):
|
||||
local_dir = os.path.join(extensions, ".local")
|
||||
|
||||
# WARNING: bad level call, avoid making this a public function just now.
|
||||
# pylint: disable-next=protected-access
|
||||
addon_utils._extension_sync_wheels(
|
||||
local_dir=local_dir,
|
||||
wheel_list=wheel_list,
|
||||
@ -1347,6 +1351,7 @@ class EXTENSIONS_OT_repo_sync(Operator, _ExtCmdMixIn):
|
||||
repos_lock.append(repo_item.directory)
|
||||
|
||||
# Lock repositories.
|
||||
# pylint: disable-next=attribute-defined-outside-init
|
||||
self.repo_lock = bl_extension_utils.RepoLock(
|
||||
repo_directories=repos_lock,
|
||||
cookie=cookie_from_session(),
|
||||
@ -1444,6 +1449,7 @@ class EXTENSIONS_OT_repo_sync_all(Operator, _ExtCmdMixIn):
|
||||
repos_lock.append(repo_item.directory)
|
||||
|
||||
# Lock repositories.
|
||||
# pylint: disable-next=attribute-defined-outside-init
|
||||
self.repo_lock = bl_extension_utils.RepoLock(
|
||||
repo_directories=repos_lock,
|
||||
cookie=cookie_from_session(),
|
||||
@ -1496,11 +1502,15 @@ class EXTENSIONS_OT_repo_refresh_all(Operator):
|
||||
# Re-generate JSON meta-data from TOML files (needed for offline repository).
|
||||
repo_cache_store.refresh_remote_from_directory(
|
||||
directory=repo_item.directory,
|
||||
# NOTE: this isn't a problem as the callback isn't stored.
|
||||
# pylint: disable-next=cell-var-from-loop
|
||||
error_fn=lambda ex: self._exceptions_as_report(repo_item.name, ex),
|
||||
force=True,
|
||||
)
|
||||
repo_cache_store.refresh_local_from_directory(
|
||||
directory=repo_item.directory,
|
||||
# NOTE: this isn't a problem as the callback isn't stored.
|
||||
# pylint: disable-next=cell-var-from-loop
|
||||
error_fn=lambda ex: self._exceptions_as_report(repo_item.name, ex),
|
||||
)
|
||||
|
||||
@ -1532,7 +1542,9 @@ class EXTENSIONS_OT_repo_enable_from_drop(Operator):
|
||||
print(self.repo_index)
|
||||
if (repo := repo_lookup_by_index_or_none_with_report(self.repo_index, self.report)) is None:
|
||||
return {'CANCELLED'}
|
||||
# pylint: disable-next=attribute-defined-outside-init
|
||||
self._repo_name = repo.name
|
||||
# pylint: disable-next=attribute-defined-outside-init
|
||||
self._repo_remote_url = repo.remote_url
|
||||
|
||||
wm = context.window_manager
|
||||
@ -1599,8 +1611,11 @@ class EXTENSIONS_OT_package_upgrade_all(Operator, _ExtCmdMixIn):
|
||||
|
||||
def exec_command_iter(self, is_modal):
|
||||
from . import bl_extension_utils
|
||||
# pylint: disable-next=attribute-defined-outside-init
|
||||
self._repo_directories = set()
|
||||
# pylint: disable-next=attribute-defined-outside-init
|
||||
self._addon_restore = []
|
||||
# pylint: disable-next=attribute-defined-outside-init
|
||||
self._theme_restore = _preferences_theme_state_create()
|
||||
|
||||
use_active_only = self.use_active_only
|
||||
@ -1690,6 +1705,7 @@ class EXTENSIONS_OT_package_upgrade_all(Operator, _ExtCmdMixIn):
|
||||
return None
|
||||
|
||||
# Lock repositories.
|
||||
# pylint: disable-next=attribute-defined-outside-init
|
||||
self.repo_lock = bl_extension_utils.RepoLock(
|
||||
repo_directories=list(self._repo_directories),
|
||||
cookie=cookie_from_session(),
|
||||
@ -1698,10 +1714,11 @@ class EXTENSIONS_OT_package_upgrade_all(Operator, _ExtCmdMixIn):
|
||||
return None
|
||||
|
||||
for repo_item, pkg_id_sequence in handle_addons_info:
|
||||
result, errors = _preferences_ensure_disabled(
|
||||
result = _preferences_ensure_disabled(
|
||||
repo_item=repo_item,
|
||||
pkg_id_sequence=pkg_id_sequence,
|
||||
default_set=False,
|
||||
error_fn=lambda ex: self.report({'ERROR'}, str(ex)),
|
||||
)
|
||||
self._addon_restore.append((repo_item, pkg_id_sequence, result))
|
||||
|
||||
@ -1765,7 +1782,9 @@ class EXTENSIONS_OT_package_install_marked(Operator, _ExtCmdMixIn):
|
||||
error_fn=self.error_fn_from_exception,
|
||||
))
|
||||
repo_pkg_map = _pkg_marked_by_repo(repo_cache_store, pkg_manifest_remote_all)
|
||||
# pylint: disable-next=attribute-defined-outside-init
|
||||
self._repo_directories = set()
|
||||
# pylint: disable-next=attribute-defined-outside-init
|
||||
self._repo_map_packages_addon_only = []
|
||||
package_count = 0
|
||||
|
||||
@ -1819,6 +1838,7 @@ class EXTENSIONS_OT_package_install_marked(Operator, _ExtCmdMixIn):
|
||||
return None
|
||||
|
||||
# Lock repositories.
|
||||
# pylint: disable-next=attribute-defined-outside-init
|
||||
self.repo_lock = bl_extension_utils.RepoLock(
|
||||
repo_directories=list(self._repo_directories),
|
||||
cookie=cookie_from_session(),
|
||||
@ -1919,7 +1939,9 @@ class EXTENSIONS_OT_package_uninstall_marked(Operator, _ExtCmdMixIn):
|
||||
repo_pkg_map = _pkg_marked_by_repo(repo_cache_store, pkg_manifest_local_all)
|
||||
package_count = 0
|
||||
|
||||
# pylint: disable-next=attribute-defined-outside-init
|
||||
self._repo_directories = set()
|
||||
# pylint: disable-next=attribute-defined-outside-init
|
||||
self._theme_restore = _preferences_theme_state_create()
|
||||
|
||||
# Track add-ons to disable before uninstalling.
|
||||
@ -1958,6 +1980,7 @@ class EXTENSIONS_OT_package_uninstall_marked(Operator, _ExtCmdMixIn):
|
||||
return None
|
||||
|
||||
# Lock repositories.
|
||||
# pylint: disable-next=attribute-defined-outside-init
|
||||
self.repo_lock = bl_extension_utils.RepoLock(
|
||||
repo_directories=list(self._repo_directories),
|
||||
cookie=cookie_from_session(),
|
||||
@ -1966,11 +1989,12 @@ class EXTENSIONS_OT_package_uninstall_marked(Operator, _ExtCmdMixIn):
|
||||
return None
|
||||
|
||||
for repo_item, pkg_id_sequence in handle_addons_info:
|
||||
# No need to store the result (`_`) because the add-ons aren't going to be enabled again.
|
||||
_, errors = _preferences_ensure_disabled(
|
||||
# No need to store the result because the add-ons aren't going to be enabled again.
|
||||
_preferences_ensure_disabled(
|
||||
repo_item=repo_item,
|
||||
pkg_id_sequence=pkg_id_sequence,
|
||||
default_set=True,
|
||||
error_fn=lambda ex: self.report({'ERROR'}, str(ex)),
|
||||
)
|
||||
|
||||
return bl_extension_utils.CommandBatch(
|
||||
@ -2066,7 +2090,9 @@ class EXTENSIONS_OT_package_install_files(Operator, _ExtCmdMixIn):
|
||||
pkg_is_legacy_addon,
|
||||
)
|
||||
|
||||
# pylint: disable-next=attribute-defined-outside-init
|
||||
self._addon_restore = []
|
||||
# pylint: disable-next=attribute-defined-outside-init
|
||||
self._theme_restore = _preferences_theme_state_create()
|
||||
|
||||
# Happens when run from scripts and this argument isn't passed in.
|
||||
@ -2138,7 +2164,9 @@ class EXTENSIONS_OT_package_install_files(Operator, _ExtCmdMixIn):
|
||||
return None
|
||||
|
||||
# Collect package ID's.
|
||||
# pylint: disable-next=attribute-defined-outside-init
|
||||
self.repo_directory = directory
|
||||
# pylint: disable-next=attribute-defined-outside-init
|
||||
self.pkg_id_sequence = pkg_id_sequence
|
||||
|
||||
# Detect upgrade.
|
||||
@ -2151,15 +2179,17 @@ class EXTENSIONS_OT_package_install_files(Operator, _ExtCmdMixIn):
|
||||
if pkg_manifest_local is not None:
|
||||
pkg_id_sequence_upgrade = [pkg_id for pkg_id in pkg_id_sequence if pkg_id in pkg_manifest_local]
|
||||
if pkg_id_sequence_upgrade:
|
||||
result, errors = _preferences_ensure_disabled(
|
||||
result = _preferences_ensure_disabled(
|
||||
repo_item=repo_item,
|
||||
pkg_id_sequence=pkg_id_sequence_upgrade,
|
||||
default_set=False,
|
||||
error_fn=lambda ex: self.report({'ERROR'}, str(ex)),
|
||||
)
|
||||
self._addon_restore.append((repo_item, pkg_id_sequence_upgrade, result))
|
||||
del repo_cache_store, pkg_manifest_local
|
||||
|
||||
# Lock repositories.
|
||||
# pylint: disable-next=attribute-defined-outside-init
|
||||
self.repo_lock = bl_extension_utils.RepoLock(
|
||||
repo_directories=[repo_item.directory],
|
||||
cookie=cookie_from_session(),
|
||||
@ -2503,7 +2533,9 @@ class EXTENSIONS_OT_package_install(Operator, _ExtCmdMixIn):
|
||||
if not self._is_ready_to_execute():
|
||||
return None
|
||||
|
||||
# pylint: disable-next=attribute-defined-outside-init
|
||||
self._addon_restore = []
|
||||
# pylint: disable-next=attribute-defined-outside-init
|
||||
self._theme_restore = _preferences_theme_state_create()
|
||||
|
||||
directory = _repo_dir_and_index_get(self.repo_index, self.repo_directory, self.report)
|
||||
@ -2531,15 +2563,17 @@ class EXTENSIONS_OT_package_install(Operator, _ExtCmdMixIn):
|
||||
|
||||
if is_installed:
|
||||
pkg_id_sequence = (pkg_id,)
|
||||
result, errors = _preferences_ensure_disabled(
|
||||
result = _preferences_ensure_disabled(
|
||||
repo_item=repo_item,
|
||||
pkg_id_sequence=pkg_id_sequence,
|
||||
default_set=False,
|
||||
error_fn=lambda ex: self.report({'ERROR'}, str(ex)),
|
||||
)
|
||||
self._addon_restore.append((repo_item, pkg_id_sequence, result))
|
||||
del pkg_id_sequence
|
||||
|
||||
# Lock repositories.
|
||||
# pylint: disable-next=attribute-defined-outside-init
|
||||
self.repo_lock = bl_extension_utils.RepoLock(
|
||||
repo_directories=[repo_item.directory],
|
||||
cookie=cookie_from_session(),
|
||||
@ -2729,7 +2763,7 @@ class EXTENSIONS_OT_package_install(Operator, _ExtCmdMixIn):
|
||||
)
|
||||
layout = self.layout
|
||||
|
||||
_repo_index, repo_name, pkg_id, item_remote = self._drop_variables
|
||||
_repo_index, repo_name, _pkg_id, item_remote = self._drop_variables
|
||||
|
||||
layout.label(text="Do you want to install the following {:s}?".format(item_remote.type))
|
||||
|
||||
@ -2809,7 +2843,6 @@ class EXTENSIONS_OT_package_install(Operator, _ExtCmdMixIn):
|
||||
repo_from_url_name, # `str`
|
||||
url, # `str`
|
||||
):
|
||||
import string
|
||||
from .bl_extension_utils import (
|
||||
platform_from_this_system,
|
||||
)
|
||||
@ -2945,6 +2978,7 @@ class EXTENSIONS_OT_package_uninstall(Operator, _ExtCmdMixIn):
|
||||
def exec_command_iter(self, is_modal):
|
||||
from . import bl_extension_utils
|
||||
|
||||
# pylint: disable-next=attribute-defined-outside-init
|
||||
self._theme_restore = _preferences_theme_state_create()
|
||||
|
||||
directory = _repo_dir_and_index_get(self.repo_index, self.repo_directory, self.report)
|
||||
@ -2959,13 +2993,16 @@ class EXTENSIONS_OT_package_uninstall(Operator, _ExtCmdMixIn):
|
||||
self.report({'ERROR'}, "Package ID not set")
|
||||
return None
|
||||
|
||||
_, errors = _preferences_ensure_disabled(
|
||||
# No need to store the result because the add-ons aren't going to be enabled again.
|
||||
_preferences_ensure_disabled(
|
||||
repo_item=repo_item,
|
||||
pkg_id_sequence=(pkg_id,),
|
||||
default_set=True,
|
||||
error_fn=lambda ex: self.report({'ERROR'}, str(ex)),
|
||||
)
|
||||
|
||||
# Lock repositories.
|
||||
# pylint: disable-next=attribute-defined-outside-init
|
||||
self.repo_lock = bl_extension_utils.RepoLock(
|
||||
repo_directories=[repo_item.directory],
|
||||
cookie=cookie_from_session(),
|
||||
@ -3388,7 +3425,7 @@ class EXTENSIONS_OT_userpref_tags_set(Operator):
|
||||
return {'CANCELLED'}
|
||||
|
||||
tags_clear(wm, tags_attr)
|
||||
if self.value is False:
|
||||
if value is False:
|
||||
tags_refresh(wm, tags_attr, default_value=False)
|
||||
|
||||
_preferences_ui_redraw()
|
||||
|
@ -561,6 +561,30 @@ def addons_panel_draw_items(
|
||||
return module_names
|
||||
|
||||
|
||||
def addons_panel_draw_error_duplicates(layout):
|
||||
import addon_utils
|
||||
box = layout.box()
|
||||
row = box.row()
|
||||
row.label(text="Multiple add-ons with the same name found!")
|
||||
row.label(icon='ERROR')
|
||||
box.label(text="Delete one of each pair to resolve:")
|
||||
for (addon_name, addon_file, addon_path) in addon_utils.error_duplicates:
|
||||
box.separator()
|
||||
sub_col = box.column(align=True)
|
||||
sub_col.label(text=addon_name + ":")
|
||||
sub_col.label(text=" " + addon_file)
|
||||
sub_col.label(text=" " + addon_path)
|
||||
|
||||
|
||||
def addons_panel_draw_error_generic(layout, lines):
|
||||
box = layout.box()
|
||||
sub = box.row()
|
||||
sub.label(text=lines[0])
|
||||
sub.label(icon='ERROR')
|
||||
for l in lines[1:]:
|
||||
box.label(text=l)
|
||||
|
||||
|
||||
def addons_panel_draw_impl(
|
||||
self,
|
||||
context, # `bpy.types.Context`
|
||||
@ -580,14 +604,26 @@ def addons_panel_draw_impl(
|
||||
|
||||
from . import repo_cache_store_ensure
|
||||
|
||||
layout = self.layout
|
||||
|
||||
# First show any errors, this should be an exceptional situation that should be resolved,
|
||||
# otherwise add-ons may not behave correctly.
|
||||
if addon_utils.error_duplicates:
|
||||
addons_panel_draw_error_duplicates(layout)
|
||||
if addon_utils.error_encoding:
|
||||
addons_panel_draw_error_generic(
|
||||
layout, (
|
||||
"One or more add-ons do not have UTF-8 encoding",
|
||||
"(see console for details)",
|
||||
),
|
||||
)
|
||||
|
||||
repo_cache_store = repo_cache_store_ensure()
|
||||
|
||||
# This isn't elegant, but the preferences aren't available on registration.
|
||||
if not repo_cache_store.is_init():
|
||||
repo_cache_store_refresh_from_prefs(repo_cache_store)
|
||||
|
||||
layout = self.layout
|
||||
|
||||
prefs = context.preferences
|
||||
|
||||
# Define a top-most column to place warnings (if-any).
|
||||
@ -716,6 +752,7 @@ def addons_panel_draw(panel, context):
|
||||
# Light weight wrapper for extension local and remote extension manifest data.
|
||||
# Used for display purposes. Includes some information for filtering.
|
||||
|
||||
# pylint: disable-next=wrong-import-order
|
||||
from collections import namedtuple
|
||||
|
||||
ExtensionUI = namedtuple(
|
||||
@ -863,15 +900,14 @@ class ExtensionUI_FilterParams:
|
||||
if is_addon:
|
||||
if is_installed:
|
||||
# Currently we only need to know the module name once installed.
|
||||
addon_module_name = repo_module_prefix + pkg_id
|
||||
# pylint: disable-next=possibly-used-before-assignment
|
||||
addon_module_name = repo_module_prefix + pkg_id
|
||||
is_enabled = addon_module_name in self.addons_enabled
|
||||
|
||||
else:
|
||||
is_enabled = False
|
||||
addon_module_name = None
|
||||
elif is_theme:
|
||||
# pylint: disable-next=possibly-used-before-assignment
|
||||
is_enabled = (repo_index, pkg_id) == self.active_theme_info
|
||||
addon_module_name = None
|
||||
else:
|
||||
|
@ -292,8 +292,9 @@ def command_output_from_json_0(
|
||||
# Internal Functions.
|
||||
#
|
||||
|
||||
|
||||
# pylint: disable-next=useless-return
|
||||
def repositories_validate_or_errors(repos: Sequence[str]) -> Optional[InfoItemSeq]:
|
||||
_ = repos
|
||||
return None
|
||||
|
||||
|
||||
@ -853,6 +854,8 @@ class CommandBatch:
|
||||
def _exec_blocking_single(
|
||||
self,
|
||||
report_fn: Callable[[str, str], None],
|
||||
# TODO: investigate using this or removing it.
|
||||
# pylint: disable-next=unused-argument
|
||||
request_exit_fn: Callable[[], bool],
|
||||
) -> bool:
|
||||
for cmd in self._batch:
|
||||
@ -1298,6 +1301,7 @@ def pkg_manifest_params_compatible_or_error(
|
||||
item=item,
|
||||
filter_blender_version=this_blender_version,
|
||||
filter_platform=this_platform,
|
||||
# pylint: disable-next=unnecessary-lambda
|
||||
skip_message_fn=lambda msg: result_report.append(msg),
|
||||
error_fn=error_fn,
|
||||
)
|
||||
@ -1891,6 +1895,7 @@ class RepoCacheStore:
|
||||
) -> Optional[Dict[str, PkgManifest_Normalized]]:
|
||||
for repo_entry in self._repos:
|
||||
if directory == repo_entry.directory:
|
||||
# pylint: disable-next=protected-access
|
||||
return repo_entry._json_data_refresh(force=force, error_fn=error_fn)
|
||||
raise ValueError("Directory {:s} not a known repo".format(directory))
|
||||
|
||||
@ -1927,6 +1932,7 @@ class RepoCacheStore:
|
||||
# While we could yield a valid manifest here,
|
||||
# leave it to the caller to skip "remote" data for local-only repositories.
|
||||
if repo_entry.remote_url:
|
||||
# pylint: disable-next=protected-access
|
||||
yield repo_entry._json_data_ensure(
|
||||
check_files=check_files,
|
||||
ignore_missing=ignore_missing,
|
||||
|
@ -232,6 +232,7 @@ def force_exit_ok_enable() -> None:
|
||||
def execfile(filepath: str) -> Dict[str, Any]:
|
||||
global_namespace = {"__file__": filepath, "__name__": "__main__"}
|
||||
with open(filepath, "rb") as fh:
|
||||
# pylint: disable-next=exec-used
|
||||
exec(compile(fh.read(), filepath, 'exec'), global_namespace)
|
||||
return global_namespace
|
||||
|
||||
@ -446,6 +447,7 @@ def sha256_from_file_or_error(
|
||||
(exact hashing method may change).
|
||||
"""
|
||||
try:
|
||||
# pylint: disable-next=consider-using-with
|
||||
fh_context = open(filepath, 'rb')
|
||||
except Exception as ex:
|
||||
return "error opening file: {:s}".format(str(ex))
|
||||
@ -531,6 +533,8 @@ def rmtree_with_fallback_or_error(
|
||||
if sys.version_info >= (3, 12):
|
||||
shutil.rmtree(path, onexc=lambda *args: errors.append(args))
|
||||
else:
|
||||
# Ignore as the deprecated logic is only used for older Python versions.
|
||||
# pylint: disable-next=deprecated-argument
|
||||
shutil.rmtree(path, onerror=lambda *args: errors.append((args[0], args[1], args[2][1])))
|
||||
|
||||
# Happy path (for practically all cases).
|
||||
@ -642,7 +646,7 @@ def pkg_manifest_from_dict_and_validate_impl(
|
||||
for key in PkgManifest._fields:
|
||||
val = data.get(key, ...)
|
||||
if val is ...:
|
||||
# pylint: disable-next=no-member
|
||||
# pylint: disable-next=no-member,protected-access
|
||||
val = PkgManifest._field_defaults.get(key, ...)
|
||||
# `pkg_manifest_is_valid_or_error{_all}` will have caught this, assert all the same.
|
||||
assert val is not ...
|
||||
@ -820,6 +824,7 @@ def pkg_manifest_from_archive_and_validate(
|
||||
strict: bool,
|
||||
) -> Union[PkgManifest, str]:
|
||||
try:
|
||||
# pylint: disable-next=consider-using-with
|
||||
zip_fh_context = zipfile.ZipFile(filepath, mode="r")
|
||||
except Exception as ex:
|
||||
return "Error extracting archive \"{:s}\"".format(str(ex))
|
||||
@ -836,6 +841,7 @@ def pkg_is_legacy_addon(filepath: str) -> bool:
|
||||
return True
|
||||
|
||||
try:
|
||||
# pylint: disable-next=consider-using-with
|
||||
zip_fh_context = zipfile.ZipFile(filepath, mode="r")
|
||||
except Exception:
|
||||
return False
|
||||
@ -1450,12 +1456,12 @@ def pkg_manifest_tags_valid_or_error(
|
||||
#
|
||||
# However manifests from severs that don't adhere to strict rules are not prevented from loading.
|
||||
|
||||
# pylint: disable-next=useless-return
|
||||
def pkg_manifest_validate_field_nop(
|
||||
value: Any,
|
||||
strict: bool,
|
||||
) -> Optional[str]:
|
||||
_ = strict, value
|
||||
# pylint: disable-next=useless-return
|
||||
return None
|
||||
|
||||
|
||||
@ -1698,9 +1704,16 @@ def pkg_manifest_validate_field_wheels(
|
||||
filename_spec = "{distribution}-{version}(-{build tag})?-{python tag}-{abi tag}-{platform tag}.whl"
|
||||
|
||||
for wheel in value:
|
||||
if "\"" in wheel:
|
||||
return "wheel paths most not contain quotes, found {!r}".format(wheel)
|
||||
if "\\" in wheel:
|
||||
return "wheel paths must use forward slashes, found {!r}".format(wheel)
|
||||
|
||||
if (error := pkg_manifest_validate_field_any_non_empty_string_stripped_no_control_chars(
|
||||
wheel, True,
|
||||
)) is not None:
|
||||
return "wheel paths detected: {:s}, found {!r}".format(error, wheel)
|
||||
|
||||
wheel_filename = os.path.basename(wheel)
|
||||
if not wheel_filename.lower().endswith(".whl"):
|
||||
return "wheel paths must end with \".whl\", found {!r}".format(wheel)
|
||||
@ -1809,11 +1822,11 @@ def pkg_manifest_is_valid_or_error_impl(
|
||||
is_default_value = False
|
||||
x_val = data.get(x_key, ...)
|
||||
if x_val is ...:
|
||||
# pylint: disable-next=no-member
|
||||
# pylint: disable-next=no-member, protected-access
|
||||
x_val = PkgManifest._field_defaults.get(x_key, ...)
|
||||
if from_repo:
|
||||
if x_val is ...:
|
||||
# pylint: disable-next=no-member
|
||||
# pylint: disable-next=no-member, protected-access
|
||||
x_val = PkgManifest_Archive._field_defaults.get(x_key, ...)
|
||||
if x_val is ...:
|
||||
error_list.append("missing \"{:s}\"".format(x_key))
|
||||
@ -1898,6 +1911,9 @@ def pkg_manifest_dict_apply_build_generated_table(manifest_dict: Dict[str, Any])
|
||||
if (platforms := build_generated.get("platforms")) is not None:
|
||||
manifest_dict["platforms"] = platforms
|
||||
|
||||
if (wheels := build_generated.get("wheels")) is not None:
|
||||
manifest_dict["wheels"] = wheels
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Standalone Utilities
|
||||
@ -1986,6 +2002,35 @@ def blender_platform_compatible_with_wheel_platform(platform: str, wheel_platfor
|
||||
return platform == platform_blender
|
||||
|
||||
|
||||
def blender_platform_compatible_with_wheel_platform_from_filepath(platform: str, wheel_filepath: str) -> bool:
|
||||
wheel_filename = os.path.splitext(os.path.basename(wheel_filepath))[0]
|
||||
|
||||
wheel_filename_split = wheel_filename.split("-")
|
||||
# This should be unreachable because the manifest has been validated, add assert.
|
||||
assert len(wheel_filename_split) >= 5, "Internal error, manifest validation disallows this"
|
||||
|
||||
wheel_platform = wheel_filename_split[-1]
|
||||
|
||||
return blender_platform_compatible_with_wheel_platform(platform, wheel_platform)
|
||||
|
||||
|
||||
def paths_filter_wheels_by_platform(
|
||||
wheels: List[str],
|
||||
platform: str,
|
||||
) -> List[str]:
|
||||
"""
|
||||
All paths are wheels with filenames that follow the wheel spec.
|
||||
Return wheels which are compatible with the ``platform``.
|
||||
"""
|
||||
wheels_result: List[str] = []
|
||||
|
||||
for wheel_filepath in wheels:
|
||||
if blender_platform_compatible_with_wheel_platform_from_filepath(platform, wheel_filepath):
|
||||
wheels_result.append(wheel_filepath)
|
||||
|
||||
return wheels_result
|
||||
|
||||
|
||||
def build_paths_filter_wheels_by_platform(
|
||||
build_paths: List[Tuple[str, str]],
|
||||
platform: str,
|
||||
@ -1997,17 +2042,7 @@ def build_paths_filter_wheels_by_platform(
|
||||
build_paths_for_platform: List[Tuple[str, str]] = []
|
||||
|
||||
for item in build_paths:
|
||||
# Both the absolute/relative path can be used to get the filename.
|
||||
# Use the relative since it's likely to be shorter.
|
||||
wheel_filename = os.path.splitext(os.path.basename(item[1]))[0]
|
||||
|
||||
wheel_filename_split = wheel_filename.split("-")
|
||||
# This should be unreachable because the manifest has been validated, add assert.
|
||||
assert len(wheel_filename_split) >= 5, "Internal error, manifest validation disallows this"
|
||||
|
||||
wheel_platform = wheel_filename_split[-1]
|
||||
|
||||
if blender_platform_compatible_with_wheel_platform(platform, wheel_platform):
|
||||
if blender_platform_compatible_with_wheel_platform_from_filepath(platform, item[1]):
|
||||
build_paths_for_platform.append(item)
|
||||
|
||||
return build_paths_for_platform
|
||||
@ -2221,7 +2256,7 @@ def pkg_manifest_toml_is_valid_or_error(filepath: str, strict: bool) -> Tuple[Op
|
||||
return None, result
|
||||
|
||||
|
||||
def pkg_manifest_detect_duplicates(pkg_idname: str, pkg_items: List[PkgManifest]) -> Optional[str]:
|
||||
def pkg_manifest_detect_duplicates(pkg_items: List[PkgManifest]) -> Optional[str]:
|
||||
"""
|
||||
When a repository includes multiple packages with the same ID, ensure they don't conflict.
|
||||
|
||||
@ -3177,7 +3212,7 @@ class subcmd_server:
|
||||
for pkg_idname, pkg_items in repo_data_idname_map.items():
|
||||
if len(pkg_items) == 1:
|
||||
continue
|
||||
if (error := pkg_manifest_detect_duplicates(pkg_idname, pkg_items)) is not None:
|
||||
if (error := pkg_manifest_detect_duplicates(pkg_items)) is not None:
|
||||
msglog.warn("archive found with duplicates for id {:s}: {:s}".format(pkg_idname, error))
|
||||
|
||||
if html:
|
||||
@ -3325,6 +3360,7 @@ class subcmd_client:
|
||||
directories_to_clean: List[str] = []
|
||||
with CleanupPathsContext(files=(), directories=directories_to_clean):
|
||||
try:
|
||||
# pylint: disable-next=consider-using-with
|
||||
zip_fh_context = zipfile.ZipFile(filepath_archive, mode="r")
|
||||
except Exception as ex:
|
||||
msglog.error("Error extracting archive: {:s}".format(str(ex)))
|
||||
@ -3527,9 +3563,6 @@ class subcmd_client:
|
||||
has_fatal_error = True
|
||||
continue
|
||||
|
||||
def error_handle(ex: Exception) -> None:
|
||||
msglog.error("{:s}: {:s}".format(pkg_idname, str(ex)))
|
||||
|
||||
pkg_info_list = [
|
||||
pkg_info for pkg_info in pkg_info_list
|
||||
if not repository_filter_skip(
|
||||
@ -3537,7 +3570,10 @@ class subcmd_client:
|
||||
filter_blender_version=blender_version_tuple,
|
||||
filter_platform=platform_this,
|
||||
skip_message_fn=None,
|
||||
error_fn=error_handle,
|
||||
error_fn=lambda ex: any_as_none(
|
||||
# pylint: disable-next=cell-var-from-loop
|
||||
msglog.error("{:s}: {:s}".format(pkg_idname, str(ex))),
|
||||
),
|
||||
)
|
||||
]
|
||||
|
||||
@ -3653,7 +3689,7 @@ class subcmd_client:
|
||||
|
||||
# Validate:
|
||||
if filename_archive_size_test != archive_size_expected:
|
||||
msglog.error("Archive size mismatch \"{:s}\", expected {:d}, was {:d}".format(
|
||||
msglog.fatal_error("Archive size mismatch \"{:s}\", expected {:d}, was {:d}".format(
|
||||
pkg_idname,
|
||||
archive_size_expected,
|
||||
filename_archive_size_test,
|
||||
@ -3661,7 +3697,7 @@ class subcmd_client:
|
||||
return False
|
||||
filename_archive_hash_test = "sha256:" + sha256.hexdigest()
|
||||
if filename_archive_hash_test != archive_hash_expected:
|
||||
msglog.error("Archive checksum mismatch \"{:s}\", expected {:s}, was {:s}".format(
|
||||
msglog.fatal_error("Archive checksum mismatch \"{:s}\", expected {:s}, was {:s}".format(
|
||||
pkg_idname,
|
||||
archive_hash_expected,
|
||||
filename_archive_hash_test,
|
||||
@ -3747,9 +3783,8 @@ class subcmd_client:
|
||||
|
||||
if (error := rmtree_with_fallback_or_error(filepath_local_pkg)) is not None:
|
||||
msglog.error("Failure to remove \"{:s}\" with error ({:s})".format(pkg_idname, error))
|
||||
continue
|
||||
|
||||
msglog.status("Removed \"{:s}\"".format(pkg_idname))
|
||||
else:
|
||||
msglog.status("Removed \"{:s}\"".format(pkg_idname))
|
||||
|
||||
filepath_local_cache_archive = os.path.join(local_cache_dir, pkg_idname + PKG_EXT)
|
||||
if os.path.exists(filepath_local_cache_archive):
|
||||
@ -3757,12 +3792,13 @@ class subcmd_client:
|
||||
|
||||
if user_dir:
|
||||
filepath_user_pkg = os.path.join(user_dir, pkg_idname)
|
||||
if os.path.isdir(filepath_user_pkg):
|
||||
if os.path.exists(filepath_user_pkg):
|
||||
if (error := rmtree_with_fallback_or_error(filepath_user_pkg)) is not None:
|
||||
msglog.error(
|
||||
"Failure to remove \"{:s}\" user files with error ({:s})".format(pkg_idname, error),
|
||||
)
|
||||
continue
|
||||
else:
|
||||
msglog.status("Removed cache \"{:s}\"".format(pkg_idname))
|
||||
|
||||
return True
|
||||
|
||||
@ -3942,7 +3978,7 @@ class subcmd_author:
|
||||
del build_paths_extra_canonical
|
||||
|
||||
except Exception as ex:
|
||||
msglog.status("Error building path list \"{:s}\"".format(str(ex)))
|
||||
msglog.fatal_error("Error building path list \"{:s}\"".format(str(ex)))
|
||||
return False
|
||||
|
||||
request_exit = False
|
||||
@ -3994,9 +4030,10 @@ class subcmd_author:
|
||||
|
||||
with CleanupPathsContext(files=(outfile_temp,), directories=()):
|
||||
try:
|
||||
# pylint: disable-next=consider-using-with
|
||||
zip_fh_context = zipfile.ZipFile(outfile_temp, 'w', zipfile.ZIP_DEFLATED, compresslevel=9)
|
||||
except Exception as ex:
|
||||
msglog.status("Error creating archive \"{:s}\"".format(str(ex)))
|
||||
msglog.fatal_error("Error creating archive \"{:s}\"".format(str(ex)))
|
||||
return False
|
||||
|
||||
with contextlib.closing(zip_fh_context) as zip_fh:
|
||||
@ -4011,13 +4048,25 @@ class subcmd_author:
|
||||
b"# This must not be included in source manifests.\n",
|
||||
b"[build.generated]\n",
|
||||
"platforms = [\"{:s}\"]\n".format(platform).encode("utf-8"),
|
||||
# Including wheels simplifies server side check as this list can be tested
|
||||
# without the server having to filter by platform too.
|
||||
b"wheels = [",
|
||||
", ".join([
|
||||
# NOTE: accept no string escaping as the rules for wheel paths
|
||||
# are already strict so strings don't require quoting.
|
||||
"\"{:s}\"".format(wheel) for wheel in paths_filter_wheels_by_platform(
|
||||
manifest.wheels or [],
|
||||
platform,
|
||||
)
|
||||
]).encode("utf-8"),
|
||||
b"]\n"
|
||||
b"# END GENERATED CONTENT.\n",
|
||||
))
|
||||
try:
|
||||
with open(filepath_abs, "rb") as temp_fh:
|
||||
zip_data_override = temp_fh.read() + zip_data_override
|
||||
except Exception as ex:
|
||||
msglog.status("Error overriding manifest \"{:s}\"".format(str(ex)))
|
||||
msglog.fatal_error("Error overriding manifest \"{:s}\"".format(str(ex)))
|
||||
return False
|
||||
|
||||
# Handy for testing that sub-directories:
|
||||
@ -4029,7 +4078,7 @@ class subcmd_author:
|
||||
else:
|
||||
zip_fh.write(filepath_abs, filepath_rel, compress_type=compress_type)
|
||||
except Exception as ex:
|
||||
msglog.status("Error adding to archive \"{:s}\"".format(str(ex)))
|
||||
msglog.fatal_error("Error adding to archive \"{:s}\"".format(str(ex)))
|
||||
return False
|
||||
|
||||
if verbose:
|
||||
@ -4140,6 +4189,7 @@ class subcmd_author:
|
||||
# extract the archive into a temporary directory and run validation there.
|
||||
|
||||
try:
|
||||
# pylint: disable-next=consider-using-with
|
||||
zip_fh_context = zipfile.ZipFile(pkg_source_archive, mode="r")
|
||||
except Exception as ex:
|
||||
msglog.status("Error extracting archive \"{:s}\"".format(str(ex)))
|
||||
@ -4147,14 +4197,14 @@ class subcmd_author:
|
||||
|
||||
with contextlib.closing(zip_fh_context) as zip_fh:
|
||||
if (archive_subdir := pkg_zipfile_detect_subdir_or_none(zip_fh)) is None:
|
||||
msglog.status("Error, archive has no manifest: \"{:s}\"".format(PKG_MANIFEST_FILENAME_TOML))
|
||||
msglog.fatal_error("Error, archive has no manifest: \"{:s}\"".format(PKG_MANIFEST_FILENAME_TOML))
|
||||
return False
|
||||
# Demote errors to status as the function of this action is to check the manifest is stable.
|
||||
manifest = pkg_manifest_from_zipfile_and_validate_all_errors(zip_fh, archive_subdir, strict=True)
|
||||
if isinstance(manifest, list):
|
||||
msglog.status("Error parsing TOML in \"{:s}\"".format(pkg_source_archive))
|
||||
msglog.fatal_error("Error parsing TOML in \"{:s}\"".format(pkg_source_archive))
|
||||
for error_msg in manifest:
|
||||
msglog.status(error_msg)
|
||||
msglog.fatal_error(error_msg)
|
||||
return False
|
||||
|
||||
if valid_tags_filepath:
|
||||
@ -4182,7 +4232,7 @@ class subcmd_author:
|
||||
ok = True
|
||||
for filepath in expected_files:
|
||||
if zip_fh.NameToInfo.get(filepath) is None:
|
||||
msglog.status("Error, file missing from {:s}: \"{:s}\"".format(
|
||||
msglog.fatal_error("Error, file missing from {:s}: \"{:s}\"".format(
|
||||
manifest.type,
|
||||
filepath,
|
||||
))
|
||||
@ -4785,6 +4835,7 @@ def main(
|
||||
# While this is typically the case, is only guaranteed to be `TextIO` so check `reconfigure` is available.
|
||||
if not isinstance(fh, io.TextIOWrapper):
|
||||
continue
|
||||
# pylint: disable-next=no-member; False positive.
|
||||
if fh.encoding.lower().partition(":")[0] == "utf-8":
|
||||
continue
|
||||
fh.reconfigure(encoding="utf-8")
|
||||
|
@ -91,7 +91,9 @@ class HTTPServerContext:
|
||||
http_thread.daemon = True
|
||||
http_thread.start()
|
||||
|
||||
# pylint: disable-next=attribute-defined-outside-init
|
||||
self._http_thread = http_thread
|
||||
# pylint: disable-next=attribute-defined-outside-init
|
||||
self._http_server = http_server
|
||||
|
||||
def __exit__(self, _type: Any, _value: Any, traceback: Any) -> None:
|
||||
|
@ -111,6 +111,7 @@ classifiers = [
|
||||
cwd=temp_dir,
|
||||
stderr=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
check=True,
|
||||
)
|
||||
|
||||
result = search(temp_dir, lambda entry: entry.name.endswith(".whl"))
|
||||
|
@ -15,9 +15,8 @@ import sys
|
||||
import tempfile
|
||||
import tomllib
|
||||
import unittest
|
||||
import zipfile
|
||||
|
||||
import unittest.util
|
||||
import zipfile
|
||||
|
||||
from typing import (
|
||||
Any,
|
||||
|
@ -291,6 +291,8 @@ def run_blender(
|
||||
},
|
||||
stderr=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
# Allow the caller to read a non-zero return-code.
|
||||
check=False,
|
||||
)
|
||||
stdout = output.stdout.decode("utf-8")
|
||||
stderr = output.stderr.decode("utf-8")
|
||||
@ -754,11 +756,8 @@ class TestPlatform(TestWithTempBlenderUser_MixIn, unittest.TestCase):
|
||||
|
||||
|
||||
def main() -> None:
|
||||
global TEMP_DIR_BLENDER_USER
|
||||
global TEMP_DIR_REMOTE
|
||||
global TEMP_DIR_LOCAL
|
||||
global TEMP_DIR_TMPDIR
|
||||
global TEMP_DIR_REMOTE_AS_URL
|
||||
# pylint: disable-next=global-statement
|
||||
global TEMP_DIR_BLENDER_USER, TEMP_DIR_REMOTE, TEMP_DIR_LOCAL, TEMP_DIR_TMPDIR, TEMP_DIR_REMOTE_AS_URL
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_prefix:
|
||||
TEMP_DIR_BLENDER_USER = os.path.join(temp_prefix, "bl_ext_blender")
|
||||
|
@ -2617,7 +2617,6 @@ class USERPREF_PT_addons(AddOnPanel, Panel):
|
||||
if filter in {"All", "Enabled"}:
|
||||
# Append missing scripts
|
||||
# First collect scripts that are used but have no script file.
|
||||
module_names = {mod.__name__ for mod in addon_modules}
|
||||
missing_modules = {
|
||||
addon_module_name for addon_module_name in used_addon_module_name_map
|
||||
if addon_module_name not in module_names
|
||||
|
@ -9853,7 +9853,7 @@ static void rna_def_modifier_grease_pencil_multiply(BlenderRNA *brna)
|
||||
RNA_def_property_int_sdna(prop, nullptr, "duplications");
|
||||
RNA_def_property_range(prop, 0, 999);
|
||||
RNA_def_property_ui_range(prop, 1, 10, 1, 1);
|
||||
RNA_def_property_ui_text(prop, "duplicates", "How many copies of strokes be displayed");
|
||||
RNA_def_property_ui_text(prop, "Duplicates", "How many copies of strokes be displayed");
|
||||
RNA_def_property_update(prop, 0, "rna_Modifier_update");
|
||||
|
||||
prop = RNA_def_property(srna, "distance", PROP_FLOAT, PROP_DISTANCE);
|
||||
|
@ -195,7 +195,6 @@ def canonical_author_map() -> Dict[str, str]:
|
||||
"Daniel Salazar <zanqdo@gmail.com>": (
|
||||
"Daniel Salazar <zanqdo>",
|
||||
"Daniel Salazar <zanqdo@noreply.localhost>",
|
||||
"Daniel Santana <dgsantana>",
|
||||
"ZanQdo <zanqdo@gmail.com>",
|
||||
"zanqdo <zanqdo@gmail.com>",
|
||||
),
|
||||
|
Loading…
Reference in New Issue
Block a user