-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSetup.hs
More file actions
233 lines (203 loc) · 7.72 KB
/
Setup.hs
File metadata and controls
233 lines (203 loc) · 7.72 KB
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
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE ScopedTypeVariables #-}
import Data.Functor ( (<&>) )
import Data.List ( intercalate )
import Data.Maybe ( catMaybes )
import Distribution.Simple
import Distribution.Simple.LocalBuildInfo
import Distribution.Simple.Setup
import Distribution.Simple.BuildPaths (exeExtension)
import Distribution.PackageDescription
import Distribution.System ( buildPlatform )
import System.FilePath
import System.Directory (doesFileExist, makeAbsolute, removeFile)
import System.Environment (getEnvironment)
import System.Process
import System.Exit
import System.IO
import System.IO.Error (isDoesNotExistError)
import Control.Monad
import Control.Exception
main :: IO ()
main = defaultMainWithHooks userhooks
userhooks :: UserHooks
userhooks = simpleUserHooks
{ copyHook = copyHook'
, instHook = instHook'
}
-- Install and copy hooks are default, but amended with .agdai files in data-files.
instHook' :: PackageDescription -> LocalBuildInfo -> UserHooks -> InstallFlags -> IO ()
instHook' pd lbi hooks flags = instHook simpleUserHooks pd' lbi hooks flags where
pd' = pd { dataFiles = concatMap (expandAgdaExt pd) $ dataFiles pd }
-- Andreas, 2020-04-25, issue #4569: defer 'generateInterface' until after
-- the library has been copied to a destination where it can be found.
-- @cabal build@ will likely no longer produce the .agdai files, but @cabal install@ does.
copyHook' :: PackageDescription -> LocalBuildInfo -> UserHooks -> CopyFlags -> IO ()
copyHook' pd lbi hooks flags = do
-- Copy library and executable etc.
copyHook simpleUserHooks pd lbi hooks flags
if wantInterfaces flags && not (skipInterfaces lbi) then do
-- Generate .agdai files.
success <- generateInterfaces pd lbi
-- Copy again, now including the .agdai files.
when success $ copyHook simpleUserHooks pd' lbi hooks flags
else
putStrLn "Skipping generation of Agda core library interface files"
where
pd' = pd
{ dataFiles = concatMap (expandAgdaExt pd) $ dataFiles pd
-- Andreas, 2020-04-25, issue #4569:
-- I tried clearing some fields to avoid copying again.
-- However, cabal does not like me messing with the PackageDescription.
-- Clearing @library@ or @executables@ leads to internal errors.
-- Thus, we just copy things again. Not a terrible problem.
-- , library = Nothing
-- , executables = []
-- , subLibraries = []
-- , foreignLibs = []
-- , testSuites = []
-- , benchmarks = []
-- , extraSrcFiles = []
-- , extraTmpFiles = []
-- , extraDocFiles = []
}
-- We only want to write interfaces if installing the executable.
-- If we're installing *just* the library, the interface files are not needed
-- and, most importantly, the executable will not be available to be run (cabal#10235)
wantInterfaces :: CopyFlags -> Bool
wantInterfaces _flags = do
#if MIN_VERSION_Cabal(3,11,0)
any isAgdaExe (copyArgs _flags)
where
isAgdaExe "exe:agda" = True
isAgdaExe _ = False
#else
True
#endif
-- Used to add .agdai files to data-files
expandAgdaExt :: PackageDescription -> FilePath -> [FilePath]
expandAgdaExt pd = \ fp ->
-- N.B. using lambda here so that @expandAgdaExt pd@ can be partially evaluated.
if takeExtension fp == ".agda" then [ fp, iFile fp ] else [ fp ]
where
iFile = toIFile pd
version :: PackageDescription -> String
version = intercalate "." . map show . versionNumbers . pkgVersion . package
-- | This returns @lib/prim@.
--
projectRoot :: PackageDescription -> FilePath
projectRoot pd = takeDirectory agdaLibFile
where
[agdaLibFile] = filter ((".agda-lib" ==) . takeExtension) $ dataFiles pd
-- | Turns e.g. @lib/prim/Agda/Primitive.agda@
-- into @lib/prim/_build/2.7.0/agda/Agda/Primitive.agdai@.
--
-- An absolute path will be returned unchanged.
toIFile ::
PackageDescription
-> FilePath -- ^ Should be a relative path.
-> FilePath -- ^ Then this is also a relative path.
toIFile pd = (buildDir </>) . fileName
where
root = projectRoot pd
-- e.g. root = "lib/prim"
buildDir = root </> "_build" </> version pd </> "agda"
-- e.g. buildDir = "lib/prim/_build/2.7.0/agda"
fileName file = makeRelative root $ replaceExtension file ".agdai"
-- e.g. fileName "lib/prim/Agda/Primitive.agda" = "Agda/Primitive.agdai"
-- Andreas, 2019-10-21, issue #4151:
-- skip the generation of interface files with program suffix "-quicker"
skipInterfaces :: LocalBuildInfo -> Bool
skipInterfaces lbi = fromPathTemplate (progSuffix lbi) == "-quicker"
-- | Returns 'True' if call to Agda executes without error.
--
generateInterfaces :: PackageDescription -> LocalBuildInfo -> IO Bool
generateInterfaces pd lbi = do
putStrLn "Generating Agda core library interface files..."
-- for debugging, these are examples how you can inspect the flags...
-- print $ flagAssignment lbi
-- print $ fromPathTemplate $ progSuffix lbi
-- then...
let bdir = buildDir lbi
agda = bdir </> "agda" </> "agda" <.> agdaExeExtension
-- We should be in the current directory root of the cabal package
-- and data-files reside in src/data relative to this.
--
ddir <- makeAbsolute $ "src" </> "data"
-- The Agda.Primitive* and Agda.Builtin* modules.
let builtins = filter ((== ".agda") . takeExtension) (dataFiles pd)
-- The absolute filenames of their interfaces.
let interfaces = map ((ddir </>) . toIFile pd) builtins
-- Remove all existing .agdai files.
forM_ interfaces $ \ fp -> removeFile fp `catch` \ e ->
unless (isDoesNotExistError e) $ throwIO e
-- Type-check all builtin modules (in a single Agda session to take
-- advantage of caching).
let agdaDirEnvVar = "Agda_datadir"
let agdaArgs =
[ "--interaction"
, "--interaction-exit-on-error"
, "-Werror"
, "-v0"
]
let loadBuiltinCmds =
[ cmd ("Cmd_load_no_metas " ++ f)
-- Fail if any meta-variable is unsolved.
| b <- builtins
, let f = show (ddir </> b)
cmd c = "IOTCM " ++ f ++ " None Indirect (" ++ c ++ ")"
]
let callLines = concat
[ [ unwords $ concat
[ [ concat [ agdaDirEnvVar, "=", ddir ] ]
, [ agda ]
, agdaArgs
, [ "<<EOF" ]
]
]
, loadBuiltinCmds
, [ "EOF" ]
]
let onIOError (e :: IOException) = False <$ do
warn $ concat
[ [ "*** Could not generate Agda library interface files."
, "*** Reason:"
, show e
, "*** The attempted call to Agda was:"
]
, callLines
]
env <- getEnvironment
handle onIOError $ do
-- Generate interface files via a call to Agda.
readCreateProcess
(proc agda agdaArgs)
{ delegate_ctlc = True
-- Make Agda look for data files in a
-- certain place.
, env = Just ((agdaDirEnvVar, ddir) : env)
}
(unlines loadBuiltinCmds)
-- Check whether all interface files have been generated.
missing <- catMaybes <$> forM interfaces \ f ->
doesFileExist f <&> \case
True -> Nothing
False -> Just f
-- Warn if not all interface files have been generated, but don't crash.
-- This might help with issue #7455.
let success = null missing
unless success $ warn $ concat
[ [ "*** Agda failed to generate the following library interface files:" ]
, missing
]
return success
warn :: [String] -> IO ()
warn msgs = putStr $ unlines $ concat
[ [ "*** Warning!" ]
, msgs
, [ "*** Ignoring error, continuing installation..." ]
]
agdaExeExtension :: String
agdaExeExtension = exeExtension buildPlatform