diff options
-rw-r--r-- | PACKAGING.md | 18 | ||||
-rw-r--r-- | README.md | 3 | ||||
-rw-r--r-- | bootstrap/src/Build.hs | 42 | ||||
-rw-r--r-- | bootstrap/src/Fpm.hs | 152 | ||||
-rw-r--r-- | doc/Contributing.md | 5 | ||||
-rw-r--r-- | doc/License.md | 5 | ||||
-rw-r--r-- | doc/Manifest.md | 6 | ||||
-rw-r--r-- | doc/Packaging.md | 5 | ||||
-rw-r--r-- | doc/index.md | 3 | ||||
-rw-r--r-- | doc/media/favicon.ico | bin | 0 -> 16958 bytes | |||
-rw-r--r-- | docs.md | 32 | ||||
-rw-r--r-- | fpm/src/fpm.f90 | 37 | ||||
-rw-r--r-- | fpm/src/fpm/cmd/new.f90 | 4 | ||||
-rw-r--r-- | fpm/src/fpm/manifest.f90 | 14 | ||||
-rw-r--r-- | fpm/src/fpm/manifest/build_config.f90 | 16 | ||||
-rw-r--r-- | fpm/src/fpm/manifest/dependency.f90 | 46 | ||||
-rw-r--r-- | fpm/src/fpm/manifest/executable.f90 | 20 | ||||
-rw-r--r-- | fpm/src/fpm/manifest/library.f90 | 16 | ||||
-rw-r--r-- | fpm/src/fpm/manifest/package.f90 | 56 | ||||
-rw-r--r-- | fpm/src/fpm/manifest/test.f90 | 28 | ||||
-rw-r--r-- | fpm/src/fpm/toml.f90 | 24 | ||||
-rw-r--r-- | fpm/src/fpm_backend.f90 | 154 | ||||
-rw-r--r-- | fpm/src/fpm_model.f90 | 38 | ||||
-rw-r--r-- | fpm/src/fpm_sources.f90 | 104 | ||||
-rw-r--r-- | fpm/src/fpm_targets.f90 | 248 | ||||
-rw-r--r-- | fpm/test/fpm_test/test_module_dependencies.f90 | 331 | ||||
-rwxr-xr-x | install.sh | 8 | ||||
-rw-r--r-- | manifest-reference.md | 361 |
28 files changed, 1258 insertions, 518 deletions
diff --git a/PACKAGING.md b/PACKAGING.md index f5f28e3..44a0c02 100644 --- a/PACKAGING.md +++ b/PACKAGING.md @@ -456,7 +456,7 @@ copyright = "2020 Jane Programmer" [library] source-dir="src" -[[executable]] +[[ executable ]] name="math_constants" source-dir="app" main="main.f90" @@ -487,12 +487,12 @@ copyright = "2020 Jane Programmer" [library] source-dir="src" -[[executable]] +[[ executable ]] name="math_constants" source-dir="app" main="main.f90" -[[test]] +[[ test ]] name="runTests" source-dir="test" main="main.f90" @@ -546,12 +546,12 @@ source-dir="src" [dependencies] helloff = { git = "https://gitlab.com/everythingfunctional/helloff.git" } -[[executable]] +[[ executable ]] name="math_constants" source-dir="app" main="main.f90" -[[test]] +[[ test ]] name="runTests" source-dir="test" main="main.f90" @@ -601,14 +601,14 @@ copyright = "2020 Jane Programmer" [library] source-dir="src" -[[executable]] +[[ executable ]] name="math_constants" source-dir="app" main="main.f90" [executable.dependencies] helloff = { git = "https://gitlab.com/everythingfunctional/helloff.git" } -[[test]] +[[ test ]] name="runTests" source-dir="test" main="main.f90" @@ -633,12 +633,12 @@ source-dir="src" [dev-dependencies] helloff = { git = "https://gitlab.com/everythingfunctional/helloff.git" } -[[executable]] +[[ executable ]] name="math_constants" source-dir="app" main="main.f90" -[[test]] +[[ test ]] name="runTests" source-dir="test" main="main.f90" @@ -81,4 +81,5 @@ to run, as can `fpm test`; like `fpm run specific_executable`. Command line arguments can also be passed to the executable(s) or test(s) with the option `--args "some arguments"`. -See additional instructions in the [Packaging guide](PACKAGING.md). +See additional instructions in the [Packaging guide](PACKAGING.md) or +the [manifest reference](manifest-reference.md). diff --git a/bootstrap/src/Build.hs b/bootstrap/src/Build.hs index 083e646..e4f9992 100644 --- a/bootstrap/src/Build.hs +++ b/bootstrap/src/Build.hs @@ -1,6 +1,7 @@ {-# LANGUAGE MultiWayIf #-} module Build - ( buildLibrary + ( CompilerSettings(..) + , buildLibrary , buildProgram , buildWithScript ) @@ -50,22 +51,28 @@ import System.Directory ( createDirectoryIfMissing , withCurrentDirectory ) +data CompilerSettings = CompilerSettings { + compilerSettingsCompiler :: FilePath + , compilerSettingsFlags :: [String] + , compilerSettingsModuleFlag :: String + , compilerSettingsIncludeFlag :: String +} + buildProgram :: FilePath -> [FilePath] -> [FilePattern] -> FilePath - -> FilePath - -> [String] + -> CompilerSettings -> String -> FilePath -> [FilePath] -> IO () -buildProgram programDirectory' libraryDirectories sourceExtensions buildDirectory' compiler flags programName programSource archives +buildProgram programDirectory' libraryDirectories sourceExtensions buildDirectory' (CompilerSettings { compilerSettingsCompiler = compiler, compilerSettingsFlags = flags, compilerSettingsModuleFlag = moduleFlag, compilerSettingsIncludeFlag = includeFlag }) programName programSource archives = do let programDirectory = foldl1 (</>) (splitDirectories programDirectory') - let buildDirectory = foldl1 (</>) (splitDirectories buildDirectory') - let includeFlags = map ("-I" ++) libraryDirectories + let buildDirectory = foldl1 (</>) (splitDirectories buildDirectory') + let includeFlags = map (includeFlag ++) libraryDirectories sourceFiles <- getDirectoriesFiles [programDirectory] sourceExtensions rawSources <- mapM sourceFileToRawSource sourceFiles let sources' = map processRawSource rawSources @@ -98,13 +105,14 @@ buildProgram programDirectory' libraryDirectories sourceExtensions buildDirector in fileMatcher &?> \(objectFile : _) -> do need (sourceFile : directDependencies) cmd compiler - ["-c", "-J" ++ buildDirectory] + ["-c", moduleFlag ++ buildDirectory] includeFlags flags ["-o", objectFile, sourceFile] want [buildDirectory </> programName <.> exe] buildDirectory </> programName <.> exe %> \executable -> do need objectFiles + need archives cmd compiler objectFiles archives ["-o", executable] flags mapM_ infoToRule compileTimeInfo @@ -112,14 +120,13 @@ buildLibrary :: FilePath -> [FilePattern] -> FilePath - -> FilePath - -> [String] + -> CompilerSettings -> String -> [FilePath] -> IO (FilePath) -buildLibrary libraryDirectory sourceExtensions buildDirectory compiler flags libraryName otherLibraryDirectories +buildLibrary libraryDirectory sourceExtensions buildDirectory (CompilerSettings { compilerSettingsCompiler = compiler, compilerSettingsFlags = flags, compilerSettingsModuleFlag = moduleFlag, compilerSettingsIncludeFlag = includeFlag }) libraryName otherLibraryDirectories = do - let includeFlags = map ("-I" ++) otherLibraryDirectories + let includeFlags = map (includeFlag ++) otherLibraryDirectories sourceFiles <- getDirectoriesFiles [libraryDirectory] sourceExtensions rawSources <- mapM sourceFileToRawSource sourceFiles let sources = map processRawSource rawSources @@ -149,7 +156,7 @@ buildLibrary libraryDirectory sourceExtensions buildDirectory compiler flags lib in fileMatcher &?> \(objectFile : _) -> do need (sourceFile : directDependencies) cmd compiler - ["-c", "-J" ++ buildDirectory] + ["-c", moduleFlag ++ buildDirectory] includeFlags flags ["-o", objectFile, sourceFile] @@ -164,18 +171,19 @@ buildWithScript :: String -> FilePath -> FilePath - -> FilePath - -> [String] + -> CompilerSettings -> String -> [FilePath] -> IO (FilePath) -buildWithScript script projectDirectory buildDirectory compiler flags libraryName otherLibraryDirectories +buildWithScript script projectDirectory buildDirectory (CompilerSettings { compilerSettingsCompiler = compiler, compilerSettingsFlags = flags, compilerSettingsModuleFlag = moduleFlag, compilerSettingsIncludeFlag = includeFlag }) libraryName otherLibraryDirectories = do absoluteBuildDirectory <- makeAbsolute buildDirectory createDirectoryIfMissing True absoluteBuildDirectory absoluteLibraryDirectories <- mapM makeAbsolute otherLibraryDirectories - setEnv "FC" compiler - setEnv "FFLAGS" (intercalate " " flags) + setEnv "FC" compiler + setEnv "FFLAGS" (intercalate " " flags) + setEnv "FINCLUDEFLAG" includeFlag + setEnv "FMODUELFLAG" moduleFlag setEnv "BUILD_DIR" $ unWindowsPath absoluteBuildDirectory setEnv "INCLUDE_DIRS" (intercalate " " (map unWindowsPath absoluteLibraryDirectories)) diff --git a/bootstrap/src/Fpm.hs b/bootstrap/src/Fpm.hs index 567a098..943393e 100644 --- a/bootstrap/src/Fpm.hs +++ b/bootstrap/src/Fpm.hs @@ -8,7 +8,8 @@ module Fpm ) where -import Build ( buildLibrary +import Build ( CompilerSettings(..) + , buildLibrary , buildProgram , buildWithScript ) @@ -18,6 +19,7 @@ import Control.Monad.Extra ( concatMapM ) import Data.Hashable ( hash ) import Data.List ( intercalate + , isInfixOf , isSuffixOf , find , nub @@ -33,6 +35,7 @@ import Development.Shake.FilePath ( (</>) , exe , splitDirectories ) +import Numeric ( showHex ) import Options.Applicative ( Parser , (<**>) , (<|>) @@ -114,10 +117,9 @@ data TomlSettings = TomlSettings { } data AppSettings = AppSettings { - appSettingsCompiler :: String + appSettingsCompiler :: CompilerSettings , appSettingsProjectName :: String , appSettingsBuildPrefix :: String - , appSettingsFlags :: [String] , appSettingsLibrary :: (Maybe Library) , appSettingsExecutables :: [Executable] , appSettingsTests :: [Executable] @@ -259,16 +261,14 @@ app args settings = case args of build :: AppSettings -> IO () build settings = do - let compiler = appSettingsCompiler settings - let projectName = appSettingsProjectName settings - let buildPrefix = appSettingsBuildPrefix settings - let flags = appSettingsFlags settings - let executables = appSettingsExecutables settings - let tests = appSettingsTests settings + let compilerSettings = appSettingsCompiler settings + let projectName = appSettingsProjectName settings + let buildPrefix = appSettingsBuildPrefix settings + let executables = appSettingsExecutables settings + let tests = appSettingsTests settings mainDependencyTrees <- fetchDependencies (appSettingsDependencies settings) builtDependencies <- buildDependencies buildPrefix - compiler - flags + compilerSettings mainDependencyTrees (executableDepends, maybeTree) <- case appSettingsLibrary settings of Just librarySettings -> do @@ -284,15 +284,13 @@ build settings = do Just script -> buildWithScript script "." (buildPrefix </> projectName) - compiler - flags + compilerSettings projectName (map fst builtDependencies) Nothing -> buildLibrary librarySourceDir' [".f90", ".f", ".F", ".F90", ".f95", ".f03"] (buildPrefix </> projectName) - compiler - flags + compilerSettings projectName (map fst builtDependencies) return @@ -306,14 +304,13 @@ build settings = do do localDependencies <- fetchExecutableDependencies maybeTree dependencies - >>= buildDependencies buildPrefix compiler flags + >>= buildDependencies buildPrefix compilerSettings buildProgram sourceDir ((map fst executableDepends) ++ (map fst localDependencies)) [".f90", ".f", ".F", ".F90", ".f95", ".f03"] (buildPrefix </> sourceDir) - compiler - flags + compilerSettings name mainFile ((map snd executableDepends) ++ (map snd localDependencies)) @@ -321,13 +318,13 @@ build settings = do executables devDependencies <- fetchExecutableDependencies maybeTree (appSettingsDevDependencies settings) - >>= buildDependencies buildPrefix compiler flags + >>= buildDependencies buildPrefix compilerSettings mapM_ (\Executable { executableSourceDir = sourceDir, executableMainFile = mainFile, executableName = name, executableDependencies = dependencies } -> do localDependencies <- fetchExecutableDependencies maybeTree dependencies - >>= buildDependencies buildPrefix compiler flags + >>= buildDependencies buildPrefix compilerSettings buildProgram sourceDir ( (map fst executableDepends) @@ -336,8 +333,7 @@ build settings = do ) [".f90", ".f", ".F", ".F90", ".f95", ".f03"] (buildPrefix </> sourceDir) - compiler - flags + compilerSettings name mainFile ( (map snd executableDepends) @@ -605,13 +601,33 @@ toml2AppSettings tomlSettings args = do Build { buildFlags = f } -> f Run { runFlags = f } -> f Test { testFlags = f } -> f + when (release && (length specifiedFlags > 0)) $ do + putStrLn "--release and --flag are mutually exclusive" + exitWith (ExitFailure 1) librarySettings <- getLibrarySettings $ tomlSettingsLibrary tomlSettings executableSettings <- getExecutableSettings (tomlSettingsExecutables tomlSettings) projectName testSettings <- getTestSettings $ tomlSettingsTests tomlSettings - let flags = if compiler == "gfortran" - then case specifiedFlags of + compilerSettings <- defineCompilerSettings specifiedFlags compiler release + buildPrefix <- makeBuildPrefix (compilerSettingsCompiler compilerSettings) + (compilerSettingsFlags compilerSettings) + let dependencies = tomlSettingsDependencies tomlSettings + let devDependencies = tomlSettingsDevDependencies tomlSettings + return AppSettings { appSettingsCompiler = compilerSettings + , appSettingsProjectName = projectName + , appSettingsBuildPrefix = buildPrefix + , appSettingsLibrary = librarySettings + , appSettingsExecutables = executableSettings + , appSettingsTests = testSettings + , appSettingsDependencies = dependencies + , appSettingsDevDependencies = devDependencies + } + +defineCompilerSettings :: [String] -> FilePath -> Bool -> IO CompilerSettings +defineCompilerSettings specifiedFlags compiler release + | "gfortran" `isInfixOf` compiler + = let flags = case specifiedFlags of [] -> if release then [ "-Wall" @@ -635,21 +651,47 @@ toml2AppSettings tomlSettings args = do , "-fcheck-array-temporaries" , "-fbacktrace" ] - flags -> flags - else specifiedFlags - buildPrefix <- makeBuildPrefix compiler flags - let dependencies = tomlSettingsDependencies tomlSettings - let devDependencies = tomlSettingsDevDependencies tomlSettings - return AppSettings { appSettingsCompiler = compiler - , appSettingsProjectName = projectName - , appSettingsBuildPrefix = buildPrefix - , appSettingsFlags = flags - , appSettingsLibrary = librarySettings - , appSettingsExecutables = executableSettings - , appSettingsTests = testSettings - , appSettingsDependencies = dependencies - , appSettingsDevDependencies = devDependencies - } + fs -> fs + in return $ CompilerSettings { compilerSettingsCompiler = compiler + , compilerSettingsFlags = flags + , compilerSettingsModuleFlag = "-J" + , compilerSettingsIncludeFlag = "-I" + } + | "caf" `isInfixOf` compiler + = let flags = case specifiedFlags of + [] -> if release + then + [ "-Wall" + , "-Wextra" + , "-Wimplicit-interface" + , "-fPIC" + , "-fmax-errors=1" + , "-O3" + , "-march=native" + , "-ffast-math" + , "-funroll-loops" + ] + else + [ "-Wall" + , "-Wextra" + , "-Wimplicit-interface" + , "-fPIC" + , "-fmax-errors=1" + , "-g" + , "-fbounds-check" + , "-fcheck-array-temporaries" + , "-fbacktrace" + ] + fs -> fs + in return $ CompilerSettings { compilerSettingsCompiler = compiler + , compilerSettingsFlags = flags + , compilerSettingsModuleFlag = "-J" + , compilerSettingsIncludeFlag = "-I" + } + | otherwise + = do + putStrLn $ "Sorry, compiler is currently unsupported: " ++ compiler + exitWith (ExitFailure 1) getLibrarySettings :: Maybe Library -> IO (Maybe Library) getLibrarySettings maybeSettings = case maybeSettings of @@ -705,15 +747,15 @@ makeBuildPrefix compiler flags = do -- Probably version, and make sure to not include path to the compiler versionInfo <- readProcess compiler ["--version"] [] let compilerName = last (splitDirectories compiler) - let versionHash = hash versionInfo - let flagsHash = hash flags + let versionHash = abs (hash versionInfo) + let flagsHash = abs (hash flags) return $ "build" </> compilerName ++ "_" - ++ show versionHash + ++ showHex versionHash "" ++ "_" - ++ show flagsHash + ++ showHex flagsHash "" {- Fetching the dependencies is done on a sort of breadth first approach. All @@ -825,37 +867,31 @@ fetchDependency name version = do the transitive dependencies have been built before trying to build this one -} buildDependencies - :: String - -> String - -> [String] - -> [DependencyTree] - -> IO [(FilePath, FilePath)] -buildDependencies buildPrefix compiler flags dependencies = do - built <- concatMapM (buildDependency buildPrefix compiler flags) dependencies + :: String -> CompilerSettings -> [DependencyTree] -> IO [(FilePath, FilePath)] +buildDependencies buildPrefix compilerSettings dependencies = do + built <- concatMapM (buildDependency buildPrefix compilerSettings) + dependencies return $ reverse (nub (reverse built)) buildDependency - :: String -> String -> [String] -> DependencyTree -> IO [(FilePath, FilePath)] -buildDependency buildPrefix compiler flags (Dependency name path sourcePath mBuildScript dependencies) + :: String -> CompilerSettings -> DependencyTree -> IO [(FilePath, FilePath)] +buildDependency buildPrefix compilerSettings (Dependency name path sourcePath mBuildScript dependencies) = do transitiveDependencies <- buildDependencies buildPrefix - compiler - flags + compilerSettings dependencies let buildPath = buildPrefix </> name thisArchive <- case mBuildScript of Just script -> buildWithScript script path buildPath - compiler - flags + compilerSettings name (map fst transitiveDependencies) Nothing -> buildLibrary sourcePath [".f90", ".f", ".F", ".F90", ".f95", ".f03"] buildPath - compiler - flags + compilerSettings name (map fst transitiveDependencies) return $ (buildPath, thisArchive) : transitiveDependencies diff --git a/doc/Contributing.md b/doc/Contributing.md new file mode 100644 index 0000000..5a7fccc --- /dev/null +++ b/doc/Contributing.md @@ -0,0 +1,5 @@ +--- +title: Contributing Guidelines +--- + +{!CONTRIBUTING.md!} diff --git a/doc/License.md b/doc/License.md new file mode 100644 index 0000000..6a51b7d --- /dev/null +++ b/doc/License.md @@ -0,0 +1,5 @@ +--- +title: License +--- + +{!LICENSE!} diff --git a/doc/Manifest.md b/doc/Manifest.md new file mode 100644 index 0000000..1ad48ce --- /dev/null +++ b/doc/Manifest.md @@ -0,0 +1,6 @@ +--- +title: Manifest reference +--- + +{!manifest-reference.md!} + diff --git a/doc/Packaging.md b/doc/Packaging.md new file mode 100644 index 0000000..46a4c1b --- /dev/null +++ b/doc/Packaging.md @@ -0,0 +1,5 @@ +--- +title: Packaging with fpm +--- + +{!PACKAGING.md!} diff --git a/doc/index.md b/doc/index.md new file mode 100644 index 0000000..2db3638 --- /dev/null +++ b/doc/index.md @@ -0,0 +1,3 @@ +--- +title: Packaging and contributing +--- diff --git a/doc/media/favicon.ico b/doc/media/favicon.ico Binary files differnew file mode 100644 index 0000000..a360390 --- /dev/null +++ b/doc/media/favicon.ico @@ -0,0 +1,32 @@ +--- +project: Fortran-lang/fpm +summary: Fortran Package Manager +project_github: https://github.com/fortran-lang/fpm +project_download: https://github.com/fortran-lang/fpm/archive/master.zip +author: fortran-lang/fpm contributors +author_pic: https://fortran-lang.org/assets/img/fortran_logo_512x512.png +author_email: fortran-lang@groups.io +github: https://github.com/fortran-lang +twitter: https://twitter.com/fortranlang +website: https://fortran-lang.org +src_dir: ./fpm/src +output_dir: ./fpm-doc +page_dir: ./doc +media_dir: ./doc/media +exclude_dir: ./bootstrap + ./archive + ./test +display: public + protected +source: true +proc_internals: true +sort: permission-alpha +favicon: doc/media/favicon.ico +print_creation_date: true +extra_mods: iso_fortran_env:https://gcc.gnu.org/onlinedocs/gfortran/ISO_005fFORTRAN_005fENV.html +creation_date: %Y-%m-%d %H:%M %z +md_extensions: markdown.extensions.toc + markdown.extensions.smarty +--- + +[TOC] diff --git a/fpm/src/fpm.f90 b/fpm/src/fpm.f90 index 575b654..01f3150 100644 --- a/fpm/src/fpm.f90 +++ b/fpm/src/fpm.f90 @@ -5,12 +5,12 @@ use fpm_command_line, only: fpm_build_settings, fpm_new_settings, & fpm_run_settings, fpm_install_settings, fpm_test_settings use fpm_environment, only: run use fpm_filesystem, only: is_dir, join_path, number_of_rows, list_files, exists, basename -use fpm_model, only: srcfile_ptr, srcfile_t, fpm_model_t, & +use fpm_model, only: fpm_model_t, srcfile_t, build_target_t, & FPM_SCOPE_UNKNOWN, FPM_SCOPE_LIB, & FPM_SCOPE_DEP, FPM_SCOPE_APP, FPM_SCOPE_TEST -use fpm_sources, only: add_executable_sources, add_sources_from_dir, & - resolve_module_dependencies +use fpm_sources, only: add_executable_sources, add_sources_from_dir +use fpm_targets, only: targets_from_sources, resolve_module_dependencies use fpm_manifest, only : get_package_data, default_executable, & default_library, package_t, default_test use fpm_error, only : error_t, fatal_error @@ -225,16 +225,17 @@ subroutine build_model(model, settings, package, error) return end if + call targets_from_sources(model,model%sources) + if(settings%list)then - do i=1,size(model%sources) - write(stderr,'(*(g0,1x))')'fpm::build<INFO>:file expected at',model%sources(i)%file_name, & - & merge('exists ','does not exist',exists(model%sources(i)%file_name) ) + do i=1,size(model%targets) + write(stderr,*) model%targets(i)%ptr%output_file enddo stop - else - call resolve_module_dependencies(model%sources,error) endif + call resolve_module_dependencies(model%targets,error) + end subroutine build_model @@ -320,7 +321,7 @@ logical :: list stop endif else - !*! expand names, duplicates are a problem?? + ! expand names, duplicates are a problem?? allocate(foundit(size(settings%name))) foundit=.false. FINDIT: do i=1,size(package%executable) @@ -340,10 +341,10 @@ logical :: list if(allocated(foundit))deallocate(foundit) endif do i=1,size(newwords) - !*! list is a new option for use with xargs, to move files to production area, valgrind, gdb, ls -l, .... - !*! maybe add as --mask and could do --mask 'echo %xx' or --mask 'cp %XX /usr/local/bin/' an so on - !*! default if blank would be filename uptodate|needs|updated|doesnotexist creation_date, ... - !*! or maybe just list filenames so can pipe through xargs, and so on + ! list is a new option for use with xargs, to move files to production area, valgrind, gdb, ls -l, .... + ! maybe add as --mask and could do --mask 'echo %xx' or --mask 'cp %XX /usr/local/bin/' an so on + ! default if blank would be filename uptodate|needs|updated|doesnotexist creation_date, ... + ! or maybe just list filenames so can pipe through xargs, and so on if(settings%list)then write(stderr,'(*(g0,1x))')'fpm::run<INFO>:executable expected at',newwords(i),& & merge('exists ','does not exist',exists(newwords(i))) @@ -402,7 +403,7 @@ logical :: list stop endif else - !*! expand names, duplicates are a problem?? + ! expand names, duplicates are a problem?? allocate(foundit(size(settings%name))) foundit=.false. FINDIT: do i=1,size(package%test) @@ -422,10 +423,10 @@ logical :: list if(allocated(foundit))deallocate(foundit) endif do i=1,size(newwords) - !*! list is a new option for use with xargs, to move files to production area, valgrind, gdb, ls -l, .... - !*! maybe add as --mask and could do --mask 'echo %xx' or --mask 'cp %XX /usr/local/bin/' an so on - !*! default if blank would be filename uptodate|needs|updated|doesnotexist creation_date, ... - !*! or maybe just list filenames so can pipe through xargs, and so on + ! list is a new option for use with xargs, to move files to production area, valgrind, gdb, ls -l, .... + ! maybe add as --mask and could do --mask 'echo %xx' or --mask 'cp %XX /usr/local/bin/' an so on + ! default if blank would be filename uptodate|needs|updated|doesnotexist creation_date, ... + ! or maybe just list filenames so can pipe through xargs, and so on if(settings%list)then write(stderr,'(*(g0,1x))')'fpm::run<INFO>:test expected at',newwords(i),& & merge('exists ','does not exist',exists(newwords(i))) diff --git a/fpm/src/fpm/cmd/new.f90 b/fpm/src/fpm/cmd/new.f90 index 91145d8..04cd7d5 100644 --- a/fpm/src/fpm/cmd/new.f90 +++ b/fpm/src/fpm/cmd/new.f90 @@ -38,8 +38,8 @@ character(len=8) :: date ! change to new directory as a test. System dependent potentially call run('cd '//settings%name) - !*! NOTE: need some system routines to handle filenames like "." - !*! like realpath() or getcwd(). + ! NOTE: need some system routines to handle filenames like "." + ! like realpath() or getcwd(). bname=basename(settings%name) ! create NAME/.gitignore file diff --git a/fpm/src/fpm/manifest.f90 b/fpm/src/fpm/manifest.f90 index d3e47de..9d2e793 100644 --- a/fpm/src/fpm/manifest.f90 +++ b/fpm/src/fpm/manifest.f90 @@ -1,11 +1,11 @@ !> 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. +!> +!> 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_build_config, only: build_config_t use fpm_manifest_executable, only : executable_t diff --git a/fpm/src/fpm/manifest/build_config.f90 b/fpm/src/fpm/manifest/build_config.f90 index 069c3e0..0509915 100644 --- a/fpm/src/fpm/manifest/build_config.f90 +++ b/fpm/src/fpm/manifest/build_config.f90 @@ -1,12 +1,12 @@ !> Implementation of the build configuration data. -! -! A build table can currently have the following fields -! -! ```toml -! [build] -! auto-executables = <bool> -! auto-tests = <bool> -! ``` +!> +!> A build table can currently have the following fields +!> +!>```toml +!>[build] +!>auto-executables = bool +!>auto-tests = bool +!>``` module fpm_manifest_build_config use fpm_error, only : error_t, syntax_error, fatal_error use fpm_toml, only : toml_table, toml_key, toml_stat, get_value diff --git a/fpm/src/fpm/manifest/dependency.f90 b/fpm/src/fpm/manifest/dependency.f90 index 599d43a..a35beb6 100644 --- a/fpm/src/fpm/manifest/dependency.f90 +++ b/fpm/src/fpm/manifest/dependency.f90 @@ -1,27 +1,27 @@ !> 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. +!> +!> 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, & diff --git a/fpm/src/fpm/manifest/executable.f90 b/fpm/src/fpm/manifest/executable.f90 index 6675519..87d9a8d 100644 --- a/fpm/src/fpm/manifest/executable.f90 +++ b/fpm/src/fpm/manifest/executable.f90 @@ -1,14 +1,14 @@ !> 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] -! ``` +!> +!> 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 diff --git a/fpm/src/fpm/manifest/library.f90 b/fpm/src/fpm/manifest/library.f90 index 7a79a2a..965e0f8 100644 --- a/fpm/src/fpm/manifest/library.f90 +++ b/fpm/src/fpm/manifest/library.f90 @@ -1,12 +1,12 @@ !> Implementation of the meta data for libraries. -! -! A library table can currently have the following fields -! -! ```toml -! [library] -! source-dir = "path" -! build-script = "file" -! ``` +!> +!> 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 diff --git a/fpm/src/fpm/manifest/package.f90 b/fpm/src/fpm/manifest/package.f90 index fc04aa8..b55e6d6 100644 --- a/fpm/src/fpm/manifest/package.f90 +++ b/fpm/src/fpm/manifest/package.f90 @@ -1,32 +1,32 @@ !> 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]] -! ``` +!> +!> 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_build_config, only: build_config_t, new_build_config use fpm_manifest_dependency, only : dependency_t, new_dependencies diff --git a/fpm/src/fpm/manifest/test.f90 b/fpm/src/fpm/manifest/test.f90 index de4c847..c01d51d 100644 --- a/fpm/src/fpm/manifest/test.f90 +++ b/fpm/src/fpm/manifest/test.f90 @@ -1,18 +1,18 @@ !> 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] -! ``` +!> +!> 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 diff --git a/fpm/src/fpm/toml.f90 b/fpm/src/fpm/toml.f90 index e2445c4..ecefdd8 100644 --- a/fpm/src/fpm/toml.f90 +++ b/fpm/src/fpm/toml.f90 @@ -1,16 +1,16 @@ !> Interface to TOML processing library. -! -! This module acts as a proxy to the `toml-f` public Fortran API and allows -! to selectively expose components from the library to `fpm`. -! The interaction with `toml-f` data types outside of this module should be -! limited to tables, arrays and key-lists, most of the necessary interactions -! are implemented in the building interface with the `get_value` and `set_value` -! procedures. -! -! This module allows to implement features necessary for `fpm`, which are -! not yet available in upstream `toml-f`. -! -! For more details on the library used see: https://github.com/toml-f/toml-f +!> +!> This module acts as a proxy to the `toml-f` public Fortran API and allows +!> to selectively expose components from the library to `fpm`. +!> The interaction with `toml-f` data types outside of this module should be +!> limited to tables, arrays and key-lists, most of the necessary interactions +!> are implemented in the building interface with the `get_value` and `set_value` +!> procedures. +!> +!> This module allows to implement features necessary for `fpm`, which are +!> not yet available in upstream `toml-f`. +!> +!> For more details on the library used see: https://toml-f.github.io/toml-f module fpm_toml use fpm_error, only : error_t, fatal_error, file_not_found_error use tomlf, only : toml_table, toml_array, toml_key, toml_stat, get_value, & diff --git a/fpm/src/fpm_backend.f90 b/fpm/src/fpm_backend.f90 index d7005bf..d705ec2 100644 --- a/fpm/src/fpm_backend.f90 +++ b/fpm/src/fpm_backend.f90 @@ -4,10 +4,10 @@ module fpm_backend use fpm_environment, only: run, get_os_type, OS_WINDOWS use fpm_filesystem, only: basename, dirname, join_path, exists, mkdir -use fpm_model, only: fpm_model_t, srcfile_t, FPM_UNIT_MODULE, & +use fpm_model, only: fpm_model_t, srcfile_t, build_target_t, FPM_UNIT_MODULE, & FPM_UNIT_SUBMODULE, FPM_UNIT_SUBPROGRAM, & FPM_UNIT_CSOURCE, FPM_UNIT_PROGRAM, & - FPM_SCOPE_TEST + FPM_SCOPE_TEST, FPM_TARGET_OBJECT, FPM_TARGET_ARCHIVE, FPM_TARGET_EXECUTABLE use fpm_strings, only: split @@ -32,127 +32,103 @@ subroutine build_package(model) call mkdir(join_path(model%output_directory,model%package_name)) end if - linking = "" - do i=1,size(model%sources) - - if (model%sources(i)%unit_type == FPM_UNIT_MODULE .or. & - model%sources(i)%unit_type == FPM_UNIT_SUBMODULE .or. & - model%sources(i)%unit_type == FPM_UNIT_SUBPROGRAM .or. & - model%sources(i)%unit_type == FPM_UNIT_CSOURCE) then - - call build_source(model,model%sources(i),linking) - - end if - - end do - - if (any([(model%sources(i)%unit_type == FPM_UNIT_PROGRAM,i=1,size(model%sources))])) then - if (.not.exists(join_path(model%output_directory,'test'))) then - call mkdir(join_path(model%output_directory,'test')) - end if - if (.not.exists(join_path(model%output_directory,'app'))) then - call mkdir(join_path(model%output_directory,'app')) - end if + if (model%targets(1)%ptr%target_type == FPM_TARGET_ARCHIVE) then + linking = " "//model%targets(1)%ptr%output_file + else + linking = " " end if - do i=1,size(model%sources) - - if (model%sources(i)%unit_type == FPM_UNIT_PROGRAM) then - - base = basename(model%sources(i)%file_name,suffix=.false.) - - if (model%sources(i)%unit_scope == FPM_SCOPE_TEST) then - subdir = 'test' - else - subdir = 'app' - end if - - call run("gfortran -c " // model%sources(i)%file_name // ' '//model%fortran_compile_flags & - // " -o " // join_path(model%output_directory,subdir,base) // ".o") - - call run("gfortran " // join_path(model%output_directory, subdir, base) // ".o "// & - linking //" " //model%link_flags // " -o " // & - join_path(model%output_directory,subdir,model%sources(i)%exe_name) ) - - end if + linking = linking//" "//model%link_flags + do i=1,size(model%targets) + + call build_target(model,model%targets(i)%ptr,linking) + end do end subroutine build_package -recursive subroutine build_source(model,source_file,linking) +recursive subroutine build_target(model,target,linking) ! Compile Fortran source, called recursively on it dependents ! type(fpm_model_t), intent(in) :: model - type(srcfile_t), intent(inout) :: source_file - character(:), allocatable, intent(inout) :: linking + type(build_target_t), intent(inout) :: target + character(:), allocatable, intent(in) :: linking - integer :: i - character(:), allocatable :: object_file + integer :: i, j + type(build_target_t), pointer :: exe_obj + character(:), allocatable :: objs - if (source_file%built) then + if (target%built) then return end if - if (source_file%touched) then - write(*,*) '(!) Circular dependency found with: ',source_file%file_name + if (target%touched) then + write(*,*) '(!) Circular dependency found with: ',target%output_file stop else - source_file%touched = .true. + target%touched = .true. end if - do i=1,size(source_file%file_dependencies) + objs = " " + + do i=1,size(target%dependencies) - if (associated(source_file%file_dependencies(i)%ptr)) then - call build_source(model,source_file%file_dependencies(i)%ptr,linking) + if (associated(target%dependencies(i)%ptr)) then + call build_target(model,target%dependencies(i)%ptr,linking) end if - end do + if (target%target_type == FPM_TARGET_ARCHIVE ) then - object_file = get_object_name(model,source_file%file_name) - - if (.not.exists(dirname(object_file))) then - call mkdir(dirname(object_file)) - end if + ! Construct object list for archive + objs = objs//" "//target%dependencies(i)%ptr%output_file - call run("gfortran -c " // source_file%file_name // model%fortran_compile_flags & - // " -o " // object_file) - linking = linking // " " // object_file + else if (target%target_type == FPM_TARGET_EXECUTABLE .and. & + target%dependencies(i)%ptr%target_type == FPM_TARGET_OBJECT) then - source_file%built = .true. + exe_obj => target%dependencies(i)%ptr + + ! Construct object list for executable + objs = " "//exe_obj%output_file + + ! Include non-library object dependencies + do j=1,size(exe_obj%dependencies) -end subroutine build_source + if (allocated(exe_obj%dependencies(j)%ptr%source)) then + if (exe_obj%dependencies(j)%ptr%source%unit_scope == exe_obj%source%unit_scope) then + objs = objs//" "//exe_obj%dependencies(j)%ptr%output_file + end if + end if + end do -function get_object_name(model,source_file_name) result(object_file) - ! Generate object target path from source name and model params - ! - ! src/test.f90 -> <output-dir>/<package-name>/test.o - ! src/subdir/test.f90 -> <output-dir>/<package-name>/subdir_test.o - ! - type(fpm_model_t), intent(in) :: model - character(*), intent(in) :: source_file_name - character(:), allocatable :: object_file + end if - integer :: i - character(1) :: filesep + end do + + if (.not.exists(dirname(target%output_file))) then + call mkdir(dirname(target%output_file)) + end if - select case(get_os_type()) - case (OS_WINDOWS) - filesep = '\' - case default - filesep = '/' - end select + select case(target%target_type) + + case (FPM_TARGET_OBJECT) + call run("gfortran -c " // target%source%file_name // model%fortran_compile_flags & + // " -o " // target%output_file) - ! Exclude first directory level from path - object_file = source_file_name(index(source_file_name,filesep)+1:) + case (FPM_TARGET_EXECUTABLE) + call run("gfortran " // objs // model%fortran_compile_flags & + //linking// " -o " // target%output_file) + + case (FPM_TARGET_ARCHIVE) + call run("ar -rs " // target%output_file // objs) + + end select - ! Construct full target path - object_file = join_path(model%output_directory, model%package_name, & - object_file//'.o') + target%built = .true. -end function get_object_name +end subroutine build_target end module fpm_backend diff --git a/fpm/src/fpm_model.f90 b/fpm/src/fpm_model.f90 index 36086df..b8c3220 100644 --- a/fpm/src/fpm_model.f90 +++ b/fpm/src/fpm_model.f90 @@ -4,12 +4,14 @@ use fpm_strings, only: string_t implicit none private -public :: srcfile_ptr, srcfile_t, fpm_model_t +public :: fpm_model_t, srcfile_t, build_target_t, build_target_ptr public :: FPM_UNIT_UNKNOWN, FPM_UNIT_PROGRAM, FPM_UNIT_MODULE, & FPM_UNIT_SUBMODULE, FPM_UNIT_SUBPROGRAM, FPM_UNIT_CSOURCE, & FPM_UNIT_CHEADER, FPM_SCOPE_UNKNOWN, FPM_SCOPE_LIB, & - FPM_SCOPE_DEP, FPM_SCOPE_APP, FPM_SCOPE_TEST + FPM_SCOPE_DEP, FPM_SCOPE_APP, FPM_SCOPE_TEST, & + FPM_TARGET_UNKNOWN, FPM_TARGET_EXECUTABLE, FPM_TARGET_ARCHIVE, & + FPM_TARGET_OBJECT integer, parameter :: FPM_UNIT_UNKNOWN = -1 integer, parameter :: FPM_UNIT_PROGRAM = 1 @@ -25,10 +27,10 @@ integer, parameter :: FPM_SCOPE_DEP = 2 integer, parameter :: FPM_SCOPE_APP = 3 integer, parameter :: FPM_SCOPE_TEST = 4 -type srcfile_ptr - ! For constructing arrays of src_file pointers - type(srcfile_t), pointer :: ptr => null() -end type srcfile_ptr +integer, parameter :: FPM_TARGET_UNKNOWN = -1 +integer, parameter :: FPM_TARGET_EXECUTABLE = 1 +integer, parameter :: FPM_TARGET_ARCHIVE = 2 +integer, parameter :: FPM_TARGET_OBJECT = 3 type srcfile_t ! Type for encapsulating a source file @@ -49,18 +51,34 @@ type srcfile_t ! Modules USEd by this source file (lowerstring) type(string_t), allocatable :: include_dependencies(:) ! Files INCLUDEd by this source file - type(srcfile_ptr), allocatable :: file_dependencies(:) - ! Resolved source file dependencies +end type srcfile_t + +type build_target_ptr + ! For constructing arrays of build_target_t pointers + type(build_target_t), pointer :: ptr => null() +end type build_target_ptr + +type build_target_t + character(:), allocatable :: output_file + ! File path of build target object relative to cwd + type(srcfile_t), allocatable :: source + ! Primary source for this build target + type(build_target_ptr), allocatable :: dependencies(:) + ! Resolved build dependencies + integer :: target_type = FPM_TARGET_UNKNOWN logical :: built = .false. logical :: touched = .false. -end type srcfile_t + +end type build_target_t type :: fpm_model_t character(:), allocatable :: package_name ! Name of package type(srcfile_t), allocatable :: sources(:) - ! Array of sources with module-dependencies resolved + ! Array of sources + type(build_target_ptr), allocatable :: targets(:) + ! Array of targets with module-dependencies resolved character(:), allocatable :: fortran_compiler ! Command line name to invoke fortran compiler character(:), allocatable :: fortran_compile_flags diff --git a/fpm/src/fpm_sources.f90 b/fpm/src/fpm_sources.f90 index 393c799..7d853e0 100644 --- a/fpm/src/fpm_sources.f90 +++ b/fpm/src/fpm_sources.f90 @@ -1,6 +1,6 @@ module fpm_sources use fpm_error, only: error_t, file_parse_error, fatal_error -use fpm_model, only: srcfile_ptr, srcfile_t, fpm_model_t, & +use fpm_model, only: srcfile_t, fpm_model_t, & FPM_UNIT_UNKNOWN, FPM_UNIT_PROGRAM, FPM_UNIT_MODULE, & FPM_UNIT_SUBMODULE, FPM_UNIT_SUBPROGRAM, & FPM_UNIT_CSOURCE, FPM_UNIT_CHEADER, FPM_SCOPE_UNKNOWN, & @@ -13,7 +13,7 @@ implicit none private public :: add_sources_from_dir, add_executable_sources -public :: parse_f_source, parse_c_source, resolve_module_dependencies +public :: parse_f_source, parse_c_source character(15), parameter :: INTRINSIC_MODULE_NAMES(*) = & ['iso_c_binding ', & @@ -583,104 +583,4 @@ function split_n(string,delims,n,stat) result(substring) end function split_n -subroutine resolve_module_dependencies(sources,error) - ! After enumerating all source files: resolve file dependencies - ! by searching on module names - ! - type(srcfile_t), intent(inout), target :: sources(:) - type(error_t), allocatable, intent(out) :: error - - type(srcfile_ptr) :: dep - - integer :: n_depend, i, pass, j - - do i=1,size(sources) - - do pass=1,2 - - n_depend = 0 - - do j=1,size(sources(i)%modules_used) - - if (sources(i)%modules_used(j)%s .in. sources(i)%modules_provided) then - ! Dependency satisfied in same file, skip - cycle - end if - - if (sources(i)%unit_scope == FPM_SCOPE_APP .OR. & - sources(i)%unit_scope == FPM_SCOPE_TEST ) then - dep%ptr => & - find_module_dependency(sources,sources(i)%modules_used(j)%s, & - include_dir = dirname(sources(i)%file_name)) - else - dep%ptr => & - find_module_dependency(sources,sources(i)%modules_used(j)%s) - end if - - if (.not.associated(dep%ptr)) then - call fatal_error(error, & - 'Unable to find source for module dependency: "' // & - sources(i)%modules_used(j)%s // & - '" used by "'//sources(i)%file_name//'"') - return - end if - - n_depend = n_depend + 1 - - if (pass == 2) then - sources(i)%file_dependencies(n_depend) = dep - end if - - end do - - if (pass == 1) then - allocate(sources(i)%file_dependencies(n_depend)) - end if - - end do - - end do - -end subroutine resolve_module_dependencies - -function find_module_dependency(sources,module_name,include_dir) result(src_ptr) - ! Find a module dependency in the library or a dependency library - ! - ! 'include_dir' specifies an allowable non-library search directory - ! (Used for executable dependencies) - ! - type(srcfile_t), intent(in), target :: sources(:) - character(*), intent(in) :: module_name - character(*), intent(in), optional :: include_dir - type(srcfile_t), pointer :: src_ptr - - integer :: k, l - - src_ptr => NULL() - - do k=1,size(sources) - - do l=1,size(sources(k)%modules_provided) - - if (module_name == sources(k)%modules_provided(l)%s) then - select case(sources(k)%unit_scope) - case (FPM_SCOPE_LIB, FPM_SCOPE_DEP) - src_ptr => sources(k) - exit - case default - if (present(include_dir)) then - if (dirname(sources(k)%file_name) == include_dir) then - src_ptr => sources(k) - exit - end if - end if - end select - end if - - end do - - end do - -end function find_module_dependency - end module fpm_sources diff --git a/fpm/src/fpm_targets.f90 b/fpm/src/fpm_targets.f90 new file mode 100644 index 0000000..2cd4418 --- /dev/null +++ b/fpm/src/fpm_targets.f90 @@ -0,0 +1,248 @@ +module fpm_targets +use fpm_error, only: error_t, fatal_error +use fpm_model +use fpm_environment, only: get_os_type, OS_WINDOWS +use fpm_filesystem, only: dirname, join_path, canon_path +use fpm_strings, only: operator(.in.) +implicit none + +contains + +subroutine targets_from_sources(model,sources) + type(fpm_model_t), intent(inout), target :: model + type(srcfile_t), intent(in) :: sources(:) + + integer :: i + type(build_target_t), pointer :: dep + logical :: with_lib + + with_lib = any([(sources(i)%unit_scope == FPM_SCOPE_LIB,i=1,size(sources))]) + + if (with_lib) call add_target(model%targets,type = FPM_TARGET_ARCHIVE,& + output_file = join_path(model%output_directory,& + model%package_name,'lib'//model%package_name//'.a')) + + do i=1,size(sources) + + select case (sources(i)%unit_type) + case (FPM_UNIT_MODULE,FPM_UNIT_SUBMODULE,FPM_UNIT_SUBPROGRAM,FPM_UNIT_CSOURCE) + + call add_target(model%targets,source = sources(i), & + type = FPM_TARGET_OBJECT,& + output_file = get_object_name(sources(i))) + + if (with_lib .and. sources(i)%unit_scope == FPM_SCOPE_LIB) then + ! Archive depends on object + call add_dependency(model%targets(1)%ptr, model%targets(size(model%targets))%ptr) + end if + + case (FPM_UNIT_PROGRAM) + + call add_target(model%targets,type = FPM_TARGET_OBJECT,& + output_file = get_object_name(sources(i)), & + source = sources(i) & + ) + + if (sources(i)%unit_scope == FPM_SCOPE_APP) then + call add_target(model%targets,type = FPM_TARGET_EXECUTABLE,& + output_file = join_path(model%output_directory,'app',sources(i)%exe_name)) + else + call add_target(model%targets,type = FPM_TARGET_EXECUTABLE,& + output_file = join_path(model%output_directory,'test',sources(i)%exe_name)) + + end if + + ! Executable depends on object + call add_dependency(model%targets(size(model%targets))%ptr, model%targets(size(model%targets)-1)%ptr) + + if (with_lib) then + ! Executable depends on library + call add_dependency(model%targets(size(model%targets))%ptr, model%targets(1)%ptr) + end if + + end select + + end do + + contains + + function get_object_name(source) result(object_file) + ! Generate object target path from source name and model params + ! + ! + type(srcfile_t), intent(in) :: source + character(:), allocatable :: object_file + + integer :: i + character(1), parameter :: filesep = '/' + character(:), allocatable :: dir + + object_file = canon_path(source%file_name) + + ! Ignore first directory level + object_file = object_file(index(object_file,filesep)+1:) + + ! Convert any remaining directory separators to underscores + i = index(object_file,filesep) + do while(i > 0) + object_file(i:i) = '_' + i = index(object_file,filesep) + end do + + select case(source%unit_scope) + + case (FPM_SCOPE_APP) + object_file = join_path(model%output_directory,'app',object_file)//'.o' + + case (FPM_SCOPE_TEST) + object_file = join_path(model%output_directory,'test',object_file)//'.o' + + case default + object_file = join_path(model%output_directory,model%package_name,object_file)//'.o' + + end select + + end function get_object_name + +end subroutine targets_from_sources + + +!> Add new target to target list +subroutine add_target(targets,type,output_file,source) + type(build_target_ptr), allocatable, intent(inout) :: targets(:) + integer, intent(in) :: type + character(*), intent(in) :: output_file + type(srcfile_t), intent(in), optional :: source + + integer :: i + type(build_target_ptr), allocatable :: temp(:) + type(build_target_t), pointer :: new_target + + if (.not.allocated(targets)) allocate(targets(0)) + + ! Check for duplicate outputs + do i=1,size(targets) + + if (targets(i)%ptr%output_file == output_file) then + + write(*,*) 'Error while building target list: duplicate output object "',& + output_file,'"' + if (present(source)) write(*,*) ' Source file: "',source%file_name,'"' + stop 1 + + end if + + end do + + allocate(new_target) + new_target%target_type = type + new_target%output_file = output_file + if (present(source)) new_target%source = source + allocate(new_target%dependencies(0)) + + targets = [targets, build_target_ptr(new_target)] + +end subroutine add_target + + +!> Add pointer to dependeny in target%dependencies +subroutine add_dependency(target, dependency) + type(build_target_t), intent(inout) :: target + type(build_target_t) , intent(in), target :: dependency + + target%dependencies = [target%dependencies, build_target_ptr(dependency)] + +end subroutine add_dependency + + +subroutine resolve_module_dependencies(targets,error) + ! After enumerating all source files: resolve file dependencies + ! by searching on module names + ! + type(build_target_ptr), intent(inout), target :: targets(:) + type(error_t), allocatable, intent(out) :: error + + type(build_target_ptr) :: dep + + integer :: i, j + + do i=1,size(targets) + + if (.not.allocated(targets(i)%ptr%source)) cycle + + do j=1,size(targets(i)%ptr%source%modules_used) + + if (targets(i)%ptr%source%modules_used(j)%s .in. targets(i)%ptr%source%modules_provided) then + ! Dependency satisfied in same file, skip + cycle + end if + + if (targets(i)%ptr%source%unit_scope == FPM_SCOPE_APP .OR. & + targets(i)%ptr%source%unit_scope == FPM_SCOPE_TEST ) then + dep%ptr => & + find_module_dependency(targets,targets(i)%ptr%source%modules_used(j)%s, & + include_dir = dirname(targets(i)%ptr%source%file_name)) + else + dep%ptr => & + find_module_dependency(targets,targets(i)%ptr%source%modules_used(j)%s) + end if + + if (.not.associated(dep%ptr)) then + call fatal_error(error, & + 'Unable to find source for module dependency: "' // & + targets(i)%ptr%source%modules_used(j)%s // & + '" used by "'//targets(i)%ptr%source%file_name//'"') + return + end if + + call add_dependency(targets(i)%ptr, dep%ptr) + + end do + + end do + +end subroutine resolve_module_dependencies + +function find_module_dependency(targets,module_name,include_dir) result(target_ptr) + ! Find a module dependency in the library or a dependency library + ! + ! 'include_dir' specifies an allowable non-library search directory + ! (Used for executable dependencies) + ! + type(build_target_ptr), intent(in), target :: targets(:) + character(*), intent(in) :: module_name + character(*), intent(in), optional :: include_dir + type(build_target_t), pointer :: target_ptr + + integer :: k, l + + target_ptr => NULL() + + do k=1,size(targets) + + if (.not.allocated(targets(k)%ptr%source)) cycle + + do l=1,size(targets(k)%ptr%source%modules_provided) + + if (module_name == targets(k)%ptr%source%modules_provided(l)%s) then + select case(targets(k)%ptr%source%unit_scope) + case (FPM_SCOPE_LIB, FPM_SCOPE_DEP) + target_ptr => targets(k)%ptr + exit + case default + if (present(include_dir)) then + if (dirname(targets(k)%ptr%source%file_name) == include_dir) then + target_ptr => targets(k)%ptr + exit + end if + end if + end select + end if + + end do + + end do + +end function find_module_dependency + +end module fpm_targets
\ No newline at end of file diff --git a/fpm/test/fpm_test/test_module_dependencies.f90 b/fpm/test/fpm_test/test_module_dependencies.f90 index 481dfb3..c73db30 100644 --- a/fpm/test/fpm_test/test_module_dependencies.f90 +++ b/fpm/test/fpm_test/test_module_dependencies.f90 @@ -1,12 +1,13 @@ !> Define tests for the `fpm_sources` module (module dependency checking) module test_module_dependencies use testsuite, only : new_unittest, unittest_t, error_t, test_failed - use fpm_sources, only: resolve_module_dependencies - use fpm_model, only: srcfile_t, srcfile_ptr, & + use fpm_targets, only: targets_from_sources, resolve_module_dependencies + use fpm_model, only: fpm_model_t, srcfile_t, build_target_t, build_target_ptr, & FPM_UNIT_UNKNOWN, FPM_UNIT_PROGRAM, FPM_UNIT_MODULE, & FPM_UNIT_SUBMODULE, FPM_UNIT_SUBPROGRAM, FPM_UNIT_CSOURCE, & FPM_UNIT_CHEADER, FPM_SCOPE_UNKNOWN, FPM_SCOPE_LIB, & - FPM_SCOPE_DEP, FPM_SCOPE_APP, FPM_SCOPE_TEST + FPM_SCOPE_DEP, FPM_SCOPE_APP, FPM_SCOPE_TEST, & + FPM_TARGET_EXECUTABLE, FPM_TARGET_OBJECT, FPM_TARGET_ARCHIVE use fpm_strings, only: string_t implicit none private @@ -14,7 +15,7 @@ module test_module_dependencies public :: collect_module_dependencies interface operator(.in.) - module procedure srcfile_in + module procedure target_in end interface contains @@ -51,91 +52,127 @@ contains type(error_t), allocatable, intent(out) :: error type(srcfile_t) :: sources(2) + type(fpm_model_t) :: model - sources(1) = new_test_module(file_name="src/my_mod_1.f90", & + model%output_directory = '' + + sources(1) = new_test_source(FPM_UNIT_MODULE,file_name="src/my_mod_1.f90", & scope = FPM_SCOPE_LIB, & provides=[string_t('my_mod_1')]) - sources(2) = new_test_module(file_name="src/my_mod_2.f90", & + sources(2) = new_test_source(FPM_UNIT_MODULE,file_name="src/my_mod_2.f90", & scope = FPM_SCOPE_LIB, & provides=[string_t('my_mod_2')], & uses=[string_t('my_mod_1')]) - call resolve_module_dependencies(sources,error) + call targets_from_sources(model,sources) + call resolve_module_dependencies(model%targets,error) if (allocated(error)) then return end if - if (size(sources(1)%file_dependencies)>0) then - call test_failed(error,'Incorrect number of file_dependencies - expecting zero') + if (size(model%targets) /= 3) then + call test_failed(error,'Incorrect number of model%targets - expecting three') return end if - if (size(sources(2)%file_dependencies) /= 1) then - call test_failed(error,'Incorrect number of file_dependencies - expecting one') - return - end if + call check_target(model%targets(1)%ptr,type=FPM_TARGET_ARCHIVE,n_depends=2, & + deps = [model%targets(2),model%targets(3)],error=error) + + if (allocated(error)) return - if (.not.(sources(1) .in. sources(2)%file_dependencies)) then - call test_failed(error,'Missing file in file_dependencies') - return - end if + + call check_target(model%targets(2)%ptr,type=FPM_TARGET_OBJECT,n_depends=0, & + source=sources(1),error=error) + + if (allocated(error)) return + + + call check_target(model%targets(3)%ptr,type=FPM_TARGET_OBJECT,n_depends=1, & + deps=[model%targets(2)],source=sources(2),error=error) + + if (allocated(error)) return end subroutine test_library_module_use - !> Check program using a library module + !> Check a program using a library module + !> Each program generates two model%targets: object file and executable + !> subroutine test_program_module_use(error) !> Error handling type(error_t), allocatable, intent(out) :: error + call test_scope(FPM_SCOPE_APP,error) + if (allocated(error)) return + + call test_scope(FPM_SCOPE_TEST,error) + if (allocated(error)) return + + contains + + subroutine test_scope(exe_scope,error) + integer, intent(in) :: exe_scope + type(error_t), allocatable, intent(out) :: error + integer :: i type(srcfile_t) :: sources(3) + type(fpm_model_t) :: model + character(:), allocatable :: scope_str + + model%output_directory = '' + + scope_str = merge('FPM_SCOPE_APP ','FPM_SCOPE_TEST',exe_scope==FPM_SCOPE_APP)//' - ' - sources(1) = new_test_module(file_name="src/my_mod_1.f90", & + sources(1) = new_test_source(FPM_UNIT_MODULE,file_name="src/my_mod_1.f90", & scope = FPM_SCOPE_LIB, & provides=[string_t('my_mod_1')]) - sources(2) = new_test_program(file_name="app/my_program.f90", & - scope=FPM_SCOPE_APP, & - uses=[string_t('my_mod_1')]) - - sources(3) = new_test_program(file_name="test/my_test.f90", & - scope=FPM_SCOPE_TEST, & + sources(2) = new_test_source(FPM_UNIT_PROGRAM,file_name="app/my_program.f90", & + scope=exe_scope, & uses=[string_t('my_mod_1')]) - call resolve_module_dependencies(sources,error) + call targets_from_sources(model,sources) + call resolve_module_dependencies(model%targets,error) if (allocated(error)) then return end if - if (size(sources(1)%file_dependencies)>0) then - call test_failed(error,'Incorrect number of file_dependencies - expecting zero') + if (size(model%targets) /= 4) then + call test_failed(error,scope_str//'Incorrect number of model%targets - expecting three') return end if - do i=2,3 + call check_target(model%targets(1)%ptr,type=FPM_TARGET_ARCHIVE,n_depends=1, & + deps=[model%targets(2)],error=error) + + if (allocated(error)) return - if (size(sources(i)%file_dependencies) /= 1) then - call test_failed(error,'Incorrect number of file_dependencies - expecting one') - return - end if + call check_target(model%targets(2)%ptr,type=FPM_TARGET_OBJECT,n_depends=0, & + source=sources(1),error=error) - if (.not.(sources(1) .in. sources(i)%file_dependencies)) then - call test_failed(error,'Missing file in file_dependencies') - return - end if + if (allocated(error)) return + + call check_target(model%targets(3)%ptr,type=FPM_TARGET_OBJECT,n_depends=1, & + deps=[model%targets(2)],source=sources(2),error=error) + + if (allocated(error)) return + + call check_target(model%targets(4)%ptr,type=FPM_TARGET_EXECUTABLE,n_depends=2, & + deps=[model%targets(1),model%targets(3)],error=error) + + if (allocated(error)) return + + end subroutine test_scope - end do - end subroutine test_program_module_use !> Check program with module in single source file - !> (Resulting source object should not include itself as a file dependency) + !> (Resulting target should not include itself as a dependency) subroutine test_program_with_module(error) !> Error handling @@ -143,22 +180,37 @@ contains integer :: i type(srcfile_t) :: sources(1) + type(fpm_model_t) :: model - sources(1) = new_test_module(file_name="app/my_program.f90", & + model%output_directory = '' + + sources(1) = new_test_source(FPM_UNIT_PROGRAM,file_name="app/my_program.f90", & scope = FPM_SCOPE_APP, & provides=[string_t('app_mod')], & uses=[string_t('app_mod')]) - call resolve_module_dependencies(sources,error) + call targets_from_sources(model,sources) + call resolve_module_dependencies(model%targets,error) if (allocated(error)) then return end if - if (size(sources(1)%file_dependencies)>0) then - call test_failed(error,'Incorrect number of file_dependencies - expecting zero') + if (size(model%targets) /= 2) then + write(*,*) size(model%targets) + call test_failed(error,'Incorrect number of model%targets - expecting two') return end if + + call check_target(model%targets(1)%ptr,type=FPM_TARGET_OBJECT,n_depends=0, & + source=sources(1),error=error) + + if (allocated(error)) return + + call check_target(model%targets(2)%ptr,type=FPM_TARGET_EXECUTABLE,n_depends=1, & + deps=[model%targets(1)],error=error) + + if (allocated(error)) return end subroutine test_program_with_module @@ -169,37 +221,63 @@ contains !> Error handling type(error_t), allocatable, intent(out) :: error + call test_scope(FPM_SCOPE_APP,error) + if (allocated(error)) return + + call test_scope(FPM_SCOPE_TEST,error) + if (allocated(error)) return + + contains + + subroutine test_scope(exe_scope,error) + integer, intent(in) :: exe_scope + type(error_t), allocatable, intent(out) :: error + type(srcfile_t) :: sources(2) + type(fpm_model_t) :: model + character(:), allocatable :: scope_str - sources(1) = new_test_module(file_name="app/app_mod.f90", & - scope = FPM_SCOPE_APP, & + model%output_directory = '' + + scope_str = merge('FPM_SCOPE_APP ','FPM_SCOPE_TEST',exe_scope==FPM_SCOPE_APP)//' - ' + + sources(1) = new_test_source(FPM_UNIT_MODULE,file_name="app/app_mod.f90", & + scope = exe_scope, & provides=[string_t('app_mod')]) - sources(2) = new_test_program(file_name="app/my_program.f90", & - scope=FPM_SCOPE_APP, & + sources(2) = new_test_source(FPM_UNIT_PROGRAM,file_name="app/my_program.f90", & + scope=exe_scope, & uses=[string_t('app_mod')]) - call resolve_module_dependencies(sources,error) + call targets_from_sources(model,sources) + call resolve_module_dependencies(model%targets,error) if (allocated(error)) then return end if - if (size(sources(1)%file_dependencies)>0) then - call test_failed(error,'Incorrect number of file_dependencies - expecting zero') + if (size(model%targets) /= 3) then + call test_failed(error,scope_str//'Incorrect number of model%targets - expecting three') return end if - if (size(sources(2)%file_dependencies) /= 1) then - call test_failed(error,'Incorrect number of file_dependencies - expecting one') - return - end if - if (.not.(sources(1) .in. sources(2)%file_dependencies)) then - call test_failed(error,'Missing file in file_dependencies') - return - end if + call check_target(model%targets(1)%ptr,type=FPM_TARGET_OBJECT,n_depends=0, & + source=sources(1),error=error) + + if (allocated(error)) return + + call check_target(model%targets(2)%ptr,type=FPM_TARGET_OBJECT,n_depends=1, & + source=sources(2),deps=[model%targets(1)],error=error) + if (allocated(error)) return + + call check_target(model%targets(3)%ptr,type=FPM_TARGET_EXECUTABLE,n_depends=1, & + deps=[model%targets(2)],error=error) + + if (allocated(error)) return + + end subroutine test_scope end subroutine test_program_own_module_use @@ -210,17 +288,21 @@ contains type(error_t), allocatable, intent(out) :: error type(srcfile_t) :: sources(2) + type(fpm_model_t) :: model - sources(1) = new_test_module(file_name="src/my_mod_1.f90", & + model%output_directory = '' + + sources(1) = new_test_source(FPM_UNIT_MODULE,file_name="src/my_mod_1.f90", & scope = FPM_SCOPE_LIB, & provides=[string_t('my_mod_1')]) - sources(2) = new_test_module(file_name="src/my_mod_2.f90", & + sources(2) = new_test_source(FPM_UNIT_MODULE,file_name="src/my_mod_2.f90", & scope = FPM_SCOPE_LIB, & provides=[string_t('my_mod_2')], & uses=[string_t('my_mod_3')]) - call resolve_module_dependencies(sources,error) + call targets_from_sources(model,sources) + call resolve_module_dependencies(model%targets,error) end subroutine test_missing_library_use @@ -232,16 +314,20 @@ contains type(error_t), allocatable, intent(out) :: error type(srcfile_t) :: sources(2) + type(fpm_model_t) :: model + + model%output_directory = '' - sources(1) = new_test_module(file_name="src/my_mod_1.f90", & + sources(1) = new_test_source(FPM_UNIT_MODULE,file_name="src/my_mod_1.f90", & scope = FPM_SCOPE_LIB, & provides=[string_t('my_mod_1')]) - sources(2) = new_test_program(file_name="app/my_program.f90", & + sources(2) = new_test_source(FPM_UNIT_PROGRAM,file_name="app/my_program.f90", & scope=FPM_SCOPE_APP, & uses=[string_t('my_mod_2')]) - call resolve_module_dependencies(sources,error) + call targets_from_sources(model,sources) + call resolve_module_dependencies(model%targets,error) end subroutine test_missing_program_use @@ -253,17 +339,21 @@ contains type(error_t), allocatable, intent(out) :: error type(srcfile_t) :: sources(2) + type(fpm_model_t) :: model + + model%output_directory = '' - sources(1) = new_test_module(file_name="app/app_mod.f90", & + sources(1) = new_test_source(FPM_UNIT_MODULE,file_name="app/app_mod.f90", & scope = FPM_SCOPE_APP, & provides=[string_t('app_mod')]) - sources(2) = new_test_module(file_name="src/my_mod.f90", & + sources(2) = new_test_source(FPM_UNIT_MODULE,file_name="src/my_mod.f90", & scope = FPM_SCOPE_LIB, & provides=[string_t('my_mod')], & uses=[string_t('app_mod')]) - call resolve_module_dependencies(sources,error) + call targets_from_sources(model,sources) + call resolve_module_dependencies(model%targets,error) end subroutine test_invalid_library_use @@ -275,22 +365,27 @@ contains type(error_t), allocatable, intent(out) :: error type(srcfile_t) :: sources(2) + type(fpm_model_t) :: model - sources(1) = new_test_module(file_name="app/subdir/app_mod.f90", & + model%output_directory = '' + + sources(1) = new_test_source(FPM_UNIT_MODULE,file_name="app/subdir/app_mod.f90", & scope = FPM_SCOPE_APP, & provides=[string_t('app_mod')]) - sources(2) = new_test_program(file_name="app/my_program.f90", & + sources(2) = new_test_source(FPM_UNIT_PROGRAM,file_name="app/my_program.f90", & scope=FPM_SCOPE_APP, & uses=[string_t('app_mod')]) - call resolve_module_dependencies(sources,error) + call targets_from_sources(model,sources) + call resolve_module_dependencies(model%targets,error) end subroutine test_invalid_own_module_use - !> Helper to create a new srcfile_t for a module - function new_test_module(file_name, scope, uses, provides) result(src) + !> Helper to create a new srcfile_t + function new_test_source(type,file_name, scope, uses, provides) result(src) + integer, intent(in) :: type character(*), intent(in) :: file_name integer, intent(in) :: scope type(string_t), intent(in), optional :: uses(:) @@ -299,7 +394,7 @@ contains src%file_name = file_name src%unit_scope = scope - src%unit_type = FPM_UNIT_MODULE + src%unit_type = type if (present(provides)) then src%modules_provided = provides @@ -315,49 +410,89 @@ contains allocate(src%include_dependencies(0)) - end function new_test_module + end function new_test_source - !> Helper to create a new srcfile_t for a program - function new_test_program(file_name, scope, uses) result(src) - character(*), intent(in) :: file_name - integer, intent(in) :: scope - type(string_t), intent(in), optional :: uses(:) - type(srcfile_t) :: src + !> Helper to check an expected output target + subroutine check_target(target,type,n_depends,deps,source,error) + type(build_target_t), intent(in) :: target + integer, intent(in) :: type + integer, intent(in) :: n_depends + type(srcfile_t), intent(in), optional :: source + type(build_target_ptr), intent(in), optional :: deps(:) + type(error_t), intent(out), allocatable :: error - src%file_name = file_name - src%unit_scope = scope - src%unit_type = FPM_UNIT_PROGRAM + integer :: i - if (present(uses)) then - src%modules_used = uses - else - allocate(src%modules_used(0)) + if (target%target_type /= type) then + call test_failed(error,'Unexpected target_type for target "'//target%output_file//'"') + return end if - allocate(src%modules_provided(0)) - allocate(src%include_dependencies(0)) + if (size(target%dependencies) /= n_depends) then + call test_failed(error,'Wrong number of dependencies for target "'//target%output_file//'"') + return + end if + + if (present(deps)) then + + do i=1,size(deps) - end function new_test_program + if (.not.(deps(i)%ptr .in. target%dependencies)) then + call test_failed(error,'Missing dependency ('//deps(i)%ptr%output_file//& + ') for target "'//target%output_file//'"') + return + end if + end do - !> Helper to check if a srcfile is in a list of srcfile_ptr - logical function srcfile_in(needle,haystack) - type(srcfile_t), intent(in), target :: needle - type(srcfile_ptr), intent(in) :: haystack(:) + end if + + if (present(source)) then + + if (allocated(target%source)) then + if (target%source%file_name /= source%file_name) then + call test_failed(error,'Incorrect source ('//target%source%file_name//') for target "'//& + target%output_file//'"'//new_line('a')//' expected "'//source%file_name//'"') + return + end if + + else + call test_failed(error,'Expecting source for target "'//target%output_file//'" but none found') + return + end if + + else + + if (allocated(target%source)) then + call test_failed(error,'Found source ('//target%source%file_name//') for target "'//& + target%output_file//'" but none expected') + return + end if + + end if + + end subroutine check_target + + + !> Helper to check if a build target is in a list of build_target_ptr + logical function target_in(needle,haystack) + type(build_target_t), intent(in), target :: needle + type(build_target_ptr), intent(in) :: haystack(:) integer :: i - srcfile_in = .false. + target_in = .false. do i=1,size(haystack) if (associated(haystack(i)%ptr,needle)) then - srcfile_in = .true. + target_in = .true. return end if end do - end function srcfile_in + end function target_in + end module test_module_dependencies @@ -5,13 +5,13 @@ set -e # exit on error install_path="$HOME/.local/bin" -if command -v stack &> /dev/null ; then +if command -v stack 1> /dev/null 2>&1 ; then echo "found stack" else echo "Haskell stack not found." echo "Installing Haskell stack to." curl -sSL https://get.haskellstack.org/ | sh - if command -v stack &> /dev/null ; then + if command -v stack 1> /dev/null 2>&1 ; then echo "Haskell stack installation successful." else echo "Haskell stack installation unsuccessful." @@ -19,14 +19,14 @@ else fi fi -if [[ -x "$install_path/fpm" ]]; then +if [ -x "$install_path/fpm" ]; then echo "Overwriting existing fpm installation in $install_path" fi cd bootstrap stack install -if [[ -x "$install_path/fpm" ]]; then +if [ -x "$install_path/fpm" ]; then echo "fpm installed successfully to $install_path" else echo "fpm installation unsuccessful: fpm not found in $install_path" diff --git a/manifest-reference.md b/manifest-reference.md new file mode 100644 index 0000000..5f0227a --- /dev/null +++ b/manifest-reference.md @@ -0,0 +1,361 @@ +# Fortran package manager (fpm) manifest reference + +The ``fpm.toml`` file for each project is called its *manifest*. +It is written using the [TOML] format. +Every manifest file consists of the following sections: + +- [*name*](#project-name): + The name of the project +- [*version*](#project-version): + The version of the project +- [*license*](#project-license): + The project license +- [*maintainer*](#project-maintainer): + Maintainer of the project +- [*author*](#project-author): + Author of the project +- [*copyright*](#project-copyright): + Copyright of the project +- [*description*](#project-description): + Description of the project +- [*categories*](#project-categories): + Categories associated with the project +- [*keywords*](#project-keywords): + Keywords describing the project +- [*homepage*](#project-homepage): + The project's homepage +- Build configuration: + - [*auto-tests*](#automatic-target-discovery): + Toggle automatic discovery of test executables + - [*auto-executables*](#automatic-target-discovery): + Toggle automatic discovery of executables +- Target sections: + - [*library*](#library-configuration) + Configuration of the library target + - [*executable*](#executable-targets) + Configuration of the executable targets + - [*test*](#test-targets) + Configuration of the test targets +- Dependency sections: + - [*dependencies*](#specifying-dependencies): + Project library dependencies + - [*dev-dependencies*](#development-dependencies): + Dependencies only needed for tests + +[TOML]: https://toml.io/ + + +## Project name + +The project name identifies the package and is used to refer to it. +It is used when listing the project as dependency for another package and the default name of the library and executable target. +Therefore, the project name must always be present. + +*Example:* + +```toml +name = "hello_world" +``` + + +## Project version + +The version number of the project is specified as string. +A standardized way to manage and specify versions is the [Semantic Versioning] scheme. + +*Example:* + +```toml +version = "1.0.0" +``` + +[Semantic Versioning]: https://semver.org + + +## Project license + +The project license field contains the license identifier. +A standardized way to specify licensing information are [SPDX] identifiers. + +*Examples:* + +Projects licensed under the [GNU Lesser General Public License](https://www.gnu.org/licenses/lgpl-3.0-standalone.html), either version 3 or any later version, is specified as + +```toml +license = "LGPL-3.0-or-later" +``` + +Dual licensed project under the [Apache license, version 2.0](http://www.apache.org/licenses/LICENSE-2.0) or the [MIT license](https://opensource.org/licenses/MIT) are specified as + +```toml +license = "Apache-2.0 OR MIT" +``` + +[SPDX]: https://spdx.org/licenses/ + + +## Project maintainer + +Information on the project maintainer and means to reach out to them. + +*Example:* + +```toml +maintainer = "jane.doe@example.com" +``` + + +## Project author + +Information on the project author. + +*Example:* + +```toml +author = "Jane Doe" +``` + + +## Project copyright + +A statement clarifying the copyright status of the project. + +*Example:* + +```toml +copyright = "Copyright 2020 Jane Doe" +``` + + +## Project description + +The decription provides a short summary on the project. +It should be plain text and not using any markup formatting. + +*Example:* + +```toml +description = "A short summary on this project" +``` + + +## Project categories + +The project can be associated with different categories. + +*Example:* + +```toml +categories = ["io"] +``` + + +## Project keywords + +The keywords field is an array of strings describing the project. + +*Example:* + +```toml +keywords = ["hdf5", "mpi"] +``` + + +## Project homepage + +URL to the webpage of the project. + +*Example:* + +```toml +homepage = "https://stdlib.fortran-lang.org" +``` + + +## Project targets + +Every fpm project can define library, executable and test targets. +Library targets are exported and useable for other projects. + + +### Library configuration + +Defines the exported library target of the project. +A library is generated if the source directory is found in a project. +The default source directory is ``src`` but can be modifed in the *library* section using the *source-dir* entry. +Paths for the source directory are given relative to the project root and use ``/`` as path separator on all platforms. + +*Example:* + +```toml +[library] +source-dir = "lib" +``` + +#### Custom build script + +> Supported in Bootstrap fpm only + +Projects with custom build scripts can specify those in the *build-script* entry. +The custom build script will be executeted when the library build step is reached. + +*Example:* + +```toml +[library] +build-script = "build.sh" +``` + +Build scripts written in ``make`` are automatically detected and executed with ``make`` + +```toml +[library] +build-script = "Makefile" +``` + + +### Executable targets + +Executable targets are Fortran programs defined as *executable* sections. +If no executable section is specified the ``app`` directory is searched for program definitions. +For explicitly specified executables the *name* entry must always be specified. +The source directory for each executable can be adjusted in the *source-dir* entry. +Paths for the source directory are given relative to the project root and use ``/`` as path separator on all platforms. +The source file containing the program body can be specified in the *main* entry. + +Executables can have their own dependencies. +See [specifying dependencies](#specifying-dependencies) for more details. + +> Dependencies supported in Bootstrap fpm only + +*Example:* + +```toml +[[ executable ]] +name = "app-name" +source-dir = "prog" +main = "program.f90" + +[[ executable ]] +name = "app-tool" +[executable.dependencies] +helloff = { git = "https://gitlab.com/everythingfunctional/helloff.git" } +``` + +Specifying many separate executables can be done by using inline tables for brevity instead + +```toml +executable = [ + { name = "a-prog" }, + { name = "app-tool", source-dir = "tool" }, +] +``` + + +### Test targets + +Test targets are Fortran programs defined as *test* sections. +They follow similar rules as the executable targets. +If no test section is specified the ``test`` directory is searched for program definitions. +For explicitly specified tests the *name* entry must always be specified. +The source directory for each test can be adjusted in the *source-dir* entry. +Paths for the source directory are given relative to the project root and use ``/`` as path separator on all platforms. +The source file containing the program body can be specified in the *main* entry. + +Tests can have their own dependencies. +See [specifying dependencies](#specifying-dependencies) for more details. + +> Dependencies supported in Bootstrap fpm only + +*Example:* + +```toml +[[ test ]] +name = "test-name" +source-dir = "testing" +main = "tester.F90" + +[[ test ]] +name = "tester" +[test.dependencies] +helloff = { git = "https://gitlab.com/everythingfunctional/helloff.git" } +``` + + +## Automatic target discovery + +> Supported in Fortran fpm only + +Executables and test can be discovered automatically in their default directories. +The automatic discovery recursively searches the ``app`` and ``test`` directories for ``program`` definitions and declares them as executable and test targets, respectively. +The automatic discovery is enabled by default. + +To disable the automatic discovery of targets set the *auto-executables* and *auto-tests* entry to *false*. + +```toml +[build] +auto-executables = false +auto-tests = false +``` + + +## Specifying dependencies + +Dependencies can be declared in the *dependencies* table in the manifest root or the [*executable*](#executable-targets) or [*test*](#test-targets) sections. +When declared in the manifest root the dependencies are exported with the project. + + +### Local dependencies + +To declare local dependencies use the *path* entry. + +```toml +[dependencies] +my-utils = { path = "utils" } +``` + +Local dependency paths are given relative to the project root and use ``/`` as path separator on all platforms. + + +### Dependencies from version control systems + +Dependencies can be specified by the projects git repository. + +```toml +[dependencies] +toml-f = { git = "https://github.com/toml-f/toml-f" } +``` + +To use a specific upstream branch declare the *branch* name with + +```toml +[dependencies] +toml-f = { git = "https://github.com/toml-f/toml-f", branch = "master" } +``` + +Alternatively, reference tags by using the *tag* entry + +```toml +[dependencies] +toml-f = { git = "https://github.com/toml-f/toml-f", tag = "v0.2.1" } +``` + +To pin a specific revision specify the commit hash in the *rev* entry + +```toml +[dependencies] +toml-f = { git = "https://github.com/toml-f/toml-f", rev = "2f5eaba" } +``` + +For more verbose layout use normal tables rather than inline tables to specify dependencies + +```toml +[dependencies] +[dependencies.toml-f] +git = "https://github.com/toml-f/toml-f" +rev = "2f5eaba864ff630ba0c3791126a3f811b6e437f3" +``` + +### Development dependencies + +Development dependencies allow to declare *dev-dependencies* in the manifest root, which are available to all tests but not exported with the project. |