Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

REFACTOR: Dotnet coreclr loading #916

Merged
merged 12 commits into from
Nov 29, 2024
13 changes: 6 additions & 7 deletions .github/workflows/ci_cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand All @@ -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
Expand All @@ -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/

Expand All @@ -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

Expand Down
15 changes: 6 additions & 9 deletions doc/source/build_breaking_change.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
------
Expand All @@ -35,7 +32,7 @@ Microsoft documentation for `.NET` on Linux to ensure proper setup and compatibi
`Register Microsoft package repository <https://learn.microsoft.com/en-us/dotnet/core/install/linux-ubuntu#register-the-microsoft-package-repository>`_
and `Install .NET <https://learn.microsoft.com/en-us/dotnet/core/install/linux-ubuntu#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.
Expand Down
26 changes: 0 additions & 26 deletions src/pyedb/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down Expand Up @@ -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 <stdin>: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()

#
Expand Down
124 changes: 92 additions & 32 deletions src/pyedb/dotnet/clr_module.py
Original file line number Diff line number Diff line change
@@ -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 <stdin>: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
Expand Down Expand Up @@ -93,6 +150,7 @@
Dictionary = None
Array = None
edb_initialized = False

if "win32com" in modules:
try:
import win32com.client as win32_client
Expand All @@ -101,3 +159,5 @@
import win32com.client as win32_client
except ImportError:
win32_client = None

warnings.showwarning = existing_showwarning
19 changes: 1 addition & 18 deletions tests/test_warnings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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()
Loading