From ef157eeaf4ade1046ee993b77e7c07ae75d7444e Mon Sep 17 00:00:00 2001 From: scivision Date: Fri, 2 Aug 2024 15:56:16 -0400 Subject: [PATCH] move MPI build scripts into here --- build_nano.cmake | 4 +- download_clang.cmake | 2 +- scripts/build_mpich.cmake | 10 +++ scripts/build_ninja/CMakeLists.txt | 4 +- scripts/build_openmpi.cmake | 35 ++++++++ scripts/install_ninja.cmake | 4 +- scripts/mpi/CMakeLists.txt | 136 +++++++++++++++++++++++++++++ scripts/mpi/README.md | 50 +++++++++++ scripts/mpi/test/CMakeLists.txt | 64 ++++++++++++++ scripts/mpi/test/mpi3.f90 | 40 +++++++++ scripts/versions.json | 4 +- 11 files changed, 345 insertions(+), 8 deletions(-) create mode 100644 scripts/build_mpich.cmake create mode 100644 scripts/build_openmpi.cmake create mode 100644 scripts/mpi/CMakeLists.txt create mode 100644 scripts/mpi/README.md create mode 100644 scripts/mpi/test/CMakeLists.txt create mode 100644 scripts/mpi/test/mpi3.f90 diff --git a/build_nano.cmake b/build_nano.cmake index f95cada..30b2cf2 100644 --- a/build_nano.cmake +++ b/build_nano.cmake @@ -1,7 +1,7 @@ # download, build, install nano text editor # requires Autotools and GNU Make -cmake_minimum_required(VERSION 3.19) +cmake_minimum_required(VERSION 3.21) set(CMAKE_EXECUTE_PROCESS_COMMAND_ECHO STDOUT) @@ -10,7 +10,7 @@ string(JSON version GET ${_j} "nano") set(stem nano-${version}) set(prefix "~/${stem}") -get_filename_component(prefix ${prefix} ABSOLUTE) +file(REAL_PATH ${prefix} prefix EXPAND_TILDE) option(CMAKE_TLS_VERIFY "verify certificates" true) diff --git a/download_clang.cmake b/download_clang.cmake index f21c5e3..52f19cb 100644 --- a/download_clang.cmake +++ b/download_clang.cmake @@ -17,7 +17,7 @@ if(NOT prefix) set(prefix ~/clang-${version}) endif() -get_filename_component(prefix ${prefix} ABSOLUTE) +file(REAL_PATH ${prefix} prefix EXPAND_TILDE) if(NOT DEFINED n) if(WIN32) diff --git a/scripts/build_mpich.cmake b/scripts/build_mpich.cmake new file mode 100644 index 0000000..e963a77 --- /dev/null +++ b/scripts/build_mpich.cmake @@ -0,0 +1,10 @@ +# USAGE: +# cmake -Dprefix=~/mpi -P build_mpich.cmake +cmake_minimum_required(VERSION 3.20) + +execute_process(COMMAND + ${CMAKE_COMMAND} -Dargs=-Dmpich:BOOL=true + -P ${CMAKE_CURRENT_LIST_DIR}/build_openmpi.cmake +) + +# NOTE: to pass-through arguments, don't quote them diff --git a/scripts/build_ninja/CMakeLists.txt b/scripts/build_ninja/CMakeLists.txt index 3f70ac2..2d93b70 100644 --- a/scripts/build_ninja/CMakeLists.txt +++ b/scripts/build_ninja/CMakeLists.txt @@ -1,6 +1,6 @@ # build and install a recent Ninja version -cmake_minimum_required(VERSION 3.19) +cmake_minimum_required(VERSION 3.21) project(ninja LANGUAGES C CXX) @@ -14,7 +14,7 @@ if(NOT version) endif() if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) - get_filename_component(p "~/ninja-${version}" ABSOLUTE) + file(REAL_PATH "~/ninja-${version}" p EXPAND_TILDE) set_property(CACHE CMAKE_INSTALL_PREFIX PROPERTY VALUE "${p}") endif() diff --git a/scripts/build_openmpi.cmake b/scripts/build_openmpi.cmake new file mode 100644 index 0000000..c8a6950 --- /dev/null +++ b/scripts/build_openmpi.cmake @@ -0,0 +1,35 @@ +# USAGE: +# cmake -Dprefix=~/mpi -P build_openmpi.cmake +cmake_minimum_required(VERSION 3.20) + +if(prefix) + list(APPEND args -DCMAKE_INSTALL_PREFIX:PATH=${prefix}) +endif() + +if(NOT bindir) + execute_process(COMMAND mktemp -d + OUTPUT_VARIABLE bindir + OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE ret + ) + if(NOT ret EQUAL 0) + string(RANDOM LENGTH 6 r) + set(bindir /tmp/build_${r}) + endif() +endif() + +execute_process(COMMAND ${CMAKE_COMMAND} + ${args} + -B${bindir} + -S${CMAKE_CURRENT_LIST_DIR}/mpi +COMMAND_ERROR_IS_FATAL ANY +) + +message(STATUS "MPI build in ${bindir}") + + +execute_process(COMMAND ${CMAKE_COMMAND} --build ${bindir} +COMMAND_ERROR_IS_FATAL ANY +) + +message(STATUS "MPI install complete.") diff --git a/scripts/install_ninja.cmake b/scripts/install_ninja.cmake index 9a46b18..b3d4690 100644 --- a/scripts/install_ninja.cmake +++ b/scripts/install_ninja.cmake @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.19...3.30) +cmake_minimum_required(VERSION 3.21...3.30) include(FetchContent) @@ -14,7 +14,7 @@ endif() if(NOT prefix) set(prefix ~/ninja-${version}) endif() -get_filename_component(prefix ${prefix} ABSOLUTE) +file(REAL_PATH ${prefix} prefix EXPAND_TILDE) file(MAKE_DIRECTORY ${prefix}) diff --git a/scripts/mpi/CMakeLists.txt b/scripts/mpi/CMakeLists.txt new file mode 100644 index 0000000..e70ceba --- /dev/null +++ b/scripts/mpi/CMakeLists.txt @@ -0,0 +1,136 @@ +cmake_minimum_required(VERSION 3.21...3.28) + +if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) + message(FATAL_ERROR "Use out of source build like + cmake -Bbuild") +endif() + +project(BuildMPI LANGUAGES C CXX Fortran) + +include(CheckIncludeFile) +include(ExternalProject) + +option(mpich "Build MPICH instead of OpenMPI") + +option(CMAKE_TLS_VERIFY "Verify TLS certs" on) + +list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../cmake) + +file(GENERATE OUTPUT .gitignore CONTENT "*") + +# --- check for updated external projects when "false" +set_property(DIRECTORY PROPERTY EP_UPDATE_DISCONNECTED true) + + +find_package(Autotools REQUIRED) + +# --- determine number of cores for parallel build +# MPI builds spawn too many threads with bare "make -j" giving build crashes like +# libtool: fork: Resource temporarily unavailable +# clang: error: unable to execute command: posix_spawn failed: Resource temporarily unavailable +if(DEFINED ENV{CMAKE_BUILD_PARALLEL_LEVEL}) + set(Ncpu $ENV{CMAKE_BUILD_PARALLEL_LEVEL}) +else() + cmake_host_system_information(RESULT Ncpu QUERY NUMBER_OF_PHYSICAL_CORES) +endif() + +set(mpi_flags --prefix=${CMAKE_INSTALL_PREFIX}) + +# to avoid ABI problems and confusing build or runtime errors, mandate that C and C++ are same compiler vendor +if(NOT CMAKE_C_COMPILER_ID STREQUAL CMAKE_CXX_COMPILER_ID) + message(FATAL_ERROR "C compiler ${CMAKE_C_COMPILER_ID} and C++ compiler ${CMAKE_CXX_COMPILER_ID} must be the same to avoid ABI build/runtime errors. +Set environment variables CC and CXX to the same compiler by prepending the command line: + CC=gcc-13 CXX=g++-13") +endiF() + +# help OpenMPI/MPICH by hinting compilers, particularly on MacOS +# assume whatever the C compiler is, the C++ and Fortran compilers are the same vendor +if(CMAKE_C_COMPILER_ID MATCHES "GNU|$Intel") + # OpenMPI / MPICH needs full path to oneAPI compilers + list(APPEND mpi_flags CC=${CMAKE_C_COMPILER} CXX=${CMAKE_CXX_COMPILER} FC=${CMAKE_Fortran_COMPILER}) +elseif(CMAKE_C_COMPILER_ID MATCHES "Clang$") + # until Flang is ready, use Gfortran + list(APPEND mpi_flags CC=clang CXX=clang++ FC=gfortran) +endif() + +# OpenMPI/MPICH have trouble finding -lm on MacOS especially +find_library(math NAMES m REQUIRED) +cmake_path(GET math PARENT_PATH math_LIBDIR) + +set(mpi_ldflags "LDFLAGS=${CMAKE_LIBRARY_PATH_FLAG}${math_LIBDIR}") + +# --- read JSON with URLs for each library +file(READ ${CMAKE_CURRENT_SOURCE_DIR}/../versions.json json_meta) + +if(mpich) + check_include_file("ISO_Fortran_binding.h" HAVE_ISO_FORTRAN_BINDING_H) + if(NOT HAVE_ISO_FORTRAN_BINDING_H) + message(FATAL_ERROR "ISO_Fortran_binding.h not found with ${CMAKE_C_COMPILER_ID} ${CMAKE_C_COMPILER_VERSION} ${CMAKE_C_COMPILER} +We suggest GCC compiler. Specify GCC by prepending \"CC=gcc-13\" or similar to your command. +Setting \"CC=gcc\" on macOS defaults to Clang, which will fail. MPICH requires ISO_Fortran_binding.h") + endif() + + string(JSON mpi_url GET ${json_meta} "mpich") + + list(APPEND mpi_flags --with-device=ch3) + # MPICH 4.2.0 etc. fails to build without this option +else() + string(JSON mpi_url GET ${json_meta} "openmpi") + + # this --with-hwloc breaks OpenMPI 4.1 and 5.0 at least + # list(APPEND mpi_flags --with-hwloc=internal) + # internal HWLOC avoids error in MPI: + # ibopen-pal.a(topology-linux.o): multiple definition of `hwloc_linux_component' + #--with-hwloc-libdir=${CMAKE_INSTALL_PREFIX}/lib + + list(APPEND mpi_flags --disable-sphinx) + # avoids errors with docs build that aren't used anyway + + find_package(ZLIB) + if(ZLIB_FOUND) + cmake_path(GET ZLIB_LIBRARIES PARENT_PATH ZLIB_LIBDIR) + list(APPEND mpi_flags --with-zlib-libdir=${ZLIB_LIBDIR}) + endif() +endif() + +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + file(REAL_PATH "~" home EXPAND_TILDE) + cmake_path(GET mpi_url STEM n) + set_property(CACHE CMAKE_INSTALL_PREFIX PROPERTY VALUE "${home}/${n}") +endif() + + +# Downloading URL instead of Git avoids very long "autogen" step +ExternalProject_Add(MPI +URL ${mpi_url} +CONFIGURE_COMMAND /configure ${mpi_flags} ${mpi_ldflags} +BUILD_COMMAND ${MAKE_EXECUTABLE} -j${Ncpu} +INSTALL_COMMAND ${MAKE_EXECUTABLE} install +TEST_COMMAND "" +USES_TERMINAL_DOWNLOAD true +USES_TERMINAL_UPDATE true +USES_TERMINAL_CONFIGURE true +USES_TERMINAL_BUILD true +USES_TERMINAL_INSTALL true +USES_TERMINAL_TEST true +CONFIGURE_HANDLED_BY_BUILD ON +) + + +if(mpich) + message(STATUS "MPICH: ${mpi_url} => ${CMAKE_INSTALL_PREFIX}") +else() + message(STATUS "OpenMPI: ${mpi_url} => ${CMAKE_INSTALL_PREFIX}") +endif() +message(STATUS "MPI flags: ${mpi_flags}") +message(STATUS "MPI LDFLAGS: ${mpi_ldflags}") + + +# --- check that MPI-3 Fortran is working +ExternalProject_add(MPItest +SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/test +CMAKE_ARGS -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} -DMPI_ROOT:PATH=${CMAKE_INSTALL_PREFIX} +INSTALL_COMMAND "" +CONFIGURE_HANDLED_BY_BUILD on +DEPENDS MPI +) diff --git a/scripts/mpi/README.md b/scripts/mpi/README.md new file mode 100644 index 0000000..9b1e030 --- /dev/null +++ b/scripts/mpi/README.md @@ -0,0 +1,50 @@ +# Build MPI + +This mini-project is to build MPI-3 library OpenMPI for those systems not having MPI for the desired compiler. + +```sh +cmake -Bbuild --install-prefix $HOME/my_mpi + +cmake --build build +``` + +## MPICH + +```sh +cmake -Dmpich=yes +``` + +builds MPICH instead of the default OpenMPI. + +Note that MPICH does [not yet work with Clang](https://releases.llvm.org/11.0.0/tools/flang/docs/RuntimeDescriptor.html#interoperability-requirements) +This is particularly relevant for macOS, where Clang is the default compiler. + +A successful MPICH configure step ends like: + +``` +config.status: executing gen_binding_f90 commands + [ /scripts/mpi/build/MPI-prefix/src/MPI/maint/gen_binding_f90.py -f-logical-size=4 -ignore-tkr=gcc ] + --> [src/binding/fortran/use_mpi/mpi_base.f90] + --> [src/binding/fortran/use_mpi/mpi_constants.f90] + --> [src/binding/fortran/use_mpi/mpi_sizeofs.f90] +config.status: executing gen_binding_f08 commands + [ /scripts/mpi/build/MPI-prefix/src/MPI/maint/gen_binding_f08.py -fint-size=4 -aint-size=8 -count-size=8 -cint-size=4 ] + --> [src/binding/fortran/use_mpi_f08/wrappers_c/f08_cdesc.c] + --> [src/binding/fortran/use_mpi_f08/wrappers_c/cdesc_proto.h] + --> [src/binding/fortran/use_mpi_f08/wrappers_f/f08ts.f90] + --> [src/binding/fortran/use_mpi_f08/wrappers_f/pf08ts.f90] + --> [src/binding/fortran/use_mpi_f08/mpi_c_interface_cdesc.f90] + --> [src/binding/fortran/use_mpi_f08/mpi_c_interface_nobuf.f90] + --> [src/binding/fortran/use_mpi_f08/mpi_f08.f90] + --> [src/binding/fortran/use_mpi_f08/pmpi_f08.f90] + --> [src/binding/fortran/use_mpi_f08/mpi_f08_types.f90] +***************************************************** +*** +*** device configuration: ch3:nemesis +*** nemesis networks: tcp +*** +***************************************************** +Configuration completed. +``` + +and the internal MPICH header confdef.h must have `#define HAVE_F08_BINDING 1` diff --git a/scripts/mpi/test/CMakeLists.txt b/scripts/mpi/test/CMakeLists.txt new file mode 100644 index 0000000..c85e248 --- /dev/null +++ b/scripts/mpi/test/CMakeLists.txt @@ -0,0 +1,64 @@ +cmake_minimum_required(VERSION 3.20) + +project(MPItest LANGUAGES C Fortran) + +enable_testing() + +include(CheckSourceCompiles) + +if(NOT DEFINED MPI_ROOT AND DEFINED ENV{MPI_ROOT}) + set(MPI_ROOT $ENV{MPI_ROOT}) +endif() +if(MPI_ROOT) + message(STATUS "Using MPI_ROOT=${MPI_ROOT}") +else() + message(STATUS "MPI_ROOT not set, using default MPI search paths") +endif() + +set(MPI_DETERMINE_LIBRARY_VERSION true) + +find_package(MPI COMPONENTS C Fortran REQUIRED) + +message(STATUS "${MPI_Fortran_LIBRARY_VERSION_STRING}") +message(STATUS "MPI libs: ${MPI_Fortran_LIBRARIES}") +message(STATUS "MPI include: ${MPI_Fortran_INCLUDE_DIRS}") +message(STATUS "MPI compile flags: ${MPI_Fortran_COMPILER_FLAGS}") +message(STATUS "MPI link flags: ${MPI_Fortran_LINK_FLAGS}") + +if(MPI_Fortran_HAVE_F08_MODULE) + return() +endif() + +set(CMAKE_REQUIRED_LIBRARIES MPI::MPI_Fortran) + +# sometimes factory FindMPI.cmake doesn't define this +message(CHECK_START "Checking for Fortran MPI-3 binding") +check_source_compiles(Fortran +[=[ +program test +use mpi_f08, only : mpi_comm_rank, mpi_real, mpi_comm_world, mpi_init, mpi_finalize +implicit none +call mpi_init +call mpi_finalize +end program +]=] +MPI_Fortran_HAVE_F08_MODULE +) + +if(MPI_Fortran_HAVE_F08_MODULE) + message(CHECK_PASS "yes") +else() + message(CHECK_FAIL "no") + message(WARNING "MPI-3 Fortran module mpi_f08 not found, builds may fail.") +endif() + +add_executable(test_mpi3 mpi3.f90) +target_link_libraries(test_mpi3 PRIVATE MPI::MPI_Fortran) + +add_test(NAME test_mpi3 COMMAND test_mpi3) + +if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.29) + set_property(TARGET test_mpi3 PROPERTY TEST_LAUNCHER ${MPIEXEC_EXECUTABLE} ${MPIEXEC_NUMPROC_FLAG} ${MPIEXEC_MAX_NUMPROCS}) +else() + set_property(TARGET test_mpi3 PROPERTY CROSSCOMPILING_EMULATOR ${MPIEXEC_EXECUTABLE} ${MPIEXEC_NUMPROC_FLAG} ${MPIEXEC_MAX_NUMPROCS}) +endif() diff --git a/scripts/mpi/test/mpi3.f90 b/scripts/mpi/test/mpi3.f90 new file mode 100644 index 0000000..f89d909 --- /dev/null +++ b/scripts/mpi/test/mpi3.f90 @@ -0,0 +1,40 @@ +program hw_mpi +!! Each process prints out a "Hello, world!" message with a process ID +!! Original Author: John Burkardt +!! Modified: Michael Hirsch, Ph.D. + +use mpi_f08, only : mpi_init, mpi_comm_size, mpi_comm_world, mpi_wtime, mpi_comm_rank, mpi_finalize +use, intrinsic:: iso_fortran_env, only: dp=>real64, compiler_version + +implicit none + +integer :: id, Nproc +real(dp) :: wtime + +!> Initialize MPI. +call MPI_Init() + +!> Get the number of processes. +call MPI_Comm_size(MPI_COMM_WORLD, Nproc) + +!> Get the individual process ID. +call MPI_Comm_rank(MPI_COMM_WORLD, id) + +!> Print a message. +if (id == 0) then + print *,compiler_version() + wtime = MPI_Wtime() + print *, 'number of processes: ', Nproc +end if + +print *, 'Process ', id + +if (id == 0) then + wtime = MPI_Wtime() - wtime + print *, 'Elapsed wall clock time = ', wtime, ' seconds.' +end if + +!> Shut down MPI. +call MPI_Finalize() + +end program diff --git a/scripts/versions.json b/scripts/versions.json index 1a55110..5fc1178 100644 --- a/scripts/versions.json +++ b/scripts/versions.json @@ -29,5 +29,7 @@ }, "ninja": "1.12.1", "nano": "8.1", - "zstd": "1.5.6" + "zstd": "1.5.6", + "openmpi": "https://download.open-mpi.org/release/open-mpi/v5.0/openmpi-5.0.5.tar.bz2", + "mpich": "https://www.mpich.org/static/downloads/4.2.2/mpich-4.2.2.tar.gz" }