diff --git a/.github/workflows/test_and_deploy.yml b/.github/workflows/test_and_deploy.yml index 039ed39c7..60c492a25 100644 --- a/.github/workflows/test_and_deploy.yml +++ b/.github/workflows/test_and_deploy.yml @@ -48,11 +48,11 @@ jobs: with: python-version: '3.10' - name: Ensure latest pip, wheel & setuptools - run: python -m pip install -q --upgrade pip wheel setuptools + run: python -m pip install -q --upgrade pip build - uses: actions/checkout@v4 - name: Generate sdist run: | - NLE_RELEASE_BUILD=1 python setup.py sdist + NLE_RELEASE_BUILD=1 python -m build --sdist --outdir dist - name: Install from sdist run: | SDISTNAME=$(ls dist/) @@ -74,11 +74,11 @@ jobs: with: python-version: 3.13 - name: Ensure latest pip, wheel & setuptools - run: python -m pip install -q --upgrade pip wheel setuptools + run: python -m pip install -q --upgrade pip build - uses: actions/checkout@v4 - name: Generate sdist run: | - NLE_RELEASE_BUILD=1 python setup.py sdist + NLE_RELEASE_BUILD=1 python -m build --sdist --outdir dist - name: Install from sdist run: | SDISTNAME=$(ls dist/) diff --git a/CMakeLists.txt b/CMakeLists.txt index e447c9c65..24a3073e0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,15 @@ cmake_minimum_required(VERSION 3.28) -file(STRINGS "version.txt" NLE_VERSION) -# Remove any rcXX suffix from the version number as CMake doesn't like it -string(REGEX REPLACE "rc[0-9+]$" "" CMAKE_NLE_VERSION ${NLE_VERSION}) +if(NOT DEFINED NLE_VERSION) + if(DEFINED SKBUILD_PROJECT_VERSION) + set(NLE_VERSION ${SKBUILD_PROJECT_VERSION}) + else() + set(NLE_VERSION "0.0.0") # Unversioned. + endif() +endif() + +# Extract version number (major.minor.patch) as CMake doesn't like rc/dev +# suffixes +string(REGEX MATCH "^[0-9]+(\\.[0-9]+)*" CMAKE_NLE_VERSION "${NLE_VERSION}") project(nle VERSION ${CMAKE_NLE_VERSION}) if(CMAKE_BUILD_TYPE MATCHES Debug) @@ -36,13 +44,6 @@ message(STATUS "Building nle backend version: ${CMAKE_NLE_VERSION}") set(CMAKE_POSITION_INDEPENDENT_CODE ON) -# We use this to decide where the root of the nle/ package is. Normally it -# shouldn't be needed, but sometimes (e.g. when using setuptools) we are -# generating some of the files outside of the original package path. -set(PYTHON_SRC_PARENT - ${nle_SOURCE_DIR} - CACHE STRING "Directory containing the nle package files") - set(HACKDIR "$ENV{HOME}/nethackdir.nle" CACHE STRING "Configuration files for nethack") @@ -202,3 +203,12 @@ target_link_libraries(_pyconverter PUBLIC converter) set_target_properties(_pyconverter PROPERTIES CXX_STANDARD 14) target_include_directories( _pyconverter PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/third_party/converter) + +# Only install if we are building as part of a Python project. +if(DEFINED SKBUILD_PROJECT_VERSION) + install( + TARGETS _pynethack _pyconverter nethack + RUNTIME DESTINATION ${PYTHON_PACKAGE_NAME} + LIBRARY DESTINATION ${PYTHON_PACKAGE_NAME} + ARCHIVE DESTINATION ${PYTHON_PACKAGE_NAME}) +endif() diff --git a/nle/nethack/nethack.py b/nle/nethack/nethack.py index 868295be0..c5c368ec7 100644 --- a/nle/nethack/nethack.py +++ b/nle/nethack/nethack.py @@ -1,5 +1,4 @@ # Copyright (c) Facebook, Inc. and its affiliates. -import importlib.resources import os import shutil import sys @@ -12,6 +11,7 @@ from nle import _pynethack DLPATH = os.path.join(os.path.dirname(_pynethack.__file__), "libnethack.so") +HACKDIR = os.path.join(os.path.dirname(_pynethack.__file__), "nethackdir") DUNGEON_SHAPE = (_pynethack.nethack.ROWNO, _pynethack.nethack.COLNO - 1) BLSTATS_SHAPE = (_pynethack.nethack.NLE_BLSTATS_SIZE,) @@ -67,12 +67,6 @@ "time", ) -try: - HACKDIR = str(importlib.resources.files("nle") / "nethackdir") -except AttributeError: # No files() function in Python 3.8. - with importlib.resources.path("nle", "nethackdir") as path: - HACKDIR = str(path) - TTYREC_VERSION = 3 diff --git a/pyproject.toml b/pyproject.toml index 71e3f6e28..d1ff3031e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,25 @@ [project] name = "nle" +dynamic = ["version"] description = "The NetHack Learning Environment (NLE): a reinforcement learning environment based on NetHack" readme = "README.md" authors = [{ name = "The NLE Dev Team" }] requires-python = ">=3.10" -dynamic = ["classifiers", "version"] +classifiers = [ + "License :: OSI Approved :: Nethack General Public License", + "Development Status :: 5 - Production/Stable", + "Operating System :: POSIX :: Linux", + "Operating System :: MacOS", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: C", + "Programming Language :: C++", + "Topic :: Scientific/Engineering :: Artificial Intelligence", + "Topic :: Games/Entertainment", +] dependencies = ["pybind11>=2.2", "numpy>=1.16", "gymnasium==1.2.0"] [project.license] @@ -15,19 +30,20 @@ Homepage = "https://github.com/NetHack-LE/nle" [build-system] # Lock build-deps as uv does not yet support locking of build deps: astral-sh/uv#5190 -requires = ["cmake~=4.2.1", "pybind11~=2.2", "setuptools~=80.9.0"] -build-backend = "setuptools.build_meta" - -[tool.setuptools.packages] -find = { include = [ - "nle", - "nle.dataset", - "nle.env", - "nle.nethack", - "nle.agent", - "nle.scripts", - "nle.tests", -] } +requires = ["scikit-build-core~=0.10", "pybind11~=2.2", "setuptools-scm~=9.2.2"] +build-backend = "scikit_build_core.build" + +[tool.scikit-build] +cmake.build-type = "Release" +cmake.args = ["-DHACKDIR=nle/nethackdir", "-DPYTHON_PACKAGE_NAME=nle"] +minimum-version = "build-system.requires" +metadata.version.provider = "scikit_build_core.metadata.setuptools_scm" +generate = [ + { path = "nle/version.py", template = '__version__ = "${version}"' }, +] + +[tool.setuptools_scm] +# This section is necessary to activate setuptools-scm, but can be empty [project.scripts] nle-play = "nle.scripts.play:main" diff --git a/setup.py b/setup.py deleted file mode 100644 index 9a11960d3..000000000 --- a/setup.py +++ /dev/null @@ -1,115 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (c) Facebook, Inc. and its affiliates. -# -# List of environment variables used: -# -# NLE_BUILD_RELEASE -# If set, builds wheel (s)dist such as to prepare it for upload to PyPI. -# -# HACKDIR -# If set, install NetHack's data files in this directory. -# -import os -import pathlib -import shutil -import subprocess -import sys -import sysconfig - -import setuptools -from setuptools.command import build_ext - - -class CMakeBuild(build_ext.build_ext): - def run(self): # Necessary for pip install -e. - for ext in self.extensions: - self.build_extension(ext) - - def build_extension(self, ext): - source_path = pathlib.Path(__file__).parent.resolve() - output_path = ( - pathlib.Path(self.get_ext_fullpath(ext.name)) - .parent.joinpath("nle") - .resolve() - ) - hackdir_path = os.getenv("HACKDIR", output_path.joinpath("nethackdir")) - - os.makedirs(self.build_temp, exist_ok=True) - build_type = "Debug" if self.debug else "Release" - - generator = "Ninja" if shutil.which("ninja") else "Unix Makefiles" - - cmake_cmd = [ - "cmake", - str(source_path), - "-G%s" % generator, - "-DPYTHON_SRC_PARENT=%s" % source_path, - # Tell cmake which Python we want. - "-DPYTHON_EXECUTABLE=%s" % sys.executable, - "-DCMAKE_BUILD_TYPE=%s" % build_type, - "-DCMAKE_INSTALL_PREFIX=%s" % sys.base_prefix, - "-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=%s" % output_path, - "-DHACKDIR=%s" % hackdir_path, - "-DPYTHON_INCLUDE_DIR=%s" % sysconfig.get_paths()["include"], - "-DPYTHON_LIBRARY=%s" % sysconfig.get_config_var("LIBDIR"), - ] - - build_cmd = ["cmake", "--build", ".", "--parallel"] - install_cmd = ["cmake", "--install", "."] - - try: - subprocess.check_call(cmake_cmd, cwd=self.build_temp) - subprocess.check_call(build_cmd, cwd=self.build_temp) - # Installs nethackdir. TODO: Can't we do this with setuptools? - subprocess.check_call(install_cmd, cwd=self.build_temp) - except subprocess.CalledProcessError: - # Don't obscure the error with a setuptools backtrace. - sys.exit(1) - - -if __name__ == "__main__": - cwd = os.path.dirname(os.path.abspath(__file__)) - version = open("version.txt", "r").read().strip() - sha = "Unknown" - - try: - sha = ( - subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=cwd) - .decode("ascii") - .strip() - ) - except subprocess.CalledProcessError: - pass - - if sha != "Unknown" and not os.getenv("NLE_RELEASE_BUILD"): - version += "+" + sha[:7] - package_name = setuptools.find_packages()[0] - print("Building wheel {}-{}".format(package_name, version)) - - version_path = os.path.join(cwd, "nle", "version.py") - with open(version_path, "w") as f: - f.write("__version__ = '{}'\n".format(version)) - f.write("git_version = {}\n".format(repr(sha))) - - setuptools.setup( - version=version, - ext_modules=[setuptools.Extension("nle", sources=[])], - cmdclass={"build_ext": CMakeBuild}, - classifiers=[ - "License :: OSI Approved :: Nethack General Public License", - "Development Status :: 5 - Production/Stable", - "Operating System :: POSIX :: Linux", - "Operating System :: MacOS", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", - "Programming Language :: C", - "Programming Language :: C++", - "Topic :: Scientific/Engineering :: Artificial Intelligence", - "Topic :: Games/Entertainment", - ], - zip_safe=False, - ) diff --git a/version.txt b/version.txt deleted file mode 100644 index 26aaba0e8..000000000 --- a/version.txt +++ /dev/null @@ -1 +0,0 @@ -1.2.0