Skip to content

Commit 7535cab

Browse files
authored
[meta] refactor: process command line args as arrays (#1123)
2 parents de52814 + 6b8e259 commit 7535cab

File tree

4 files changed

+152
-50
lines changed

4 files changed

+152
-50
lines changed

src/fpm_compiler.F90

Lines changed: 84 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,15 @@ module fpm_compiler
3939
OS_UNKNOWN
4040
use fpm_filesystem, only: join_path, basename, get_temp_filename, delete_file, unix_path, &
4141
& getline, run
42-
use fpm_strings, only: split, string_cat, string_t, str_ends_with, str_begins_with_str
42+
use fpm_strings, only: split, string_cat, string_t, str_ends_with, str_begins_with_str, &
43+
& string_array_contains
4344
use fpm_manifest, only : package_config_t
4445
use fpm_error, only: error_t, fatal_error
4546
use fpm_toml, only: serializable_t, toml_table, set_string, set_value, toml_stat, get_value
47+
use shlex_module, only: shlex_split => split
4648
implicit none
4749
public :: compiler_t, new_compiler, archiver_t, new_archiver, get_macros
50+
public :: append_clean_flags, append_clean_flags_array
4851
public :: debug
4952

5053
enum, bind(C)
@@ -1071,17 +1074,17 @@ subroutine new_archiver(self, ar, echo, verbose)
10711074
! Attempt "ar"
10721075
call execute_command_line("ar --version > "//get_temp_filename()//" 2>&1", &
10731076
& exitstat=estat)
1074-
1075-
if (estat == 0) then
1076-
1077+
1078+
if (estat == 0) then
1079+
10771080
self%ar = "ar"//arflags
1078-
1081+
10791082
else
1080-
1083+
10811084
! Then "gcc-ar"
10821085
call execute_command_line("gcc-ar --version > "//get_temp_filename()//" 2>&1", &
1083-
& exitstat=estat)
1084-
1086+
& exitstat=estat)
1087+
10851088
if (estat /= 0) then
10861089
self%ar = "lib"//libflags
10871090
else
@@ -1440,38 +1443,38 @@ end function compiler_name
14401443
logical function check_fortran_source_runs(self, input) result(success)
14411444
!> Instance of the compiler object
14421445
class(compiler_t), intent(in) :: self
1443-
!> Program Source
1446+
!> Program Source
14441447
character(len=*), intent(in) :: input
1445-
1448+
14461449
integer :: stat,unit
14471450
character(:), allocatable :: source,object,logf,exe
1448-
1451+
14491452
success = .false.
1450-
1453+
14511454
!> Create temporary source file
14521455
exe = get_temp_filename()
14531456
source = exe//'.f90'
14541457
object = exe//'.o'
14551458
logf = exe//'.log'
14561459
open(newunit=unit, file=source, action='readwrite', iostat=stat)
14571460
if (stat/=0) return
1458-
1461+
14591462
!> Write contents
14601463
write(unit,*) input
1461-
close(unit)
1462-
1463-
!> Compile and link program
1464+
close(unit)
1465+
1466+
!> Compile and link program
14641467
call self%compile_fortran(source, object, self%get_default_flags(release=.false.), logf, stat)
14651468
if (stat==0) &
14661469
call self%link(exe, self%get_default_flags(release=.false.)//" "//object, logf, stat)
1467-
1468-
!> Run and retrieve exit code
1470+
1471+
!> Run and retrieve exit code
14691472
if (stat==0) &
14701473
call run(exe,echo=.false., exitstat=stat, verbose=.false., redirect=logf)
1471-
1474+
14721475
!> Successful exit on 0 exit code
14731476
success = stat==0
1474-
1477+
14751478
!> Delete files
14761479
open(newunit=unit, file=source, action='readwrite', iostat=stat)
14771480
close(unit,status='delete')
@@ -1481,23 +1484,81 @@ logical function check_fortran_source_runs(self, input) result(success)
14811484
close(unit,status='delete')
14821485
open(newunit=unit, file=exe, action='readwrite', iostat=stat)
14831486
close(unit,status='delete')
1484-
1487+
14851488
end function check_fortran_source_runs
14861489

1487-
!> Check if the current compiler supports 128-bit real precision
1490+
!> Check if the current compiler supports 128-bit real precision
14881491
logical function with_qp(self)
14891492
!> Instance of the compiler object
14901493
class(compiler_t), intent(in) :: self
14911494
with_qp = self%check_fortran_source_runs &
14921495
('if (selected_real_kind(33) == -1) stop 1; end')
14931496
end function with_qp
14941497

1495-
!> Check if the current compiler supports 80-bit "extended" real precision
1498+
!> Check if the current compiler supports 80-bit "extended" real precision
14961499
logical function with_xdp(self)
14971500
!> Instance of the compiler object
14981501
class(compiler_t), intent(in) :: self
14991502
with_xdp = self%check_fortran_source_runs &
15001503
('if (any(selected_real_kind(18) == [-1, selected_real_kind(33)])) stop 1; end')
15011504
end function with_xdp
15021505

1506+
!> Append new flags to existing flags, removing duplicates and empty flags (string version)
1507+
subroutine append_clean_flags(flags, new_flags)
1508+
character(:), intent(inout), allocatable :: flags
1509+
character(*), intent(in) :: new_flags
1510+
1511+
type(string_t), allocatable :: flags_array(:), new_flags_array(:)
1512+
integer :: i
1513+
1514+
call tokenize_flags(flags, flags_array)
1515+
call tokenize_flags(new_flags, new_flags_array)
1516+
1517+
call append_clean_flags_array(flags_array, new_flags_array)
1518+
1519+
do i = 1, size(flags_array)
1520+
flags = flags // " " // flags_array(i)%s
1521+
end do
1522+
end subroutine append_clean_flags
1523+
1524+
!> Append new flags to existing flags, removing duplicates and empty flags (array version)
1525+
subroutine append_clean_flags_array(flags_array, new_flags_array)
1526+
type(string_t), allocatable, intent(inout) :: flags_array(:)
1527+
type(string_t), intent(in) :: new_flags_array(:)
1528+
1529+
integer :: i
1530+
1531+
do i = 1, size(new_flags_array)
1532+
if (string_array_contains(new_flags_array(i)%s, flags_array)) cycle
1533+
! Filter out empty flags and arguments
1534+
if (new_flags_array(i)%s == "") cycle
1535+
if (trim(new_flags_array(i)%s) == "-l") cycle
1536+
if (trim(new_flags_array(i)%s) == "-L") cycle
1537+
if (trim(new_flags_array(i)%s) == "-I") cycle
1538+
if (trim(new_flags_array(i)%s) == "-J") cycle
1539+
if (trim(new_flags_array(i)%s) == "-M") cycle
1540+
flags_array = [flags_array, new_flags_array(i)]
1541+
end do
1542+
end subroutine append_clean_flags_array
1543+
1544+
!> Tokenize a string into an array of compiler flags
1545+
subroutine tokenize_flags(flags, flags_array)
1546+
character(*), intent(in) :: flags
1547+
type(string_t), allocatable, intent(out) :: flags_array(:)
1548+
character(len=:), allocatable :: flags_char_array(:)
1549+
1550+
integer :: i
1551+
logical :: success
1552+
1553+
flags_char_array = shlex_split(flags, join_spaced=.true., keep_quotes=.true., success=success)
1554+
if (.not. success) then
1555+
allocate(flags_array(0))
1556+
return
1557+
end if
1558+
allocate(flags_array(size(flags_char_array)))
1559+
do i = 1, size(flags_char_array)
1560+
flags_array(i)%s = trim(adjustl(flags_char_array(i)))
1561+
end do
1562+
end subroutine tokenize_flags
1563+
15031564
end module fpm_compiler

src/metapackage/fpm_meta_base.f90

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ module fpm_meta_base
55
use fpm_command_line, only: fpm_cmd_settings, fpm_run_settings
66
use fpm_manifest_dependency, only: dependency_config_t
77
use fpm_manifest, only: package_config_t
8-
use fpm_strings, only: string_t, len_trim
8+
use fpm_strings, only: string_t, len_trim, split, join
9+
use fpm_compiler, only: append_clean_flags, append_clean_flags_array
910

1011
implicit none
1112

@@ -111,30 +112,30 @@ subroutine resolve_model(self,model,error)
111112

112113
! Add global build flags, to apply to all sources
113114
if (self%has_build_flags) then
114-
model%fortran_compile_flags = model%fortran_compile_flags//self%flags%s
115-
model%c_compile_flags = model%c_compile_flags//self%flags%s
116-
model%cxx_compile_flags = model%cxx_compile_flags//self%flags%s
115+
call append_clean_flags(model%fortran_compile_flags, self%flags%s)
116+
call append_clean_flags(model%c_compile_flags, self%flags%s)
117+
call append_clean_flags(model%cxx_compile_flags, self%flags%s)
117118
endif
118119

119120
! Add language-specific flags
120-
if (self%has_fortran_flags) model%fortran_compile_flags = model%fortran_compile_flags//self%fflags%s
121-
if (self%has_c_flags) model%c_compile_flags = model%c_compile_flags//self%cflags%s
122-
if (self%has_cxx_flags) model%cxx_compile_flags = model%cxx_compile_flags//self%cxxflags%s
121+
if (self%has_fortran_flags) call append_clean_flags(model%fortran_compile_flags, self%fflags%s)
122+
if (self%has_c_flags) call append_clean_flags(model%c_compile_flags, self%cflags%s)
123+
if (self%has_cxx_flags) call append_clean_flags(model%cxx_compile_flags, self%cxxflags%s)
123124

124125
if (self%has_link_flags) then
125-
model%link_flags = model%link_flags//' '//self%link_flags%s
126+
call append_clean_flags(model%link_flags, self%link_flags%s)
126127
end if
127128

128129
if (self%has_link_libraries) then
129-
model%link_libraries = [model%link_libraries,self%link_libs]
130+
call append_clean_flags_array(model%link_libraries, self%link_libs)
130131
end if
131132

132133
if (self%has_include_dirs) then
133-
model%include_dirs = [model%include_dirs,self%incl_dirs]
134+
call append_clean_flags_array(model%include_dirs, self%incl_dirs)
134135
end if
135136

136137
if (self%has_external_modules) then
137-
model%external_modules = [model%external_modules,self%external_modules]
138+
call append_clean_flags_array(model%external_modules, self%external_modules)
138139
end if
139140

140141
end subroutine resolve_model
@@ -185,6 +186,6 @@ pure function dn(bool)
185186
end if
186187
end function dn
187188

188-
189189
end subroutine resolve_package_config
190+
190191
end module fpm_meta_base

src/metapackage/fpm_meta_util.f90

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ subroutine add_pkg_config_compile_options(this, name, include_flag, libdir, erro
2121
character(len=*), intent(in) :: include_flag
2222
type(error_t), allocatable, intent(out) :: error
2323

24-
character(len=:), allocatable :: libdir, ext, pref
25-
type(string_t) :: log, this_lib
24+
character(len=:), allocatable :: libdir
25+
type(string_t) :: log, current_include_dir, current_lib
2626
type(string_t), allocatable :: libs(:), flags(:)
2727
integer :: i
2828

@@ -42,8 +42,10 @@ subroutine add_pkg_config_compile_options(this, name, include_flag, libdir, erro
4242
libdir = ""
4343
do i = 1, size(libs)
4444
if (str_begins_with_str(libs(i)%s, '-l')) then
45+
current_lib = string_t(libs(i)%s(3:))
46+
if (len_trim(current_lib%s) == 0) cycle
4547
this%has_link_libraries = .true.
46-
this%link_libs = [this%link_libs, string_t(libs(i)%s(3:))]
48+
this%link_libs = [this%link_libs, current_lib]
4749
else ! -L and others: concatenate
4850
this%has_link_flags = .true.
4951
this%link_flags = string_t(trim(this%link_flags%s)//' '//libs(i)%s)
@@ -63,8 +65,10 @@ subroutine add_pkg_config_compile_options(this, name, include_flag, libdir, erro
6365

6466
do i = 1, size(flags)
6567
if (str_begins_with_str(flags(i)%s, include_flag)) then
68+
current_include_dir = string_t(flags(i)%s(len(include_flag)+1:))
69+
if (len_trim(current_include_dir%s) == 0) cycle
6670
this%has_include_dirs = .true.
67-
this%incl_dirs = [this%incl_dirs, string_t(flags(i)%s(len(include_flag)+1:))]
71+
this%incl_dirs = [this%incl_dirs, current_include_dir]
6872
else
6973
this%has_build_flags = .true.
7074
this%flags = string_t(trim(this%flags%s)//' '//flags(i)%s)

test/fpm_test/test_compiler.f90

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ module test_compiler
33
use testsuite, only : new_unittest, unittest_t, error_t, test_failed, &
44
& check_string
55
use fpm_environment, only : OS_WINDOWS, OS_LINUX
6-
use fpm_compiler , only : compiler_t, new_compiler
6+
use fpm_compiler , only : compiler_t, new_compiler, tokenize_flags
7+
use fpm_strings , only : string_t
78
use fpm_command_line, only: get_fpm_env
89
implicit none
910
private
@@ -19,38 +20,73 @@ subroutine collect_compiler(testsuite)
1920
type(unittest_t), allocatable, intent(out) :: testsuite(:)
2021

2122
testsuite = [ &
22-
& new_unittest("check-fortran-source-runs", test_check_fortran_source_runs)]
23+
& new_unittest("check-fortran-source-runs", test_check_fortran_source_runs), &
24+
& new_unittest("tokenize-flags", test_tokenize_flags)]
2325

2426
end subroutine collect_compiler
25-
27+
2628
subroutine test_check_fortran_source_runs(error)
2729
!> Error handling
2830
type(error_t), allocatable, intent(out) :: error
29-
31+
3032
character(:), allocatable :: fc,cc,cxx
31-
32-
33+
34+
3335
type(compiler_t) :: compiler
3436

3537
!> Get default compiler
3638
fc = get_fpm_env("FC", default="gfortran")
3739
cc = get_fpm_env("CC", default=" ")
3840
cxx = get_fpm_env("CXX", default=" ")
39-
41+
4042
call new_compiler(compiler, fc, cc, cxx, echo=.false., verbose=.false.)
4143

4244
if (compiler%is_unknown()) then
4345
call test_failed(error, "Cannot initialize Fortran compiler")
4446
return
4547
end if
46-
48+
4749
!> Test fortran-source runs
48-
if (.not.compiler%check_fortran_source_runs("print *, 'Hello world!'; end")) then
50+
if (.not.compiler%check_fortran_source_runs("print *, 'Hello world!'; end")) then
4951
call test_failed(error, "Cannot run Fortran hello world")
5052
return
51-
end if
53+
end if
54+
55+
end subroutine test_check_fortran_source_runs
56+
57+
subroutine test_tokenize_flags(error)
58+
type(error_t), allocatable, intent(out) :: error
59+
60+
character(:), allocatable :: flags
61+
type(string_t), allocatable :: tokens(:)
62+
integer :: i
63+
64+
flags = '-I/path/to/include -I /test -I"/path/to/include with spaces" ' // &
65+
'-I "spaces here too" -L/path/to/lib -lmylib -O2 -g -Wall'
66+
call tokenize_flags(flags, tokens)
67+
68+
do i = 1, size(tokens)
69+
print *, "Tokens ", i, ": ", tokens(i)%s
70+
end do
71+
72+
if (tokens(1)%s /= '-I/path/to/include') then
73+
call test_failed(error, "Tokenization of flags failed: expected '-I/path/to/include'")
74+
return
75+
end if
76+
if (tokens(2)%s /= '-I/test') then
77+
call test_failed(error, "Tokenization of flags failed: expected '-I/test'")
78+
return
79+
end if
80+
if (tokens(3)%s /= '-I"/path/to/include with spaces"') then
81+
call test_failed(error, 'Tokenization of flags failed: expected -I"/path/to/include with spaces"')
82+
return
83+
end if
5284

53-
end subroutine test_check_fortran_source_runs
85+
if (size(tokens) /= 9) then
86+
call test_failed(error, "Tokenization of flags failed: expected 9 tokens")
87+
return
88+
end if
5489

90+
end subroutine test_tokenize_flags
5591

5692
end module test_compiler

0 commit comments

Comments
 (0)