!># The fpm interface to pkg-config !> !> This module contains wrapper functions to interface with a pkg-config installation. !> module fpm_pkg_config use fpm_strings, only: string_t,str_begins_with_str,len_trim,remove_newline_characters, & split use fpm_error, only: error_t, fatal_error, fpm_stop use fpm_filesystem, only: get_temp_filename,getline use fpm_environment, only: get_env,os_is_unix,set_env,delete_env use shlex_module, only: shlex_split => split implicit none private public :: assert_pkg_config public :: pkgcfg_get_version public :: pkgcfg_get_libs public :: pkgcfg_get_build_flags public :: pkgcfg_has_package public :: pkgcfg_list_all public :: run_wrapper contains !> Check whether pkg-config is available on the local system logical function assert_pkg_config() integer :: exitcode logical :: success type(string_t) :: log call run_wrapper(wrapper=string_t('pkg-config'),args=[string_t('-h')], & exitcode=exitcode,cmd_success=success,screen_output=log) assert_pkg_config = exitcode==0 .and. success end function assert_pkg_config !> Get package version from pkg-config type(string_t) function pkgcfg_get_version(package,error) result(screen) !> Package name character(*), intent(in) :: package !> Error handler type(error_t), allocatable, intent(out) :: error integer :: exitcode logical :: success type(string_t) :: log call run_wrapper(wrapper=string_t('pkg-config'), & args=[string_t(package),string_t('--modversion')], & exitcode=exitcode,cmd_success=success,screen_output=log) if (success .and. exitcode==0) then call remove_newline_characters(log) screen = log else screen = string_t("") end if end function pkgcfg_get_version !> Check if pkgcfg has package logical function pkgcfg_has_package(name) result(success) !> Package name character(*), intent(in) :: name integer :: exitcode logical :: cmdok type(string_t) :: log call run_wrapper(wrapper=string_t('pkg-config'), & args=[string_t(name),string_t('--exists')], & exitcode=exitcode,cmd_success=cmdok,screen_output=log) !> pkg-config --exists returns 0 only if the package exists success = cmdok .and. exitcode==0 end function pkgcfg_has_package !> Get package libraries from pkg-config function pkgcfg_get_libs(package,error) result(libraries) !> Package name character(*), intent(in) :: package !> Error handler type(error_t), allocatable, intent(out) :: error !> A list of libraries type(string_t), allocatable :: libraries(:) integer :: exitcode,nlib,i logical :: success character(len=:), allocatable :: tokens(:) type(string_t) :: log call run_wrapper(wrapper=string_t('pkg-config'), & args=[string_t(package),string_t('--libs')], & exitcode=exitcode,cmd_success=success,screen_output=log) if (success .and. exitcode==0) then call remove_newline_characters(log) ! Split all arguments tokens = shlex_split(log%s) nlib = size(tokens) allocate(libraries(nlib)) do i=1,nlib libraries(i) = string_t(trim(adjustl(tokens(i)))) end do else allocate(libraries(0)) call fatal_error(error,'cannot get <'//package//'> libraries from pkg-config') end if end function pkgcfg_get_libs !> Return whole list of available pkg-cfg packages function pkgcfg_list_all(error,descriptions) result(modules) !> Error handler type(error_t), allocatable, intent(out) :: error !> A list of all available packages type(string_t), allocatable :: modules(:) !> An optional list of package descriptions type(string_t), optional, allocatable, intent(out) :: descriptions(:) integer :: exitcode,i,spc logical :: success character(len=:), allocatable :: lines(:) type(string_t) :: log type(string_t), allocatable :: mods(:),descr(:) character(*), parameter :: CRLF = achar(13)//new_line('a') call run_wrapper(wrapper=string_t('pkg-config'), & args=[string_t('--list-all')], & exitcode=exitcode,cmd_success=success,screen_output=log) if (.not.(success .and. exitcode==0)) then call fatal_error(error,'cannot get pkg-config modules') allocate(modules(0)) return end if !> Extract list call split(log%s,lines,CRLF) allocate(mods(size(lines)),descr(size(lines))) do i=1,size(lines) ! Module names have no spaces spc = index(lines(i),' ') if (spc>0) then mods(i) = string_t(trim(adjustl(lines(i)(1:spc)))) descr(i) = string_t(trim(adjustl(lines(i)(spc+1:)))) else mods(i) = string_t(trim(adjustl(lines(i)))) descr(i) = string_t("") end if end do call move_alloc(from=mods,to=modules) if (present(descriptions)) call move_alloc(from=descr,to=descriptions) end function pkgcfg_list_all !> Get build flags (option to include flags from system directories, that !> gfortran does not look into by default) function pkgcfg_get_build_flags(name,allow_system,error) result(flags) !> Package name character(*), intent(in) :: name !> Should pkg-config look in system paths? This is necessary for gfortran !> that doesn't otherwise look into them logical, intent(in) :: allow_system !> Error flag type(error_t), allocatable, intent(out) :: error !> List of compile flags type(string_t), allocatable :: flags(:) integer :: exitcode,i,nlib logical :: old_had,success,old_allow character(:), allocatable :: old,tokens(:) type(string_t) :: log ! Check if the current environment includes system flags old = get_env('PKG_CONFIG_ALLOW_SYSTEM_CFLAGS',default='ERROR') old_had = old/='ERROR' old_allow = merge(old=='1',.false.,old_had) ! Set system flags success = set_env('PKG_CONFIG_ALLOW_SYSTEM_CFLAGS',value=merge('1','0',allow_system)) if (.not.success) then call fatal_error(error,'Cannot get pkg-config build flags: environment variable error.') return end if ! Now run wrapper call run_wrapper(wrapper=string_t('pkg-config'), & args=[string_t(name),string_t('--cflags')], & exitcode=exitcode,cmd_success=success,screen_output=log) if (success .and. exitcode==0) then call remove_newline_characters(log) ! Split all arguments tokens = shlex_split(log%s) nlib = size(tokens) allocate(flags(nlib)) do i=1,nlib flags(i) = string_t(trim(adjustl(tokens(i)))) end do else allocate(flags(0)) call fatal_error(error,'cannot get <'//name//'> build flags from pkg-config') end if ! Restore environment variable if (old_had) then success = set_env('PKG_CONFIG_ALLOW_SYSTEM_CFLAGS',value=old) else success = delete_env('PKG_CONFIG_ALLOW_SYSTEM_CFLAGS') end if if (.not.success) then call fatal_error(error,'Cannot get pkg-config build flags: environment variable error.') return end if end function pkgcfg_get_build_flags !> Simple call to execute_command_line involving one mpi* wrapper subroutine run_wrapper(wrapper,args,verbose,exitcode,cmd_success,screen_output) type(string_t), intent(in) :: wrapper type(string_t), intent(in), optional :: args(:) logical, intent(in), optional :: verbose integer, intent(out), optional :: exitcode logical, intent(out), optional :: cmd_success type(string_t), intent(out), optional :: screen_output logical :: echo_local character(:), allocatable :: redirect_str,command,redirect,line integer :: iunit,iarg,stat,cmdstat if(present(verbose))then echo_local=verbose else echo_local=.false. end if ! No redirection and non-verbose output if (present(screen_output)) then redirect = get_temp_filename() redirect_str = ">"//redirect//" 2>&1" else if (os_is_unix()) then redirect_str = " >/dev/null 2>&1" else redirect_str = " >NUL 2>&1" end if end if ! Empty command if (len_trim(wrapper)<=0) then if (echo_local) print *, '+ <EMPTY COMMAND>' if (present(exitcode)) exitcode = 0 if (present(cmd_success)) cmd_success = .true. if (present(screen_output)) screen_output = string_t("") return end if ! Init command command = trim(wrapper%s) add_arguments: if (present(args)) then do iarg=1,size(args) if (len_trim(args(iarg))<=0) cycle command = trim(command)//' '//args(iarg)%s end do endif add_arguments if (echo_local) print *, '+ ', command ! Test command call execute_command_line(command//redirect_str,exitstat=stat,cmdstat=cmdstat) ! Command successful? if (present(cmd_success)) cmd_success = cmdstat==0 ! Program exit code? if (present(exitcode)) exitcode = stat ! Want screen output? if (present(screen_output) .and. cmdstat==0) then allocate(character(len=0) :: screen_output%s) open(newunit=iunit,file=redirect,status='old',iostat=stat) if (stat == 0)then do call getline(iunit, line, stat) if (stat /= 0) exit screen_output%s = screen_output%s//new_line('a')//line if (echo_local) write(*,'(A)') trim(line) end do ! Close and delete file close(iunit,status='delete') else call fpm_stop(1,'cannot read temporary file from successful MPI wrapper') endif end if end subroutine run_wrapper end module fpm_pkg_config