Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ omit = */test_*
*/__init__.py
*/run*
*/out3Plot/out*
*/com9MoTVoellmy/_buildMoTVoellmy.py
23 changes: 17 additions & 6 deletions .github/workflows/buildAndUploadPyPi.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Deploy to pypi for all plattforms


name: Build and upload to PyPI

on:
Expand Down Expand Up @@ -29,6 +30,14 @@ jobs:
env:
CIBW_PLATFORM: ${{ matrix.platform || 'auto' }}
# CIBW_BUILD_FRONTEND: "build[uv]"
CIBW_BEFORE_BUILD_LINUX: >
python {package}/avaframe/com9MoTVoellmy/_buildMoTVoellmy.py
CIBW_BEFORE_BUILD_WINDOWS: >
choco install mingw -y &&
python {package}/avaframe/com9MoTVoellmy/_buildMoTVoellmy.py
CIBW_BEFORE_BUILD_MACOS: >
python {package}/avaframe/com9MoTVoellmy/_buildMoTVoellmy.py
CIBW_ENVIRONMENT: GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}

- uses: actions/upload-artifact@v7
with:
Expand All @@ -51,12 +60,14 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install numpy cython build
# - name: Compile cython code
# run: |
# python setup.py build_ext --inplace
# - name: Install avaframe
# run: |
# pip install .
- name: Compile MoT-Voellmy binaries
run: |
python avaframe/com9MoTVoellmy/_buildMoTVoellmy.py
sudo apt-get install -y gcc-mingw-w64-x86-64
C_FILE=$(ls avaframe/com9MoTVoellmy/MoT-Voellmy.*.c 2>/dev/null | head -1)
if [ -n "$C_FILE" ]; then
x86_64-w64-mingw32-gcc -Wall -pedantic -o avaframe/com9MoTVoellmy/MoT-Voellmy_win.exe "$C_FILE" -lm
fi
- name: create source distribution
run: python -m build
# - name: Build sdist
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/runTestSinglePython.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ jobs:
cache: false
- name: Pixi run pytest
run: |
pixi run compilemot
pixi run pytest -ra --cov --cov-report=xml --cov-report lcov:cov.info --cov-config=.coveragerc
- uses: qltysh/qlty-action/coverage@main
with:
Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ avaframe/com1DFA/*.so

/.pixi/

# MoT-Voellmy compiled binaries and downloaded sources
avaframe/com9MoTVoellmy/MoT-Voellmy_linux.exe
avaframe/com9MoTVoellmy/MoT-Voellmy_win.exe
avaframe/com9MoTVoellmy/MoT-Voellmy.*.c

# auxilliary geodata .xml files (created by GIS software packages)
*.aux.xml

Expand Down
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ include avaframe/version.py
include avaframe/in3Utils/logging.conf
include avaframe/com1DFA/*.pyx
include avaframe/com9MoTVoellmy/MoT-Voellmy*.exe
include avaframe/com9MoTVoellmy/_buildMoTVoellmy.py
recursive-include avaframe *Cfg.ini
global-exclude local_*Cfg.ini
prune benchmarks
Expand Down
Binary file removed avaframe/com9MoTVoellmy/MoT-Voellmy_linux.exe
Binary file not shown.
Binary file removed avaframe/com9MoTVoellmy/MoT-Voellmy_win.exe
Binary file not shown.
191 changes: 191 additions & 0 deletions avaframe/com9MoTVoellmy/_buildMoTVoellmy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
"""Build MoT-Voellmy binary for the current platform.
Comment thread
fso42 marked this conversation as resolved.

Usage:
python _buildMoTVoellmy.py [path/to/source.c]

If a path is provided, that C source file is used. Otherwise, the script
discovers and downloads the latest source from the upstream GitHub repo.

On success, the compiled binary is written to this directory alongside the script.
On failure, the script exits with a non-zero code (for pixi task / CI).
When imported from setup.py, it returns a status instead of exiting.
"""

import os
import platform
import re
import shutil
import subprocess
import sys
import urllib.request

upstreamRepo = "norwegian-geotechnical-institute/MoT-Voellmy"
upstreamApi = f"https://api.github.com/repos/{upstreamRepo}"
upstreamContents = f"{upstreamApi}/contents/"
sourcePattern = re.compile(r"^MoT-Voellmy\..*\.c$")

outputDir = os.path.dirname(os.path.abspath(__file__))


def _getReleaseTag():
"""Return the tag name of the latest GitHub release, or None."""
headers = {}
token = os.environ.get("GITHUB_TOKEN")
if token:
headers["Authorization"] = f"Bearer {token}"

try:
url = f"{upstreamApi}/releases/latest"
req = urllib.request.Request(url, headers=headers)
with urllib.request.urlopen(req, timeout=30) as resp:
import json

data = json.loads(resp.read().decode())
tag = data.get("tag_name")
if tag:
print(f"Latest upstream release: {tag}")
return tag
except Exception as e:
print(f"Failed to query GitHub releases: {e}", file=sys.stderr)
return None


def _findGithubSource():
"""Query the latest GitHub release for the .c source file.

Returns (download_url, filename) or None if not found.
"""
headers = {}
token = os.environ.get("GITHUB_TOKEN")
if token:
headers["Authorization"] = f"Bearer {token}"

tag = _getReleaseTag()
ref = tag if tag else "main"

try:
url = f"{upstreamContents}?ref={ref}"
req = urllib.request.Request(url, headers=headers)
with urllib.request.urlopen(req, timeout=30) as resp:
import json

contents = json.loads(resp.read().decode())
except Exception as e:
print(f"Failed to query GitHub API: {e}", file=sys.stderr)
return None

for item in contents:
if item.get("type") != "file":
continue
name = item.get("name", "")
if sourcePattern.match(name):
url = item.get("download_url")
if url:
print(f"Found upstream source: {name}")
return url, name

print("No MoT-Voellmy source file found in upstream repo", file=sys.stderr)
return None


def _downloadSource(url, destPath):
"""Download a file from url to destPath."""
print(f"Downloading {url} ...")
try:
with urllib.request.urlopen(url, timeout=60) as resp:
with open(destPath, "wb") as f:
shutil.copyfileobj(resp, f)
print(f"Downloaded to {destPath}")
return True
except Exception as e:
print(f"Download failed: {e}", file=sys.stderr)
return False


def _compile(sourcePath):
"""Compile sourcePath for the current platform.

Returns True on success, False on failure.
"""
system = platform.system()

if system == "Linux":
outName = "MoT-Voellmy_linux.exe"
cmd = ["gcc", "-Wall", "-pedantic", "-o", outName, sourcePath, "-lm"]
elif system == "Windows":
outName = "MoT-Voellmy_win.exe"
cmd = ["gcc", "-Wall", "-pedantic", "-o", outName, sourcePath, "-lm"]
elif system == "Darwin":
outName = "MoT-Voellmy_mac.exe"
cmd = ["gcc", "-Wall", "-pedantic", "-arch", "arm64", "-arch", "x86_64",
"-o", outName, sourcePath, "-lm"]
else:
print(f"Unknown platform: {system}", file=sys.stderr)
return False

# Run from outputDir so the binary lands alongside the Python module
print(f"Compiling: {' '.join(cmd)}")
try:
result = subprocess.run(cmd, cwd=outputDir, capture_output=True, text=True)
if result.returncode != 0:
print(f"Compilation failed:\n{result.stderr}", file=sys.stderr)
return False
except FileNotFoundError:
print(
f"gcc not found. Install gcc (e.g. MinGW on Windows) or download "
f"the precompiled binary from https://github.com/norwegian-geotechnical-institute/"
f"MoT-Voellmy and copy it to the com9MoTVoellmy directory as {outName}.",
file=sys.stderr,
)
return False

# Make executable on Unix
outPath = os.path.join(outputDir, outName)
if system != "Windows":
os.chmod(outPath, 0o755)

print(f"Compiled {outPath}")
Comment thread
fso42 marked this conversation as resolved.
Comment thread
fso42 marked this conversation as resolved.
Comment thread
qltysh[bot] marked this conversation as resolved.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function with many returns (count = 4): _compile [qlty:return-statements]

return True


def buildMoTVoellmy(sourcePath=None):
"""Compile MoT-Voellmy binary.

Args:
sourcePath: Optional path to a local .c file. If None, downloads
the latest source from the upstream GitHub repo.

Returns:
True if compilation succeeded, False otherwise.
"""
# 1. CLI argument
if sourcePath is not None:
if not os.path.isfile(sourcePath):
print(f"Source file not found: {sourcePath}", file=sys.stderr)
return False
return _compile(sourcePath)

# 2. GitHub download
result = _findGithubSource()
if result is None:
return False

url, filename = result
dest = os.path.join(outputDir, filename)

# Use cached copy if available and download is a fallback
if not os.path.isfile(dest):
if not _downloadSource(url, dest):
return False

Comment thread
fso42 marked this conversation as resolved.
return _compile(dest)


def main():
source = sys.argv[1] if len(sys.argv) > 1 else None
success = buildMoTVoellmy(source)
sys.exit(0 if success else 1)


if __name__ == "__main__":
main()
4 changes: 1 addition & 3 deletions avaframe/com9MoTVoellmy/com9MoTVoellmy.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,9 +173,7 @@ def com9MoTVoellmyTask(rcfFile):
if os.name == "nt":
exeName = "MoT-Voellmy_win.exe"
elif platform.system() == "Darwin":
message = "MoT-Voellmy does not support MacOS at the moment"
log.error(message)
raise OSError(message)
exeName = "./MoT-Voellmy_mac.exe"
else:
exeName = "./MoT-Voellmy_linux.exe"

Expand Down
2 changes: 1 addition & 1 deletion avaframe/com9MoTVoellmy/com9MoTVoellmyCfg.ini
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
modelType = dfa

# list of simulations that shall be performed (null, ent, res, entres, available (use all available input data))
simTypeList = res
simTypeList = available


#+++++Release thickness++++
Expand Down
Loading
Loading