diff options
Diffstat (limited to 'bootstrap/src')
-rw-r--r-- | bootstrap/src/Fpm.hs | 342 |
1 files changed, 240 insertions, 102 deletions
diff --git a/bootstrap/src/Fpm.hs b/bootstrap/src/Fpm.hs index 115b63e..cfb67df 100644 --- a/bootstrap/src/Fpm.hs +++ b/bootstrap/src/Fpm.hs @@ -3,7 +3,6 @@ module Fpm ( Arguments(..) - , Command(..) , getArguments , start ) @@ -17,6 +16,7 @@ import Control.Monad.Extra ( concatMapM , forM_ , when ) +import Data.Hashable ( hash ) import Data.List ( isSuffixOf , find , nub @@ -30,6 +30,7 @@ import Development.Shake ( FilePattern import Development.Shake.FilePath ( (</>) , (<.>) , exe + , splitDirectories ) import Options.Applicative ( Parser , (<**>) @@ -42,8 +43,12 @@ import Options.Applicative ( Parser , helper , info , long + , many , metavar + , optional , progDesc + , short + , showDefault , strArgument , strOption , subparser @@ -59,7 +64,7 @@ import System.Directory ( createDirectory import System.Exit ( ExitCode(..) , exitWith ) -import System.Process ( runCommand +import System.Process ( readProcess , system ) import Toml ( TomlCodec @@ -67,7 +72,32 @@ import Toml ( TomlCodec ) import qualified Toml -data Arguments = Arguments { command' :: Command, release :: Bool, commandArguments :: String } +data Arguments = + New + { newName :: String + , newWithExecutable :: Bool + , newWithTest :: Bool + , newWithLib :: Bool + } + | Build + { buildRelease :: Bool + , buildCompiler :: FilePath + , buildFlags :: [String] + } + | Run + { runRelease :: Bool + , runCompiler :: FilePath + , runFlags :: [String] + , runTarget :: Maybe String + , runArgs :: Maybe String + } + | Test + { testRelease :: Bool + , testCompiler :: FilePath + , testFlags :: [String] + , testTarget :: Maybe String + , testArgs :: Maybe String + } data TomlSettings = TomlSettings { tomlSettingsProjectName :: String @@ -107,8 +137,6 @@ data GitRef = Tag String | Branch String | Commit String deriving Show data PathVersionSpec = PathVersionSpec { pathVersionSpecPath :: String } deriving Show -data Command = Run String | Test String | Build | New String Bool Bool - data DependencyTree = Dependency { dependencyName :: String , dependencyPath :: FilePath @@ -118,22 +146,22 @@ data DependencyTree = Dependency { } start :: Arguments -> IO () -start args = case command' args of - New projectName withExecutable withTest -> - createNewProject projectName withExecutable withTest +start args = case args of + New { newName = name, newWithExecutable = withExecutable, newWithTest = withTest, newWithLib = withLib } + -> createNewProject name withExecutable withTest withLib _ -> do fpmContents <- TIO.readFile "fpm.toml" let tomlSettings = Toml.decode settingsCodec fpmContents case tomlSettings of Left err -> print err Right tomlSettings' -> do - appSettings <- toml2AppSettings tomlSettings' (release args) + appSettings <- toml2AppSettings tomlSettings' args app args appSettings app :: Arguments -> AppSettings -> IO () -app args settings = case command' args of - Build -> build settings - Run whichOne -> do +app args settings = case args of + Build{} -> build settings + Run { runTarget = whichOne, runArgs = runArgs } -> do build settings let buildPrefix = appSettingsBuildPrefix settings let @@ -148,10 +176,16 @@ app args settings = case command' args of case canonicalExecutables of [] -> putStrLn "No Executables Found" _ -> case whichOne of - "" -> do + Nothing -> do exitCodes <- mapM system - (map (++ " " ++ commandArguments args) canonicalExecutables) + (map + (++ case runArgs of + Nothing -> "" + Just theArgs -> " " ++ theArgs + ) + canonicalExecutables + ) forM_ exitCodes (\exitCode -> when @@ -161,13 +195,15 @@ app args settings = case command' args of ) (exitWith exitCode) ) - name -> do + Just name -> do case find (name `isSuffixOf`) canonicalExecutables of Nothing -> putStrLn "Executable Not Found" Just specified -> do - exitCode <- system (specified ++ " " ++ (commandArguments args)) + exitCode <- case runArgs of + Nothing -> system specified + Just theArgs -> system (specified ++ " " ++ theArgs) exitWith exitCode - Test whichOne -> do + Test { testTarget = whichOne, testArgs = testArgs } -> do build settings let buildPrefix = appSettingsBuildPrefix settings let @@ -182,10 +218,16 @@ app args settings = case command' args of case canonicalExecutables of [] -> putStrLn "No Tests Found" _ -> case whichOne of - "" -> do + Nothing -> do exitCodes <- mapM system - (map (++ " " ++ commandArguments args) canonicalExecutables) + (map + (++ case testArgs of + Nothing -> "" + Just theArgs -> " " ++ theArgs + ) + canonicalExecutables + ) forM_ exitCodes (\exitCode -> when @@ -195,12 +237,15 @@ app args settings = case command' args of ) (exitWith exitCode) ) - name -> do + Just name -> do case find (name `isSuffixOf`) canonicalExecutables of Nothing -> putStrLn "Test Not Found" Just specified -> do - exitCode <- system (specified ++ " " ++ (commandArguments args)) + exitCode <- case testArgs of + Nothing -> system specified + Just theArgs -> system (specified ++ " " ++ theArgs) exitWith exitCode + _ -> putStrLn "Shouldn't be able to get here" build :: AppSettings -> IO () build settings = do @@ -302,42 +347,110 @@ getArguments = execParser ) arguments :: Parser Arguments -arguments = - Arguments - <$> subparser - ( command "run" (info runArguments (progDesc "Run the executable")) - <> command "test" (info testArguments (progDesc "Run the tests")) - <> command "build" - (info buildArguments (progDesc "Build the executable")) - <> command - "new" - (info newArguments - (progDesc "Create a new project in a new directory") - ) +arguments = subparser + ( command + "new" + (info (newArguments <**> helper) + (progDesc "Create a new project in a new directory") + ) + <> command + "build" + (info (buildArguments <**> helper) (progDesc "Build the project")) + <> command + "run" + (info (runArguments <**> helper) (progDesc "Run the executable(s)")) + <> command "test" + (info (testArguments <**> helper) (progDesc "Run the test(s)")) + ) + +newArguments :: Parser Arguments +newArguments = + New + <$> strArgument + ( metavar "NAME" + <> help "Name of new project (must be a valid Fortran identifier)" + ) + <*> switch (long "app" <> help "Include an executable") + <*> switch (long "test" <> help "Include a test") + <*> switch (long "lib" <> help "Include a library") + +buildArguments :: Parser Arguments +buildArguments = + Build + <$> switch + ( long "release" + <> help "Build with optimizations instead of debugging" ) - <*> switch (long "release" <> help "Build in release mode") <*> strOption - (long "args" <> metavar "ARGS" <> value "" <> help - "Arguments to pass to executables/tests" + ( long "compiler" + <> metavar "COMPILER" + <> value "gfortran" + <> help "specify the compiler to use" + <> showDefault + ) + <*> many + (strOption + ( long "flag" + <> metavar "FLAG" + <> help + "specify an addional argument to pass to the compiler (can appear multiple times)" + ) ) -runArguments :: Parser Command -runArguments = Run <$> strArgument - (metavar "EXE" <> value "" <> help "Which executable to run") +runArguments :: Parser Arguments +runArguments = + Run + <$> switch + ( long "release" + <> help "Build with optimizations instead of debugging" + ) + <*> strOption + ( long "compiler" + <> metavar "COMPILER" + <> value "gfortran" + <> help "specify the compiler to use" + <> showDefault + ) + <*> many + (strOption + ( long "flag" + <> metavar "FLAG" + <> help + "specify an addional argument to pass to the compiler (can appear multiple times)" + ) + ) + <*> optional + (strArgument + (metavar "TARGET" <> help "Name of the executable to run") + ) + <*> optional + (strArgument (metavar "ARGS" <> help "Arguments to the executable")) -testArguments :: Parser Command +testArguments :: Parser Arguments testArguments = - Test <$> strArgument (metavar "TEST" <> value "" <> help "Which test to run") - -buildArguments :: Parser Command -buildArguments = pure Build - -newArguments :: Parser Command -newArguments = - New - <$> strArgument (metavar "NAME" <> help "Name of new project") - <*> switch (long "with-executable" <> help "Include an executable") - <*> switch (long "with-test" <> help "Include a test") + Test + <$> switch + ( long "release" + <> help "Build with optimizations instead of debugging" + ) + <*> strOption + ( long "compiler" + <> metavar "COMPILER" + <> value "gfortran" + <> help "specify the compiler to use" + <> showDefault + ) + <*> many + (strOption + ( long "flag" + <> metavar "FLAG" + <> help + "specify an addional argument to pass to the compiler (can appear multiple times)" + ) + ) + <*> optional + (strArgument (metavar "TARGET" <> help "Name of the test to run")) + <*> optional (strArgument (metavar "ARGS" <> help "Arguments to the test")) getDirectoriesFiles :: [FilePath] -> [FilePattern] -> IO [FilePath] getDirectoriesFiles dirs exts = getDirectoryFilesIO "" newPatterns @@ -437,51 +550,66 @@ pathVersionCodec :: Toml.TomlCodec PathVersionSpec pathVersionCodec = PathVersionSpec <$> Toml.string "path" .= pathVersionSpecPath -toml2AppSettings :: TomlSettings -> Bool -> IO AppSettings -toml2AppSettings tomlSettings release = do +toml2AppSettings :: TomlSettings -> Arguments -> IO AppSettings +toml2AppSettings tomlSettings args = do + let release = case args of + Build { buildRelease = r } -> r + Run { runRelease = r } -> r + Test { testRelease = r } -> r let projectName = tomlSettingsProjectName tomlSettings - let compiler = "gfortran" + let compiler = case args of + Build { buildCompiler = c } -> c + Run { runCompiler = c } -> c + Test { testCompiler = c } -> c + let specifiedFlags = case args of + Build { buildFlags = f } -> f + Run { runFlags = f } -> f + Test { testFlags = f } -> f librarySettings <- getLibrarySettings $ tomlSettingsLibrary tomlSettings executableSettings <- getExecutableSettings (tomlSettingsExecutables tomlSettings) projectName testSettings <- getTestSettings $ tomlSettingsTests tomlSettings - buildPrefix <- makeBuildPrefix compiler release + let flags = if compiler == "gfortran" + then 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" + ] + 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 = 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" - ] - , appSettingsLibrary = librarySettings - , appSettingsExecutables = executableSettings - , appSettingsTests = testSettings - , appSettingsDependencies = dependencies - , appSettingsDevDependencies = devDependencies - } + return AppSettings { appSettingsCompiler = compiler + , appSettingsProjectName = projectName + , appSettingsBuildPrefix = buildPrefix + , appSettingsFlags = flags + , appSettingsLibrary = librarySettings + , appSettingsExecutables = executableSettings + , appSettingsTests = testSettings + , appSettingsDependencies = dependencies + , appSettingsDevDependencies = devDependencies + } getLibrarySettings :: Maybe Library -> IO (Maybe Library) getLibrarySettings maybeSettings = case maybeSettings of @@ -531,11 +659,21 @@ getTestSettings [] = do else return [] getTestSettings tests = return tests -makeBuildPrefix :: String -> Bool -> IO String -makeBuildPrefix compiler release = +makeBuildPrefix :: FilePath -> [String] -> IO FilePath +makeBuildPrefix compiler flags = do -- TODO Figure out what other info should be part of this -- Probably version, and make sure to not include path to the compiler - return $ "build" </> compiler ++ "_" ++ if release then "release" else "debug" + versionInfo <- readProcess compiler ["--version"] [] + let compilerName = last (splitDirectories compiler) + let versionHash = hash versionInfo + let flagsHash = hash flags + return + $ "build" + </> compilerName + ++ "_" + ++ show versionHash + ++ "_" + ++ show flagsHash {- Fetching the dependencies is done on a sort of breadth first approach. All @@ -682,19 +820,20 @@ buildDependency buildPrefix compiler flags (Dependency name path sourcePath mBui (map fst transitiveDependencies) return $ (buildPath, thisArchive) : transitiveDependencies -createNewProject :: String -> Bool -> Bool -> IO () -createNewProject projectName withExecutable withTest = do +createNewProject :: String -> Bool -> Bool -> Bool -> IO () +createNewProject projectName withExecutable withTest withLib = do createDirectory projectName writeFile (projectName </> "fpm.toml") (templateFpmToml projectName) writeFile (projectName </> "README.md") (templateReadme projectName) writeFile (projectName </> ".gitignore") "build/*\n" - createDirectory (projectName </> "src") - writeFile (projectName </> "src" </> projectName <.> "f90") - (templateModule projectName) + when withLib $ do + createDirectory (projectName </> "src") + writeFile (projectName </> "src" </> projectName <.> "f90") + (templateModule projectName) when withExecutable $ do createDirectory (projectName </> "app") writeFile (projectName </> "app" </> "main.f90") - (templateProgram projectName) + (templateProgram projectName withLib) when withTest $ do createDirectory (projectName </> "test") writeFile (projectName </> "test" </> "main.f90") templateTest @@ -736,12 +875,11 @@ templateReadme :: String -> String templateReadme projectName = "# " ++ projectName ++ "\n" ++ "\n" ++ "My cool new project!\n" -templateProgram :: String -> String -templateProgram projectName = +templateProgram :: String -> Bool -> String +templateProgram projectName withLib = "program main\n" - ++ " use " - ++ projectName - ++ ", only: say_hello\n" + ++ (if withLib then " use " ++ projectName ++ ", only: say_hello\n" else "" + ) ++ "\n" ++ " implicit none\n" ++ "\n" |