diff options
Diffstat (limited to 'src/fpm_sources.f90')
-rw-r--r-- | src/fpm_sources.f90 | 220 |
1 files changed, 220 insertions, 0 deletions
diff --git a/src/fpm_sources.f90 b/src/fpm_sources.f90 new file mode 100644 index 0000000..c781535 --- /dev/null +++ b/src/fpm_sources.f90 @@ -0,0 +1,220 @@ +!># Discovery of sources +!> +!> This module implements subroutines for building a list of +!> `[[srcfile_t]]` objects by looking for source files in the filesystem. +!> +module fpm_sources +use fpm_error, only: error_t +use fpm_model, only: srcfile_t, FPM_UNIT_PROGRAM +use fpm_filesystem, only: basename, canon_path, dirname, join_path, list_files +use fpm_strings, only: lower, str_ends_with, string_t, operator(.in.) +use fpm_source_parsing, only: parse_f_source, parse_c_source +use fpm_manifest_executable, only: executable_config_t +implicit none + +private +public :: add_sources_from_dir, add_executable_sources + +character(4), parameter :: fortran_suffixes(2) = [".f90", & + ".f "] + +contains + +!> Wrapper to source parsing routines. +!> Selects parsing routine based on source file name extension +function parse_source(source_file_path,error) result(source) + character(*), intent(in) :: source_file_path + type(error_t), allocatable, intent(out) :: error + type(srcfile_t) :: source + + if (str_ends_with(lower(source_file_path), fortran_suffixes)) then + + source = parse_f_source(source_file_path, error) + + if (source%unit_type == FPM_UNIT_PROGRAM) then + source%exe_name = basename(source_file_path,suffix=.false.) + end if + + else if (str_ends_with(lower(source_file_path), [".c", ".h"])) then + + source = parse_c_source(source_file_path,error) + + end if + + if (allocated(error)) then + return + end if + +end function parse_source + +!> Add to `sources` by looking for source files in `directory` +subroutine add_sources_from_dir(sources,directory,scope,with_executables,recurse,error) + !> List of `[[srcfile_t]]` objects to append to. Allocated if not allocated + type(srcfile_t), allocatable, intent(inout), target :: sources(:) + !> Directory in which to search for source files + character(*), intent(in) :: directory + !> Scope to apply to the discovered sources, see [[fpm_model]] for enumeration + integer, intent(in) :: scope + !> Executable sources (fortran `program`s) are ignored unless `with_executables=.true.` + logical, intent(in), optional :: with_executables + !> Whether to recursively search subdirectories, default is `.true.` + logical, intent(in), optional :: recurse + !> Error handling + type(error_t), allocatable, intent(out) :: error + + integer :: i + logical, allocatable :: is_source(:), exclude_source(:) + type(string_t), allocatable :: file_names(:) + type(string_t), allocatable :: src_file_names(:) + type(string_t), allocatable :: existing_src_files(:) + type(srcfile_t), allocatable :: dir_sources(:) + + ! Scan directory for sources + call list_files(directory, file_names,recurse=merge(recurse,.true.,present(recurse))) + + if (allocated(sources)) then + allocate(existing_src_files(size(sources))) + do i=1,size(sources) + existing_src_files(i)%s = canon_path(sources(i)%file_name) + end do + else + allocate(existing_src_files(0)) + end if + + is_source = [(.not.(canon_path(file_names(i)%s) .in. existing_src_files) .and. & + (str_ends_with(lower(file_names(i)%s), fortran_suffixes) .or. & + str_ends_with(lower(file_names(i)%s),[".c",".h"]) ),i=1,size(file_names))] + src_file_names = pack(file_names,is_source) + + allocate(dir_sources(size(src_file_names))) + allocate(exclude_source(size(src_file_names))) + + do i = 1, size(src_file_names) + + dir_sources(i) = parse_source(src_file_names(i)%s,error) + if (allocated(error)) return + + dir_sources(i)%unit_scope = scope + + ! Exclude executables unless specified otherwise + exclude_source(i) = (dir_sources(i)%unit_type == FPM_UNIT_PROGRAM) + if (dir_sources(i)%unit_type == FPM_UNIT_PROGRAM .and. & + & present(with_executables)) then + if (with_executables) then + + exclude_source(i) = .false. + + end if + end if + + end do + + if (.not.allocated(sources)) then + sources = pack(dir_sources,.not.exclude_source) + else + sources = [sources, pack(dir_sources,.not.exclude_source)] + end if + +end subroutine add_sources_from_dir + + +!> Add to `sources` using the executable and test entries in the manifest and +!> applies any executable-specific overrides such as `executable%name`. +!> Adds all sources (including modules) from each `executable%source_dir` +subroutine add_executable_sources(sources,executables,scope,auto_discover,error) + !> List of `[[srcfile_t]]` objects to append to. Allocated if not allocated + type(srcfile_t), allocatable, intent(inout), target :: sources(:) + !> List of `[[executable_config_t]]` entries from manifest + class(executable_config_t), intent(in) :: executables(:) + !> Scope to apply to the discovered sources: either `FPM_SCOPE_APP` or `FPM_SCOPE_TEST`, see [[fpm_model]] + integer, intent(in) :: scope + !> If `.false.` only executables and tests specified in the manifest are added to `sources` + logical, intent(in) :: auto_discover + !> Error handling + type(error_t), allocatable, intent(out) :: error + + integer :: i, j + + type(string_t), allocatable :: exe_dirs(:) + type(srcfile_t) :: exe_source + + call get_executable_source_dirs(exe_dirs,executables) + + do i=1,size(exe_dirs) + call add_sources_from_dir(sources,exe_dirs(i)%s, scope, & + with_executables=auto_discover, recurse=.false., error=error) + + if (allocated(error)) then + return + end if + end do + + exe_loop: do i=1,size(executables) + + ! Check if executable already discovered automatically + ! and apply any overrides + do j=1,size(sources) + + if (basename(sources(j)%file_name,suffix=.true.) == executables(i)%main .and.& + canon_path(dirname(sources(j)%file_name)) == & + canon_path(executables(i)%source_dir) ) then + + sources(j)%exe_name = executables(i)%name + if (allocated(executables(i)%link)) then + sources(j)%link_libraries = executables(i)%link + end if + cycle exe_loop + + end if + + end do + + ! Add if not already discovered (auto_discovery off) + exe_source = parse_source(join_path(executables(i)%source_dir,executables(i)%main),error) + exe_source%exe_name = executables(i)%name + if (allocated(executables(i)%link)) then + exe_source%link_libraries = executables(i)%link + end if + exe_source%unit_scope = scope + + if (allocated(error)) return + + if (.not.allocated(sources)) then + sources = [exe_source] + else + sources = [sources, exe_source] + end if + + end do exe_loop + +end subroutine add_executable_sources + +!> Build a list of unique source directories +!> from executables specified in manifest +subroutine get_executable_source_dirs(exe_dirs,executables) + type(string_t), allocatable, intent(inout) :: exe_dirs(:) + class(executable_config_t), intent(in) :: executables(:) + + type(string_t) :: dirs_temp(size(executables)) + + integer :: i, n + + n = 0 + do i=1,size(executables) + if (.not.(executables(i)%source_dir .in. dirs_temp)) then + + n = n + 1 + dirs_temp(n)%s = executables(i)%source_dir + + end if + end do + + if (.not.allocated(exe_dirs)) then + exe_dirs = dirs_temp(1:n) + else + exe_dirs = [exe_dirs,dirs_temp(1:n)] + end if + +end subroutine get_executable_source_dirs + +end module fpm_sources |