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

Add Windows wheels. #1365

Draft
wants to merge 27 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
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
17 changes: 9 additions & 8 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,13 @@ project(
NMODL
VERSION ${NMODL_GIT_LAST_TAG}
LANGUAGES CXX)

# =============================================================================
# Adjust install prefix for wheel
# =============================================================================
if(NOT LINK_AGAINST_PYTHON AND NOT NMODL_AS_SUBPROJECT)
set(NMODL_INSTALL_DIR_SUFFIX "nmodl/.data/")
endif()

# =============================================================================
# HPC Coding Conventions
Expand Down Expand Up @@ -131,9 +138,10 @@ endif()
cpp_cc_git_submodule(json BUILD PACKAGE nlohmann_json REQUIRED)
cpp_cc_git_submodule(pybind11 BUILD PACKAGE pybind11 REQUIRED)
if(WIN32)
cpp_cc_git_submodule(dlfcn-win32 BUILD)
cpp_cc_git_submodule(dlfcn-win32 BUILD EXCLUDE_FROM_ALL)
add_library(dlfcn-win32::dl ALIAS dl)
set(CMAKE_DL_LIBS dlfcn-win32::dl)
install(TARGETS dl DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin)
endif()
# Tell spdlog not to use its bundled fmt, it should either use the fmt submodule or a truly external
# installation for consistency. This line should be harmless if we use an external spdlog.
Expand Down Expand Up @@ -166,13 +174,6 @@ add_custom_target(
clean_ipynb
"${CMAKE_SOURCE_DIR}/docs/notebooks/*.ipynb")

# =============================================================================
# Adjust install prefix for wheel
# =============================================================================
if(NOT LINK_AGAINST_PYTHON AND NOT NMODL_AS_SUBPROJECT)
set(NMODL_INSTALL_DIR_SUFFIX "nmodl/.data/")
endif()

# =============================================================================
# Find required python packages
# =============================================================================
Expand Down
47 changes: 47 additions & 0 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,53 @@ stages:
displayName: 'Publish wheel as build artifact'
- template: ci/upload-wheels.yml

- job: 'windows_wheels_x86_64'
timeoutInMinutes: 45
pool:
vmImage: 'windows-2022'
steps:
- checkout: self
submodules: True
condition: succeeded()
- pwsh: |
choco install winflexbison3
condition: succeeded()
displayName: 'Install Dependencies'

- pwsh: |
if ($env:RELEASEWHEELBUILD) {
Write-Output "##vso[task.setvariable variable=TAG;]"
} else {
Write-Output "##vso[task.setvariable variable=TAG;]-nightly"
}
displayName: "Set wheel tag"

- pwsh: |
Write-Output "##vso[task.setvariable variable=CIBW_BUILD;]cp38* cp312*"
condition: and(succeeded(), eq(variables['Build.Reason'], 'PullRequest'))
displayName: "Set build identifiers"

- task: UsePythonVersion@0

- pwsh: |
Write-Output "Using TAG '$env:TAG'"
Write-Output "Using CIBW_BUILD '$env:CIBW_BUILD'"
python -m pip install --upgrade pip
python -m pip install cibuildwheel==2.16.5 tomli tomli-w
# change the name accordingly
python packaging/change_name.py pyproject.toml "NMODL$env:TAG"
$env:SETUPTOOLS_SCM_PRETEND_VERSION=((git describe --tags) -split "-"|Select-Object -First 2) -join "."
python -m cibuildwheel --output-dir wheelhouse
condition: succeeded()
displayName: 'Build Windows Wheel (x86_64)'

- task: PublishBuildArtifacts@1
inputs:
pathToPublish: '$(Build.SourcesDirectory)/wheelhouse'
condition: succeeded()
displayName: 'Publish wheel as build artifact'
- template: ci/upload-wheels.yml

- job: 'test_manylinux_wheels'
dependsOn: 'manylinux_wheels'
timeoutInMinutes: 45
Expand Down
47 changes: 47 additions & 0 deletions packaging/test_wheel.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
param (
[Parameter(Mandatory=$true)][string]$wheel,
[bool]$venv=$true
)

$TEST_DIR = "$($Env:temp)/tmp$([convert]::tostring((get-random 65535),16).padleft(4,'0')).tmp"
New-Item -ItemType Directory -Path $TEST_DIR
New-Item -ItemType Directory -Path $TEST_DIR/input
New-Item -ItemType Directory -Path $TEST_DIR/output

$NMODL_ROOT=(Split-Path -Parent $PSScriptRoot)

