Skip to content
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

Enforce module naming #828

Merged
merged 33 commits into from
Feb 22, 2023
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
194f051
Add `--no-module-names` switch to fpm build settings
perazz Jan 12, 2023
239e28a
Improve help description
perazz Jan 12, 2023
d3509e9
Pass module naming flag to `fpm_model_t`
perazz Jan 12, 2023
d6d0515
Implement name enforcing rule function and basic unit test
perazz Jan 12, 2023
56a82f5
Add unit tests of invalid module names
perazz Jan 12, 2023
cdb9f54
Refactor: move `use`d to top of module
perazz Jan 12, 2023
107888e
Enable package names with dashes `-` (also test it)
perazz Jan 12, 2023
4c39860
Implement module name checking
perazz Jan 12, 2023
cab41da
Improve error output
perazz Jan 12, 2023
2163058
Implement new naming policy; update tests
perazz Jan 19, 2023
9633604
do not enforce module names by default; remove CLI option `--no-modul…
perazz Jan 19, 2023
39343e4
add `module-naming` option to the manifest under `[build]`
perazz Jan 19, 2023
04b78ad
add manifest tests for `module-naming` fpm.toml flag
perazz Jan 19, 2023
50e0901
`test_manifest.f90`: remove unused variables
perazz Jan 19, 2023
6b90e47
Ensure package name does not end with `_`
perazz Jan 20, 2023
1abf870
cleanup
perazz Jan 20, 2023
01141ce
fix: case-insensitive naming check
perazz Jan 31, 2023
1c39290
fix typo
perazz Jan 31, 2023
dbfbd58
case_sensitive typo
perazz Jan 31, 2023
8c15a42
Parse optional custom prefix
perazz Feb 9, 2023
236e566
bugfix tests with new naming option input
perazz Feb 9, 2023
1b9ba7b
move naming functions to `fpm_strings.f90`
perazz Feb 9, 2023
f6f309d
test custom prefixes
perazz Feb 9, 2023
f8a94f3
test standard and custom-prefixed module names
perazz Feb 9, 2023
bf4b837
query and check custom prefix from all dependencies
perazz Feb 9, 2023
396524c
add warning
perazz Feb 9, 2023
ee2bb68
rename all test modules to `fpm_test*`
perazz Feb 9, 2023
691c9cc
bugfix: rename `testsuite` to `fpm_testsuite` in test main file
perazz Feb 9, 2023
7780d7e
restore original test module names: `fpm_test*`->`test*`
perazz Feb 17, 2023
60b90b8
do not enforce naming on test modules
perazz Feb 17, 2023
2a26611
move to the source loop
perazz Feb 17, 2023
7076752
Merge branch 'main' into enforce_module_naming
perazz Feb 17, 2023
9518b62
Merge branch 'main' into enforce_module_naming
awvwgk Feb 22, 2023
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@ build/*

# Visual Studio Code
.vscode/

# CodeBlocks
project/
128 changes: 125 additions & 3 deletions src/fpm.f90
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module fpm
use fpm_strings, only: string_t, operator(.in.), glob, join, string_cat, &
lower, str_ends_with
lower, str_ends_with, is_fortran_name, str_begins_with_str, &
to_fortran_name
use fpm_backend, only: build_package
use fpm_command_line, only: fpm_build_settings, fpm_new_settings, &
fpm_run_settings, fpm_install_settings, fpm_test_settings, &
Expand Down Expand Up @@ -29,6 +30,7 @@ module fpm
private
public :: cmd_build, cmd_run, cmd_clean
public :: build_model, check_modules_for_duplicates
public :: is_valid_module_name

contains

Expand Down Expand Up @@ -92,6 +94,7 @@ subroutine build_model(model, settings, package, error)
model%build_prefix = join_path("build", basename(model%compiler%fc))

model%include_tests = settings%build_tests
model%enforce_module_names = package%build%module_naming

allocate(model%packages(model%deps%ndep))

Expand All @@ -107,7 +110,7 @@ subroutine build_model(model, settings, package, error)
model%packages(i)%name = dependency%name
call package%version%to_string(version)
model%packages(i)%version = version

if (allocated(dependency%preprocess)) then
do j = 1, size(dependency%preprocess)
if (dependency%preprocess(j)%name == "cpp") then
Expand Down Expand Up @@ -233,7 +236,11 @@ subroutine build_model(model, settings, package, error)
write(*,*)'<INFO> CXX COMPILER OPTIONS: ', model%cxx_compile_flags
write(*,*)'<INFO> LINKER OPTIONS: ', model%link_flags
write(*,*)'<INFO> INCLUDE DIRECTORIES: [', string_cat(model%include_dirs,','),']'
end if
end if

! Check for invalid module names
call check_module_names(model, error)
if (allocated(error)) return

! Check for duplicate modules
call check_modules_for_duplicates(model, duplicates_found)
Expand Down Expand Up @@ -286,6 +293,69 @@ subroutine check_modules_for_duplicates(model, duplicates_found)
end do
end subroutine check_modules_for_duplicates

! Check names of all modules in this package and its dependencies
subroutine check_module_names(model, error)
type(fpm_model_t), intent(in) :: model
type(error_t), allocatable, intent(out) :: error
integer :: i,j,k,l,m
logical :: valid,errors_found
type(string_t) :: package_name,module_name

errors_found = .false.

! Loop through modules provided by each source file of every package
! Add it to the array if it is not already there
! Otherwise print out warning about duplicates
do k=1,size(model%packages)

package_name = string_t(model%packages(k)%name)

do l=1,size(model%packages(k)%sources)
if (allocated(model%packages(k)%sources(l)%modules_provided)) then
do m=1,size(model%packages(k)%sources(l)%modules_provided)

module_name = model%packages(k)%sources(l)%modules_provided(m)

valid = is_valid_module_name(module_name, &
package_name, &
model%enforce_module_names)

if (.not.valid) then

if (model%enforce_module_names) then

write(stderr, *) "ERROR: Module ",module_name%s, &
" in ",model%packages(k)%sources(l)%file_name, &
" does not match its package name ("//package_name%s//")."

else

write(stderr, *) "ERROR: Module ",module_name%s, &
" in ",model%packages(k)%sources(l)%file_name, &
" has an invalid Fortran name. "

end if

errors_found = .true.

end if
end do
end if
end do
end do

if (errors_found) then

if (model%enforce_module_names) &
write(stderr, *) " Hint: Try disabling module naming in the manifest: [build] module-naming=false . "

call fatal_error(error,"The package contains invalid module names. "// &
"Naming conventions "//merge('are','not',model%enforce_module_names)// &
" being requested.")
end if

end subroutine check_module_names

subroutine cmd_build(settings)
type(fpm_build_settings), intent(in) :: settings
type(package_config_t) :: package
Expand Down Expand Up @@ -532,6 +602,58 @@ end subroutine compact_list

end subroutine cmd_run

!> Check that a module name fits the current naming rules:
!> 1) It must be a valid FORTRAN name (<=63 chars, begin with letter, "_" is only allowed non-alphanumeric)
!> 2) It must begin with the package name
!> 3) If longer, package name must be followed by default separator plus at least one char
logical function is_valid_module_name(module_name,package_name,enforce_module_names) result(valid)

type(string_t), intent(in) :: module_name
type(string_t), intent(in) :: package_name
logical , intent(in) :: enforce_module_names

!> Default package__module separator: two underscores
character(*), parameter :: SEP = "__"

character(len=:), allocatable :: fortranized_pkg
logical :: is_same,has_separator,same_beginning
integer :: lpkg,lmod,lsep

!> Basic check: check the name is Fortran-compliant
valid = is_fortran_name(module_name%s)

!> FPM package enforcing: check that the module name begins with the package name
if (valid .and. enforce_module_names) then

fortranized_pkg = to_fortran_name(package_name%s)

!> Query string lengths
lpkg = len_trim(fortranized_pkg)
lmod = len_trim(module_name%s)
lsep = len_trim(SEP)

same_beginning = str_begins_with_str(module_name%s,fortranized_pkg)

is_same = lpkg==lmod .and. same_beginning

if (lmod>=lpkg+lsep) then
has_separator = str_begins_with_str(module_name%s(lpkg+1:lpkg+lsep),SEP)
else
has_separator = .false.
endif

!> 2) It must begin with the package name.
!> 3) It can be equal to the package name, or, if longer, must be followed by the
! default separator plus at least one character
!> 4) Package name must not end with an underscore
valid = is_fortran_name(fortranized_pkg) .and. &
fortranized_pkg(lpkg:lpkg)/='_' .and. &
(is_same .or. (lmod>lpkg+lsep .and. has_separator))

end if

end function is_valid_module_name

subroutine delete_skip(unix)
!> delete directories in the build folder, skipping dependencies
logical, intent(in) :: unix
Expand Down
13 changes: 13 additions & 0 deletions src/fpm/manifest/build.f90
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ module fpm_manifest_build
!> Automatic discovery of tests
logical :: auto_tests

!> Enforcing of package module names
logical :: module_naming = .false.

!> Libraries to link against
type(string_t), allocatable :: link(:)

Expand Down Expand Up @@ -86,6 +89,12 @@ subroutine new_build_config(self, table, error)
return
end if

call get_value(table, "module-naming", self%module_naming, .false., stat=stat)

if (stat /= toml_stat%success) then
call fatal_error(error,"Error while reading value for 'module-naming' in fpm.toml, expecting logical")
return
end if

call get_list(table, "link", self%link, error)
if (allocated(error)) return
Expand Down Expand Up @@ -119,6 +128,9 @@ subroutine check(table, error)
case("auto-executables", "auto-examples", "auto-tests", "link", "external-modules")
continue

case ("module-naming")
continue

case default
call syntax_error(error, "Key "//list(ikey)%key//" is not allowed in [build]")
exit
Expand Down Expand Up @@ -156,6 +168,7 @@ subroutine info(self, unit, verbosity)
write(unit, fmt) " - auto-discovery (apps) ", merge("enabled ", "disabled", self%auto_executables)
write(unit, fmt) " - auto-discovery (examples) ", merge("enabled ", "disabled", self%auto_examples)
write(unit, fmt) " - auto-discovery (tests) ", merge("enabled ", "disabled", self%auto_tests)
write(unit, fmt) " - enforce module naming ", merge("enabled ", "disabled", self%module_naming)
if (allocated(self%link)) then
write(unit, fmt) " - link against"
do ilink = 1, size(self%link)
Expand Down
7 changes: 7 additions & 0 deletions src/fpm_model.f90
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,9 @@ module fpm_model
!> Whether tests should be added to the build list
logical :: include_tests = .true.

!> Whether module names should be prefixed with the package name
logical :: enforce_module_names = .false.

end type fpm_model_t

contains
Expand Down Expand Up @@ -343,6 +346,10 @@ function info_model(model) result(s)
! TODO: print `dependency_tree_t` properly, which should become part of the
! model, not imported from another file
s = s // ", deps=dependency_tree_t(...)"

! Print module naming convention
s = s // ', enforce_module_names="' // merge('T','F',model%enforce_module_names) // '"'

!end type fpm_model_t
s = s // ")"
end function info_model
Expand Down
36 changes: 23 additions & 13 deletions test/fpm_test/test_manifest.f90
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ subroutine test_valid_manifest(error)
& '[build]', &
& 'auto-executables = false', &
& 'auto-tests = false', &
& 'module-naming = false', &
& '[dependencies.fpm]', &
& 'git = "https://github.com/fortran-lang/fpm"', &
& '[[executable]]', &
Expand Down Expand Up @@ -361,7 +362,7 @@ subroutine test_dependency_invalid_git(error)

type(toml_table) :: table
type(toml_table), pointer :: child
integer :: stat

type(dependency_config_t) :: dependency

call new_table(table)
Expand Down Expand Up @@ -523,7 +524,6 @@ subroutine test_profiles_keyvalue_table(error)
type(package_config_t) :: package
character(len=*), parameter :: manifest = 'fpm-profiles-error.toml'
integer :: unit
character(:), allocatable :: profile_name, compiler, flags

open(file=manifest, newunit=unit)
write(unit, '(a)') &
Expand Down Expand Up @@ -635,7 +635,8 @@ subroutine test_build_valid(error)
& 'name = "example"', &
& '[build]', &
& 'auto-executables = false', &
& 'auto-tests = false'
& 'auto-tests = false ', &
& 'module-naming = true '
close(unit)

call get_package_data(package, temp_file, error)
Expand All @@ -652,6 +653,11 @@ subroutine test_build_valid(error)
return
end if

if (.not.package%build%module_naming) then
call test_failed(error, "Wong value of 'module-naming' read, expecting .true.")
return
end if

end subroutine test_build_valid


Expand Down Expand Up @@ -688,6 +694,11 @@ subroutine test_build_empty(error)
return
end if

if (package%build%module_naming) then
call test_failed(error, "Wong default value of 'module-naming' read, expecting .false.")
return
end if

end subroutine test_build_empty


Expand Down Expand Up @@ -1225,7 +1236,7 @@ subroutine test_install_wrongkey(error)
call new_install_config(install, table, error)

end subroutine test_install_wrongkey

subroutine test_preprocess_empty(error)
use fpm_mainfest_preprocess
use fpm_toml, only : new_table, toml_table
Expand Down Expand Up @@ -1261,7 +1272,7 @@ subroutine test_preprocess_wrongkey(error)
call add_table(table, 'wrong-field', child, stat)

call new_preprocess_config(preprocess, table, error)

end subroutine test_preprocess_wrongkey

!> Preprocess table cannot be empty.
Expand Down Expand Up @@ -1289,7 +1300,6 @@ subroutine test_macro_parsing(error)
!> Error handling
type(error_t), allocatable, intent(out) :: error

character(len=:), allocatable :: flags
character(len=:), allocatable :: version

type(package_config_t) :: package
Expand All @@ -1304,9 +1314,9 @@ subroutine test_macro_parsing(error)
& 'name = "example"', &
& 'version = "0.1.0"', &
& '[preprocess]', &
& '[preprocess.cpp]', &
& '[preprocess.cpp]', &
& 'macros = ["FOO", "BAR=2", "VERSION={version}"]'
close(unit)
close(unit)

call get_package_data(package, temp_file, error)

Expand All @@ -1317,7 +1327,7 @@ subroutine test_macro_parsing(error)
if (get_macros(id, package%preprocess(1)%macros, version) /= " -DFOO -DBAR=2 -DVERSION=0.1.0") then
call test_failed(error, "Macros were not parsed correctly")
end if

end subroutine test_macro_parsing

!> Test macro parsing of the package and its dependency.
Expand Down Expand Up @@ -1346,10 +1356,10 @@ subroutine test_macro_parsing_dependency(error)
& 'name = "example"', &
& 'version = "0.1.0"', &
& '[dependencies]', &
& '[dependencies.dependency-name]', &
& '[dependencies.dependency-name]', &
& 'git = "https://github.com/fortran-lang/dependency-name"', &
& '[preprocess]', &
& '[preprocess.cpp]', &
& '[preprocess.cpp]', &
& 'macros = ["FOO", "BAR=2", "VERSION={version}"]'
close(unit)

Expand All @@ -1358,7 +1368,7 @@ subroutine test_macro_parsing_dependency(error)
& 'name = "dependency-name"', &
& 'version = "0.2.0"', &
& '[preprocess]', &
& '[preprocess.cpp]', &
& '[preprocess.cpp]', &
& 'macros = ["FOO1", "BAR2=2", "VERSION={version}"]'
close(unit)

Expand All @@ -1379,7 +1389,7 @@ subroutine test_macro_parsing_dependency(error)
if (macrosPackage == macrosDependency) then
call test_failed(error, "Macros of package and dependency should not be equal")
end if

end subroutine test_macro_parsing_dependency

end module test_manifest
Loading