Skip to content

Commit 1ae7351

Browse files
authored
fix Windows bootstrap (#1107)
2 parents ac06dfc + f2a2fbb commit 1ae7351

File tree

3 files changed

+191
-8
lines changed

3 files changed

+191
-8
lines changed

src/fpm_filesystem.F90

+3-5
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ module fpm_filesystem
77
OS_UNKNOWN, OS_LINUX, OS_MACOS, OS_WINDOWS, &
88
OS_CYGWIN, OS_SOLARIS, OS_FREEBSD, OS_OPENBSD
99
use fpm_environment, only: separator, get_env, os_is_unix
10-
use fpm_strings, only: f_string, replace, string_t, split, split_first_last, dilate, str_begins_with_str
10+
use fpm_strings, only: f_string, replace, string_t, split, split_lines_first_last, dilate, str_begins_with_str
1111
use iso_c_binding, only: c_char, c_ptr, c_int, c_null_char, c_associated, c_f_pointer
1212
use fpm_error, only : fpm_stop, error_t, fatal_error
1313
implicit none
@@ -51,8 +51,6 @@ end function c_is_dir
5151
end interface
5252
#endif
5353

54-
character(*), parameter :: eol = new_line('a') !! End of line
55-
5654
contains
5755

5856
!> Extract filename from path with/without suffix
@@ -319,7 +317,7 @@ function read_lines_expanded(filename) result(lines)
319317
return
320318
end if
321319

322-
call split_first_last(content, eol, first, last) ! TODO: \r (< macOS X), \n (>=macOS X/Linux/Unix), \r\n (Windows)
320+
call split_lines_first_last(content, first, last)
323321

324322
! allocate lines from file content string
325323
allocate (lines(size(first)))
@@ -344,7 +342,7 @@ function read_lines(filename) result(lines)
344342
return
345343
end if
346344

347-
call split_first_last(content, eol, first, last) ! TODO: \r (< macOS X), \n (>=macOS X/Linux/Unix), \r\n (Windows)
345+
call split_lines_first_last(content, first, last)
348346

349347
! allocate lines from file content string
350348
allocate (lines(size(first)))

src/fpm_strings.f90

+46-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ module fpm_strings
4242
implicit none
4343

4444
private
45-
public :: f_string, lower, upper, split, split_first_last, str_ends_with, string_t, str_begins_with_str
45+
public :: f_string, lower, upper, split, split_first_last, split_lines_first_last, str_ends_with, string_t, str_begins_with_str
4646
public :: to_fortran_name, is_fortran_name
4747
public :: string_array_contains, string_cat, len_trim, operator(.in.), fnv_1a
4848
public :: replace, resize, str, join, glob
@@ -551,6 +551,51 @@ pure subroutine split_first_last(string, set, first, last)
551551

552552
end subroutine split_first_last
553553

554+
!! Author: Federico Perini
555+
!! Computes the first and last indices of lines in input string, delimited
556+
!! by either CR, LF, or CRLF, and stores them into first and last output
557+
!! arrays.
558+
pure subroutine split_lines_first_last(string, first, last)
559+
character(*), intent(in) :: string
560+
integer, allocatable, intent(out) :: first(:)
561+
integer, allocatable, intent(out) :: last(:)
562+
563+
integer, dimension(len(string) + 1) :: istart, iend
564+
integer :: p, n, slen
565+
character, parameter :: CR = achar(13)
566+
character, parameter :: LF = new_line('A')
567+
568+
slen = len(string)
569+
570+
n = 0
571+
if (slen > 0) then
572+
p = 1
573+
do while (p <= slen)
574+
575+
if (index(CR//LF, string(p:p)) == 0) then
576+
n = n + 1
577+
istart(n) = p
578+
do while (p <= slen)
579+
if (index(CR//LF, string(p:p)) /= 0) exit
580+
p = p + 1
581+
end do
582+
iend(n) = p - 1
583+
end if
584+
585+
! Handle Windows CRLF by skipping LF after CR
586+
if (p < slen) then
587+
if (string(p:p) == CR .and. string(p+1:p+1) == LF) p = p + 1
588+
endif
589+
590+
p = p + 1
591+
end do
592+
end if
593+
594+
first = istart(:n)
595+
last = iend(:n)
596+
597+
end subroutine split_lines_first_last
598+
554599
!! Author: Milan Curcic
555600
!! If back is absent, computes the leftmost token delimiter in string whose
556601
!! position is > pos. If back is present and true, computes the rightmost

test/fpm_test/test_filesystem.f90

+142-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
module test_filesystem
22
use testsuite, only: new_unittest, unittest_t, error_t, test_failed
33
use fpm_filesystem, only: canon_path, is_dir, mkdir, os_delete_dir, &
4-
join_path, is_absolute_path, get_home
4+
join_path, is_absolute_path, get_home, &
5+
delete_file, read_lines, get_temp_filename
56
use fpm_environment, only: OS_WINDOWS, get_os_type, os_is_unix
7+
use fpm_strings, only: string_t, split_lines_first_last
68
implicit none
79
private
810

@@ -20,7 +22,9 @@ subroutine collect_filesystem(tests)
2022
& new_unittest("canon-path", test_canon_path), &
2123
& new_unittest("create-delete-directory", test_mkdir_rmdir), &
2224
& new_unittest("test-is-absolute-path", test_is_absolute_path), &
23-
& new_unittest("test-get-home", test_get_home) &
25+
& new_unittest("test-get-home", test_get_home), &
26+
& new_unittest("test-split-lines-first-last", test_split_lines_first_last), &
27+
& new_unittest("test-crlf-lines", test_dir_with_crlf) &
2428
]
2529

2630
end subroutine collect_filesystem
@@ -289,5 +293,141 @@ subroutine test_get_home(error)
289293
end if
290294

291295
end subroutine test_get_home
296+
297+
! Test line splitting on MS windows
298+
subroutine test_split_lines_first_last(error)
299+
!> Error handling
300+
type(error_t), allocatable, intent(out) :: error
301+
302+
character, parameter :: CR = achar(13)
303+
character, parameter :: LF = new_line('A')
304+
character(*), parameter :: CRLF = CR//LF
305+
integer, allocatable :: first(:), last(:)
306+
307+
call split_lines_first_last(CR//LF//'line1'//CR//'line2'//LF//'line3'//CR//LF//'hello', first, last)
308+
if (.not.(all(first==[3,9,15,22]) .and. all(last==[7,13,19,26]))) then
309+
call test_failed(error, "Test split_lines_first_last #1 failed")
310+
return
311+
end if
312+
313+
call split_lines_first_last('single_line', first, last)
314+
if (.not.(all(first==[1]) .and. all(last==[11]))) then
315+
call test_failed(error, "Test split_lines_first_last #2 failed")
316+
return
317+
end if
318+
319+
call split_lines_first_last(CR//LF//CR//LF//'test', first, last)
320+
if (.not.(all(first == [5]) .and. all(last == [8]))) then
321+
call test_failed(error, "Test split_lines_first_last #3 failed")
322+
return
323+
end if
324+
325+
call split_lines_first_last('a'//CR//'b'//LF//'c'//CR//LF//'d', first, last)
326+
if (.not.(all(first == [1, 3, 5, 8]) .and. all(last == [1, 3, 5, 8]))) then
327+
call test_failed(error, "Test split_lines_first_last #4 failed")
328+
return
329+
end if
330+
331+
call split_lines_first_last('', first, last)
332+
if (.not.(size(first) == 0 .and. size(last) == 0)) then
333+
call test_failed(error, "Test split_lines_first_last #5 failed")
334+
return
335+
end if
336+
337+
call split_lines_first_last('build.f90'//CRLF//&
338+
'dependency.f90'//CRLF//&
339+
'example.f90'//CRLF//&
340+
'executable.f90'//CRLF//&
341+
'fortran.f90'//CRLF, &
342+
first, last)
343+
344+
if (.not.(all(first == [1,12,28,41,57]) .and. all(last == [9,25,38,54,67]))) then
345+
call test_failed(error, "Test split_lines_first_last #6 failed")
346+
return
347+
end if
348+
349+
end subroutine test_split_lines_first_last
350+
351+
! On MS windows, directory listings are printed to files with CR//LF endings.
352+
! Check that the lines can be properly read back from such files.
353+
subroutine test_dir_with_crlf(error)
354+
type(error_t), allocatable, intent(out) :: error
355+
356+
character, parameter :: CR = achar(13)
357+
character, parameter :: LF = new_line('A')
358+
character(*), parameter :: CRLF = CR//LF
359+
360+
character(*), parameter :: test_lines = 'build.f90'//CRLF//&
361+
'dependency.f90'//CRLF//&
362+
'example.f90'//CRLF//&
363+
'executable.f90'//CRLF//&
364+
'fortran.f90'//CRLF
365+
366+
type(string_t), allocatable :: lines(:)
367+
character(len=:), allocatable :: temp_file
368+
character(256) :: msg
369+
integer :: unit, i, ios
370+
371+
temp_file = get_temp_filename()
372+
373+
open(newunit=unit,file=temp_file,access='stream',action='write',iostat=ios)
374+
if (ios/=0) then
375+
call test_failed(error, "cannot create temporary file")
376+
return
377+
end if
378+
379+
write(unit,iostat=ios) test_lines
380+
if (ios/=0) then
381+
call test_failed(error, "cannot write to temporary file")
382+
return
383+
end if
384+
385+
close(unit,iostat=ios)
386+
if (ios/=0) then
387+
call test_failed(error, "cannot close temporary file")
388+
return
389+
end if
390+
391+
lines = read_lines(temp_file)
392+
393+
if (.not.allocated(lines)) then
394+
write(msg, 1) 'no output'
395+
call test_failed(error, msg)
396+
return
397+
end if
398+
399+
if (size(lines)/=5) then
400+
write(msg, 1) 'wrong number of lines: expected ',5,', actual ',size(lines)
401+
call test_failed(error, msg)
402+
return
403+
end if
404+
405+
if (lines(1)%s/='build.f90') then
406+
call test_failed(error, "Failed reading file with CRLF: at build.f90")
407+
return
408+
end if
409+
if (lines(2)%s/='dependency.f90') then
410+
call test_failed(error, "Failed reading file with CRLF: at dependency.f90")
411+
return
412+
end if
413+
if (lines(3)%s/='example.f90') then
414+
call test_failed(error, "Failed reading file with CRLF: at example.f90")
415+
return
416+
end if
417+
if (lines(4)%s/='executable.f90') then
418+
call test_failed(error, "Failed reading file with CRLF: at executable.f90")
419+
return
420+
end if
421+
if (lines(5)%s/='fortran.f90') then
422+
call test_failed(error, "Failed reading file with CRLF: at fortran.f90")
423+
return
424+
end if
425+
426+
call delete_file(temp_file)
427+
428+
1 format("Failed reading file with CRLF: ",a,:,i0,:,a,:,i0)
429+
430+
end subroutine test_dir_with_crlf
431+
292432

293433
end module test_filesystem

0 commit comments

Comments
 (0)