diff --git a/.github/workflows/build-and-test-wheels.yml b/.github/workflows/build-and-test-wheels.yml new file mode 100644 index 000000000..47f16e125 --- /dev/null +++ b/.github/workflows/build-and-test-wheels.yml @@ -0,0 +1,183 @@ +name: Build and test Python wheels and make PyPI release + +on: + workflow_dispatch: + push: + paths: + - '.github/workflows/build-and-test-wheels.yml' + - 'include/**' + - 'scripts/**' + - 'src/**' + - 'CMakeLists.txt' + - 'setup.py' + - 'pyproject.toml' + branches: [master] + release: + types: [published] + +jobs: + build_wheels: + name: Build wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-22.04, macos-12, macos-14] + fail-fast: false + + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Report OS + run: | + echo ${{ matrix.os }} + echo ${{ runner.os }} + uname -p + + - name: Set up QEMU + if: runner.os == 'Linux' + uses: docker/setup-qemu-action@v2 + with: + platforms: all + + - name: Build manylinux wheels + if: matrix.os == 'ubuntu-22.04' + uses: pypa/cibuildwheel@v2.19.2 + env: + # Configure cibuildwheel to build native archs, and some emulated ones + CIBW_ARCHS_LINUX: x86_64 aarch64 + CIBW_BUILD_VERBOSITY: 1 + + - name: Build macOS Intel wheels + if: matrix.os == 'macos-12' + uses: pypa/cibuildwheel@v2.19.2 + env: + CIBW_ARCHS_MACOS: x86_64 + CIBW_ENVIRONMENT_MACOS: VIZDOOM_MACOS_ARCH=x86_64 HOMEBREW_NO_AUTO_UPDATE=1 HOMEBREW_NO_INSTALL_CLEANUP=1 MACOSX_DEPLOYMENT_TARGET=12.0 + CIBW_BUILD_VERBOSITY: 1 + + - name: Build macOS Apple Silicon wheels + if: matrix.os == 'macos-14' + uses: pypa/cibuildwheel@v2.19.2 + env: + CIBW_ARCHS_MACOS: arm64 + CIBW_ENVIRONMENT_MACOS: VIZDOOM_MACOS_ARCH=arm64 HOMEBREW_NO_AUTO_UPDATE=1 HOMEBREW_NO_INSTALL_CLEANUP=1 MACOSX_DEPLOYMENT_TARGET=14.0 + CIBW_BUILD_VERBOSITY: 1 + + - name: Report built wheels + run: | + ls -l ./wheelhouse/*.whl + + - name: Upload artifacts + uses: actions/upload-artifact@v3 + with: + path: ./wheelhouse/*.whl + + test_wheels: + name: Test wheels on ${{ matrix.os }} + needs: [build_wheels] + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-22.04, ubuntu-24.04, macos-12, macos-13, macos-14] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] + fail-fast: false + + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Set up Python ${{ matrix.python-version }} environment + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Download all dists + uses: actions/download-artifact@v3 + with: + # Unpacks default artifact into dist/ + # If `name: artifact` is omitted, the action will create extra parent dir + name: artifact + path: dist + + - name: Report dist directory + run: ls -l dist + + - name: Report environment + run: | + echo ${{ matrix.os }} + echo ${{ runner.os }} + uname -p + python -c "import sys; print(sys.version)" + + - name: Install macOS Intel wheel on ${{ matrix.os }} + if: matrix.os == 'macos-12' || matrix.os == 'macos-13' + run: | + export PYTHON_VERSION=$(python -c "import sys; print(f'{sys.version_info.major}{sys.version_info.minor}')") + export WHEEL=$(ls dist/vizdoom*cp${PYTHON_VERSION}*macosx*x86_64.whl) + python -m pip install ${WHEEL}[test] + + - name: Install macOS Apple Silicon wheel on ${{ matrix.os }} + if: matrix.os == 'macos-14' + run: | + export PYTHON_VERSION=$(python -c "import sys; print(f'{sys.version_info.major}{sys.version_info.minor}')") + export WHEEL=$(ls dist/vizdoom*cp${PYTHON_VERSION}*macosx*arm64.whl) + python -m pip install ${WHEEL}[test] + + - name: Install manylinux wheel on ${{ matrix.os }} + if: matrix.os == 'ubuntu-22.04' || matrix.os == 'ubuntu-24.04' + run: | + export PYTHON_VERSION=$(python -c "import sys; print(f'{sys.version_info.major}{sys.version_info.minor}')") + export WHEEL=$(ls dist/vizdoom*cp${PYTHON_VERSION}*manylinux*x86_64.whl) + python -m pip install ${WHEEL}[test] + + - name: Import check + run: python -c "import vizdoom" + + - name: Run tests + # Skip tests on macOS with Apple Silicon, because they are slow (TODO: investigate) + if: matrix.os != 'macos-14' + run: pytest tests + + build_sdist: + name: Build source distribution + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Build sdist + run: pipx run build --sdist + + - name: Upload sdist + uses: actions/upload-artifact@v3 + with: + path: dist/*.tar.gz + + upload_pypi: + name: Upload to PyPI + needs: [build_wheels, build_sdist, test_wheels] + runs-on: ubuntu-latest + environment: pypi + permissions: + id-token: write + if: github.event_name == 'release' && github.event.action == 'published' + steps: + - name: Download all dists + uses: actions/download-artifact@v3 + with: + # Unpacks default artifact into dist/ + # If `name: artifact` is omitted, the action will create extra parent dir + name: artifact + path: dist + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.PYPI_API_TOKEN }} + # To test: + # with: + # repository_url: https://test.pypi.org/legacy/ diff --git a/.github/workflows/build-and-test-windows-wheels.yml b/.github/workflows/build-and-test-windows-wheels.yml index 01d4f7551..39962b3c7 100644 --- a/.github/workflows/build-and-test-windows-wheels.yml +++ b/.github/workflows/build-and-test-windows-wheels.yml @@ -14,7 +14,7 @@ on: branches: [master] pull_request: paths: - - '.github/workflows/**' + - '.github/workflows/build-and-test-windows-wheels.yml' - 'include/**' - 'scripts/**' - 'src/**' diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 3a8d3be1e..0e9679887 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -14,7 +14,7 @@ on: branches: [master] pull_request: paths: - - '.github/workflows/**' + - '.github/workflows/build-and-test.yml' - 'include/**' - 'scripts/**' - 'src/**' @@ -76,4 +76,6 @@ jobs: run: python -c "import vizdoom" - name: Run tests + # Skip tests on macOS with Apple Silicon, because they are slow (TODO: investigate) + if: matrix.os != 'macos-14' run: pytest tests diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml deleted file mode 100644 index d0cd8b5cc..000000000 --- a/.github/workflows/build-wheels.yml +++ /dev/null @@ -1,115 +0,0 @@ -name: Build Python wheels and make PyPI release - -on: - workflow_dispatch: - push: - paths: - - '.github/workflows/build-wheels.yml' - - 'include/**' - - 'scripts/**' - - 'src/**' - - 'CMakeLists.txt' - - 'setup.py' - - 'pyproject.toml' - branches: [master] - release: - types: [published] - -jobs: - build_wheels: - name: Build wheels on ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-22.04, macos-13, macos-14] - - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Report OS - run: | - echo ${{ matrix.os }} - echo ${{ runner.os }} - uname -p - - - name: Set up QEMU - if: runner.os == 'Linux' - uses: docker/setup-qemu-action@v2 - with: - platforms: all - - - name: Build manylinux wheels - if: matrix.os == 'ubuntu-22.04' - uses: pypa/cibuildwheel@v2.19.2 - env: - # Configure cibuildwheel to build native archs, and some emulated ones - CIBW_ARCHS_LINUX: x86_64 aarch64 - CIBW_BUILD_VERBOSITY: 3 - CIBW_REPAIR_WHEEL_COMMAND_LINUX: > - auditwheel show {wheel} && auditwheel repair -w {dest_dir} {wheel} - - - name: Build macOS Intel wheels - if: matrix.os == 'macos-13' - uses: pypa/cibuildwheel@v2.19.2 - env: - CIBW_ARCHS_MACOS: x86_64 - CIBW_BUILD_VERBOSITY: 3 - - - name: Build macOS Apple Silicon wheels - if: matrix.os == 'macos-14' - uses: pypa/cibuildwheel@v2.19.2 - env: - CIBW_ARCHS_MACOS: arm64 - CIBW_BUILD_VERBOSITY: 3 - - - name: Report built wheels - run: | - ls -l ./wheelhouse/*.whl - - - name: Upload artifacts - uses: actions/upload-artifact@v3 - with: - path: ./wheelhouse/*.whl - - build_sdist: - name: Build source distribution - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Build sdist - run: pipx run build --sdist - - - name: Upload sdist - uses: actions/upload-artifact@v3 - with: - path: dist/*.tar.gz - - upload_pypi: - name: Upload to PyPI - needs: [build_wheels, build_sdist] - runs-on: ubuntu-latest - environment: pypi - permissions: - id-token: write - if: github.event_name == 'release' && github.event.action == 'published' - steps: - - name: Download all dists - uses: actions/download-artifact@v3 - with: - # Unpacks default artifact into dist/ - # If `name: artifact` is omitted, the action will create extra parent dir - name: artifact - path: dist - - - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - password: ${{ secrets.PYPI_API_TOKEN }} - # To test: - # with: - # repository_url: https://test.pypi.org/legacy/ diff --git a/CMakeLists.txt b/CMakeLists.txt index b9347d402..46bf0aad4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -69,12 +69,15 @@ if(APPLE) set(CMAKE_MACOSX_RPATH ON) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden -fvisibility-inlines-hidden") - if(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "arm64") - set(CMAKE_OSX_ARCHITECTURES "arm64" CACHE INTERNAL "" FORCE) - message(STATUS "Apple Silicon detected, building for arm64") - else() - set(CMAKE_OSX_ARCHITECTURES "x86_64" CACHE INTERNAL "" FORCE) - message(STATUS "Intel CPU detected, building for x86_64") + if(CMAKE_APPLE_SILICON_PROCESSOR MATCHES "") + message(STATUS "CMAKE_APPLE_SILICON_PROCESSOR not set") + if(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "arm64") + set(CMAKE_OSX_ARCHITECTURES "arm64" CACHE INTERNAL "" FORCE) + message(STATUS "Apple Silicon detected, building for arm64") + else() + set(CMAKE_OSX_ARCHITECTURES "x86_64" CACHE INTERNAL "" FORCE) + message(STATUS "Intel CPU detected, building for x86_64") + endif() endif() endif(APPLE) diff --git a/README.md b/README.md index 77f52f902..d0481b12e 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ or ## Python quick start -#### At the moment ViZDoom does not work with numpy 2.0. Using ViZDoom with numpy 2.0 results in a silent bug - all the state buffers containing all data. At the moment, please use ViZDoom with numpy 1.26. +#### Versions 1.2.3 and below does not work correctly with numpy 2.0+. Please upgrade ViZDoom to the newest version or downgrade numpy to the last 1.21+ version. ### Linux To install the latest release of ViZDoom, just run: @@ -85,12 +85,18 @@ ViZDoom requires a C++11 compiler, CMake 3.12+, Boost 1.54+ SDL2, OpenAL (option ### macOS -To install the latest release of ViZDoom, just run (it may take a few minutes as it will build ViZDoom from source on M1/M2 chips): +To install the latest release of ViZDoom, just run: ```sh -brew install cmake boost sdl2 openal-soft pip install vizdoom ``` Both Intel and Apple Silicon CPUs are supported. +Pre-build wheels are available for Intel macOS 12.0+ and Apple Silicon macOS 14.0+. + +If Python wheel is not available for your platform (Python version <3.8, older macOS version), pip will try to install (build) ViZDoom from the source. +In this case, install the required dependencies using Homebrew: +```sh +brew install cmake boost sdl2 +``` We recommend using at least macOS High Sierra 10.13+ with Python 3.8+. On Apple Silicon (M1, M2, and M3), make sure you are using Python/Pip for Apple Silicon. @@ -101,7 +107,7 @@ To install the latest release of ViZDoom, just run: pip install vizdoom ``` At the moment, only x86-64 architecture is supported on Windows. -Wheels are available for Python 3.8+ on Windows. +Wheels are available for Python 3.9+ on Windows. Please note that the Windows version is not as well-tested as Linux and macOS versions. It can be used for development and testing but if you want to conduct serious (time and resource-extensive) experiments on Windows, @@ -124,7 +130,7 @@ See [documentation](https://github.com/Farama-Foundation/ViZDoom/blob/master/doc - [Python](https://github.com/Farama-Foundation/ViZDoom/blob/master/examples/python) (contain learning examples implemented in PyTorch, TensorFlow, and Theano) - [C++](https://github.com/Farama-Foundation/ViZDoom/blob/master/examples/c%2B%2B) -Python examples are currently the richest, so we recommend looking at them, even if you plan to use C++. +Python examples are currently the richest, so we recommend looking at them, even if you plan to use C++. The API is almost identical between the languages, with the only difference being that Python uses snake_case and C++ camelCase for methods and functions. diff --git a/cmake_modules/FindSDL2.cmake b/cmake_modules/FindSDL2.cmake index 614426ccc..213fd288a 100644 --- a/cmake_modules/FindSDL2.cmake +++ b/cmake_modules/FindSDL2.cmake @@ -35,7 +35,7 @@ # Modified by Eric Wing. # Added code to assist with automated building by using environmental variables # and providing a more controlled/consistent search behavior. -# Added new modifications to recognize OS X frameworks and +# Added new modifications to recognize macOS frameworks and # additional Unix paths (FreeBSD, etc). # Also corrected the header search path to follow "proper" SDL2 guidelines. # Added a search for SDL2main which is needed by some platforms. @@ -70,6 +70,8 @@ # (To distribute this file outside of CMake, substitute the full # License text for the above reference.) +file(GLOB SDL2_SEARCH_PATHS "/usr/local/Cellar/sdl2/2.*" "/opt/homebrew/Cellar/sdl2/2.*") + FIND_PATH(SDL2_INCLUDE_DIR SDL.h HINTS $ENV{SDL2DIR} @@ -79,10 +81,13 @@ FIND_PATH(SDL2_INCLUDE_DIR SDL.h /Library/Frameworks /usr/local/include/SDL2 /usr/include/SDL2 + /usr/local/Cellar/sdl2 # Brew Intel /sw # Fink + /opt/homebrew/Cellar/sdl2 # Brew Apple Silicon /opt/local # DarwinPorts /opt/csw # Blastwave /opt + ${SDL2_SEARCH_PATHS} # Brew Intel and Apple Silicon with versions ) #MESSAGE("SDL2_INCLUDE_DIR is ${SDL2_INCLUDE_DIR}") @@ -92,18 +97,20 @@ FIND_LIBRARY(SDL2_LIBRARY_TEMP $ENV{SDL2DIR} PATH_SUFFIXES lib64 lib PATHS + /usr/local/Cellar/sdl2 /sw + /opt/homebrew/Cellar/sdl2 /opt/local /opt/csw /opt + ${SDL2_SEARCH_PATHS} ) - #MESSAGE("SDL2_LIBRARY_TEMP is ${SDL2_LIBRARY_TEMP}") IF(NOT SDL2_BUILDING_LIBRARY) IF(NOT ${SDL2_INCLUDE_DIR} MATCHES ".framework") - # Non-OS X framework versions expect you to also dynamically link to - # SDL2main. This is mainly for Windows and OS X. Other (Unix) platforms + # Non-macOS framework versions expect you to also dynamically link to + # SDL2main. This is mainly for Windows and macOS. Other (Unix) platforms # seem to provide SDL2main for compatibility even though they don't # necessarily need it. FIND_LIBRARY(SDL2MAIN_LIBRARY @@ -112,10 +119,13 @@ IF(NOT SDL2_BUILDING_LIBRARY) $ENV{SDL2DIR} PATH_SUFFIXES lib64 lib PATHS + /usr/local/Cellar/sdl2 /sw + /opt/homebrew/Cellar/sdl2 /opt/local /opt/csw /opt + ${SDL2_SEARCH_PATHS} ) ENDIF(NOT ${SDL2_INCLUDE_DIR} MATCHES ".framework") ENDIF(NOT SDL2_BUILDING_LIBRARY) diff --git a/pyproject.toml b/pyproject.toml index 9d6205c2d..afd3457b7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,7 @@ build = "cp{38,39,310,311,312}-*" [tool.cibuildwheel.linux] # Only manylinux is supported (no musl) build = "cp{38,39,310,311,312}-manylinux*" +repair-wheel-command = "auditwheel show {wheel} && auditwheel repair -w {dest_dir} {wheel}" # For manylinux_2_28 we need to install the following dependencies using yum: before-all = "yum install -y cmake git boost-devel SDL2-devel openal-soft-devel" @@ -50,3 +51,7 @@ manylinux-aarch64-image = "manylinux_2_28" [tool.cibuildwheel.macos] before-all = "brew install cmake boost sdl2 openal-soft" +repair-wheel-command = "delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel}" + +# In addition, MACOSX_DEPLOYMENT_TARGET maybe needed to be set depending on the version of macOS +environment = {"HOMEBREW_NO_AUTO_UPDATE" = "1", "HOMEBREW_NO_INSTALL_CLEANUP" = "1"} diff --git a/setup.py b/setup.py index 017746a0b..e4117aa15 100644 --- a/setup.py +++ b/setup.py @@ -131,6 +131,12 @@ def run(self): f"VIZDOOM_CMAKE_ARGS is set, the following arguments will be added to cmake command: {env_cmake_args}" ) + # MacOS specific flag for specifying the architecture of the binary + if platform.startswith("darwin"): + macos_arch = os.getenv("VIZDOOM_MACOS_ARCH") + if macos_arch is not None: + cmake_arg_list.append(f"-DCMAKE_APPLE_SILICON_PROCESSOR={macos_arch}") + # Windows specific version of the libraries if platform.startswith("win"): generator = os.getenv("VIZDOOM_BUILD_GENERATOR_NAME") @@ -182,7 +188,7 @@ def run(self): os.remove("CMakeCache.txt") cmake_arg_list.append(".") - print(f"Running cmake with arguments: {cmake_arg_list}", file=sys.stderr) + sys.stderr.write(f"Running cmake with arguments: {cmake_arg_list}") try: if platform.startswith("win"): diff --git a/src/lib_python/CMakeLists.txt b/src/lib_python/CMakeLists.txt index 5b95a9067..b712439c9 100644 --- a/src/lib_python/CMakeLists.txt +++ b/src/lib_python/CMakeLists.txt @@ -8,9 +8,7 @@ if("${BUILD_PYTHON_VERSION}" STREQUAL "") endif() set(PYBIND11_PYTHON_VERSION ${BUILD_PYTHON_VERSION}) -if(WIN32) - set(PYBIND11_FINDPYTHON True) -endif() +set(PYBIND11_FINDPYTHON True) set(VIZDOOM_PYTHON_OUTPUT_DIR ${VIZDOOM_OUTPUT_DIR}/python${BUILD_PYTHON_VERSION}) set(VIZDOOM_PYTHON_PACKAGE_DIR ${VIZDOOM_PYTHON_OUTPUT_DIR}/vizdoom)