update to 5.3.0

This commit is contained in:
Tom Callaway 2023-03-09 10:55:11 -05:00
commit d82c9680eb
9 changed files with 2795 additions and 7 deletions

63
CMakeLists.txt Normal file
View file

@ -0,0 +1,63 @@
project(uranium NONE)
cmake_minimum_required(VERSION 3.6)
message(STATUS ${CMAKE_MODULE_PATH})
set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/
${CMAKE_MODULE_PATH})
include(UraniumTranslationTools)
include(GNUInstallDirs)
find_package(PythonInterp 3 REQUIRED)
message("Using python version ${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}")
# # Checks using pylint
# Note that we use exit 0 here to not mark the build as a failure on check failure
# In addition, the specified pylint configuration uses the spellchecker plugin. This required python-enchant to be installed.
add_custom_target(check)
add_custom_command(TARGET check POST_BUILD COMMAND "PYTHONPATH=${CMAKE_SOURCE_DIR}" ${PYTHON_EXECUTABLE} -m pylint --rcfile=${CMAKE_SOURCE_DIR}/pylint.cfg UM --msg-template=\"{path}:{line}: [{msg_id}({symbol}) , {obj}] {msg}\" > ${CMAKE_BINARY_DIR}/pylint.log || exit 0 WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
# # Check using Mypy
add_custom_target(typecheck)
add_custom_command(TARGET typecheck POST_BUILD COMMAND ${PYTHON_EXECUTABLE} run_mypy.py WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
# # Tests
include(UraniumTests)
# # Benchmarks
# add_custom_target(benchmark)
# file(GLOB bench_files tests/benchmarks/*/profile*.py)
# foreach(file ${bench_files})
# add_custom_command(TARGET benchmark POST_BUILD COMMAND "PYTHONPATH=${CMAKE_SOURCE_DIR}" kernprof ARGS -l -v ${file})
# endforeach()
# Documentation
find_package(Doxygen)
if(${DOXYGEN_FOUND})
add_custom_target(doc ${DOXYGEN_EXECUTABLE} ${CMAKE_SOURCE_DIR}/Doxyfile WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
endif()
# Extract Strings
add_custom_target(extract-messages ${CMAKE_SOURCE_DIR}/scripts/extract-messages ${CMAKE_SOURCE_DIR} uranium)
# Build Translations
CREATE_TRANSLATION_TARGETS()
if(EXISTS /etc/debian_version)
install(DIRECTORY UM DESTINATION lib${LIB_SUFFIX}/python${PYTHON_VERSION_MAJOR}/dist-packages)
else()
install(DIRECTORY UM DESTINATION lib${LIB_SUFFIX}/python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}/site-packages)
endif()
install(FILES ${CMAKE_SOURCE_DIR}/cmake/UraniumTranslationTools.cmake
DESTINATION ${CMAKE_INSTALL_DATADIR}/cmake-${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}/Modules/ )
install(DIRECTORY resources DESTINATION ${CMAKE_INSTALL_DATADIR}/uranium)
# Detect plugins to install
include(UraniumPluginInstall)
include(CPackConfig.cmake)

34
CPackConfig.cmake Normal file
View file

@ -0,0 +1,34 @@
set(CPACK_PACKAGE_VENDOR "Ultimaker")
set(CPACK_PACKAGE_CONTACT "Arjen Hiemstra <a.hiemstra@ultimaker.com>")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Uranium 3D Application Framework")
set(CPACK_PACKAGE_VERSION_MAJOR 15)
set(CPACK_PACKAGE_VERSION_MINOR 05)
set(CPACK_PACKAGE_VERSION_PATCH 93)
set(CPACK_GENERATOR "DEB;RPM")
set(RPM_REQUIRES
"python3 >= 3.5.0"
"python3-qt5 >= 5.6.0"
"qt5-qtquickcontrols >= 5.6.0"
"arcus >= 15.05.90"
)
string(REPLACE ";" "," RPM_REQUIRES "${RPM_REQUIRES}")
set(CPACK_RPM_PACKAGE_REQUIRES ${RPM_REQUIRES})
set(DEB_DEPENDS
"python3 (>= 3.5.0)"
"python3-pyqt5 (>= 5.6.0)"
"python3-pyqt5.qtopengl (>= 5.6.0)"
"python3-pyqt5.qtquick (>= 5.6.0)"
"python3-pyqt5.qtsvg (>= 5.6.0)"
"qml-module-qtquick2 (>= 5.6.0)"
"qml-module-qtquick-window2 (>= 5.6.0)"
"qml-module-qtquick-layouts (>= 5.6.0)"
"qml-module-qtquick-dialogs (>= 5.6.0)"
"qml-module-qtquick-controls (>= 5.6.0)"
"arcus (>= 15.05.90)"
)
string(REPLACE ";" "," DEB_DEPENDS "${DEB_DEPENDS}")
set(CPACK_DEBIAN_PACKAGE_DEPENDS ${DEB_DEPENDS})
include(CPack)

2331
Doxyfile Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,99 @@
# Copyright (c) 2019 Ultimaker B.V.
# UraniumPluginInstall.cmake is released under the terms of the LGPLv3 or higher.
#
# This module detects all plugins that need to be installed and adds them using the CMake install() command.
# It detects all plugin folder in the path "plugins/*" where there's a "plugin.json" in it.
#
# Plugins can be configured to NOT BE INSTALLED via the variable "UM_NO_INSTALL_PLUGINS" as a list of string in the
# form of "a;b;c" or "a,b,c". By default all plugins will be installed.
#
# FIXME: Remove the code for CMake <3.12 once we have switched over completely.
# FindPython3 is a new module since CMake 3.12. It deprecates FindPythonInterp and FindPythonLibs. The FindPython3
# module is copied from the CMake repository here so in CMake <3.12 we can still use it.
if(${CMAKE_VERSION} VERSION_LESS 3.12)
# Use FindPythonInterp and FindPythonLibs for CMake <3.12
find_package(PythonInterp 3 REQUIRED)
set(Python3_EXECUTABLE ${PYTHON_EXECUTABLE})
else()
# Use FindPython3 for CMake >=3.12
find_package(Python3 REQUIRED COMPONENTS Interpreter)
endif()
# Options or configuration variables
set(UM_NO_INSTALL_PLUGINS "" CACHE STRING "A list of plugins that should not be installed, separated with ';' or ','.")
file(GLOB_RECURSE _plugin_json_list ${CMAKE_SOURCE_DIR}/plugins/*/plugin.json)
list(LENGTH _plugin_json_list _plugin_json_list_len)
# Sort the lists alphabetically so we can handle cases like this:
# - plugins/my_plugin/plugin.json
# - plugins/my_plugin/my_module/plugin.json
# In this case, only "plugins/my_plugin" should be added via install().
set(_no_install_plugin_list ${UM_NO_INSTALL_PLUGINS})
# Sanitize the string so the comparison will be case-insensitive.
string(STRIP "${_no_install_plugin_list}" _no_install_plugin_list)
string(TOLOWER "${_no_install_plugin_list}" _no_install_plugin_list)
# WORKAROUND counterpart of what's in cura-build.
string(REPLACE "," ";" _no_install_plugin_list "${_no_install_plugin_list}")
list(LENGTH _no_install_plugin_list _no_install_plugin_list_len)
if(_no_install_plugin_list_len GREATER 0)
list(SORT _no_install_plugin_list)
endif()
if(_plugin_json_list_len GREATER 0)
list(SORT _plugin_json_list)
endif()
# Check all plugin directories and add them via install() if needed.
set(_install_plugin_list "")
foreach(_plugin_json_path ${_plugin_json_list})
get_filename_component(_plugin_dir ${_plugin_json_path} DIRECTORY)
file(RELATIVE_PATH _rel_plugin_dir ${CMAKE_CURRENT_SOURCE_DIR} ${_plugin_dir})
get_filename_component(_plugin_dir_name ${_plugin_dir} NAME)
# Make plugin name comparison case-insensitive
string(TOLOWER "${_plugin_dir_name}" _plugin_dir_name_lowercase)
# Check if this plugin needs to be skipped for installation
set(_add_plugin ON) # Indicates if this plugin should be added to the build or not.
set(_is_no_install_plugin OFF) # If this plugin will not be added, this indicates if it's because the plugin is
# specified in the NO_INSTALL_PLUGINS list.
if(_no_install_plugin_list)
if("${_plugin_dir_name_lowercase}" IN_LIST _no_install_plugin_list)
set(_add_plugin OFF)
set(_is_no_install_plugin ON)
endif()
endif()
# Make sure this is not a subdirectory in a plugin that's already in the install list
if(_add_plugin)
foreach(_known_install_plugin_dir ${_install_plugin_list})
if(_plugin_dir MATCHES "${_known_install_plugin_dir}.+")
set(_add_plugin OFF)
break()
endif()
endforeach()
endif()
if(_add_plugin)
message(STATUS "[+] PLUGIN TO INSTALL: ${_rel_plugin_dir}")
get_filename_component(_rel_plugin_parent_dir ${_rel_plugin_dir} DIRECTORY)
install(DIRECTORY ${_rel_plugin_dir}
DESTINATION lib${LIB_SUFFIX}/uranium/${_rel_plugin_parent_dir}
PATTERN "__pycache__" EXCLUDE
PATTERN "*.qmlc" EXCLUDE
)
list(APPEND _install_plugin_list ${_plugin_dir})
elseif(_is_no_install_plugin)
message(STATUS "[-] PLUGIN TO REMOVE : ${_rel_plugin_dir}")
execute_process(COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/mod_bundled_packages_json.py
-d ${CMAKE_CURRENT_SOURCE_DIR}/resources/bundled_packages
${_plugin_dir_name}
RESULT_VARIABLE _mod_json_result)
endif()
endforeach()

56
UraniumTests.cmake Normal file
View file

@ -0,0 +1,56 @@
# Copyright (c) 2018 Ultimaker B.V.
# Uranium is released under the terms of the LGPLv3 or higher.
enable_testing()
include(CMakeParseArguments)
add_custom_target(test-verbose COMMAND ${CMAKE_CTEST_COMMAND} --verbose)
function(uranium_add_test)
set(_single_args NAME DIRECTORY PYTHONPATH)
cmake_parse_arguments("" "" "${_single_args}" "" ${ARGN})
if(NOT _NAME)
message(FATAL_ERROR "UraniumAddTest requires a test name argument")
endif()
if(NOT _DIRECTORY)
message(FATAL_ERROR "UraniumAddTest requires a directory to test")
endif()
if(NOT _PYTHONPATH)
set(_PYTHONPATH ${_DIRECTORY})
endif()
if(WIN32)
string(REPLACE "|" "\\;" _PYTHONPATH ${_PYTHONPATH})
set(_PYTHONPATH "${_PYTHONPATH}\\;$ENV{PYTHONPATH}")
else()
string(REPLACE "|" ":" _PYTHONPATH ${_PYTHONPATH})
set(_PYTHONPATH "${_PYTHONPATH}:$ENV{PYTHONPATH}")
endif()
add_test(
NAME ${_NAME}
COMMAND ${PYTHON_EXECUTABLE} -m pytest --junitxml=${CMAKE_BINARY_DIR}/junit-${_NAME}.xml ${_DIRECTORY}
)
set_tests_properties(${_NAME} PROPERTIES ENVIRONMENT LANG=C)
set_tests_properties(${_NAME} PROPERTIES ENVIRONMENT "PYTHONPATH=${_PYTHONPATH}")
endfunction()
uranium_add_test(NAME pytest-main DIRECTORY ${CMAKE_SOURCE_DIR}/tests PYTHONPATH ${CMAKE_SOURCE_DIR})
file(GLOB_RECURSE _plugins plugins/*/__init__.py)
foreach(_plugin ${_plugins})
get_filename_component(_plugin_directory ${_plugin} DIRECTORY)
if(EXISTS ${_plugin_directory}/tests)
get_filename_component(_plugin_name ${_plugin_directory} NAME)
uranium_add_test(NAME pytest-${_plugin_name} DIRECTORY ${_plugin_directory} PYTHONPATH "${CMAKE_SOURCE_DIR}|${_plugin_directory}")
endif()
endforeach()
#Add code style test.
add_test(
NAME "code-style"
COMMAND ${PYTHON_EXECUTABLE} run_mypy.py WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)

View file

@ -0,0 +1,114 @@
## 3rd party code:
# Macro needed to list all sub-directory of a directory.
# There is no function in cmake as far as I know.
# Found at: http://stackoverflow.com/a/7788165
MACRO(SUBDIRLIST result curdir)
FILE(GLOB children RELATIVE ${curdir} ${curdir}/*)
SET(dirlist "")
FOREACH(child ${children})
IF(IS_DIRECTORY ${curdir}/${child})
STRING(REPLACE "/" "" child ${child})
LIST(APPEND dirlist ${child})
ENDIF()
ENDFOREACH()
SET(${result} ${dirlist})
ENDMACRO()
## Translation tools:
SET(CURA_BINARY_DATA_DIRECTORY CACHE PATH "Directory to the cura-binary-data repository")
if(NOT CURA_BINARY_DATA_DIRECTORY AND NOT DEFINED $ENV{CURA_BINARY_DATA_DIRECTORY})
message(STATUS "Using CURA_BINARY_DATA_DIRECTORY from set of environment variables...")
SET(CURA_BINARY_DATA_DIRECTORY $ENV{CURA_BINARY_DATA_DIRECTORY})
endif()
# Dynamically creates targets for each language to create a *.po-file
MACRO(TARGETS_FOR_PO_FILES language)
if(DEFINED GETTEXT_MSGINIT_EXECUTABLE)
message(STATUS "Creating target i18n-create-po-${language}")
add_custom_target(i18n-create-po-${language})
add_dependencies(i18n-create-po i18n-create-po-${language})
endif()
message(STATUS "Creating target i18n-update-po-${language}")
add_custom_target(i18n-update-po-${language})
add_dependencies(i18n-update-po i18n-update-po-${language})
foreach(pot_file ${pot_files})
string(REGEX REPLACE ".*/(.*).pot" "${CMAKE_SOURCE_DIR}/resources/i18n/${language}/\\1.po" po_file ${pot_file})
if(DEFINED GETTEXT_MSGINIT_EXECUTABLE)
add_custom_command(TARGET i18n-create-po-${language} POST_BUILD
COMMAND ${GETTEXT_MSGINIT_EXECUTABLE} ARGS --no-wrap --no-translator -l ${language} -i ${pot_file} -o ${po_file})
endif()
add_custom_command(TARGET i18n-update-po-${language} POST_BUILD
COMMAND ${GETTEXT_MSGMERGE_EXECUTABLE} ARGS --no-wrap --no-fuzzy-matching -o ${po_file} ${po_file} ${pot_file})
endforeach()
ENDMACRO()
# Dynamically creates targets for each language to create a *.mo-file
MACRO(TARGETS_FOR_MO_FILES language)
message(STATUS "Creating target i18n-create-mo-${language}")
add_custom_target(i18n-create-mo-${language})
add_dependencies(i18n-create-mo i18n-create-mo-${language})
if(TARGET i18n-copy-mo)
message(STATUS "Creating target i18n-copy-mo-${language}")
add_custom_target(i18n-copy-mo-${language})
add_dependencies(i18n-copy-mo i18n-copy-mo-${language})
endif()
file(GLOB po_files ${CMAKE_SOURCE_DIR}/resources/i18n/${language}/*.po)
foreach(po_file ${po_files})
string(REGEX REPLACE ".*/(.*).po" "${CMAKE_BINARY_DIR}/resources/i18n/${language}/LC_MESSAGES/\\1.mo" mo_file ${po_file})
add_custom_command(TARGET i18n-create-mo-${language} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/resources/i18n/${language}/LC_MESSAGES/
COMMAND ${GETTEXT_MSGFMT_EXECUTABLE} ARGS ${po_file} -o ${mo_file} -f)
if(TARGET i18n-copy-mo-${language})
string(REGEX REPLACE ".*/(.*).po" "${CURA_BINARY_DATA_DIRECTORY}/${PROJECT_NAME}/resources/i18n/${language}/LC_MESSAGES/\\1.mo" mo_file_binary_copy ${po_file})
add_custom_command(TARGET i18n-copy-mo-${language} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory ${CURA_BINARY_DATA_DIRECTORY}/resources/i18n/${language}/LC_MESSAGES/
COMMAND ${CMAKE_COMMAND} -E copy ${mo_file} ${mo_file_binary_copy})
add_dependencies(i18n-copy-mo-${language} i18n-create-mo-${language})
endif()
endforeach()
ENDMACRO()
# Checks for availability of gettext and when found creates all targets
# TODO: Adding option to set the PROJECT_NAME externally!
MACRO(CREATE_TRANSLATION_TARGETS)
find_package(Gettext)
if(GETTEXT_FOUND)
# translations target will convert .po files into .mo and .qm as needed.
# The files are checked for a _qt suffix and if it is found, converted to
# qm, otherwise they are converted to .po.
if(DEFINED GETTEXT_MSGINIT_EXECUTABLE)
message(STATUS "Creating target i18n-create-po")
add_custom_target(i18n-create-po)
else()
message(WARNING "GETTEXT_MSGINIT_EXECUTABLE is undefined!\nSkipping to create i18n-create-po* targets...")
endif()
if(CURA_BINARY_DATA_DIRECTORY)
if(EXISTS ${CURA_BINARY_DATA_DIRECTORY})
message(STATUS "CURA_BINARY_DATA_DIRECTORY: ${CURA_BINARY_DATA_DIRECTORY}")
message(STATUS "Creating target i18n-copy-mo")
add_custom_target(i18n-copy-mo)
else()
message(WARNING "CURA_BINARY_DATA_DIRECTORY does not exist! (${CURA_BINARY_DATA_DIRECTORY})")
endif()
else()
message(WARNING "CURA_BINARY_DATA_DIRECTORY is not set!")
endif()
message(STATUS "Creating target i18n-update-po")
add_custom_target(i18n-update-po)
message(STATUS "Creating target i18n-create-mo")
add_custom_target(i18n-create-mo ALL)
SUBDIRLIST(languages ${CMAKE_SOURCE_DIR}/resources/i18n/)
file(GLOB pot_files ${CMAKE_SOURCE_DIR}/resources/i18n/*.pot)
foreach(language ${languages})
TARGETS_FOR_PO_FILES(${language})
TARGETS_FOR_MO_FILES(${language})
endforeach()
install(DIRECTORY ${CMAKE_BINARY_DIR}/resources DESTINATION ${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME}/)
endif()
ENDMACRO()

View file

@ -0,0 +1,69 @@
#!/usr/bin/env python3
#
# This script removes the given package entries in the bundled_packages JSON files. This is used by the PluginInstall
# CMake module.
#
import argparse
import collections
import json
import os
import sys
## Finds all JSON files in the given directory recursively and returns a list of those files in absolute paths.
#
# \param work_dir The directory to look for JSON files recursively.
# \return A list of JSON files in absolute paths that are found in the given directory.
def find_json_files(work_dir: str) -> list:
json_file_list = []
for root, dir_names, file_names in os.walk(work_dir):
for file_name in file_names:
abs_path = os.path.abspath(os.path.join(root, file_name))
json_file_list.append(abs_path)
return json_file_list
## Removes the given entries from the given JSON file. The file will modified in-place.
#
# \param file_path The JSON file to modify.
# \param entries A list of strings as entries to remove.
# \return None
def remove_entries_from_json_file(file_path: str, entries: list) -> None:
try:
with open(file_path, "r", encoding = "utf-8") as f:
package_dict = json.load(f, object_hook = collections.OrderedDict)
except Exception as e:
msg = "Failed to load '{file_path}' as a JSON file. This file will be ignored Exception: {e}"\
.format(file_path = file_path, e = e)
sys.stderr.write(msg + os.linesep)
return
for entry in entries:
if entry in package_dict:
del package_dict[entry]
print("[INFO] Remove entry [{entry}] from [{file_path}]".format(file_path = file_path, entry = entry))
try:
with open(file_path, "w", encoding = "utf-8", newline = "\n") as f:
json.dump(package_dict, f, indent = 4)
except Exception as e:
msg = "Failed to write '{file_path}' as a JSON file. Exception: {e}".format(file_path = file_path, e = e)
raise IOError(msg)
def main() -> None:
parser = argparse.ArgumentParser("mod_bundled_packages_json")
parser.add_argument("-d", "--dir", dest = "work_dir",
help = "The directory to look for bundled packages JSON files, recursively.")
parser.add_argument("entries", metavar = "ENTRIES", type = str, nargs = "+")
args = parser.parse_args()
json_file_list = find_json_files(args.work_dir)
for json_file_path in json_file_list:
remove_entries_from_json_file(json_file_path, args.entries)
if __name__ == "__main__":
main()

View file

@ -1,11 +1,20 @@
Name: python-uranium
Version: 4.13.1
Release: 5%{?dist}
Version: 5.3.0
Release: 1%{?dist}
Summary: A Python framework for building desktop applications
License: LGPLv3+
URL: https://github.com/Ultimaker/Uranium
Source0: %{url}/archive/%{version}.tar.gz#/Uranium-%{version}.tar.gz
# Cmake bits taken from 4.13.1, before upstream went nuts with conan
Source2: mod_bundled_packages_json.py
Source3: UraniumPluginInstall.cmake
Source4: UraniumTests.cmake
Source5: UraniumTranslationTools.cmake
Source6: CMakeLists.txt
Source7: CPackConfig.cmake
Source8: Doxyfile
BuildRequires: python3-devel
BuildRequires: python3-pip
BuildRequires: /usr/bin/doxygen
@ -14,13 +23,15 @@ BuildRequires: cmake
BuildRequires: git-core
# Tests
BuildRequires: python3-arcus == %{version}
BuildRequires: python3-arcus >= 5.2.2
BuildRequires: python3-cryptography
BuildRequires: python3-numpy
BuildRequires: python3-scipy
BuildRequires: python3-shapely
BuildRequires: python3-qt5
BuildRequires: python3-pyclipper
BuildRequires: python3-pyqt6-devel
BuildRequires: python3-pytest
BuildRequires: python3-pytest-benchmark
BuildRequires: python3-twisted
BuildArch: noarch
@ -38,12 +49,12 @@ Summary: %{summary}
Provides: uranium = %{version}-%{release}
%{?python_provide:%python_provide python3-uranium}
Requires: python3-arcus == %{version}
Requires: python3-arcus >= 5.2.2
Requires: python3-cryptography
Requires: python3-numpy
Requires: python3-scipy
Requires: python3-shapely
Requires: python3-qt5
Requires: python3-pyqt6
Recommends: python3-numpy-stl
%description -n python3-uranium
@ -59,6 +70,14 @@ related applications.
%prep
%autosetup -n Uranium-%{version} -p1 -S git
mkdir cmake
cp -a %{SOURCE2} %{SOURCE3} %{SOURCE4} %{SOURCE5} cmake/
rm -rf CMakeLists.txt
cp -a %{SOURCE6} %{SOURCE7} %{SOURCE8} .
# fix compile-shaders
sed -i 's|qsb |qsb-qt6 |g' scripts/compile-shaders
%build
# there is no arch specific content, so we set LIB_SUFFIX to nothing
# see https://github.com/Ultimaker/Uranium/commit/862a246bdfd7e25541b04a35406957612c6f4bb7
@ -111,6 +130,9 @@ popd
%changelog
* Wed Mar 8 2023 Tom Callaway <spot@fedoraproject.org> - 5.3.0-1
- update to 5.3.0
* Fri Jan 20 2023 Fedora Release Engineering <releng@fedoraproject.org> - 4.13.1-5
- Rebuilt for https://fedoraproject.org/wiki/Fedora_38_Mass_Rebuild

View file

@ -1 +1 @@
SHA512 (Uranium-4.13.1.tar.gz) = ff9573104c9cdf0a1ba3ff304c17dbbcdab561a7158138067067ea3c2d063c139545effb1b1c944da986053cb3ea68dc5b947c30c5578ef29f6858e3f9e461c3
SHA512 (Uranium-5.3.0.tar.gz) = ae21693e531b8744173d72612333675ae59dcdb3da021d345ff983b3e046f1d9b578c2f43bc2d9482ab5f3d0fb6c554586a43d504fe264df5ee65cdac6ac8316