Merge branch 'blender-v4.2-release'
This commit is contained in:
commit
21a2d4f962
@ -498,7 +498,7 @@ def repo_cache_store_ensure():
|
||||
bl_extension_ops,
|
||||
bl_extension_utils,
|
||||
)
|
||||
_repo_cache_store = bl_extension_utils.RepoCacheStore()
|
||||
_repo_cache_store = bl_extension_utils.RepoCacheStore(bpy.app.version)
|
||||
bl_extension_ops.repo_cache_store_refresh_from_prefs(_repo_cache_store)
|
||||
return _repo_cache_store
|
||||
|
||||
|
@ -1423,6 +1423,7 @@ class EXTENSIONS_OT_package_upgrade_all(Operator, _ExtCmdMixIn):
|
||||
remote_url=url_append_defaults(repo_item.remote_url),
|
||||
pkg_id_sequence=pkg_id_sequence,
|
||||
online_user_agent=online_user_agent_from_blender(),
|
||||
blender_version=bpy.app.version,
|
||||
access_token=repo_item.access_token,
|
||||
use_cache=repo_item.use_cache,
|
||||
use_idle=is_modal,
|
||||
@ -1532,6 +1533,7 @@ class EXTENSIONS_OT_package_install_marked(Operator, _ExtCmdMixIn):
|
||||
remote_url=url_append_defaults(repo_item.remote_url),
|
||||
pkg_id_sequence=pkg_id_sequence,
|
||||
online_user_agent=online_user_agent_from_blender(),
|
||||
blender_version=bpy.app.version,
|
||||
access_token=repo_item.access_token,
|
||||
use_cache=repo_item.use_cache,
|
||||
use_idle=is_modal,
|
||||
@ -2154,6 +2156,7 @@ class EXTENSIONS_OT_package_install(Operator, _ExtCmdMixIn):
|
||||
remote_url=url_append_defaults(repo_item.remote_url),
|
||||
pkg_id_sequence=(pkg_id,),
|
||||
online_user_agent=online_user_agent_from_blender(),
|
||||
blender_version=bpy.app.version,
|
||||
access_token=repo_item.access_token,
|
||||
use_cache=repo_item.use_cache,
|
||||
use_idle=is_modal,
|
||||
|
@ -360,20 +360,10 @@ def repo_index_outdated(directory: str) -> bool:
|
||||
|
||||
|
||||
def platform_from_this_system() -> str:
|
||||
import platform
|
||||
system_replace = {
|
||||
"darwin": "macos",
|
||||
}
|
||||
machine_replace = {
|
||||
"x86_64": "x64",
|
||||
"amd64": "x64",
|
||||
}
|
||||
system = platform.system().lower()
|
||||
machine = platform.machine().lower()
|
||||
return "{:s}-{:s}".format(
|
||||
system_replace.get(system, system),
|
||||
machine_replace.get(machine, machine),
|
||||
)
|
||||
from .cli.blender_ext import platform_from_this_system
|
||||
result = platform_from_this_system()
|
||||
assert isinstance(result, str)
|
||||
return result
|
||||
|
||||
|
||||
def _url_append_query(url: str, query: Dict[str, str]) -> str:
|
||||
@ -565,6 +555,7 @@ def pkg_install(
|
||||
directory: str,
|
||||
remote_url: str,
|
||||
pkg_id_sequence: Sequence[str],
|
||||
blender_version: Tuple[int, int, int],
|
||||
online_user_agent: str,
|
||||
access_token: str,
|
||||
use_cache: bool,
|
||||
@ -578,6 +569,7 @@ def pkg_install(
|
||||
"install", ",".join(pkg_id_sequence),
|
||||
"--local-dir", directory,
|
||||
"--remote-url", remote_url,
|
||||
"--blender-version", "{:d}.{:d}.{:d}".format(*blender_version),
|
||||
"--online-user-agent", online_user_agent,
|
||||
"--access-token", access_token,
|
||||
"--local-cache", str(int(use_cache)),
|
||||
@ -1175,17 +1167,33 @@ def repository_id_with_error_fn(
|
||||
return pkg_idname
|
||||
|
||||
|
||||
def repository_filter_skip(item: Dict[str, Any]) -> bool:
|
||||
# TODO: filter out items that:
|
||||
# - Don't match this systems platform.
|
||||
# - Don't match the version range of Blender.
|
||||
return False
|
||||
# Values used to exclude incompatible packages when listing & installing.
|
||||
class PkgManifest_FilterParams(NamedTuple):
|
||||
platform: str
|
||||
blender_version: Tuple[int, int, int]
|
||||
|
||||
|
||||
def repository_filter_skip(
|
||||
item: Dict[str, Any],
|
||||
filter_params: PkgManifest_FilterParams,
|
||||
error_fn: Callable[[Exception], None],
|
||||
) -> bool:
|
||||
from .cli.blender_ext import repository_filter_skip
|
||||
result = repository_filter_skip(
|
||||
item,
|
||||
filter_blender_version=filter_params.blender_version,
|
||||
filter_platform=filter_params.platform,
|
||||
error_fn=error_fn,
|
||||
)
|
||||
assert isinstance(result, bool)
|
||||
return result
|
||||
|
||||
|
||||
def repository_filter_packages(
|
||||
data: List[Dict[str, Any]],
|
||||
*,
|
||||
repo_directory: str,
|
||||
filter_params: PkgManifest_FilterParams,
|
||||
error_fn: Callable[[Exception], None],
|
||||
) -> Dict[str, PkgManifest_Normalized]:
|
||||
pkg_manifest_map = {}
|
||||
@ -1197,7 +1205,7 @@ def repository_filter_packages(
|
||||
)) is None:
|
||||
continue
|
||||
|
||||
if repository_filter_skip(item):
|
||||
if repository_filter_skip(item, filter_params, error_fn):
|
||||
continue
|
||||
|
||||
if (value := PkgManifest_Normalized.from_dict_with_error_fn(
|
||||
@ -1288,14 +1296,20 @@ class _RepoDataSouce_JSON(_RepoDataSouce_ABC):
|
||||
"_data",
|
||||
|
||||
"_filepath",
|
||||
"_filter_params",
|
||||
"_mtime",
|
||||
)
|
||||
|
||||
def __init__(self, directory: str):
|
||||
def __init__(
|
||||
self,
|
||||
directory: str,
|
||||
filter_params: PkgManifest_FilterParams,
|
||||
):
|
||||
filepath = os.path.join(directory, REPO_LOCAL_JSON)
|
||||
|
||||
self._filepath: str = filepath
|
||||
self._mtime: int = 0
|
||||
self._filter_params: PkgManifest_FilterParams = filter_params
|
||||
self._data: Optional[RepoRemoteData] = None
|
||||
|
||||
def exists(self) -> bool:
|
||||
@ -1362,6 +1376,7 @@ class _RepoDataSouce_JSON(_RepoDataSouce_ABC):
|
||||
pkg_manifest_map=repository_filter_packages(
|
||||
data_dict.get("data", []),
|
||||
repo_directory=os.path.dirname(self._filepath),
|
||||
filter_params=self._filter_params,
|
||||
error_fn=error_fn,
|
||||
),
|
||||
)
|
||||
@ -1377,11 +1392,17 @@ class _RepoDataSouce_TOML_FILES(_RepoDataSouce_ABC):
|
||||
"_data",
|
||||
|
||||
"_directory",
|
||||
"_filter_params",
|
||||
"_mtime_for_each_package",
|
||||
)
|
||||
|
||||
def __init__(self, directory: str):
|
||||
def __init__(
|
||||
self,
|
||||
directory: str,
|
||||
filter_params: PkgManifest_FilterParams,
|
||||
):
|
||||
self._directory: str = directory
|
||||
self._filter_params = filter_params
|
||||
self._mtime_for_each_package: Optional[Dict[str, int]] = None
|
||||
self._data: Optional[RepoRemoteData] = None
|
||||
|
||||
@ -1449,7 +1470,7 @@ class _RepoDataSouce_TOML_FILES(_RepoDataSouce_ABC):
|
||||
)) is None:
|
||||
continue
|
||||
|
||||
if repository_filter_skip(item_local):
|
||||
if repository_filter_skip(item_local, self._filter_params, error_fn):
|
||||
continue
|
||||
|
||||
if (value := PkgManifest_Normalized.from_dict_with_error_fn(
|
||||
@ -1546,7 +1567,12 @@ class _RepoCacheEntry:
|
||||
|
||||
)
|
||||
|
||||
def __init__(self, directory: str, remote_url: str) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
directory: str,
|
||||
remote_url: str,
|
||||
filter_params: PkgManifest_FilterParams,
|
||||
) -> None:
|
||||
assert directory != ""
|
||||
self.directory = directory
|
||||
self.remote_url = remote_url
|
||||
@ -1554,8 +1580,8 @@ class _RepoCacheEntry:
|
||||
self._pkg_manifest_local: Optional[Dict[str, PkgManifest_Normalized]] = None
|
||||
self._pkg_manifest_remote: Optional[Dict[str, PkgManifest_Normalized]] = None
|
||||
self._pkg_manifest_remote_data_source: _RepoDataSouce_ABC = (
|
||||
_RepoDataSouce_JSON(directory) if remote_url else
|
||||
_RepoDataSouce_TOML_FILES(directory)
|
||||
_RepoDataSouce_JSON(directory, filter_params) if remote_url else
|
||||
_RepoDataSouce_TOML_FILES(directory, filter_params)
|
||||
)
|
||||
# Avoid many noisy prints.
|
||||
self._pkg_manifest_remote_has_warning = False
|
||||
@ -1691,11 +1717,16 @@ class _RepoCacheEntry:
|
||||
class RepoCacheStore:
|
||||
__slots__ = (
|
||||
"_repos",
|
||||
"_filter_params",
|
||||
"_is_init",
|
||||
)
|
||||
|
||||
def __init__(self) -> None:
|
||||
def __init__(self, blender_version: Tuple[int, int, int]) -> None:
|
||||
self._repos: List[_RepoCacheEntry] = []
|
||||
self._filter_params = PkgManifest_FilterParams(
|
||||
platform=platform_from_this_system(),
|
||||
blender_version=blender_version,
|
||||
)
|
||||
self._is_init = False
|
||||
|
||||
def is_init(self) -> bool:
|
||||
@ -1718,7 +1749,7 @@ class RepoCacheStore:
|
||||
for directory, remote_url in repos:
|
||||
repo_entry_test = repos_prev.get((directory, remote_url))
|
||||
if repo_entry_test is None:
|
||||
repo_entry_test = _RepoCacheEntry(directory, remote_url)
|
||||
repo_entry_test = _RepoCacheEntry(directory, remote_url, self._filter_params)
|
||||
self._repos.append(repo_entry_test)
|
||||
self._is_init = True
|
||||
|
||||
|
@ -307,6 +307,7 @@ class PkgManifest(NamedTuple):
|
||||
copyright: Optional[List[str]] = None
|
||||
permissions: Optional[List[str]] = None
|
||||
tags: Optional[List[str]] = None
|
||||
platforms: Optional[List[str]] = None
|
||||
wheels: Optional[List[str]] = None
|
||||
|
||||
|
||||
@ -1490,6 +1491,7 @@ pkg_manifest_known_keys_and_types: Tuple[
|
||||
# Type should be `dict` eventually, some existing packages will have a list of strings instead.
|
||||
("permissions", (dict, list), pkg_manifest_validate_field_permissions),
|
||||
("tags", list, pkg_manifest_validate_field_any_non_empty_list_of_non_empty_strings),
|
||||
("platforms", list, pkg_manifest_validate_field_any_non_empty_list_of_non_empty_strings),
|
||||
("wheels", list, pkg_manifest_validate_field_wheels),
|
||||
)
|
||||
|
||||
@ -1611,6 +1613,99 @@ def pkg_manifest_is_valid_or_error_all(
|
||||
# -----------------------------------------------------------------------------
|
||||
# Standalone Utilities
|
||||
|
||||
def platform_from_this_system() -> str:
|
||||
import platform
|
||||
system_replace = {
|
||||
"darwin": "macos",
|
||||
}
|
||||
machine_replace = {
|
||||
"x86_64": "x64",
|
||||
"amd64": "x64",
|
||||
}
|
||||
system = platform.system().lower()
|
||||
machine = platform.machine().lower()
|
||||
return "{:s}-{:s}".format(
|
||||
system_replace.get(system, system),
|
||||
machine_replace.get(machine, machine),
|
||||
)
|
||||
|
||||
|
||||
def repository_filter_skip(
|
||||
item: Dict[str, Any],
|
||||
*,
|
||||
filter_blender_version: Tuple[int, int, int],
|
||||
filter_platform: str,
|
||||
error_fn: Callable[[Exception], None],
|
||||
) -> bool:
|
||||
if (platforms := item.get("platforms")) is not None:
|
||||
if not isinstance(platforms, list):
|
||||
# Possibly noisy, but this should *not* be happening on a regular basis.
|
||||
error_fn(TypeError("platforms is not a list, found a: {:s}".format(str(type(platforms)))))
|
||||
elif platforms and (filter_platform not in platforms):
|
||||
return True
|
||||
|
||||
if filter_blender_version != (0, 0, 0):
|
||||
version_min_str = item.get("blender_version_min")
|
||||
version_max_str = item.get("blender_version_max")
|
||||
|
||||
if not (isinstance(version_min_str, str) or version_min_str is None):
|
||||
error_fn(TypeError("blender_version_min expected a string, found: {:s}".format(str(type(version_min_str)))))
|
||||
version_min_str = None
|
||||
if not (isinstance(version_max_str, str) or version_max_str is None):
|
||||
error_fn(TypeError("blender_version_max expected a string, found: {:s}".format(str(type(version_max_str)))))
|
||||
version_max_str = None
|
||||
|
||||
if version_min_str is None:
|
||||
version_min = None
|
||||
elif isinstance(version_min := blender_version_parse_any_or_error(version_min_str), str):
|
||||
error_fn(TypeError("blender_version_min invalid format: {:s}".format(version_min)))
|
||||
version_min = None
|
||||
|
||||
if version_max_str is None:
|
||||
version_max = None
|
||||
elif isinstance(version_max := blender_version_parse_any_or_error(version_max_str), str):
|
||||
error_fn(TypeError("blender_version_max invalid format: {:s}".format(version_max)))
|
||||
version_max = None
|
||||
|
||||
del version_min_str, version_max_str
|
||||
|
||||
assert (isinstance(version_min, tuple) or version_min is None)
|
||||
assert (isinstance(version_max, tuple) or version_max is None)
|
||||
|
||||
if (version_min is not None) and (filter_blender_version < version_min):
|
||||
# Blender is older than the packages minimum supported version.
|
||||
return True
|
||||
if (version_max is not None) and (filter_blender_version >= version_max):
|
||||
# Blender is newer or equal to the maximum value.
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def blender_version_parse_or_error(version: str) -> Union[Tuple[int, int, int], str]:
|
||||
try:
|
||||
version_tuple: Tuple[int, ...] = tuple(int(x) for x in version.split("."))
|
||||
except Exception as ex:
|
||||
return "unable to parse blender version: {:s}, {:s}".format(version, str(ex))
|
||||
|
||||
if not version_tuple:
|
||||
return "unable to parse empty blender version: {:s}".format(version)
|
||||
|
||||
# `mypy` can't detect that this is guaranteed to be 3 items.
|
||||
return (
|
||||
version_tuple if (len(version_tuple) == 3) else
|
||||
(*version_tuple, (0, 0))[:3] # type: ignore
|
||||
)
|
||||
|
||||
|
||||
def blender_version_parse_any_or_error(version: Any) -> Union[Tuple[int, int, int], str]:
|
||||
if not isinstance(version, str):
|
||||
return "blender version should be a string, found a: {:s}".format(str(type(version)))
|
||||
|
||||
result = blender_version_parse_or_error(version)
|
||||
assert isinstance(result, (tuple, str))
|
||||
return result
|
||||
|
||||
|
||||
def url_request_headers_create(*, accept_json: bool, user_agent: str, access_token: str) -> Dict[str, str]:
|
||||
headers = {}
|
||||
@ -1694,6 +1789,107 @@ 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]:
|
||||
"""
|
||||
When a repository includes multiple packages with the same ID, ensure they don't conflict.
|
||||
|
||||
Ensure packages have non-overlapping:
|
||||
- Platforms.
|
||||
- Blender versions.
|
||||
|
||||
Return an error if they do, otherwise None.
|
||||
"""
|
||||
|
||||
# Dummy ranges for the purpose of valid comparisons.
|
||||
dummy_verion_min = 0, 0, 0
|
||||
dummy_verion_max = 1000, 0, 0
|
||||
|
||||
def parse_version_or_default(version: Optional[str], default: Tuple[int, int, int]) -> Tuple[int, int, int]:
|
||||
if version is None:
|
||||
return default
|
||||
if isinstance(version_parsed := blender_version_parse_or_error(version), str):
|
||||
# NOTE: any error here will have already been handled.
|
||||
assert False, "unreachable"
|
||||
return default
|
||||
return version_parsed
|
||||
|
||||
def version_range_as_str(version_min: Tuple[int, int, int], version_max: Tuple[int, int, int]) -> str:
|
||||
dummy_min = version_min == dummy_verion_min
|
||||
dummy_max = version_max == dummy_verion_max
|
||||
if dummy_min and dummy_max:
|
||||
return "[undefined]"
|
||||
version_min_str = "..." if dummy_min else "{:d}.{:d}.{:d}".format(*version_min)
|
||||
version_max_str = "..." if dummy_max else "{:d}.{:d}.{:d}".format(*version_max)
|
||||
return "[{:s} -> {:s}]".format(version_min_str, version_max_str)
|
||||
|
||||
# Sort for predictable output.
|
||||
platforms_all = tuple(sorted(set(
|
||||
platform
|
||||
for manifest in pkg_items
|
||||
for platform in (manifest.platforms or ())
|
||||
)))
|
||||
|
||||
manifest_per_platform: Dict[str, List[PkgManifest]] = {platform: [] for platform in platforms_all}
|
||||
if platforms_all:
|
||||
for manifest in pkg_items:
|
||||
# No platforms means all platforms.
|
||||
for platform in (manifest.platforms or platforms_all):
|
||||
manifest_per_platform[platform].append(manifest)
|
||||
else:
|
||||
manifest_per_platform[""] = pkg_items
|
||||
|
||||
# Packages have been split by platform, now detect version overlap.
|
||||
platform_dupliates = {}
|
||||
for platform, pkg_items_platform in manifest_per_platform.items():
|
||||
# Must never be empty.
|
||||
assert pkg_items_platform
|
||||
if len(pkg_items_platform) == 1:
|
||||
continue
|
||||
|
||||
version_ranges: List[Tuple[Tuple[int, int, int], Tuple[int, int, int]]] = []
|
||||
for manifest in pkg_items_platform:
|
||||
version_ranges.append((
|
||||
parse_version_or_default(manifest.blender_version_min, dummy_verion_min),
|
||||
parse_version_or_default(manifest.blender_version_max, dummy_verion_max),
|
||||
))
|
||||
# Sort by the version range so overlaps can be detected between adjacent members.
|
||||
version_ranges.sort()
|
||||
|
||||
duplicates_found = []
|
||||
item_prev = version_ranges[0]
|
||||
for i in range(1, len(version_ranges)):
|
||||
item_curr = version_ranges[i]
|
||||
|
||||
# Previous maximum is less than or equal to the current minimum, no overlap.
|
||||
if not (item_prev[1] <= item_curr[0]):
|
||||
duplicates_found.append("{:s} & {:s}".format(
|
||||
version_range_as_str(*item_prev),
|
||||
version_range_as_str(*item_curr),
|
||||
))
|
||||
item_prev = item_curr
|
||||
|
||||
if duplicates_found:
|
||||
platform_dupliates[platform] = duplicates_found
|
||||
|
||||
if platform_dupliates:
|
||||
# Simpler, no platforms.
|
||||
if platforms_all:
|
||||
error_text = ", ".join([
|
||||
"\"{:s}\": ({:s})".format(platform, ", ".join(errors))
|
||||
for platform, errors in platform_dupliates.items()
|
||||
])
|
||||
else:
|
||||
error_text = ", ".join(platform_dupliates[""])
|
||||
|
||||
return "{:d} duplicate(s) found, conflicting blender versions {:s}".format(
|
||||
sum(map(len, platform_dupliates.values())),
|
||||
error_text,
|
||||
)
|
||||
|
||||
# No collisions found.
|
||||
return None
|
||||
|
||||
|
||||
def toml_from_bytes(data: bytes) -> Optional[Dict[str, Any]]:
|
||||
result = tomllib.loads(data.decode('utf-8'))
|
||||
assert isinstance(result, dict)
|
||||
@ -1975,6 +2171,19 @@ def generic_arg_local_dir(subparse: argparse.ArgumentParser) -> None:
|
||||
)
|
||||
|
||||
|
||||
def generic_arg_blender_version(subparse: argparse.ArgumentParser) -> None:
|
||||
subparse.add_argument(
|
||||
"--blender-version",
|
||||
dest="blender_version",
|
||||
default="0.0.0",
|
||||
type=str,
|
||||
help=(
|
||||
"The version of Blender used for selecting packages."
|
||||
),
|
||||
required=False,
|
||||
)
|
||||
|
||||
|
||||
# Only for authoring.
|
||||
def generic_arg_package_source_path_positional(subparse: argparse.ArgumentParser) -> None:
|
||||
subparse.add_argument(
|
||||
@ -2176,7 +2385,7 @@ class subcmd_server:
|
||||
message_error(msg_fn, "Directory: {!r} not found!".format(repo_dir))
|
||||
return False
|
||||
|
||||
repo_data_idname_unique: Set[str] = set()
|
||||
repo_data_idname_map: Dict[str, List[PkgManifest]] = {}
|
||||
repo_data: List[Dict[str, Any]] = []
|
||||
# Write package meta-data into each directory.
|
||||
repo_gen_dict = {
|
||||
@ -2204,11 +2413,10 @@ class subcmd_server:
|
||||
continue
|
||||
manifest_dict = manifest._asdict()
|
||||
|
||||
repo_data_idname_unique_len = len(repo_data_idname_unique)
|
||||
repo_data_idname_unique.add(manifest_dict["id"])
|
||||
if len(repo_data_idname_unique) == repo_data_idname_unique_len:
|
||||
message_warn(msg_fn, "archive found with duplicate id {!r}, {!r}".format(manifest_dict["id"], filepath))
|
||||
continue
|
||||
pkg_idname = manifest_dict["id"]
|
||||
if (pkg_items := repo_data_idname_map.get(pkg_idname)) is None:
|
||||
pkg_items = repo_data_idname_map[pkg_idname] = []
|
||||
pkg_items.append(manifest)
|
||||
|
||||
# Call all optional keys so the JSON never contains `null` items.
|
||||
for key, value in list(manifest_dict.items()):
|
||||
@ -2239,6 +2447,15 @@ class subcmd_server:
|
||||
|
||||
repo_data.append(manifest_dict)
|
||||
|
||||
# Detect duplicates:
|
||||
# repo_data_idname_map
|
||||
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:
|
||||
message_warn(msg_fn, "archive found with duplicates for id {:s}: {:s}".format(pkg_idname, error))
|
||||
del repo_data_idname_map
|
||||
|
||||
filepath_repo_json = os.path.join(repo_dir, PKG_REPO_LIST_FILENAME)
|
||||
|
||||
with open(filepath_repo_json, "w", encoding="utf-8") as fh:
|
||||
@ -2484,6 +2701,7 @@ class subcmd_client:
|
||||
local_cache: bool,
|
||||
packages: Sequence[str],
|
||||
online_user_agent: str,
|
||||
blender_version: str,
|
||||
access_token: str,
|
||||
timeout_in_seconds: float,
|
||||
) -> bool:
|
||||
@ -2493,6 +2711,11 @@ class subcmd_client:
|
||||
message_error(msg_fn, error)
|
||||
return False
|
||||
|
||||
if isinstance(blender_version_tuple := blender_version_parse_or_error(blender_version), str):
|
||||
message_error(msg_fn, blender_version_tuple)
|
||||
return False
|
||||
assert isinstance(blender_version_tuple, tuple)
|
||||
|
||||
# Extract...
|
||||
pkg_repo_data = repo_pkginfo_from_local(local_dir=local_dir)
|
||||
if pkg_repo_data is None:
|
||||
@ -2521,6 +2744,8 @@ class subcmd_client:
|
||||
for pkg_info in json_data_pkg_info:
|
||||
json_data_pkg_info_map[pkg_info["id"]].append(pkg_info)
|
||||
|
||||
platform_this = platform_from_this_system()
|
||||
|
||||
has_error = False
|
||||
packages_info: List[PkgManifest_Archive] = []
|
||||
for pkg_idname, pkg_info_list in json_data_pkg_info_map.items():
|
||||
@ -2529,6 +2754,24 @@ class subcmd_client:
|
||||
has_error = True
|
||||
continue
|
||||
|
||||
def error_handle(ex: Exception) -> None:
|
||||
message_warn(msg_fn, "{:s}: {:s}".format(pkg_idname, str(ex)))
|
||||
|
||||
pkg_info_list = [
|
||||
pkg_info for pkg_info in pkg_info_list
|
||||
if not repository_filter_skip(
|
||||
pkg_info,
|
||||
filter_blender_version=blender_version_tuple,
|
||||
filter_platform=platform_this,
|
||||
error_fn=error_handle,
|
||||
)
|
||||
]
|
||||
|
||||
if not pkg_info_list:
|
||||
message_error(msg_fn, "Package \"{:s}\", found but not compatible with this system".format(pkg_idname))
|
||||
has_error = True
|
||||
continue
|
||||
|
||||
# TODO: use a tie breaker.
|
||||
pkg_info = pkg_info_list[0]
|
||||
|
||||
@ -3301,6 +3544,7 @@ def argparse_create_client_install(subparsers: "argparse._SubParsersAction[argpa
|
||||
generic_arg_local_dir(subparse)
|
||||
generic_arg_local_cache(subparse)
|
||||
generic_arg_online_user_agent(subparse)
|
||||
generic_arg_blender_version(subparse)
|
||||
generic_arg_access_token(subparse)
|
||||
|
||||
generic_arg_output_type(subparse)
|
||||
@ -3314,6 +3558,7 @@ def argparse_create_client_install(subparsers: "argparse._SubParsersAction[argpa
|
||||
local_cache=args.local_cache,
|
||||
packages=args.packages.split(","),
|
||||
online_user_agent=args.online_user_agent,
|
||||
blender_version=args.blender_version,
|
||||
access_token=args.access_token,
|
||||
timeout_in_seconds=args.timeout,
|
||||
),
|
||||
|
@ -5,7 +5,9 @@
|
||||
"""
|
||||
This test emulates running packaging commands with Blender via the command line.
|
||||
|
||||
This also happens to test packages with ``*.whl``.
|
||||
This also happens to test:
|
||||
- Packages with ``*.whl``.
|
||||
- Packages compatibility (mixing supported/unsupported platforms & versions).
|
||||
|
||||
Command to run this test:
|
||||
make test_cli_blender BLENDER_BIN=$PWD/../../../blender.bin
|
||||
@ -21,12 +23,18 @@ import time
|
||||
import unittest
|
||||
|
||||
from typing import (
|
||||
Any,
|
||||
Dict,
|
||||
NamedTuple,
|
||||
Optional,
|
||||
Sequence,
|
||||
Tuple,
|
||||
)
|
||||
|
||||
|
||||
# For more useful output that isn't clipped.
|
||||
unittest.util._MAX_LENGTH = 10_000
|
||||
|
||||
PKG_MANIFEST_FILENAME_TOML = "blender_manifest.toml"
|
||||
|
||||
VERBOSE_CMD = False
|
||||
@ -36,6 +44,10 @@ BLENDER_BIN = os.environ.get("BLENDER_BIN")
|
||||
if BLENDER_BIN is None:
|
||||
raise Exception("BLENDER_BIN: environment variable not defined")
|
||||
|
||||
BLENDER_VERSION_STR = subprocess.check_output([BLENDER_BIN, "--version"]).split()[1].decode('ascii')
|
||||
BLENDER_VERSION: Tuple[int, int, int] = tuple(int(x) for x in BLENDER_VERSION_STR.split(".")) # type: ignore
|
||||
assert len(BLENDER_VERSION) == 3
|
||||
|
||||
|
||||
# Arguments to ensure extensions are enabled (currently it's an experimental feature).
|
||||
BLENDER_ENABLE_EXTENSION_ARGS = [
|
||||
@ -48,6 +60,27 @@ sys.path.append(os.path.join(BASE_DIR, "modules"))
|
||||
import python_wheel_generate # noqa: E402
|
||||
|
||||
|
||||
# Don't import as module, instead load the class.
|
||||
def execfile(filepath: str, *, name: str = "__main__") -> Dict[str, Any]:
|
||||
global_namespace = {"__file__": filepath, "__name__": name}
|
||||
with open(filepath, encoding="utf-8") as file_handle:
|
||||
exec(compile(file_handle.read(), filepath, 'exec'), global_namespace)
|
||||
return global_namespace
|
||||
|
||||
|
||||
_blender_ext = execfile(
|
||||
os.path.join(
|
||||
BASE_DIR,
|
||||
"..",
|
||||
"cli",
|
||||
"blender_ext.py",
|
||||
),
|
||||
name="blender_ext",
|
||||
)
|
||||
platform_from_this_system = _blender_ext["platform_from_this_system"]
|
||||
assert callable(platform_from_this_system)
|
||||
|
||||
|
||||
# Write the command to a script, use so it's possible to manually run commands outside of the test environment.
|
||||
TEMP_COMMAND_OUTPUT = "" # os.path.join(tempfile.gettempdir(), "blender_test.sh")
|
||||
|
||||
@ -58,6 +91,37 @@ USE_PAUSE_BEFORE_EXIT = False
|
||||
# -----------------------------------------------------------------------------
|
||||
# Utility Functions
|
||||
|
||||
|
||||
# Generate different version numbers as strings, used for automatically creating versions
|
||||
# which are known to be compatible or incompatible with the current version.
|
||||
def blender_version_relative(version_offset: Tuple[int, int, int]) -> str:
|
||||
version_new = (
|
||||
BLENDER_VERSION[0] + version_offset[0],
|
||||
BLENDER_VERSION[1] + version_offset[1],
|
||||
BLENDER_VERSION[2] + version_offset[2],
|
||||
)
|
||||
assert min(*version_new) >= 0
|
||||
return "{:d}.{:d}.{:d}".format(*version_new)
|
||||
|
||||
|
||||
def python_script_generate_for_addon(text: str) -> str:
|
||||
return (
|
||||
'''def register():\n'''
|
||||
''' print("Register success{sep:s}{text:s}:", __name__)\n'''
|
||||
'''\n'''
|
||||
'''def unregister():\n'''
|
||||
''' print("Unregister success{sep:s}{text:s}:", __name__)\n'''
|
||||
).format(
|
||||
sep=" " if text else "",
|
||||
text=text,
|
||||
)
|
||||
|
||||
|
||||
class WheelModuleParams(NamedTuple):
|
||||
module_name: str
|
||||
module_version: str
|
||||
|
||||
|
||||
def path_to_url(path: str) -> str:
|
||||
from urllib.parse import urljoin
|
||||
from urllib.request import pathname2url
|
||||
@ -67,7 +131,7 @@ def path_to_url(path: str) -> str:
|
||||
def pause_until_keyboard_interrupt() -> None:
|
||||
print("Waiting for keyboard interrupt...")
|
||||
try:
|
||||
time.sleep(10_000)
|
||||
time.sleep(100_000)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
print("Exiting!")
|
||||
@ -93,26 +157,34 @@ def contents_to_filesystem(
|
||||
|
||||
def create_package(
|
||||
pkg_src_dir: str,
|
||||
*,
|
||||
pkg_idname: str,
|
||||
wheel_module_name: str,
|
||||
wheel_module_version: str,
|
||||
|
||||
# Optional.
|
||||
wheel_params: Optional[WheelModuleParams] = None,
|
||||
platforms: Optional[Tuple[str, ...]] = None,
|
||||
blender_version_min: Optional[str] = None,
|
||||
blender_version_max: Optional[str] = None,
|
||||
python_script: Optional[str] = None,
|
||||
) -> None:
|
||||
pkg_name = pkg_idname.replace("_", " ").title()
|
||||
|
||||
wheel_filename, wheel_filedata = python_wheel_generate.generate_from_source(
|
||||
module_name=wheel_module_name,
|
||||
version=wheel_module_version,
|
||||
source=(
|
||||
"__version__ = {!r}\n"
|
||||
"print(\"The wheel has been found\")\n"
|
||||
).format(wheel_module_version),
|
||||
)
|
||||
if wheel_params is not None:
|
||||
wheel_filename, wheel_filedata = python_wheel_generate.generate_from_source(
|
||||
module_name=wheel_params.module_name,
|
||||
version=wheel_params.module_version,
|
||||
source=(
|
||||
"__version__ = {!r}\n"
|
||||
"print(\"The wheel has been found\")\n"
|
||||
).format(wheel_params.module_version),
|
||||
)
|
||||
|
||||
wheel_dir = os.path.join(pkg_src_dir, "wheels")
|
||||
os.makedirs(wheel_dir, exist_ok=True)
|
||||
path = os.path.join(wheel_dir, wheel_filename)
|
||||
with open(path, "wb") as fh:
|
||||
fh.write(wheel_filedata)
|
||||
wheel_dir = os.path.join(pkg_src_dir, "wheels")
|
||||
os.makedirs(wheel_dir, exist_ok=True)
|
||||
|
||||
wheel_path = os.path.join(wheel_dir, wheel_filename)
|
||||
with open(wheel_path, "wb") as fh:
|
||||
fh.write(wheel_filedata)
|
||||
|
||||
with open(os.path.join(pkg_src_dir, PKG_MANIFEST_FILENAME_TOML), "w", encoding="utf-8") as fh:
|
||||
fh.write('''# Example\n''')
|
||||
@ -125,19 +197,25 @@ def create_package(
|
||||
fh.write('''license = ["SPDX:GPL-2.0-or-later"]\n''')
|
||||
fh.write('''version = "1.0.0"\n''')
|
||||
fh.write('''tagline = "This is a tagline"\n''')
|
||||
fh.write('''blender_version_min = "0.0.0"\n''')
|
||||
fh.write('''blender_version_min = "{:s}"\n'''.format(blender_version_min or "0.0.0"))
|
||||
if blender_version_min is not None:
|
||||
fh.write('''blender_version_max = "{:s}"\n'''.format(blender_version_max))
|
||||
fh.write('''\n''')
|
||||
fh.write('''wheels = ["./wheels/{:s}"]\n'''.format(wheel_filename))
|
||||
|
||||
if wheel_params is not None:
|
||||
fh.write('''wheels = ["./wheels/{:s}"]\n'''.format(wheel_filename))
|
||||
|
||||
if platforms is not None:
|
||||
fh.write('''platforms = [{:s}]\n'''.format(", ".join(["\"{:s}\"".format(x) for x in platforms])))
|
||||
|
||||
with open(os.path.join(pkg_src_dir, "__init__.py"), "w", encoding="utf-8") as fh:
|
||||
fh.write((
|
||||
'''import {:s}\n'''
|
||||
'''def register():\n'''
|
||||
''' print("Register success:", __name__)\n'''
|
||||
'''\n'''
|
||||
'''def unregister():\n'''
|
||||
''' print("Unregister success:", __name__)\n'''
|
||||
).format(wheel_module_name))
|
||||
if wheel_params is not None:
|
||||
fh.write("import {:s}\n".format(wheel_params.module_name))
|
||||
|
||||
if python_script is not None:
|
||||
fh.write(python_script)
|
||||
else:
|
||||
fh.write(python_script_generate_for_addon(text=""))
|
||||
|
||||
|
||||
def run_blender(
|
||||
@ -275,34 +353,64 @@ user_dirs: Tuple[str, ...] = (
|
||||
|
||||
class TestWithTempBlenderUser_MixIn(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
@staticmethod
|
||||
def _repo_dirs_create() -> None:
|
||||
for dirname in user_dirs:
|
||||
os.makedirs(os.path.join(TEMP_DIR_BLENDER_USER, dirname), exist_ok=True)
|
||||
os.makedirs(os.path.join(TEMP_DIR_BLENDER_USER, dirname), exist_ok=True)
|
||||
os.makedirs(TEMP_DIR_REMOTE, exist_ok=True)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls) -> None:
|
||||
@staticmethod
|
||||
def _repo_dirs_destroy() -> None:
|
||||
for dirname in user_dirs:
|
||||
shutil.rmtree(os.path.join(TEMP_DIR_BLENDER_USER, dirname))
|
||||
shutil.rmtree(TEMP_DIR_REMOTE)
|
||||
|
||||
def setUp(self) -> None:
|
||||
self._repo_dirs_create()
|
||||
|
||||
class TestSimple(TestWithTempBlenderUser_MixIn, unittest.TestCase):
|
||||
def tearDown(self) -> None:
|
||||
self._repo_dirs_destroy()
|
||||
|
||||
# Internal utilities.
|
||||
def _build_package(
|
||||
def repo_add(self, *, repo_id: str, repo_name: str) -> None:
|
||||
stdout = run_blender_extensions_no_errors((
|
||||
"repo-add",
|
||||
"--name", repo_name,
|
||||
"--directory", TEMP_DIR_LOCAL,
|
||||
"--url", TEMP_DIR_REMOTE_AS_URL,
|
||||
# A bit odd, this argument avoids running so many commands to setup a test.
|
||||
"--clear-all",
|
||||
repo_id,
|
||||
))
|
||||
self.assertEqual(stdout, "Info: Preferences saved\n")
|
||||
|
||||
def build_package(
|
||||
self,
|
||||
*,
|
||||
pkg_idname: str,
|
||||
wheel_module_name: str,
|
||||
wheel_module_version: str,
|
||||
wheel_params: Optional[WheelModuleParams] = None,
|
||||
|
||||
# Optional.
|
||||
pkg_filename: Optional[str] = None,
|
||||
platforms: Optional[Tuple[str, ...]] = None,
|
||||
blender_version_min: Optional[str] = None,
|
||||
blender_version_max: Optional[str] = None,
|
||||
python_script: Optional[str] = None,
|
||||
) -> None:
|
||||
pkg_output_filepath = os.path.join(TEMP_DIR_REMOTE, pkg_idname + ".zip")
|
||||
if pkg_filename is None:
|
||||
pkg_filename = pkg_idname
|
||||
pkg_output_filepath = os.path.join(TEMP_DIR_REMOTE, pkg_filename + ".zip")
|
||||
with tempfile.TemporaryDirectory() as package_build_dir:
|
||||
create_package(
|
||||
package_build_dir,
|
||||
pkg_idname=pkg_idname,
|
||||
wheel_module_name=wheel_module_name,
|
||||
wheel_module_version=wheel_module_version,
|
||||
|
||||
# Optional.
|
||||
wheel_params=wheel_params,
|
||||
platforms=platforms,
|
||||
blender_version_min=blender_version_min,
|
||||
blender_version_max=blender_version_max,
|
||||
python_script=python_script,
|
||||
)
|
||||
stdout = run_blender_extensions_no_errors((
|
||||
"build",
|
||||
@ -315,35 +423,32 @@ class TestSimple(TestWithTempBlenderUser_MixIn, unittest.TestCase):
|
||||
"Building {:s}.zip\n"
|
||||
"complete\n"
|
||||
"created \"{:s}\", {:d}\n"
|
||||
).format(pkg_idname, pkg_output_filepath, os.path.getsize(pkg_output_filepath)),
|
||||
).format(pkg_filename, pkg_output_filepath, os.path.getsize(pkg_output_filepath)),
|
||||
)
|
||||
|
||||
|
||||
class TestSimple(TestWithTempBlenderUser_MixIn, unittest.TestCase):
|
||||
|
||||
def test_simple_package(self) -> None:
|
||||
"""
|
||||
Create a simple package and install it.
|
||||
"""
|
||||
|
||||
repo_id = "test_repo_module_name"
|
||||
repo_name = "MyTestRepo"
|
||||
|
||||
stdout = run_blender_extensions_no_errors((
|
||||
"repo-add",
|
||||
"--name", "MyTestRepo",
|
||||
"--directory", TEMP_DIR_LOCAL,
|
||||
"--url", TEMP_DIR_REMOTE_AS_URL,
|
||||
# A bit odd, this argument avoids running so many commands to setup a test.
|
||||
"--clear-all",
|
||||
repo_id,
|
||||
))
|
||||
self.assertEqual(stdout, "Info: Preferences saved\n")
|
||||
self.repo_add(repo_id=repo_id, repo_name=repo_name)
|
||||
|
||||
wheel_module_name = "my_custom_wheel"
|
||||
|
||||
# Create a package contents.
|
||||
pkg_idname = "my_test_pkg"
|
||||
self._build_package(
|
||||
self.build_package(
|
||||
pkg_idname=pkg_idname,
|
||||
wheel_module_name=wheel_module_name,
|
||||
wheel_module_version="1.0.1",
|
||||
wheel_params=WheelModuleParams(
|
||||
module_name=wheel_module_name,
|
||||
module_version="1.0.1",
|
||||
),
|
||||
)
|
||||
|
||||
# Generate the repository.
|
||||
@ -367,19 +472,19 @@ class TestSimple(TestWithTempBlenderUser_MixIn, unittest.TestCase):
|
||||
self.assertEqual(
|
||||
stdout,
|
||||
(
|
||||
'''test_repo_module_name:\n'''
|
||||
'''{:s}:\n'''
|
||||
''' name: "MyTestRepo"\n'''
|
||||
''' directory: "{:s}"\n'''
|
||||
''' url: "{:s}"\n'''
|
||||
).format(TEMP_DIR_LOCAL, TEMP_DIR_REMOTE_AS_URL))
|
||||
).format(repo_id, TEMP_DIR_LOCAL, TEMP_DIR_REMOTE_AS_URL))
|
||||
|
||||
stdout = run_blender_extensions_no_errors(("list",))
|
||||
self.assertEqual(
|
||||
stdout,
|
||||
(
|
||||
'''Repository: "MyTestRepo" (id=test_repo_module_name)\n'''
|
||||
'''Repository: "MyTestRepo" (id={:s})\n'''
|
||||
''' my_test_pkg: "My Test Pkg", This is a tagline\n'''
|
||||
)
|
||||
).format(repo_id)
|
||||
)
|
||||
|
||||
stdout = run_blender_extensions_no_errors(("install", pkg_idname, "--enable"))
|
||||
@ -424,10 +529,12 @@ class TestSimple(TestWithTempBlenderUser_MixIn, unittest.TestCase):
|
||||
("my_test_pkg_c", "3.0.1"),
|
||||
):
|
||||
packages_to_install.append(pkg_idname)
|
||||
self._build_package(
|
||||
self.build_package(
|
||||
pkg_idname=pkg_idname,
|
||||
wheel_module_name=wheel_module_name,
|
||||
wheel_module_version=wheel_module_version,
|
||||
wheel_params=WheelModuleParams(
|
||||
module_name=wheel_module_name,
|
||||
module_version=wheel_module_version,
|
||||
),
|
||||
)
|
||||
|
||||
# Generate the repository.
|
||||
@ -488,6 +595,156 @@ class TestSimple(TestWithTempBlenderUser_MixIn, unittest.TestCase):
|
||||
pause_until_keyboard_interrupt()
|
||||
|
||||
|
||||
class TestPlatform(TestWithTempBlenderUser_MixIn, unittest.TestCase):
|
||||
def test_platform_filter(self) -> None:
|
||||
"""
|
||||
Check that packages from different platforms are properly filtered.
|
||||
"""
|
||||
platforms_other = ["linux-x64", "macos-arm64", "windows-x64"]
|
||||
|
||||
platform_this = platform_from_this_system()
|
||||
|
||||
if platform_this in platforms_other:
|
||||
platforms_other.remove(platform_this)
|
||||
else: # For a predictable length.
|
||||
del platforms_other[-1]
|
||||
assert len(platforms_other) == 2
|
||||
|
||||
# Create two packages with the same ID, and ensure the package seen by Blender is the one for our platform.
|
||||
|
||||
repo_id = "test_repo_module_name"
|
||||
repo_name = "MyTestRepo"
|
||||
|
||||
self.repo_add(repo_id=repo_id, repo_name=repo_name)
|
||||
|
||||
# Create a range of versions, note that only minimum versions beginning
|
||||
# with `version_c_this` and higher can be installed with this Blender session.
|
||||
version_a = blender_version_relative((-2, 0, 0))
|
||||
version_b = blender_version_relative((-1, 0, 0))
|
||||
version_c_this = blender_version_relative((0, 0, 0))
|
||||
version_d = blender_version_relative((0, 1, 0))
|
||||
version_e = blender_version_relative((0, 2, 0))
|
||||
|
||||
python_script_this = python_script_generate_for_addon("for this platform")
|
||||
python_script_old = python_script_generate_for_addon("old")
|
||||
python_script_new = python_script_generate_for_addon("new")
|
||||
python_script_other = python_script_generate_for_addon("other")
|
||||
python_script_conflict = python_script_generate_for_addon("conflict")
|
||||
|
||||
# Create a package contents (with a different wheel version).
|
||||
pkg_idname = "my_platform_test"
|
||||
for platform in (platform_this, *platforms_other):
|
||||
if platform == platform_this:
|
||||
python_script = python_script_this
|
||||
else:
|
||||
python_script = python_script_other
|
||||
|
||||
self.build_package(
|
||||
pkg_idname=pkg_idname,
|
||||
platforms=(platform,),
|
||||
# Needed to prevent duplicates.
|
||||
pkg_filename="{:s}-{:s}".format(pkg_idname, platform.replace("-", "_")),
|
||||
blender_version_min=version_c_this,
|
||||
blender_version_max=version_d,
|
||||
python_script=python_script,
|
||||
)
|
||||
|
||||
# Generate the repository.
|
||||
stdout = run_blender_extensions_no_errors((
|
||||
"server-generate",
|
||||
"--repo-dir", TEMP_DIR_REMOTE,
|
||||
))
|
||||
self.assertEqual(stdout, "found 3 packages.\n")
|
||||
|
||||
for version_range, pkg_filename_suffix, python_script in (
|
||||
((version_a, version_b), "_no_conflict_old", python_script_old),
|
||||
((version_d, version_e), "_no_conflict_new", python_script_new),
|
||||
):
|
||||
self.build_package(
|
||||
pkg_idname=pkg_idname,
|
||||
platforms=(platform_this,),
|
||||
pkg_filename="{:s}-{:s}{:s}".format(pkg_idname, platform_this.replace("-", "_"), pkg_filename_suffix),
|
||||
blender_version_min=version_range[0],
|
||||
blender_version_max=version_range[1],
|
||||
python_script=python_script + "\n" + "print(" + repr(version_range) + ")",
|
||||
)
|
||||
|
||||
# Re-generate the repository (no conflicts).
|
||||
stdout = run_blender_extensions_no_errors((
|
||||
"server-generate",
|
||||
"--repo-dir", TEMP_DIR_REMOTE,
|
||||
))
|
||||
self.assertEqual(stdout, "found 5 packages.\n")
|
||||
|
||||
# Install the package and check it installs the correct package.
|
||||
stdout = run_blender_extensions_no_errors((
|
||||
"sync",
|
||||
))
|
||||
self.assertEqual(
|
||||
stdout.rstrip("\n").split("\n")[-1],
|
||||
"STATUS Extensions list for \"MyTestRepo\" updated",
|
||||
)
|
||||
|
||||
stdout = run_blender_extensions_no_errors(("list",))
|
||||
self.assertEqual(
|
||||
stdout,
|
||||
(
|
||||
'''Repository: "MyTestRepo" (id={:s})\n'''
|
||||
''' {:s}: "My Platform Test", This is a tagline\n'''
|
||||
).format(repo_id, pkg_idname)
|
||||
)
|
||||
|
||||
stdout = run_blender_extensions_no_errors(("install", pkg_idname, "--enable"), force_script_and_pause=False)
|
||||
self.assertEqual(
|
||||
[line for line in stdout.split("\n") if line.startswith("STATUS ")][0],
|
||||
"STATUS Installed \"{:s}\"".format(pkg_idname)
|
||||
)
|
||||
|
||||
# Ensure the correct package was installed, using the script text as an identifier.
|
||||
self.assertTrue("Register success for this platform: " in stdout)
|
||||
|
||||
stdout = run_blender_extensions_no_errors(("remove", pkg_idname))
|
||||
self.assertEqual(
|
||||
[line for line in stdout.split("\n") if line.startswith("STATUS ")][0],
|
||||
"STATUS Removed \"{:s}\"".format(pkg_idname)
|
||||
)
|
||||
|
||||
# Now add two conflicting packages, one with a version, one without any versions.
|
||||
for version_range, pkg_filename_suffix in (
|
||||
(("", ""), "_conflict_no_version"),
|
||||
((version_a, version_e), "_conflict"),
|
||||
):
|
||||
self.build_package(
|
||||
pkg_idname=pkg_idname,
|
||||
platforms=(platform_this,),
|
||||
pkg_filename="{:s}-{:s}{:s}".format(pkg_idname, platform_this.replace("-", "_"), pkg_filename_suffix),
|
||||
blender_version_min=version_range[0] or None,
|
||||
blender_version_max=version_range[1] or None,
|
||||
python_script=python_script_conflict,
|
||||
)
|
||||
|
||||
stdout = run_blender_extensions_no_errors((
|
||||
"server-generate",
|
||||
"--repo-dir", TEMP_DIR_REMOTE,
|
||||
))
|
||||
self.assertEqual(stdout, (
|
||||
'''WARN: archive found with duplicates for id {pkg_idname:s}: '''
|
||||
'''3 duplicate(s) found, conflicting blender versions \"{platform:s}\": '''
|
||||
'''([undefined] & [{version_a:s} -> {version_b:s}], '''
|
||||
'''[{version_a:s} -> {version_b:s}] & [{version_a:s} -> {version_e:s}], '''
|
||||
'''[{version_a:s} -> {version_e:s}] & [{version_c:s} -> {version_d:s}])\n'''
|
||||
'''found 7 packages.\n'''
|
||||
).format(
|
||||
pkg_idname=pkg_idname,
|
||||
platform=platform_this,
|
||||
version_a=version_a,
|
||||
version_b=version_b,
|
||||
version_c=version_c_this,
|
||||
version_d=version_d,
|
||||
version_e=version_e,
|
||||
))
|
||||
|
||||
|
||||
def main() -> None:
|
||||
global TEMP_DIR_BLENDER_USER
|
||||
global TEMP_DIR_REMOTE
|
||||
|
Loading…
Reference in New Issue
Block a user