Skip to content

Commit 0a5440e

Browse files
committed
Cleanup and refactor for PR
Move enumeration of link target object dependencies out of backend and into fpm_targets module. Add string_cat function for concatenating arrays of string_t. Add comments and procedure descriptions.
1 parent 9c342f4 commit 0a5440e

File tree

5 files changed

+183
-98
lines changed

5 files changed

+183
-98
lines changed

fpm/src/fpm.f90

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@ module fpm
88
use fpm_model, only: fpm_model_t, srcfile_t, build_target_t, &
99
FPM_SCOPE_UNKNOWN, FPM_SCOPE_LIB, &
1010
FPM_SCOPE_DEP, FPM_SCOPE_APP, FPM_SCOPE_TEST, &
11-
FPM_TARGET_EXECUTABLE
11+
FPM_TARGET_EXECUTABLE, FPM_TARGET_ARCHIVE
1212

1313
use fpm_sources, only: add_executable_sources, add_sources_from_dir
14-
use fpm_targets, only: targets_from_sources, resolve_module_dependencies, FPM_TARGET_ARCHIVE
14+
use fpm_targets, only: targets_from_sources, resolve_module_dependencies, &
15+
resolve_target_linking
1516
use fpm_manifest, only : get_package_data, default_executable, &
1617
default_library, package_t, default_test
1718
use fpm_error, only : error_t, fatal_error
@@ -247,6 +248,8 @@ subroutine build_model(model, settings, package, error)
247248

248249
call resolve_module_dependencies(model%targets,error)
249250

251+
call resolve_target_linking(model%targets)
252+
250253
end subroutine build_model
251254

252255
!> Apply package defaults

fpm/src/fpm_backend.f90

Lines changed: 87 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,86 +1,87 @@
1+
!> Implements the native fpm build backend
12
module fpm_backend
23

3-
! Implements the native fpm build backend
4-
5-
use fpm_environment, only: run, get_os_type, OS_WINDOWS
6-
use fpm_filesystem, only: basename, dirname, join_path, exists, mkdir
7-
use fpm_model, only: fpm_model_t, srcfile_t, build_target_t, build_target_ptr, &
8-
FPM_UNIT_MODULE, FPM_UNIT_SUBMODULE, FPM_UNIT_SUBPROGRAM, &
9-
FPM_UNIT_CSOURCE, FPM_UNIT_PROGRAM, &
10-
FPM_SCOPE_TEST, FPM_TARGET_OBJECT, FPM_TARGET_ARCHIVE, FPM_TARGET_EXECUTABLE
4+
use fpm_environment, only: run
5+
use fpm_filesystem, only: dirname, join_path, exists, mkdir
6+
use fpm_model, only: fpm_model_t, build_target_t, build_target_ptr, &
7+
FPM_TARGET_OBJECT, FPM_TARGET_ARCHIVE, FPM_TARGET_EXECUTABLE
118

12-
use fpm_strings, only: split
9+
use fpm_strings, only: string_cat
1310

1411
implicit none
1512

1613
private
17-
public :: build_package
14+
public :: build_package, sort_target, schedule_targets
1815

1916
contains
2017

21-
18+
!> Top-level routine to build package described by `model`
2219
subroutine build_package(model)
2320
type(fpm_model_t), intent(inout) :: model
2421

2522
integer :: i, j
2623
type(build_target_ptr), allocatable :: queue(:)
27-
integer, allocatable :: region_ptr(:)
24+
integer, allocatable :: schedule_ptr(:)
2825

29-
if (.not.exists(model%output_directory)) then
30-
call mkdir(model%output_directory)
31-
end if
26+
! Need to make output directory for include (mod) files
3227
if (.not.exists(join_path(model%output_directory,model%package_name))) then
3328
call mkdir(join_path(model%output_directory,model%package_name))
3429
end if
3530

31+
! Perform depth-first topological sort of targets
3632
do i=1,size(model%targets)
3733

38-
call schedule_target(model%targets(i)%ptr)
34+
call sort_target(model%targets(i)%ptr)
3935

4036
end do
4137

42-
call get_build_queue(queue, region_ptr, model%targets)
38+
! Construct build schedule queue
39+
call schedule_targets(queue, schedule_ptr, model%targets)
4340

44-
do i=1,size(region_ptr)-1
41+
! Loop over parallel schedule regions
42+
do i=1,size(schedule_ptr)-1
4543

44+
! Build targets in schedule region i
4645
!$OMP PARALLEL DO DEFAULT(SHARED)
47-
do j=region_ptr(i),(region_ptr(i+1)-1)
46+
do j=schedule_ptr(i),(schedule_ptr(i+1)-1)
4847

4948
call build_target(model,queue(j)%ptr)
5049

5150
end do
5251
!$OMP END PARALLEL DO
5352

5453
end do
55-
5654

