TOP DIRECTORY NAME PROCESSING see if requested new directory already exists and process appropriately temporarily change to new directory as a test. NB: System dependent
Type | Intent | Optional | Attributes | Name | ||
---|---|---|---|---|---|---|
type(fpm_new_settings), | intent(in) | :: | settings |
subroutine cmd_new(settings)
type(fpm_new_settings), intent(in) :: settings
integer,parameter :: tfc = selected_char_kind('DEFAULT')
character(len=:,kind=tfc),allocatable :: bname ! baeename of NAME
character(len=:,kind=tfc),allocatable :: tomlfile(:)
character(len=:,kind=tfc),allocatable :: littlefile(:)
!> TOP DIRECTORY NAME PROCESSING
!> see if requested new directory already exists and process appropriately
if(exists(settings%name) .and. .not.settings%backfill )then
write(stderr,'(*(g0,1x))')&
& '<ERROR>',settings%name,'already exists.'
write(stderr,'(*(g0,1x))')&
& ' perhaps you wanted to add --backfill ?'
return
elseif(is_dir(settings%name) .and. settings%backfill )then
write(*,'(*(g0))')'backfilling ',settings%name
elseif(exists(settings%name) )then
write(stderr,'(*(g0,1x))')&
& '<ERROR>',settings%name,'already exists and is not a directory.'
return
else
! make new directory
call mkdir(settings%name)
endif
!> temporarily change to new directory as a test. NB: System dependent
call run('cd '//settings%name)
! NOTE: need some system routines to handle filenames like "."
! like realpath() or getcwd().
bname=basename(settings%name)
littlefile=[character(len=80) :: '# '//bname, 'My cool new project!']
! create NAME/README.md
call warnwrite(join_path(settings%name, 'README.md'), littlefile)
! start building NAME/fpm.toml
if(settings%with_full)then
tomlfile=[character(len=80) :: &
&' # This is your fpm(Fortran Package Manager) manifest file ',&
&' # ("fpm.toml"). It is heavily annotated to help guide you though ',&
&' # customizing a package build, although the defaults are sufficient ',&
&' # for many basic packages. ',&
&' # ',&
&' # The manifest file is not only used to provide metadata identifying ',&
&' # your project (so it can be used by others as a dependency). It can ',&
&' # specify where your library and program sources live, what the name ',&
&' # of the executable(s) will be, what files to build, dependencies on ',&
&' # other fpm packages, and what external libraries are required. ',&
&' # ',&
&' # The manifest format must conform to the TOML configuration file ',&
&' # standard. ',&
&' # ',&
&' # TOML files support flexible use of white-space and commenting of the ',&
&' # configuration data, but for clarity in this sample active directives ',&
&' # begin in column one. Inactive example directives are commented ',&
&' # out with a pound character ("#") but begin in column one as well. ',&
&' # Commentary begins with a pound character in column three. ',&
&' # ',&
&' # This file draws heavily upon the following references: ',&
&' # ',&
&' # The fpm home page at ',&
&' # https://github.com/fortran-lang/fpm ',&
&' # A complete list of keys and their attributes at ',&
&' # https://github.com/fortran-lang/fpm/blob/main/manifest-reference.md ',&
&' # examples of fpm project packaging at ',&
&' # https://github.com/fortran-lang/fpm/blob/main/PACKAGING.md ',&
&' # The Fortran TOML file interface and it''s references at ',&
&' # https://github.com/toml-f/toml-f ',&
&' # ',&
&' #----------------------- ',&
&' # project Identification ',&
&' #----------------------- ',&
&' # We begin with project metadata at the manifest root. This data is designed ',&
&' # to aid others when searching for the project in a repository and to ',&
&' # identify how and when to contact the package supporters. ',&
&' ',&
&'name = "'//bname//'"',&
&' # The project name (required) is how the project will be referred to. ',&
&' # The name is used by other packages using it as a dependency. It also ',&
&' # is used as the default name of any library built and the optional ',&
&' # default executable built from app/main.f90. It must conform to the rules ',&
&' # for a Fortran variable name. ',&
&' ',&
&'version = "0.1.0" ',&
&' # The project version number is a string. A recommended scheme for ',&
&' # specifying versions is the Semantic Versioning scheme. ',&
&' ',&
&'license = "license" ',&
&' # Licensing information specified using SPDX identifiers is preferred ',&
&' # (eg. "Apache-2.0 OR MIT" or "LGPL-3.0-or-later"). ',&
&' ',&
&'maintainer = "jane.doe@example.com" ',&
&' # Information on the project maintainer and means to reach out to them. ',&
&' ',&
&'author = "Jane Doe" ',&
&' # Information on the project author. ',&
&' ',&
&'copyright = "Copyright 2020 Jane Doe" ',&
&' # A statement clarifying the Copyright status of the project. ',&
&' ',&
&'#description = "A short project summary in plain text" ',&
&' # The description provides a short summary on the project. It should be ',&
&' # plain text and not use any markup formatting. ',&
&' ',&
&'#categories = ["fortran", "graphics"] ',&
&' # Categories associated with the project. Listing only one is preferred. ',&
&' ',&
&'#keywords = ["hdf5", "mpi"] ',&
&' # The keywords field is an array of strings describing the project. ',&
&' ',&
&'#homepage = "https://stdlib.fortran-lang.org" ',&
&' # URL to the webpage of the project. ',&
&' ',&
&' # ----------------------------------------- ',&
&' # We are done with identifying the project. ',&
&' # ----------------------------------------- ',&
&' # ',&
&' # Now lets start describing how the project should be built. ',&
&' # ',&
&' # Note tables would go here but we will not be talking about them (much)!!' ,&
&' # ',&
&' # Tables are a way to explicitly specify large numbers of programs in ',&
&' # a compact format instead of individual per-program entries in the ',&
&' # [[executable]], [[test]], and [[example]] sections to follow but ',&
&' # will not be discussed further except for the following notes: ',&
&' # ',&
&' # + Tables must appear (here) before any sections are declared. Once a ',&
&' # section is specified in a TOML file everything afterwards must be ',&
&' # values for that section or the beginning of a new section. A simple ',&
&' # example looks like: ',&
&' ',&
&'#executable = [ ',&
&'# { name = "a-prog" }, ',&
&'# { name = "app-tool", source-dir = "tool" }, ',&
&'# { name = "fpm-man", source-dir = "tool", main="fman.f90" } ',&
&'#] ',&
&' ',&
&' # This would be in lieue of the [[executable]] section found later in this ',&
&' # configuration file. ',&
&' # + See the reference documents (at the beginning of this document) ',&
&' # for more information on tables if you have long lists of programs ',&
&' # to build and are not simply depending on auto-detection. ',&
&' # ',&
&' # Now lets begin the TOML sections (lines beginning with "[") ... ',&
&' # ',&
&' ',&
&'[install] # Options for the "install" subcommand ',&
&' ',&
&' # When you run the "install" subcommand only executables are installed by ',&
&' # default on the local system. Library projects that will be used outside of ',&
&' # "fpm" can set the "library" boolean to also allow installing the module ',&
&' # files and library archive. Without this being set to "true" an "install" ',&
&' # subcommand ignores parameters that specify library installation. ',&
&' ',&
&'library = false ',&
&' ',&
&'[build] # General Build Options ',&
&' ',&
&' ### Automatic target discovery ',&
&' # ',&
&' # Normally fpm recursively searches the app/, example/, and test/ directories ',&
&' # for program sources and builds them. To disable this automatic discovery of ',&
&' # program targets set the following to "false": ',&
&' ',&
&'#auto-executables = true ',&
&'#auto-examples = true ',&
&'#auto-tests = true ',&
&' ',&
&' ### Package-level External Library Links ',&
&' # ',&
&' # To declare link-time dependencies on external libraries a list of ',&
&' # native libraries can be specified with the "link" entry. You may ',&
&' # have one library name or a list of strings in case several ',&
&' # libraries should be linked. This list of library dependencies is ',&
&' # exported to dependent packages. You may have to alter your library ',&
&' # search-path to ensure the libraries can be accessed. Typically, ',&
&' # this is done with the LD_LIBRARY_PATH environment variable on ULS ',&
&' # (Unix-Like Systems). You only specify the core name of the library ',&
&' # (as is typical with most programming environments, where you ',&
&' # would specify "-lz" on your load command to link against the zlib ',&
&' # compression library even though the library file would typically be ',&
&' # a file called "libz.a" "or libz.so"). So to link against that library ',&
&' # you would specify: ',&
&' ',&
&'#link = "z" ',&
&' ',&
&' # Note that in some cases the order of the libraries matters: ',&
&' ',&
&'#link = ["blas", "lapack"] ',&
&'']
endif
if(settings%with_bare)then
elseif(settings%with_lib)then
call mkdir(join_path(settings%name,'src') )
! create next section of fpm.toml
if(settings%with_full)then
tomlfile=[character(len=80) :: tomlfile, &
&'[library] ',&
&' ',&
&' # You can change the name of the directory to search for your library ',&
&' # source from the default of "src/". Library targets are exported ',&
&' # and usable by other projects. ',&
&' ',&
&'source-dir="src" ',&
&' ',&
&' # this can be a list: ',&
&' ',&
&'#source-dir=["src", "src2"] ',&
&' ',&
&' # More complex libraries may organize their modules in subdirectories. ',&
&' # For modules in a top-level directory fpm requires (but does not ',&
&' # enforce) that: ',&
&' # ',&
&' # + The module has the same name as the source file. This is important. ',&
&' # + There should be only one module per file. ',&
&' # ',&
&' # These two requirements simplify the build process for fpm. As Fortran ',&
&' # compilers emit module files (.mod) with the same name as the module ',&
&' # itself (but not the source file, .f90), naming the module the same ',&
&' # as the source file allows fpm to: ',&
&' # ',&
&' # + Uniquely and exactly map a source file (.f90) to its object (.o) ',&
&' # and module (.mod) files. ',&
&' # + Avoid conflicts with modules of the same name that could appear ',&
&' # in dependency packages. ',&
&' # ',&
&' ### Multi-level library source ',&
&' # You can place your module source files in any number of levels of ',&
&' # subdirectories inside your source directory, but there are certain naming ',&
&' # conventions to be followed -- module names must contain the path components ',&
&' # of the directory that its source file is in. ',&
&' # ',&
&' # This rule applies generally to any number of nested directories and ',&
&' # modules. For example, src/a/b/c/d.f90 must define a module called a_b_c_d. ',&
&' # Again, this is not enforced but may be required in future releases. ',&
&'']
endif
! create placeholder module src/bname.f90
littlefile=[character(len=80) :: &
&'module '//to_fortran_name(bname), &
&' implicit none', &
&' private', &
&'', &
&' public :: say_hello', &
&'contains', &
&' subroutine say_hello', &
&' print *, "Hello, '//bname//'!"', &
&' end subroutine say_hello', &
&'end module '//to_fortran_name(bname)]
! create NAME/src/NAME.f90
call warnwrite(join_path(settings%name, 'src', bname//'.f90'),&
& littlefile)
endif
if(settings%with_full)then
tomlfile=[character(len=80) :: tomlfile ,&
&'[dependencies] ',&
&' ',&
&' # Inevitably, you will want to be able to include other packages in ',&
&' # a project. Fpm makes this incredibly simple, by taking care of ',&
&' # fetching and compiling your dependencies for you. You just tell it ',&
&' # what your dependencies names are, and where to find them. ',&
&' # ',&
&' # If you are going to distribute your package only place dependencies ',&
&' # here someone using your package as a remote dependency needs built. ',&
&' # You can define dependencies just for developer executables in the ',&
&' # next section, or even for specific executables as we will see below ',&
&' # (Then fpm will still fetch and compile it when building your ',&
&' # developer executables, but users of your library will not have to). ',&
&' # ',&
&' ## GLOBAL DEPENDENCIES (exported with your project) ',&
&' # ',&
&' # Typically, dependencies are defined by specifying the project''s ',&
&' # git repository. ',&
&' # ',&
&' # You can be specific about which version of a dependency you would ',&
&' # like. By default the latest default branch is used. You can ',&
&' # optionally specify a branch, a tag or a commit value. ',&
&' # ',&
&' # So here are several alternates for specifying a remote dependency (you ',&
&' # can have at most one of "branch", "rev" or "tag" present): ',&
&' ',&
&'#stdlib = { git = "https://github.com/LKedward/stdlib-fpm.git" } ',&
&'#stdlib = {git="https://github.com/LKedward/stdlib-fpm.git",branch = "master" },',&
&'#stdlib = {git="https://github.com/LKedward/stdlib-fpm.git", tag = "v0.1.0" }, ',&
&'#stdlib = {git="https://github.com/LKedward/stdlib-fpm.git", rev = "5a9b7a8" }. ',&
&' ',&
&' # There may be multiple packages listed: ',&
&' ',&
&'#M_strings = { git = "https://github.com/urbanjost/M_strings.git" } ',&
&'#M_time = { git = "https://github.com/urbanjost/M_time.git" } ',&
&' ',&
&' # ',&
&' # You can even specify the local path to another project if it is in ',&
&' # a sub-folder (If for example you have got another fpm package **in ',&
&' # the same repository**) like this: ',&
&' ',&
&'#M_strings = { path = "M_strings" } ',&
&' ',&
&' # This tells fpm that we depend on a crate called M_strings which is found ',&
&' # in the M_strings folder (relative to the fpm.toml it’s written in). ',&
&' # ',&
&' # For a more verbose layout use normal tables rather than inline tables ',&
&' # to specify dependencies: ',&
&' ',&
&'#[dependencies.toml-f] ',&
&'#git = "https://github.com/toml-f/toml-f" ',&
&'#rev = "2f5eaba864ff630ba0c3791126a3f811b6e437f3" ',&
&' ',&
&' # Now you can use any modules from these libraries anywhere in your ',&
&' # code -- whether is in your library source or a program source. ',&
&' ',&
&'[dev-dependencies] ',&
&' ',&
&' ## Dependencies Only for Development ',&
&' # ',&
&' # You can specify dependencies your library or application does not ',&
&' # depend on in a similar way. The difference is that these will not ',&
&' # be exported as part of your project to those using it as a remote ',&
&' # dependency. ',&
&' # ',&
&' # Currently, like a global dependency it will still be available for ',&
&' # all codes. It is up to the developer to ensure that nothing except ',&
&' # developer test programs rely upon it. ',&
&' ',&
&'#M_msg = { git = "https://github.com/urbanjost/M_msg.git" } ',&
&'#M_verify = { git = "https://github.com/urbanjost/M_verify.git" } ',&
&'']
endif
if(settings%with_bare)then
elseif(settings%with_executable)then
! create next section of fpm.toml
call mkdir(join_path(settings%name, 'app'))
! create NAME/app or stop
if(settings%with_full)then
tomlfile=[character(len=80) :: tomlfile, &
&' #----------------------------------- ',&
&' ## Application-specific declarations ',&
&' #----------------------------------- ',&
&' # Now lets begin entries for the TOML tables (lines beginning with "[[") ',&
&' # that describe the program sources -- applications, tests, and examples. ',&
&' # ',&
&' # First we will configuration individual applications run with "fpm run". ',&
&' # ',&
&' # + the "name" entry for the executable to be built must always ',&
&' # be specified. The name must satisfy the rules for a Fortran ',&
&' # variable name. This will be the name of the binary installed by ',&
&' # the "install" subcommand and used on the "run" subcommand. ',&
&' # + The source directory for each executable can be adjusted by the ',&
&' # "source-dir" entry. ',&
&' # + The basename of the source file containing the program body can ',&
&' # be specified with the "main" entry. ',&
&' # + Executables can also specify their own external package and ',&
&' # library link dependencies. ',&
&' # ',&
&' # Currently, like a global dependency any external package dependency ',&
&' # will be available for all codes. It is up to the developer to ensure ',&
&' # that nothing except the application programs specified rely upon it. ',&
&' # ',&
&' # Note if your application needs to use a module internally, but you do not ',&
&' # intend to build it as a library to be used in other projects, you can ',&
&' # include the module in your program source file or directory as well. ',&
&' ',&
&'[[executable]] ',&
&'name="'//bname//'"',&
&'source-dir="app" ',&
&'main="main.f90" ',&
&' ',&
&' # You may repeat this pattern to define additional applications. For instance,',&
&' # the following sample illustrates all accepted options, where "link" and ',&
&' # "executable.dependencies" keys are the same as the global external library ',&
&' # links and package dependencies described previously except they apply ',&
&' # only to this executable: ',&
&' ',&
&'#[[ executable ]] ',&
&'#name = "app-name" ',&
&'#source-dir = "prog" ',&
&'#main = "program.f90" ',&
&'#link = "z" ',&
&'#[executable.dependencies] ',&
&'#M_CLI = { git = "https://github.com/urbanjost/M_CLI.git" } ',&
&'#helloff = { git = "https://gitlab.com/everythingfunctional/helloff.git" } ',&
&'#M_path = { git = "https://github.com/urbanjost/M_path.git" } ',&
&'']
endif
if(exists(bname//'/src/'))then
littlefile=[character(len=80) :: &
&'program main', &
&' use '//to_fortran_name(bname)//', only: say_hello', &
&' implicit none', &
&'', &
&' call say_hello()', &
&'end program main']
else
littlefile=[character(len=80) :: &
&'program main', &
&' implicit none', &
&'', &
&' print *, "hello from project '//bname//'"', &
&'end program main']
endif
call warnwrite(join_path(settings%name, 'app/main.f90'), littlefile)
endif
if(settings%with_bare)then
elseif(settings%with_test)then
! create NAME/test or stop
call mkdir(join_path(settings%name, 'test'))
! create next section of fpm.toml
if(settings%with_full)then
tomlfile=[character(len=80) :: tomlfile ,&
&'[[test]] ',&
&' ',&
&' # The same declarations can be made for test programs, which are ',&
&' # executed with the "fpm test" command and are not build when your ',&
&' # package is used as a dependency by other packages. These are ',&
&' # typically unit tests of the package only used during package ',&
&' # development. ',&
&' ',&
&'name="runTests" ',&
&'source-dir="test" ',&
&'main="check.f90" ',&
&' ',&
&' # you may repeat this pattern to add additional explicit test program ',&
&' # parameters. The following example contains a sample of all accepted ',&
&' # options. ',&
&' ',&
&'#[[ test ]] ',&
&'#name = "tester" ',&
&'#source-dir="test" ',&
&'#main="tester.f90" ',&
&'#link = ["blas", "lapack"] ',&
&'#[test.dependencies] ',&
&'#M_CLI2 = { git = "https://github.com/urbanjost/M_CLI2.git" } ',&
&'#M_io = { git = "https://github.com/urbanjost/M_io.git" } ',&
&'#M_system= { git = "https://github.com/urbanjost/M_system.git" } ',&
&'']
endif
littlefile=[character(len=80) :: &
&'program check', &
&'implicit none', &
&'', &
&'print *, "Put some tests in here!"', &
&'end program check']
! create NAME/test/check.f90
call warnwrite(join_path(settings%name, 'test/check.f90'), littlefile)
endif
if(settings%with_bare)then
elseif(settings%with_example)then
! create NAME/example or stop
call mkdir(join_path(settings%name, 'example'))
! create next section of fpm.toml
if(settings%with_full)then
tomlfile=[character(len=80) :: tomlfile, &
&'[[example]] ',&
&' ',&
&' # Example applications for a project are defined here. ',&
&' # These are run via "fpm run --example NAME" and like the ',&
&' # test applications, are not built when this package is used as a ',&
&' # dependency by other packages. ',&
&' ',&
&'name="demo" ',&
&'source-dir="example" ',&
&'main="demo.f90" ',&
&' ',&
&' # ',&
&' # you may add additional programs to the example table. The following ',&
&' # example contains a sample of all accepted options ',&
&' ',&
&'#[[ example ]] ',&
&'#name = "example-tool" ',&
&'#source-dir="example" ',&
&'#main="tool.f90" ',&
&'#link = "z" ',&
&'#[example.dependencies] ',&
&'#M_kracken95 = { git = "https://github.com/urbanjost/M_kracken95.git" } ',&
&'#datetime = {git = "https://github.com/wavebitscientific/datetime-fortran.git" }',&
&'']
endif
littlefile=[character(len=80) :: &
&'program demo', &
&'implicit none', &
&'', &
&'print *, "Put some examples in here!"', &
&'end program demo']
! create NAME/example/demo.f90
call warnwrite(join_path(settings%name, 'example/demo.f90'), littlefile)
endif
! now that built it write NAME/fpm.toml
if( allocated(tomlfile) )then
call validate_toml_data(tomlfile)
call warnwrite(join_path(settings%name, 'fpm.toml'), tomlfile)
else
call create_verified_basic_manifest(join_path(settings%name, 'fpm.toml'))
endif
! assumes git(1) is installed and in path
if(which('git')/='')then
call run('git init ' // settings%name)
endif
contains
function git_metadata(what) result(returned)
!> get metadata values such as email address and git name from git(1) or return appropriate default
use fpm_filesystem, only : get_temp_filename, getline
character(len=*), intent(in) :: what ! keyword designating what git metatdata to query
character(len=:), allocatable :: returned ! value to return for requested keyword
character(len=:), allocatable :: command
character(len=:), allocatable :: temp_filename
character(len=:), allocatable :: iomsg
character(len=:), allocatable :: temp_value
integer :: stat, unit
temp_filename = get_temp_filename()
! for known keywords set default value for RETURNED and associated git(1) command for query
select case(what)
case('uname')
returned = "Jane Doe"
command = "git config --get user.name > " // temp_filename
case('email')
returned = "jane.doe@example.com"
command = "git config --get user.email > " // temp_filename
case default
write(stderr,'(*(g0,1x))')&
& '<ERROR> *git_metadata* unknown metadata name ',trim(what)
returned=''
return
end select
! Execute command if git(1) is in command path
if(which('git')/='')then
call run(command, exitstat=stat)
if (stat /= 0) then ! If command failed just return default
return
else ! Command did not return an error so try to read expected output file
open(file=temp_filename, newunit=unit,iostat=stat)
if(stat == 0)then
! Read file into a scratch variable until status of doing so is checked
call getline(unit, temp_value, stat, iomsg)
if (stat == 0 .and. temp_value /= '') then
! Return output from successful command
returned=temp_value
endif
endif
! Always do the CLOSE because a failed open has unpredictable results.
! Add IOSTAT so a failed close does not cause program to stop
close(unit, status="delete",iostat=stat)
endif
endif
end function git_metadata
subroutine create_verified_basic_manifest(filename)
!> create a basic but verified default manifest file
use fpm_toml, only : toml_table, toml_serialize, set_value
use fpm_manifest_package, only : package_config_t, new_package
use fpm_error, only : error_t
implicit none
character(len=*),intent(in) :: filename
type(toml_table) :: table
type(package_config_t) :: package
type(error_t), allocatable :: error
integer :: lun
character(len=8) :: date
character(:), allocatable :: output
if(exists(filename))then
write(stderr,'(*(g0,1x))')'<INFO> ',filename,&
& 'already exists. Not overwriting'
return
endif
!> get date to put into metadata in manifest file "fpm.toml"
call date_and_time(DATE=date)
table = toml_table()
call fileopen(filename,lun) ! fileopen stops on error
call set_value(table, "name", BNAME)
call set_value(table, "version", "0.1.0")
call set_value(table, "license", "license")
call set_value(table, "author", git_metadata('uname'))
call set_value(table, "maintainer", git_metadata('email'))
call set_value(table, "copyright", 'Copyright '//date(1:4)//', '//git_metadata('uname'))
! continue building of manifest
! ...
call new_package(package, table, error=error)
if (allocated(error)) call fpm_stop( 3,'')
output = toml_serialize(table)
if(settings%verbose)then
print '(a)', output
endif
write(lun, '(a)') output
call fileclose(lun) ! fileopen stops on error
end subroutine create_verified_basic_manifest
subroutine validate_toml_data(input)
!> verify a string array is a valid fpm.toml file
!
use tomlf, only : toml_load
use fpm_toml, only : toml_table, toml_serialize
implicit none
character(kind=tfc,len=:),intent(in),allocatable :: input(:)
character(len=1), parameter :: nl = new_line('a')
type(toml_table), allocatable :: table
character(kind=tfc, len=:), allocatable :: joined_string
! you have to add a newline character by using the intrinsic
! function `new_line("a")` to get the lines processed correctly.
joined_string = join(input,right=nl)
if (allocated(table)) deallocate(table)
call toml_load(table, joined_string)
if (allocated(table)) then
if(settings%verbose)then
! If the TOML file is successfully parsed the table will be allocated and
! can be written by `toml_serialize` to the standard output
print '(a)', toml_serialize(table)
endif
call table%destroy
endif
end subroutine validate_toml_data
end subroutine cmd_new