diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 4017180..b776196 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -7,7 +7,7 @@ jobs: strategy: fail-fast: false matrix: - build: [fpm, meson] + build: [cmake, fpm, meson] os: [ubuntu-latest, macos-latest, windows-latest] gcc: [10] # Version of GFortran we want to use. build-type: [debug] @@ -25,6 +25,7 @@ jobs: CC: gcc FPM_FC: gfortran FPM_CC: gcc + BUILD_DIR: ${{ matrix.build == 'cmake' && 'build' || '.' }} steps: - name: Checkout code uses: actions/checkout@v2 @@ -66,11 +67,27 @@ jobs: - name: Install dependencies uses: mamba-org/provision-with-micromamba@main + if: ${{ matrix.build == 'fpm' || 'meson' }} with: environment-file: config/ci/${{ matrix.build }}-env.yaml extra-specs: | ${{ matrix.build-type == 'coverage' && 'gcovr' || '' }} + - name: Configure (cmake) + if: ${{ matrix.build == 'cmake' }} + run: >- + cmake -Wdev + -DCMAKE_INSTALL_PREFIX=$PWD/_dist + -S . -B ${{ env.BUILD_DIR }} + + - name: Compile (cmake) + if: ${{ matrix.build == 'cmake' }} + run: cmake --build ${{ env.BUILD_DIR }} --parallel + + - name: Run test (cmake) + if: ${{ matrix.build == 'cmake' }} + run: cmake --build ${{ env.BUILD_DIR }} --target test + - name: Compile (fpm) if: ${{ matrix.build == 'fpm' }} run: fpm build --profile release diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..5edcbe4 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,35 @@ +cmake_minimum_required(VERSION 3.14.0) + +# Include overwrites before setting up the project +set(CMAKE_USER_MAKE_RULES_OVERRIDE ${CMAKE_CURRENT_SOURCE_DIR}/config/DefaultFlags.cmake) + +project(minpack + LANGUAGES Fortran + VERSION "2.0.0" + DESCRIPTION "Minpack includes software for solving nonlinear equations and nonlinear least squares problems" +) + +include(CTest) + +# Follow GNU conventions for installation directories +include(GNUInstallDirs) + +# --- CMake specific configuration and package data export +add_subdirectory(config) + +# --- compiler feature checks +include(CheckFortranSourceCompiles) +include(CheckFortranSourceRuns) + +add_subdirectory(src) + +if(BUILD_TESTING) + enable_testing() + add_subdirectory(test) + add_subdirectory(examples) +endif() + +install(EXPORT ${PROJECT_NAME}-targets + NAMESPACE ${PROJECT_NAME}:: + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" +) diff --git a/README.md b/README.md index 8cd67ab..bec6994 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ Further updates are planned... To build this project from the source code in this repository you need to have a Fortran compiler supporting Fortran 2008 and one of the supported build systems: +- [cmake](https://cmake.org) version 3.14.0 or newer - [fpm](https://fpm.fortran-lang.org) version 0.3.0 or newer - [meson](https://mesonbuild.com) version 0.55 or newer, with a build-system backend, *i.e.* [ninja](https://ninja-build.org) version 1.7 or newer @@ -44,6 +45,21 @@ cd minpack ``` +#### Building with cmake + +Invoke cmake in the project root with + +``` +cmake -B build +cmake --build build +``` + +To run the testsuite use + +``` +cmake --build build --target test +``` + #### Building with fpm Invoke fpm in the project root with diff --git a/config/CMakeLists.txt b/config/CMakeLists.txt new file mode 100644 index 0000000..f215d39 --- /dev/null +++ b/config/CMakeLists.txt @@ -0,0 +1,53 @@ +# SPDX-Identifier: MIT + +if(NOT DEFINED CMAKE_INSTALL_MODULEDIR) + set( + CMAKE_INSTALL_MODULEDIR + "${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/${CMAKE_Fortran_COMPILER_ID}-${CMAKE_Fortran_COMPILER_VERSION}" + CACHE + STRING + "Directory in prefix to install generated module files" + ) +endif() + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") +set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}" PARENT_SCOPE) + +# Export a pkg-config file +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/template.pc" + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc" + @ONLY +) +install( + FILES + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig" +) + +# Export CMake package file +include(CMakePackageConfigHelpers) +configure_package_config_file( + "${CMAKE_CURRENT_SOURCE_DIR}/template.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config.cmake" + INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" +) +if(BUILD_SHARED_LIBS OR PROJECT_VERSION_MAJOR EQUAL 0) + # Due to the uncertain ABI compatibility of Fortran shared libraries + # limit compatibility for dynamic linking to same minor version. + set(COMPATIBILITY SameMinorVersion) +else() + # Require API compatibility via semantic versioning for static linking. + set(COMPATIBILITY SameMajorVersion) +endif() +write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake" + VERSION "${PROJECT_VERSION}" + COMPATIBILITY ${COMPATIBILITY} +) +install( + FILES + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" +) diff --git a/config/DefaultFlags.cmake b/config/DefaultFlags.cmake new file mode 100644 index 0000000..eafe7a4 --- /dev/null +++ b/config/DefaultFlags.cmake @@ -0,0 +1,50 @@ +if(CMAKE_Fortran_COMPILER_ID STREQUAL "GNU") + set( + CMAKE_Fortran_FLAGS_INIT + "-fimplicit-none" + "-ffree-line-length-132" + ) + set( + CMAKE_Fortran_FLAGS_RELEASE_INIT + ) + set( + CMAKE_Fortran_FLAGS_DEBUG_INIT + "-Wall" + "-Wextra" + "-Wimplicit-procedure" + "-std=f2018" + ) +elseif(CMAKE_Fortran_COMPILER_ID MATCHES "^Intel") + set( + CMAKE_Fortran_FLAGS_INIT + ) + set( + CMAKE_Fortran_FLAGS_RELEASE_INIT + ) + if(WIN32) + set( + CMAKE_Fortran_FLAGS_DEBUG_INIT + "/stand:f18" + "/warn:declarations,general,usage,interfaces,unused" + ) + else() + set( + CMAKE_Fortran_FLAGS_DEBUG_INIT + "-stand f18" + "-warn declarations,general,usage,interfaces,unused" + ) + endif() +else() + set( + CMAKE_Fortran_FLAGS_INIT + ) + set( + CMAKE_Fortran_FLAGS_RELEASE_INIT + ) + set( + CMAKE_Fortran_FLAGS_DEBUG_INIT + ) +endif() +string(REPLACE ";" " " CMAKE_Fortran_FLAGS_INIT "${CMAKE_Fortran_FLAGS_INIT}") +string(REPLACE ";" " " CMAKE_Fortran_FLAGS_RELEASE_INIT "${CMAKE_Fortran_FLAGS_RELEASE_INIT}") +string(REPLACE ";" " " CMAKE_Fortran_FLAGS_DEBUG_INIT "${CMAKE_Fortran_FLAGS_DEBUG_INIT}") diff --git a/config/template.cmake b/config/template.cmake new file mode 100644 index 0000000..4746fe4 --- /dev/null +++ b/config/template.cmake @@ -0,0 +1,5 @@ +@PACKAGE_INIT@ + +if(NOT TARGET "@PROJECT_NAME@::@PROJECT_NAME@") + include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@-targets.cmake") +endif() diff --git a/config/template.pc b/config/template.pc new file mode 100644 index 0000000..eecdda9 --- /dev/null +++ b/config/template.pc @@ -0,0 +1,10 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@ +includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ +moduledir=${prefix}/@CMAKE_INSTALL_MODULEDIR@ + +Name: @PROJECT_NAME@ +Description: @PROJECT_DESCRIPTION@ +Version: @PROJECT_VERSION@ +Libs: -L${libdir} -l@PROJECT_NAME@ +Cflags: -I${includedir} -I${moduledir} diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..8123001 --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,19 @@ +macro(ADDEXAMPLE name) + add_executable(example_${name} example_${name}.f90) + target_link_libraries(example_${name} "${PROJECT_NAME}") + add_test(NAME ${name} + COMMAND $ ${CMAKE_CURRENT_BINARY_DIR} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) +endmacro(ADDEXAMPLE) + +set(examples + "hybrd" + "hybrd1" + "lmder1" + "lmdif1" + "primes" +) + +foreach(example IN LISTS examples) + ADDEXAMPLE(${example}) +endforeach() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..6a12a1a --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,44 @@ +set(SRC + minpack.f90 + minpack_capi.f90 +) + +add_library(${PROJECT_NAME} ${SRC}) + +set_target_properties( + ${PROJECT_NAME} + PROPERTIES + POSITION_INDEPENDENT_CODE ON + WINDOWS_EXPORT_ALL_SYMBOLS ON +) + +if(CMAKE_Fortran_COMPILER_ID STREQUAL GNU AND CMAKE_Fortran_COMPILER_VERSION VERSION_LESS 10.0) + target_compile_options( + ${PROJECT_NAME} + PRIVATE + $<$:-fno-range-check> + ) +endif() + +set(LIB_MOD_DIR ${CMAKE_CURRENT_BINARY_DIR}/mod_files/) +# We need the module directory before we finish the configure stage since the +# build interface might resolve before the module directory is generated by CMake +if(NOT EXISTS "${LIB_MOD_DIR}") + make_directory("${LIB_MOD_DIR}") +endif() + +set_target_properties(${PROJECT_NAME} PROPERTIES + Fortran_MODULE_DIRECTORY ${LIB_MOD_DIR}) + +target_include_directories(${PROJECT_NAME} PUBLIC + $ + $ +) + +install(TARGETS ${PROJECT_NAME} + EXPORT ${PROJECT_NAME}-targets + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" +) +install(DIRECTORY ${LIB_MOD_DIR} DESTINATION "${CMAKE_INSTALL_MODULEDIR}") diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..435f31f --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,22 @@ +macro(ADDTEST name) + add_executable(test_${name} test_${name}.f90) + target_link_libraries(test_${name} "${PROJECT_NAME}") + add_test(NAME ${name} + COMMAND $ ${CMAKE_CURRENT_BINARY_DIR} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) +endmacro(ADDTEST) + +set(tests + "chkder" + "hybrd" + "hybrj" + "lmder" + "lmdif" + "lmstr" +) + +foreach(test IN LISTS tests) + ADDTEST(${test}) +endforeach() + +