Skip to content

[Fortran fpm] Internal dependencies & build backend #155

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 31 commits into from
Sep 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
bf86469
Restructure: move some routines out of fpm module.
LKedward Aug 26, 2020
cdedb0a
Use temporary file for directory listing output.
LKedward Aug 26, 2020
1746dd0
Minor fix: to read_lines subroutine.
LKedward Aug 26, 2020
fbe3370
Add: Sourcefiles module for processing sources.
LKedward Aug 26, 2020
a06b644
Minor fix: to count_rows in filesystem mod.
LKedward Aug 26, 2020
6d6c336
Add: initial fpm build backend.
LKedward Aug 26, 2020
d44bb2e
Add: initial support for c sources.
LKedward Aug 26, 2020
434033f
Minor fix: add dependency pointer guard.
LKedward Aug 26, 2020
a6df3bb
Add: fpm_ prefix to all module names.
LKedward Aug 28, 2020
43dd6e1
Update: for extracting modules
LKedward Sep 1, 2020
b6000d8
Add: stubs for model and manifest structures
LKedward Sep 1, 2020
54a5c6d
Fix: for multilevel submodules
LKedward Sep 2, 2020
f466572
Add basic fields to model structure.
LKedward Sep 2, 2020
d8afa4d
Fix: EOL to unix LF
LKedward Sep 2, 2020
f85f291
Update: fortran fpm test script.
LKedward Sep 2, 2020
07c5828
explicit imports throughout; reorder imports alphabetically; explicit…
milancurcic Sep 3, 2020
03f79c6
Merge remote-tracking branch 'awvwgk/fortran-impl' into dependencies-…
LKedward Sep 5, 2020
82146de
Use manifest data for library and executables
LKedward Sep 5, 2020
a734a82
Merge remote-tracking branch 'awvwgk/fortran-impl' into dependencies-…
LKedward Sep 5, 2020
fdcf6b8
Refactor: add basename filesystem fcn
LKedward Sep 7, 2020
44a848e
Add: join_path for output paths
LKedward Sep 7, 2020
dd6ac6f
Fix: for windows paths
LKedward Sep 7, 2020
5500927
Update: test scripts for fortran fpm
LKedward Sep 7, 2020
8b1e4a6
Merge remote-tracking branch 'upstream/master' into dependencies
LKedward Sep 7, 2020
f32d6c3
Fix: allocation for default library.
LKedward Sep 7, 2020
35ae709
Fix: basename function with trim
LKedward Sep 7, 2020
9bd9d3f
Fix: more trimming of split string output.
LKedward Sep 7, 2020
518341b
Update: fpm_sources with ieee intrinsic module names
LKedward Sep 8, 2020
8c8e4e9
Fix: erroneous optional attribute
LKedward Sep 8, 2020
fd49a2e
Updates: for improved readability
LKedward Sep 8, 2020
eed082b
Isolate model definition from model construction
LKedward Sep 11, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion ci/run_tests.bat
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,24 @@ if errorlevel 1 exit 1
..\..\..\fpm\build\gfortran_debug\app\fpm build
if errorlevel 1 exit 1

.\hello_world
.\build\gfortran_debug\app\hello_world
if errorlevel 1 exit 1


cd ..\hello_complex
if errorlevel 1 exit 1

..\..\..\fpm\build\gfortran_debug\app\fpm build
if errorlevel 1 exit 1

.\build\gfortran_debug\app\say_Hello
if errorlevel 1 exit 1

.\build\gfortran_debug\app\say_goodbye
if errorlevel 1 exit 1

.\build\gfortran_debug\test\greet_test
if errorlevel 1 exit 1

.\build\gfortran_debug\test\farewell_test
if errorlevel 1 exit 1
10 changes: 9 additions & 1 deletion ci/run_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ fpm build
fpm run
fpm test
build/gfortran_debug/app/fpm

cd ../test/example_packages/hello_world
../../../fpm/build/gfortran_debug/app/fpm build
./hello_world
./build/gfortran_debug/app/hello_world

cd ../hello_complex
../../../fpm/build/gfortran_debug/app/fpm build
./build/gfortran_debug/app/say_Hello
./build/gfortran_debug/app/say_goodbye
./build/gfortran_debug/test/greet_test
./build/gfortran_debug/test/farewell_test
6 changes: 3 additions & 3 deletions fpm/app/main.f90
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
program main
use command_line, only: &
use fpm_command_line, only: &
fpm_cmd_settings, &
fpm_new_settings, &
fpm_build_settings, &
Expand All @@ -15,11 +15,11 @@ program main

call get_command_line_settings(cmd_settings)

