From 196015799d3e0fcdc29d85408bbeac05fa4b1fd8 Mon Sep 17 00:00:00 2001 From: Federico Perini Date: Sat, 30 Aug 2025 09:19:22 +0200 Subject: [PATCH 1/6] add `module-dir` to the `install` table --- src/fpm/manifest/install.f90 | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/fpm/manifest/install.f90 b/src/fpm/manifest/install.f90 index 97634837fe..cc10c07e2b 100644 --- a/src/fpm/manifest/install.f90 +++ b/src/fpm/manifest/install.f90 @@ -4,11 +4,12 @@ !> !>```toml !>library = bool +!>module-dir = "path" !>``` module fpm_manifest_install use fpm_error, only : error_t, fatal_error, syntax_error use tomlf, only : toml_table, toml_key, toml_stat - use fpm_toml, only : get_value, set_value, serializable_t + use fpm_toml, only : get_value, set_value, serializable_t, set_string implicit none private @@ -23,6 +24,9 @@ module fpm_manifest_install !> Install tests with this project logical :: test = .false. + !> Directory where compiled module files should be installed + character(len=:), allocatable :: module_dir + contains !> Print information on this instance @@ -56,6 +60,7 @@ subroutine new_install_config(self, table, error) call get_value(table, "library", self%library, .false.) call get_value(table, "test", self%test, .false.) + call get_value(table, "module-dir", self%module_dir) end subroutine new_install_config @@ -80,7 +85,7 @@ subroutine check(table, error) case default call syntax_error(error, "Key "//list(ikey)%key//" is not allowed in install table") exit - case("library","test") + case("library","test","module-dir") continue end select end do @@ -114,6 +119,9 @@ subroutine info(self, unit, verbosity) write(unit, fmt) "Install configuration" write(unit, fmt) " - library install", trim(merge("enabled ", "disabled", self%library)) write(unit, fmt) " - test install", trim(merge("enabled ", "disabled", self%test)) + if (allocated(self%module_dir)) then + write(unit, fmt) " - module directory", self%module_dir + end if end subroutine info @@ -127,6 +135,10 @@ logical function install_conf_same(this,that) type is (install_config_t) if (this%library.neqv.other%library) return if (this%test.neqv.other%test) return + if (allocated(this%module_dir).neqv.allocated(other%module_dir)) return + if (allocated(this%module_dir)) then + if (.not.(this%module_dir==other%module_dir)) return + end if class default ! Not the same type return @@ -155,6 +167,9 @@ subroutine dump_to_toml(self, table, error) call set_value(table, "test", self%test, error, class_name) if (allocated(error)) return + call set_string(table, "module-dir", self%module_dir, error, class_name) + if (allocated(error)) return + end subroutine dump_to_toml !> Read install config from toml table (no checks made at this stage) @@ -175,6 +190,8 @@ subroutine load_from_toml(self, table, error) if (allocated(error)) return call get_value(table, "test", self%test, error, class_name) if (allocated(error)) return + call get_value(table, "module-dir", self%module_dir) + if (allocated(error)) return end subroutine load_from_toml From 6455ce44a6962876cbeca9129fad8eec6e9c6583 Mon Sep 17 00:00:00 2001 From: Federico Perini Date: Sat, 30 Aug 2025 09:25:20 +0200 Subject: [PATCH 2/6] manifest: add test --- test/fpm_test/test_manifest.f90 | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/test/fpm_test/test_manifest.f90 b/test/fpm_test/test_manifest.f90 index 42ab78cafa..5fd5b2a64b 100644 --- a/test/fpm_test/test_manifest.f90 +++ b/test/fpm_test/test_manifest.f90 @@ -68,6 +68,7 @@ subroutine collect_manifest(tests) & new_unittest("example-empty", test_example_empty, should_fail=.true.), & & new_unittest("install-library", test_install_library), & & new_unittest("install-empty", test_install_empty), & + & new_unittest("install-module-dir", test_install_module_dir), & & new_unittest("install-wrongkey", test_install_wrongkey, should_fail=.true.), & & new_unittest("preprocess-empty", test_preprocess_empty), & & new_unittest("preprocess-wrongkey", test_preprocess_wrongkey, should_fail=.true.), & @@ -1359,6 +1360,34 @@ subroutine test_install_wrongkey(error) end subroutine test_install_wrongkey + + subroutine test_install_module_dir(error) + use fpm_manifest_install + + !> Error handling + type(error_t), allocatable, intent(out) :: error + + type(toml_table) :: table + type(install_config_t) :: install + + table = toml_table() + call set_value(table, "module-dir", "custom_modules") + + call new_install_config(install, table, error) + if (allocated(error)) return + + if (.not.allocated(install%module_dir)) then + call test_failed(error, "Module directory should be allocated") + return + end if + + if (install%module_dir /= "custom_modules") then + call test_failed(error, "Module directory should match input") + return + end if + + end subroutine test_install_module_dir + subroutine test_preprocess_empty(error) use fpm_manifest_preprocess From 388108d110757d3a5c2cbe138d0afef97a93cf48 Mon Sep 17 00:00:00 2001 From: Federico Perini Date: Sat, 30 Aug 2025 14:59:10 +0200 Subject: [PATCH 3/6] installer: use `moduledir` for modules --- src/fpm/cmd/install.f90 | 4 ++-- src/fpm/installer.f90 | 29 ++++++++++++++++++++++++++++- test/fpm_test/test_installer.f90 | 17 +++++++++++++++++ 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/src/fpm/cmd/install.f90 b/src/fpm/cmd/install.f90 index 867959526c..0a33f044fb 100644 --- a/src/fpm/cmd/install.f90 +++ b/src/fpm/cmd/install.f90 @@ -63,7 +63,7 @@ subroutine cmd_install(settings) call new_installer(installer, prefix=settings%prefix, & bindir=settings%bindir, libdir=settings%libdir, testdir=settings%testdir, & - includedir=settings%includedir, & + includedir=settings%includedir, moduledir=package%install%module_dir, & verbosity=merge(2, 1, settings%verbose)) if (allocated(package%library) .and. package%install%library) then @@ -141,7 +141,7 @@ subroutine install_module_files(installer, targets, error) call filter_modules(targets, modules) do ii = 1, size(modules) - call installer%install_header(modules(ii)%s//".mod", error) + call installer%install_module(modules(ii)%s//".mod", error) if (allocated(error)) exit end do if (allocated(error)) return diff --git a/src/fpm/installer.f90 b/src/fpm/installer.f90 index 42cba10b91..704c10a243 100644 --- a/src/fpm/installer.f90 +++ b/src/fpm/installer.f90 @@ -27,6 +27,8 @@ module fpm_installer character(len=:), allocatable :: testdir !> Include directory relative to the installation prefix character(len=:), allocatable :: includedir + !> Module directory relative to the installation prefix + character(len=:), allocatable :: moduledir !> Output unit for informative printout integer :: unit = output_unit !> Verbosity of the installer @@ -46,6 +48,8 @@ module fpm_installer procedure :: install_library !> Install a header/module in its correct subdirectory procedure :: install_header + !> Install a module in its correct subdirectory + procedure :: install_module !> Install a test program in its correct subdirectory procedure :: install_test !> Install a generic file into a subdirectory in the installation prefix @@ -69,6 +73,9 @@ module fpm_installer !> Default name of the include subdirectory character(len=*), parameter :: default_includedir = "include" + !> Default name of the module subdirectory + character(len=*), parameter :: default_moduledir = "include" + !> Copy command on Unix platforms character(len=*), parameter :: default_copy_unix = "cp" @@ -90,7 +97,7 @@ module fpm_installer contains !> Create a new instance of an installer - subroutine new_installer(self, prefix, bindir, libdir, includedir, testdir, verbosity, & + subroutine new_installer(self, prefix, bindir, libdir, includedir, moduledir, testdir, verbosity, & copy, move) !> Instance of the installer type(installer_t), intent(out) :: self @@ -102,6 +109,8 @@ subroutine new_installer(self, prefix, bindir, libdir, includedir, testdir, verb character(len=*), intent(in), optional :: libdir !> Include directory relative to the installation prefix character(len=*), intent(in), optional :: includedir + !> Module directory relative to the installation prefix + character(len=*), intent(in), optional :: moduledir !> Test directory relative to the installation prefix character(len=*), intent(in), optional :: testdir !> Verbosity of the installer @@ -139,6 +148,12 @@ subroutine new_installer(self, prefix, bindir, libdir, includedir, testdir, verb else self%includedir = default_includedir end if + + if (present(moduledir)) then + self%moduledir = moduledir + else + self%moduledir = default_moduledir + end if if (present(testdir)) then self%testdir = testdir @@ -288,6 +303,18 @@ subroutine install_header(self, header, error) call self%install(header, self%includedir, error) end subroutine install_header + !> Install a module in its correct subdirectory + subroutine install_module(self, module, error) + !> Instance of the installer + class(installer_t), intent(inout) :: self + !> Path to the module + character(len=*), intent(in) :: module + !> Error handling + type(error_t), allocatable, intent(out) :: error + + call self%install(module, self%moduledir, error) + end subroutine install_module + !> Install a generic file into a subdirectory in the installation prefix subroutine install(self, source, destination, error) !> Instance of the installer diff --git a/test/fpm_test/test_installer.f90 b/test/fpm_test/test_installer.f90 index edc5838d88..1d47ed2e41 100644 --- a/test/fpm_test/test_installer.f90 +++ b/test/fpm_test/test_installer.f90 @@ -36,6 +36,7 @@ subroutine collect_installer(testsuite) & new_unittest("install-pkgconfig", test_install_pkgconfig), & & new_unittest("install-sitepackages", test_install_sitepackages), & & new_unittest("install-mod", test_install_mod), & + & new_unittest("install-module-custom", test_install_module_custom), & & new_unittest("install-exe-unix", test_install_exe_unix), & & new_unittest("install-exe-win", test_install_exe_win), & & new_unittest("install-test-unix", test_install_tests_unix), & @@ -184,6 +185,22 @@ subroutine test_install_mod(error) end subroutine test_install_mod + subroutine test_install_module_custom(error) + !> Error handling + type(error_t), allocatable, intent(out) :: error + + type(mock_installer_t) :: mock + type(installer_t) :: installer + + call new_installer(installer, prefix="PREFIX", moduledir="custom/modules", verbosity=0, copy="mock") + mock%installer_t = installer + mock%expected_dir = join_path("PREFIX", "custom/modules") + mock%expected_run = 'mock "test_module.mod" "'//join_path("PREFIX", "custom/modules")//'"' + + call mock%install_module("test_module.mod", error) + + end subroutine test_install_module_custom + subroutine test_install_shared_library_unix(error) !> Error handling type(error_t), allocatable, intent(out) :: error From 01872bd1abc2f968d26754c3a79cb8aca77064aa Mon Sep 17 00:00:00 2001 From: Federico Perini Date: Sat, 30 Aug 2025 15:09:51 +0200 Subject: [PATCH 4/6] add example package --- example_packages/custom_module_dir/README.md | 32 +++++++++++++++++++ example_packages/custom_module_dir/fpm.toml | 3 ++ .../custom_module_dir/src/greeting.f90 | 13 ++++++++ .../custom_module_dir/src/math_utils.f90 | 20 ++++++++++++ 4 files changed, 68 insertions(+) create mode 100644 example_packages/custom_module_dir/README.md create mode 100644 example_packages/custom_module_dir/fpm.toml create mode 100644 example_packages/custom_module_dir/src/greeting.f90 create mode 100644 example_packages/custom_module_dir/src/math_utils.f90 diff --git a/example_packages/custom_module_dir/README.md b/example_packages/custom_module_dir/README.md new file mode 100644 index 0000000000..92790bb6a2 --- /dev/null +++ b/example_packages/custom_module_dir/README.md @@ -0,0 +1,32 @@ +# Custom Module Directory Example + +This example demonstrates the use of a custom module directory in the `[install]` section of `fpm.toml`. + +## Features + +- Two simple Fortran modules: `greeting` and `math_utils` +- Custom module installation directory specified as `custom/modules` +- Shows how modules can be installed to a different location than headers + +## Configuration + +In `fpm.toml`: + +```toml +[install] +library = true +module-dir = "custom/modules" +``` + +This configuration will install compiled `.mod` files to the `custom/modules` directory instead of the default `include` directory. + +## Testing + +To test this example: + +```bash +cd example_packages/custom_module_dir +fpm build +fpm install --prefix /tmp/test_install +# Check that .mod files are in /tmp/test_install/custom/modules/ +``` \ No newline at end of file diff --git a/example_packages/custom_module_dir/fpm.toml b/example_packages/custom_module_dir/fpm.toml new file mode 100644 index 0000000000..1f51b240f1 --- /dev/null +++ b/example_packages/custom_module_dir/fpm.toml @@ -0,0 +1,3 @@ +name = "custom-module-dir" +install.library = true +install.module-dir = "custom/modules" diff --git a/example_packages/custom_module_dir/src/greeting.f90 b/example_packages/custom_module_dir/src/greeting.f90 new file mode 100644 index 0000000000..3cb2b20266 --- /dev/null +++ b/example_packages/custom_module_dir/src/greeting.f90 @@ -0,0 +1,13 @@ +module greeting + implicit none + private + public :: say_hello + +contains + + subroutine say_hello(name) + character(len=*), intent(in) :: name + print *, 'Hello, ' // name // '!' + end subroutine say_hello + +end module greeting \ No newline at end of file diff --git a/example_packages/custom_module_dir/src/math_utils.f90 b/example_packages/custom_module_dir/src/math_utils.f90 new file mode 100644 index 0000000000..c8915dd630 --- /dev/null +++ b/example_packages/custom_module_dir/src/math_utils.f90 @@ -0,0 +1,20 @@ +module math_utils + implicit none + private + public :: add_numbers, multiply_numbers + +contains + + function add_numbers(a, b) result(sum) + integer, intent(in) :: a, b + integer :: sum + sum = a + b + end function add_numbers + + function multiply_numbers(a, b) result(product) + integer, intent(in) :: a, b + integer :: product + product = a * b + end function multiply_numbers + +end module math_utils \ No newline at end of file From f01f9cb1aaf9e1bc4884347049984302d08c2afe Mon Sep 17 00:00:00 2001 From: Federico Perini Date: Sat, 30 Aug 2025 15:09:55 +0200 Subject: [PATCH 5/6] add CI test --- ci/run_tests.sh | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/ci/run_tests.sh b/ci/run_tests.sh index 0260743f16..fbf147fe4f 100755 --- a/ci/run_tests.sh +++ b/ci/run_tests.sh @@ -344,5 +344,19 @@ pushd static_app_only test $EXIT_CODE -eq 0 popd +# Test custom module directory +pushd custom_module_dir +"$fpm" build +rm -rf ./test_custom_install +"$fpm" install --prefix ./test_custom_install +# Verify modules are installed in custom directory +test -f ./test_custom_install/custom/modules/greeting.mod +test -f ./test_custom_install/custom/modules/math_utils.mod +# Verify library is still installed normally +test -f ./test_custom_install/lib/libcustom-module-dir.a +# Clean up +rm -rf ./test_custom_install +popd + # Cleanup rm -rf ./*/build From 42ca4d0c84f406851a49a15228d246ba648954da Mon Sep 17 00:00:00 2001 From: Federico Perini Date: Sat, 30 Aug 2025 15:33:19 +0200 Subject: [PATCH 6/6] do not use slash in test (windows compliant) --- ci/run_tests.sh | 4 ++-- example_packages/custom_module_dir/README.md | 8 ++++---- example_packages/custom_module_dir/fpm.toml | 2 +- test/fpm_test/test_installer.f90 | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ci/run_tests.sh b/ci/run_tests.sh index fbf147fe4f..72f666d4a9 100755 --- a/ci/run_tests.sh +++ b/ci/run_tests.sh @@ -350,8 +350,8 @@ pushd custom_module_dir rm -rf ./test_custom_install "$fpm" install --prefix ./test_custom_install # Verify modules are installed in custom directory -test -f ./test_custom_install/custom/modules/greeting.mod -test -f ./test_custom_install/custom/modules/math_utils.mod +test -f ./test_custom_install/custom_modules/greeting.mod +test -f ./test_custom_install/custom_modules/math_utils.mod # Verify library is still installed normally test -f ./test_custom_install/lib/libcustom-module-dir.a # Clean up diff --git a/example_packages/custom_module_dir/README.md b/example_packages/custom_module_dir/README.md index 92790bb6a2..76ffa5d25d 100644 --- a/example_packages/custom_module_dir/README.md +++ b/example_packages/custom_module_dir/README.md @@ -5,7 +5,7 @@ This example demonstrates the use of a custom module directory in the `[install] ## Features - Two simple Fortran modules: `greeting` and `math_utils` -- Custom module installation directory specified as `custom/modules` +- Custom module installation directory specified as `custom_modules` - Shows how modules can be installed to a different location than headers ## Configuration @@ -15,10 +15,10 @@ In `fpm.toml`: ```toml [install] library = true -module-dir = "custom/modules" +module-dir = "custom_modules" ``` -This configuration will install compiled `.mod` files to the `custom/modules` directory instead of the default `include` directory. +This configuration will install compiled `.mod` files to the `custom_modules` directory instead of the default `include` directory. ## Testing @@ -28,5 +28,5 @@ To test this example: cd example_packages/custom_module_dir fpm build fpm install --prefix /tmp/test_install -# Check that .mod files are in /tmp/test_install/custom/modules/ +# Check that .mod files are in /tmp/test_install/custom_modules/ ``` \ No newline at end of file diff --git a/example_packages/custom_module_dir/fpm.toml b/example_packages/custom_module_dir/fpm.toml index 1f51b240f1..9487cc1268 100644 --- a/example_packages/custom_module_dir/fpm.toml +++ b/example_packages/custom_module_dir/fpm.toml @@ -1,3 +1,3 @@ name = "custom-module-dir" install.library = true -install.module-dir = "custom/modules" +install.module-dir = "custom_modules" diff --git a/test/fpm_test/test_installer.f90 b/test/fpm_test/test_installer.f90 index 1d47ed2e41..1a7e3641f2 100644 --- a/test/fpm_test/test_installer.f90 +++ b/test/fpm_test/test_installer.f90 @@ -192,10 +192,10 @@ subroutine test_install_module_custom(error) type(mock_installer_t) :: mock type(installer_t) :: installer - call new_installer(installer, prefix="PREFIX", moduledir="custom/modules", verbosity=0, copy="mock") + call new_installer(installer, prefix="PREFIX", moduledir="custom_modules", verbosity=0, copy="mock") mock%installer_t = installer - mock%expected_dir = join_path("PREFIX", "custom/modules") - mock%expected_run = 'mock "test_module.mod" "'//join_path("PREFIX", "custom/modules")//'"' + mock%expected_dir = join_path("PREFIX", "custom_modules") + mock%expected_run = 'mock "test_module.mod" "'//join_path("PREFIX", "custom_modules")//'"' call mock%install_module("test_module.mod", error)