-
Notifications
You must be signed in to change notification settings - Fork 0
/
Main.hs
215 lines (200 loc) · 9.06 KB
/
Main.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
{-# LANGUAGE OverloadedStrings #-}
{-|
Module : Main
Description : Entry-point for the emperor package manager
Copyright : (c) Edward Jones, 2019
License : GPL-3
Maintainer : Edward Jones
Stability : experimental
Portability : POSIX
Language : Haskell2010
This is the emperor package manager, responsible for fetching, compiling,
installing and resolving dependencies. A list of locations where emperor files
may be found can be obtained through the various command-line options.
The most important output---that which resolves dependencies---is given as a set
of GCC flags.
-}
module Main (main, getPackageLocationAction, addDependencyAction, installDependenciesAction, cFlagsAction, libsAction) where
import Args (Args, addDependency, bareProject, binaryInstallLocation, cFlags, dataInstallLocation,
entryPoint, getPackageLocation, includeLocation, input, installDependencies,
languageHeaderLocation, libraryInstallLocation, libs, parseArgv,
updatePackageRepo)
import Control.Monad (unless)
import Data.Aeson (encode)
import Data.ByteString.Lazy (writeFile)
import Defaults (getDefaultDependencies)
import Install (doInstallDependencies, ensurePackageRepoExists, installPackageDependencies)
import Locations (getBinLoc, getIncludeInstallLoc, getPackageInstallLoc)
import Package (Dependency(..), Package(dependencies), getPackageMeta, hasDependency,
insertDependency, name, parseDependencyString, version)
import PackageRepo (getMostRecentVersion)
import Prelude hiding (writeFile)
import System.Environment (getProgName)
import System.Exit (exitFailure)
import System.IO (hPutStrLn, stderr)
-- | Entry point, parses commandline arguments and outputs as necessary
main :: IO ()
main = do
args <- parseArgv
-- Go through the actions, perform each if necessary
if (not . null) (getPackageLocation args) then
getPackageLocationAction args
else if (not . null) (addDependency args) then
addDependencyAction args
else if installDependencies args then
installDependenciesAction args
else if cFlags args then
cFlagsAction args
else if libs args then
libsAction args
else if binaryInstallLocation args then do
binLoc <- getBinLoc
putStrLn binLoc
else if libraryInstallLocation args then do
libLoc <- getPackageInstallLoc
putStrLn libLoc
else if dataInstallLocation args then do
dataLoc <- getPackageInstallLoc
putStrLn dataLoc
else if includeLocation args then do
includeLoc <- getPackageInstallLoc
putStrLn includeLoc
else if updatePackageRepo args then
ensurePackageRepoExists args
else if languageHeaderLocation args then do
includeInstallLoc <- getIncludeInstallLoc
putStrLn includeInstallLoc
else do
progname <- getProgName
hPutStrLn stderr $ "Please specify a command flag\nTry '" ++ progname ++ " -h' for more information"
exitFailure
-- | Pring the location on disc of a package specified by a @-g@ command
getPackageLocationAction :: Args -> IO ()
getPackageLocationAction args = do
let pn = getPackageLocation args
r <- getPackageMeta args
case r of
Nothing -> do
vr <- getMostRecentVersion pn
case vr of
Left m -> do
hPutStrLn stderr m
exitFailure
Right v ->
getPackageLocationAction' Dependency { dependencyName = pn, dependencyVersion = v }
Just p -> do
let ds = filter (\d -> name d == pn) $ dependencies p
if null ds then do
hPutStrLn stderr $ show pn ++ " is not a documented dependency of this project, please add it to the manifest with 'emperor-setup -a " ++ show pn ++ "'"
exitFailure
else if length ds >= 2 then do
hPutStrLn stderr $ "There are multiple named dependencies named " ++ show pn ++ ", please reduce this down to one"
exitFailure
else do
let d = head ds
getPackageLocationAction' d
where
getPackageLocationAction' :: Dependency -> IO ()
getPackageLocationAction' d = do
packageInstallLoc <- getPackageInstallLoc
putStrLn $ packageInstallLoc ++ name d ++ '/' : version d ++ "/"
-- | Add a package specified by a @-a@ option to the list of dependencies in the current package
addDependencyAction :: Args -> IO ()
addDependencyAction args = do
dep <- case parseDependencyString $ addDependency args of
(d, Nothing) -> do
vr <- getMostRecentVersion d
case vr of
Left m -> do
hPutStrLn stderr m
exitFailure
Right v -> return Dependency { dependencyName = d, dependencyVersion = v }
(d, Just v) -> return Dependency { dependencyName = d, dependencyVersion = v }
r <- getPackageMeta args
case r of
Nothing -> do
hPutStrLn stderr "Cannot add dependencies with no manifest.json"
exitFailure
Just p ->
if p `hasDependency` dep then do
hPutStrLn stderr $ "This project already depends on " ++ show dep
exitFailure
else do
putStrLn $ "Adding dependency " ++ show dep
let p' = insertDependency p dep
writePackageMeta args p'
installPackageDependencies args p'
-- | Install the dependencies specified in the manifest
installDependenciesAction :: Args -> IO ()
installDependenciesAction = doInstallDependencies
-- | Output the GCC flags required to compile the package
cFlagsAction :: Args -> IO ()
cFlagsAction args = do
r <- getPackageMeta args
includeInstallLoc <- if (not . bareProject) args then getIncludeInstallLoc else return ""
packageInstallLoc <- if (not . bareProject) args then getPackageInstallLoc else return ""
let formattedIncludeInstallLoc = if (not . bareProject) args then " -I" ++ includeInstallLoc else ""
let warningOpts = "-Wall -Wextra -Wpedantic -Werror -pedantic-errors -Wno-unused-variable -Wno-unused-parameter"
let optimisationOpts = "-O3"
let codeGenerationOpts = "-g -rdynamic -fno-exceptions"
let standardOptions = warningOpts ++ ' ' : optimisationOpts ++ ' ' : codeGenerationOpts ++ (if entryPoint args then "" else " -c") ++ " -I." ++ formattedIncludeInstallLoc
lr <- case r of
Nothing -> do
sr <- getDefaultDependencies
case sr of
Right ls -> return $ Right ls
Left m -> return $ Left m
Just p -> return . Right $ dependencies p
case lr of
Right ls -> do
let libraryLocations = unwords $ (\d -> "-L" ++ packageInstallLoc ++ name d ++ "/" ++ version d ++ "/") . sanitise <$> ls
let includeLocations = unwords $ (\d -> "-I" ++ packageInstallLoc ++ name d ++ "/" ++ version d ++ "/") . sanitise <$> ls
putStrLn $ if (not . bareProject) args then
standardOptions ++ ' ' : libraryLocations ++ ' ' : includeLocations
else
standardOptions
Left m -> do
hPutStrLn stderr m
exitFailure
-- | Make a dependency safe to use on the commandline
sanitise :: Dependency -> Dependency
sanitise d = Dependency { dependencyName = (sanitiseShellString . name) d, dependencyVersion = (sanitiseShellString . version) d }
-- | Output the libraries required by the package
libsAction :: Args -> IO ()
libsAction args =
unless (bareProject args) $ do
r <- getPackageMeta args
dsr <- case r of
Nothing -> getDefaultDependencies
Just p -> return . Right $ dependencies p
case dsr of
Left m -> do
hPutStrLn stderr m
exitFailure
Right ds -> putStrLn . unwords $ (\d -> "-l" ++ (sanitiseShellString . name) d) <$> ds
-- | Output a JSON representation of the package
writePackageMeta :: Args -> Package -> IO ()
writePackageMeta args p = do
let c = encode p
if (not . null) (input args) then
if input args == "-" then
print c
else
writeFile (input args) c
else
writeFile "./manifest.json" c
sanitiseShellString :: String -> String
sanitiseShellString = (replace <$>)
where
replace :: Char -> Char
replace '\'' = '_'
replace '"' = '_'
replace ';' = '_'
replace '#' = '_'
replace '*' = '_'
replace '!' = '_'
replace '~' = '_'
replace '|' = '_'
replace '>' = '_'
replace '<' = '_'
replace x = x