1 {-# LANGUAGE FlexibleContexts #-}
2 {-# LANGUAGE NondecreasingIndentation #-}
3 {-# LANGUAGE OverloadedStrings #-}
4 {-# LANGUAGE RankNTypes #-}
6 -----------------------------------------------------------------------------
9 -- Module : Distribution.Simple.Program.Ar
10 -- Copyright : Duncan Coutts 2009
12 -- Maintainer : cabal-devel@haskell.org
13 -- Portability : portable
15 -- This module provides an library interface to the @ar@ program.
16 module Distribution
.Simple
.Program
.Ar
18 , multiStageProgramInvocation
21 import Distribution
.Compat
.Prelude
24 import qualified Data
.ByteString
as BS
25 import qualified Data
.ByteString
.Char8
as BS8
26 import Distribution
.Compat
.CopyFile
(filesEqual
)
27 import Distribution
.Simple
.Compiler
(arDashLSupported
, arResponseFilesSupported
)
28 import Distribution
.Simple
.Flag
31 import Distribution
.Simple
.LocalBuildInfo
(LocalBuildInfo
(..))
32 import Distribution
.Simple
.Program
37 import Distribution
.Simple
.Program
.ResponseFile
40 import Distribution
.Simple
.Program
.Run
41 ( multiStageProgramInvocation
43 , runProgramInvocation
45 import Distribution
.Simple
.Setup
.Config
46 ( configUseResponseFiles
48 import Distribution
.Simple
.Utils
49 ( defaultTempFileOptions
53 import Distribution
.System
58 import Distribution
.Verbosity
63 import System
.Directory
(doesFileExist, renameFile)
64 import System
.FilePath (splitFileName
, (</>))
67 , IOMode (ReadWriteMode
)
68 , SeekMode (AbsoluteSeek
)
74 -- | Call @ar@ to create a library archive from a bunch of object files.
81 createArLibArchive verbosity lbi targetPath files
= do
82 (ar
, _
) <- requireProgram verbosity arProgram progDb
84 let (targetDir
, targetName
) = splitFileName targetPath
85 withTempDirectory verbosity targetDir
"objs" $ \tmpDir
-> do
86 let tmpPath
= tmpDir
</> targetName
88 -- The args to use with "ar" are actually rather subtle and system-dependent.
89 -- In particular we have the following issues:
91 -- -- On OS X, "ar q" does not make an archive index. Archives with no
92 -- index cannot be used.
94 -- -- GNU "ar r" will not let us add duplicate objects, only "ar q" lets us
95 -- do that. We have duplicates because of modules like "A.M" and "B.M"
96 -- both make an object file "M.o" and ar does not consider the directory.
98 -- -- llvm-ar, which GHC >=9.4 uses on Windows, supports a "L" modifier
99 -- in "q" mode which compels the archiver to add the members of an input
100 -- archive to the output, rather than the archive itself. This is
101 -- necessary as GHC may produce .o files that are actually archives. See
102 -- https://gitlab.haskell.org/ghc/ghc/-/issues/21068.
104 -- Our solution is to use "ar r" in the simple case when one call is enough.
105 -- When we need to call ar multiple times we use "ar q" and for the last
106 -- call on OSX we use "ar qs" so that it'll make the index.
108 let simpleArgs
= case hostOS
of
110 _ | dashLSupported
-> ["-qL"]
114 finalArgs
= case hostOS
of
116 _ | dashLSupported
-> ["-qL"]
119 extraArgs
= verbosityOpts verbosity
++ [tmpPath
]
121 simple
= programInvocation ar
(simpleArgs
++ extraArgs
)
122 initial
= programInvocation ar
(initialArgs
++ extraArgs
)
124 final
= programInvocation ar
(finalArgs
++ extraArgs
)
126 oldVersionManualOverride
=
127 fromFlagOrDefault
False $ configUseResponseFiles
$ configFlags lbi
128 responseArgumentsNotSupported
=
129 not (arResponseFilesSupported
(compiler lbi
))
131 arDashLSupported
(compiler lbi
)
133 invokeWithResponesFile
:: FilePath -> ProgramInvocation
134 invokeWithResponesFile atFile
=
135 programInvocation ar
$
136 simpleArgs
++ extraArgs
++ ['@' : atFile
]
138 if oldVersionManualOverride || responseArgumentsNotSupported
141 [ runProgramInvocation verbosity inv
143 multiStageProgramInvocation
145 (initial
, middle
, final
)
148 else withResponseFile verbosity defaultTempFileOptions tmpDir
"ar.rsp" Nothing files
$
149 \path
-> runProgramInvocation verbosity
$ invokeWithResponesFile path
152 ( hostArch
== Arm
-- See #1537
155 $ wipeMetadata verbosity tmpPath
-- AIX uses its own "ar" format variant
156 equal
<- filesEqual tmpPath targetPath
157 unless equal
$ renameFile tmpPath targetPath
159 progDb
= withPrograms lbi
160 Platform hostArch hostOS
= hostPlatform lbi
162 | v
>= deafening
= ["-v"]
164 |
otherwise = ["-c"] -- Do not warn if library had to be created.
166 -- | @ar@ by default includes various metadata for each object file in their
167 -- respective headers, so the output can differ for the same inputs, making
168 -- it difficult to avoid re-linking. GNU @ar@(1) has a deterministic mode
169 -- (@-D@) flag that always writes zero for the mtime, UID and GID, and 0644
170 -- for the file mode. However detecting whether @-D@ is supported seems
171 -- rather harder than just re-implementing this feature.
172 wipeMetadata
:: Verbosity
-> FilePath -> IO ()
173 wipeMetadata verbosity path
= do
174 -- Check for existence first (ReadWriteMode would create one otherwise)
175 exists
<- doesFileExist path
176 unless exists
$ wipeError
"Temporary file disappeared"
177 withBinaryFile path ReadWriteMode
$ \h
-> hFileSize h
>>= wipeArchive h
180 dieWithLocation
' verbosity path Nothing
$
181 "Distribution.Simple.Program.Ar.wipeMetadata: " ++ msg
182 archLF
= "!<arch>\x0a" -- global magic, 8 bytes
183 x60LF
= "\x60\x0a" -- header magic, 2 bytes
186 [ "0 " -- mtime, 12 bytes
187 , "0 " -- UID, 6 bytes
188 , "0 " -- GID, 6 bytes
189 , "0644 " -- mode, 8 bytes
194 -- http://en.wikipedia.org/wiki/Ar_(Unix)#File_format_details
195 wipeArchive
:: Handle -> Integer -> IO ()
196 wipeArchive h archiveSize
= do
197 global
<- BS
.hGet h
(BS
.length archLF
)
198 unless (global
== archLF
) $ wipeError
"Bad global header"
199 wipeHeader
(toInteger $ BS
.length archLF
)
201 wipeHeader
:: Integer -> IO ()
202 wipeHeader offset
= case compare offset archiveSize
of
204 GT
-> wipeError
(atOffset
"Archive truncated")
206 header
<- BS
.hGet h headerSize
207 unless (BS
.length header
== headerSize
) $
208 wipeError
(atOffset
"Short header")
209 let magic
= BS
.drop 58 header
210 unless (magic
== x60LF
) . wipeError
. atOffset
$
211 "Bad magic " ++ show magic
++ " in header"
213 let name
= BS
.take 16 header
214 let size
= BS
.take 10 $ BS
.drop 48 header
215 objSize
<- case reads (BS8
.unpack size
) of
216 [(n
, s
)] |
all isSpace s
-> return n
217 _
-> wipeError
(atOffset
"Bad file size in header")
219 let replacement
= BS
.concat [name
, metadata
, size
, magic
]
220 unless (BS
.length replacement
== headerSize
) $
221 wipeError
(atOffset
"Something has gone terribly wrong")
222 hSeek h AbsoluteSeek offset
223 BS
.hPut h replacement
227 + toInteger headerSize
229 -- Odd objects are padded with an extra '\x0a'
230 if odd objSize
then objSize
+ 1 else objSize
231 hSeek h AbsoluteSeek nextHeader
232 wipeHeader nextHeader
234 atOffset msg
= msg
++ " at offset " ++ show offset