aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLKedward <laurence.kedward@bristol.ac.uk>2021-03-06 10:51:09 +0000
committerLKedward <laurence.kedward@bristol.ac.uk>2021-03-06 10:51:09 +0000
commitfcbab5d43956fc16370292fcb60a54dc6638a494 (patch)
treebf8c7c504fa6afe743223919902d22541d43f89d
parent4336020fe3aa914a498867541f8d36978d645799 (diff)
parent32c96e56af29e6c51d7e971c6416701005a2b41f (diff)
downloadfpm-fcbab5d43956fc16370292fcb60a54dc6638a494.tar.gz
fpm-fcbab5d43956fc16370292fcb60a54dc6638a494.zip
Merge remote-tracking branch 'upstream/master' into separate-targets
-rw-r--r--README.md24
-rw-r--r--fpm/src/fpm.f90112
-rw-r--r--fpm/src/fpm_command_line.f90103
-rw-r--r--fpm/src/fpm_compiler.f9027
-rw-r--r--fpm/src/fpm_environment.f9013
-rw-r--r--fpm/src/fpm_strings.f90520
-rw-r--r--fpm/test/help_test/help_test.f90101
-rw-r--r--fpm/test/new_test/new_test.f9034
-rwxr-xr-xinstall.sh118
9 files changed, 787 insertions, 265 deletions
diff --git a/README.md b/README.md
index 00dd73d..be96b4f 100644
--- a/README.md
+++ b/README.md
@@ -72,32 +72,26 @@ $ cd fpm/
#### Build a bootstrap version of fpm
-You can use the install script to perform the build of the Haskell version of *fpm* with:
+You can use the install script to bootstrap and install *fpm*:
```bash
$ ./install.sh
```
-On Linux, the above command installs `fpm` to `${HOME}/.local/bin/`.
-
-Now you can build the Fortran *fpm* version with
+By default, the above command installs `fpm` to `${HOME}/.local/bin/`.
+To specify an alternative destination use the `--prefix=` flag, for example:
```bash
-$ cd fpm/
-$ fpm build
+$ ./install.sh --prefix=/usr/local
```
-Test that everything is working as expected
+which will install *fpm* to `/usr/local/bin`.
-```bash
-$ fpm test
-```
-
-Finally, install the Fortran *fpm* version with
+To test that everything is working as expected you can now build *fpm*
+with itself and run the tests with:
```bash
-$ fpm run --runner mv -- ~/.local/bin
+$ cd fpm
+$ fpm test
```
-Or choose another location if you do not want to overwrite the bootstrapping version.
-From now on you can rebuild *fpm* with your Fortran *fpm* version.
diff --git a/fpm/src/fpm.f90 b/fpm/src/fpm.f90
index 5837189..68385cd 100644
--- a/fpm/src/fpm.f90
+++ b/fpm/src/fpm.f90
@@ -1,5 +1,5 @@
module fpm
-use fpm_strings, only: string_t, operator(.in.)
+use fpm_strings, only: string_t, operator(.in.), glob, join
use fpm_backend, only: build_package
use fpm_command_line, only: fpm_build_settings, fpm_new_settings, &
fpm_run_settings, fpm_install_settings, fpm_test_settings
@@ -65,6 +65,9 @@ subroutine build_model(model, settings, package, error)
model%output_directory = join_path('build',basename(model%fortran_compiler)//'_'//settings%build_name)
call add_compile_flag_defaults(settings%build_name, basename(model%fortran_compiler), model)
+ if(settings%verbose)then
+ write(*,*)'<INFO>COMPILER OPTIONS: ', model%fortran_compile_flags
+ endif
allocate(model%packages(model%deps%ndep))
@@ -197,8 +200,7 @@ subroutine cmd_run(settings,test)
class(fpm_run_settings), intent(in) :: settings
logical, intent(in) :: test
- integer, parameter :: LINE_WIDTH = 80
- integer :: i, j, col_width, nCol
+ integer :: i, j, col_width
logical :: found(size(settings%name))
type(error_t), allocatable :: error
type(package_config_t) :: package
@@ -209,6 +211,8 @@ subroutine cmd_run(settings,test)
type(build_target_t), pointer :: exe_target
type(srcfile_t), pointer :: exe_source
integer :: run_scope
+ character(len=:),allocatable :: line
+ logical :: toomany
call get_package_data(package, "fpm.toml", error, apply_defaults=.true.)
if (allocated(error)) then
@@ -260,7 +264,7 @@ subroutine cmd_run(settings,test)
do j=1,size(settings%name)
- if (trim(settings%name(j))==exe_source%exe_name) then
+ if (glob(trim(exe_source%exe_name),trim(settings%name(j)))) then
found(j) = .true.
exe_cmd%s = exe_target%output_file
@@ -289,15 +293,61 @@ subroutine cmd_run(settings,test)
end if
! Check all names are valid
- if (any(.not.found)) then
+ ! or no name and found more than one file
+ toomany= size(settings%name).eq.0 .and. size(executables).gt.1
+ if ( any(.not.found) &
+ & .or. &
+ & ( (toomany .and. .not.test) .or. (toomany .and. settings%runner .ne. '') ) &
+ & .and. &
+ & .not.settings%list) then
+ line=join(settings%name)
+ if(line.ne.'.')then ! do not report these special strings
+ if(any(.not.found))then
+ write(stderr,'(A)',advance="no")'fpm::run<ERROR> specified names '
+ do j=1,size(settings%name)
+ if (.not.found(j)) write(stderr,'(A)',advance="no") '"'//trim(settings%name(j))//'" '
+ end do
+ write(stderr,'(A)') 'not found.'
+ write(stderr,*)
+ else if(settings%verbose)then
+ write(stderr,'(A)',advance="yes")'<INFO>when more than one executable is available'
+ write(stderr,'(A)',advance="yes")' program names must be specified.'
+ endif
+ endif
- write(stderr,'(A)',advance="no")'fpm::run<ERROR> specified names '
- do j=1,size(settings%name)
- if (.not.found(j)) write(stderr,'(A)',advance="no") '"'//trim(settings%name(j))//'" '
- end do
- write(stderr,'(A)') 'not found.'
- write(stderr,*)
+ call compact_list_all()
+
+ if(line.eq.'.' .or. line.eq.' ')then ! do not report these special strings
+ stop
+ else
+ stop 1
+ endif
+
+ end if
+
+ call build_package(targets,model)
+ if (settings%list) then
+ call compact_list()
+ else
+
+ do i=1,size(executables)
+ if (exists(executables(i)%s)) then
+ if(settings%runner .ne. ' ')then
+ call run(settings%runner//' '//executables(i)%s//" "//settings%args,echo=settings%verbose)
+ else
+ call run(executables(i)%s//" "//settings%args,echo=settings%verbose)
+ endif
+ else
+ write(stderr,*)'fpm::run<ERROR>',executables(i)%s,' not found'
+ stop 1
+ end if
+ end do
+ endif
+ contains
+ subroutine compact_list_all()
+ integer, parameter :: LINE_WIDTH = 80
+ integer :: i, j, nCol
j = 1
nCol = LINE_WIDTH/col_width
write(stderr,*) 'Available names:'
@@ -317,36 +367,24 @@ subroutine cmd_run(settings,test)
j = j + 1
end if
-
end if
-
end do
-
write(stderr,*)
- stop 1
+ end subroutine compact_list_all
- end if
-
- call build_package(targets,model)
-
- do i=1,size(executables)
- if (settings%list) then
- write(stderr,*) executables(i)%s
- else
-
- if (exists(executables(i)%s)) then
- if(settings%runner .ne. ' ')then
- call run(settings%runner//' '//executables(i)%s//" "//settings%args)
- else
- call run(executables(i)%s//" "//settings%args)
- endif
- else
- write(stderr,*)'fpm::run<ERROR>',executables(i)%s,' not found'
- stop 1
- end if
-
- end if
- end do
+ subroutine compact_list()
+ integer, parameter :: LINE_WIDTH = 80
+ integer :: i, j, nCol
+ j = 1
+ nCol = LINE_WIDTH/col_width
+ write(stderr,*) 'Matched names:'
+ do i=1,size(executables)
+ write(stderr,'(A)',advance=(merge("yes","no ",modulo(j,nCol)==0))) &
+ & [character(len=col_width) :: basename(executables(i)%s)]
+ j = j + 1
+ enddo
+ write(stderr,*)
+ end subroutine compact_list
end subroutine cmd_run
diff --git a/fpm/src/fpm_command_line.f90 b/fpm/src/fpm_command_line.f90
index 0217154..4d184e4 100644
--- a/fpm/src/fpm_command_line.f90
+++ b/fpm/src/fpm_command_line.f90
@@ -152,6 +152,7 @@ contains
call set_args('&
& --target " " &
& --list F &
+ & --all F &
& --release F&
& --example F&
& --runner " " &
@@ -167,13 +168,26 @@ contains
names=[character(len=len(names)) :: ]
endif
+
if(specified('target') )then
call split(sget('target'),tnames,delimiters=' ,:')
names=[character(len=max(len(names),len(tnames))) :: names,tnames]
endif
+ ! convert --all to '*'
+ if(lget('all'))then
+ names=[character(len=max(len(names),1)) :: names,'*' ]
+ endif
+
+ ! convert special string '..' to equivalent (shorter) '*'
+ ! to allow for a string that does not require shift-key and quoting
+ do i=1,size(names)
+ if(names(i).eq.'..')names(i)='*'
+ enddo
+
allocate(fpm_run_settings :: cmd_settings)
val_runner=sget('runner')
+ if(specified('runner') .and. val_runner.eq.'')val_runner='echo'
cmd_settings=fpm_run_settings(&
& args=remaining,&
& build_name=val_build,&
@@ -375,8 +389,15 @@ contains
names=[character(len=max(len(names),len(tnames))) :: names,tnames]
endif
+ ! convert special string '..' to equivalent (shorter) '*'
+ ! to allow for a string that does not require shift-key and quoting
+ do i=1,size(names)
+ if(names(i).eq.'..')names(i)='*'
+ enddo
+
allocate(fpm_test_settings :: cmd_settings)
val_runner=sget('runner')
+ if(specified('runner') .and. val_runner.eq.'')val_runner='echo'
cmd_settings=fpm_test_settings(&
& args=remaining, &
& build_name=val_build, &
@@ -501,8 +522,8 @@ contains
' [--full|--bare][--backfill] ', &
' update [NAME(s)] [--fetch-only] [--clean] [--verbose] ', &
' list [--list] ', &
- ' run [[--target] NAME(s)] [--release] [--runner "CMD"] [--list] [--example] ', &
- ' [--compiler COMPILER_NAME] [-- ARGS] ', &
+ ' run [[--target] NAME(s) [--example] [--release] [--all] [--runner "CMD"] ', &
+ ' [--compiler COMPILER_NAME] [--list] [-- ARGS] ', &
' test [[--target] NAME(s)] [--release] [--runner "CMD"] [--list] ', &
' [--compiler COMPILER_NAME] [-- ARGS] ', &
' install [--release] [--no-rebuild] [--prefix PATH] [options] ', &
@@ -529,7 +550,8 @@ contains
'OPTION ', &
' --runner ''CMD'' quoted command used to launch the fpm(1) executables. ', &
' Available for both the "run" and "test" subcommands. ', &
- ' ', &
+ ' If the keyword is specified without a value the default command ', &
+ ' is "echo". ', &
' -- SUFFIX_OPTIONS additional options to suffix the command CMD and executable ', &
' file names with. ', &
'EXAMPLES ', &
@@ -584,7 +606,7 @@ contains
' ', &
'DESCRIPTION ', &
' fpm(1) is a package manager that helps you create Fortran projects ', &
- ' from source. ', &
+ ' from source -- it automatically determines dependencies! ', &
' ', &
' Most significantly fpm(1) lets you draw upon other fpm(1) packages ', &
' in distributed git(1) repositories as if the packages were a basic ', &
@@ -614,7 +636,7 @@ contains
' new NAME [[--lib|--src] [--app] [--test] [--example]]| ', &
' [--full|--bare][--backfill] ', &
' update [NAME(s)] [--fetch-only] [--clean] ', &
- ' run [[--target] NAME(s)] [--release] [--list] [--example] ', &
+ ' run [[--target] NAME(s)] [--release] [--list] [--example] [--all] ', &
' [--runner "CMD"] [--compiler COMPILER_NAME] [-- ARGS] ', &
' test [[--target] NAME(s)] [--release] [--list] ', &
' [--runner "CMD"] [--compiler COMPILER_NAME] [-- ARGS] ', &
@@ -686,50 +708,60 @@ contains
' run(1) - the fpm(1) subcommand to run project applications ', &
' ', &
'SYNOPSIS ', &
- ' fpm run [[--target] NAME(s)][--release][--compiler COMPILER_NAME] ', &
- ' [--runner "CMD"] [--example] [--list][-- ARGS] ', &
+ ' fpm run [[--target] NAME(s)[--release][--compiler COMPILER_NAME] ', &
+ ' [--runner "CMD"] [--example] [--list] [--all] [-- ARGS] ', &
' ', &
' fpm run --help|--version ', &
' ', &
'DESCRIPTION ', &
- ' Run applications you have built in your fpm(1) project. ', &
- ' By default applications specified in as "executable" in your package ', &
- ' manifest are used, alternatively also demonstration programs under ', &
- ' "example" can be used with this subcommand. ', &
+ ' Run the applications in your fpm(1) package. By default applications ', &
+ ' in /app or specified as "executable" in your "fpm.toml" manifest are ', &
+ ' used. Alternatively demonstration programs in example/ or specified in', &
+ ' the "example" section in "fpm.toml" can be executed. The applications ', &
+ ' are automatically rebuilt before being run if they are out of date. ', &
' ', &
'OPTIONS ', &
- ' --target NAME(s) optional list of specific names to execute. ', &
- ' The default is to run all the applications in app/ ', &
- ' or the programs listed in the "fpm.toml" file. ', &
- ' --example run example programs instead of applications ', &
- ' --release selects the optimized build instead of the debug ', &
- ' build. ', &
+ ' --target NAME(s) list of application names to execute. No name is ', &
+ ' required if only one target exists. If no name is ', &
+ ' supplied and more than one candidate exists or a ', &
+ ' name has no match a list is produced and fpm(1) ', &
+ ' exits. ', &
+ ' ', &
+ ' Basic "globbing" is supported where "?" represents ', &
+ ' any single character and "*" represents any string. ', &
+ ' Note The glob string normally needs quoted to ', &
+ ' the special characters from shell expansion. ', &
+ ' --all Run all examples or applications. An alias for --target ''*''. ', &
+ ' --example Run example programs instead of applications. ', &
+ ' --release selects the optimized build instead of the debug build. ', &
' --compiler COMPILER_NAME Specify a compiler name. The default is ', &
' "gfortran" unless set by the environment ', &
' variable FPM_COMPILER. ', &
' --runner CMD A command to prefix the program execution paths with. ', &
' see "fpm help runner" for further details. ', &
- ' --list list candidates instead of building or running them ', &
- ' -- ARGS optional arguments to pass to the program(s). ', &
- ' The same arguments are passed to all names ', &
- ' specified. ', &
+ ' --list list pathname of candidates instead of running them. Note ', &
+ ' out-of-date candidates will still be rebuilt before being ', &
+ ' listed. ', &
+ ' -- ARGS optional arguments to pass to the program(s). The same ', &
+ ' arguments are passed to all program names specified. ', &
' ', &
'EXAMPLES ', &
- ' fpm(1) "run" project applications ', &
+ ' fpm(1) - run or display project applications: ', &
' ', &
- ' # run default programs in /app or as specified in "fpm.toml" ', &
- ' fpm run ', &
+ ' fpm run # run a target when only one exists or list targets ', &
+ ' fpm run --list # list all targets, running nothing. ', &
+ ' fpm run --all # run all targets, no matter how many there are. ', &
' ', &
- ' # run default programs in /app or as specified in "fpm.toml" ', &
- ' # using the compiler command "f90". ', &
+ ' # run default program built or to be built with the compiler command ', &
+ ' # "f90". If more than one app exists a list displays and target names', &
+ ' # are required. ', &
' fpm run --compiler f90 ', &
' ', &
- ' # run example and demonstration programs instead of the default ', &
- ' # application programs (specified in "fpm.toml") ', &
- ' fpm run --example ', &
+ ' # run example programs instead of the application programs. ', &
+ ' fpm run --example ''*'' ', &
' ', &
' # run a specific program and pass arguments to the command ', &
- ' fpm run mytest -- -x 10 -y 20 --title "my title line" ', &
+ ' fpm run myprog -- -x 10 -y 20 --title "my title line" ', &
' ', &
' # run production version of two applications ', &
' fpm run --target prg1,prg2 --release ', &
@@ -756,7 +788,7 @@ contains
' o src/ for modules and procedure source ', &
' o app/ main program(s) for applications ', &
' o test/ main program(s) and support files for project tests ', &
- ' o example/ main program(s) for examples and demonstrations ', &
+ ' o example/ main program(s) for example programs ', &
' Changed or new files found are rebuilt. The results are placed in ', &
' the build/ directory. ', &
' ', &
@@ -908,9 +940,9 @@ contains
' cd myproject # Enter the new directory ', &
' # and run commands such as ', &
' fpm build ', &
- ' fpm run # run example application program(s) ', &
+ ' fpm run # run lone example application program ', &
' fpm test # run example test program(s) ', &
- ' fpm run --example # run example program(s) ', &
+ ' fpm run --example # run lone example program ', &
' ', &
' fpm new A --full # create example/ and an annotated fpm.toml as well', &
' fpm new A --bare # create no directories ', &
@@ -934,6 +966,11 @@ contains
' --target NAME(s) optional list of specific test names to execute. ', &
' The default is to run all the tests in test/ ', &
' or the tests listed in the "fpm.toml" file. ', &
+ ' ', &
+ ' Basic "globbing" is supported where "?" represents ', &
+ ' any single character and "*" represents any string. ', &
+ ' Note The glob string normally needs quoted to ', &
+ ' protect the special characters from shell expansion.', &
' --release selects the optimized build instead of the debug ', &
' build. ', &
' --compiler COMPILER_NAME Specify a compiler name. The default is ', &
diff --git a/fpm/src/fpm_compiler.f90 b/fpm/src/fpm_compiler.f90
index ba840e6..99aa77d 100644
--- a/fpm/src/fpm_compiler.f90
+++ b/fpm/src/fpm_compiler.f90
@@ -12,7 +12,7 @@ type(fpm_model_t), intent(inout) :: model
! could just be a function to return a string instead of passing model
! but likely to change other components like matching C compiler
-character(len=:),allocatable :: fflags ! optional flags that might be overridden by user
+character(len=:),allocatable :: fflags ! optional flags that might be overridden by user
character(len=:),allocatable :: modpath
character(len=:),allocatable :: mandatory ! flags required for fpm to function properly;
! ie. add module path and module include directory as appropriate
@@ -42,6 +42,24 @@ character(len=:),allocatable :: mandatory ! flags required for fpm to function p
! G95 ? ? -fmod= -I -fopenmp discontinued
! Open64 ? ? -module -I -mp discontinued
! Unisys ? ? ? ? ? discontinued
+character(len=*),parameter :: names(*)=[ character(len=10) :: &
+& 'caf', &
+& 'gfortran', &
+& 'f95', &
+& 'nvfortran', &
+& 'ifort', &
+& 'ifx', &
+& 'pgfortran', &
+& 'pgf90', &
+& 'pgf95', &
+& 'flang', &
+& 'lfc', &
+& 'nagfor', &
+& 'crayftn', &
+& 'xlf90', &
+& 'unknown']
+integer :: i
+
modpath=join_path(model%output_directory,model%package_name)
fflags=''
mandatory=''
@@ -143,7 +161,6 @@ character(len=:),allocatable :: mandatory ! flags required for fpm to function p
& -reentrancy threaded&
& -nogen-interfaces&
& -assume byterecl&
- & -assume nounderscore&
&'
mandatory=' -module '//modpath//' -I '//modpath
case('debug_ifort')
@@ -219,10 +236,8 @@ character(len=:),allocatable :: mandatory ! flags required for fpm to function p
case default
fflags = ' '
mandatory=' -module '//modpath//' -I '//modpath
- write(*,*)'<WARNING> unknown compiler (',compiler,')'
- write(*,*)' and build name (',build_name,')'
- write(*,*)' combination.'
- write(*,*)' known compilers are gfortran, nvfortran, ifort'
+ write(*,'(*(a))')'<WARNING> unknown compiler (',compiler,') and build name (',build_name,') combination.'
+ write(*,'(a,*(T31,6(a:,", "),/))')' known compilers are ',(trim(names(i)),i=1,size(names)-1)
end select
model%fortran_compile_flags = fflags//' '//mandatory
diff --git a/fpm/src/fpm_environment.f90 b/fpm/src/fpm_environment.f90
index 181252d..929a704 100644
--- a/fpm/src/fpm_environment.f90
+++ b/fpm/src/fpm_environment.f90
@@ -117,10 +117,19 @@ contains
unix = os /= OS_WINDOWS
end function os_is_unix
- subroutine run(cmd)
+ subroutine run(cmd,echo)
character(len=*), intent(in) :: cmd
+ logical,intent(in),optional :: echo
+ logical :: echo_local
integer :: stat
- print *, '+ ', cmd
+
+ if(present(echo))then
+ echo_local=echo
+ else
+ echo_local=.true.
+ endif
+ if(echo_local) print *, '+ ', cmd
+
call execute_command_line(cmd, exitstat=stat)
if (stat /= 0) then
print *, 'Command failed'
diff --git a/fpm/src/fpm_strings.f90 b/fpm/src/fpm_strings.f90
index 2b036d1..7623e43 100644
--- a/fpm/src/fpm_strings.f90
+++ b/fpm/src/fpm_strings.f90
@@ -5,7 +5,7 @@ implicit none
private
public :: f_string, lower, split, str_ends_with, string_t
public :: string_array_contains, string_cat, len_trim, operator(.in.), fnv_1a
-public :: replace, resize, str, join
+public :: replace, resize, str, join, glob
type string_t
character(len=:), allocatable :: s
@@ -383,93 +383,103 @@ subroutine resize_string(list, n)
end subroutine resize_string
-pure function join(str,sep,trm,left,right) result (string)
-
-!> M_strings::join(3f): append an array of character variables with specified separator into a single CHARACTER variable
-!>
-!>##NAME
-!> join(3f) - [M_strings:EDITING] append CHARACTER variable array into
-!> a single CHARACTER variable with specified separator
-!> (LICENSE:PD)
-!>
-!>##SYNOPSIS
-!>
-!> pure function join(str,sep,trm,left,right) result (string)
-!>
-!> character(len=*),intent(in) :: str(:)
-!> character(len=*),intent(in),optional :: sep
-!> logical,intent(in),optional :: trm
-!> character(len=*),intent(in),optional :: right
-!> character(len=*),intent(in),optional :: left
-!> character(len=:),allocatable :: string
-!>
-!>##DESCRIPTION
-!> JOIN(3f) appends the elements of a CHARACTER array into a single
-!> CHARACTER variable, with elements 1 to N joined from left to right.
-!> By default each element is trimmed of trailing spaces and the
-!> default separator is a null string.
-!>
-!>##OPTIONS
-!> STR(:) array of CHARACTER variables to be joined
-!> SEP separator string to place between each variable. defaults
-!> to a null string.
-!> LEFT string to place at left of each element
-!> RIGHT string to place at right of each element
-!> TRM option to trim each element of STR of trailing
-!> spaces. Defaults to .TRUE.
-!>
-!>##RESULT
-!> STRING CHARACTER variable composed of all of the elements of STR()
-!> appended together with the optional separator SEP placed
-!> between the elements and optional left and right elements.
-!>
-!>##EXAMPLE
-!>
-!> Sample program:
-!>
-!> program demo_join
-!> use M_strings, only: join
-!> implicit none
-!> character(len=:),allocatable :: s(:)
-!> character(len=:),allocatable :: out
-!> integer :: i
-!> s=[character(len=10) :: 'United',' we',' stand,', &
-!> & ' divided',' we fall.']
-!> out=join(s)
-!> write(*,'(a)') out
-!> write(*,'(a)') join(s,trm=.false.)
-!> write(*,'(a)') (join(s,trm=.false.,sep='|'),i=1,3)
-!> write(*,'(a)') join(s,sep='<>')
-!> write(*,'(a)') join(s,sep=';',left='[',right=']')
-!> write(*,'(a)') join(s,left='[',right=']')
-!> write(*,'(a)') join(s,left='>>')
-!> end program demo_join
-!>
-!> Expected output:
-!>
-!> United we stand, divided we fall.
-!> United we stand, divided we fall.
-!> United | we | stand, | divided | we fall. |
-!> United | we | stand, | divided | we fall. |
-!> United | we | stand, | divided | we fall. |
-!> United<> we<> stand,<> divided<> we fall.<>
-!> [United];[ we];[ stand,];[ divided];[ we fall.];
-!> [United][ we][ stand,][ divided][ we fall.]
-!> >>United>> we>> stand,>> divided>> we fall.
+pure function join(str,sep,trm,left,right,start,end) result (string)
!>
-!>##AUTHOR
-!> John S. Urban
-!>
-!>##LICENSE
-!> Public Domain
-
-character(len=*),intent(in) :: str(:)
-character(len=*),intent(in),optional :: sep, right, left
-logical,intent(in),optional :: trm
-character(len=:),allocatable :: string
-integer :: i
-logical :: trm_local
-character(len=:),allocatable :: sep_local, left_local, right_local
+!!##NAME
+!! join(3f) - [fpm_strings:EDITING] append CHARACTER variable array into
+!! a single CHARACTER variable with specified separator
+!! (LICENSE:PD)
+!!
+!!##SYNOPSIS
+!!
+!! pure function join(str,sep,trm,left,right,start,end) result (string)
+!!
+!! character(len=*),intent(in) :: str(:)
+!! character(len=*),intent(in),optional :: sep
+!! logical,intent(in),optional :: trm
+!! character(len=*),intent(in),optional :: right
+!! character(len=*),intent(in),optional :: left
+!! character(len=*),intent(in),optional :: start
+!! character(len=*),intent(in),optional :: end
+!! character(len=:),allocatable :: string
+!!
+!!##DESCRIPTION
+!! JOIN(3f) appends the elements of a CHARACTER array into a single
+!! CHARACTER variable, with elements 1 to N joined from left to right.
+!! By default each element is trimmed of trailing spaces and the
+!! default separator is a null string.
+!!
+!!##OPTIONS
+!! STR(:) array of CHARACTER variables to be joined
+!! SEP separator string to place between each variable. defaults
+!! to a null string.
+!! LEFT string to place at left of each element
+!! RIGHT string to place at right of each element
+!! START prefix string
+!! END suffix string
+!! TRM option to trim each element of STR of trailing
+!! spaces. Defaults to .TRUE.
+!!
+!!##RESULT
+!! STRING CHARACTER variable composed of all of the elements of STR()
+!! appended together with the optional separator SEP placed
+!! between the elements.
+!!
+!!##EXAMPLE
+!!
+!! Sample program:
+!!
+!! program demo_join
+!! use fpm_strings, only: join
+!! implicit none
+!! character(len=:),allocatable :: s(:)
+!! character(len=:),allocatable :: out
+!! integer :: i
+!! s=[character(len=10) :: 'United',' we',' stand,', &
+!! & ' divided',' we fall.']
+!! out=join(s)
+!! write(*,'(a)') out
+!! write(*,'(a)') join(s,trm=.false.)
+!! write(*,'(a)') (join(s,trm=.false.,sep='|'),i=1,3)
+!! write(*,'(a)') join(s,sep='<>')
+!! write(*,'(a)') join(s,sep=';',left='[',right=']')
+!! write(*,'(a)') join(s,left='[',right=']')
+!! write(*,'(a)') join(s,left='>>')
+!! end program demo_join
+!!
+!! Expected output:
+!!
+!! United we stand, divided we fall.
+!! United we stand, divided we fall.
+!! United | we | stand, | divided | we fall.
+!! United | we | stand, | divided | we fall.
+!! United | we | stand, | divided | we fall.
+!! United<> we<> stand,<> divided<> we fall.
+!! [United];[ we];[ stand,];[ divided];[ we fall.]
+!! [United][ we][ stand,][ divided][ we fall.]
+!! >>United>> we>> stand,>> divided>> we fall.
+!!
+!!##AUTHOR
+!! John S. Urban
+!!
+!!##LICENSE
+!! Public Domain
+
+! @(#)join(3f): append an array of character variables with specified separator into a single CHARACTER variable
+
+character(len=*),intent(in) :: str(:)
+character(len=*),intent(in),optional :: sep
+character(len=*),intent(in),optional :: right
+character(len=*),intent(in),optional :: left
+character(len=*),intent(in),optional :: start
+character(len=*),intent(in),optional :: end
+logical,intent(in),optional :: trm
+character(len=:),allocatable :: string
+integer :: i
+logical :: trm_local
+character(len=:),allocatable :: sep_local
+character(len=:),allocatable :: left_local
+character(len=:),allocatable :: right_local
if(present(sep))then ; sep_local=sep ; else ; sep_local='' ; endif
if(present(trm))then ; trm_local=trm ; else ; trm_local=.true. ; endif
@@ -477,14 +487,348 @@ character(len=:),allocatable :: sep_local, left_local, right_local
if(present(right))then ; right_local=right ; else ; right_local='' ; endif
string=''
- do i = 1,size(str)
+ do i = 1,size(str)-1
if(trm_local)then
string=string//left_local//trim(str(i))//right_local//sep_local
else
string=string//left_local//str(i)//right_local//sep_local
endif
enddo
+ if(trm_local)then
+ string=string//left_local//trim(str(i))//right_local
+ else
+ string=string//left_local//str(i)//right_local
+ endif
+ if(present(start))string=start//string
+ if(present(end))string=string//end
end function join
+
+function glob(tame,wild)
+!>
+!!##NAME
+!! glob(3f) - [fpm_strings:COMPARE] compare given string for match to
+!! pattern which may contain wildcard characters
+!! (LICENSE:PD)
+!!
+!!##SYNOPSIS
+!!
+!! logical function glob(string, pattern )
+!!
+!! character(len=*),intent(in) :: string
+!! character(len=*),intent(in) :: pattern
+!!
+!!##DESCRIPTION
+!! glob(3f) compares given STRING for match to PATTERN which may
+!! contain wildcard characters.
+!!
+!! In this version to get a match the entire string must be described
+!! by PATTERN. Trailing whitespace is significant, so trim the input
+!! string to have trailing whitespace ignored.
+!!
+!!##OPTIONS
+!! string the input string to test to see if it contains the pattern.
+!! pattern the following simple globbing options are available
+!!
+!! o "?" matching any one character
+!! o "*" matching zero or more characters.
+!! Do NOT use adjacent asterisks.
+!! o Both strings may have trailing spaces which
+!! are ignored.
+!! o There is no escape character, so matching strings with
+!! literal question mark and asterisk is problematic.
+!!
+!!##EXAMPLES
+!!
+!! Example program
+!!
+!! program demo_glob
+!! implicit none
+!! ! This main() routine passes a bunch of test strings
+!! ! into the above code. In performance comparison mode,
+!! ! it does that over and over. Otherwise, it does it just
+!! ! once. Either way, it outputs a passed/failed result.
+!! !
+!! integer :: nReps
+!! logical :: allpassed
+!! integer :: i
+!! allpassed = .true.
+!!
+!! nReps = 10000
+!! ! Can choose as many repetitions as you're expecting
+!! ! in the real world.
+!! nReps = 1
+!!
+!! do i=1,nReps
+!! ! Cases with repeating character sequences.
+!! allpassed=allpassed .and. test("a*abab", "a*b", .true.)
+!! !!cycle
+!! allpassed=allpassed .and. test("ab", "*?", .true.)
+!! allpassed=allpassed .and. test("abc", "*?", .true.)
+!! allpassed=allpassed .and. test("abcccd", "*ccd", .true.)
+!! allpassed=allpassed .and. test("bLah", "bLaH", .false.)
+!! allpassed=allpassed .and. test("mississippi", "*sip*", .true.)
+!! allpassed=allpassed .and. &
+!! & test("xxxx*zzzzzzzzy*f", "xxx*zzy*f", .true.)
+!! allpassed=allpassed .and. &
+!! & test("xxxx*zzzzzzzzy*f", "xxxx*zzy*fffff", .false.)
+!! allpassed=allpassed .and. &
+!! & test("mississipissippi", "*issip*ss*", .true.)
+!! allpassed=allpassed .and. &
+!! & test("xxxxzzzzzzzzyf", "xxxx*zzy*fffff", .false.)
+!! allpassed=allpassed .and. &
+!! & test("xxxxzzzzzzzzyf", "xxxx*zzy*f", .true.)
+!! allpassed=allpassed .and. test("xyxyxyzyxyz", "xy*z*xyz", .true.)
+!! allpassed=allpassed .and. test("xyxyxyxyz", "xy*xyz", .true.)
+!! allpassed=allpassed .and. test("mississippi", "mi*sip*", .true.)
+!! allpassed=allpassed .and. test("ababac", "*abac*", .true.)
+!! allpassed=allpassed .and. test("aaazz", "a*zz*", .true.)
+!! allpassed=allpassed .and. test("a12b12", "*12*23", .false.)
+!! allpassed=allpassed .and. test("a12b12", "a12b", .false.)
+!! allpassed=allpassed .and. test("a12b12", "*12*12*", .true.)
+!!
+!! ! Additional cases where the '*' char appears in the tame string.
+!! allpassed=allpassed .and. test("*", "*", .true.)
+!! allpassed=allpassed .and. test("a*r", "a*", .true.)
+!! allpassed=allpassed .and. test("a*ar", "a*aar", .false.)
+!!
+!! ! More double wildcard scenarios.
+!! allpassed=allpassed .and. test("XYXYXYZYXYz", "XY*Z*XYz", .true.)
+!! allpassed=allpassed .and. test("missisSIPpi", "*SIP*", .true.)
+!! allpassed=allpassed .and. test("mississipPI", "*issip*PI", .true.)
+!! allpassed=allpassed .and. test("xyxyxyxyz", "xy*xyz", .true.)
+!! allpassed=allpassed .and. test("miSsissippi", "mi*sip*", .true.)
+!! allpassed=allpassed .and. test("miSsissippi", "mi*Sip*", .false.)
+!! allpassed=allpassed .and. test("abAbac", "*Abac*", .true.)
+!! allpassed=allpassed .and. test("aAazz", "a*zz*", .true.)
+!! allpassed=allpassed .and. test("A12b12", "*12*23", .false.)
+!! allpassed=allpassed .and. test("a12B12", "*12*12*", .true.)
+!! allpassed=allpassed .and. test("oWn", "*oWn*", .true.)
+!!
+!! ! Completely tame (no wildcards) cases.
+!! allpassed=allpassed .and. test("bLah", "bLah", .true.)
+!!
+!! ! Simple mixed wildcard tests suggested by IBMer Marlin Deckert.
+!! allpassed=allpassed .and. test("a", "*?", .true.)
+!!
+!! ! More mixed wildcard tests including coverage for false positives.
+!! allpassed=allpassed .and. test("a", "??", .false.)
+!! allpassed=allpassed .and. test("ab", "?*?", .true.)
+!! allpassed=allpassed .and. test("ab", "*?*?*", .true.)
+!! allpassed=allpassed .and. test("abc", "?**?*?", .true.)
+!! allpassed=allpassed .and. test("abc", "?**?*&?", .false.)
+!! allpassed=allpassed .and. test("abcd", "?b*??", .true.)
+!! allpassed=allpassed .and. test("abcd", "?a*??", .false.)
+!! allpassed=allpassed .and. test("abcd", "?**?c?", .true.)
+!! allpassed=allpassed .and. test("abcd", "?**?d?", .false.)
+!! allpassed=allpassed .and. test("abcde", "?*b*?*d*?", .true.)
+!!
+!! ! Single-character-match cases.
+!! allpassed=allpassed .and. test("bLah", "bL?h", .true.)
+!! allpassed=allpassed .and. test("bLaaa", "bLa?", .false.)
+!! allpassed=allpassed .and. test("bLah", "bLa?", .true.)
+!! allpassed=allpassed .and. test("bLaH", "?Lah", .false.)
+!! allpassed=allpassed .and. test("bLaH", "?LaH", .true.)
+!!
+!! ! Many-wildcard scenarios.
+!! allpassed=allpassed .and. test(&
+!! &"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&
+!! &aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab",&
+!! &"a*a*a*a*a*a*aa*aaa*a*a*b",&
+!! &.true.)
+!! allpassed=allpassed .and. test(&
+!! &"abababababababababababababababababababaacacacacacacac&
+!! &adaeafagahaiajakalaaaaaaaaaaaaaaaaaffafagaagggagaaaaaaaab",&
+!! &"*a*b*ba*ca*a*aa*aaa*fa*ga*b*",&
+!! &.true.)
+!! allpassed=allpassed .and. test(&
+!! &"abababababababababababababababababababaacacacacacaca&
+!! &cadaeafagahaiajakalaaaaaaaaaaaaaaaaaffafagaagggagaaaaaaaab",&
+!! &"*a*b*ba*ca*a*x*aaa*fa*ga*b*",&
+!! &.false.)
+!! allpassed=allpassed .and. test(&
+!! &"abababababababababababababababababababaacacacacacacacad&
+!! &aeafagahaiajakalaaaaaaaaaaaaaaaaaffafagaagggagaaaaaaaab",&
+!! &"*a*b*ba*ca*aaaa*fa*ga*gggg*b*",&
+!! &.false.)
+!! allpassed=allpassed .and. test(&
+!! &"abababababababababababababababababababaacacacacacacacad&
+!! &aeafagahaiajakalaaaaaaaaaaaaaaaaaffafagaagggagaaaaaaaab",&
+!! &"*a*b*ba*ca*aaaa*fa*ga*ggg*b*",&
+!! &.true.)
+!! allpassed=allpassed .and. test("aaabbaabbaab", "*aabbaa*a*", .true.)
+!! allpassed=allpassed .and. &
+!! test("a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*",&
+!! &"a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*", .true.)
+!! allpassed=allpassed .and. test("aaaaaaaaaaaaaaaaa",&
+!! &"*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*", .true.)
+!! allpassed=allpassed .and. test("aaaaaaaaaaaaaaaa",&
+!! &"*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*", .false.)
+!! allpassed=allpassed .and. test(&
+!! &"abc*abcd*abcde*abcdef*abcdefg*abcdefgh*abcdefghi*abcdefghij&
+!! &*abcdefghijk*abcdefghijkl*abcdefghijklm*abcdefghijklmn",&
+!! & "abc*abc*abc*abc*abc*abc*abc*abc*abc*abc*abc*abc*abc*abc&
+!! &*abc*abc*abc*",&
+!! &.false.)
+!! allpassed=allpassed .and. test(&
+!! &"abc*abcd*abcde*abcdef*abcdefg*abcdefgh*abcdefghi*abcdefghij&
+!! &*abcdefghijk*abcdefghijkl*abcdefghijklm*abcdefghijklmn",&
+!! &"abc*abc*abc*abc*abc*abc*abc*abc*abc*abc*abc*abc*",&
+!! &.true.)
+!! allpassed=allpassed .and. test("abc*abcd*abcd*abc*abcd",&
+!! &"abc*abc*abc*abc*abc", .false.)
+!! allpassed=allpassed .and. test( "abc*abcd*abcd*abc*abcd*abcd&
+!! &*abc*abcd*abc*abc*abcd", &
+!! &"abc*abc*abc*abc*abc*abc*abc*abc*abc*abc*abcd",&
+!! &.true.)
+!! allpassed=allpassed .and. test("abc",&
+!! &"********a********b********c********", .true.)
+!! allpassed=allpassed .and.&
+!! &test("********a********b********c********", "abc", .false.)
+!! allpassed=allpassed .and. &
+!! &test("abc", "********a********b********b********", .false.)
+!! allpassed=allpassed .and. test("*abc*", "***a*b*c***", .true.)
+!!
+!! ! A case-insensitive algorithm test.
+!! ! allpassed=allpassed .and. test("mississippi", "*issip*PI", .true.)
+!! enddo
+!!
+!! if (allpassed)then
+!! write(*,'(a)')"Passed",nReps
+!! else
+!! write(*,'(a)')"Failed"
+!! endif
+!! contains
+!! ! This is a test program for wildcard matching routines.
+!! ! It can be used either to test a single routine for correctness,
+!! ! or to compare the timings of two (or more) different wildcard
+!! ! matching routines.
+!! !
+!! function test(tame, wild, bExpectedResult) result(bpassed)
+!! use fpm_strings, only : glob
+!! character(len=*) :: tame
+!! character(len=*) :: wild
+!! logical :: bExpectedResult
+!! logical :: bResult
+!! logical :: bPassed
+!! bResult = .true. ! We'll do "&=" cumulative checking.
+!! bPassed = .false. ! Assume the worst.
+!! write(*,*)repeat('=',79)
+!! bResult = glob(tame, wild) ! Call a wildcard matching routine.
+!!
+!! ! To assist correctness checking, output the two strings in any
+!! ! failing scenarios.
+!! if (bExpectedResult .eqv. bResult) then
+!! bPassed = .true.
+!! if(nReps == 1) write(*,*)"Passed match on ",tame," vs. ", wild
+!! else
+!! if(nReps == 1) write(*,*)"Failed match on ",tame," vs. ", wild
+!! endif
+!!
+!! end function test
+!! end program demo_glob
+!!
+!! Expected output
+!!
+!!##AUTHOR
+!! John S. Urban
+!!
+!!##REFERENCE
+!! The article "Matching Wildcards: An Empirical Way to Tame an Algorithm"
+!! in Dr Dobb's Journal, By Kirk J. Krauss, October 07, 2014
+!!
+!!##LICENSE
+!! Public Domain
+
+! @(#)fpm_strings::glob(3f): function compares text strings, one of which can have wildcards ('*' or '?').
+
+logical :: glob
+character(len=*) :: tame ! A string without wildcards
+character(len=*) :: wild ! A (potentially) corresponding string with wildcards
+character(len=len(tame)+1) :: tametext
+character(len=len(wild)+1) :: wildtext
+character(len=1),parameter :: NULL=char(0)
+integer :: wlen
+integer :: ti, wi
+integer :: i
+character(len=:),allocatable :: tbookmark, wbookmark
+! These two values are set when we observe a wildcard character. They
+! represent the locations, in the two strings, from which we start once we've observed it.
+ tametext=tame//NULL
+ wildtext=wild//NULL
+ tbookmark = NULL
+ wbookmark = NULL
+ wlen=len(wild)
+ wi=1
+ ti=1
+ do ! Walk the text strings one character at a time.
+ if(wildtext(wi:wi) == '*')then ! How do you match a unique text string?
+ do i=wi,wlen ! Easy: unique up on it!
+ if(wildtext(wi:wi).eq.'*')then
+ wi=wi+1
+ else
+ exit
+ endif
+ enddo
+ if(wildtext(wi:wi).eq.NULL) then ! "x" matches "*"
+ glob=.true.
+ return
+ endif
+ if(wildtext(wi:wi) .ne. '?') then
+ ! Fast-forward to next possible match.
+ do while (tametext(ti:ti) .ne. wildtext(wi:wi))
+ ti=ti+1
+ if (tametext(ti:ti).eq.NULL)then
+ glob=.false.
+ return ! "x" doesn't match "*y*"
+ endif
+ enddo
+ endif
+ wbookmark = wildtext(wi:)
+ tbookmark = tametext(ti:)
+ elseif(tametext(ti:ti) .ne. wildtext(wi:wi) .and. wildtext(wi:wi) .ne. '?') then
+ ! Got a non-match. If we've set our bookmarks, back up to one or both of them and retry.
+ if(wbookmark.ne.NULL) then
+ if(wildtext(wi:).ne. wbookmark) then
+ wildtext = wbookmark;
+ wlen=len_trim(wbookmark)
+ wi=1
+ ! Don't go this far back again.
+ if (tametext(ti:ti) .ne. wildtext(wi:wi)) then
+ tbookmark=tbookmark(2:)
+ tametext = tbookmark
+ ti=1
+ cycle ! "xy" matches "*y"
+ else
+ wi=wi+1
+ endif
+ endif
+ if (tametext(ti:ti).ne.NULL) then
+ ti=ti+1
+ cycle ! "mississippi" matches "*sip*"
+ endif
+ endif
+ glob=.false.
+ return ! "xy" doesn't match "x"
+ endif
+ ti=ti+1
+ wi=wi+1
+ if (tametext(ti:ti).eq.NULL) then ! How do you match a tame text string?
+ if(wildtext(wi:wi).ne.NULL)then
+ do while (wildtext(wi:wi) == '*') ! The tame way: unique up on it!
+ wi=wi+1 ! "x" matches "x*"
+ if(wildtext(wi:wi).eq.NULL)exit
+ enddo
+ endif
+ if (wildtext(wi:wi).eq.NULL)then
+ glob=.true.
+ return ! "x" matches "x"
+ endif
+ glob=.false.
+ return ! "x" doesn't match "xy"
+ endif
+ enddo
+end function glob
+
pure integer function str_int_len(i) result(sz)
! Returns the length of the string representation of 'i'
integer, intent(in) :: i
diff --git a/fpm/test/help_test/help_test.f90 b/fpm/test/help_test/help_test.f90
index a44786c..8f0c455 100644
--- a/fpm/test/help_test/help_test.f90
+++ b/fpm/test/help_test/help_test.f90
@@ -2,6 +2,8 @@ program help_test
! note hardcoded len=k1 instead of len=: in this test is a work-around a gfortran bug in old
! pre-v8.3 versions
use,intrinsic :: iso_fortran_env, only : stdin=>input_unit, stdout=>output_unit, stderr=>error_unit
+use fpm_filesystem, only : dirname, join_path, exists
+use fpm_environment, only : get_os_type, OS_WINDOWS
implicit none
integer :: i, j
integer :: be, af
@@ -11,7 +13,7 @@ integer,parameter :: k1=132
character(len=k1) :: message
logical,allocatable :: tally(:)
!intel-bug!character(len=:),allocatable :: book1(:), book2(:)
-character(len=k1),allocatable :: book1(:), book2(:), book3(:)
+character(len=k1),allocatable :: book1(:), book2(:)
!intel-bug!character(len=:),allocatable :: page1(:)
character(len=k1),allocatable :: page1(:)
integer :: lines
@@ -20,58 +22,57 @@ integer :: chars
character(len=*),parameter :: cmds(*) = [character(len=80) :: &
! build manual as pieces using various help commands
! debug version
-'fpm run -- --version ',& ! verify fpm version being used
-'fpm run -- --help > fpm_scratch_help.txt',&
-'fpm run -- help new >> fpm_scratch_help.txt',&
-'fpm run -- help update >> fpm_scratch_help.txt',&
-'fpm run -- build --help >> fpm_scratch_help.txt',&
-'fpm run -- help run >> fpm_scratch_help.txt',&
-'fpm run -- help test >> fpm_scratch_help.txt',&
-'fpm run -- help runner >> fpm_scratch_help.txt',&
-'fpm run -- help install >> fpm_scratch_help.txt',&
-'fpm run -- help list >> fpm_scratch_help.txt',&
-'fpm run -- help help >> fpm_scratch_help.txt',&
-'fpm run -- --version >> fpm_scratch_help.txt',&
-! release version
-'fpm run --release -- --version ',& ! verify fpm version being used
-'fpm run --release -- --help > fpm_scratch_help3.txt',&
-'fpm run --release -- help new >> fpm_scratch_help3.txt',&
-'fpm run --release -- help update >> fpm_scratch_help3.txt',&
-'fpm run --release -- build --help >> fpm_scratch_help3.txt',&
-'fpm run --release -- help run >> fpm_scratch_help3.txt',&
-'fpm run --release -- help test >> fpm_scratch_help3.txt',&
-'fpm run --release -- help runner >> fpm_scratch_help3.txt',&
-'fpm run --release -- help install >> fpm_scratch_help3.txt',&
-'fpm run --release -- help list >> fpm_scratch_help3.txt',&
-'fpm run --release -- help help >> fpm_scratch_help3.txt',&
-'fpm run --release -- --version >> fpm_scratch_help3.txt',&
+' --version ',& ! verify fpm version being used
+' --help > fpm_scratch_help.txt',&
+' help new >> fpm_scratch_help.txt',&
+' help update >> fpm_scratch_help.txt',&
+' build --help >> fpm_scratch_help.txt',&
+' help run >> fpm_scratch_help.txt',&
+' help test >> fpm_scratch_help.txt',&
+' help runner >> fpm_scratch_help.txt',&
+' help install >> fpm_scratch_help.txt',&
+' help list >> fpm_scratch_help.txt',&
+' help help >> fpm_scratch_help.txt',&
+' --version >> fpm_scratch_help.txt',&
! generate manual
-'fpm run -- help manual > fpm_scratch_manual.txt']
+' help manual > fpm_scratch_manual.txt']
!'fpm run >> fpm_scratch_help.txt',&
!'fpm run -- --list >> fpm_scratch_help.txt',&
!'fpm run -- list --list >> fpm_scratch_help.txt',&
character(len=*),parameter :: names(*)=[character(len=10) ::&
'fpm','new','update','build','run','test','runner','install','list','help']
-character(len=:),allocatable :: add
+character(len=:), allocatable :: prog
+integer :: length
+
+ ! FIXME: Super hacky way to get the name of the fpm executable,
+ ! it works better than invoking fpm again but should be replaced ASAP.
+ call get_command_argument(0, length=length)
+ allocate(character(len=length) :: prog)
+ call get_command_argument(0, prog)
+ path = dirname(prog)
+ if (get_os_type() == OS_WINDOWS) then
+ prog = join_path(path, "..", "app", "fpm.exe")
+ if (.not.exists(prog)) then
+ prog = join_path(path, "..", "..", "app", "fpm.exe")
+ end if
+ else
+ prog = join_path(path, "..", "app", "fpm")
+ if (.not.exists(prog)) then
+ prog = join_path(path, "..", "..", "app", "fpm")
+ end if
+ end if
write(*,'(g0:,1x)')'<INFO>TEST help SUBCOMMAND STARTED'
if(allocated(tally))deallocate(tally)
allocate(tally(0))
call wipe('fpm_scratch_help.txt')
- call wipe('fpm_scratch_help3.txt')
call wipe('fpm_scratch_manual.txt')
! check that output has NAME SYNOPSIS DESCRIPTION
- do j=1,2
- if(j.eq.1)then
- ADD=' '
- else
- ADD=' --release '
- endif
do i=1,size(names)
write(*,*)'<INFO>check '//names(i)//' for NAME SYNOPSIS DESCRIPTION'
- path= 'fpm run '//add//' -- help '//names(i)//' >fpm_scratch_help.txt'
+ path= prog // ' help '//names(i)//' >fpm_scratch_help.txt'
message=''
call execute_command_line(path,exitstat=estat,cmdstat=cstat,cmdmsg=message)
write(*,'(*(g0))')'<INFO>CMD=',path,' EXITSTAT=',estat,' CMDSTAT=',cstat,' MESSAGE=',trim(message)
@@ -99,13 +100,12 @@ character(len=:),allocatable :: add
write(*,*)'<INFO>have completed ',count(tally),' tests'
call wipe('fpm_scratch_help.txt')
enddo
- enddo
! execute the fpm(1) commands
do i=1,size(cmds)
message=''
- path= cmds(i)
+ path= prog // cmds(i)
call execute_command_line(path,exitstat=estat,cmdstat=cstat,cmdmsg=message)
write(*,'(*(g0))')'<INFO>CMD=',path,' EXITSTAT=',estat,' CMDSTAT=',cstat,' MESSAGE=',trim(message)
tally=[tally,all([estat.eq.0,cstat.eq.0])]
@@ -114,14 +114,11 @@ character(len=:),allocatable :: add
! compare book written in fragments with manual
call swallow('fpm_scratch_help.txt',book1)
call swallow('fpm_scratch_manual.txt',book2)
- call swallow('fpm_scratch_help3.txt',book3)
! get rid of lines from run() which is not on stderr at the moment
book1=pack(book1,index(book1,' + build/').eq.0)
book2=pack(book1,index(book2,' + build/').eq.0)
- book3=pack(book3,index(book3,' + build/').eq.0)
write(*,*)'<INFO>book1 ',size(book1), len(book1)
write(*,*)'<INFO>book2 ',size(book2), len(book2)
- write(*,*)'<INFO>book2 ',size(book3), len(book3)
if(size(book1).ne.size(book2))then
write(*,*)'<ERROR>manual and "debug" appended pages are not the same size'
tally=[tally,.false.]
@@ -134,18 +131,6 @@ character(len=:),allocatable :: add
tally=[tally,.true.]
endif
endif
- if(size(book3).ne.size(book2))then
- write(*,*)'<ERROR>manual and "release" appended pages are not the same size'
- tally=[tally,.false.]
- else
- if(all(book3.ne.book2))then
- tally=[tally,.false.]
- write(*,*)'<ERROR>manual and "release" appended pages are not the same'
- else
- write(*,*)'<INFO>manual and "release" appended pages are the same'
- tally=[tally,.true.]
- endif
- endif
! overall size of manual
!chars=size(book2)
@@ -159,19 +144,9 @@ character(len=:),allocatable :: add
write(*,*)'<INFO>"debug" manual size in bytes=',chars,' lines=',lines
tally=[tally,.true.]
endif
- chars=sum(len_trim(book3)) ! SUM TRIMMED LENGTH
- lines=size(book3)
- if( (chars.lt.12000) .or. (lines.lt.350) )then
- write(*,*)'<ERROR>"release" manual is suspiciously small, bytes=',chars,' lines=',lines
- tally=[tally,.false.]
- else
- write(*,*)'<INFO>"release" manual size in bytes=',chars,' lines=',lines
- tally=[tally,.true.]
- endif
write(*,'("<INFO>HELP TEST TALLY=",*(g0))')tally
call wipe('fpm_scratch_help.txt')
- call wipe('fpm_scratch_help3.txt')
call wipe('fpm_scratch_manual.txt')
if(all(tally))then
write(*,'(*(g0))')'<INFO>PASSED: all ',count(tally),' tests passed '
diff --git a/fpm/test/new_test/new_test.f90 b/fpm/test/new_test/new_test.f90
index 4ff00c3..3c8c453 100644
--- a/fpm/test/new_test/new_test.f90
+++ b/fpm/test/new_test/new_test.f90
@@ -1,6 +1,7 @@
program new_test
use,intrinsic :: iso_fortran_env, only : stdin=>input_unit, stdout=>output_unit, stderr=>error_unit
-use fpm_filesystem, only : is_dir, list_files, exists, windows_path, join_path
+use fpm_filesystem, only : is_dir, list_files, exists, windows_path, join_path, &
+ dirname
use fpm_strings, only : string_t, operator(.in.)
use fpm_environment, only : run, get_os_type
use fpm_environment, only : OS_UNKNOWN, OS_LINUX, OS_MACOS, OS_CYGWIN, OS_SOLARIS, OS_FREEBSD, OS_WINDOWS
@@ -158,18 +159,29 @@ logical :: IS_OS_WINDOWS
stop 5
endif
contains
- function get_command_path() result(command_path)
- character(len=:), allocatable :: command_path
+ function get_command_path() result(prog)
+ character(len=:), allocatable :: prog
- type(string_t), allocatable :: files(:)
- integer :: i
+ character(len=:), allocatable :: path
+ integer :: length
- call list_files("build", files)
- do i = 1, size(files)
- if (index(files(i)%s, "gfortran") > 0) then
- command_path = join_path(files(i)%s, "app", "fpm")
- return
+ ! FIXME: Super hacky way to get the name of the fpm executable,
+ ! it works better than invoking fpm again but should be replaced ASAP.
+ call get_command_argument(0, length=length)
+ allocate(character(len=length) :: prog)
+ call get_command_argument(0, prog)
+ path = dirname(prog)
+ if (get_os_type() == OS_WINDOWS) then
+ prog = join_path(path, "..", "app", "fpm.exe")
+ if (.not.exists(prog)) then
+ prog = join_path(path, "..", "..", "app", "fpm.exe")
end if
- end do
+ else
+ prog = join_path(path, "..", "app", "fpm")
+ if (.not.exists(prog)) then
+ prog = join_path(path, "..", "..", "app", "fpm")
+ end if
+ end if
+
end function
end program new_test
diff --git a/install.sh b/install.sh
index 578b156..de2aaa8 100755
--- a/install.sh
+++ b/install.sh
@@ -1,33 +1,131 @@
#!/bin/sh
-set -u # error on use of undefined variable
set -e # exit on error
-install_path="$HOME/.local/bin"
+usage()
+{
+ echo "Fortran Package Manager Bootstrap Script"
+ echo ""
+ echo "USAGE:"
+ echo "./install.sh [--help | [--prefix=PREFIX] [--update[=REF]]"
+ echo " [--no-openmp] [--static] [--haskell] ]"
+ echo ""
+ echo " --help Display this help text"
+ echo " --prefix=PREFIX Install binary in 'PREFIX/bin'"
+ echo " Default prefix='\$HOME/.local/bin'"
+ echo " --update[=REF] Update repository from latest release tag"
+ echo " or from git reference REF if specified"
+ echo " --no-openmp Don't build fpm with openmp support"
+ echo " --static Statically link fpm executable"
+ echo " (implies --no-openmp)"
+ echo " --haskell Only install Haskell fpm"
+ echo ""
+ echo " '--no-openmp' and '--static' do not affect the Haskell fpm"
+ echo " build."
+ echo ""
+}
+
+PREFIX="$HOME/.local"
+UPDATE=false
+OMP=true
+STATIC=false
+HASKELL_ONLY=false
+
+STACK_BIN_PATH="$HOME/.local/bin"
+REF=$(git describe --tag --abbrev=0)
+RELEASE_FLAGS="--flag -g --flag -fbacktrace --flag -O3"
+
+while [ "$1" != "" ]; do
+ PARAM=$(echo "$1" | awk -F= '{print $1}')
+ VALUE=$(echo "$1" | awk -F= '{print $2}')
+ case $PARAM in
+ -h | --help)
+ usage
+ exit
+ ;;
+ --prefix)
+ PREFIX=$VALUE
+ ;;
+ --update)
+ UPDATE=true
+ if [ "$VALUE" != "" ]; then
+ REF=$VALUE
+ fi
+ ;;
+ --no-openmp)
+ OMP=false
+ ;;
+ --static)
+ STATIC=true
+ OMP=false
+ ;;
+ --haskell)
+ HASKELL_ONLY=true
+ ;;
+ *)
+ echo "ERROR: unknown parameter \"$PARAM\""
+ usage
+ exit 1
+ ;;
+ esac
+ shift
+done
+
+set -u # error on use of undefined variable
+
+INSTALL_PATH="$PREFIX/bin"
if command -v stack 1> /dev/null 2>&1 ; then
- echo "found stack"
+ echo "Found stack"
else
echo "Haskell stack not found."
- echo "Installing Haskell stack to."
+ echo "Installing Haskell stack"
curl -sSL https://get.haskellstack.org/ | sh
if command -v stack 1> /dev/null 2>&1 ; then
echo "Haskell stack installation successful."
else
- echo "Haskell stack installation unsuccessful."
+ echo "ERROR: Haskell stack installation unsuccessful."
exit 1
fi
fi
-if [ -x "$install_path/fpm" ]; then
- echo "Overwriting existing fpm installation in $install_path"
+if [ -x "$INSTALL_PATH/fpm" ]; then
+ echo "Overwriting existing fpm installation in $INSTALL_PATH"
+fi
+
+if [ "$UPDATE" = true ]; then
+ git checkout "$REF"
+ if [ $? != 0 ]; then
+ echo "ERROR: Unable to checkout $REF."
+ exit 1
+ fi
fi
cd bootstrap
stack install
-if [ -x "$install_path/fpm" ]; then
- echo "fpm installed successfully to $install_path"
+if [ "$STACK_BIN_PATH" != "$INSTALL_PATH" ]; then
+ mv "$STACK_BIN_PATH/fpm" "$INSTALL_PATH/"
+fi
+
+if [ "$HASKELL_ONLY" = true ]; then
+ exit
+fi
+
+if [ "$STATIC" = true ]; then
+ RELEASE_FLAGS="$RELEASE_FLAGS --flag -static"
+fi
+
+if [ "$OMP" = true ]; then
+ RELEASE_FLAGS="$RELEASE_FLAGS --flag -fopenmp"
+fi
+
+cd ../fpm
+"$INSTALL_PATH/fpm" run $RELEASE_FLAGS --runner mv -- "$INSTALL_PATH/"
+
+if [ -x "$INSTALL_PATH/fpm" ]; then
+ echo "fpm installed successfully to $INSTALL_PATH"
else
- echo "fpm installation unsuccessful: fpm not found in $install_path"
+ echo "ERROR: fpm installation unsuccessful: fpm not found in $INSTALL_PATH"
+ exit 1
fi