5755
end subroutine build_package
5856

5957

60-
61-
recursive subroutine schedule_target(target)
62-
!
63-
!
58+
!> Topologically sort a target for scheduling by
59+
!> recursing over it's dependencies.
60+
!>
61+
!> Checks disk-cached source hashes to determine if objects are
62+
!> up-to-date. Up-to-date sources are tagged as skipped.
63+
!>
64+
recursive subroutine sort_target(target)
6465
type(build_target_t), intent(inout), target :: target
6566

6667
integer :: i, j, fh, stat
6768
type(build_target_t), pointer :: exe_obj
6869

69-
if (target%scheduled .or. target%skip) then
70+
! Check if target has already been processed (as a dependency)
71+
if (target%sorted .or. target%skip) then
7072
return
7173
end if
7274

73-
if (.not.exists(dirname(target%output_file))) then
74-
call mkdir(dirname(target%output_file))
75-
end if
76-
75+
! Check for a circular dependency
76+
! (If target has been touched but not processed)
7777
if (target%touched) then
7878
write(*,*) '(!) Circular dependency found with: ',target%output_file
7979
stop
8080
else
81-
target%touched = .true.
81+
target%touched = .true. ! Set touched flag
8282
end if
8383

84+
! Load cached source file digest if present
8485
if (.not.allocated(target%digest_cached) .and. &
8586
exists(target%output_file) .and. &
8687
exists(target%output_file//'.digest')) then
@@ -90,120 +91,117 @@ recursive subroutine schedule_target(target)
9091
read(fh,*,iostat=stat) target%digest_cached
9192
close(fh)
9293

93-
if (stat /= 0) then
94-
write(*,*) 'Internal error: unable to read cached source hash'
95-
write(*,*) target%output_file//'.digest',' stat = ', stat
96-
error stop
94+
if (stat /= 0) then ! Cached digest is not recognized
95+
deallocate(target%digest_cached)
9796
end if
9897

9998
end if
10099

101100
if (allocated(target%source)) then
101+
102+
! Skip if target is source-based and source file is unmodified
102103
if (allocated(target%digest_cached)) then
103104
if (target%digest_cached == target%source%digest) target%skip = .true.
104105
end if
106+
105107
elseif (exists(target%output_file)) then
108+
109+
! Skip if target is not source-based and already exists
106110
target%skip = .true.
111+
107112
end if
108113

109-
target%link_objects = " "
110-
target%region = 1
114+
! Loop over target dependencies
115+
target%schedule = 1
111116
do i=1,size(target%dependencies)
112117

113-
call schedule_target(target%dependencies(i)%ptr)
118+
! Sort dependency
119+
call sort_target(target%dependencies(i)%ptr)
114120

115121
if (.not.target%dependencies(i)%ptr%skip) then
116122

123+
! Can't skip target if any dependency is not skipped
117124
target%skip = .false.
118-
target%region = max(target%region,target%dependencies(i)%ptr%region+1)
119125

120-
end if
121-
122-
if (target%target_type == FPM_TARGET_ARCHIVE ) then
123-
124-
! Construct object list for archive
125-
target%link_objects = target%link_objects//" "//target%dependencies(i)%ptr%output_file
126-
127-
else if (target%target_type == FPM_TARGET_EXECUTABLE .and. &
128-
target%dependencies(i)%ptr%target_type == FPM_TARGET_OBJECT) then
129-
130-
exe_obj => target%dependencies(i)%ptr
131-
132-
! Construct object list for executable
133-
target%link_objects = " "//exe_obj%output_file
134-
135-
! Include non-library object dependencies
136-
do j=1,size(exe_obj%dependencies)
137-
138-
if (allocated(exe_obj%dependencies(j)%ptr%source)) then
139-
if (exe_obj%dependencies(j)%ptr%source%unit_scope == exe_obj%source%unit_scope) then
140-
target%link_objects = target%link_objects//" "//exe_obj%dependencies(j)%ptr%output_file
141-
end if
142-
end if
143-
144-
end do
126+
! Set target schedule after all of its dependencies
127+
target%schedule = max(target%schedule,target%dependencies(i)%ptr%schedule+1)
145128

146129
end if
147130

148131
end do
149132

150-
target%scheduled = .not.target%skip
133+
! Mark flag as processed: either sorted or skipped
134+
target%sorted = .not.target%skip
151135

152-
end subroutine schedule_target
136+
end subroutine sort_target
153137

154138

155-
subroutine get_build_queue(queue, region_ptr, targets)
139+
!> Construct a build schedule from the sorted targets.
140+
!>
141+
!> The schedule is broken into regions, described by `schedule_ptr`,
142+
!> where targets in each region can be compiled in parallel.
143+
!>
144+
subroutine schedule_targets(queue, schedule_ptr, targets)
156145
type(build_target_ptr), allocatable, intent(out) :: queue(:)
157-
integer, allocatable :: region_ptr(:)
146+
integer, allocatable :: schedule_ptr(:)
158147
type(build_target_ptr), intent(in) :: targets(:)
159148

160149
integer :: i, j
161-
integer :: nRegion, n_scheduled
150+
integer :: n_schedule, n_sorted
162151

163-
nRegion = 0
164-
n_scheduled = 0
152+
n_schedule = 0 ! Number of schedule regions
153+
n_sorted = 0 ! Total number of targets to build
165154
do i=1,size(targets)
166155

167-
if (targets(i)%ptr%scheduled) then
168-
n_scheduled = n_scheduled + 1
156+
if (targets(i)%ptr%sorted) then
157+
n_sorted = n_sorted + 1
169158
end if
170-
nRegion = max(nRegion, targets(i)%ptr%region)
159+
n_schedule = max(n_schedule, targets(i)%ptr%schedule)
171160

172161
end do
173162

174-
allocate(queue(n_scheduled))
175-
allocate(region_ptr(nRegion+1))
163+
allocate(queue(n_sorted))
164+
allocate(schedule_ptr(n_schedule+1))
176165

177-
n_scheduled = 1
178-
region_ptr(n_scheduled) = 1
179-
do i=1,nRegion
166+
! Construct the target queue and schedule region pointer
167+
n_sorted = 1
168+
schedule_ptr(n_sorted) = 1
169+
do i=1,n_schedule
180170

181171
do j=1,size(targets)
182172

183-
if (targets(j)%ptr%scheduled) then
184-
if (targets(j)%ptr%region == i) then
173+
if (targets(j)%ptr%sorted) then
174+
if (targets(j)%ptr%schedule == i) then
185175

186-
queue(n_scheduled)%ptr => targets(j)%ptr
187-
n_scheduled = n_scheduled + 1
176+
queue(n_sorted)%ptr => targets(j)%ptr
177+
n_sorted = n_sorted + 1
188178
end if
189179
end if
190180

191181
end do
192182

193-
region_ptr(i+1) = n_scheduled
183+
schedule_ptr(i+1) = n_sorted
194184

195185
end do
196186

197-
end subroutine get_build_queue
187+
end subroutine schedule_targets
198188

199189

190+
!> Call compile/link command for a single target.
191+
!>
192+
!> If successful, also caches the source file digest to disk.
193+
!>
200194
subroutine build_target(model,target)
201195
type(fpm_model_t), intent(in) :: model
202196
type(build_target_t), intent(in), target :: target
203197

204198
integer :: ilib, fh
205199
character(:), allocatable :: link_flags
206200

201+
if (.not.exists(dirname(target%output_file))) then
202+
call mkdir(dirname(target%output_file))
203+
end if
204+
207205
select case(target%target_type)
208206

209207
case (FPM_TARGET_OBJECT)
@@ -218,16 +216,16 @@ subroutine build_target(model,target)
218216
end if
219217

220218
if (allocated(target%link_libraries)) then
221-
do ilib = 1, size(target%link_libraries)
222-
link_flags = link_flags // " -l" // target%link_libraries(ilib)%s
223-
end do
219+
if (size(target%link_libraries) > 0) then
220+
link_flags = link_flags // " -l" // string_cat(target%link_libraries," -l")
221+
end if
224222
end if
225223

226-
call run("gfortran " // target%link_objects // model%fortran_compile_flags &
224+
call run("gfortran " // string_cat(target%link_objects," ") // model%fortran_compile_flags &
227225
//link_flags// " -o " // target%output_file)
228226

229227
case (FPM_TARGET_ARCHIVE)
230-
call run("ar -rs " // target%output_file // target%link_objects)
228+
call run("ar -rs " // target%output_file // " " // string_cat(target%link_objects," "))
231229

232230
end select
233231

fpm/src/fpm_model.f90

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,18 +73,18 @@ module fpm_model
7373
integer :: target_type = FPM_TARGET_UNKNOWN
7474
type(string_t), allocatable :: link_libraries(:)
7575
! Native libraries to link against
76-
77-
character(:), allocatable :: link_objects
76+
type(string_t), allocatable :: link_objects(:)
7877
! Objects needed to link this target
78+
7979
logical :: touched = .false.
8080
! Flag set when first visited to check for circular dependencies
81-
logical :: scheduled = .false.
82-
! Flag set if build target is scheduled for building
81+
logical :: sorted = .false.
82+
! Flag set if build target is sorted for building
8383
logical :: skip = .false.
8484
! Flag set if build target will be skipped (not built)
8585

86-
integer :: region
87-
! Targets in the same region are guaranteed independent
86+
integer :: schedule
87+
! Targets in the same schedule group are guaranteed to be independent
8888
integer(int64), allocatable :: digest_cached
8989
! Previous hash
9090

0 commit comments

Comments
 (0)