Write-Output $NMODL_ROOT
Get-ChildItem -Path (Join-Path $NMODL_ROOT "python/nmodl/ext/example") -Filter "*.mod" | ForEach-Object {
Copy-Item $_ $TEST_DIR/input
}
Copy-Item "$NMODL_ROOT/test/integration/mod/cabpump.mod" $TEST_DIR/input
Copy-Item "$NMODL_ROOT/test/integration/mod/var_init.inc" $TEST_DIR/input
Copy-Item "$NMODL_ROOT/test/integration/mod/glia_sparse.mod" $TEST_DIR/input

if ($venv) {
python -m venv wheel_test_venv
./wheel_test_venv/Scripts/activate.ps1

pip uninstall -y nmodl nmodl-nightly
pip install "${wheel}[test]"
}

pip show -f nmodl
pip show -f nmodl-nightly

Get-ChildItem -Path $TEST_DIR/input -Filter "*.mod" | ForEach-Object {
$path = $_ -replace "\\","/"
Write-Output "nmodl -o $TEST_DIR/output $path sympy --analytic"
nmodl -o $TEST_DIR/output $path sympy --analytic
if (! $?) {
Write-Output "Failed NMODL run"
Exit 1
}
python -c "import nmodl; driver = nmodl.NmodlDriver(); driver.parse_file('$path')"
if (! $?) {
Write-Output "Failed NMODL Python module parsing"
Exit 1
}
}

# rm -r $TEST_DIR
5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,7 @@ environment = { PATH = "/nmodlwheel/flex/bin:/nmodlwheel/bison/bin:$PATH" }
test-command = "true"

[tool.cibuildwheel.windows]
environment = { SKBUILD_CMAKE_ARGS = "-DNMODL_BUILD_WHEEL=ON;-DFLEX_INCLUDE_PATH=C:/ProgramData/chocolatey/lib/winflexbison3/tools" }
environment = { SKBUILD_CMAKE_ARGS = "-DNMODL_BUILD_WHEEL=ON;-DFLEX_INCLUDE_PATH=C:/ProgramData/chocolatey/lib/winflexbison3/tools" }
test-command = [
"pwsh -File {package}/packaging/test_wheel.ps1 {wheel} -venv:false",
]
22 changes: 14 additions & 8 deletions python/nmodl/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,20 @@
"to the Python library"
) from exc

# add nmodl home to environment (i.e. necessary for nrnunits.lib) if not
# already set
# `files` will automatically raise a `ModuleNotFoundError`
os.environ["NMODLHOME"] = os.environ.get(
"NMODLHOME",
str(files("nmodl") / ".data"),
)

if os.name == "nt":
# On Windows, DLLs get installed alongside with the binary. But _nmodl also links
# against them, so instruct Python where to look for the DLLs
bindir = files("nmodl") / ".data" / "bin"
os.add_dll_directory(bindir)
else:
# add nmodl home to environment (i.e. necessary for nrnunits.lib) if not
# already set
# `files` will automatically raise a `ModuleNotFoundError`
os.environ["NMODLHOME"] = os.environ.get(
"NMODLHOME",
str(files("nmodl") / ".data"),
)
print("Setting HOME to: ", os.environ["NMODLHOME"])

import builtins

