diff --git a/ci/run_tests.sh b/ci/run_tests.sh index ff477e7e47..5b08cb51bf 100755 --- a/ci/run_tests.sh +++ b/ci/run_tests.sh @@ -98,5 +98,30 @@ pushd c_header_only "$fpm" build popd +pushd profiles_with_c +"$fpm" build +"$fpm" run +popd + +pushd program_with_compiler_profiles +"$fpm" build +"$fpm" run +popd + +pushd program_with_free_form_in_dot_f +"$fpm" build +"$fpm" run +popd + +pushd "program_with_profiles_scope/primary_package" +"$fpm" build +"$fpm" run +popd + +pushd "profiles_priorities/main_package" +rm -rf build +"$fpm" build +popd + # Cleanup rm -rf ./*/build diff --git a/example_packages/profiles_priorities/d1/.gitignore b/example_packages/profiles_priorities/d1/.gitignore new file mode 100644 index 0000000000..a007feab07 --- /dev/null +++ b/example_packages/profiles_priorities/d1/.gitignore @@ -0,0 +1 @@ +build/* diff --git a/example_packages/profiles_priorities/d1/app/d1.f90 b/example_packages/profiles_priorities/d1/app/d1.f90 new file mode 100644 index 0000000000..9fc99a141d --- /dev/null +++ b/example_packages/profiles_priorities/d1/app/d1.f90 @@ -0,0 +1,7 @@ +program d1 + use d1_m, only: say_hi + + implicit none + + call say_hi() +end program d1 diff --git a/example_packages/profiles_priorities/d1/fpm.toml b/example_packages/profiles_priorities/d1/fpm.toml new file mode 100644 index 0000000000..0309566754 --- /dev/null +++ b/example_packages/profiles_priorities/d1/fpm.toml @@ -0,0 +1,21 @@ +name = "d1" + +[library] +source-dir="source" + +[[executable]] +name="d1" +source-dir="app" +main="d1.F90" + +[profiles.debug.gfortran] +flags="-g -O1 -Dnot_defined=3" + +[profiles.debug.gfortran.windows] +flags="/g /O1 /Dnot_defined=3" + +[dependencies] +"d11" = {path = "../d11"} +"d12" = {path = "../d12"} + + diff --git a/example_packages/profiles_priorities/d1/source/d1_m.F90 b/example_packages/profiles_priorities/d1/source/d1_m.F90 new file mode 100644 index 0000000000..e4acbad7d5 --- /dev/null +++ b/example_packages/profiles_priorities/d1/source/d1_m.F90 @@ -0,0 +1,13 @@ +module d1_m + use d11_m, only: create_greeting + use d12_m, only: get_name + implicit none + + public :: say_hi +contains + subroutine say_hi() + if (not_defined /= 3) stop 1 + print *, create_greeting("hello"), get_name("developer","fpm") + end subroutine say_hi +end module d1_m + diff --git a/example_packages/profiles_priorities/d11/.gitignore b/example_packages/profiles_priorities/d11/.gitignore new file mode 100644 index 0000000000..a007feab07 --- /dev/null +++ b/example_packages/profiles_priorities/d11/.gitignore @@ -0,0 +1 @@ +build/* diff --git a/example_packages/profiles_priorities/d11/app/d11.f90 b/example_packages/profiles_priorities/d11/app/d11.f90 new file mode 100644 index 0000000000..674b310a4f --- /dev/null +++ b/example_packages/profiles_priorities/d11/app/d11.f90 @@ -0,0 +1,8 @@ +program d11 + use d11_m, only: create_greeting + + implicit none + + print *,create_greeting("hey") +end program d11 + diff --git a/example_packages/profiles_priorities/d11/fpm.toml b/example_packages/profiles_priorities/d11/fpm.toml new file mode 100644 index 0000000000..a828b9ef4b --- /dev/null +++ b/example_packages/profiles_priorities/d11/fpm.toml @@ -0,0 +1,15 @@ +name = "d11" + +[library] +source-dir="source" + +[[executable]] +name="d11" +source-dir="app" +main="d11.F90" + +[profiles.debug.gfortran] +flags="-g -O2 -Dnot_defined=4" + +[profiles.debug.gfortran.windows] +flags="/g /O2 /Dnot_defined=4" diff --git a/example_packages/profiles_priorities/d11/source/d11_m.F90 b/example_packages/profiles_priorities/d11/source/d11_m.F90 new file mode 100644 index 0000000000..64e7b640d8 --- /dev/null +++ b/example_packages/profiles_priorities/d11/source/d11_m.F90 @@ -0,0 +1,15 @@ +module d11_m + implicit none + + public :: create_greeting +contains + function create_greeting(greeting) result(created) + character(len=*), intent(in) :: greeting + character(len=:), allocatable :: created + + if (not_defined /= 4) stop 1 + created = greeting // " " + end function create_greeting +end module d11_m + + diff --git a/example_packages/profiles_priorities/d12/.gitignore b/example_packages/profiles_priorities/d12/.gitignore new file mode 100644 index 0000000000..a007feab07 --- /dev/null +++ b/example_packages/profiles_priorities/d12/.gitignore @@ -0,0 +1 @@ +build/* diff --git a/example_packages/profiles_priorities/d12/app/d12.f90 b/example_packages/profiles_priorities/d12/app/d12.f90 new file mode 100644 index 0000000000..58e0043ae2 --- /dev/null +++ b/example_packages/profiles_priorities/d12/app/d12.f90 @@ -0,0 +1,9 @@ +program d12 + use d12_m, only: get_name + + implicit none + + print *,get_name("developer", "fpm") +end program d12 + + diff --git a/example_packages/profiles_priorities/d12/fpm.toml b/example_packages/profiles_priorities/d12/fpm.toml new file mode 100644 index 0000000000..c931545731 --- /dev/null +++ b/example_packages/profiles_priorities/d12/fpm.toml @@ -0,0 +1,10 @@ +name = "d12" + +[library] +source-dir="source" + +[[executable]] +name="d12" +source-dir="app" +main="d12.F90" + diff --git a/example_packages/profiles_priorities/d12/source/d12_m.F90 b/example_packages/profiles_priorities/d12/source/d12_m.F90 new file mode 100644 index 0000000000..87a1158b44 --- /dev/null +++ b/example_packages/profiles_priorities/d12/source/d12_m.F90 @@ -0,0 +1,16 @@ +module d12_m + implicit none + + public :: get_name +contains + function get_name(name, surname) result(full_name) + character(len=*), intent(in) :: name, surname + character(len=:), allocatable :: full_name + + if (not_defined /= 3) stop 1 + full_name = surname // " " // name + end function get_name +end module d12_m + + + diff --git a/example_packages/profiles_priorities/d2/.gitignore b/example_packages/profiles_priorities/d2/.gitignore new file mode 100644 index 0000000000..a007feab07 --- /dev/null +++ b/example_packages/profiles_priorities/d2/.gitignore @@ -0,0 +1 @@ +build/* diff --git a/example_packages/profiles_priorities/d2/app/d2.f90 b/example_packages/profiles_priorities/d2/app/d2.f90 new file mode 100644 index 0000000000..6b212b1323 --- /dev/null +++ b/example_packages/profiles_priorities/d2/app/d2.f90 @@ -0,0 +1,8 @@ +program d2 + use d2_m, only: count_to_ten + + implicit none + + call count_to_ten() +end program d2 + diff --git a/example_packages/profiles_priorities/d2/fpm.toml b/example_packages/profiles_priorities/d2/fpm.toml new file mode 100644 index 0000000000..5318afb40e --- /dev/null +++ b/example_packages/profiles_priorities/d2/fpm.toml @@ -0,0 +1,16 @@ +name = "d2" + +[library] +source-dir="source" + +[[executable]] +name="d2" +source-dir="app" +main="d2.F90" + +[dependencies] +"d21" = {path = "../d21"} +"d22" = {path = "../d22"} + + + diff --git a/example_packages/profiles_priorities/d2/source/d2_m.F90 b/example_packages/profiles_priorities/d2/source/d2_m.F90 new file mode 100644 index 0000000000..d385cfcf11 --- /dev/null +++ b/example_packages/profiles_priorities/d2/source/d2_m.F90 @@ -0,0 +1,18 @@ +module d2_m + use d21_m, only: count_iter + use d22_m, only: count_rec + implicit none + + public :: count_to_ten +contains + subroutine count_to_ten() + if (not_defined /= 1) stop 1 + print *,"This is test of counting to ten:" + print *,"Iterative version" + call count_iter(10) + print *,"Recursive version" + call count_rec(1,10) + end subroutine count_to_ten +end module d2_m + + diff --git a/example_packages/profiles_priorities/d21/.gitignore b/example_packages/profiles_priorities/d21/.gitignore new file mode 100644 index 0000000000..a007feab07 --- /dev/null +++ b/example_packages/profiles_priorities/d21/.gitignore @@ -0,0 +1 @@ +build/* diff --git a/example_packages/profiles_priorities/d21/app/d21.f90 b/example_packages/profiles_priorities/d21/app/d21.f90 new file mode 100644 index 0000000000..53f3e9cab5 --- /dev/null +++ b/example_packages/profiles_priorities/d21/app/d21.f90 @@ -0,0 +1,10 @@ +program d21 + use d21_m, only: count_iter + + implicit none + + call count_iter(10) +end program d21 + + + diff --git a/example_packages/profiles_priorities/d21/fpm.toml b/example_packages/profiles_priorities/d21/fpm.toml new file mode 100644 index 0000000000..8353e89bfc --- /dev/null +++ b/example_packages/profiles_priorities/d21/fpm.toml @@ -0,0 +1,16 @@ +name = "d21" + +[library] +source-dir="source" + +[[executable]] +name="d21" +source-dir="app" +main="d21.F90" + +[profiles.debug.gfortran] +flags="-g -O2 -Dnot_defined=2" + +[profiles.debug.gfortran.windows] +flags="/g /O2 /Dnot_defined=2" + diff --git a/example_packages/profiles_priorities/d21/source/d21_m.F90 b/example_packages/profiles_priorities/d21/source/d21_m.F90 new file mode 100644 index 0000000000..e9f727185d --- /dev/null +++ b/example_packages/profiles_priorities/d21/source/d21_m.F90 @@ -0,0 +1,16 @@ +module d21_m + implicit none + + public :: count_iter +contains + subroutine count_iter(n) + integer :: n, i + if (not_defined /= 2) stop 1 + do i=1,n + print *,i + end do + end subroutine count_iter +end module d21_m + + + diff --git a/example_packages/profiles_priorities/d22/.gitignore b/example_packages/profiles_priorities/d22/.gitignore new file mode 100644 index 0000000000..a007feab07 --- /dev/null +++ b/example_packages/profiles_priorities/d22/.gitignore @@ -0,0 +1 @@ +build/* diff --git a/example_packages/profiles_priorities/d22/app/d22.f90 b/example_packages/profiles_priorities/d22/app/d22.f90 new file mode 100644 index 0000000000..2586631201 --- /dev/null +++ b/example_packages/profiles_priorities/d22/app/d22.f90 @@ -0,0 +1,9 @@ +program d22 + use d22_m, only: count_rec + + implicit none + + call count_rec(1,10) +end program d22 + + diff --git a/example_packages/profiles_priorities/d22/fpm.toml b/example_packages/profiles_priorities/d22/fpm.toml new file mode 100644 index 0000000000..9037de63a1 --- /dev/null +++ b/example_packages/profiles_priorities/d22/fpm.toml @@ -0,0 +1,10 @@ +name = "d22" + +[library] +source-dir="source" + +[[executable]] +name="d22" +source-dir="app" +main="d22.F90" + diff --git a/example_packages/profiles_priorities/d22/source/d22_m.F90 b/example_packages/profiles_priorities/d22/source/d22_m.F90 new file mode 100644 index 0000000000..fe4b891c5c --- /dev/null +++ b/example_packages/profiles_priorities/d22/source/d22_m.F90 @@ -0,0 +1,16 @@ +module d22_m + implicit none + + public :: count_rec +contains + recursive subroutine count_rec(c, n) + integer :: c,n + if (not_defined /= 1) stop 1 + if (n > 0) then + print *,c + call count_rec(c+1, n-1) + end if + end subroutine count_rec +end module d22_m + + diff --git a/example_packages/profiles_priorities/main_package/.gitignore b/example_packages/profiles_priorities/main_package/.gitignore new file mode 100644 index 0000000000..a007feab07 --- /dev/null +++ b/example_packages/profiles_priorities/main_package/.gitignore @@ -0,0 +1 @@ +build/* diff --git a/example_packages/profiles_priorities/main_package/app/main.f90 b/example_packages/profiles_priorities/main_package/app/main.f90 new file mode 100644 index 0000000000..0086519a82 --- /dev/null +++ b/example_packages/profiles_priorities/main_package/app/main.f90 @@ -0,0 +1,8 @@ +program main_package + use d1_m, only: say_hi + use d2_m, only: count_to_ten + + call say_hi() + call count_to_ten() +end program main_package + diff --git a/example_packages/profiles_priorities/main_package/fpm.toml b/example_packages/profiles_priorities/main_package/fpm.toml new file mode 100644 index 0000000000..6d2a6bc0b8 --- /dev/null +++ b/example_packages/profiles_priorities/main_package/fpm.toml @@ -0,0 +1,12 @@ +name = "main_package" + +[profiles.debug.gfortran] +flags="-g -Dnot_defined=1" + +[profiles.debug.gfortran.windows] +flags="/g /Dnot_defined=1" + +[dependencies] +"d1" = {path = "../d1"} +"d2" = {path = "../d2"} + diff --git a/example_packages/profiles_with_c/.gitignore b/example_packages/profiles_with_c/.gitignore new file mode 100644 index 0000000000..a007feab07 --- /dev/null +++ b/example_packages/profiles_with_c/.gitignore @@ -0,0 +1 @@ +build/* diff --git a/example_packages/profiles_with_c/app/main.f90 b/example_packages/profiles_with_c/app/main.f90 new file mode 100644 index 0000000000..4d3174b61e --- /dev/null +++ b/example_packages/profiles_with_c/app/main.f90 @@ -0,0 +1,10 @@ +program with_c_app +use with_c +implicit none + +write(*,*) "isdir('app') = ", system_isdir('app') +write(*,*) "isdir('src') = ", system_isdir('src') +write(*,*) "isdir('test') = ", system_isdir('test') +write(*,*) "isdir('bench') = ", system_isdir('bench') + +end program with_c_app \ No newline at end of file diff --git a/example_packages/profiles_with_c/fpm.toml b/example_packages/profiles_with_c/fpm.toml new file mode 100644 index 0000000000..d0487997d7 --- /dev/null +++ b/example_packages/profiles_with_c/fpm.toml @@ -0,0 +1,4 @@ +name = "with_c" + +[profiles.debug.gfortran.linux] +c-flags="-O1 -g" diff --git a/example_packages/profiles_with_c/src/c_code.c b/example_packages/profiles_with_c/src/c_code.c new file mode 100644 index 0000000000..44604f029c --- /dev/null +++ b/example_packages/profiles_with_c/src/c_code.c @@ -0,0 +1,10 @@ +#include +/* + * Decides whether a given file name is a directory. + * return 1 if file exists and is a directory + * Source (Public domain): https://github.com/urbanjost/M_system + */ +int my_isdir (const char *path) { + struct stat sb; + return stat(path, &sb) == 0 && S_ISDIR (sb.st_mode); +} \ No newline at end of file diff --git a/example_packages/profiles_with_c/src/with_c.f90 b/example_packages/profiles_with_c/src/with_c.f90 new file mode 100644 index 0000000000..edd839e3c4 --- /dev/null +++ b/example_packages/profiles_with_c/src/with_c.f90 @@ -0,0 +1,26 @@ +module with_c + use iso_c_binding, only: c_char, c_int, c_null_char + implicit none + +contains + + function system_isdir(dirname) + ! Source (Public domain): https://github.com/urbanjost/M_system + ! + implicit none + character(len=*),intent(in) :: dirname + logical :: system_isdir + + interface + function c_isdir(dirname) bind (C,name="my_isdir") result (c_ierr) + import c_char,c_int + character(kind=c_char,len=1),intent(in) :: dirname(*) + integer(kind=c_int) :: c_ierr + end function c_isdir + end interface + + system_isdir= c_isdir(trim(dirname)//c_null_char) == 1 + + end function system_isdir + +end module with_c \ No newline at end of file diff --git a/example_packages/program_with_compiler_profiles/.gitignore b/example_packages/program_with_compiler_profiles/.gitignore new file mode 100644 index 0000000000..a007feab07 --- /dev/null +++ b/example_packages/program_with_compiler_profiles/.gitignore @@ -0,0 +1 @@ +build/* diff --git a/example_packages/program_with_compiler_profiles/app/main.f90 b/example_packages/program_with_compiler_profiles/app/main.f90 new file mode 100644 index 0000000000..d16022bcc8 --- /dev/null +++ b/example_packages/program_with_compiler_profiles/app/main.f90 @@ -0,0 +1,3 @@ +program hello_world + print *, "Hello, World!" +end program hello_world diff --git a/example_packages/program_with_compiler_profiles/fpm.toml b/example_packages/program_with_compiler_profiles/fpm.toml new file mode 100644 index 0000000000..7e7a0d8d5d --- /dev/null +++ b/example_packages/program_with_compiler_profiles/fpm.toml @@ -0,0 +1,14 @@ +name = "hello_world" + +[profiles.debug.gfortran.linux] +flags="-g" + +[profiles.debug.ifort.linux] +flags="-g -traceback" + +[profiles.gfortran] +flags="-g -Wall" + +[profiles.gfortran.windows] +flags="/g" + diff --git a/example_packages/program_with_free_form_in_dot_f/.gitignore b/example_packages/program_with_free_form_in_dot_f/.gitignore new file mode 100644 index 0000000000..a007feab07 --- /dev/null +++ b/example_packages/program_with_free_form_in_dot_f/.gitignore @@ -0,0 +1 @@ +build/* diff --git a/example_packages/program_with_free_form_in_dot_f/apps/say_goodbye/say_goodbye.f90 b/example_packages/program_with_free_form_in_dot_f/apps/say_goodbye/say_goodbye.f90 new file mode 100644 index 0000000000..6966e790f6 --- /dev/null +++ b/example_packages/program_with_free_form_in_dot_f/apps/say_goodbye/say_goodbye.f90 @@ -0,0 +1,7 @@ +program say_goodbye + use farewell_m, only: make_farewell + + implicit none + + print *, make_farewell("World") +end program say_goodbye diff --git a/example_packages/program_with_free_form_in_dot_f/apps/say_hello/say_Hello.f90 b/example_packages/program_with_free_form_in_dot_f/apps/say_hello/say_Hello.f90 new file mode 100644 index 0000000000..cf4a7421d3 --- /dev/null +++ b/example_packages/program_with_free_form_in_dot_f/apps/say_hello/say_Hello.f90 @@ -0,0 +1,7 @@ +program say_Hello + use greet_m, only: make_greeting + + implicit none + + print *, make_greeting("World") +end program say_Hello diff --git a/example_packages/program_with_free_form_in_dot_f/fpm.toml b/example_packages/program_with_free_form_in_dot_f/fpm.toml new file mode 100644 index 0000000000..6bd3a70786 --- /dev/null +++ b/example_packages/program_with_free_form_in_dot_f/fpm.toml @@ -0,0 +1,36 @@ +name = "hello_complex" + +[library] +source-dir="source" + +[[executable]] +name="say_Hello" +source-dir="apps/say_hello" +main="say_Hello.f90" + +[[executable]] +name="say_goodbye" +source-dir="apps/say_goodbye" +main="say_goodbye.f90" + +[[test]] +name="greet_test" +source-dir="tests/greet" +main="greet_test.f90" + +[[test]] +name="farewell_test" +source-dir="tests/farewell" +main="farewell_test.f90" + +[profiles.debug.gfortran] +flags="-ffree-form" + +[profiles.debug.gfortran.windows] +flags="/ffree-form" + +[profiles.debug.ifort] +flags="-free" + +[profiles.debug.ifort.windows] +flags="/free" diff --git a/example_packages/program_with_free_form_in_dot_f/source/farewell_m.f b/example_packages/program_with_free_form_in_dot_f/source/farewell_m.f new file mode 100644 index 0000000000..fbc45edf22 --- /dev/null +++ b/example_packages/program_with_free_form_in_dot_f/source/farewell_m.f @@ -0,0 +1,14 @@ +module farewell_m + use subdir_constants, only: FAREWELL_STR + implicit none + private + + public :: make_farewell +contains + function make_farewell(name) result(greeting) + character(len=*), intent(in) :: name + character(len=:), allocatable :: greeting + + greeting = FAREWELL_STR // name // "!" + end function make_farewell +end module farewell_m diff --git a/example_packages/program_with_free_form_in_dot_f/source/greet_m.f b/example_packages/program_with_free_form_in_dot_f/source/greet_m.f new file mode 100644 index 0000000000..38afd08352 --- /dev/null +++ b/example_packages/program_with_free_form_in_dot_f/source/greet_m.f @@ -0,0 +1,14 @@ +module greet_m + use subdir_constants, only: GREET_STR + implicit none + private + + public :: make_greeting +contains + function make_greeting(name) result(greeting) + character(len=*), intent(in) :: name + character(len=:), allocatable :: greeting + + greeting = GREET_STR // name // "!" + end function make_greeting +end module greet_m diff --git a/example_packages/program_with_free_form_in_dot_f/source/subdir/constants.f90 b/example_packages/program_with_free_form_in_dot_f/source/subdir/constants.f90 new file mode 100644 index 0000000000..59d6e5fee6 --- /dev/null +++ b/example_packages/program_with_free_form_in_dot_f/source/subdir/constants.f90 @@ -0,0 +1,7 @@ +module subdir_constants +implicit none + +character(*), parameter :: GREET_STR = 'Hello, ' +character(*), parameter :: FAREWELL_STR = 'Goodbye, ' + +end module subdir_constants diff --git a/example_packages/program_with_free_form_in_dot_f/tests/farewell/farewell_test.f90 b/example_packages/program_with_free_form_in_dot_f/tests/farewell/farewell_test.f90 new file mode 100644 index 0000000000..1d619deccb --- /dev/null +++ b/example_packages/program_with_free_form_in_dot_f/tests/farewell/farewell_test.f90 @@ -0,0 +1,18 @@ +program farewell_test + use farewell_m, only: make_farewell + use iso_fortran_env, only: error_unit, output_unit + + implicit none + + character(len=:), allocatable :: farewell + + allocate(character(len=0) :: farewell) + farewell = make_farewell("World") + + if (farewell == "Goodbye, World!") then + write(output_unit, *) "Passed" + else + write(error_unit, *) "Failed" + stop 1 + end if +end program farewell_test diff --git a/example_packages/program_with_free_form_in_dot_f/tests/greet/greet_test.f90 b/example_packages/program_with_free_form_in_dot_f/tests/greet/greet_test.f90 new file mode 100644 index 0000000000..bb5d0f92dd --- /dev/null +++ b/example_packages/program_with_free_form_in_dot_f/tests/greet/greet_test.f90 @@ -0,0 +1,18 @@ +program greet_test + use greet_m, only: make_greeting + use iso_fortran_env, only: error_unit, output_unit + + implicit none + + character(len=:), allocatable :: greeting + + allocate(character(len=0) :: greeting) + greeting = make_greeting("World") + + if (greeting == "Hello, World!") then + write(output_unit, *) "Passed" + else + write(error_unit, *) "Failed" + stop 1 + end if +end program greet_test diff --git a/example_packages/program_with_profiles_scope/hello_complex/.gitignore b/example_packages/program_with_profiles_scope/hello_complex/.gitignore new file mode 100644 index 0000000000..a007feab07 --- /dev/null +++ b/example_packages/program_with_profiles_scope/hello_complex/.gitignore @@ -0,0 +1 @@ +build/* diff --git a/example_packages/program_with_profiles_scope/hello_complex/apps/say_goodbye/say_goodbye.f90 b/example_packages/program_with_profiles_scope/hello_complex/apps/say_goodbye/say_goodbye.f90 new file mode 100644 index 0000000000..6966e790f6 --- /dev/null +++ b/example_packages/program_with_profiles_scope/hello_complex/apps/say_goodbye/say_goodbye.f90 @@ -0,0 +1,7 @@ +program say_goodbye + use farewell_m, only: make_farewell + + implicit none + + print *, make_farewell("World") +end program say_goodbye diff --git a/example_packages/program_with_profiles_scope/hello_complex/apps/say_hello/say_Hello.f90 b/example_packages/program_with_profiles_scope/hello_complex/apps/say_hello/say_Hello.f90 new file mode 100644 index 0000000000..cf4a7421d3 --- /dev/null +++ b/example_packages/program_with_profiles_scope/hello_complex/apps/say_hello/say_Hello.f90 @@ -0,0 +1,7 @@ +program say_Hello + use greet_m, only: make_greeting + + implicit none + + print *, make_greeting("World") +end program say_Hello diff --git a/example_packages/program_with_profiles_scope/hello_complex/fpm.toml b/example_packages/program_with_profiles_scope/hello_complex/fpm.toml new file mode 100644 index 0000000000..33ea93da34 --- /dev/null +++ b/example_packages/program_with_profiles_scope/hello_complex/fpm.toml @@ -0,0 +1,29 @@ +name = "hello_complex" + +[library] +source-dir="source" + +[[executable]] +name="say_Hello" +source-dir="apps/say_hello" +main="say_Hello.f90" + +[[executable]] +name="say_goodbye" +source-dir="apps/say_goodbye" +main="say_goodbye.f90" + +[[test]] +name="greet_test" +source-dir="tests/greet" +main="greet_test.f90" + +[[test]] +name="farewell_test" +source-dir="tests/farewell" +main="farewell_test.f90" + +[profiles.debug.gfortran.linux] +flags = '-fcheck=bounds' +files={"source/greet_m.f90"="-Wall -g -fcheck=all", "source/farewell_m.f90"="-Og"} + diff --git a/example_packages/program_with_profiles_scope/hello_complex/source/farewell_m.f90 b/example_packages/program_with_profiles_scope/hello_complex/source/farewell_m.f90 new file mode 100644 index 0000000000..fbc45edf22 --- /dev/null +++ b/example_packages/program_with_profiles_scope/hello_complex/source/farewell_m.f90 @@ -0,0 +1,14 @@ +module farewell_m + use subdir_constants, only: FAREWELL_STR + implicit none + private + + public :: make_farewell +contains + function make_farewell(name) result(greeting) + character(len=*), intent(in) :: name + character(len=:), allocatable :: greeting + + greeting = FAREWELL_STR // name // "!" + end function make_farewell +end module farewell_m diff --git a/example_packages/program_with_profiles_scope/hello_complex/source/greet_m.f90 b/example_packages/program_with_profiles_scope/hello_complex/source/greet_m.f90 new file mode 100644 index 0000000000..38afd08352 --- /dev/null +++ b/example_packages/program_with_profiles_scope/hello_complex/source/greet_m.f90 @@ -0,0 +1,14 @@ +module greet_m + use subdir_constants, only: GREET_STR + implicit none + private + + public :: make_greeting +contains + function make_greeting(name) result(greeting) + character(len=*), intent(in) :: name + character(len=:), allocatable :: greeting + + greeting = GREET_STR // name // "!" + end function make_greeting +end module greet_m diff --git a/example_packages/program_with_profiles_scope/hello_complex/source/subdir/constants.f90 b/example_packages/program_with_profiles_scope/hello_complex/source/subdir/constants.f90 new file mode 100644 index 0000000000..59d6e5fee6 --- /dev/null +++ b/example_packages/program_with_profiles_scope/hello_complex/source/subdir/constants.f90 @@ -0,0 +1,7 @@ +module subdir_constants +implicit none + +character(*), parameter :: GREET_STR = 'Hello, ' +character(*), parameter :: FAREWELL_STR = 'Goodbye, ' + +end module subdir_constants diff --git a/example_packages/program_with_profiles_scope/hello_complex/tests/farewell/farewell_test.f90 b/example_packages/program_with_profiles_scope/hello_complex/tests/farewell/farewell_test.f90 new file mode 100644 index 0000000000..0f21b18015 --- /dev/null +++ b/example_packages/program_with_profiles_scope/hello_complex/tests/farewell/farewell_test.f90 @@ -0,0 +1,18 @@ +program farewell_test + use farewell_m, only: make_farewell + use iso_fortran_env, only: error_unit, output_unit + + implicit none + + character(len=:), allocatable :: farewell + + allocate(character(len=0) :: farewell) + farewell = make_farewell("World") + + if (farewell == "Goodbye, World!") then + write(output_unit, *) "Passed" + else + write(error_unit, *) "Failed" + call exit(1) + end if +end program farewell_test diff --git a/example_packages/program_with_profiles_scope/hello_complex/tests/greet/greet_test.f90 b/example_packages/program_with_profiles_scope/hello_complex/tests/greet/greet_test.f90 new file mode 100644 index 0000000000..41fa50878e --- /dev/null +++ b/example_packages/program_with_profiles_scope/hello_complex/tests/greet/greet_test.f90 @@ -0,0 +1,18 @@ +program greet_test + use greet_m, only: make_greeting + use iso_fortran_env, only: error_unit, output_unit + + implicit none + + character(len=:), allocatable :: greeting + + allocate(character(len=0) :: greeting) + greeting = make_greeting("World") + + if (greeting == "Hello, World!") then + write(output_unit, *) "Passed" + else + write(error_unit, *) "Failed" + call exit(1) + end if +end program greet_test diff --git a/example_packages/program_with_profiles_scope/primary_package/.gitignore b/example_packages/program_with_profiles_scope/primary_package/.gitignore new file mode 100644 index 0000000000..a007feab07 --- /dev/null +++ b/example_packages/program_with_profiles_scope/primary_package/.gitignore @@ -0,0 +1 @@ +build/* diff --git a/example_packages/program_with_profiles_scope/primary_package/app/main.f90 b/example_packages/program_with_profiles_scope/primary_package/app/main.f90 new file mode 100644 index 0000000000..9e532ca537 --- /dev/null +++ b/example_packages/program_with_profiles_scope/primary_package/app/main.f90 @@ -0,0 +1,4 @@ +program hello_world + use greet_m, only: make_greeting + print *, make_greeting("fpm developpers") +end program hello_world diff --git a/example_packages/program_with_profiles_scope/primary_package/fpm.toml b/example_packages/program_with_profiles_scope/primary_package/fpm.toml new file mode 100644 index 0000000000..e185c1c86d --- /dev/null +++ b/example_packages/program_with_profiles_scope/primary_package/fpm.toml @@ -0,0 +1,17 @@ +name = "hello_world" + +[profiles.debug.gfortran.linux] +flags="-g" + +[profiles.debug.ifort.linux] +flags="-g -traceback" + +[profiles.gfortran] +flags="-g -Wall" + +[profiles.gfortran.windows] +flags="/g" + +[dependencies] +"hello_complex" = {path = "../hello_complex"} + diff --git a/manifest-reference.md b/manifest-reference.md index d97f32cd8b..5865d95dbe 100644 --- a/manifest-reference.md +++ b/manifest-reference.md @@ -47,6 +47,9 @@ Every manifest file consists of the following sections: Project library dependencies - [*dev-dependencies*](#development-dependencies): Dependencies only needed for tests +- Compiler profiles sections: + - [*compiler profiles toml*](#compiler-flags-profiles-toml): + - [*compiler profiles hierarchy*](#compiler-flags-profiles-hierarchy): - [*install*](#installation-configuration): Installation configuration - [*extra*](#additional-free-data-field): @@ -469,6 +472,85 @@ rev = "2f5eaba864ff630ba0c3791126a3f811b6e437f3" Development dependencies allow to declare *dev-dependencies* in the manifest root, which are available to all tests but not exported with the project. +## Compiler flags profiles +### Compiler flags profiles - Toml +Compiler flags profiles can be declared in the *profiles* table. They are organised into subtables in the following order: + +| Subtable | Profile name | Compiler | Operating system | +|---|:---:|:---:|:---:| +| Example | `debug` | `gfortran` | `linux` | + +- Profile name can be an arbitrary string. +- Compiler can be a string from the following list: + - "gfortran" + - "ifort" + - "ifx" + - "pgfortran" + - "nvfortran" + - "flang" + - "caf" + - "f95" + - "lfortran" + - "lfc" + - "nagfor" + - "crayftn" + - "xlf90" + - "ftn95" +- Operating system can be a lowercase string from the following list: + - "linux" + - "macos" + - "windows" + - "cygwin" + - "solaris" + - "freebsd" + - "openbsd" + - "unknown" + +There are 4 fields that can be specified for each of the profiles: +- `'flags'` - Fortran compiler flags +- `'c-flags'` - C compiler flags +- `'link-time-flags'` - Compiler flags applied at linking time to executables +- `'files'` - A subtable containing file name-flags pairs with flags applied to single source files (these overwrite profile flags) + +An example of a complete table follows: +```toml +[profiles.debug.gfortran.linux] +flags = '-g -Wall' +files={"source/greet_m.f90"="-Wall -g -fcheck=all", "source/farewell_m.f90"="-Og"} +``` + +Both profile name and operating system subtables can be omitted in the definition. In such case the following behaviour is applied: +- *Profile name* is omitted - Fields of this subtable are added to fields of all profiles with matching compiler and OS definitions (this is not the case for `files` field) +- *Operating system* is omitted - Fields of this subtable are used if and only if there is no profile with perfectly matching OS definition + +Example: +- The flags field of the following profile is appended to flags fields of all profiles using `gfortran` on `linux` OS +```toml +[profiles.gfortran.linux] +flags = '-g -Wall' +``` + +### Compiler flags profiles - Hierarchy +There are 18 built-in profiles which are implemented in `fpm_manifest_profiles.f90`. They should cover the most used cases. If user wishes to specify their own profiles +such profiles have priority over the built-in ones. This priority can be propagated to dependencies if they do not specify the profiles. + +Example: +In `example_packages/profiles_priorities`, there are 7 packages in total. The main package is called `main_package` and uses `d1` and `d2`. +`d1` uses `d11` and `d12` and similarly for `d2`. +The compiler flags defined in these packages are as follows: +| Package | Flags specified | Flags used | +|---|:---:|:---:| +| `main_package` | `-g` | `-g` | +| `d1` | `-g -O1` | `-g -O1` | +| `d11` | `-g -O2` | `-g -O2` | +| `d12` | *none* | `-g -O1` | +| `d2` | *none* | `-g` | +| `d11` | `-g -O2` | `-g -O2` | +| `d12` | *none* | `-g` | + +As `d12`, `d2` and `d22` do not specify any profiles in their `fpm.toml`, they inherit profile from their parents. +For `d12` the first ancestor with specified profiles is `d1`, therefore it inherits flags `-g -O1`. +The parent of `d22` is `d2` which does not have profiles specified, therefore, both of them inherit `-g` from the main package. ## Installation configuration diff --git a/src/fpm.f90 b/src/fpm.f90 index 68e2bbdfcb..4411299962 100644 --- a/src/fpm.f90 +++ b/src/fpm.f90 @@ -4,15 +4,11 @@ module fpm use fpm_command_line, only: fpm_build_settings, fpm_new_settings, & fpm_run_settings, fpm_install_settings, fpm_test_settings use fpm_dependency, only : new_dependency_tree -use fpm_environment, only: run, get_env +use fpm_environment, only: run, get_env, get_os_type use fpm_filesystem, only: is_dir, join_path, number_of_rows, list_files, exists, basename, filewrite, mkdir -use fpm_model, only: fpm_model_t, srcfile_t, show_model, & - FPM_SCOPE_UNKNOWN, FPM_SCOPE_LIB, FPM_SCOPE_DEP, & - FPM_SCOPE_APP, FPM_SCOPE_EXAMPLE, FPM_SCOPE_TEST +use fpm_model use fpm_compiler, only: get_module_flags, is_unknown_compiler, get_default_c_compiler, & get_archiver - - use fpm_sources, only: add_executable_sources, add_sources_from_dir use fpm_targets, only: targets_from_sources, resolve_module_dependencies, & resolve_target_linking, build_target_t, build_target_ptr, & @@ -24,6 +20,7 @@ module fpm & stdout=>output_unit, & & stderr=>error_unit use fpm_manifest_dependency, only: dependency_config_t +use fpm_manifest_profile, only: profile_config_t, find_profile, DEFAULT_COMPILER use, intrinsic :: iso_fortran_env, only: error_unit implicit none private @@ -43,9 +40,9 @@ subroutine build_model(model, settings, package, error) integer :: i, j type(package_config_t) :: dependency - character(len=:), allocatable :: manifest, lib_dir + character(len=:), allocatable :: manifest, lib_dir, profile, compiler_flags, file_scope_flag - logical :: duplicates_found = .false. + logical :: duplicates_found = .false., profile_found type(string_t) :: include_dir model%package_name = package%name @@ -64,27 +61,11 @@ subroutine build_model(model, settings, package, error) end if if(settings%compiler.eq.'')then - model%fortran_compiler = 'gfortran' + model%fortran_compiler = DEFAULT_COMPILER else model%fortran_compiler = settings%compiler endif - model%archiver = get_archiver() - call get_default_c_compiler(model%fortran_compiler, model%c_compiler) - model%c_compiler = get_env('FPM_C_COMPILER',model%c_compiler) - - if (is_unknown_compiler(model%fortran_compiler)) then - write(*, '(*(a:,1x))') & - "", "Unknown compiler", model%fortran_compiler, "requested!", & - "Defaults for this compiler might be incorrect" - end if - model%output_directory = join_path('build',basename(model%fortran_compiler)//'_'//settings%build_name) - - call get_module_flags(model%fortran_compiler, & - & join_path(model%output_directory,model%package_name), & - & model%fortran_compile_flags) - model%fortran_compile_flags = settings%flag // model%fortran_compile_flags - allocate(model%packages(model%deps%ndep)) ! Add sources from executable directories @@ -151,10 +132,13 @@ subroutine build_model(model, settings, package, error) manifest = join_path(dep%proj_dir, "fpm.toml") call get_package_data(dependency, manifest, error, & - apply_defaults=.true.) + apply_defaults=.true., proj_dir=dep%proj_dir) if (allocated(error)) exit model%packages(i)%name = dependency%name + if (allocated(dependency%profiles)) model%packages(i)%profiles = dependency%profiles + if (allocated(dep%parent)) model%packages(i)%parent = dep%parent + if (.not.allocated(model%packages(i)%sources)) allocate(model%packages(i)%sources(0)) if (allocated(dependency%library)) then @@ -190,11 +174,16 @@ subroutine build_model(model, settings, package, error) end do if (allocated(error)) return + if (.not.(trim(settings%flag).eq.'')) then + model%cmd_compile_flags = settings%flag + else + model%cmd_compile_flags = '' + end if + if (settings%verbose) then - write(*,*)' BUILD_NAME: ',settings%build_name write(*,*)' COMPILER: ',settings%compiler write(*,*)' C COMPILER: ',model%c_compiler - write(*,*)' COMPILER OPTIONS: ', model%fortran_compile_flags + write(*,*)' COMMAND LINE COMPILER OPTIONS: ', model%cmd_compile_flags write(*,*)' INCLUDE DIRECTORIES: [', string_cat(model%include_dirs,','),']' end if @@ -203,6 +192,131 @@ subroutine build_model(model, settings, package, error) if (duplicates_found) then call fpm_stop(1,'*build_model*:Error: One or more duplicate module names found.') end if + + ! Compiler flags logic + if(settings%profile.eq.'')then + if (trim(settings%flag).eq.'') then + profile = 'debug' + end if + else + profile = settings%profile + endif + + ! Choose profile for each package + if (allocated(profile)) then + do i=1,size(model%packages) + model%packages(i)%chosen_profile = look_for_profile(i) + end do + end if + + model%archiver = get_archiver() + call get_default_c_compiler(model%fortran_compiler, model%c_compiler) + model%c_compiler = get_env('FPM_C_COMPILER',model%c_compiler) + + if (is_unknown_compiler(model%fortran_compiler)) then + write(*, '(*(a:,1x))') & + "", "Unknown compiler", model%fortran_compiler, "requested!", & + "Defaults for this compiler might be incorrect" + end if + + ! Choose profiles flags or file specific flags + do j=1,size(model%packages) + associate(package=>model%packages(j), sources=>model%packages(j)%sources, profile=>model%packages(j)%chosen_profile) + do i=1,size(sources) + select case (sources(i)%unit_type) + case (FPM_UNIT_MODULE,FPM_UNIT_SUBMODULE,FPM_UNIT_SUBPROGRAM,FPM_UNIT_CSOURCE,FPM_UNIT_PROGRAM) + file_scope_flag = get_file_scope_flags(sources(i), profile) + if (file_scope_flag.eq."") then + if (sources(i)%unit_type.eq.FPM_UNIT_CSOURCE) then + sources(i)%flags=model%cmd_compile_flags//" "//profile%c_flags + else + sources(i)%flags=model%cmd_compile_flags//" "//profile%flags + end if + else + sources(i)%flags=model%cmd_compile_flags//" "//file_scope_flag + end if + + if (sources(i)%unit_type == FPM_UNIT_PROGRAM) then + sources(i)%link_time_flags=profile%link_time_flags + end if + end select + end do + end associate + end do + + contains + + ! Look for an appropriate profile + ! If package has specified profile, return it + ! If it has just built-in profile, try to find specified one in parents, otherwise return it + ! If it has no profiles, try to find one in parents + function look_for_profile(package_id) result (chosen_profile) + integer, intent(in) :: package_id + + integer :: idx + type(profile_config_t), allocatable :: built_in, chosen_profile + type(profile_config_t) :: current + logical :: profile_found + + idx = package_id + associate(pkgs => model%packages) + do while (.true.) + profile_found = .false. + if (allocated(pkgs(idx)%profiles)) then + call find_profile(pkgs(idx)%profiles, profile, model%fortran_compiler, & + & get_os_type(), profile_found, current) + if (profile_found) then + if (current%is_built_in) then + if (.not. allocated(built_in)) then + built_in = current + chosen_profile = current + end if + if (allocated(pkgs(idx)%parent)) then + idx = pkgs(idx)%parent(1) + else + exit + end if + else + chosen_profile = current + return + end if + end if + else + if (allocated(pkgs(idx)%parent)) then + idx = pkgs(idx)%parent(1) + else + call fpm_stop(1,'*look_for_profile*:Error: Orphan package does not have any profiles.') + end if + end if + end do + end associate + if (.not. allocated(chosen_profile)) call fpm_stop(1,'*look_for_profile*:Error: No profile found.') + end function look_for_profile + + function get_file_scope_flags(source, profile) result(file_scope_flag) + ! Try to match source%file_name in profile%file_scope_flags + ! + ! + type(srcfile_t), intent(in) :: source + type(profile_config_t), intent(in) :: profile + + character(:), allocatable :: file_scope_flag, current + integer :: i + + file_scope_flag = "" + + if (allocated(profile%file_scope_flags)) then + associate(fflags=>profile%file_scope_flags) + do i=1,size(fflags) + if (source%file_name.eq.fflags(i)%file_name) then + file_scope_flag = fflags(i)%flags//" " + exit + end if + end do + end associate + end if + end function get_file_scope_flags + end subroutine build_model ! Check for duplicate modules @@ -255,6 +369,7 @@ subroutine cmd_build(settings) type(fpm_model_t) :: model type(build_target_ptr), allocatable :: targets(:) type(error_t), allocatable :: error +type(string_t), allocatable :: build_dirs(:) integer :: i @@ -268,7 +383,7 @@ subroutine cmd_build(settings) call fpm_stop(1,'*cmd_build*:model error:'//error%message) end if -call targets_from_sources(targets,model,error) +call targets_from_sources(targets,model,error,build_dirs) if (allocated(error)) then call fpm_stop(1,'*cmd_build*:target error:'//error%message) end if @@ -280,7 +395,7 @@ subroutine cmd_build(settings) else if (settings%show_model) then call show_model(model) else - call build_package(targets,model) + call build_package(targets,model,build_dirs) endif end subroutine @@ -299,6 +414,7 @@ subroutine cmd_run(settings,test) type(string_t), allocatable :: executables(:) type(build_target_t), pointer :: exe_target type(srcfile_t), pointer :: exe_source + type(string_t), allocatable :: build_dirs(:) integer :: run_scope integer, allocatable :: stat(:) character(len=:),allocatable :: line @@ -314,7 +430,7 @@ subroutine cmd_run(settings,test) call fpm_stop(1, '*cmd_run*:model error:'//error%message) end if - call targets_from_sources(targets,model,error) + call targets_from_sources(targets,model,error,build_dirs) if (allocated(error)) then call fpm_stop(1, '*cmd_run*:targets error:'//error%message) end if @@ -411,7 +527,7 @@ subroutine cmd_run(settings,test) end if - call build_package(targets,model) + call build_package(targets,model,build_dirs) if (settings%list) then call compact_list() diff --git a/src/fpm/dependency.f90 b/src/fpm/dependency.f90 index bd85b6f014..7c1394562c 100644 --- a/src/fpm/dependency.f90 +++ b/src/fpm/dependency.f90 @@ -57,14 +57,15 @@ module fpm_dependency use, intrinsic :: iso_fortran_env, only : output_unit use fpm_environment, only : get_os_type, OS_WINDOWS - use fpm_error, only : error_t, fatal_error + use fpm_error, only : error_t, fatal_error, fpm_stop use fpm_filesystem, only : exists, join_path, mkdir, canon_path, windows_path use fpm_git, only : git_target_revision, git_target_default, git_revision use fpm_manifest, only : package_config_t, dependency_config_t, & get_package_data use fpm_strings, only : string_t, operator(.in.) - use fpm_toml, only : toml_table, toml_key, toml_error, toml_serializer, & - toml_parse, get_value, set_value, add_table + use fpm_toml, only : toml_table, toml_array, toml_key, toml_error, & + & toml_serializer, toml_parse, toml_stat, get_value, set_value, & + & add_table, add_array, len use fpm_versioning, only : version_t, new_version, char implicit none private @@ -92,6 +93,8 @@ module fpm_dependency logical :: done = .false. !> Dependency should be updated logical :: update = .false. + !> List of indices of parent nodes in the tree + integer, allocatable :: parent(:) contains !> Update dependency from project manifest procedure :: register @@ -191,7 +194,7 @@ subroutine new_dependency_tree(self, verbosity, cache) end subroutine new_dependency_tree !> Create a new dependency node from a configuration - pure subroutine new_dependency_node(self, dependency, version, proj_dir, update) + pure subroutine new_dependency_node(self, dependency, version, proj_dir, update, parent) !> Instance of the dependency node type(dependency_node_t), intent(out) :: self !> Dependency configuration data @@ -202,6 +205,8 @@ pure subroutine new_dependency_node(self, dependency, version, proj_dir, update) character(len=*), intent(in), optional :: proj_dir !> Dependency should be updated logical, intent(in), optional :: update + !> Index of parent node + integer, intent(in), optional :: parent self%dependency_config_t = dependency @@ -217,6 +222,11 @@ pure subroutine new_dependency_node(self, dependency, version, proj_dir, update) self%update = update end if + if (present(parent)) then + allocate(self%parent(1)) + self%parent(1) = parent + end if + end subroutine new_dependency_node !> Add project dependencies, each depth level after each other. @@ -252,16 +262,16 @@ subroutine add_project(self, package, error) if (allocated(error)) return ! Resolve the root project - call self%resolve(root, error) + call self%resolve(root, error, parent=package%name) if (allocated(error)) return ! Add the root project dependencies (depth 1) - call self%add(package, root, .true., error) + call self%add(package, root, .true., error, parent=package%name) if (allocated(error)) return ! Now decent into the dependency tree, level for level do while(.not.self%finished()) - call self%resolve(root, error) + call self%resolve(root, error, parent=package%name) if (allocated(error)) exit end do if (allocated(error)) return @@ -274,7 +284,7 @@ subroutine add_project(self, package, error) end subroutine add_project !> Add a project and its dependencies to the dependency tree - recursive subroutine add_project_dependencies(self, package, root, main, error) + recursive subroutine add_project_dependencies(self, package, root, main, error, parent) !> Instance of the dependency tree class(dependency_tree_t), intent(inout) :: self !> Project configuration to add @@ -285,24 +295,26 @@ recursive subroutine add_project_dependencies(self, package, root, main, error) logical, intent(in) :: main !> Error handling type(error_t), allocatable, intent(out) :: error + !> Name of parent package + character(len=*), intent(in), optional :: parent integer :: ii if (allocated(package%dependency)) then - call self%add(package%dependency, error) + call self%add(package%dependency, error, parent=package%name) if (allocated(error)) return end if if (main) then if (allocated(package%dev_dependency)) then - call self%add(package%dev_dependency, error) + call self%add(package%dev_dependency, error, parent=package%name) if (allocated(error)) return end if if (allocated(package%executable)) then do ii = 1, size(package%executable) if (allocated(package%executable(ii)%dependency)) then - call self%add(package%executable(ii)%dependency, error) + call self%add(package%executable(ii)%dependency, error, parent=package%name) if (allocated(error)) exit end if end do @@ -312,7 +324,7 @@ recursive subroutine add_project_dependencies(self, package, root, main, error) if (allocated(package%example)) then do ii = 1, size(package%example) if (allocated(package%example(ii)%dependency)) then - call self%add(package%example(ii)%dependency, error) + call self%add(package%example(ii)%dependency, error, parent=package%name) if (allocated(error)) exit end if end do @@ -322,7 +334,7 @@ recursive subroutine add_project_dependencies(self, package, root, main, error) if (allocated(package%test)) then do ii = 1, size(package%test) if (allocated(package%test(ii)%dependency)) then - call self%add(package%test(ii)%dependency, error) + call self%add(package%test(ii)%dependency, error, parent=package%name) if (allocated(error)) exit end if end do @@ -333,13 +345,15 @@ recursive subroutine add_project_dependencies(self, package, root, main, error) end subroutine add_project_dependencies !> Add a list of dependencies to the dependency tree - subroutine add_dependencies(self, dependency, error) + subroutine add_dependencies(self, dependency, error, parent) !> Instance of the dependency tree class(dependency_tree_t), intent(inout) :: self !> Dependency configuration to add type(dependency_config_t), intent(in) :: dependency(:) !> Error handling type(error_t), allocatable, intent(out) :: error + !> Name of parent package + character(len=*), intent(in), optional :: parent integer :: ii, ndep @@ -349,7 +363,7 @@ subroutine add_dependencies(self, dependency, error) end if do ii = 1, size(dependency) - call self%add(dependency(ii), error) + call self%add(dependency(ii), error, parent=parent) if (allocated(error)) exit end do if (allocated(error)) return @@ -357,20 +371,41 @@ subroutine add_dependencies(self, dependency, error) end subroutine add_dependencies !> Add a single dependency to the dependency tree - pure subroutine add_dependency(self, dependency, error) +! pure subroutine add_dependency(self, dependency, error, parent) + subroutine add_dependency(self, dependency, error, parent) !> Instance of the dependency tree class(dependency_tree_t), intent(inout) :: self !> Dependency configuration to add type(dependency_config_t), intent(in) :: dependency !> Error handling type(error_t), allocatable, intent(out) :: error + !> Name of parent package + character(len=*), intent(in), optional :: parent - integer :: id + integer :: id, i, parent_id + logical :: found + parent_id = 0 + found = .false. + if (present(parent)) then + parent_id = self%find(parent) + if (parent_id < 1) call fpm_stop(1,'*add_dependency*:Error: No such package in dependency tree.') + end if id = self%find(dependency) if (id == 0) then self%ndep = self%ndep + 1 - call new_dependency_node(self%dep(self%ndep), dependency) + if (parent_id > 0) then + call new_dependency_node(self%dep(self%ndep), dependency, parent=parent_id) + else + call new_dependency_node(self%dep(self%ndep), dependency) + end if + else if (present(parent) .and. allocated(self%dep(id)%parent)) then + do i=1, size(self%dep(id)%parent) + if (self%dep(id)%parent(i)==parent_id) then + found=.true. + end if + end do + if (.not. found) self%dep(id)%parent = [self%dep(id)%parent, parent_id] end if end subroutine add_dependency @@ -420,18 +455,20 @@ subroutine update_dependency(self, name, error) end subroutine update_dependency !> Resolve all dependencies in the tree - subroutine resolve_dependencies(self, root, error) + subroutine resolve_dependencies(self, root, error, parent) !> Instance of the dependency tree class(dependency_tree_t), intent(inout) :: self !> Current installation prefix character(len=*), intent(in) :: root !> Error handling type(error_t), allocatable, intent(out) :: error + !> Name of parent package + character(len=*), intent(in), optional :: parent integer :: ii do ii = 1, self%ndep - call self%resolve(self%dep(ii), root, error) + call self%resolve(self%dep(ii), root, error, parent=parent) if (allocated(error)) exit end do @@ -440,7 +477,7 @@ subroutine resolve_dependencies(self, root, error) end subroutine resolve_dependencies !> Resolve a single dependency node - subroutine resolve_dependency(self, dependency, root, error) + subroutine resolve_dependency(self, dependency, root, error, parent) !> Instance of the dependency tree class(dependency_tree_t), intent(inout) :: self !> Dependency configuration to add @@ -449,6 +486,8 @@ subroutine resolve_dependency(self, dependency, root, error) character(len=*), intent(in) :: root !> Error handling type(error_t), allocatable, intent(out) :: error + !> Name of parent package + character(len=*), intent(in), optional :: parent type(package_config_t) :: package character(len=:), allocatable :: manifest, proj_dir, revision @@ -491,7 +530,7 @@ subroutine resolve_dependency(self, dependency, root, error) "at", dependency%proj_dir end if - call self%add(package, proj_dir, .false., error) + call self%add(package, proj_dir, .false., error, parent=parent) if (allocated(error)) return end subroutine resolve_dependency @@ -638,11 +677,12 @@ subroutine load_from_toml(self, table, error) !> Error handling type(error_t), allocatable, intent(out) :: error - integer :: ndep, ii + integer :: ndep, ii, ip logical :: unix - character(len=:), allocatable :: version, url, obj, rev, proj_dir + character(len=:), allocatable :: version, url, obj, rev, proj_dir, parent_name type(toml_key), allocatable :: list(:) type(toml_table), pointer :: ptr + type(toml_array), pointer :: p_array call table%get_keys(list) @@ -660,6 +700,7 @@ subroutine load_from_toml(self, table, error) call get_value(ptr, "git", url) call get_value(ptr, "obj", obj) call get_value(ptr, "rev", rev) + call get_value(ptr, "parent", p_array, requested=.false.) if (.not.allocated(proj_dir)) cycle self%ndep = self%ndep + 1 associate(dep => self%dep(self%ndep)) @@ -691,6 +732,13 @@ subroutine load_from_toml(self, table, error) else dep%path = proj_dir end if + if (associated(p_array)) then + allocate(dep%parent(len(p_array))) + do ip = 1, len(p_array) + call get_value(p_array, ip, parent_name) + dep%parent(ip) = self%find(parent_name) + end do + end if end associate end do if (allocated(error)) return @@ -746,12 +794,13 @@ subroutine dump_to_toml(self, table, error) !> Error handling type(error_t), allocatable, intent(out) :: error - integer :: ii + integer :: ii, ip type(toml_table), pointer :: ptr + type(toml_array), pointer :: parent_ptr character(len=:), allocatable :: proj_dir do ii = 1, self%ndep - associate(dep => self%dep(ii)) + associate(dep => self%dep(ii), deps => self%dep) call add_table(table, dep%name, ptr) if (.not.associated(ptr)) then call fatal_error(error, "Cannot create entry for "//dep%name) @@ -771,6 +820,12 @@ subroutine dump_to_toml(self, table, error) call set_value(ptr, "rev", dep%revision) end if end if + if (allocated(dep%parent) .and. size(dep%parent) > 0) then + call add_array(ptr, "parent", parent_ptr) + do ip=1,size(dep%parent) + call set_value(parent_ptr, ip, self%dep(dep%parent(ip))%name) + end do + end if end associate end do if (allocated(error)) return diff --git a/src/fpm/manifest.f90 b/src/fpm/manifest.f90 index 8c39aa6915..0874460997 100644 --- a/src/fpm/manifest.f90 +++ b/src/fpm/manifest.f90 @@ -88,7 +88,7 @@ end subroutine default_test !> Obtain package meta data from a configuation file - subroutine get_package_data(package, file, error, apply_defaults) + subroutine get_package_data(package, file, error, apply_defaults, proj_dir) !> Parsed package meta data type(package_config_t), intent(out) :: package @@ -102,6 +102,9 @@ subroutine get_package_data(package, file, error, apply_defaults) !> Apply package defaults (uses file system operations) logical, intent(in), optional :: apply_defaults + !> Path to project directory of the current package + character(len=*), intent(in), optional :: proj_dir + type(toml_table), allocatable :: table character(len=:), allocatable :: root @@ -113,7 +116,7 @@ subroutine get_package_data(package, file, error, apply_defaults) return end if - call new_package(package, table, dirname(file), error) + call new_package(package, table, dirname(file), error, proj_dir) if (allocated(error)) return if (present(apply_defaults)) then diff --git a/src/fpm/manifest/package.f90 b/src/fpm/manifest/package.f90 index 5cd8765996..326f38f111 100644 --- a/src/fpm/manifest/package.f90 +++ b/src/fpm/manifest/package.f90 @@ -24,6 +24,7 @@ !>[library] !>[dependencies] !>[dev-dependencies] +!>[profiles] !>[build] !>[install] !>[[ executable ]] @@ -34,6 +35,7 @@ module fpm_manifest_package use fpm_manifest_build, only: build_config_t, new_build_config use fpm_manifest_dependency, only : dependency_config_t, new_dependencies + use fpm_manifest_profile, only : profile_config_t, new_profiles, get_default_profiles use fpm_manifest_example, only : example_config_t, new_example use fpm_manifest_executable, only : executable_config_t, new_executable use fpm_manifest_library, only : library_config_t, new_library @@ -44,6 +46,7 @@ module fpm_manifest_package use fpm_toml, only : toml_table, toml_array, toml_key, toml_stat, get_value, & & len use fpm_versioning, only : version_t, new_version + use fpm_filesystem, only: join_path implicit none private @@ -83,6 +86,9 @@ module fpm_manifest_package !> Development dependency meta data type(dependency_config_t), allocatable :: dev_dependency(:) + !> Profiles meta data + type(profile_config_t), allocatable :: profiles(:) + !> Example meta data type(example_config_t), allocatable :: example(:) @@ -101,7 +107,7 @@ module fpm_manifest_package !> Construct a new package configuration from a TOML data structure - subroutine new_package(self, table, root, error) + subroutine new_package(self, table, root, error, proj_dir) !> Instance of the package configuration type(package_config_t), intent(out) :: self @@ -115,13 +121,16 @@ subroutine new_package(self, table, root, error) !> Error handling type(error_t), allocatable, intent(out) :: error + !> Path to project directory of the current package + character(len=*), intent(in), optional :: proj_dir + ! Backspace (8), tabulator (9), newline (10), formfeed (12) and carriage ! return (13) are invalid in package names character(len=*), parameter :: invalid_chars = & achar(8) // achar(9) // achar(10) // achar(12) // achar(13) type(toml_table), pointer :: child, node type(toml_array), pointer :: children - character(len=:), allocatable :: version, version_file + character(len=:), allocatable :: version, version_file, file_scope_path integer :: ii, nn, stat, io call check(table, error) @@ -204,6 +213,17 @@ subroutine new_package(self, table, root, error) call new_library(self%library, child, error) if (allocated(error)) return end if + + call get_value(table, "profiles", child, requested=.false.) + file_scope_path = "" + if (associated(child)) then + if (present(proj_dir)) file_scope_path = proj_dir + call new_profiles(self%profiles, child, error, file_scope_path) + if (allocated(error)) return + else + self%profiles = get_default_profiles(error) + if (allocated(error)) return + end if call get_value(table, "executable", children, requested=.false.) if (associated(children)) then @@ -303,7 +323,7 @@ subroutine check(table, error) case("version", "license", "author", "maintainer", "copyright", & & "description", "keywords", "categories", "homepage", "build", & - & "dependencies", "dev-dependencies", "test", "executable", & + & "dependencies", "dev-dependencies", "profiles", "test", "executable", & & "example", "library", "install", "extra") continue @@ -400,6 +420,15 @@ subroutine info(self, unit, verbosity) call self%dev_dependency(ii)%info(unit, pr - 1) end do end if + + if (allocated(self%profiles)) then + if (size(self%profiles) > 1 .or. pr > 2) then + write(unit, fmti) "- profiles", size(self%profiles) + end if + do ii = 1, size(self%profiles) + call self%profiles(ii)%info(unit, pr - 1) + end do + end if end subroutine info diff --git a/src/fpm/manifest/profiles.f90 b/src/fpm/manifest/profiles.f90 new file mode 100644 index 0000000000..10c3281ed7 --- /dev/null +++ b/src/fpm/manifest/profiles.f90 @@ -0,0 +1,885 @@ +!> Implementation of the meta data for compiler flag profiles. +!> +!> A profiles table can currently have the following subtables: +!> Profile names - any string, if omitted, flags are appended to all matching profiles +!> Compiler - any from the following list, omitting it yields an error +!> - "gfortran" +!> - "ifort" +!> - "ifx" +!> - "pgfortran" +!> - "nvfortran" +!> - "flang" +!> - "caf" +!> - "f95" +!> - "lfortran" +!> - "lfc" +!> - "nagfor" +!> - "crayftn" +!> - "xlf90" +!> - "ftn95" +!> OS - any from the following list, if omitted, the profile is used if and only +!> if there is no profile perfectly matching the current configuration +!> - "linux" +!> - "macos" +!> - "windows" +!> - "cygwin" +!> - "solaris" +!> - "freebsd" +!> - "openbsd" +!> - "unknown" +!> +!> Each of the subtables currently supports the following fields: +!>```toml +!>[profile.debug.gfortran.linux] +!> flags="-Wall -g -Og" +!> c-flags="-g O1" +!> link-time-flags="-xlinkopt" +!> files={"hello_world.f90"="-Wall -O3"} +!>``` +!> +module fpm_manifest_profile + use fpm_error, only : error_t, syntax_error, fatal_error, fpm_stop + use fpm_toml, only : toml_table, toml_key, toml_stat, get_value + use fpm_strings, only: lower + use fpm_environment, only: get_os_type, OS_UNKNOWN, OS_LINUX, OS_MACOS, OS_WINDOWS, & + OS_CYGWIN, OS_SOLARIS, OS_FREEBSD, OS_OPENBSD + use fpm_filesystem, only: join_path + implicit none + public :: profile_config_t, new_profile, new_profiles, get_default_profiles, & + & info_profile, find_profile, DEFAULT_COMPILER + + !> Name of the default compiler + character(len=*), parameter :: DEFAULT_COMPILER = 'gfortran' + integer, parameter :: OS_ALL = -1 + character(len=:), allocatable :: path + + !> Type storing file name - file scope compiler flags pairs + type :: file_scope_flag + + !> Name of the file + character(len=:), allocatable :: file_name + + !> File scope flags + character(len=:), allocatable :: flags + + end type file_scope_flag + + !> Configuration meta data for a profile + type :: profile_config_t + !> Name of the profile + character(len=:), allocatable :: profile_name + + !> Name of the compiler + character(len=:), allocatable :: compiler + + !> Value repesenting OS + integer :: os_type + + !> Fortran compiler flags + character(len=:), allocatable :: flags + + !> C compiler flags + character(len=:), allocatable :: c_flags + + !> Link time compiler flags + character(len=:), allocatable :: link_time_flags + + !> File scope flags + type(file_scope_flag), allocatable :: file_scope_flags(:) + + !> Is this profile one of the built-in ones? + logical :: is_built_in + + contains + + !> Print information on this instance + procedure :: info + + end type profile_config_t + + contains + + !> Construct a new profile configuration from a TOML data structure + function new_profile(profile_name, compiler, os_type, flags, c_flags, link_time_flags, file_scope_flags, is_built_in) & + & result(profile) + + !> Name of the profile + character(len=*), intent(in) :: profile_name + + !> Name of the compiler + character(len=*), intent(in) :: compiler + + !> Type of the OS + integer, intent(in) :: os_type + + !> Fortran compiler flags + character(len=*), optional, intent(in) :: flags + + !> C compiler flags + character(len=*), optional, intent(in) :: c_flags + + !> Link time compiler flags + character(len=*), optional, intent(in) :: link_time_flags + + !> File scope flags + type(file_scope_flag), optional, intent(in) :: file_scope_flags(:) + + !> Is this profile one of the built-in ones? + logical, optional, intent(in) :: is_built_in + + type(profile_config_t) :: profile + + profile%profile_name = profile_name + profile%compiler = compiler + profile%os_type = os_type + if (present(flags)) then + profile%flags = flags + else + profile%flags = "" + end if + if (present(c_flags)) then + profile%c_flags = c_flags + else + profile%c_flags = "" + end if + if (present(link_time_flags)) then + profile%link_time_flags = link_time_flags + else + profile%link_time_flags = "" + end if + if (present(file_scope_flags)) then + profile%file_scope_flags = file_scope_flags + end if + if (present(is_built_in)) then + profile%is_built_in = is_built_in + else + profile%is_built_in = .false. + end if + + end function new_profile + + !> Check if compiler name is a valid compiler name + subroutine validate_compiler_name(compiler_name, is_valid) + + !> Name of a compiler + character(len=:), allocatable, intent(in) :: compiler_name + + !> Boolean value of whether compiler_name is valid or not + logical, intent(out) :: is_valid + select case(compiler_name) + case("gfortran", "ifort", "ifx", "pgfortran", "nvfortran", "flang", "caf", & + & "f95", "lfortran", "lfc", "nagfor", "crayftn", "xlf90", "ftn95") + is_valid = .true. + case default + is_valid = .false. + end select + end subroutine validate_compiler_name + + !> Check if os_name is a valid name of a supported OS + subroutine validate_os_name(os_name, is_valid) + + !> Name of an operating system + character(len=:), allocatable, intent(in) :: os_name + + !> Boolean value of whether os_name is valid or not + logical, intent(out) :: is_valid + + select case (os_name) + case ("linux", "macos", "windows", "cygwin", "solaris", "freebsd", & + & "openbsd", "unknown") + is_valid = .true. + case default + is_valid = .false. + end select + + end subroutine validate_os_name + + !> Match os_type enum to a lowercase string with name of OS + subroutine match_os_type(os_name, os_type) + + !> Name of operating system + character(len=:), allocatable, intent(in) :: os_name + + !> Enum representing type of OS + integer, intent(out) :: os_type + + select case (os_name) + case ("linux"); os_type = OS_LINUX + case ("macos"); os_type = OS_WINDOWS + case ("cygwin"); os_type = OS_CYGWIN + case ("solaris"); os_type = OS_SOLARIS + case ("freebsd"); os_type = OS_FREEBSD + case ("openbsd"); os_type = OS_OPENBSD + case ("all"); os_type = OS_ALL + case default; os_type = OS_UNKNOWN + end select + + end subroutine match_os_type + + subroutine validate_profile_table(profile_name, compiler_name, key_list, table, error, os_valid) + + !> Name of profile + character(len=:), allocatable, intent(in) :: profile_name + + !> Name of compiler + character(len=:), allocatable, intent(in) :: compiler_name + + !> List of keys in the table + type(toml_key), allocatable, intent(in) :: key_list(:) + + !> Table containing OS tables + type(toml_table), pointer, intent(in) :: table + + !> Error handling + type(error_t), allocatable, intent(out) :: error + + !> Was called with valid operating system + logical, intent(in) :: os_valid + + character(len=:), allocatable :: flags, c_flags, link_time_flags, key_name, file_name, file_flags, err_message + type(toml_table), pointer :: files + type(toml_key), allocatable :: file_list(:) + integer :: ikey, ifile, stat + logical :: is_valid + + if (size(key_list).ge.1) then + do ikey=1,size(key_list) + key_name = key_list(ikey)%key + if (key_name.eq.'flags') then + call get_value(table, 'flags', flags, stat=stat) + if (stat /= toml_stat%success) then + call syntax_error(error, "flags has to be a key-value pair") + return + end if + else if (key_name.eq.'c-flags') then + call get_value(table, 'c-flags', c_flags, stat=stat) + if (stat /= toml_stat%success) then + call syntax_error(error, "c-flags has to be a key-value pair") + return + end if + else if (key_name.eq.'link-time-flags') then + call get_value(table, 'link-time-flags', link_time_flags, stat=stat) + if (stat /= toml_stat%success) then + call syntax_error(error, "link-time-flags has to be a key-value pair") + return + end if + else if (key_name.eq.'files') then + call get_value(table, 'files', files, stat=stat) + if (stat /= toml_stat%success) then + call syntax_error(error, "files has to be a table") + return + end if + call files%get_keys(file_list) + do ifile=1,size(file_list) + file_name = file_list(ifile)%key + call get_value(files, file_name, file_flags, stat=stat) + if (stat /= toml_stat%success) then + call syntax_error(error, "file scope flags has to be a key-value pair") + return + end if + end do + else if (.not. os_valid) then + call validate_os_name(key_name, is_valid) + err_message = "Unnexpected key " // key_name // " found in profile table "//profile_name//" "//compiler_name//"." + if (.not. is_valid) call syntax_error(error, err_message) + else + err_message = "Unnexpected key " // key_name // " found in profile table "//profile_name//" "//compiler_name//"." + call syntax_error(error, err_message) + end if + end do + end if + + if (allocated(error)) return + + end subroutine validate_profile_table + + !> Look for flags, c-flags, link-time-flags key-val pairs + !> and files table in a given table and create new profiles + subroutine get_flags(profile_name, compiler_name, os_type, key_list, table, profiles, profindex, os_valid) + + !> Name of profile + character(len=:), allocatable, intent(in) :: profile_name + + !> Name of compiler + character(len=:), allocatable, intent(in) :: compiler_name + + !> OS type + integer, intent(in) :: os_type + + !> List of keys in the table + type(toml_key), allocatable, intent(in) :: key_list(:) + + !> Table containing OS tables + type(toml_table), pointer, intent(in) :: table + + !> List of profiles + type(profile_config_t), allocatable, intent(inout) :: profiles(:) + + !> Index in the list of profiles + integer, intent(inout) :: profindex + + !> Was called with valid operating system + logical, intent(in) :: os_valid + + character(len=:), allocatable :: flags, c_flags, link_time_flags, key_name, file_name, file_flags, err_message + type(toml_table), pointer :: files + type(toml_key), allocatable :: file_list(:) + type(file_scope_flag), allocatable :: file_scope_flags(:) + integer :: ikey, ifile, stat + logical :: is_valid + + call get_value(table, 'flags', flags) + call get_value(table, 'c-flags', c_flags) + call get_value(table, 'link-time-flags', link_time_flags) + call get_value(table, 'files', files) + if (associated(files)) then + call files%get_keys(file_list) + allocate(file_scope_flags(size(file_list))) + do ifile=1,size(file_list) + file_name = file_list(ifile)%key + call get_value(files, file_name, file_flags) + associate(cur_file=>file_scope_flags(ifile)) + if (.not.(path.eq."")) file_name = join_path(path, file_name) + cur_file%file_name = file_name + cur_file%flags = file_flags + end associate + end do + end if + + profiles(profindex) = new_profile(profile_name, compiler_name, os_type, & + & flags, c_flags, link_time_flags, file_scope_flags) + profindex = profindex + 1 + end subroutine get_flags + + !> Traverse operating system tables + subroutine traverse_oss(profile_name, compiler_name, os_list, table, error, profiles_size, profiles, profindex) + + !> Name of profile + character(len=:), allocatable, intent(in) :: profile_name + + !> Name of compiler + character(len=:), allocatable, intent(in) :: compiler_name + + !> List of OSs in table with profile name and compiler name given + type(toml_key), allocatable, intent(in) :: os_list(:) + + !> Table containing OS tables + type(toml_table), pointer, intent(in) :: table + + !> Error handling + type(error_t), allocatable, intent(out) :: error + + !> Number of profiles in list of profiles + integer, intent(inout), optional :: profiles_size + + !> List of profiles + type(profile_config_t), allocatable, intent(inout), optional :: profiles(:) + + !> Index in the list of profiles + integer, intent(inout), optional :: profindex + + type(toml_key), allocatable :: key_list(:) + character(len=:), allocatable :: os_name, l_os_name + type(toml_table), pointer :: os_node + character(len=:), allocatable :: flags + integer :: ios, stat, os_type + logical :: is_valid, key_val_added, is_key_val + + if (size(os_list)<1) return + key_val_added = .false. + do ios = 1, size(os_list) + os_name = os_list(ios)%key + call validate_os_name(os_name, is_valid) + if (is_valid) then + call get_value(table, os_name, os_node, stat=stat) + if (stat /= toml_stat%success) then + call syntax_error(error, "os "//os_name//" has to be a table") + return + end if + call os_node%get_keys(key_list) + if (present(profiles_size)) then + profiles_size = profiles_size + 1 + call validate_profile_table(profile_name, compiler_name, key_list, os_node, error, .true.) + else + if (.not.(present(profiles).and.present(profindex))) then + call fatal_error(error, "Both profiles and profindex have to be present") + return + end if + call match_os_type(os_name, os_type) + call get_flags(profile_name, compiler_name, os_type, key_list, os_node, profiles, profindex, .true.) + end if + else + ! Not lowercase OS name + l_os_name = lower(os_name) + call validate_os_name(l_os_name, is_valid) + if (is_valid) then + call fatal_error(error,'*traverse_oss*:Error: Invalid OS name.') + end if + if (allocated(error)) return + + ! Missing OS name + is_key_val = .false. + os_name = os_list(ios)%key + call get_value(table, os_name, os_node, stat=stat) + if (stat /= toml_stat%success) then + is_key_val = .true. + end if + os_node=>table + if (present(profiles_size)) then + if (is_key_val.and..not.key_val_added) then + key_val_added = .true. + is_key_val = .false. + profiles_size = profiles_size + 1 + else if (.not.is_key_val) then + profiles_size = profiles_size + 1 + end if + call validate_profile_table(profile_name, compiler_name, os_list, os_node, error, .false.) + else + if (.not.(present(profiles).and.present(profindex))) then + call fatal_error(error, "Both profiles and profindex have to be present") + return + end if + os_type = OS_ALL + call get_flags(profile_name, compiler_name, os_type, os_list, os_node, profiles, profindex, .false.) + end if + end if + end do + end subroutine traverse_oss + + !> Traverse compiler tables + subroutine traverse_compilers(profile_name, comp_list, table, error, profiles_size, profiles, profindex) + + !> Name of profile + character(len=:), allocatable, intent(in) :: profile_name + + !> List of OSs in table with profile name given + type(toml_key), allocatable, intent(in) :: comp_list(:) + + !> Table containing compiler tables + type(toml_table), pointer, intent(in) :: table + + !> Error handling + type(error_t), allocatable, intent(out) :: error + + !> Number of profiles in list of profiles + integer, intent(inout), optional :: profiles_size + + !> List of profiles + type(profile_config_t), allocatable, intent(inout), optional :: profiles(:) + + !> Index in the list of profiles + integer, intent(inout), optional :: profindex + + character(len=:), allocatable :: compiler_name + type(toml_table), pointer :: comp_node + type(toml_key), allocatable :: os_list(:) + integer :: icomp, stat + logical :: is_valid + + if (size(comp_list)<1) return + do icomp = 1, size(comp_list) + call validate_compiler_name(comp_list(icomp)%key, is_valid) + if (is_valid) then + compiler_name = comp_list(icomp)%key + call get_value(table, compiler_name, comp_node, stat=stat) + if (stat /= toml_stat%success) then + call syntax_error(error, "Compiler "//comp_list(icomp)%key//" must be a table entry") + exit + end if + call comp_node%get_keys(os_list) + if (present(profiles_size)) then + call traverse_oss(profile_name, compiler_name, os_list, comp_node, error, profiles_size=profiles_size) + if (allocated(error)) return + else + if (.not.(present(profiles).and.present(profindex))) then + call fatal_error(error, "Both profiles and profindex have to be present") + return + end if + call traverse_oss(profile_name, compiler_name, os_list, comp_node, & + & error, profiles=profiles, profindex=profindex) + if (allocated(error)) return + end if + else + call fatal_error(error,'*traverse_compilers*:Error: Compiler name not specified or invalid.') + end if + end do + end subroutine traverse_compilers + + !> Construct new profiles array from a TOML data structure + subroutine new_profiles(profiles, table, error, file_scope_path) + + !> Instance of the dependency configuration + type(profile_config_t), allocatable, intent(out) :: profiles(:) + + !> Instance of the TOML data structure + type(toml_table), target, intent(inout) :: table + + !> Error handling + type(error_t), allocatable, intent(out) :: error + + !> Path to project directory of the current package + character(len=*), intent(in), optional :: file_scope_path + + type(toml_table), pointer :: prof_node + type(toml_key), allocatable :: prof_list(:) + type(toml_key), allocatable :: comp_list(:) + type(toml_key), allocatable :: os_list(:) + character(len=:), allocatable :: profile_name, compiler_name + integer :: profiles_size, iprof, stat, profindex + logical :: is_valid + type(profile_config_t), allocatable :: default_profiles(:) + + if (present(file_scope_path)) then + path = file_scope_path + else + path = '' + end if + default_profiles = get_default_profiles(error) + if (allocated(error)) return + call table%get_keys(prof_list) + + if (size(prof_list) < 1) return + + profiles_size = 0 + + do iprof = 1, size(prof_list) + profile_name = prof_list(iprof)%key + call validate_compiler_name(profile_name, is_valid) + if (is_valid) then + profile_name = "all" + comp_list = prof_list(iprof:iprof) + prof_node=>table + call traverse_compilers(profile_name, comp_list, prof_node, error, profiles_size=profiles_size) + if (allocated(error)) return + else + call validate_os_name(profile_name, is_valid) + if (is_valid) then + os_list = prof_list(iprof:iprof) + profile_name = 'all' + compiler_name = DEFAULT_COMPILER + call traverse_oss(profile_name, compiler_name, os_list, table, error, profiles_size=profiles_size) + if (allocated(error)) return + else + call get_value(table, profile_name, prof_node, stat=stat) + if (stat /= toml_stat%success) then + call syntax_error(error, "Profile "//prof_list(iprof)%key//" must be a table entry") + exit + end if + call prof_node%get_keys(comp_list) + call traverse_compilers(profile_name, comp_list, prof_node, error, profiles_size=profiles_size) + if (allocated(error)) return + end if + end if + end do + + profiles_size=profiles_size+size(default_profiles) + allocate(profiles(profiles_size)) + + do profindex=1, size(default_profiles) + profiles(profindex) = default_profiles(profindex) + end do + + do iprof = 1, size(prof_list) + profile_name = prof_list(iprof)%key + call validate_compiler_name(profile_name, is_valid) + if (is_valid) then + profile_name = "all" + comp_list = prof_list(iprof:iprof) + prof_node=>table + call traverse_compilers(profile_name, comp_list, prof_node, error, profiles=profiles, profindex=profindex) + if (allocated(error)) return + else + call validate_os_name(profile_name, is_valid) + if (is_valid) then + os_list = prof_list(iprof:iprof) + profile_name = 'all' + compiler_name = DEFAULT_COMPILER + prof_node=>table + call traverse_oss(profile_name, compiler_name, os_list, prof_node, error, profiles=profiles, profindex=profindex) + if (allocated(error)) return + else + call get_value(table, profile_name, prof_node, stat=stat) + call prof_node%get_keys(comp_list) + call traverse_compilers(profile_name, comp_list, prof_node, error, profiles=profiles, profindex=profindex) + if (allocated(error)) return + end if + end if + end do + + ! Apply profiles with profile name 'all' to matching profiles + do iprof = 1,size(profiles) + if (profiles(iprof)%profile_name.eq.'all') then + do profindex = 1,size(profiles) + if (.not.(profiles(profindex)%profile_name.eq.'all') & + & .and.(profiles(profindex)%compiler.eq.profiles(iprof)%compiler) & + & .and.(profiles(profindex)%os_type.eq.profiles(iprof)%os_type)) then + profiles(profindex)%flags=profiles(profindex)%flags// & + & " "//profiles(iprof)%flags + profiles(profindex)%c_flags=profiles(profindex)%c_flags// & + & " "//profiles(iprof)%c_flags + profiles(profindex)%link_time_flags=profiles(profindex)%link_time_flags// & + & " "//profiles(iprof)%link_time_flags + end if + end do + end if + end do + end subroutine new_profiles + + !> Construct an array of built-in profiles + function get_default_profiles(error) result(default_profiles) + + !> Error handling + type(error_t), allocatable, intent(out) :: error + + type(profile_config_t), allocatable :: default_profiles(:) + + default_profiles = [ & + & new_profile('release', & + & 'caf', & + & OS_ALL, & + & flags=' -O3 -Wimplicit-interface -fPIC -fmax-errors=1 -funroll-loops', & + & is_built_in=.true.), & + & new_profile('release', & + & 'gfortran', & + & OS_ALL, & + & flags=' -O3 -Wimplicit-interface -fPIC -fmax-errors=1 -funroll-loops -fcoarray=single', & + & is_built_in=.true.), & + & new_profile('release', & + & 'f95', & + & OS_ALL, & + & flags=' -O3 -Wimplicit-interface -fPIC -fmax-errors=1 -ffast-math -funroll-loops', & + & is_built_in=.true.), & + & new_profile('release', & + & 'nvfortran', & + & OS_ALL, & + & flags = ' -Mbackslash', & + & is_built_in=.true.), & + & new_profile('release', & + & 'ifort', & + & OS_ALL, & + & flags = ' -fp-model precise -pc64 -align all -error-limit 1 -reentrancy& + & threaded -nogen-interfaces -assume byterecl', & + & is_built_in=.true.), & + & new_profile('release', & + & 'ifort', & + & OS_WINDOWS, & + & flags = ' /fp:precise /align:all /error-limit:1 /reentrancy:threaded& + & /nogen-interfaces /assume:byterecl', & + & is_built_in=.true.), & + & new_profile('release', & + & 'ifx', & + & OS_ALL, & + & flags = ' -fp-model=precise -pc64 -align all -error-limit 1 -reentrancy& + & threaded -nogen-interfaces -assume byterecl', & + & is_built_in=.true.), & + & new_profile('release', & + & 'ifx', & + & OS_WINDOWS, & + & flags = ' /fp:precise /align:all /error-limit:1 /reentrancy:threaded& + & /nogen-interfaces /assume:byterecl', & + & is_built_in=.true.), & + & new_profile('release', & + &'nagfor', & + & OS_ALL, & + & flags = ' -O4 -coarray=single -PIC', & + & is_built_in=.true.), & + & new_profile('debug', & + & 'caf', & + & OS_ALL, & + & flags = ' -Wall -Wextra -Wimplicit-interface -fPIC -fmax-errors=1 -g -fcheck=bounds& + & -fcheck=array-temps -fbacktrace', & + & is_built_in=.true.), & + & new_profile('debug', & + & 'gfortran', & + & OS_ALL, & + & flags = ' -Wall -Wextra -Wimplicit-interface -fPIC -fmax-errors=1 -g -fcheck=bounds& + & -fcheck=array-temps -fbacktrace -fcoarray=single', & + & is_built_in=.true.), & + & new_profile('debug', & + & 'f95', & + & OS_ALL, & + & flags = ' -Wall -Wextra -Wimplicit-interface -fPIC -fmax-errors=1 -g -fcheck=bounds& + & -fcheck=array-temps -Wno-maybe-uninitialized -Wno-uninitialized -fbacktrace', & + & is_built_in=.true.), & + & new_profile('debug', & + & 'nvfortran', & + & OS_ALL, & + & flags = ' -Minform=inform -Mbackslash -g -Mbounds -Mchkptr -Mchkstk -traceback', & + & is_built_in=.true.), & + & new_profile('debug', & + & 'ifort', & + & OS_ALL, & + & flags = ' -warn all -check all -error-limit 1 -O0 -g -assume byterecl -traceback', & + & is_built_in=.true.), & + & new_profile('debug', & + & 'ifort', & + & OS_WINDOWS, & + & flags = ' /warn:all /check:all /error-limit:1& + & /Od /Z7 /assume:byterecl /traceback', & + & is_built_in=.true.), & + & new_profile('debug', & + & 'ifx', & + & OS_ALL, & + & flags = ' -warn all -check all -error-limit 1 -O0 -g -assume byterecl -traceback', & + & is_built_in=.true.), & + & new_profile('debug', & + & 'ifx', & + & OS_WINDOWS, & + & flags = ' /warn:all /check:all /error-limit:1 /Od /Z7 /assume:byterecl', & + & is_built_in=.true.), & + & new_profile('debug', & + & 'nagfor', & + & OS_ALL, & + & flags = ' -g -C=all -O0 -gline -coarray=single -PIC', & + & is_built_in=.true.) & + &] + end function get_default_profiles + + !> Write information on instance + subroutine info(self, unit, verbosity) + + !> Instance of the profile configuration + class(profile_config_t), intent(in) :: self + + !> Unit for IO + integer, intent(in) :: unit + + !> Verbosity of the printout + integer, intent(in), optional :: verbosity + + integer :: pr + character(len=*), parameter :: fmt = '("#", 1x, a, t30, a)' + + if (present(verbosity)) then + pr = verbosity + else + pr = 1 + end if + + write(unit, fmt) "Profile" + if (allocated(self%profile_name)) then + write(unit, fmt) "- profile name", self%profile_name + end if + + if (allocated(self%compiler)) then + write(unit, fmt) "- compiler", self%compiler + end if + + write(unit, fmt) "- os", self%os_type + + if (allocated(self%flags)) then + write(unit, fmt) "- compiler flags", self%flags + end if + + end subroutine info + + !> Print a representation of profile_config_t + function info_profile(profile) result(s) + + !> Profile to be represented + type(profile_config_t), intent(in) :: profile + + !> String representation of given profile + character(:), allocatable :: s + + integer :: i + + s = "profile_config_t(" + s = s // 'profile_name="' // profile%profile_name // '"' + s = s // ', compiler="' // profile%compiler // '"' + s = s // ", os_type=" + select case(profile%os_type) + case (OS_UNKNOWN) + s = s // "OS_UNKNOWN" + case (OS_LINUX) + s = s // "OS_LINUX" + case (OS_MACOS) + s = s // "OS_MACOS" + case (OS_WINDOWS) + s = s // "OS_WINDOWS" + case (OS_CYGWIN) + s = s // "OS_CYGWIN" + case (OS_SOLARIS) + s = s // "OS_SOLARIS" + case (OS_FREEBSD) + s = s // "OS_FREEBSD" + case (OS_OPENBSD) + s = s // "OS_OPENBSD" + case (OS_ALL) + s = s // "OS_ALL" + case default + s = s // "INVALID" + end select + if (allocated(profile%flags)) s = s // ', flags="' // profile%flags // '"' + if (allocated(profile%c_flags)) s = s // ', c_flags="' // profile%c_flags // '"' + if (allocated(profile%link_time_flags)) s = s // ', link_time_flags="' // profile%link_time_flags // '"' + if (allocated(profile%file_scope_flags)) then + do i=1,size(profile%file_scope_flags) + s = s // ', flags for '//profile%file_scope_flags(i)%file_name// & + & ' ="' // profile%file_scope_flags(i)%flags // '"' + end do + end if + s = s // ")" + + end function info_profile + + !> Look for profile with given configuration in array profiles + subroutine find_profile(profiles, profile_name, compiler, os_type, found_matching, chosen_profile) + + !> Array of profiles + type(profile_config_t), allocatable, intent(in) :: profiles(:) + + !> Name of profile + character(:), allocatable, intent(in) :: profile_name + + !> Name of compiler + character(:), allocatable, intent(in) :: compiler + + !> Type of operating system (enum) + integer, intent(in) :: os_type + + !> Boolean value containing true if matching profile was found + logical, intent(out) :: found_matching + + !> Last matching profile in the profiles array + type(profile_config_t), intent(out) :: chosen_profile + + character(:), allocatable :: curr_profile_name + character(:), allocatable :: curr_compiler + integer :: curr_os + integer :: i, priority, curr_priority + + found_matching = .false. + if (size(profiles) < 1) return + ! Try to find profile with matching OS type + do i=1,size(profiles) + curr_profile_name = profiles(i)%profile_name + curr_compiler = profiles(i)%compiler + curr_os = profiles(i)%os_type + if (curr_profile_name.eq.profile_name) then + if (curr_compiler.eq.compiler) then + if (curr_os.eq.os_type) then + chosen_profile = profiles(i) + found_matching = .true. + end if + end if + end if + end do + ! Try to find profile with OS type 'all' + if (.not. found_matching) then + do i=1,size(profiles) + curr_profile_name = profiles(i)%profile_name + curr_compiler = profiles(i)%compiler + curr_os = profiles(i)%os_type + if (curr_profile_name.eq.profile_name) then + if (curr_compiler.eq.compiler) then + if (curr_os.eq.OS_ALL) then + chosen_profile = profiles(i) + found_matching = .true. + end if + end if + end if + end do + end if + end subroutine find_profile +end module fpm_manifest_profile diff --git a/src/fpm_backend.f90 b/src/fpm_backend.f90 index 4d0c70943b..1422809955 100644 --- a/src/fpm_backend.f90 +++ b/src/fpm_backend.f90 @@ -44,20 +44,16 @@ module fpm_backend contains !> Top-level routine to build package described by `model` -subroutine build_package(targets,model) +subroutine build_package(targets,model,build_dirs) type(build_target_ptr), intent(inout) :: targets(:) type(fpm_model_t), intent(in) :: model + type(string_t), allocatable, intent(in), optional :: build_dirs(:) integer :: i, j type(build_target_ptr), allocatable :: queue(:) integer, allocatable :: schedule_ptr(:), stat(:) logical :: build_failed, skip_current - ! Need to make output directory for include (mod) files - if (.not.exists(join_path(model%output_directory,model%package_name))) then - call mkdir(join_path(model%output_directory,model%package_name)) - end if - ! Perform depth-first topological sort of targets do i=1,size(targets) @@ -68,6 +64,19 @@ subroutine build_package(targets,model) ! Construct build schedule queue call schedule_targets(queue, schedule_ptr, targets) + ! Create all build directories + if (allocated(model%include_dirs)) then + do i=1,size(model%include_dirs) + call mkdir(model%include_dirs(i)%s) + end do + end if + if (present(build_dirs)) then + if (allocated(build_dirs)) then + do i=1,size(build_dirs) + call mkdir(build_dirs(i)%s) + end do + end if + end if ! Initialise build status flags allocate(stat(size(queue))) stat(:) = 0 diff --git a/src/fpm_command_line.f90 b/src/fpm_command_line.f90 index 959a13f2b6..9f921d6ee2 100644 --- a/src/fpm_command_line.f90 +++ b/src/fpm_command_line.f90 @@ -70,7 +70,6 @@ module fpm_command_line logical :: show_model=.false. character(len=:),allocatable :: compiler character(len=:),allocatable :: profile - character(len=:),allocatable :: build_name character(len=:),allocatable :: flag end type @@ -199,7 +198,6 @@ subroutine get_command_line_settings(cmd_settings) if(specified('runner') .and. val_runner.eq.'')val_runner='echo' cmd_settings=fpm_run_settings(& & args=remaining,& - & build_name=val_build,& & profile=val_profile,& & compiler=val_compiler, & & flag=val_flag, & @@ -223,7 +221,6 @@ subroutine get_command_line_settings(cmd_settings) allocate( fpm_build_settings :: cmd_settings ) cmd_settings=fpm_build_settings( & - & build_name=val_build,& & profile=val_profile,& & compiler=val_compiler, & & flag=val_flag, & @@ -361,7 +358,6 @@ subroutine get_command_line_settings(cmd_settings) allocate(install_settings) install_settings = fpm_install_settings(& list=lget('list'), & - build_name=val_build, & profile=val_profile,& compiler=val_compiler, & flag=val_flag, & @@ -417,7 +413,6 @@ subroutine get_command_line_settings(cmd_settings) if(specified('runner') .and. val_runner.eq.'')val_runner='echo' cmd_settings=fpm_test_settings(& & args=remaining, & - & build_name=val_build, & & profile=val_profile, & & compiler=val_compiler, & & flag=val_flag, & @@ -478,7 +473,6 @@ subroutine get_command_line_settings(cmd_settings) contains subroutine check_build_vals() - character(len=:), allocatable :: flags val_compiler=sget('compiler') if(val_compiler.eq.'') then @@ -487,17 +481,6 @@ subroutine check_build_vals() val_flag = " " // sget('flag') val_profile = sget('profile') - if (val_flag == '') then - call get_default_compile_flags(val_compiler, val_profile == "release", val_flag) - else - select case(val_profile) - case("release", "debug") - call get_default_compile_flags(val_compiler, val_profile == "release", flags) - val_flag = flags // val_flag - end select - end if - allocate(character(len=16) :: val_build) - write(val_build, '(z16.16)') fnv_1a(val_flag) end subroutine check_build_vals diff --git a/src/fpm_compiler.f90 b/src/fpm_compiler.f90 index b3e3a56157..ea6954fc73 100644 --- a/src/fpm_compiler.f90 +++ b/src/fpm_compiler.f90 @@ -304,28 +304,28 @@ subroutine get_module_flags(compiler, modpath, flags) select case(id) case default - flags=' -module '//modpath//' -I '//modpath + flags=' -module '//modpath case(id_caf, id_gcc, id_f95, id_cray) - flags=' -J '//modpath//' -I '//modpath + flags=' -J '//modpath case(id_nvhpc, id_pgi, id_flang) - flags=' -module '//modpath//' -I '//modpath + flags=' -module '//modpath case(id_intel_classic_nix, id_intel_classic_mac, id_intel_classic_unknown, id_intel_llvm_nix, id_intel_llvm_unknown) - flags=' -module '//modpath//' -I'//modpath + flags=' -module '//modpath case(id_intel_classic_windows, id_intel_llvm_windows) - flags=' /module:'//modpath//' /I'//modpath + flags=' /module:'//modpath case(id_lahey) - flags=' -M '//modpath//' -I '//modpath + flags=' -M '//modpath case(id_nag) - flags=' -mdir '//modpath//' -I '//modpath ! + flags=' -mdir '//modpath ! case(id_ibmxl) - flags=' -qmoddir '//modpath//' -I '//modpath + flags=' -qmoddir '//modpath end select diff --git a/src/fpm_model.f90 b/src/fpm_model.f90 index 49f598eacb..17411bc7b4 100644 --- a/src/fpm_model.f90 +++ b/src/fpm_model.f90 @@ -21,6 +21,7 @@ module fpm_model use iso_fortran_env, only: int64 use fpm_strings, only: string_t, str use fpm_dependency, only: dependency_tree_t +use fpm_manifest_profile, only: profile_config_t, info_profile implicit none private @@ -86,6 +87,12 @@ module fpm_model !> Native libraries to link against type(string_t), allocatable :: link_libraries(:) + !> Fortran compiler flags + character(len=:), allocatable :: flags + + !> Link time compiler flags + character(len=:), allocatable :: link_time_flags + !> Current hash integer(int64) :: digest @@ -101,6 +108,14 @@ module fpm_model !> Array of sources type(srcfile_t), allocatable :: sources(:) + !> Array of compiler profiles + type(profile_config_t), allocatable :: profiles(:) + + !> Chosen compiler profile + type(profile_config_t) :: chosen_profile + + !> Indices of parent packages + integer, allocatable :: parent(:) end type package_t @@ -123,8 +138,8 @@ module fpm_model !> Command line name to invoke c compiler character(:), allocatable :: c_compiler - !> Command line flags passed to fortran for compilation - character(:), allocatable :: fortran_compile_flags + !> Command line flags passed for compilation + character(:), allocatable :: cmd_compile_flags !> Base directory for build character(:), allocatable :: output_directory @@ -161,6 +176,14 @@ function info_package(p) result(s) if (i < size(p%sources)) s = s // ", " end do s = s // "]" + if (allocated(p%profiles)) then + s = s // ', profiles=[' + do i=1,size(p%profiles) + s = s // info_profile(p%profiles(i)) + if (i < size(p%profiles)) s = s // ", " + end do + s = s // "]" + end if s = s // ")" end function info_package @@ -195,10 +218,12 @@ function info_srcfile(source) result(s) end select ! type(string_t), allocatable :: modules_provided(:) s = s // ", modules_provided=[" - do i = 1, size(source%modules_provided) - s = s // '"' // source%modules_provided(i)%s // '"' - if (i < size(source%modules_provided)) s = s // ", " - end do + if (allocated(source%modules_provided)) then + do i = 1, size(source%modules_provided) + s = s // '"' // source%modules_provided(i)%s // '"' + if (i < size(source%modules_provided)) s = s // ", " + end do + end if s = s // "]" ! integer :: unit_type = FPM_UNIT_UNKNOWN s = s // ", unit_type=" @@ -222,24 +247,30 @@ function info_srcfile(source) result(s) end select ! type(string_t), allocatable :: modules_used(:) s = s // ", modules_used=[" - do i = 1, size(source%modules_used) - s = s // '"' // source%modules_used(i)%s // '"' - if (i < size(source%modules_used)) s = s // ", " - end do + if (allocated(source%modules_used)) then + do i = 1, size(source%modules_used) + s = s // '"' // source%modules_used(i)%s // '"' + if (i < size(source%modules_used)) s = s // ", " + end do + end if s = s // "]" ! type(string_t), allocatable :: include_dependencies(:) s = s // ", include_dependencies=[" - do i = 1, size(source%include_dependencies) - s = s // '"' // source%include_dependencies(i)%s // '"' - if (i < size(source%include_dependencies)) s = s // ", " - end do + if (allocated(source%include_dependencies)) then + do i = 1, size(source%include_dependencies) + s = s // '"' // source%include_dependencies(i)%s // '"' + if (i < size(source%include_dependencies)) s = s // ", " + end do + end if s = s // "]" ! type(string_t), allocatable :: link_libraries(:) s = s // ", link_libraries=[" - do i = 1, size(source%link_libraries) - s = s // '"' // source%link_libraries(i)%s // '"' - if (i < size(source%link_libraries)) s = s // ", " - end do + if (allocated(source%link_libraries)) then + do i = 1, size(source%link_libraries) + s = s // '"' // source%link_libraries(i)%s // '"' + if (i < size(source%link_libraries)) s = s // ", " + end do + end if s = s // "]" ! integer(int64) :: digest s = s // ", digest=" // str(source%digest) @@ -274,7 +305,7 @@ function info_model(model) result(s) ! character(:), allocatable :: fortran_compiler s = s // ', fortran_compiler="' // model%fortran_compiler // '"' ! character(:), allocatable :: fortran_compile_flags - s = s // ', fortran_compile_flags="' // model%fortran_compile_flags // '"' + s = s // ', cmd_compile_flags="' // model%cmd_compile_flags // '"' ! character(:), allocatable :: output_directory s = s // ', output_directory="' // model%output_directory // '"' ! type(string_t), allocatable :: link_libraries(:) diff --git a/src/fpm_targets.f90 b/src/fpm_targets.f90 index d480866847..73d337910b 100644 --- a/src/fpm_targets.f90 +++ b/src/fpm_targets.f90 @@ -28,8 +28,10 @@ module fpm_targets use fpm_error, only: error_t, fatal_error, fpm_stop use fpm_model use fpm_environment, only: get_os_type, OS_WINDOWS -use fpm_filesystem, only: dirname, join_path, canon_path -use fpm_strings, only: string_t, operator(.in.), string_cat +use fpm_filesystem, only: basename, dirname, join_path, canon_path +use fpm_strings, only: string_t, operator(.in.), string_cat, fnv_1a, string_t +use fpm_compiler, only: get_module_flags +use fpm_manifest_profile, only: profile_config_t implicit none private @@ -110,7 +112,7 @@ module fpm_targets contains !> High-level wrapper to generate build target information -subroutine targets_from_sources(targets,model,error) +subroutine targets_from_sources(targets,model,error,build_dirs) !> The generated list of build targets type(build_target_ptr), intent(out), allocatable :: targets(:) @@ -118,15 +120,22 @@ subroutine targets_from_sources(targets,model,error) !> The package model from which to construct the target list type(fpm_model_t), intent(inout), target :: model + !> Include directories from sources + type(string_t), allocatable, intent(out), optional :: build_dirs(:) + !> Error structure type(error_t), intent(out), allocatable :: error - call build_target_list(targets,model) + type(string_t), allocatable :: build_dirs_array(:) + + call build_target_list(targets,model,build_dirs_array) call resolve_module_dependencies(targets,model%external_modules,error) if (allocated(error)) return - call resolve_target_linking(targets,model) + call resolve_target_linking(targets,model,build_dirs_array) + + if (present(build_dirs)) build_dirs = build_dirs_array end subroutine targets_from_sources @@ -150,7 +159,7 @@ end subroutine targets_from_sources !> is a library, then the executable target has an additional dependency on the library !> archive target. !> -subroutine build_target_list(targets,model) +subroutine build_target_list(targets,model, build_dirs) !> The generated list of build targets type(build_target_ptr), intent(out), allocatable :: targets(:) @@ -158,8 +167,12 @@ subroutine build_target_list(targets,model) !> The package model from which to construct the target list type(fpm_model_t), intent(inout), target :: model + !> Include dirs from sources + type(string_t), allocatable, intent(out) :: build_dirs(:) + integer :: i, j, n_source - character(:), allocatable :: xsuffix, exe_dir + character(:), allocatable :: xsuffix, exe_dir, output_file, module_flags + type(build_target_t), pointer :: dep logical :: with_lib ! Check for empty build (e.g. header-only lib) @@ -168,6 +181,7 @@ subroutine build_target_list(targets,model) if (n_source < 1) then allocate(targets(0)) + allocate(build_dirs(0)) return end if @@ -181,23 +195,24 @@ subroutine build_target_list(targets,model) i=1,size(model%packages(j)%sources)), & j=1,size(model%packages))]) - if (with_lib) call add_target(targets,type = FPM_TARGET_ARCHIVE,& - output_file = join_path(model%output_directory,& - model%package_name,'lib'//model%package_name//'.a')) + if (with_lib) then + call get_object_name(output_file, unit_type=FPM_TARGET_ARCHIVE) + call add_target(targets,type = FPM_TARGET_ARCHIVE,& + output_file = output_file) + end if do j=1,size(model%packages) - associate(sources=>model%packages(j)%sources) - do i=1,size(sources) select case (sources(i)%unit_type) case (FPM_UNIT_MODULE,FPM_UNIT_SUBMODULE,FPM_UNIT_SUBPROGRAM,FPM_UNIT_CSOURCE) + call get_object_name(output_file, module_flags=module_flags, source=sources(i)) call add_target(targets,source = sources(i), & type = merge(FPM_TARGET_C_OBJECT,FPM_TARGET_OBJECT,& sources(i)%unit_type==FPM_UNIT_CSOURCE), & - output_file = get_object_name(sources(i))) + output_file = output_file, module_flags = module_flags) if (with_lib .and. sources(i)%unit_scope == FPM_SCOPE_LIB) then ! Archive depends on object @@ -206,29 +221,16 @@ subroutine build_target_list(targets,model) case (FPM_UNIT_PROGRAM) + call get_object_name(output_file, module_flags=module_flags, source=sources(i)) call add_target(targets,type = FPM_TARGET_OBJECT,& - output_file = get_object_name(sources(i)), & - source = sources(i) & - ) - - if (sources(i)%unit_scope == FPM_SCOPE_APP) then - - exe_dir = 'app' - - else if (sources(i)%unit_scope == FPM_SCOPE_EXAMPLE) then - - exe_dir = 'example' - - else - - exe_dir = 'test' - - end if + output_file = output_file, & + source = sources(i), & + module_flags = module_flags) + call get_object_name(output_file, source=sources(i), unit_type=FPM_TARGET_EXECUTABLE) call add_target(targets,type = FPM_TARGET_EXECUTABLE,& link_libraries = sources(i)%link_libraries, & - output_file = join_path(model%output_directory,exe_dir, & - sources(i)%exe_name//xsuffix)) + output_file = output_file) ! Executable depends on object call add_dependency(targets(size(targets))%ptr, targets(size(targets)-1)%ptr) @@ -248,38 +250,98 @@ subroutine build_target_list(targets,model) contains - function get_object_name(source) result(object_file) + subroutine get_object_name(object_file, module_flags, source, unit_type) ! Generate object target path from source name and model params ! ! - type(srcfile_t), intent(in) :: source - character(:), allocatable :: object_file + character(:), allocatable, intent(out) :: object_file + character(:), allocatable, optional, intent(out) :: module_flags + type(srcfile_t), optional, intent(in) :: source + integer, optional, intent(in) :: unit_type integer :: i character(1), parameter :: filesep = '/' + character(:), allocatable :: dir, out_dir, flags_for_archive, exe_dir + character(len=16) :: build_name + + if (.not. present(unit_type) .and. present(source)) then + object_file = canon_path(source%file_name) + + out_dir = get_output_directory(source) - object_file = canon_path(source%file_name) + call get_module_flags(model%fortran_compiler, out_dir, module_flags) - ! Convert any remaining directory separators to underscores - i = index(object_file,filesep) - do while(i > 0) - object_file(i:i) = '_' + ! Convert any remaining directory separators to underscores i = index(object_file,filesep) - end do + do while(i > 0) + object_file(i:i) = '_' + i = index(object_file,filesep) + end do + + object_file = join_path(out_dir,model%package_name, object_file)//'.o' + else + if (unit_type == FPM_TARGET_ARCHIVE) then + + if (allocated(model%packages(1)%profiles)) then + flags_for_archive = model%cmd_compile_flags//" "//model%packages(1)%chosen_profile%flags + else + flags_for_archive = model%cmd_compile_flags + end if - object_file = join_path(model%output_directory,model%package_name,object_file)//'.o' + write(build_name, '(z16.16)') fnv_1a(flags_for_archive) - end function get_object_name + object_file = join_path('build',basename(model%fortran_compiler)//'_'// & + & build_name, model%package_name, 'lib'//model%package_name//'.a') + + else if (unit_type == FPM_TARGET_EXECUTABLE .and. present(source)) then + + if (source%unit_scope == FPM_SCOPE_APP) then + exe_dir = 'app' + else if (source%unit_scope == FPM_SCOPE_EXAMPLE) then + exe_dir = 'example' + else + exe_dir = 'test' + end if + + object_file = join_path(get_output_directory(source),exe_dir, source%exe_name//xsuffix) + end if + + end if + + end subroutine get_object_name + + function get_output_directory(source) result(out_dir) + ! Generate build directory name by hashing the flags of the source + ! + ! + type(srcfile_t), intent(in) :: source + + character(len=16) :: build_name + character(:), allocatable :: out_dir + type(string_t) :: include_dir + + if (allocated(source%flags)) then + write(build_name, '(z16.16)') fnv_1a(source%flags) + end if + out_dir = join_path('build',basename(model%fortran_compiler)//'_'//build_name) + include_dir = string_t(out_dir) + if (.not. allocated(build_dirs)) then + build_dirs = [include_dir] + else if (.not. (out_dir.in.build_dirs)) then + build_dirs = [build_dirs, include_dir] + end if + end function get_output_directory end subroutine build_target_list !> Allocate a new target and append to target list -subroutine add_target(targets,type,output_file,source,link_libraries) +subroutine add_target(targets,type,output_file,source, module_flags, link_libraries) type(build_target_ptr), allocatable, intent(inout) :: targets(:) integer, intent(in) :: type character(*), intent(in) :: output_file type(srcfile_t), intent(in), optional :: source + character(*), intent(in), optional :: module_flags type(string_t), intent(in), optional :: link_libraries(:) integer :: i @@ -304,7 +366,17 @@ subroutine add_target(targets,type,output_file,source,link_libraries) allocate(new_target) new_target%target_type = type new_target%output_file = output_file - if (present(source)) new_target%source = source + if (present(source)) then + new_target%source = source + if (allocated(source%flags)) then + if (present(module_flags)) then + new_target%compile_flags = " "//source%flags//module_flags + else + new_target%compile_flags = " "//source%flags + end if + end if + if (allocated(source%link_time_flags)) new_target%link_flags = " "//source%link_time_flags//" " + end if if (present(link_libraries)) new_target%link_libraries = link_libraries allocate(new_target%dependencies(0)) @@ -442,9 +514,10 @@ end function find_module_dependency !> Construct the linker flags string for each target !> `target%link_flags` includes non-library objects and library flags !> -subroutine resolve_target_linking(targets, model) +subroutine resolve_target_linking(targets, model, build_dirs) type(build_target_ptr), intent(inout), target :: targets(:) type(fpm_model_t), intent(in) :: model + type(string_t), intent(in) :: build_dirs(:) integer :: i character(:), allocatable :: global_link_flags @@ -468,7 +541,11 @@ subroutine resolve_target_linking(targets, model) if (allocated(model%include_dirs)) then if (size(model%include_dirs) > 0) then global_include_flags = global_include_flags // & - & " -I" // string_cat(model%include_dirs," -I") + & " -I " // string_cat(model%include_dirs," -I ") + end if + if (size(build_dirs) > 0) then + global_include_flags = global_include_flags // & + & " -I " // string_cat(build_dirs," -I ") end if end if @@ -476,11 +553,7 @@ subroutine resolve_target_linking(targets, model) associate(target => targets(i)%ptr) - if (target%target_type /= FPM_TARGET_C_OBJECT) then - target%compile_flags = model%fortran_compile_flags//" "//global_include_flags - else - target%compile_flags = global_include_flags - end if + target%compile_flags = target%compile_flags // global_include_flags allocate(target%link_objects(0)) @@ -494,7 +567,7 @@ subroutine resolve_target_linking(targets, model) call get_link_objects(target%link_objects,target,is_exe=.true.) - target%link_flags = string_cat(target%link_objects," ") + target%link_flags = target%link_flags // string_cat(target%link_objects," ") if (allocated(target%link_libraries)) then if (size(target%link_libraries) > 0) then diff --git a/test/fpm_test/test_manifest.f90 b/test/fpm_test/test_manifest.f90 index 6fde671213..da1fb6d1eb 100644 --- a/test/fpm_test/test_manifest.f90 +++ b/test/fpm_test/test_manifest.f90 @@ -4,6 +4,7 @@ module test_manifest use testsuite, only : new_unittest, unittest_t, error_t, test_failed, & & check_string use fpm_manifest + use fpm_manifest_profile, only: profile_config_t, find_profile use fpm_strings, only: operator(.in.) implicit none private @@ -33,6 +34,8 @@ subroutine collect_manifest(testsuite) & new_unittest("dependency-wrongkey", test_dependency_wrongkey, should_fail=.true.), & & new_unittest("dependencies-empty", test_dependencies_empty), & & new_unittest("dependencies-typeerror", test_dependencies_typeerror, should_fail=.true.), & + & new_unittest("profiles", test_profiles), & + & new_unittest("profiles-keyvalue-table", test_profiles_keyvalue_table, should_fail=.true.), & & new_unittest("executable-empty", test_executable_empty, should_fail=.true.), & & new_unittest("executable-typeerror", test_executable_typeerror, should_fail=.true.), & & new_unittest("executable-noname", test_executable_noname, should_fail=.true.), & @@ -391,6 +394,102 @@ subroutine test_dependencies_typeerror(error) end subroutine test_dependencies_typeerror + subroutine test_profiles(error) + + !> Error handling + type(error_t), allocatable, intent(out) :: error + + type(package_config_t) :: package + character(len=*), parameter :: manifest = 'fpm-profiles.toml' + integer :: unit + character(:), allocatable :: profile_name, compiler, flags + logical :: profile_found + type(profile_config_t) :: chosen_profile + + open(file=manifest, newunit=unit) + write(unit, '(a)') & + & 'name = "example"', & + & '[profiles.release.gfortran.linux]', & + & 'flags = "1" #release.gfortran.linux', & + & '[profiles.release.gfortran]', & + & 'flags = "2" #release.gfortran.all', & + & '[profiles.gfortran.linux]', & + & 'flags = "3" #all.gfortran.linux', & + & '[profiles.gfortran]', & + & 'flags = "4" #all.gfortran.all', & + & '[profiles.release.ifort]', & + & 'flags = "5" #release.ifort.all' + close(unit) + + call get_package_data(package, manifest, error) + + open(file=manifest, newunit=unit) + close(unit, status='delete') + + if (allocated(error)) return + + profile_name = 'release' + compiler = 'gfortran' + call find_profile(package%profiles, profile_name, compiler, 1, profile_found, chosen_profile) + if (.not.(chosen_profile%flags.eq.'1 3')) then + call test_failed(error, "Failed to append flags from profiles named 'all'") + return + end if + + profile_name = 'release' + compiler = 'gfortran' + call find_profile(package%profiles, profile_name, compiler, 3, profile_found, chosen_profile) + if (.not.(chosen_profile%flags.eq.'2 4')) then + call test_failed(error, "Failed to choose profile with OS 'all'") + return + end if + + profile_name = 'publish' + compiler = 'gfortran' + call find_profile(package%profiles, profile_name, compiler, 1, profile_found, chosen_profile) + if (allocated(chosen_profile%flags)) then + call test_failed(error, "Profile named "//profile_name//" should not exist") + return + end if + + profile_name = 'debug' + compiler = 'ifort' + call find_profile(package%profiles, profile_name, compiler, 3, profile_found, chosen_profile) + if (.not.(chosen_profile%flags.eq.' /warn:all /check:all /error-limit:1 /Od /Z7 /assume:byterecl /traceback')) then + call test_failed(error, "Failed to load built-in profile"//flags) + return + end if + + profile_name = 'release' + compiler = 'ifort' + call find_profile(package%profiles, profile_name, compiler, 1, profile_found, chosen_profile) + if (.not.(chosen_profile%flags.eq.'5')) then + call test_failed(error, "Failed to overwrite built-in profile") + return + end if + end subroutine test_profiles + + subroutine test_profiles_keyvalue_table(error) + + !> Error handling + type(error_t), allocatable, intent(out) :: error + + type(package_config_t) :: package + character(len=*), parameter :: manifest = 'fpm-profiles-error.toml' + integer :: unit + character(:), allocatable :: profile_name, compiler, flags + + open(file=manifest, newunit=unit) + write(unit, '(a)') & + & 'name = "example"', & + & '[profiles.linux.flags]' + close(unit) + + call get_package_data(package, manifest, error) + + open(file=manifest, newunit=unit) + close(unit, status='delete') + end subroutine test_profiles_keyvalue_table !> Executables cannot be created from empty tables subroutine test_executable_empty(error) diff --git a/test/fpm_test/test_package_dependencies.f90 b/test/fpm_test/test_package_dependencies.f90 index a3192ff2f9..7e8a879361 100644 --- a/test/fpm_test/test_package_dependencies.f90 +++ b/test/fpm_test/test_package_dependencies.f90 @@ -212,7 +212,7 @@ end subroutine test_add_dependencies !> Resolve a single dependency node - subroutine resolve_dependency_once(self, dependency, root, error) + subroutine resolve_dependency_once(self, dependency, root, error, parent) !> Mock instance of the dependency tree class(mock_dependency_tree_t), intent(inout) :: self !> Dependency configuration to add @@ -221,6 +221,8 @@ subroutine resolve_dependency_once(self, dependency, root, error) character(len=*), intent(in) :: root !> Error handling type(error_t), allocatable, intent(out) :: error + !> Name of the parent package + character(len=*), intent(in), optional :: parent if (dependency%done) then call test_failed(error, "Should only visit this node once")