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

NEST wheels #2170

Closed
wants to merge 77 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
f7422fe
Added wheel build environment to .gitignore
Sep 23, 2021
17a2643
Cache PYEXECDIR to allow Python to fish it out of CMake
Sep 23, 2021
ac9da28
Vendor deprecated distutils, useful for making distributions.
Sep 23, 2021
0983f78
Added script to prepare the wheel build environment
Sep 23, 2021
a25e1b9
Build wheel protocol to be executed by CI
Sep 23, 2021
fb5ee3e
Added CMake rig to `setup.py`. Only executed on wheel build.
Sep 23, 2021
6345f85
Actually use the rigged class
Sep 23, 2021
aed3c45
Fixed install location and RPATH of `pynestkernel.so` during buildwheel
Sep 30, 2021
a3e7352
Clone wheel env: current commit from current remote
Helveg Sep 30, 2021
8256081
include sli in package
Helveg Sep 30, 2021
294712a
set env var before py script
Helveg Sep 30, 2021
db7f135
make full clone
Helveg Sep 30, 2021
966e7ab
Read NEST_CMAKE_BUILDWHEEL from env
Helveg Sep 30, 2021
1aa2028
Install the Python source code to copy it. Auto detect all packages.
Helveg Sep 30, 2021
5688e1f
CI attempt
Helveg Sep 30, 2021
5495e5b
Set install prefix for tmp wheel env
Helveg Sep 30, 2021
0c66356
copyright headers
Helveg Sep 30, 2021
cb5beb3
pip install wheel and cython
Helveg Sep 30, 2021
7f35f5e
Install `libomp-dev`
Helveg Sep 30, 2021
3fb809c
Fix MacOS OpenMP install command
Helveg Sep 30, 2021
cda72f8
didn't source'ing work?
Helveg Sep 30, 2021
ce90ab1
Install with `-j4`, set buildwheel env var in cibuildwheel options.
Helveg Sep 30, 2021
88fd2eb
Hardcoded semver as a solution to obtain wheels
Helveg Sep 30, 2021
d283c70
Get Python libraries
Helveg Sep 30, 2021
9ea1086
Guard commit SHA race conditions on GHA by using `actions/checkout`.
Oct 1, 2021
b65ee64
fixed uses/run combo
Oct 1, 2021
6b606ed
fixed prep_wheel_env changes
Oct 1, 2021
4c9b042
no sudo on GHA
Oct 1, 2021
a7c5800
use yum on manylinux_2010
Oct 1, 2021
2026e35
Added `cibuildwheel` wheels
Helveg Oct 7, 2021
ff5bf8f
Only 64 bit wheels(No OpenMP on 32 bit manylinux image compilers?)
Helveg Oct 7, 2021
ba1d368
Test `auto64` wheels
Helveg Oct 7, 2021
99b7035
Add `cibuildwheel` config to pyproject. skip musllinux wheels
Helveg Oct 7, 2021
a3399ff
toml syntax error
Helveg Oct 7, 2021
144cc13
fix `environment` key
Helveg Oct 7, 2021
3ef78d8
place `wheelhouse` in default directory (parent of pwd at that point)
Helveg Oct 7, 2021
18b1c0f
Use pypa `cibuildwheel` action. clean up. fixed copyright headers.
Helveg Oct 7, 2021
86a5127
List `cmake` as a build system requirement.
Helveg Oct 7, 2021
b1cdf25
Added `build_sdist` and `upload_pypi` step
Helveg Oct 7, 2021
fffccce
Don't copy nest folder SLI files
Helveg Oct 22, 2021
e72a97e
pass "GHA" conditionally as arg when running on GHA
Helveg Oct 22, 2021
5f55d66
added containerbuild script
Helveg Oct 22, 2021
19c3a99
add SLI include inlining script
terhorstd Oct 22, 2021
b75be4a
move of inlining helper script to extras
terhorstd Oct 22, 2021
9a5411e
fix updated func name
Helveg Oct 22, 2021
0458534
turn on wheelbuild in containerbuild
Helveg Oct 22, 2021
7e23dcf
Don't use cibuildwheel action. How to change working directory for it?
Helveg Oct 22, 2021
1abe04a
Added cython to containerbuild build requirements
Helveg Oct 22, 2021
efa2e5e
check build with package dir arg
Helveg Oct 22, 2021
db96457
Merge branch 'circular-wheels' of https://github.com/Helveg/nest-simu…
jougs Oct 22, 2021
75c7e76
added copyright statement
terhorstd Oct 22, 2021
f78d054
modify SLI imports from .cc files
terhorstd Oct 22, 2021
f9cd834
fix typo in filename
terhorstd Oct 22, 2021
d1c19e1
exclude versionchecker from the build wheel env
Helveg Oct 22, 2021
258c5b4
Move PyNEST initializer code for SLI to standard NEST initializer
jougs Oct 22, 2021
f04b0f9
Merge branch 'circular-wheels' of https://github.com/Helveg/nest-simu…
jougs Oct 22, 2021
13cea5a
fix multline raw strings
terhorstd Oct 22, 2021
38f3237
Merge branch 'master' into circular-wheels
Helveg Oct 22, 2021
7ee020c
Merge branch 'circular-wheels' of https://github.com/Helveg/nest-simu…
jougs Oct 22, 2021
c12ad63
Inline SLI init files to remove SLI library paths (#4)
terhorstd Oct 22, 2021
e702eb5
Move PyNEST initializer code for SLI to standard NEST initializer (#3)
jougs Oct 22, 2021
cbd7ffd
stop clang-format from asking to break the code
terhorstd Oct 22, 2021
01013c0
change inline.py file handling
terhorstd Oct 22, 2021
9c23c1c
Merge branch 'sli-init-inlining' into circular-wheels
Helveg Oct 22, 2021
c1c0af0
Merge branch 'circular-wheels' into sli-init-inlining
terhorstd Oct 22, 2021
8dadbf1
Remove unused argument modulepath
jougs Oct 22, 2021
d594c97
Merge branch 'circular-wheels' of https://github.com/Helveg/nest-simu…
jougs Oct 22, 2021
cbed9fd
fix indent typo
terhorstd Oct 22, 2021
9d74cbe
Merge branch 'sli-init-inlining' of github.com:terhorstd/nest-simulat…
terhorstd Oct 22, 2021
326bb76
Merge branch 'sli-init-inlining' into circular-wheels
Helveg Oct 22, 2021
5b1a007
Merge remote-tracking branch 'jougs/circular-wheels' into circular-wh…
Helveg Oct 22, 2021
a205b30
Inlined SLI code for wheel build
Helveg Oct 22, 2021
eee711f
Fix kwarg order for non kwarg calls
Helveg Oct 22, 2021
3222d80
Skip unsupported <3.8 Python wheels
Helveg Oct 22, 2021
4d17035
static code analysis fixes
Helveg Oct 22, 2021
61b69ef
Hatcheting some `run` statements in examples and doc top prevent inline
Helveg Nov 8, 2021
f63c2e7
Merge branch 'master' into circular-wheels
Helveg Nov 16, 2021
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
69 changes: 69 additions & 0 deletions .github/workflows/wheels.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
name: Build wheels

on: [push, pull_request]

jobs:
build_wheels:
name: Build wheels on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-20.04, macOS-10.15]

steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- name: Install OpenMP (MacOS)
if: runner.os == 'macOS'
run: brew install libomp
- name: Install OpenMP (Ubuntu)
if: runner.os == 'Linux'
run: sudo apt-get install libomp-dev
- name: Install Python wheel building prep requirements
run: |
python -m pip install wheel cython
- name: Clone wheel building environment
uses: actions/checkout@v2
with:
path: build_wheel_env
- name: Prepare wheel building environment
run: |
source extras/wheelbuild/wheelbuild.sh GHA
- name: Build wheels
uses: pypa/[email protected]
with:
package-dir: build_wheel_env
- uses: actions/upload-artifact@v2
with:
path: ./wheelhouse/*.whl

build_sdist:
name: Build source distribution
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- name: Build sdist
run: python setup.py sdist
- uses: actions/upload-artifact@v2
with:
path: dist/*.tar.gz

upload_pypi:
needs: [build_wheels, build_sdist]
runs-on: ubuntu-latest
# upload to PyPI on every tag starting with 'v'
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/v')
# alternatively, to publish when a GitHub Release is created, use the following rule:
# if: github.event_name == 'release' && github.event.action == 'published'
steps:
- uses: actions/download-artifact@v2
with:
name: artifact
path: dist

- uses: pypa/[email protected]
with:
user: __token__
password: ${{ secrets.pypi_password }}
# To test: repository_url: https://test.pypi.org/legacy/
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@

__pycache__/
build/
build_wheel_env/
_build/
install/
reports/
Expand Down
6 changes: 5 additions & 1 deletion cmake/ProcessOptions.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,8 @@ function( NEST_PROCESS_STATIC_LIBRARIES )
"@loader_path/../../${CMAKE_INSTALL_LIBDIR}/nest"
# for pynestkernel: origin at <prefix>/lib/python3.x/site-packages/nest
"@loader_path/../../../nest"
# for wheels
"@loader_path"
PARENT_SCOPE )
else ()
set( CMAKE_INSTALL_RPATH
Expand All @@ -243,6 +245,8 @@ function( NEST_PROCESS_STATIC_LIBRARIES )
"\$ORIGIN/../../${CMAKE_INSTALL_LIBDIR}/nest"
# for pynestkernel: origin at <prefix>/lib/python3.x/site-packages/nest
"\$ORIGIN/../../../nest"
# for wheels
"\$ORIGIN"
PARENT_SCOPE )
endif ()

Expand Down Expand Up @@ -451,7 +455,7 @@ endfunction()

function( NEST_POST_PROCESS_WITH_PYTHON )
if ( Python_FOUND )
set( PYEXECDIR "${CMAKE_INSTALL_LIBDIR}/python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}/site-packages" PARENT_SCOPE )
set( PYEXECDIR "${CMAKE_INSTALL_LIBDIR}/python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}/site-packages" CACHE PATH "Python module destination" FORCE)
Copy link
Contributor Author

@Helveg Helveg Oct 11, 2021

Choose a reason for hiding this comment

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

Should insert comment why:

The wheel build queries cmake -LAH for PYEXECDIR, to appear there PYEXECDIR must be cached.

endif()
endfunction()

Expand Down
68 changes: 68 additions & 0 deletions extras/inline_sli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# inline_sli.py
#
# This file is part of NEST.
#
# Copyright (C) 2004 The NEST Initiative
#
# NEST is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# NEST is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with NEST. If not, see <http://www.gnu.org/licenses/>.

import re
import sys
from pathlib import Path

SLI_LIBPATH = Path(__file__).parent.parent / "lib" / "sli"


def replaced(filename, noinclude=None):
'''
Recursively replace "(xy) run" SLI commands.
'''
if noinclude is None:
noinclude = []
sliimport = re.compile(r'\((?P<name>[^)]+)\) run')
for no, line in enumerate(filename.open('r', encoding="utf8")):
found = False
for match in sliimport.finditer(line):
incfile = (SLI_LIBPATH / match.group('name')).with_suffix(".sli")
if incfile in noinclude:
continue
if not incfile.is_file():
yield(f"%%% INSERT FAILED FOR >>> {incfile} <<<\n")
yield(f"%%% {line}")
continue
yield(f"%%% INSERT {incfile} LITERALLY\n")
yield(f"%%% L{no}: {line}")
noinclude.append(incfile)
for includedline in replaced(incfile, noinclude):
yield includedline.replace('"', "'")
yield(f"%%% END {incfile}\n")
found = True
if not found:
yield f"{line}"


def inline(files):
'''
SLI code inlining.
'''
for arg in files:
filename = Path(arg)
newfile = filename.with_suffix(".new")
with newfile.open("w", encoding="utf8") as outfile:
for line in replaced(filename):
outfile.write(line)
newfile.rename(filename)
158 changes: 158 additions & 0 deletions extras/wheelbuild/_vendored.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# -*- coding: utf-8 -*-
#
# _vendored.py
#
# This file is part of NEST.
#
# Copyright (C) 2004 The NEST Initiative
#
# NEST is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# NEST is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with NEST. If not, see <http://www.gnu.org/licenses/>.

# This code is taken from the Python `distutils` module.

import os

_path_created = dict()

def mkpath(name, mode=0o777, verbose=1, dry_run=0):
"""Create a directory and any missing ancestor directories.

If the directory already exists (or if 'name' is the empty string, which
means the current directory, which of course exists), then do nothing.
Raise DistutilsFileError if unable to create some directory along the way
(eg. some sub-path exists, but is a file rather than a directory).
If 'verbose' is true, print a one-line summary of each mkdir to stdout.
Return the list of directories actually created.
"""

global _path_created

# Detect a common bug -- name is None
if not isinstance(name, str):
raise Exception(
"mkpath: 'name' must be a string (got %r)" % (name,))

# XXX what's the better way to handle verbosity? print as we create
# each directory in the path (the current behaviour), or only announce
# the creation of the whole path? (quite easy to do the latter since
# we're not using a recursive algorithm)

name = os.path.normpath(name)
created_dirs = []
if os.path.isdir(name) or name == '':
return created_dirs
if _path_created.get(os.path.abspath(name)):
return created_dirs

(head, tail) = os.path.split(name)
tails = [tail] # stack of lone dirs to create

while head and tail and not os.path.isdir(head):
(head, tail) = os.path.split(head)
tails.insert(0, tail) # push next higher dir onto stack

# now 'head' contains the deepest directory that already exists
# (that is, the child of 'head' in 'name' is the highest directory
# that does *not* exist)
for d in tails:
#print "head = %s, d = %s: " % (head, d),
head = os.path.join(head, d)
abs_head = os.path.abspath(head)

if _path_created.get(abs_head):
continue

if not dry_run:
try:
os.mkdir(head, mode)
except OSError as exc:
if not (exc.errno == errno.EEXIST and os.path.isdir(head)):
raise Exception(
"could not create '%s': %s" % (head, exc.args[-1]))
created_dirs.append(head)

_path_created[abs_head] = 1
return created_dirs

def copy_py_tree(src, dst, preserve_mode=1, preserve_times=1,
preserve_symlinks=0, update=0, verbose=1, dry_run=0, exclude=None):
"""Copy an entire directory tree 'src' to a new location 'dst'.

Both 'src' and 'dst' must be directory names. If 'src' is not a
directory, raise DistutilsFileError. If 'dst' does not exist, it is
created with 'mkpath()'. The end result of the copy is that every
file in 'src' is copied to 'dst', and directories under 'src' are
recursively copied to 'dst'. Return the list of files that were
copied or might have been copied, using their output name. The
return value is unaffected by 'update' or 'dry_run': it is simply
the list of all files under 'src', with the names changed to be
under 'dst'.

'preserve_mode' and 'preserve_times' are the same as for
'copy_file'; note that they only apply to regular files, not to
directories. If 'preserve_symlinks' is true, symlinks will be
copied as symlinks (on platforms that support them!); otherwise
(the default), the destination of the symlink will be copied.
'update' and 'verbose' are the same as for 'copy_file'.
"""
from distutils.file_util import copy_file

if exclude is None:
exclude = []

if not dry_run and not os.path.isdir(src):
raise Exception(
"cannot copy tree '%s': not a directory" % src)
try:
names = os.listdir(src)
except OSError as e:
if dry_run:
names = []
else:
raise Exception(
"error listing files in '%s': %s" % (src, e.strerror))

if not dry_run:
mkpath(dst, verbose=verbose)

outputs = []

for n in names:
src_name = os.path.join(src, n)
dst_name = os.path.join(dst, n)

if n.startswith('.nfs'):
# skip NFS rename files
continue

if preserve_symlinks and os.path.islink(src_name):
link_dest = os.readlink(src_name)
if verbose >= 1:
log.info("linking %s -> %s", dst_name, link_dest)
if not dry_run:
os.symlink(link_dest, dst_name)
outputs.append(dst_name)

elif os.path.isdir(src_name):
outputs.extend(
copy_py_tree(src_name, dst_name, preserve_mode,
preserve_times, preserve_symlinks, update,
verbose=verbose, dry_run=dry_run))
elif n.endswith(".py") and n not in exclude:
copy_file(src_name, dst_name, preserve_mode,
preserve_times, update, verbose=verbose,
dry_run=dry_run)
outputs.append(dst_name)

return outputs
18 changes: 18 additions & 0 deletions extras/wheelbuild/containerbuild.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/bin/bash
set -e -u -x

rm -rf /opt/python/cp35-cp35m
export NEST_CMAKE_BUILDWHEEL=ON

for PYBIN in /opt/python/*/bin; do
"${PYBIN}/pip" install wheel cmake cython
export PATH="${PYBIN}":$PATH
which cmake
"${PYBIN}/python" ./setup.py bdist_wheel
done

# Bundle external shared libraries into the wheels
/opt/python/cp39-cp39/bin/pip install auditwheel
for whl in ./dist/*.whl; do
/opt/python/cp39-cp39/bin/auditwheel repair "$whl"
done
Loading