From fd77e6ba357390ec9a21506315b5578aaff513ce Mon Sep 17 00:00:00 2001 From: Sebastian Ehlert <28669218+awvwgk@users.noreply.github.com> Date: Thu, 3 Sep 2020 11:13:29 +0200 Subject: Rename config to manifest --- fpm/src/fpm.f90 | 2 +- fpm/src/fpm/config.f90 | 79 ----------- fpm/src/fpm/config/dependency.f90 | 241 -------------------------------- fpm/src/fpm/config/executable.f90 | 173 ----------------------- fpm/src/fpm/config/library.f90 | 126 ----------------- fpm/src/fpm/config/package.f90 | 270 ------------------------------------ fpm/src/fpm/config/test.f90 | 166 ---------------------- fpm/src/fpm/manifest.f90 | 79 +++++++++++ fpm/src/fpm/manifest/dependency.f90 | 241 ++++++++++++++++++++++++++++++++ fpm/src/fpm/manifest/executable.f90 | 173 +++++++++++++++++++++++ fpm/src/fpm/manifest/library.f90 | 126 +++++++++++++++++ fpm/src/fpm/manifest/package.f90 | 270 ++++++++++++++++++++++++++++++++++++ fpm/src/fpm/manifest/test.f90 | 166 ++++++++++++++++++++++ fpm/src/fpm/toml.f90 | 10 +- fpm/test/main.f90 | 6 +- fpm/test/test_config.f90 | 188 ------------------------- fpm/test/test_manifest.f90 | 188 +++++++++++++++++++++++++ fpm/test/test_toml.f90 | 16 +-- 18 files changed, 1260 insertions(+), 1260 deletions(-) delete mode 100644 fpm/src/fpm/config.f90 delete mode 100644 fpm/src/fpm/config/dependency.f90 delete mode 100644 fpm/src/fpm/config/executable.f90 delete mode 100644 fpm/src/fpm/config/library.f90 delete mode 100644 fpm/src/fpm/config/package.f90 delete mode 100644 fpm/src/fpm/config/test.f90 create mode 100644 fpm/src/fpm/manifest.f90 create mode 100644 fpm/src/fpm/manifest/dependency.f90 create mode 100644 fpm/src/fpm/manifest/executable.f90 create mode 100644 fpm/src/fpm/manifest/library.f90 create mode 100644 fpm/src/fpm/manifest/package.f90 create mode 100644 fpm/src/fpm/manifest/test.f90 delete mode 100644 fpm/test/test_config.f90 create mode 100644 fpm/test/test_manifest.f90 diff --git a/fpm/src/fpm.f90 b/fpm/src/fpm.f90 index 5123436..9c8918b 100644 --- a/fpm/src/fpm.f90 +++ b/fpm/src/fpm.f90 @@ -1,6 +1,6 @@ module fpm use environment, only: get_os_type, OS_LINUX, OS_MACOS, OS_WINDOWS -use fpm_config, only : get_package_data, default_executable, default_library, & +use fpm_manifest, only : get_package_data, default_executable, default_library, & & package_t use fpm_error, only : error_t implicit none diff --git a/fpm/src/fpm/config.f90 b/fpm/src/fpm/config.f90 deleted file mode 100644 index 03ad768..0000000 --- a/fpm/src/fpm/config.f90 +++ /dev/null @@ -1,79 +0,0 @@ -!> Package configuration data. -! -! This module provides the necessary procedure to translate a TOML document -! to the corresponding Fortran type, while verifying it with respect to -! its schema. -! -! Additionally, the required data types for users of this module are reexported -! to hide the actual implementation details. -module fpm_config - use fpm_config_executable, only : executable_t - use fpm_config_library, only : library_t - use fpm_config_package, only : package_t, new_package - use fpm_error, only : error_t, fatal_error, file_not_found_error - use fpm_toml, only : toml_table, read_package_file - implicit none - private - - public :: get_package_data, default_executable, default_library - public :: package_t - - -contains - - - !> Populate library in case we find the default src directory - subroutine default_library(self) - - !> Instance of the library meta data - type(library_t), intent(out) :: self - - self%source_dir = "src" - - end subroutine default_library - - - !> Populate executable in case we find the default app directory - subroutine default_executable(self, name) - - !> Instance of the executable meta data - type(executable_t), intent(out) :: self - - !> Name of the package - character(len=*), intent(in) :: name - - self%name = name - self%source_dir = "app" - self%main = "main.f90" - - end subroutine default_executable - - - !> Obtain package meta data from a configuation file - subroutine get_package_data(package, file, error) - - !> Parsed package meta data - type(package_t), intent(out) :: package - - !> Name of the package configuration file - character(len=*), intent(in) :: file - - !> Error status of the operation - type(error_t), allocatable, intent(out) :: error - - type(toml_table), allocatable :: table - - call read_package_file(table, file, error) - if (allocated(error)) return - - if (.not.allocated(table)) then - call fatal_error(error, "Unclassified error while reading: '"//file//"'") - return - end if - - call new_package(package, table, error) - - end subroutine get_package_data - - -end module fpm_config diff --git a/fpm/src/fpm/config/dependency.f90 b/fpm/src/fpm/config/dependency.f90 deleted file mode 100644 index d98951f..0000000 --- a/fpm/src/fpm/config/dependency.f90 +++ /dev/null @@ -1,241 +0,0 @@ -!> Implementation of the meta data for dependencies. -! -! A dependency table can currently have the following fields -! -! ```toml -! [dependencies] -! "dep1" = { git = "url" } -! "dep2" = { git = "url", branch = "name" } -! "dep3" = { git = "url", tag = "name" } -! "dep4" = { git = "url", rev = "sha1" } -! "dep0" = { path = "path" } -! ``` -! -! To reduce the amount of boilerplate code this module provides two constructors -! for dependency types, one basic for an actual dependency (inline) table -! and another to collect all dependency objects from a dependencies table, -! which is handling the allocation of the objects and is forwarding the -! individual dependency tables to their respective constructors. -! The usual entry point should be the constructor for the super table. -! -! This objects contains a target to retrieve required `fpm` projects to -! build the target declaring the dependency. -! Resolving a dependency will result in obtaining a new package configuration -! data for the respective project. -module fpm_config_dependency - use fpm_error, only : error_t, syntax_error - use fpm_git, only : git_target_t, git_target_tag, git_target_branch, & - & git_target_revision, git_target_default - use fpm_toml, only : toml_table, toml_key, toml_stat, get_value - implicit none - private - - public :: dependency_t, new_dependency, new_dependencies - - - !> Configuration meta data for a dependency - type :: dependency_t - - !> Name of the dependency - character(len=:), allocatable :: name - - !> Local target - character(len=:), allocatable :: path - - !> Git descriptor - type(git_target_t), allocatable :: git - - contains - - !> Print information on this instance - procedure :: info - - end type dependency_t - - -contains - - - !> Construct a new dependency configuration from a TOML data structure - subroutine new_dependency(self, table, error) - - !> Instance of the dependency configuration - type(dependency_t), intent(out) :: self - - !> Instance of the TOML data structure - type(toml_table), intent(inout) :: table - - !> Error handling - type(error_t), allocatable, intent(out) :: error - - character(len=:), allocatable :: url, obj - - call check(table, error) - if (allocated(error)) return - - call table%get_key(self%name) - - call get_value(table, "path", url) - if (allocated(url)) then - call move_alloc(url, self%path) - else - call get_value(table, "git", url) - - call get_value(table, "tag", obj) - if (allocated(obj)) then - self%git = git_target_tag(url, obj) - end if - - if (.not.allocated(self%git)) then - call get_value(table, "branch", obj) - if (allocated(obj)) then - self%git = git_target_branch(url, obj) - end if - end if - - if (.not.allocated(self%git)) then - call get_value(table, "revision", obj) - if (allocated(obj)) then - self%git = git_target_revision(url, obj) - end if - end if - - if (.not.allocated(self%git)) then - self%git = git_target_default(url) - end if - - end if - - end subroutine new_dependency - - - !> Check local schema for allowed entries - subroutine check(table, error) - - !> Instance of the TOML data structure - type(toml_table), intent(inout) :: table - - !> Error handling - type(error_t), allocatable, intent(out) :: error - - character(len=:), allocatable :: name - type(toml_key), allocatable :: list(:) - logical :: url_present, git_target_present - integer :: ikey - - url_present = .false. - git_target_present = .false. - - call table%get_key(name) - call table%get_keys(list) - - if (.not.allocated(list)) then - call syntax_error(error, "Dependency "//name//" does not provide sufficient entries") - return - end if - - do ikey = 1, size(list) - select case(list(ikey)%key) - case default - call syntax_error(error, "Key "//list(ikey)%key//" is not allowed in dependency "//name) - exit - - case("git", "path") - if (url_present) then - call syntax_error(error, "Dependency "//name//" cannot have both git and path entries") - exit - end if - url_present = .true. - - case("branch", "rev", "tag") - if (git_target_present) then - call syntax_error(error, "Dependency "//name//" can only have one of branch, rev or tag present") - exit - end if - git_target_present = .true. - - end select - end do - if (allocated(error)) return - - if (.not.url_present .and. git_target_present) then - call syntax_error(error, "Dependency "//name//" uses a local path, therefore no git identifiers are allowed") - end if - - end subroutine check - - - !> Construct new dependency array from a TOML data structure - subroutine new_dependencies(deps, table, error) - - !> Instance of the dependency configuration - type(dependency_t), allocatable, intent(out) :: deps(:) - - !> Instance of the TOML data structure - type(toml_table), intent(inout) :: table - - !> Error handling - type(error_t), allocatable, intent(out) :: error - - class(toml_table), pointer :: node - type(toml_key), allocatable :: list(:) - integer :: idep, stat - - call table%get_keys(list) - ! An empty table is okay - if (.not.allocated(list)) return - - allocate(deps(size(list))) - do idep = 1, size(list) - call get_value(table, list(idep)%key, node, stat=stat) - if (stat /= toml_stat%success) then - call syntax_error(error, "Dependency "//list(idep)%key//" must be a table entry") - exit - end if - call new_dependency(deps(idep), node, error) - if (allocated(error)) exit - end do - - end subroutine new_dependencies - - - !> Write information on instance - subroutine info(self, unit, verbosity) - - !> Instance of the dependency configuration - class(dependency_t), intent(in) :: self - - !> Unit for IO - integer, intent(in) :: unit - - !> Verbosity of the printout - integer, intent(in), optional :: verbosity - - integer :: pr - character(len=*), parameter :: fmt = '("#", 1x, a, t30, a)' - - if (present(verbosity)) then - pr = verbosity - else - pr = 1 - end if - - write(unit, fmt) "Dependency" - if (allocated(self%name)) then - write(unit, fmt) "- name", self%name - end if - - if (allocated(self%git)) then - write(unit, fmt) "- kind", "git" - call self%git%info(unit, pr - 1) - end if - - if (allocated(self%path)) then - write(unit, fmt) "- kind", "local" - write(unit, fmt) "- path", self%path - end if - - end subroutine info - - -end module fpm_config_dependency diff --git a/fpm/src/fpm/config/executable.f90 b/fpm/src/fpm/config/executable.f90 deleted file mode 100644 index f5078eb..0000000 --- a/fpm/src/fpm/config/executable.f90 +++ /dev/null @@ -1,173 +0,0 @@ -!> Implementation of the meta data for an executables. -! -! An executable table can currently have the following fields -! -! ```toml -! [[executable]] -! name = "string" -! source-dir = "path" -! main = "file" -! [executable.dependencies] -! ``` -module fpm_config_executable - use fpm_config_dependency, only : dependency_t, new_dependencies - use fpm_error, only : error_t, syntax_error - use fpm_toml, only : toml_table, toml_key, toml_stat, get_value - implicit none - private - - public :: executable_t, new_executable - - - !> Configuation meta data for an executable - type :: executable_t - - !> Name of the resulting executable - character(len=:), allocatable :: name - - !> Source directory for collecting the executable - character(len=:), allocatable :: source_dir - - !> Name of the source file declaring the main program - character(len=:), allocatable :: main - - !> Dependency meta data for this executable - type(dependency_t), allocatable :: dependency(:) - - contains - - !> Print information on this instance - procedure :: info - - end type executable_t - - -contains - - - !> Construct a new executable configuration from a TOML data structure - subroutine new_executable(self, table, error) - - !> Instance of the executable configuration - type(executable_t), intent(out) :: self - - !> Instance of the TOML data structure - type(toml_table), intent(inout) :: table - - !> Error handling - type(error_t), allocatable, intent(out) :: error - - class(toml_table), pointer :: child - - call check(table, error) - if (allocated(error)) return - - call get_value(table, "name", self%name) - call get_value(table, "source-dir", self%source_dir, "app") - call get_value(table, "main", self%main, "main.f90") - - call get_value(table, "dependencies", child, requested=.false.) - if (associated(child)) then - call new_dependencies(self%dependency, child, error) - if (allocated(error)) return - end if - - end subroutine new_executable - - - !> Check local schema for allowed entries - subroutine check(table, error) - - !> Instance of the TOML data structure - type(toml_table), intent(inout) :: table - - !> Error handling - type(error_t), allocatable, intent(out) :: error - - type(toml_key), allocatable :: list(:) - logical :: name_present - integer :: ikey - - name_present = .false. - - call table%get_keys(list) - - if (.not.allocated(list)) then - call syntax_error(error, "Executable section does not provide sufficient entries") - return - end if - - do ikey = 1, size(list) - select case(list(ikey)%key) - case default - call syntax_error(error, "Key "//list(ikey)%key//" is not allowed executable entry") - exit - - case("name") - name_present = .true. - - case("source-dir", "main", "dependencies") - continue - - end select - end do - - if (.not.name_present) then - call syntax_error(error, "Executable name is not provided, please add a name entry") - end if - - end subroutine check - - - !> Write information on instance - subroutine info(self, unit, verbosity) - - !> Instance of the executable configuration - class(executable_t), intent(in) :: self - - !> Unit for IO - integer, intent(in) :: unit - - !> Verbosity of the printout - integer, intent(in), optional :: verbosity - - integer :: pr, ii - character(len=*), parameter :: fmt = '("#", 1x, a, t30, a)', & - & fmti = '("#", 1x, a, t30, i0)' - - if (present(verbosity)) then - pr = verbosity - else - pr = 1 - end if - - if (pr < 1) return - - write(unit, fmt) "Executable target" - if (allocated(self%name)) then - write(unit, fmt) "- name", self%name - end if - if (allocated(self%source_dir)) then - if (self%source_dir /= "app" .or. pr > 2) then - write(unit, fmt) "- source directory", self%source_dir - end if - end if - if (allocated(self%main)) then - if (self%main /= "main.f90" .or. pr > 2) then - write(unit, fmt) "- program source", self%main - end if - end if - - if (allocated(self%dependency)) then - if (size(self%dependency) > 1 .or. pr > 2) then - write(unit, fmti) "- dependencies", size(self%dependency) - end if - do ii = 1, size(self%dependency) - call self%dependency(ii)%info(unit, pr - 1) - end do - end if - - end subroutine info - - -end module fpm_config_executable diff --git a/fpm/src/fpm/config/library.f90 b/fpm/src/fpm/config/library.f90 deleted file mode 100644 index 0650051..0000000 --- a/fpm/src/fpm/config/library.f90 +++ /dev/null @@ -1,126 +0,0 @@ -!> Implementation of the meta data for libraries. -! -! A library table can currently have the following fields -! -! ```toml -! [library] -! source-dir = "path" -! build-script = "file" -! ``` -module fpm_config_library - use fpm_error, only : error_t, syntax_error - use fpm_toml, only : toml_table, toml_key, toml_stat, get_value - implicit none - private - - public :: library_t, new_library - - - !> Configuration meta data for a library - type :: library_t - - !> Source path prefix - character(len=:), allocatable :: source_dir - - !> Alternative build script to be invoked - character(len=:), allocatable :: build_script - - contains - - !> Print information on this instance - procedure :: info - - end type library_t - - -contains - - - !> Construct a new library configuration from a TOML data structure - subroutine new_library(self, table, error) - - !> Instance of the library configuration - type(library_t), intent(out) :: self - - !> Instance of the TOML data structure - type(toml_table), intent(inout) :: table - - !> Error handling - type(error_t), allocatable, intent(out) :: error - - call check(table, error) - if (allocated(error)) return - - call get_value(table, "source-dir", self%source_dir, "src") - call get_value(table, "build-script", self%build_script) - - end subroutine new_library - - - !> Check local schema for allowed entries - subroutine check(table, error) - - !> Instance of the TOML data structure - type(toml_table), intent(inout) :: table - - !> Error handling - type(error_t), allocatable, intent(out) :: error - - type(toml_key), allocatable :: list(:) - integer :: ikey - - call table%get_keys(list) - - ! table can be empty - if (.not.allocated(list)) return - - do ikey = 1, size(list) - select case(list(ikey)%key) - case default - call syntax_error(error, "Key "//list(ikey)%key//" is not allowed in package file") - exit - - case("source-dir", "build-script") - continue - - end select - end do - - end subroutine check - - - !> Write information on instance - subroutine info(self, unit, verbosity) - - !> Instance of the library configuration - class(library_t), intent(in) :: self - - !> Unit for IO - integer, intent(in) :: unit - - !> Verbosity of the printout - integer, intent(in), optional :: verbosity - - integer :: pr - character(len=*), parameter :: fmt = '("#", 1x, a, t30, a)' - - if (present(verbosity)) then - pr = verbosity - else - pr = 1 - end if - - if (pr < 1) return - - write(unit, fmt) "Library target" - if (allocated(self%source_dir)) then - write(unit, fmt) "- source directory", self%source_dir - end if - if (allocated(self%build_script)) then - write(unit, fmt) "- custom build", self%build_script - end if - - end subroutine info - - -end module fpm_config_library diff --git a/fpm/src/fpm/config/package.f90 b/fpm/src/fpm/config/package.f90 deleted file mode 100644 index 66f275d..0000000 --- a/fpm/src/fpm/config/package.f90 +++ /dev/null @@ -1,270 +0,0 @@ -!> Define the package data containing the meta data from the configuration file. -! -! The package data defines a Fortran type corresponding to the respective -! TOML document, after creating it from a package file no more interaction -! with the TOML document is required. -! -! Every configuration type provides it custom constructor (prefixed with `new_`) -! and knows how to deserialize itself from a TOML document. -! To ensure we find no untracked content in the package file all keywords are -! checked and possible entries have to be explicitly allowed in the `check` -! function. -! If entries are mutally exclusive or interdependent inside the current table -! the `check` function is required to enforce this schema on the data structure. -! -! The package file root allows the following keywords -! -! ```toml -! name = "string" -! version = "string" -! license = "string" -! author = "string" -! maintainer = "string" -! copyright = "string -! [library] -! [dependencies] -! [dev-dependencies] -! [[executable]] -! [[test]] -! ``` -module fpm_config_package - use fpm_config_dependency, only : dependency_t, new_dependencies - use fpm_config_executable, only : executable_t, new_executable - use fpm_config_library, only : library_t, new_library - use fpm_config_test, only : test_t, new_test - use fpm_error, only : error_t, fatal_error, syntax_error - use fpm_toml, only : toml_table, toml_array, toml_key, toml_stat, get_value, & - & len - implicit none - private - - public :: package_t, new_package - - - !> Package meta data - type :: package_t - - !> Name of the package - character(len=:), allocatable :: name - - !> Library meta data - type(library_t), allocatable :: library - - !> Executable meta data - type(executable_t), allocatable :: executable(:) - - !> Dependency meta data - type(dependency_t), allocatable :: dependency(:) - - !> Development dependency meta data - type(dependency_t), allocatable :: dev_dependency(:) - - !> Test meta data - type(test_t), allocatable :: test(:) - - contains - - !> Print information on this instance - procedure :: info - - end type package_t - - -contains - - - !> Construct a new package configuration from a TOML data structure - subroutine new_package(self, table, error) - - !> Instance of the package configuration - type(package_t), intent(out) :: self - - !> Instance of the TOML data structure - type(toml_table), intent(inout) :: table - - !> Error handling - type(error_t), allocatable, intent(out) :: error - - class(toml_table), pointer :: child, node - class(toml_array), pointer :: children - integer :: ii, nn, stat - - call check(table, error) - if (allocated(error)) return - - call get_value(table, "name", self%name) - - call get_value(table, "dependencies", child, requested=.false.) - if (associated(child)) then - call new_dependencies(self%dependency, child, error) - if (allocated(error)) return - end if - - call get_value(table, "dev-dependencies", child, requested=.false.) - if (associated(child)) then - call new_dependencies(self%dev_dependency, child, error) - if (allocated(error)) return - end if - - call get_value(table, "library", child, requested=.false.) - if (associated(child)) then - allocate(self%library) - call new_library(self%library, child, error) - end if - - call get_value(table, "executable", children, requested=.false.) - if (associated(children)) then - nn = len(children) - allocate(self%executable(nn)) - do ii = 1, nn - call get_value(children, ii, node, stat=stat) - if (stat /= toml_stat%success) then - call fatal_error(error, "Could not retrieve executable from array entry") - exit - end if - call new_executable(self%executable(ii), node, error) - if (allocated(error)) exit - end do - end if - - call get_value(table, "test", children, requested=.false.) - if (associated(children)) then - nn = len(children) - allocate(self%test(nn)) - do ii = 1, nn - call get_value(children, ii, node, stat=stat) - if (stat /= toml_stat%success) then - call fatal_error(error, "Could not retrieve test from array entry") - exit - end if - call new_test(self%test(ii), node, error) - if (allocated(error)) exit - end do - end if - - end subroutine new_package - - - !> Check local schema for allowed entries - subroutine check(table, error) - - !> Instance of the TOML data structure - type(toml_table), intent(inout) :: table - - !> Error handling - type(error_t), allocatable, intent(out) :: error - - character(len=:), allocatable :: name - type(toml_key), allocatable :: list(:) - logical :: name_present - integer :: ikey - - name_present = .false. - - call table%get_key(name) - call table%get_keys(list) - - if (.not.allocated(list)) then - call syntax_error(error, "Package file is empty") - return - end if - - do ikey = 1, size(list) - select case(list(ikey)%key) - case default - call syntax_error(error, "Key "//list(ikey)%key//" is not allowed in package file") - exit - - case("name") - name_present = .true. - - case("version", "license", "author", "maintainer", "copyright", & - & "dependencies", "dev-dependencies", "test", "executable", & - & "library") - continue - - end select - end do - if (allocated(error)) return - - if (.not.name_present) then - call syntax_error(error, "Package name is not provided, please add a name entry") - end if - - end subroutine check - - - !> Write information on instance - subroutine info(self, unit, verbosity) - - !> Instance of the package configuration - class(package_t), intent(in) :: self - - !> Unit for IO - integer, intent(in) :: unit - - !> Verbosity of the printout - integer, intent(in), optional :: verbosity - - integer :: pr, ii - character(len=*), parameter :: fmt = '("#", 1x, a, t30, a)', & - & fmti = '("#", 1x, a, t30, i0)' - - if (present(verbosity)) then - pr = verbosity - else - pr = 1 - end if - - if (pr < 1) return - - write(unit, fmt) "Package" - if (allocated(self%name)) then - write(unit, fmt) "- name", self%name - end if - - if (allocated(self%library)) then - write(unit, fmt) "- target", "archive" - call self%library%info(unit, pr - 1) - end if - - if (allocated(self%executable)) then - if (size(self%executable) > 1 .or. pr > 2) then - write(unit, fmti) "- executables", size(self%executable) - end if - do ii = 1, size(self%executable) - call self%executable(ii)%info(unit, pr - 1) - end do - end if - - if (allocated(self%dependency)) then - if (size(self%dependency) > 1 .or. pr > 2) then - write(unit, fmti) "- dependencies", size(self%dependency) - end if - do ii = 1, size(self%dependency) - call self%dependency(ii)%info(unit, pr - 1) - end do - end if - - if (allocated(self%test)) then - if (size(self%test) > 1 .or. pr > 2) then - write(unit, fmti) "- tests", size(self%test) - end if - do ii = 1, size(self%test) - call self%test(ii)%info(unit, pr - 1) - end do - end if - - if (allocated(self%dev_dependency)) then - if (size(self%dev_dependency) > 1 .or. pr > 2) then - write(unit, fmti) "- development deps.", size(self%dev_dependency) - end if - do ii = 1, size(self%dev_dependency) - call self%dev_dependency(ii)%info(unit, pr - 1) - end do - end if - - end subroutine info - - -end module fpm_config_package diff --git a/fpm/src/fpm/config/test.f90 b/fpm/src/fpm/config/test.f90 deleted file mode 100644 index 5c6c9f3..0000000 --- a/fpm/src/fpm/config/test.f90 +++ /dev/null @@ -1,166 +0,0 @@ -!> Implementation of the meta data for a test. -! -! The test data structure is effectively a decorated version of an executable -! and shares most of its properties, except for the defaults and can be -! handled under most circumstances just like any other executable. -! -! A test table can currently have the following fields -! -! ```toml -! [[test]] -! name = "string" -! source-dir = "path" -! main = "file" -! [test.dependencies] -! ``` -module fpm_config_test - use fpm_config_dependency, only : dependency_t, new_dependencies - use fpm_config_executable, only : executable_t - use fpm_error, only : error_t, syntax_error - use fpm_toml, only : toml_table, toml_key, toml_stat, get_value - implicit none - private - - public :: test_t, new_test - - - !> Configuation meta data for an test - type, extends(executable_t) :: test_t - - contains - - !> Print information on this instance - procedure :: info - - end type test_t - - -contains - - - !> Construct a new test configuration from a TOML data structure - subroutine new_test(self, table, error) - - !> Instance of the test configuration - type(test_t), intent(out) :: self - - !> Instance of the TOML data structure - type(toml_table), intent(inout) :: table - - !> Error handling - type(error_t), allocatable, intent(out) :: error - - class(toml_table), pointer :: child - - call check(table, error) - if (allocated(error)) return - - call get_value(table, "name", self%name) - call get_value(table, "source-dir", self%source_dir, "test") - call get_value(table, "main", self%main, "main.f90") - - call get_value(table, "dependencies", child, requested=.false.) - if (associated(child)) then - call new_dependencies(self%dependency, child, error) - if (allocated(error)) return - end if - - end subroutine new_test - - - !> Check local schema for allowed entries - subroutine check(table, error) - - !> Instance of the TOML data structure - type(toml_table), intent(inout) :: table - - !> Error handling - type(error_t), allocatable, intent(out) :: error - - type(toml_key), allocatable :: list(:) - logical :: name_present - integer :: ikey - - name_present = .false. - - call table%get_keys(list) - - if (.not.allocated(list)) then - call syntax_error(error, "Executable section does not provide sufficient entries") - return - end if - - do ikey = 1, size(list) - select case(list(ikey)%key) - case default - call syntax_error(error, "Key "//list(ikey)%key//" is not allowed in test entry") - exit - - case("name") - name_present = .true. - - case("source-dir", "main", "dependencies") - continue - - end select - end do - - if (.not.name_present) then - call syntax_error(error, "Executable name is not provided, please add a name entry") - end if - - end subroutine check - - - !> Write information on instance - subroutine info(self, unit, verbosity) - - !> Instance of the test configuration - class(test_t), intent(in) :: self - - !> Unit for IO - integer, intent(in) :: unit - - !> Verbosity of the printout - integer, intent(in), optional :: verbosity - - integer :: pr, ii - character(len=*), parameter :: fmt = '("#", 1x, a, t30, a)', & - & fmti = '("#", 1x, a, t30, i0)' - - if (present(verbosity)) then - pr = verbosity - else - pr = 1 - end if - - if (pr < 1) return - - write(unit, fmt) "Test target" - if (allocated(self%name)) then - write(unit, fmt) "- name", self%name - end if - if (allocated(self%source_dir)) then - if (self%source_dir /= "test" .or. pr > 2) then - write(unit, fmt) "- source directory", self%source_dir - end if - end if - if (allocated(self%main)) then - if (self%main /= "main.f90" .or. pr > 2) then - write(unit, fmt) "- test source", self%main - end if - end if - - if (allocated(self%dependency)) then - if (size(self%dependency) > 1 .or. pr > 2) then - write(unit, fmti) "- dependencies", size(self%dependency) - end if - do ii = 1, size(self%dependency) - call self%dependency(ii)%info(unit, pr - 1) - end do - end if - - end subroutine info - - -end module fpm_config_test diff --git a/fpm/src/fpm/manifest.f90 b/fpm/src/fpm/manifest.f90 new file mode 100644 index 0000000..af4e0fa --- /dev/null +++ b/fpm/src/fpm/manifest.f90 @@ -0,0 +1,79 @@ +!> Package configuration data. +! +! This module provides the necessary procedure to translate a TOML document +! to the corresponding Fortran type, while verifying it with respect to +! its schema. +! +! Additionally, the required data types for users of this module are reexported +! to hide the actual implementation details. +module fpm_manifest + use fpm_manifest_executable, only : executable_t + use fpm_manifest_library, only : library_t + use fpm_manifest_package, only : package_t, new_package + use fpm_error, only : error_t, fatal_error, file_not_found_error + use fpm_toml, only : toml_table, read_package_file + implicit none + private + + public :: get_package_data, default_executable, default_library + public :: package_t + + +contains + + + !> Populate library in case we find the default src directory + subroutine default_library(self) + + !> Instance of the library meta data + type(library_t), intent(out) :: self + + self%source_dir = "src" + + end subroutine default_library + + + !> Populate executable in case we find the default app directory + subroutine default_executable(self, name) + + !> Instance of the executable meta data + type(executable_t), intent(out) :: self + + !> Name of the package + character(len=*), intent(in) :: name + + self%name = name + self%source_dir = "app" + self%main = "main.f90" + + end subroutine default_executable + + + !> Obtain package meta data from a configuation file + subroutine get_package_data(package, file, error) + + !> Parsed package meta data + type(package_t), intent(out) :: package + + !> Name of the package configuration file + character(len=*), intent(in) :: file + + !> Error status of the operation + type(error_t), allocatable, intent(out) :: error + + type(toml_table), allocatable :: table + + call read_package_file(table, file, error) + if (allocated(error)) return + + if (.not.allocated(table)) then + call fatal_error(error, "Unclassified error while reading: '"//file//"'") + return + end if + + call new_package(package, table, error) + + end subroutine get_package_data + + +end module fpm_manifest diff --git a/fpm/src/fpm/manifest/dependency.f90 b/fpm/src/fpm/manifest/dependency.f90 new file mode 100644 index 0000000..1ee61b7 --- /dev/null +++ b/fpm/src/fpm/manifest/dependency.f90 @@ -0,0 +1,241 @@ +!> Implementation of the meta data for dependencies. +! +! A dependency table can currently have the following fields +! +! ```toml +! [dependencies] +! "dep1" = { git = "url" } +! "dep2" = { git = "url", branch = "name" } +! "dep3" = { git = "url", tag = "name" } +! "dep4" = { git = "url", rev = "sha1" } +! "dep0" = { path = "path" } +! ``` +! +! To reduce the amount of boilerplate code this module provides two constructors +! for dependency types, one basic for an actual dependency (inline) table +! and another to collect all dependency objects from a dependencies table, +! which is handling the allocation of the objects and is forwarding the +! individual dependency tables to their respective constructors. +! The usual entry point should be the constructor for the super table. +! +! This objects contains a target to retrieve required `fpm` projects to +! build the target declaring the dependency. +! Resolving a dependency will result in obtaining a new package configuration +! data for the respective project. +module fpm_manifest_dependency + use fpm_error, only : error_t, syntax_error + use fpm_git, only : git_target_t, git_target_tag, git_target_branch, & + & git_target_revision, git_target_default + use fpm_toml, only : toml_table, toml_key, toml_stat, get_value + implicit none + private + + public :: dependency_t, new_dependency, new_dependencies + + + !> Configuration meta data for a dependency + type :: dependency_t + + !> Name of the dependency + character(len=:), allocatable :: name + + !> Local target + character(len=:), allocatable :: path + + !> Git descriptor + type(git_target_t), allocatable :: git + + contains + + !> Print information on this instance + procedure :: info + + end type dependency_t + + +contains + + + !> Construct a new dependency configuration from a TOML data structure + subroutine new_dependency(self, table, error) + + !> Instance of the dependency configuration + type(dependency_t), intent(out) :: self + + !> Instance of the TOML data structure + type(toml_table), intent(inout) :: table + + !> Error handling + type(error_t), allocatable, intent(out) :: error + + character(len=:), allocatable :: url, obj + + call check(table, error) + if (allocated(error)) return + + call table%get_key(self%name) + + call get_value(table, "path", url) + if (allocated(url)) then + call move_alloc(url, self%path) + else + call get_value(table, "git", url) + + call get_value(table, "tag", obj) + if (allocated(obj)) then + self%git = git_target_tag(url, obj) + end if + + if (.not.allocated(self%git)) then + call get_value(table, "branch", obj) + if (allocated(obj)) then + self%git = git_target_branch(url, obj) + end if + end if + + if (.not.allocated(self%git)) then + call get_value(table, "revision", obj) + if (allocated(obj)) then + self%git = git_target_revision(url, obj) + end if + end if + + if (.not.allocated(self%git)) then + self%git = git_target_default(url) + end if + + end if + + end subroutine new_dependency + + + !> Check local schema for allowed entries + subroutine check(table, error) + + !> Instance of the TOML data structure + type(toml_table), intent(inout) :: table + + !> Error handling + type(error_t), allocatable, intent(out) :: error + + character(len=:), allocatable :: name + type(toml_key), allocatable :: list(:) + logical :: url_present, git_target_present + integer :: ikey + + url_present = .false. + git_target_present = .false. + + call table%get_key(name) + call table%get_keys(list) + + if (.not.allocated(list)) then + call syntax_error(error, "Dependency "//name//" does not provide sufficient entries") + return + end if + + do ikey = 1, size(list) + select case(list(ikey)%key) + case default + call syntax_error(error, "Key "//list(ikey)%key//" is not allowed in dependency "//name) + exit + + case("git", "path") + if (url_present) then + call syntax_error(error, "Dependency "//name//" cannot have both git and path entries") + exit + end if + url_present = .true. + + case("branch", "rev", "tag") + if (git_target_present) then + call syntax_error(error, "Dependency "//name//" can only have one of branch, rev or tag present") + exit + end if + git_target_present = .true. + + end select + end do + if (allocated(error)) return + + if (.not.url_present .and. git_target_present) then + call syntax_error(error, "Dependency "//name//" uses a local path, therefore no git identifiers are allowed") + end if + + end subroutine check + + + !> Construct new dependency array from a TOML data structure + subroutine new_dependencies(deps, table, error) + + !> Instance of the dependency configuration + type(dependency_t), allocatable, intent(out) :: deps(:) + + !> Instance of the TOML data structure + type(toml_table), intent(inout) :: table + + !> Error handling + type(error_t), allocatable, intent(out) :: error + + class(toml_table), pointer :: node + type(toml_key), allocatable :: list(:) + integer :: idep, stat + + call table%get_keys(list) + ! An empty table is okay + if (.not.allocated(list)) return + + allocate(deps(size(list))) + do idep = 1, size(list) + call get_value(table, list(idep)%key, node, stat=stat) + if (stat /= toml_stat%success) then + call syntax_error(error, "Dependency "//list(idep)%key//" must be a table entry") + exit + end if + call new_dependency(deps(idep), node, error) + if (allocated(error)) exit + end do + + end subroutine new_dependencies + + + !> Write information on instance + subroutine info(self, unit, verbosity) + + !> Instance of the dependency configuration + class(dependency_t), intent(in) :: self + + !> Unit for IO + integer, intent(in) :: unit + + !> Verbosity of the printout + integer, intent(in), optional :: verbosity + + integer :: pr + character(len=*), parameter :: fmt = '("#", 1x, a, t30, a)' + + if (present(verbosity)) then + pr = verbosity + else + pr = 1 + end if + + write(unit, fmt) "Dependency" + if (allocated(self%name)) then + write(unit, fmt) "- name", self%name + end if + + if (allocated(self%git)) then + write(unit, fmt) "- kind", "git" + call self%git%info(unit, pr - 1) + end if + + if (allocated(self%path)) then + write(unit, fmt) "- kind", "local" + write(unit, fmt) "- path", self%path + end if + + end subroutine info + + +end module fpm_manifest_dependency diff --git a/fpm/src/fpm/manifest/executable.f90 b/fpm/src/fpm/manifest/executable.f90 new file mode 100644 index 0000000..704396a --- /dev/null +++ b/fpm/src/fpm/manifest/executable.f90 @@ -0,0 +1,173 @@ +!> Implementation of the meta data for an executables. +! +! An executable table can currently have the following fields +! +! ```toml +! [[executable]] +! name = "string" +! source-dir = "path" +! main = "file" +! [executable.dependencies] +! ``` +module fpm_manifest_executable + use fpm_manifest_dependency, only : dependency_t, new_dependencies + use fpm_error, only : error_t, syntax_error + use fpm_toml, only : toml_table, toml_key, toml_stat, get_value + implicit none + private + + public :: executable_t, new_executable + + + !> Configuation meta data for an executable + type :: executable_t + + !> Name of the resulting executable + character(len=:), allocatable :: name + + !> Source directory for collecting the executable + character(len=:), allocatable :: source_dir + + !> Name of the source file declaring the main program + character(len=:), allocatable :: main + + !> Dependency meta data for this executable + type(dependency_t), allocatable :: dependency(:) + + contains + + !> Print information on this instance + procedure :: info + + end type executable_t + + +contains + + + !> Construct a new executable configuration from a TOML data structure + subroutine new_executable(self, table, error) + + !> Instance of the executable configuration + type(executable_t), intent(out) :: self + + !> Instance of the TOML data structure + type(toml_table), intent(inout) :: table + + !> Error handling + type(error_t), allocatable, intent(out) :: error + + class(toml_table), pointer :: child + + call check(table, error) + if (allocated(error)) return + + call get_value(table, "name", self%name) + call get_value(table, "source-dir", self%source_dir, "app") + call get_value(table, "main", self%main, "main.f90") + + call get_value(table, "dependencies", child, requested=.false.) + if (associated(child)) then + call new_dependencies(self%dependency, child, error) + if (allocated(error)) return + end if + + end subroutine new_executable + + + !> Check local schema for allowed entries + subroutine check(table, error) + + !> Instance of the TOML data structure + type(toml_table), intent(inout) :: table + + !> Error handling + type(error_t), allocatable, intent(out) :: error + + type(toml_key), allocatable :: list(:) + logical :: name_present + integer :: ikey + + name_present = .false. + + call table%get_keys(list) + + if (.not.allocated(list)) then + call syntax_error(error, "Executable section does not provide sufficient entries") + return + end if + + do ikey = 1, size(list) + select case(list(ikey)%key) + case default + call syntax_error(error, "Key "//list(ikey)%key//" is not allowed executable entry") + exit + + case("name") + name_present = .true. + + case("source-dir", "main", "dependencies") + continue + + end select + end do + + if (.not.name_present) then + call syntax_error(error, "Executable name is not provided, please add a name entry") + end if + + end subroutine check + + + !> Write information on instance + subroutine info(self, unit, verbosity) + + !> Instance of the executable configuration + class(executable_t), intent(in) :: self + + !> Unit for IO + integer, intent(in) :: unit + + !> Verbosity of the printout + integer, intent(in), optional :: verbosity + + integer :: pr, ii + character(len=*), parameter :: fmt = '("#", 1x, a, t30, a)', & + & fmti = '("#", 1x, a, t30, i0)' + + if (present(verbosity)) then + pr = verbosity + else + pr = 1 + end if + + if (pr < 1) return + + write(unit, fmt) "Executable target" + if (allocated(self%name)) then + write(unit, fmt) "- name", self%name + end if + if (allocated(self%source_dir)) then + if (self%source_dir /= "app" .or. pr > 2) then + write(unit, fmt) "- source directory", self%source_dir + end if + end if + if (allocated(self%main)) then + if (self%main /= "main.f90" .or. pr > 2) then + write(unit, fmt) "- program source", self%main + end if + end if + + if (allocated(self%dependency)) then + if (size(self%dependency) > 1 .or. pr > 2) then + write(unit, fmti) "- dependencies", size(self%dependency) + end if + do ii = 1, size(self%dependency) + call self%dependency(ii)%info(unit, pr - 1) + end do + end if + + end subroutine info + + +end module fpm_manifest_executable diff --git a/fpm/src/fpm/manifest/library.f90 b/fpm/src/fpm/manifest/library.f90 new file mode 100644 index 0000000..a297c2f --- /dev/null +++ b/fpm/src/fpm/manifest/library.f90 @@ -0,0 +1,126 @@ +!> Implementation of the meta data for libraries. +! +! A library table can currently have the following fields +! +! ```toml +! [library] +! source-dir = "path" +! build-script = "file" +! ``` +module fpm_manifest_library + use fpm_error, only : error_t, syntax_error + use fpm_toml, only : toml_table, toml_key, toml_stat, get_value + implicit none + private + + public :: library_t, new_library + + + !> Configuration meta data for a library + type :: library_t + + !> Source path prefix + character(len=:), allocatable :: source_dir + + !> Alternative build script to be invoked + character(len=:), allocatable :: build_script + + contains + + !> Print information on this instance + procedure :: info + + end type library_t + + +contains + + + !> Construct a new library configuration from a TOML data structure + subroutine new_library(self, table, error) + + !> Instance of the library configuration + type(library_t), intent(out) :: self + + !> Instance of the TOML data structure + type(toml_table), intent(inout) :: table + + !> Error handling + type(error_t), allocatable, intent(out) :: error + + call check(table, error) + if (allocated(error)) return + + call get_value(table, "source-dir", self%source_dir, "src") + call get_value(table, "build-script", self%build_script) + + end subroutine new_library + + + !> Check local schema for allowed entries + subroutine check(table, error) + + !> Instance of the TOML data structure + type(toml_table), intent(inout) :: table + + !> Error handling + type(error_t), allocatable, intent(out) :: error + + type(toml_key), allocatable :: list(:) + integer :: ikey + + call table%get_keys(list) + + ! table can be empty + if (.not.allocated(list)) return + + do ikey = 1, size(list) + select case(list(ikey)%key) + case default + call syntax_error(error, "Key "//list(ikey)%key//" is not allowed in package file") + exit + + case("source-dir", "build-script") + continue + + end select + end do + + end subroutine check + + + !> Write information on instance + subroutine info(self, unit, verbosity) + + !> Instance of the library configuration + class(library_t), intent(in) :: self + + !> Unit for IO + integer, intent(in) :: unit + + !> Verbosity of the printout + integer, intent(in), optional :: verbosity + + integer :: pr + character(len=*), parameter :: fmt = '("#", 1x, a, t30, a)' + + if (present(verbosity)) then + pr = verbosity + else + pr = 1 + end if + + if (pr < 1) return + + write(unit, fmt) "Library target" + if (allocated(self%source_dir)) then + write(unit, fmt) "- source directory", self%source_dir + end if + if (allocated(self%build_script)) then + write(unit, fmt) "- custom build", self%build_script + end if + + end subroutine info + + +end module fpm_manifest_library diff --git a/fpm/src/fpm/manifest/package.f90 b/fpm/src/fpm/manifest/package.f90 new file mode 100644 index 0000000..f318ad7 --- /dev/null +++ b/fpm/src/fpm/manifest/package.f90 @@ -0,0 +1,270 @@ +!> Define the package data containing the meta data from the configuration file. +! +! The package data defines a Fortran type corresponding to the respective +! TOML document, after creating it from a package file no more interaction +! with the TOML document is required. +! +! Every configuration type provides it custom constructor (prefixed with `new_`) +! and knows how to deserialize itself from a TOML document. +! To ensure we find no untracked content in the package file all keywords are +! checked and possible entries have to be explicitly allowed in the `check` +! function. +! If entries are mutally exclusive or interdependent inside the current table +! the `check` function is required to enforce this schema on the data structure. +! +! The package file root allows the following keywords +! +! ```toml +! name = "string" +! version = "string" +! license = "string" +! author = "string" +! maintainer = "string" +! copyright = "string +! [library] +! [dependencies] +! [dev-dependencies] +! [[executable]] +! [[test]] +! ``` +module fpm_manifest_package + use fpm_manifest_dependency, only : dependency_t, new_dependencies + use fpm_manifest_executable, only : executable_t, new_executable + use fpm_manifest_library, only : library_t, new_library + use fpm_manifest_test, only : test_t, new_test + use fpm_error, only : error_t, fatal_error, syntax_error + use fpm_toml, only : toml_table, toml_array, toml_key, toml_stat, get_value, & + & len + implicit none + private + + public :: package_t, new_package + + + !> Package meta data + type :: package_t + + !> Name of the package + character(len=:), allocatable :: name + + !> Library meta data + type(library_t), allocatable :: library + + !> Executable meta data + type(executable_t), allocatable :: executable(:) + + !> Dependency meta data + type(dependency_t), allocatable :: dependency(:) + + !> Development dependency meta data + type(dependency_t), allocatable :: dev_dependency(:) + + !> Test meta data + type(test_t), allocatable :: test(:) + + contains + + !> Print information on this instance + procedure :: info + + end type package_t + + +contains + + + !> Construct a new package configuration from a TOML data structure + subroutine new_package(self, table, error) + + !> Instance of the package configuration + type(package_t), intent(out) :: self + + !> Instance of the TOML data structure + type(toml_table), intent(inout) :: table + + !> Error handling + type(error_t), allocatable, intent(out) :: error + + class(toml_table), pointer :: child, node + class(toml_array), pointer :: children + integer :: ii, nn, stat + + call check(table, error) + if (allocated(error)) return + + call get_value(table, "name", self%name) + + call get_value(table, "dependencies", child, requested=.false.) + if (associated(child)) then + call new_dependencies(self%dependency, child, error) + if (allocated(error)) return + end if + + call get_value(table, "dev-dependencies", child, requested=.false.) + if (associated(child)) then + call new_dependencies(self%dev_dependency, child, error) + if (allocated(error)) return + end if + + call get_value(table, "library", child, requested=.false.) + if (associated(child)) then + allocate(self%library) + call new_library(self%library, child, error) + end if + + call get_value(table, "executable", children, requested=.false.) + if (associated(children)) then + nn = len(children) + allocate(self%executable(nn)) + do ii = 1, nn + call get_value(children, ii, node, stat=stat) + if (stat /= toml_stat%success) then + call fatal_error(error, "Could not retrieve executable from array entry") + exit + end if + call new_executable(self%executable(ii), node, error) + if (allocated(error)) exit + end do + end if + + call get_value(table, "test", children, requested=.false.) + if (associated(children)) then + nn = len(children) + allocate(self%test(nn)) + do ii = 1, nn + call get_value(children, ii, node, stat=stat) + if (stat /= toml_stat%success) then + call fatal_error(error, "Could not retrieve test from array entry") + exit + end if + call new_test(self%test(ii), node, error) + if (allocated(error)) exit + end do + end if + + end subroutine new_package + + + !> Check local schema for allowed entries + subroutine check(table, error) + + !> Instance of the TOML data structure + type(toml_table), intent(inout) :: table + + !> Error handling + type(error_t), allocatable, intent(out) :: error + + character(len=:), allocatable :: name + type(toml_key), allocatable :: list(:) + logical :: name_present + integer :: ikey + + name_present = .false. + + call table%get_key(name) + call table%get_keys(list) + + if (.not.allocated(list)) then + call syntax_error(error, "Package file is empty") + return + end if + + do ikey = 1, size(list) + select case(list(ikey)%key) + case default + call syntax_error(error, "Key "//list(ikey)%key//" is not allowed in package file") + exit + + case("name") + name_present = .true. + + case("version", "license", "author", "maintainer", "copyright", & + & "dependencies", "dev-dependencies", "test", "executable", & + & "library") + continue + + end select + end do + if (allocated(error)) return + + if (.not.name_present) then + call syntax_error(error, "Package name is not provided, please add a name entry") + end if + + end subroutine check + + + !> Write information on instance + subroutine info(self, unit, verbosity) + + !> Instance of the package configuration + class(package_t), intent(in) :: self + + !> Unit for IO + integer, intent(in) :: unit + + !> Verbosity of the printout + integer, intent(in), optional :: verbosity + + integer :: pr, ii + character(len=*), parameter :: fmt = '("#", 1x, a, t30, a)', & + & fmti = '("#", 1x, a, t30, i0)' + + if (present(verbosity)) then + pr = verbosity + else + pr = 1 + end if + + if (pr < 1) return + + write(unit, fmt) "Package" + if (allocated(self%name)) then + write(unit, fmt) "- name", self%name + end if + + if (allocated(self%library)) then + write(unit, fmt) "- target", "archive" + call self%library%info(unit, pr - 1) + end if + + if (allocated(self%executable)) then + if (size(self%executable) > 1 .or. pr > 2) then + write(unit, fmti) "- executables", size(self%executable) + end if + do ii = 1, size(self%executable) + call self%executable(ii)%info(unit, pr - 1) + end do + end if + + if (allocated(self%dependency)) then + if (size(self%dependency) > 1 .or. pr > 2) then + write(unit, fmti) "- dependencies", size(self%dependency) + end if + do ii = 1, size(self%dependency) + call self%dependency(ii)%info(unit, pr - 1) + end do + end if + + if (allocated(self%test)) then + if (size(self%test) > 1 .or. pr > 2) then + write(unit, fmti) "- tests", size(self%test) + end if + do ii = 1, size(self%test) + call self%test(ii)%info(unit, pr - 1) + end do + end if + + if (allocated(self%dev_dependency)) then + if (size(self%dev_dependency) > 1 .or. pr > 2) then + write(unit, fmti) "- development deps.", size(self%dev_dependency) + end if + do ii = 1, size(self%dev_dependency) + call self%dev_dependency(ii)%info(unit, pr - 1) + end do + end if + + end subroutine info + + +end module fpm_manifest_package diff --git a/fpm/src/fpm/manifest/test.f90 b/fpm/src/fpm/manifest/test.f90 new file mode 100644 index 0000000..9b50315 --- /dev/null +++ b/fpm/src/fpm/manifest/test.f90 @@ -0,0 +1,166 @@ +!> Implementation of the meta data for a test. +! +! The test data structure is effectively a decorated version of an executable +! and shares most of its properties, except for the defaults and can be +! handled under most circumstances just like any other executable. +! +! A test table can currently have the following fields +! +! ```toml +! [[test]] +! name = "string" +! source-dir = "path" +! main = "file" +! [test.dependencies] +! ``` +module fpm_manifest_test + use fpm_manifest_dependency, only : dependency_t, new_dependencies + use fpm_manifest_executable, only : executable_t + use fpm_error, only : error_t, syntax_error + use fpm_toml, only : toml_table, toml_key, toml_stat, get_value + implicit none + private + + public :: test_t, new_test + + + !> Configuation meta data for an test + type, extends(executable_t) :: test_t + + contains + + !> Print information on this instance + procedure :: info + + end type test_t + + +contains + + + !> Construct a new test configuration from a TOML data structure + subroutine new_test(self, table, error) + + !> Instance of the test configuration + type(test_t), intent(out) :: self + + !> Instance of the TOML data structure + type(toml_table), intent(inout) :: table + + !> Error handling + type(error_t), allocatable, intent(out) :: error + + class(toml_table), pointer :: child + + call check(table, error) + if (allocated(error)) return + + call get_value(table, "name", self%name) + call get_value(table, "source-dir", self%source_dir, "test") + call get_value(table, "main", self%main, "main.f90") + + call get_value(table, "dependencies", child, requested=.false.) + if (associated(child)) then + call new_dependencies(self%dependency, child, error) + if (allocated(error)) return + end if + + end subroutine new_test + + + !> Check local schema for allowed entries + subroutine check(table, error) + + !> Instance of the TOML data structure + type(toml_table), intent(inout) :: table + + !> Error handling + type(error_t), allocatable, intent(out) :: error + + type(toml_key), allocatable :: list(:) + logical :: name_present + integer :: ikey + + name_present = .false. + + call table%get_keys(list) + + if (.not.allocated(list)) then + call syntax_error(error, "Executable section does not provide sufficient entries") + return + end if + + do ikey = 1, size(list) + select case(list(ikey)%key) + case default + call syntax_error(error, "Key "//list(ikey)%key//" is not allowed in test entry") + exit + + case("name") + name_present = .true. + + case("source-dir", "main", "dependencies") + continue + + end select + end do + + if (.not.name_present) then + call syntax_error(error, "Executable name is not provided, please add a name entry") + end if + + end subroutine check + + + !> Write information on instance + subroutine info(self, unit, verbosity) + + !> Instance of the test configuration + class(test_t), intent(in) :: self + + !> Unit for IO + integer, intent(in) :: unit + + !> Verbosity of the printout + integer, intent(in), optional :: verbosity + + integer :: pr, ii + character(len=*), parameter :: fmt = '("#", 1x, a, t30, a)', & + & fmti = '("#", 1x, a, t30, i0)' + + if (present(verbosity)) then + pr = verbosity + else + pr = 1 + end if + + if (pr < 1) return + + write(unit, fmt) "Test target" + if (allocated(self%name)) then + write(unit, fmt) "- name", self%name + end if + if (allocated(self%source_dir)) then + if (self%source_dir /= "test" .or. pr > 2) then + write(unit, fmt) "- source directory", self%source_dir + end if + end if + if (allocated(self%main)) then + if (self%main /= "main.f90" .or. pr > 2) then + write(unit, fmt) "- test source", self%main + end if + end if + + if (allocated(self%dependency)) then + if (size(self%dependency) > 1 .or. pr > 2) then + write(unit, fmti) "- dependencies", size(self%dependency) + end if + do ii = 1, size(self%dependency) + call self%dependency(ii)%info(unit, pr - 1) + end do + end if + + end subroutine info + + +end module fpm_manifest_test diff --git a/fpm/src/fpm/toml.f90 b/fpm/src/fpm/toml.f90 index d847c69..d95a093 100644 --- a/fpm/src/fpm/toml.f90 +++ b/fpm/src/fpm/toml.f90 @@ -27,13 +27,13 @@ contains !> Process the configuration file to a TOML data structure - subroutine read_package_file(table, config, error) + subroutine read_package_file(table, manifest, error) !> TOML data structure type(toml_table), allocatable, intent(out) :: table !> Name of the package configuration file - character(len=*), intent(in) :: config + character(len=*), intent(in) :: manifest !> Error status of the operation type(error_t), allocatable, intent(out) :: error @@ -42,14 +42,14 @@ contains integer :: unit logical :: exist - inquire(file=config, exist=exist) + inquire(file=manifest, exist=exist) if (.not.exist) then - call file_not_found_error(error, config) + call file_not_found_error(error, manifest) return end if - open(file=config, newunit=unit) + open(file=manifest, newunit=unit) call toml_parse(table, unit, parse_error) close(unit) diff --git a/fpm/test/main.f90 b/fpm/test/main.f90 index c4bfee5..19bcdb6 100644 --- a/fpm/test/main.f90 +++ b/fpm/test/main.f90 @@ -3,7 +3,7 @@ program fpm_testing use, intrinsic :: iso_fortran_env, only : error_unit use testsuite, only : run_testsuite use test_toml, only : collect_toml - use test_config, only : collect_config + use test_manifest, only : collect_manifest implicit none integer :: stat character(len=*), parameter :: fmt = '("#", *(1x, a))' @@ -16,8 +16,8 @@ program fpm_testing error stop 1 end if - write(error_unit, fmt) "Testing:", "fpm_config" - call run_testsuite(collect_config, error_unit, stat) + write(error_unit, fmt) "Testing:", "fpm_manifest" + call run_testsuite(collect_manifest, error_unit, stat) if (stat > 0) then write(error_unit, '(i0, 1x, a)') stat, "tests failed!" diff --git a/fpm/test/test_config.f90 b/fpm/test/test_config.f90 deleted file mode 100644 index ecdf0a5..0000000 --- a/fpm/test/test_config.f90 +++ /dev/null @@ -1,188 +0,0 @@ -!> Define tests for the `fpm_config` modules -module test_config - use testsuite, only : new_unittest, unittest_t, error_t, test_failed - use fpm_config - implicit none - private - - public :: collect_config - - -contains - - - !> Collect all exported unit tests - subroutine collect_config(testsuite) - - !> Collection of tests - type(unittest_t), allocatable, intent(out) :: testsuite(:) - - testsuite = [ & - & new_unittest("valid-config", test_valid_config), & - & new_unittest("invalid-config", test_invalid_config, should_fail=.true.), & - & new_unittest("default-library", test_default_library), & - & new_unittest("default-executable", test_default_executable)] - - end subroutine collect_config - - - !> Try to read some unnecessary obscure and convoluted but not invalid package file - subroutine test_valid_config(error) - - !> Error handling - type(error_t), allocatable, intent(out) :: error - - type(package_t) :: package - character(len=*), parameter :: config = 'fpm-valid-config.toml' - character(len=:), allocatable :: string - integer :: unit - - open(file=config, newunit=unit) - write(unit, '(a)') & - & 'name = "example"', & - & '[dependencies.fpm]', & - & 'git = "https://github.com/fortran-lang/fpm"', & - & '[[executable]]', & - & 'name = "example-#1" # comment', & - & 'source-dir = "prog"', & - & '[dependencies]', & - & 'toml-f.git = "git@github.com:toml-f/toml-f.git"', & - & '"toml..f" = { path = ".." }', & - & '[["executable"]]', & - & 'name = "example-#2"', & - & 'source-dir = "prog"', & - & '[executable.dependencies]', & - & '[''library'']', & - & 'source-dir = """', & - & 'lib""" # comment' - close(unit) - - call get_package_data(package, config, error) - - open(file=config, newunit=unit) - close(unit, status='delete') - - if (allocated(error)) return - - if (package%name /= "example") then - call test_failed(error, "Package name is "//package%name//" but should be example") - return - end if - - if (.not.allocated(package%library)) then - call test_failed(error, "library is not present in package data") - return - end if - - if (.not.allocated(package%executable)) then - call test_failed(error, "executable is not present in package data") - return - end if - - if (size(package%executable) /= 2) then - call test_failed(error, "Number of executables in package is not two") - return - end if - - if (.not.allocated(package%dependency)) then - call test_failed(error, "dependency is not present in package data") - return - end if - - if (size(package%dependency) /= 3) then - call test_failed(error, "Number of dependencies in package is not three") - return - end if - - if (allocated(package%test)) then - call test_failed(error, "test is present in package but not in package file") - return - end if - - end subroutine test_valid_config - - - !> Try to read a valid TOML document which represent an invalid package file - subroutine test_invalid_config(error) - - !> Error handling - type(error_t), allocatable, intent(out) :: error - - type(package_t) :: package - character(len=*), parameter :: config = 'fpm-invalid-config.toml' - character(len=:), allocatable :: string - integer :: unit - - open(file=config, newunit=unit) - write(unit, '(a)') & - & '[package]', & - & 'name = "example"', & - & 'version = "0.1.0"' - close(unit) - - call get_package_data(package, config, error) - - open(file=config, newunit=unit) - close(unit, status='delete') - - end subroutine test_invalid_config - - - !> Create a default library - subroutine test_default_library(error) - - !> Error handling - type(error_t), allocatable, intent(out) :: error - - type(package_t) :: package - - allocate(package%library) - call default_library(package%library) - - if (.not.allocated(package%library%source_dir)) then - call test_failed(error, "Default library source-dir is not set") - return - end if - - if (package%library%source_dir /= "src") then - call test_failed(error, "Default library source-dir is "// & - & package%library%source_dir//" but should be src") - return - end if - - end subroutine test_default_library - - - !> Create a default executable - subroutine test_default_executable(error) - - !> Error handling - type(error_t), allocatable, intent(out) :: error - - type(package_t) :: package - character(len=*), parameter :: name = "default" - - allocate(package%executable(1)) - call default_executable(package%executable(1), name) - - if (.not.allocated(package%executable(1)%source_dir)) then - call test_failed(error, "Default executable source-dir is not set") - return - end if - - if (package%executable(1)%source_dir /= "app") then - call test_failed(error, "Default executable source-dir is "// & - & package%executable(1)%source_dir//" but should be app") - return - end if - - if (package%executable(1)%name /= name) then - call test_failed(error, "Default executable name is "// & - & package%executable(1)%name//" but should be "//name) - return - end if - - end subroutine test_default_executable - - -end module test_config diff --git a/fpm/test/test_manifest.f90 b/fpm/test/test_manifest.f90 new file mode 100644 index 0000000..08236d5 --- /dev/null +++ b/fpm/test/test_manifest.f90 @@ -0,0 +1,188 @@ +!> Define tests for the `fpm_manifest` modules +module test_manifest + use testsuite, only : new_unittest, unittest_t, error_t, test_failed + use fpm_manifest + implicit none + private + + public :: collect_manifest + + +contains + + + !> Collect all exported unit tests + subroutine collect_manifest(testsuite) + + !> Collection of tests + type(unittest_t), allocatable, intent(out) :: testsuite(:) + + testsuite = [ & + & new_unittest("valid-manifest", test_valid_manifest), & + & new_unittest("invalid-manifest", test_invalid_manifest, should_fail=.true.), & + & new_unittest("default-library", test_default_library), & + & new_unittest("default-executable", test_default_executable)] + + end subroutine collect_manifest + + + !> Try to read some unnecessary obscure and convoluted but not invalid package file + subroutine test_valid_manifest(error) + + !> Error handling + type(error_t), allocatable, intent(out) :: error + + type(package_t) :: package + character(len=*), parameter :: manifest = 'fpm-valid-manifest.toml' + character(len=:), allocatable :: string + integer :: unit + + open(file=manifest, newunit=unit) + write(unit, '(a)') & + & 'name = "example"', & + & '[dependencies.fpm]', & + & 'git = "https://github.com/fortran-lang/fpm"', & + & '[[executable]]', & + & 'name = "example-#1" # comment', & + & 'source-dir = "prog"', & + & '[dependencies]', & + & 'toml-f.git = "git@github.com:toml-f/toml-f.git"', & + & '"toml..f" = { path = ".." }', & + & '[["executable"]]', & + & 'name = "example-#2"', & + & 'source-dir = "prog"', & + & '[executable.dependencies]', & + & '[''library'']', & + & 'source-dir = """', & + & 'lib""" # comment' + close(unit) + + call get_package_data(package, manifest, error) + + open(file=manifest, newunit=unit) + close(unit, status='delete') + + if (allocated(error)) return + + if (package%name /= "example") then + call test_failed(error, "Package name is "//package%name//" but should be example") + return + end if + + if (.not.allocated(package%library)) then + call test_failed(error, "library is not present in package data") + return + end if + + if (.not.allocated(package%executable)) then + call test_failed(error, "executable is not present in package data") + return + end if + + if (size(package%executable) /= 2) then + call test_failed(error, "Number of executables in package is not two") + return + end if + + if (.not.allocated(package%dependency)) then + call test_failed(error, "dependency is not present in package data") + return + end if + + if (size(package%dependency) /= 3) then + call test_failed(error, "Number of dependencies in package is not three") + return + end if + + if (allocated(package%test)) then + call test_failed(error, "test is present in package but not in package file") + return + end if + + end subroutine test_valid_manifest + + + !> Try to read a valid TOML document which represent an invalid package file + subroutine test_invalid_manifest(error) + + !> Error handling + type(error_t), allocatable, intent(out) :: error + + type(package_t) :: package + character(len=*), parameter :: manifest = 'fpm-invalid-manifest.toml' + character(len=:), allocatable :: string + integer :: unit + + open(file=manifest, newunit=unit) + write(unit, '(a)') & + & '[package]', & + & 'name = "example"', & + & 'version = "0.1.0"' + close(unit) + + call get_package_data(package, manifest, error) + + open(file=manifest, newunit=unit) + close(unit, status='delete') + + end subroutine test_invalid_manifest + + + !> Create a default library + subroutine test_default_library(error) + + !> Error handling + type(error_t), allocatable, intent(out) :: error + + type(package_t) :: package + + allocate(package%library) + call default_library(package%library) + + if (.not.allocated(package%library%source_dir)) then + call test_failed(error, "Default library source-dir is not set") + return + end if + + if (package%library%source_dir /= "src") then + call test_failed(error, "Default library source-dir is "// & + & package%library%source_dir//" but should be src") + return + end if + + end subroutine test_default_library + + + !> Create a default executable + subroutine test_default_executable(error) + + !> Error handling + type(error_t), allocatable, intent(out) :: error + + type(package_t) :: package + character(len=*), parameter :: name = "default" + + allocate(package%executable(1)) + call default_executable(package%executable(1), name) + + if (.not.allocated(package%executable(1)%source_dir)) then + call test_failed(error, "Default executable source-dir is not set") + return + end if + + if (package%executable(1)%source_dir /= "app") then + call test_failed(error, "Default executable source-dir is "// & + & package%executable(1)%source_dir//" but should be app") + return + end if + + if (package%executable(1)%name /= name) then + call test_failed(error, "Default executable name is "// & + & package%executable(1)%name//" but should be "//name) + return + end if + + end subroutine test_default_executable + + +end module test_manifest diff --git a/fpm/test/test_toml.f90 b/fpm/test/test_toml.f90 index 8d57150..d30ef0d 100644 --- a/fpm/test/test_toml.f90 +++ b/fpm/test/test_toml.f90 @@ -31,11 +31,11 @@ contains type(error_t), allocatable, intent(out) :: error type(toml_table), allocatable :: table - character(len=*), parameter :: config = 'fpm-valid-toml.toml' + character(len=*), parameter :: manifest = 'fpm-valid-toml.toml' character(len=:), allocatable :: string integer :: unit - open(file=config, newunit=unit) + open(file=manifest, newunit=unit) write(unit, '(a)') & & 'name = "example"', & & '[dependencies.fpm]', & @@ -55,9 +55,9 @@ contains & 'lib""" # comment' close(unit) - call read_package_file(table, config, error) + call read_package_file(table, manifest, error) - open(file=config, newunit=unit) + open(file=manifest, newunit=unit) close(unit, status='delete') end subroutine test_valid_toml @@ -70,11 +70,11 @@ contains type(error_t), allocatable, intent(out) :: error type(toml_table), allocatable :: table - character(len=*), parameter :: config = 'fpm-invalid-toml.toml' + character(len=*), parameter :: manifest = 'fpm-invalid-toml.toml' character(len=:), allocatable :: string integer :: unit - open(file=config, newunit=unit) + open(file=manifest, newunit=unit) write(unit, '(a)') & & '# INVALID TOML DOC', & & 'name = "example"', & @@ -84,9 +84,9 @@ contains & '"toml..f" = { path = ".." }' close(unit) - call read_package_file(table, config, error) + call read_package_file(table, manifest, error) - open(file=config, newunit=unit) + open(file=manifest, newunit=unit) close(unit, status='delete') end subroutine test_invalid_toml -- cgit v1.2.3