diff --git a/.conda/bin/jenkins-server-customization b/.conda/bin/jenkins-server-customization deleted file mode 100644 index 4fc1568f1..000000000 --- a/.conda/bin/jenkins-server-customization +++ /dev/null @@ -1,7 +0,0 @@ -# This Bash script must be sourced, not executed -# Customizations required for the server that currently runs Jenkins - -# TensorFlow 2.0 breaks Pymanopt: -# https://github.com/pymanopt/pymanopt/issues/86 -conda install --yes mpi4py mpich\<3.3 tensorflow\<2 scipy\<1.3 numpy!=1.17.\* -module unload gcc diff --git a/.conda/meta.yaml b/.conda/meta.yaml index 971ee237f..6bb30199c 100644 --- a/.conda/meta.yaml +++ b/.conda/meta.yaml @@ -7,7 +7,7 @@ package: # Repeating name because of the following issue: # https://github.com/conda/conda-build/issues/2475 name: brainiak - version: {{ GIT_DESCRIBE_TAG[1:] }} + version: {{ environ.get('GIT_DESCRIBE_TAG', 'v0.1.dev1')[1:] }} about: home: {{ data.get('url') }} @@ -18,7 +18,7 @@ source: path: ../ build: - number: {{ GIT_DESCRIBE_NUMBER }} + number: {{ environ.get('GIT_DESCRIBE_NUMBER', 0) }} script_env: # We need the source directory because conda removes source / working diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 000000000..24cec1681 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,46 @@ +on: [pull_request] +jobs: + pypi: + env: + IGNORE_CONDA: true + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-20.04, macos-latest] + python-version: [3.8] + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - uses: mpi4py/setup-mpi@v1 + - run: | + python3 -m pip install -U pip + - if: ${{ contains(matrix.os, 'ubuntu') }} + run: | + ./pr-check.sh + - if: ${{ contains(matrix.os, 'macos') }} + run: | + export CC=$(brew --prefix)/opt/llvm/bin/clang + export CXX=$(brew --prefix)/opt/llvm/bin/clang++ + export LDFLAGS="-L/usr/local/opt/llvm/lib + -Wl,-rpath,/usr/local/opt/llvm/lib $LDFLAGS" + export CPPFLAGS="-I/usr/local/opt/llvm/include $CPPFLAGS" + ./pr-check.sh + - uses: codecov/codecov-action@v1 + conda: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-20.04, macos-latest] + python-version: [3.8] + steps: + - uses: actions/checkout@v2 + - uses: conda-incubator/setup-miniconda@v2 + with: + channels: conda-forge + python-version: ${{ matrix.python-version }} + - run: | + export CONDA_HOME=$CONDA + conda install conda-build + .conda/bin/build ${{ matrix.python-version }} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index d936c8b50..000000000 --- a/.travis.yml +++ /dev/null @@ -1,51 +0,0 @@ -matrix: - include: - - os: linux - dist: xenial - sudo: required - language: python - python: 3.6 - addons: &linux_addons - apt: &apt - packages: - - build-essential libgomp1 libmpich-dev mpich - - - os: linux - dist: xenial - sudo: required - language: python - python: 3.8 - addons: *linux_addons - - - os: osx - language: generic - env: &macos_env - - CC=/usr/local/opt/llvm/bin/clang - - CXX=/usr/local/opt/llvm/bin/clang++ - - LDFLAGS="-L/usr/local/opt/llvm/lib - -Wl,-rpath,/usr/local/opt/llvm/lib $LDFLAGS" - - CPPFLAGS="-I/usr/local/opt/llvm/include $CPPFLAGS" - - HOMEBREW_NO_AUTO_UPDATE=1 - before_install: &macos_before_install - - brew install llvm mpich python3 - - # Build and test conda packages - - os: linux - dist: xenial - install: ./.conda/bin/install-miniconda - script: .conda/bin/build - - - os: osx - install: ./.conda/bin/install-miniconda - script: .conda/bin/build - allow_failures: - - os: linux - script: .conda/bin/build - -install: - - python3 -m pip install -U pip codecov - -script: - - ./pr-check.sh -after_success: - - codecov diff --git a/README.rst b/README.rst index 0efdb9c61..1ae08821f 100644 --- a/README.rst +++ b/README.rst @@ -1,8 +1,9 @@ Brain Imaging Analysis Kit ========================== -.. image:: https://travis-ci.org/brainiak/brainiak.svg?branch=master - :target: https://travis-ci.org/brainiak/brainiak +.. image:: https://github.com/brainiak/brainiak/actions/workflows/main.yml/badge.svg + :alt: Status of GitHub Actions workflow + :target: https://github.com/brainiak/brainiak/actions .. image:: https://codecov.io/gh/brainiak/brainiak/branch/master/graph/badge.svg :target: https://codecov.io/gh/brainiak/brainiak diff --git a/pr-check.sh b/pr-check.sh index 287d4fb78..1d58bb8b9 100755 --- a/pr-check.sh +++ b/pr-check.sh @@ -73,7 +73,7 @@ function exit_with_error_and_venv { exit_with_error "$1" } -if [ $(which conda) ] +if [ -z $IGNORE_CONDA ] && [ $(which conda) ] then export PYTHONNOUSERSITE=True create_venv=create_conda_venv @@ -113,27 +113,13 @@ python3 -m pip install -U pip || \ # brainiak will also be installed together with the developer dependencies, but # we install it first here to check that installation succeeds without the # developer dependencies. -python3 -m pip install $ignore_installed -U -e . || \ +python3 -m pip install $ignore_installed -U -e .[matnormal] || \ exit_with_error_and_venv "Failed to install BrainIAK." # install developer dependencies python3 -m pip install $ignore_installed -U -r requirements-dev.txt || \ exit_with_error_and_venv "Failed to install development requirements." -# Source a user-specified Bash script to customize the installation. -# This option is not supported and should not be documented in user docs. -# In particular we use it on our CentOS 6 Jenkins server with .conda/bin/ -customization_script_flag=$1 -if [ ${customization_script_flag:-default} = "--customization-script" ] -then - customization_script=$2 - if [ ${customization_script:-default} = "default" ] - then - exit_with_error_and_venv "Must specify customization script" - else - source $customization_script - fi -fi # static analysis ./run-checks.sh || \ diff --git a/requirements-dev.txt b/requirements-dev.txt index 168c864c4..576d9e66b 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,4 +1,3 @@ --e .[matnormal] # Installing BrainIAK here ensures no requirement conflict. coverage flake8 flake8-print diff --git a/run-tests.sh b/run-tests.sh index 181cc84a7..0c7fda315 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -37,7 +37,7 @@ if [ -z $mpi_command ] then mpi_command=mpiexec fi -$mpi_command -n 2 coverage run -m pytest +$mpi_command -n 2 coverage run -m mpi4py -m pytest # Coverage produces empty files which trigger warnings on combine find . -name ".coverage.*" -size 0 -print0 | xargs -0 rm -f diff --git a/setup.cfg b/setup.cfg index f7429fbc3..e616ffe02 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,16 +1,8 @@ [flake8] max-complexity = 10 -ignore = - # Defaults - # https://gitlab.com/pycqa/flake8/blob/master/src/flake8/defaults.py - E121, - E123, - E126, - E226, - E24, - E704, - W503, - W504, +extend-ignore = + # Docstrings + D, [coverage:run] source = brainiak diff --git a/setup.py b/setup.py index 99ef11f31..fb4043ce8 100644 --- a/setup.py +++ b/setup.py @@ -3,6 +3,7 @@ from setuptools import setup, Extension, find_packages from setuptools.command.build_ext import build_ext import os +import site import sys import setuptools from copy import deepcopy @@ -12,6 +13,9 @@ "lower versions are not supported" ) +# https://github.com/pypa/pip/issues/7953#issuecomment-645133255 +site.ENABLE_USER_SITE = "--user" in sys.argv[1:] + here = os.path.abspath(os.path.dirname(__file__)) # Get the long description from the README file diff --git a/tests/.flake8 b/tests/.flake8 index 3c0105d98..b90c8c63a 100644 --- a/tests/.flake8 +++ b/tests/.flake8 @@ -1,16 +1,8 @@ [flake8] max-complexity = 10 -ignore = - # Defaults - # https://gitlab.com/pycqa/flake8/blob/master/src/flake8/defaults.py - E121, - E123, - E126, - E226, - E24, - E704, - W503, - W504, +extend-ignore = # Print restriction only applies to libraries T001, - T003 + T003, + # Docstrings + D, diff --git a/tests/conftest.py b/tests/conftest.py index de5ec6b56..5f0864bf0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,5 @@ +import multiprocessing + from mpi4py import MPI import pytest import numpy @@ -14,3 +16,10 @@ def seeded_rng(): random.seed(0) numpy.random.seed(0) tensorflow.random.set_seed(0) + + +skip_non_fork = pytest.mark.skipif( + multiprocessing.get_start_method() != "fork" + and MPI.COMM_WORLD.Get_attr(MPI.APPNUM) is not None, + reason="MPI only works with multiprocessing fork start method.", +) diff --git a/tests/fcma/test_mvpa_voxel_selection.py b/tests/fcma/test_mvpa_voxel_selection.py index 763f87cc2..9ff90ed0a 100644 --- a/tests/fcma/test_mvpa_voxel_selection.py +++ b/tests/fcma/test_mvpa_voxel_selection.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +import conftest + from brainiak.fcma.mvpa_voxelselector import MVPAVoxelSelector from brainiak.searchlight.searchlight import Searchlight from sklearn import svm @@ -23,6 +25,7 @@ prng = RandomState(1234567890) +@conftest.skip_non_fork def test_mvpa_voxel_selection(): data = prng.rand(5, 5, 5, 8).astype(np.float32) # all MPI processes read the mask; the mask file is small diff --git a/tests/fcma/test_voxel_selection.py b/tests/fcma/test_voxel_selection.py index b4c7739db..ed32d2b62 100644 --- a/tests/fcma/test_voxel_selection.py +++ b/tests/fcma/test_voxel_selection.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +import conftest + from brainiak.fcma.voxelselector import VoxelSelector from scipy.stats.mstats import zscore from sklearn import svm @@ -37,6 +39,7 @@ def create_epoch(): return mat +@conftest.skip_non_fork def test_voxel_selection(): fake_raw_data = [create_epoch() for i in range(8)] labels = [0, 1, 0, 1, 0, 1, 0, 1] @@ -80,6 +83,7 @@ def test_voxel_selection(): "results") +@conftest.skip_non_fork def test_voxel_selection_with_two_masks(): fake_raw_data1 = [create_epoch() for i in range(8)] fake_raw_data2 = [create_epoch() for i in range(8)] diff --git a/tests/searchlight/test_searchlight.py b/tests/searchlight/test_searchlight.py index 0af7b1afd..a6b4c1751 100644 --- a/tests/searchlight/test_searchlight.py +++ b/tests/searchlight/test_searchlight.py @@ -16,6 +16,7 @@ import numpy as np from mpi4py import MPI +import conftest from brainiak.searchlight.searchlight import Searchlight from brainiak.searchlight.searchlight import Diamond, Ball @@ -30,6 +31,7 @@ def cube_sfn(data, msk, myrad, bcast_var): return None +@conftest.skip_non_fork def test_searchlight_with_cube(): sl = Searchlight(sl_rad=3) comm = MPI.COMM_WORLD @@ -97,6 +99,7 @@ def diamond_sfn(data, msk, myrad, bcast_var): return None +@conftest.skip_non_fork def test_searchlight_with_diamond(): sl = Searchlight(sl_rad=3, shape=Diamond) comm = MPI.COMM_WORLD @@ -136,6 +139,7 @@ def ball_sfn(data, msk, myrad, bcast_var): return None +@conftest.skip_non_fork def test_searchlight_with_ball(): sl = Searchlight(sl_rad=3, shape=Ball) comm = MPI.COMM_WORLD @@ -212,6 +216,7 @@ def block_test_sfn(data, msk, myrad, bcast_var, extra_params): return outmat[myrad:-myrad, myrad:-myrad, myrad:-myrad] +@conftest.skip_non_fork def test_correctness(): # noqa: C901 def voxel_test(data, mask, max_blk_edge, rad):