select type(cmd_settings)
select type(settings=>cmd_settings)
type is (fpm_new_settings)
call cmd_new()
type is (fpm_build_settings)
call cmd_build()
call cmd_build(settings)
type is (fpm_run_settings)
call cmd_run()
type is (fpm_test_settings)
Expand Down
156 changes: 51 additions & 105 deletions fpm/src/fpm.f90
Original file line number Diff line number Diff line change
@@ -1,99 +1,61 @@
module fpm
use environment, only: get_os_type, OS_LINUX, OS_MACOS, OS_WINDOWS
use fpm_manifest, only : get_package_data, default_executable, default_library, &
& package_t

use fpm_strings, only: string_t, str_ends_with
use fpm_backend, only: build_package
use fpm_command_line, only: fpm_build_settings
use fpm_environment, only: run, get_os_type, OS_LINUX, OS_MACOS, OS_WINDOWS
use fpm_filesystem, only: join_path, number_of_rows, list_files, exists
use fpm_model, only: srcfile_ptr, srcfile_t, fpm_model_t
use fpm_sources, only: add_executable_sources, add_sources_from_dir, &
resolve_module_dependencies
use fpm_manifest, only : get_package_data, default_executable, &
default_library, package_t
use fpm_error, only : error_t
implicit none
private
public :: cmd_build, cmd_install, cmd_new, cmd_run, cmd_test

type string_t
character(len=:), allocatable :: s
end type

contains

integer function number_of_rows(s) result(nrows)
! determine number or rows
integer,intent(in)::s
integer :: ios
character(len=100) :: r
rewind(s)
nrows = 0
do
read(s, *, iostat=ios) r
if (ios /= 0) exit
nrows = nrows + 1
end do
rewind(s)
end function