Expand Down
37 changes: 21 additions & 16 deletions python/nmodl/_binwrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
Please create a softlink with the binary name to be called.
"""
import os
from pathlib import Path
import subprocess
import stat
import sys

Expand All @@ -26,22 +28,25 @@ def main():
except PackageNotFoundError:
pass

NMODL_PREFIX = files("nmodl")
NMODL_HOME = NMODL_PREFIX / ".data"
NMODL_BIN = NMODL_HOME / "bin"
prefix = files("nmodl")
exe = prefix / ".data" / "bin" / Path(sys.argv[0]).name

# add libpython*.so path to environment
os.environ["NMODL_PYLIB"] = find_libpython()

# add nmodl home to environment (i.e. necessary for nrnunits.lib)
os.environ["NMODLHOME"] = str(NMODL_HOME)
if os.name == "nt":
exe = exe.with_suffix(".exe")
else:
st = os.stat(exe)
os.chmod(exe, st.st_mode | stat.S_IEXEC)

env = dict(os.environ)
# add libpython*.so path to environment
env["NMODL_PYLIB"] = find_libpython()
# set PYTHONPATH for embedded python to properly find the nmodl module
os.environ["PYTHONPATH"] = (
str(NMODL_PREFIX.parent) + ":" + os.environ.get("PYTHONPATH", "")
)

exe = NMODL_BIN / os.path.basename(sys.argv[0])
st = os.stat(exe)
os.chmod(exe, st.st_mode | stat.S_IEXEC)
os.execv(exe, sys.argv)
env["PYTHONPATH"] = str(prefix.parent)
if pth := os.environ.get("PYTHONPATH"):
env["PYTHONPATH"] += os.pathsep + pth

cmd = [exe] + sys.argv[1:]
try:
subprocess.check_call(cmd, env=env)
except subprocess.CalledProcessError:
sys.exit(1)
95 changes: 91 additions & 4 deletions src/config/config.cpp.in
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,27 @@

#include "config/config.h"

#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <sstream>

#if defined(_WIN32)
#include <windows.h>
#elif defined(__APPLE__)
#include <limits.h>
#include <mach-o/dyld.h>
#else
#endif

namespace fs = std::filesystem;

/// Git version of the project
const std::string nmodl::Version::GIT_REVISION = "@NMODL_GIT_REVISION@";

/// NMODL version
const std::string nmodl::Version::NMODL_VERSION = "@PROJECT_VERSION@";

const std::string nmodl::CMakeInfo::SHARED_LIBRARY_SUFFIX = "@CMAKE_SHARED_LIBRARY_SUFFIX@";

/**
* \brief Path of nrnutils.lib file
*
Expand All @@ -23,5 +36,79 @@ const std::string nmodl::CMakeInfo::SHARED_LIBRARY_SUFFIX = "@CMAKE_SHARED_LIBRA
* from CMAKE_INSTALL_PREFIX. Note that this use of NMODL_PROJECT_BINARY_DIR
* will cause ccache misses when the build prefix is changed.
*/
std::vector<std::string> nmodl::NrnUnitsLib::NRNUNITSLIB_PATH =
{"@CMAKE_INSTALL_PREFIX@/share/nmodl/nrnunits.lib", "@NMODL_PROJECT_BINARY_DIR@/share/nmodl/nrnunits.lib"};
const std::vector<std::string> nmodl::PathHelper::BASE_SEARCH_PATHS =
{"@CMAKE_INSTALL_PREFIX@", "@NMODL_PROJECT_BINARY_DIR@"};

const std::string nmodl::PathHelper::SHARED_LIBRARY_PREFIX = "@CMAKE_SHARED_LIBRARY_PREFIX@";
const std::string nmodl::PathHelper::SHARED_LIBRARY_SUFFIX = "@CMAKE_SHARED_LIBRARY_SUFFIX@";

namespace {

std::string maybe_from_env(const std::string& varname) {
const auto value = std::getenv(varname.c_str());
if (value != nullptr) {
return value;
}

#if defined(_WIN32)
std::vector<char> buffer;
DWORD copied = 0;
do {
buffer.resize(buffer.size() + MAX_PATH);
copied = GetModuleFileName(0, &buffer.at(0), buffer.size());
} while (copied >= buffer.size());
buffer.resize(copied);
fs::path executable(std::wstring(buffer.begin(), buffer.end()));
#elif defined(__APPLE__)
char buffer[PATH_MAX + 1];
uint32_t bufsize = PATH_MAX + 1;
if( _NSGetExecutablePath(buffer, &bufsize) != 0) {
return "";
}
auto executable = fs::path(buffer);
#else
auto executable = fs::read_symlink("/proc/self/exe");
#endif

auto executable_dir = fs::weakly_canonical(executable).parent_path();
if (executable_dir.filename() == "bin") {
return executable_dir.parent_path().string();
} else {
// On Windows, we may find ourselves in the top-level directory without a bin/
return executable_dir.string();
}
}

}

const std::string nmodl::PathHelper::NMODL_HOME = maybe_from_env("NMODLHOME");

std::string nmodl::PathHelper::get_path(const std::string& what, bool is_library) {
std::vector<std::string> search_paths = BASE_SEARCH_PATHS;
if (!NMODL_HOME.empty()) {
search_paths.emplace(search_paths.begin(), (fs::path(NMODL_HOME) / "bin").string());
search_paths.emplace(search_paths.begin(), (fs::path(NMODL_HOME) / "lib").string());
search_paths.emplace(search_paths.begin(), NMODL_HOME);
}

std::string filename = what;
if (is_library) {
filename = SHARED_LIBRARY_PREFIX + what + SHARED_LIBRARY_SUFFIX;
}

// check paths in order and return if found
for (const auto& path: search_paths) {
auto full_path = fs::path(path) / filename;
std::ifstream f(full_path);
if (f.good()) {
return full_path.string();
}
}
std::ostringstream err_msg;
err_msg << "Could not find '" << filename << "' in any of:\n";
for (const auto& path: search_paths) {
err_msg << "\t" << path << "\n";
}
err_msg << "Please try setting the NMODLHOME environment variable\n";
throw std::runtime_error(err_msg.str());
}
Loading
Loading