diff --git a/.github/scripts/install_cuda_el8.sh b/.github/scripts/install_cuda_el8.sh new file mode 100755 index 000000000..862cc5f8f --- /dev/null +++ b/.github/scripts/install_cuda_el8.sh @@ -0,0 +1,173 @@ +# Install CUDA on Alma8/manylinux_2_28. + +## ------------------- +## Constants +## ------------------- + +# dnf install cuda-nvrtc-devel-11-4 cuda-compiler-11-4 cuda-cudart-devel-11-4 cuda-nvcc-11-4 cuda-nvrtc-11-4 cuda-nvtx-11-4 libcurand-devel-11-4 + +# List of sub-packages to install. +# @todo - pass this in from outside the script? +# @todo - check the specified subpackages exist via apt pre-install? apt-rdepends cuda-9-0 | grep "^cuda-"? + +# Ideally choose from the list of meta-packages to minimise variance between cuda versions (although it does change too) +CUDA_PACKAGES_IN=( + "cuda-compiler" + "cuda-cudart-devel" # libcudart.so + "cuda-driver-devel" # libcuda.so + "cuda-nvtx" + "cuda-nvrtc-devel" + "libcurand-devel" # 11-0+ +) + +## ------------------- +## Bash functions +## ------------------- +# returns 0 (true) if a >= b +function version_ge() { + [ "$#" != "2" ] && echo "${FUNCNAME[0]} requires exactly 2 arguments." && exit 1 + [ "$(printf '%s\n' "$@" | sort -V | head -n 1)" == "$2" ] +} +# returns 0 (true) if a > b +function version_gt() { + [ "$#" != "2" ] && echo "${FUNCNAME[0]} requires exactly 2 arguments." && exit 1 + [ "$1" = "$2" ] && return 1 || version_ge $1 $2 +} +# returns 0 (true) if a <= b +function version_le() { + [ "$#" != "2" ] && echo "${FUNCNAME[0]} requires exactly 2 arguments." && exit 1 + [ "$(printf '%s\n' "$@" | sort -V | head -n 1)" == "$1" ] +} +# returns 0 (true) if a < b +function version_lt() { + [ "$#" != "2" ] && echo "${FUNCNAME[0]} requires exactly 2 arguments." && exit 1 + [ "$1" = "$2" ] && return 1 || version_le $1 $2 +} + + +## ------------------- +## Select CUDA version +## ------------------- + +# Get the cuda version from the environment as $cuda. +CUDA_VERSION_MAJOR_MINOR=${cuda} + +# Split the version. +# We (might/probably) don't know PATCH at this point - it depends which version gets installed. +CUDA_MAJOR=$(echo "${CUDA_VERSION_MAJOR_MINOR}" | cut -d. -f1) +CUDA_MINOR=$(echo "${CUDA_VERSION_MAJOR_MINOR}" | cut -d. -f2) +CUDA_PATCH=$(echo "${CUDA_VERSION_MAJOR_MINOR}" | cut -d. -f3) +# query rpm to find the major enterprise linux release +EL_MAJOR=$(rpm -E %{rhel}) + +echo "CUDA_MAJOR: ${CUDA_MAJOR}" +echo "CUDA_MINOR: ${CUDA_MINOR}" +echo "CUDA_PATCH: ${CUDA_PATCH}" +echo "EL_MAJOR: ${EL_MAJOR}" + +# If we don't know the CUDA_MAJOR or MINOR, error. +if [ -z "${CUDA_MAJOR}" ] ; then + echo "Error: Unknown CUDA Major version. Aborting." + exit 1 +fi +if [ -z "${CUDA_MINOR}" ] ; then + echo "Error: Unknown CUDA Minor version. Aborting." + exit 1 +fi +# If we don't know the Ubuntu version, error. +if [ -z ${EL_MAJOR} ]; then + echo "Error: Unknown EL version. Aborting." + exit 1 +fi + +## ------------------------------- +## Select CUDA packages to install +## ------------------------------- +CUDA_PACKAGES="" +for package in "${CUDA_PACKAGES_IN[@]}" +do : + # CUDA < 11, lib* packages were actually cuda-cu* (generally, this might be greedy.) + if [[ ${package} == libcu* ]] && version_lt "$CUDA_VERSION_MAJOR_MINOR" "11.0" ; then + package="${package/libcu/cuda-cu}" + fi + # CUDA < 11, -devel- packages were actually -dev + if [[ ${package} == *devel* ]] && version_lt "$CUDA_VERSION_MAJOR_MINOR" "11.0" ; then + package="${package//devel/dev}" + fi + # Build the full package name and append to the string. + CUDA_PACKAGES+=" ${package}-${CUDA_MAJOR}-${CUDA_MINOR}" +done +echo "CUDA_PACKAGES ${CUDA_PACKAGES}" + +## ----------------- +## Prepare to install +## ----------------- + +CPU_ARCH="x86_64" +# Nvidia don't provide an explicit alma repo. 11.2's closest is RHEL. +# 12.4 includes rocky8/9, rhel7/8/9, cent7, so RHEL is the closes that should hopefully be fine, otherwise will have to switch to the much slower runfile installer. +DNF_REPO_URI="https://developer.download.nvidia.com/compute/cuda/repos/rhel${EL_MAJOR}/${CPU_ARCH}/cuda-rhel${EL_MAJOR}.repo" + +echo "DNF_REPO_URI ${DNF_REPO_URI}" + +## ----------------- +## Check for root/sudo +## ----------------- + +# Detect if the script is being run as root, storing true/false in is_root. +is_root=false +if (( $EUID == 0)); then + is_root=true +fi +# Find if sudo is available +has_sudo=false +if command -v sudo &> /dev/null ; then + has_sudo=true +fi +# Decide if we can proceed or not (root or sudo is required) and if so store whether sudo should be used or not. +if [ "$is_root" = false ] && [ "$has_sudo" = false ]; then + echo "Root or sudo is required. Aborting." + exit 1 +elif [ "$is_root" = false ] ; then + USE_SUDO=sudo +else + USE_SUDO= +fi + +## ----------------- +## Install +## ----------------- +echo "Adding CUDA Repository" +$USE_SUDO dnf config-manager --add-repo ${DNF_REPO_URI} +$USE_SUDO dnf clean all + +echo "Installing CUDA packages ${CUDA_PACKAGES}" +$USE_SUDO dnf -y install ${CUDA_PACKAGES} + +if [[ $? -ne 0 ]]; then + echo "CUDA Installation Error." + exit 1 +fi + +## ----------------- +## Set environment vars / vars to be propagated +## ----------------- + +CUDA_PATH=/usr/local/cuda-${CUDA_MAJOR}.${CUDA_MINOR} +echo "CUDA_PATH=${CUDA_PATH}" +export CUDA_PATH=${CUDA_PATH} +export PATH="$CUDA_PATH/bin:$PATH" +export LD_LIBRARY_PATH="$CUDA_PATH/lib:$LD_LIBRARY_PATH" +export LD_LIBRARY_PATH="$CUDA_PATH/lib64:$LD_LIBRARY_PATH" +# Check nvcc is now available. +nvcc -V + +# If executed on github actions, make the appropriate echo statements to update the environment +if [[ $GITHUB_ACTIONS ]]; then + # Set paths for subsequent steps, using ${CUDA_PATH} + echo "Adding CUDA to CUDA_PATH, PATH and LD_LIBRARY_PATH" + echo "CUDA_PATH=${CUDA_PATH}" >> $GITHUB_ENV + echo "${CUDA_PATH}/bin" >> $GITHUB_PATH + echo "LD_LIBRARY_PATH=${CUDA_PATH}/lib:${LD_LIBRARY_PATH}" >> $GITHUB_ENV + echo "LD_LIBRARY_PATH=${CUDA_PATH}/lib64:${LD_LIBRARY_PATH}" >> $GITHUB_ENV +fi \ No newline at end of file diff --git a/.github/workflows/Manylinux_2_28.yml b/.github/workflows/Manylinux_2_28.yml new file mode 100644 index 000000000..f9c121cca --- /dev/null +++ b/.github/workflows/Manylinux_2_28.yml @@ -0,0 +1,182 @@ +# Build manylinux wheels, and upload them to the action for testing within a short time frame +name: Manylinux_2_28 + +# Run on branch push events (i.e. not tag pushes) and on pull requests +on: + # Branch pushes that do not only modify other workflow files + push: + branches: + - '**' + paths: + - "**" + - "!.github/**" + - ".github/scripts/install_cuda_el8.sh" + - ".github/workflows/Manylinux_2_28.yml" + # Disabled for now. See https://github.com/FLAMEGPU/FLAMEGPU2/pull/644 + # pull_request: + # Allow manual invocation. + workflow_dispatch: + +defaults: + run: + shell: bash + +# A single job, which builds manylinux_2_28 wheels, which ships with GCC 12. +jobs: + build: + runs-on: ${{ matrix.cudacxx.os }} + # Run steps inside a manylinux container. + container: quay.io/pypa/manylinux_2_28_x86_64 + strategy: + fail-fast: false + # Multiplicative build matrix + # optional exclude: can be partial, include: must be specific + matrix: + cudacxx: + - cuda: "12.0" + cuda_arch: "50" + hostcxx: gcc-toolset-12 + os: ubuntu-22.04 + - cuda: "11.2" + cuda_arch: "35" + hostcxx: gcc-toolset-9 + os: ubuntu-22.04 + python: + - "3.12" + config: + - name: "Release" + config: "Release" + SEATBELTS: "ON" + VISUALISATION: + - "ON" + - "OFF" + + # Name the job based on matrix/env options + name: "build (${{ matrix.cudacxx.cuda }}, ${{matrix.python}}, ${{ matrix.VISUALISATION }}, ${{ matrix.config.name }}, ${{ matrix.cudacxx.os }})" + + env: + # Control if the wheel should be repaired. This will fail until .so's are addressed + AUDITWHEEL_REPAIR: "OFF" + MANYLINUX: "manylinux_2_28" + ARCH: "x86_64" + # Control if static GLEW should be built and used or not. + USE_STATIC_GLEW: "ON" + # Compute the wheelhouse name which should be unique within the matrix. This must be unique per build matrix/job combination + ARTIFACT_NAME: wheel-manylinux_2_28-${{ matrix.cudacxx.cuda }}-${{matrix.python}}-${{ matrix.VISUALISATION }}-${{ matrix.config.name }}-${{ matrix.cudacxx.os }} + # Define constants + BUILD_DIR: "build" + FLAMEGPU_BUILD_TESTS: "OFF" + # Conditional based on matrix via awkward almost ternary + FLAMEGPU_BUILD_PYTHON: ${{ fromJSON('{true:"ON",false:"OFF"}')[matrix.python != ''] }} + # Port matrix options to environment, for more portability. + CUDA: ${{ matrix.cudacxx.cuda }} + CUDA_ARCH: ${{ matrix.cudacxx.cuda_arch }} + HOSTCXX: ${{ matrix.cudacxx.hostcxx }} + OS: ${{ matrix.cudacxx.os }} + CONFIG: ${{ matrix.config.config }} + FLAMEGPU_SEATBELTS: ${{ matrix.config.SEATBELTS }} + PYTHON: ${{ matrix.python}} + VISUALISATION: ${{ matrix.VISUALISATION }} + # Short term fix to use node16 not node20 for actions. This will stop working eventually, forcing our hand in dropping manylinux2014 support. + ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true + + steps: + - uses: actions/checkout@v3 + + # Downgrade the gcc-toolset in the image based on the build matrix + - name: Install RHEL gcc-toolset (EL 8) + if: ${{ startsWith(env.HOSTCXX, 'gcc-toolset-') }} + run: | + # Install gcc-toolset-X + yum install -y ${{ env.HOSTCXX }} + # Enable the toolset via source not scl enable which doesn't get on with multi-step GHA + source /opt/rh/${{ env.HOSTCXX }}/enable + # Export the new environment / compilers for subsequent steps. + echo "PATH=${PATH}" >> $GITHUB_ENV + echo "CC=$(which gcc)" >> $GITHUB_ENV + echo "CXX=$(which g++)" >> $GITHUB_ENV + echo "CUDAHOSTCXX=$(which g++)" >> $GITHUB_ENV + + - name: Install CUDA (EL 8) + if: ${{ env.CUDA != '' }} + env: + cuda: ${{ env.CUDA }} + run: .github/scripts/install_cuda_el8.sh + + - name: Install Visualisation Dependencies (EL 8) + if: ${{ env.VISUALISATION == 'ON' }} + run: | + yum install -y glew-devel fontconfig-devel SDL2-devel freetype-devel + # Build/Install DevIL from source. + yum install -y freeglut-devel + git clone --depth 1 https://github.com/DentonW/DevIL.git + cd DevIL/DevIL + mkdir -p build + cd build + cmake .. -DCMAKE_BUILD_TYPE=${{ env.CONFIG }} -Wno-error=dev + make -j `nproc` + make install + + - name: Build and install GLEW including static GLEW + if: ${{ env.VISUALISATION == 'ON' && env.USE_STATIC_GLEW == 'ON' }} + env: + GLEW_VERSION: "2.2.0" + run: | + yum install -y wget + wget https://github.com/nigels-com/glew/releases/download/glew-${{ env.GLEW_VERSION }}/glew-${{ env.GLEW_VERSION }}.tgz + tar -zxf glew-${{ env.GLEW_VERSION }}.tgz + cd glew-${{ env.GLEW_VERSION }} + make + make install + + - name: Add custom problem matchers for annotations + run: echo "::add-matcher::.github/problem-matchers.json" + + # This patches a bug where ManyLinux doesn't generate buildnumber as git dir is owned by diff user + - name: Enable git safe-directory + run: git config --global --add safe.directory $GITHUB_WORKSPACE + + - name: Configure cmake + run: > + cmake . -B "${{ env.BUILD_DIR }}" + -DCMAKE_BUILD_TYPE="${{ env.CONFIG }}" + -Werror=dev + -DCMAKE_WARN_DEPRECATED="OFF" + -DFLAMEGPU_WARNINGS_AS_ERRORS="ON" + -DCMAKE_CUDA_ARCHITECTURES="${{ env.CUDA_ARCH }}" + -DFLAMEGPU_BUILD_TESTS="${{ env.FLAMEGPU_BUILD_TESTS }}" + -DFLAMEGPU_BUILD_PYTHON="${{ env.FLAMEGPU_BUILD_PYTHON }}" + -DPYTHON3_EXACT_VERSION="${{ env.PYTHON }}" + -DFLAMEGPU_VISUALISATION="${{ env.VISUALISATION }}" + -DFLAMEGPU_ENABLE_NVTX="ON" + -DGLEW_USE_STATIC_LIBS="${{ env.USE_STATIC_GLEW }}" + -DOpenGL_GL_PREFERENCE:STRING=LEGACY + -DFLAMEGPU_BUILD_PYTHON_PATCHELF=ON + + - name: Build python wheel + if: ${{ env.FLAMEGPU_BUILD_PYTHON == 'ON' }} + working-directory: ${{ env.BUILD_DIR }} + run: cmake --build . --target pyflamegpu --verbose -j `nproc` + + # Run audithweel show for information, but do not repair. + - name: Run auditwheel show + working-directory: ${{ env.BUILD_DIR }} + run: auditwheel show lib/${{ env.CONFIG }}/python/dist/*whl + + # Ideally we should use auditwheel repair to check/enforce conformity + # But we cannot due to cuda shared object (libcuda.so.1) dependencies which we cannot/shouldnot/wil not package into the wheel. + - name: Run auditwheel repair + if: ${{ env.AUDITWHEEL_REPAIR == 'ON' }} + working-directory: ${{ env.BUILD_DIR }} + run: auditwheel repair --plat ${{ env.MANYLINUX }}_${{ env.ARCH }} lib/${{ env.CONFIG }}/python/dist/*whl -w lib/${{ env.CONFIG }}/python/dist + + # Upload wheel artifacts to the job on GHA, with a short retention + # Use a unique name per job matrix run, to avoid a risk of corruption according to the docs (although it should work with unique filenames) + - name: Upload Wheel Artifacts + if: ${{env.FLAMEGPU_BUILD_PYTHON == 'ON' }} + uses: actions/upload-artifact@v3 + with: + name: ${{ env.ARTIFACT_NAME }} + path: ${{ env.BUILD_DIR }}/lib/${{ env.CONFIG }}/python/dist/*.whl + if-no-files-found: error + retention-days: 5