diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index 630ffcaab4..1328f44602 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -286,7 +286,7 @@ jobs: needs: [smoke-tests] runs-on: [ self-hosted, Linux, pyaedt ] env: - ANSYSEM_ROOT242: '/ansys_inc/AnsysEM/v242/Linux64' + ANSYSEM_ROOT242: '/opt/AnsysEM/v242/Linux64' ANS_NODEPCHECK: '1' steps: - name: Install Git and checkout project @@ -304,12 +304,10 @@ jobs: - name: Create Python venv run: | - export LD_LIBRARY_PATH=${{ env.ANSYSEM_ROOT242 }}/common/mono/Linux64/lib64:${{ env.ANSYSEM_ROOT242 }}/Delcross:$LD_LIBRARY_PATH python -m venv .venv - name: Update pip run: | - export LD_LIBRARY_PATH=${{ env.ANSYSEM_ROOT242 }}/common/mono/Linux64/lib64:${{ env.ANSYSEM_ROOT242 }}/Delcross:$LD_LIBRARY_PATH . .venv/bin/activate python -m pip install -U pip @@ -322,18 +320,19 @@ jobs: - name: Install PyAEDT main branch version with its test dependencies run: | - export LD_LIBRARY_PATH=${{ env.ANSYSEM_ROOT242 }}/common/mono/Linux64/lib64:${{ env.ANSYSEM_ROOT242 }}/Delcross:$LD_LIBRARY_PATH + export LD_LIBRARY_PATH=${{ env.ANSYSEM_ROOT242 }}/common/mono/Linux64/lib64:$LD_LIBRARY_PATH . .venv/bin/activate pip install --no-cache-dir external/pyaedt[tests] - name: Install PyEDB run: | - export LD_LIBRARY_PATH=${{ env.ANSYSEM_ROOT242 }}/common/mono/Linux64/lib64:${{ env.ANSYSEM_ROOT242 }}/Delcross:$LD_LIBRARY_PATH + export LD_LIBRARY_PATH=${{ env.ANSYSEM_ROOT242 }}/common/mono/Linux64/lib64:$LD_LIBRARY_PATH . .venv/bin/activate python -m pip install . - name: Install CI dependencies (e.g. vtk-osmesa) run: | + export LD_LIBRARY_PATH=${{ env.ANSYSEM_ROOT242 }}/common/mono/Linux64/lib64:$LD_LIBRARY_PATH . .venv/bin/activate # Uninstall conflicting dependencies pip uninstall --yes vtk @@ -348,7 +347,7 @@ jobs: retry_on: error timeout_minutes: 50 command: | - export LD_LIBRARY_PATH=${{ env.ANSYSEM_ROOT242 }}/common/mono/Linux64/lib64:${{ env.ANSYSEM_ROOT242 }}/Delcross:$LD_LIBRARY_PATH + export LD_LIBRARY_PATH=${{ env.ANSYSEM_ROOT242 }}/common/mono/Linux64/lib64:$LD_LIBRARY_PATH . .venv/bin/activate pytest -n auto --dist loadfile --durations=50 -v external/pyaedt/tests/system/general/ @@ -361,7 +360,7 @@ jobs: retry_on: error timeout_minutes: 50 command: | - export LD_LIBRARY_PATH=${{ env.ANSYSEM_ROOT242 }}/common/mono/Linux64/lib64:${{ env.ANSYSEM_ROOT242 }}/Delcross:$LD_LIBRARY_PATH + export LD_LIBRARY_PATH=${{ env.ANSYSEM_ROOT242 }}/common/mono/Linux64/lib64:$LD_LIBRARY_PATH . .venv/bin/activate pytest --durations=50 -v external/pyaedt/tests/system/solvers diff --git a/doc/source/build_breaking_change.rst b/doc/source/build_breaking_change.rst index 0e5f382ea5..5be766739d 100644 --- a/doc/source/build_breaking_change.rst +++ b/doc/source/build_breaking_change.rst @@ -18,14 +18,11 @@ A temporary workaround was considered, which involved manually installing an old `libssl1.1` library. While this allowed the use of `dotnetcore2`, it is **not recommended** as a long-term solution for the following reasons: -- **Security risks**: Installing an older version of `libssl` introduces vulnerabilities, as it may -lack the latest security updates provided in the newer versions. -- **System instability**: Manually forcing an older version of `libssl` can lead to dependency -conflicts with other software packages that rely on newer versions of this library, potentially -causing further compatibility issues in the system. -- **Maintenance overhead**: Relying on deprecated or unsupported libraries increases the -complexity of future upgrades and system maintenance, making the environment harder to manage in the -long term. +- **Security risks**: Installing an older version of `libssl` introduces vulnerabilities, as it may lack the latest security updates provided in the newer versions. + +- **System instability**: Manually forcing an older version of `libssl` can lead to dependency conflicts with other software packages that rely on newer versions of this library, potentially causing further compatibility issues in the system. + +- **Maintenance overhead**: Relying on deprecated or unsupported libraries increases the complexity of future upgrades and system maintenance, making the environment harder to manage in the long term. Impact ------ @@ -35,7 +32,7 @@ Microsoft documentation for `.NET` on Linux to ensure proper setup and compatibi `Register Microsoft package repository `_ and `Install .NET `_. -.. note:: Ubuntu 22.04 and later versions +.. note:: Starting with Ubuntu 22.04, `.NET` is available in the official Ubuntu repository. If you want to use the Microsoft package to install `.NET`, you can use the following approach to *"demote"* the Ubuntu packages so that the Microsoft packages take precedence. diff --git a/src/pyedb/__init__.py b/src/pyedb/__init__.py index 64f28cdbf6..50870ad661 100644 --- a/src/pyedb/__init__.py +++ b/src/pyedb/__init__.py @@ -11,13 +11,6 @@ os.environ["PYEDB_USE_DOTNET"] = "0" LATEST_DEPRECATED_PYTHON_VERSION = (3, 7) -LINUX_WARNING = ( - "Due to compatibility issues between .NET Core and libssl on some Linux versions, " - "for example Ubuntu 22.04, we are going to stop depending on `dotnetcore2`." - "Instead of using this package which embeds .NET Core 3, users will be required to " - "install .NET themselves. For more information, see " - "https://edb.docs.pyansys.com/version/stable/build_breaking_change.html" -) def deprecation_warning(): @@ -46,25 +39,6 @@ def custom_show_warning(message, category, filename, lineno, file=None, line=Non warnings.showwarning = existing_showwarning -def linux_warning(): - """Warning message informing Linux users a future breaking change is coming.""" - # Store warnings showwarning - existing_showwarning = warnings.showwarning - - # Define and use custom showwarning - def custom_show_warning(message, category, filename, lineno, file=None, line=None): - """Custom warning used to remove :loc: pattern.""" - print("{}: {}".format(category.__name__, message)) - - warnings.showwarning = custom_show_warning - - if os.name == "posix": - warnings.warn(LINUX_WARNING, FutureWarning) - - # Restore warnings showwarning - warnings.showwarning = existing_showwarning - - deprecation_warning() # diff --git a/src/pyedb/dotnet/clr_module.py b/src/pyedb/dotnet/clr_module.py index 24636c61f0..710d31329a 100644 --- a/src/pyedb/dotnet/clr_module.py +++ b/src/pyedb/dotnet/clr_module.py @@ -1,68 +1,125 @@ import os +from pathlib import Path import pkgutil +import shutil import sys import warnings +import pyedb + +LINUX_WARNING = ( + "Due to compatibility issues between .NET Core and libssl on some Linux versions, " + "for example Ubuntu 22.04, we are going to stop depending on `dotnetcore2`." + "Instead of using this package which embeds .NET Core 3, users will be required to " + "install .NET themselves. For more information, see " + "https://edb.docs.pyansys.com/version/stable/build_breaking_change.html" +) + +existing_showwarning = warnings.showwarning + + +def custom_show_warning(message, category, filename, lineno, file=None, line=None): + """Custom warning used to remove :loc: pattern.""" + print(f"{category.__name__}: {message}") + + +warnings.showwarning = custom_show_warning + modules = [tup[1] for tup in pkgutil.iter_modules()] cpython = "IronPython" not in sys.version and ".NETFramework" not in sys.version is_linux = os.name == "posix" is_windows = not is_linux is_clr = False +pyedb_path = Path(pyedb.__file__).parent +sys.path.append(str(pyedb_path / "dlls" / "PDFReport")) -try: - import pyedb - pyedb_path = os.path.dirname(os.path.abspath(pyedb.__file__)) - sys.path.append(os.path.join(pyedb_path, "dlls", "PDFReport")) -except ImportError: - pyedb_path = None - warnings.warn("Cannot import pyedb.") +def find_dotnet_root() -> Path: + """Find dotnet root path.""" + dotnet_path = shutil.which("dotnet") + if not dotnet_path: + raise FileNotFoundError("The 'dotnet' executable was not found in the PATH.") -if is_linux and cpython: # pragma: no cover + dotnet_path = Path(dotnet_path).resolve() + dotnet_root = dotnet_path.parent + return dotnet_root + + +def find_runtime_config(dotnet_root: Path) -> Path: + """Find dotnet runtime configuration file path.""" + sdk_path = dotnet_root / "sdk" + if not sdk_path.is_dir(): + raise EnvironmentError(f"The 'sdk' directory could not be found in: {dotnet_root}") + sdk_versions = sorted(sdk_path.iterdir(), key=lambda x: x.name, reverse=True) + if not sdk_versions: + raise FileNotFoundError("No SDK versions were found.") + runtime_config = sdk_versions[0] / "dotnet.runtimeconfig.json" + if not runtime_config.is_file(): + raise FileNotFoundError(f"The configuration file '{runtime_config}' does not exist.") + return runtime_config + + +if is_linux: # pragma: no cover + from pythonnet import load + + # Use system DOTNET core runtime try: + from clr_loader import get_coreclr + + runtime = get_coreclr() + load(runtime) + is_clr = True + # Define DOTNET root and runtime config file to load DOTNET core runtime + except Exception: if os.environ.get("DOTNET_ROOT") is None: - runtime = None try: - import dotnet + dotnet_root = find_dotnet_root() + runtime_config = find_runtime_config(dotnet_root) + except Exception: + warnings.warn( + "Unable to set DOTNET root and locate the runtime configuration file. " + "Falling back to using dotnetcore2." + ) + warnings.warn(LINUX_WARNING) - runtime = os.path.join(os.path.dirname(dotnet.__path__)) - except: import dotnetcore2 - runtime = os.path.join(os.path.dirname(dotnetcore2.__file__), "bin") - finally: - os.environ["DOTNET_ROOT"] = runtime - - from pythonnet import load - - if pyedb_path is not None: - json_file = os.path.abspath(os.path.join(pyedb_path, "misc", "pyedb.runtimeconfig.json")) - load("coreclr", runtime_config=json_file, dotnet_root=os.environ["DOTNET_ROOT"]) - print("DotNet Core correctly loaded.") + dotnet_root = Path(dotnetcore2.__file__).parent / "bin" + runtime_config = pyedb_path / "misc" / "pyedb.runtimeconfig.json" + else: + dotnet_root = Path(os.environ["DOTNET_ROOT"]) + try: + runtime_config = find_runtime_config(dotnet_root) + except Exception as e: + raise RuntimeError( + "Configuration file could not be found from DOTNET_ROOT. " + "Please ensure that .NET SDK is correctly installed or " + "that DOTNET_ROOT is correctly set." + ) + try: + load("coreclr", runtime_config=str(runtime_config), dotnet_root=str(dotnet_root)) if "mono" not in os.getenv("LD_LIBRARY_PATH", ""): warnings.warn("LD_LIBRARY_PATH needs to be setup to use pyedb.") - warnings.warn("export ANSYSEM_ROOT232=/path/to/AnsysEM/v232/Linux64") + warnings.warn("export ANSYSEM_ROOT242=/path/to/AnsysEM/v242/Linux64") msg = "export LD_LIBRARY_PATH=" - msg += "$ANSYSEM_ROOT232/common/mono/Linux64/lib64:$LD_LIBRARY_PATH" + msg += "$ANSYSEM_ROOT242/common/mono/Linux64/lib64:$LD_LIBRARY_PATH" msg += ( - "If PyEDB will run on AEDT<2023.2 then $ANSYSEM_ROOT222/Delcross should be added to LD_LIBRARY_PATH" + "If PyEDB is used with AEDT<2023.2 then /path/to/AnsysEM/v2XY/Linux64/Delcross " + "should be added to LD_LIBRARY_PATH." ) warnings.warn(msg) is_clr = True - else: - print("DotNet Core not correctly loaded.") - except ImportError: - msg = "pythonnet or dotnetcore not installed. Pyedb will work only in client mode." - warnings.warn(msg) + except ImportError: + msg = "pythonnet or dotnetcore not installed. Pyedb will work only in client mode." + warnings.warn(msg) else: try: from pythonnet import load load("coreclr") is_clr = True - except: - pass + warnings.warn("Unable to load DOTNET core runtime") try: # work around a number formatting bug in the EDB API for non-English locales @@ -93,6 +150,7 @@ Dictionary = None Array = None edb_initialized = False + if "win32com" in modules: try: import win32com.client as win32_client @@ -101,3 +159,5 @@ import win32com.client as win32_client except ImportError: win32_client = None + +warnings.showwarning = existing_showwarning diff --git a/tests/test_warnings.py b/tests/test_warnings.py index 47ed1b3904..9e1e36630d 100644 --- a/tests/test_warnings.py +++ b/tests/test_warnings.py @@ -20,17 +20,11 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -import os import sys from unittest.mock import patch import warnings -from pyedb import ( - LATEST_DEPRECATED_PYTHON_VERSION, - LINUX_WARNING, - deprecation_warning, - linux_warning, -) +from pyedb import LATEST_DEPRECATED_PYTHON_VERSION, deprecation_warning @patch.object(warnings, "warn") @@ -48,14 +42,3 @@ def test_deprecation_warning(mock_warn): mock_warn.assert_called_once_with(expected, PendingDeprecationWarning) else: mock_warn.assert_not_called() - - -@patch.object(warnings, "warn") -def test_linux_warning(mock_warn): - linux_warning() - - is_linux = os.name == "posix" - if is_linux: - mock_warn.assert_called_once_with(LINUX_WARNING, PendingDeprecationWarning) - else: - mock_warn.assert_not_called()