subroutine list_files(dir, files)
character(len=*), intent(in) :: dir
type(string_t), allocatable, intent(out) :: files(:)
character(len=100) :: filename
integer :: stat, u, i
! Using `inquire` / exists on directories works with gfortran, but not ifort
if (.not. exists(dir)) then
allocate(files(0))
return
end if
select case (get_os_type())
case (OS_LINUX)
call execute_command_line("ls " // dir // " > fpm_ls.out", exitstat=stat)
case (OS_MACOS)
call execute_command_line("ls " // dir // " > fpm_ls.out", exitstat=stat)
case (OS_WINDOWS)
call execute_command_line("dir /b " // dir // " > fpm_ls.out", exitstat=stat)
end select
if (stat /= 0) then
print *, "execute_command_line() failed"
error stop
end if
open(newunit=u, file="fpm_ls.out", status="old")
allocate(files(number_of_rows(u)))
do i = 1, size(files)
read(u, *) filename
files(i)%s = trim(filename)
end do
close(u)
end subroutine

subroutine run(cmd)
character(len=*), intent(in) :: cmd
integer :: stat
print *, "+ ", cmd
call execute_command_line(cmd, exitstat=stat)
if (stat /= 0) then
print *, "Command failed"
error stop
end if
end subroutine

logical function exists(filename) result(r)
character(len=*), intent(in) :: filename
inquire(file=filename, exist=r)
end function

logical function str_ends_with(s, e) result(r)
character(*), intent(in) :: s, e
integer :: n1, n2
n1 = len(s)-len(e)+1
n2 = len(s)
if (n1 < 1) then
r = .false.
else
r = (s(n1:n2) == e)
end if
end function

subroutine cmd_build()
subroutine build_model(model, settings, package)
! Constructs a valid fpm model from command line settings and toml manifest
!
type(fpm_model_t), intent(out) :: model
type(fpm_build_settings), intent(in) :: settings
type(package_t), intent(in) :: package

model%package_name = package%name

! #TODO: Choose flags and output directory based on cli settings & manifest inputs
model%fortran_compiler = 'gfortran'
model%output_directory = 'build/gfortran_debug'
model%fortran_compile_flags = ' -Wall -Wextra -Wimplicit-interface -fPIC -fmax-errors=1 -g '// &
'-fbounds-check -fcheck-array-temporaries -fbacktrace '// &
'-J'//join_path(model%output_directory,model%package_name)
model%link_flags = ''

! Add sources from executable directories
if (allocated(package%executable)) then
call add_executable_sources(model%sources, package%executable,is_test=.false.)
end if
if (allocated(package%test)) then
call add_executable_sources(model%sources, package%test,is_test=.true.)
end if

if (allocated(package%library)) then
call add_sources_from_dir(model%sources,package%library%source_dir)
end if

call resolve_module_dependencies(model%sources)

end subroutine build_model

subroutine cmd_build(settings)
type(fpm_build_settings), intent(in) :: settings
type(package_t) :: package
type(fpm_model_t) :: model
type(error_t), allocatable :: error
type(string_t), allocatable :: files(:)
character(:), allocatable :: basename, linking
integer :: i, n
call get_package_data(package, "fpm.toml", error)
if (allocated(error)) then
print '(a)', error%message
Expand All @@ -102,6 +64,7 @@ subroutine cmd_build()

! Populate library in case we find the default src directory
if (.not.allocated(package%library) .and. exists("src")) then
allocate(package%library)
call default_library(package%library)
end if

Expand All @@ -116,27 +79,10 @@ subroutine cmd_build()
error stop 1
end if

linking = ""
if (allocated(package%library)) then
call list_files(package%library%source_dir, files)
do i = 1, size(files)
if (str_ends_with(files(i)%s, ".f90")) then
n = len(files(i)%s)
basename = files(i)%s
call run("gfortran -c " // package%library%source_dir // "/" // &
& basename // " -o " // basename // ".o")
linking = linking // " " // basename // ".o"
end if
end do
end if
call build_model(model, settings, package)

call build_package(model)

do i = 1, size(package%executable)
basename = package%executable(i)%main
call run("gfortran -c " // package%executable(i)%source_dir // "/" // &
& basename // " -o " // basename // ".o")
call run("gfortran " // basename // ".o " // linking // " -o " // &
& package%executable(i)%name)
end do
end subroutine

subroutine cmd_install()
Expand Down
123 changes: 123 additions & 0 deletions fpm/src/fpm_backend.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
module fpm_backend

! Implements the native fpm build backend

use fpm_environment, only: run
use fpm_filesystem, only: basename, join_path, exists, mkdir
use fpm_model, only: fpm_model_t, srcfile_t, FPM_UNIT_MODULE, &
FPM_UNIT_SUBMODULE, FPM_UNIT_SUBPROGRAM, &
FPM_UNIT_CSOURCE, FPM_UNIT_PROGRAM
use fpm_strings, only: split

implicit none

private
public :: build_package

contains


subroutine build_package(model)
type(fpm_model_t), intent(inout) :: model

integer :: i
character(:), allocatable :: base, linking, subdir

if (.not.exists(model%output_directory)) then
call mkdir(model%output_directory)
end if
if (.not.exists(join_path(model%output_directory,model%package_name))) then
call mkdir(join_path(model%output_directory,model%package_name))
end if

linking = ""
do i=1,size(model%sources)

if (model%sources(i)%unit_type == FPM_UNIT_MODULE .or. &
model%sources(i)%unit_type == FPM_UNIT_SUBMODULE .or. &
model%sources(i)%unit_type == FPM_UNIT_SUBPROGRAM .or. &
model%sources(i)%unit_type == FPM_UNIT_CSOURCE) then

call build_source(model,model%sources(i),linking)

end if

end do

if (any([(model%sources(i)%unit_type == FPM_UNIT_PROGRAM,i=1,size(model%sources))])) then
if (.not.exists(join_path(model%output_directory,'test'))) then
call mkdir(join_path(model%output_directory,'test'))
end if
if (.not.exists(join_path(model%output_directory,'app'))) then
call mkdir(join_path(model%output_directory,'app'))
end if
end if

do i=1,size(model%sources)

if (model%sources(i)%unit_type == FPM_UNIT_PROGRAM) then

base = basename(model%sources(i)%file_name,suffix=.false.)

if (model%sources(i)%is_test) then
subdir = 'test'
else
subdir = 'app'
end if

call run("gfortran -c " // model%sources(i)%file_name // ' '//model%fortran_compile_flags &
// " -o " // join_path(model%output_directory,subdir,base) // ".o")

call run("gfortran " // join_path(model%output_directory, subdir, base) // ".o "// &
linking //" " //model%link_flags // " -o " // &
join_path(model%output_directory,subdir,model%sources(i)%exe_name) )

end if

end do

end subroutine build_package



recursive subroutine build_source(model,source_file,linking)
! Compile Fortran source, called recursively on it dependents
!
type(fpm_model_t), intent(in) :: model
type(srcfile_t), intent(inout) :: source_file
character(:), allocatable, intent(inout) :: linking

integer :: i
character(:), allocatable :: object_file

if (source_file%built) then
return
end if

if (source_file%touched) then
write(*,*) '(!) Circular dependency found with: ',source_file%file_name
stop
else
source_file%touched = .true.
end if

do i=1,size(source_file%file_dependencies)

if (associated(source_file%file_dependencies(i)%ptr)) then
call build_source(model,source_file%file_dependencies(i)%ptr,linking)
end if

end do

object_file = join_path(model%output_directory, model%package_name, &
basename(source_file%file_name,suffix=.false.)//'.o')

call run("gfortran -c " // source_file%file_name // model%fortran_compile_flags &
// " -o " // object_file)
linking = linking // " " // object_file

source_file%built = .true.

end subroutine build_source

end module fpm_backend
Loading