Skip to content

Commit

Permalink
REFACTOR: Dotnet coreclr loading (#916)
Browse files Browse the repository at this point in the history
* REFACTOR: Dotnet coreclr loading

* REFACTOR: Remove warning from pyedb import

* TESTS: Remove warning tests on pyedb import

* DOCS: Update breaking changes

* DOCS: Update documentation

* TBR: Run pyaedt tests

* CI: Update environment variable path

* TBR: Target updated VM

* TEST: strange behavior might be related to delcross

* CI: fix typo

* REFACTOR: Remove print

* CI: Revert testing changes
  • Loading branch information
SMoraisAnsys authored Nov 29, 2024
1 parent 34725f9 commit 9c70077
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 92 deletions.
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()

0 comments on commit 9c70077

Please sign in to comment.