2 {-# LANGUAGE DeriveDataTypeable #-}
3 {-# LANGUAGE DeriveGeneric #-}
4 {-# LANGUAGE FlexibleContexts #-}
5 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
6 {-# LANGUAGE RankNTypes #-}
7 {-# LANGUAGE ScopedTypeVariables #-}
9 module Distribution
.Compat
.Time
10 ( ModTime
(..) -- Needed for testing
14 , posixSecondsToModTime
15 , calibrateMtimeChangeDelay
19 import Distribution
.Compat
.Prelude
22 import System
.Directory
(getModificationTime)
24 import Distribution
.Simple
.Utils
(withTempDirectory
)
25 import Distribution
.Verbosity
(silent
)
27 import System
.FilePath
29 import Data
.Time
(diffUTCTime
, getCurrentTime
)
30 import Data
.Time
.Clock
.POSIX
(POSIXTime
, getPOSIXTime
, posixDayLength
)
32 #if defined mingw32_HOST_OS
34 import qualified Prelude
35 import Data
.Bits
((.|
.), unsafeShiftL
)
36 #if MIN_VERSION_base
(4,7,0)
37 import Data
.Bits
(finiteBitSize
)
39 import Data
.Bits
(bitSize
)
42 import Foreign
( allocaBytes
, peekByteOff
)
43 import System
.IO.Error
( mkIOError
, doesNotExistErrorType
)
44 import System
.Win32
.Types
( BOOL
, DWORD
, LPCTSTR
, LPVOID
, withTString
)
48 import System
.Posix
.Files
( FileStatus
, getFileStatus
)
50 #if MIN_VERSION_unix
(2,6,0)
51 import System
.Posix
.Files
( modificationTimeHiRes
)
53 import System
.Posix
.Files
( modificationTime
)
58 -- | An opaque type representing a file's modification time, represented
59 -- internally as a 64-bit unsigned integer in the Windows UTC format.
60 newtype ModTime
= ModTime Word64
61 deriving (Binary
, Generic
, Bounded
, Eq
, Ord
, Typeable
)
63 instance Structured ModTime
65 instance Show ModTime
where
66 show (ModTime x
) = show x
68 instance Read ModTime
where
69 readsPrec p str
= map (first ModTime
) (readsPrec p str
)
71 -- | Return modification time of the given file. Works around the low clock
72 -- resolution problem that 'getModificationTime' has on GHC < 7.8.
74 -- This is a modified version of the code originally written for Shake by Neil
75 -- Mitchell. See module Development.Shake.FileInfo.
76 getModTime
:: FilePath -> IO ModTime
78 #if defined mingw32_HOST_OS
80 -- Directly against the Win32 API.
81 getModTime path
= allocaBytes size_WIN32_FILE_ATTRIBUTE_DATA
$ \info
-> do
82 res
<- getFileAttributesEx path info
85 let err
= mkIOError doesNotExistErrorType
86 "Distribution.Compat.Time.getModTime"
90 dwLow
<- peekByteOff info
91 index_WIN32_FILE_ATTRIBUTE_DATA_ftLastWriteTime_dwLowDateTime
92 dwHigh
<- peekByteOff info
93 index_WIN32_FILE_ATTRIBUTE_DATA_ftLastWriteTime_dwHighDateTime
94 #if MIN_VERSION_base
(4,7,0)
96 (fromIntegral (dwHigh
:: DWORD
) `unsafeShiftL` finiteBitSize dwHigh
)
97 .|
. (fromIntegral (dwLow
:: DWORD
))
100 (fromIntegral (dwHigh
:: DWORD
) `unsafeShiftL` bitSize dwHigh
)
101 .|
. (fromIntegral (dwLow
:: DWORD
))
103 return $! ModTime
(qwTime
:: Word64
)
105 {- FOURMOLU_DISABLE -}
106 #ifdef x86_64_HOST_ARCH
107 #define CALLCONV ccall
109 #define CALLCONV stdcall
112 foreign import CALLCONV
"windows.h GetFileAttributesExW"
113 c_getFileAttributesEx
:: LPCTSTR
-> Int32
-> LPVOID
-> Prelude
.IO BOOL
115 getFileAttributesEx
:: String -> LPVOID
-> IO BOOL
116 getFileAttributesEx path lpFileInformation
=
117 withTString path
$ \c_path
->
118 c_getFileAttributesEx c_path getFileExInfoStandard lpFileInformation
120 getFileExInfoStandard
:: Int32
121 getFileExInfoStandard
= 0
123 size_WIN32_FILE_ATTRIBUTE_DATA
:: Int
124 size_WIN32_FILE_ATTRIBUTE_DATA
= 36
126 index_WIN32_FILE_ATTRIBUTE_DATA_ftLastWriteTime_dwLowDateTime
:: Int
127 index_WIN32_FILE_ATTRIBUTE_DATA_ftLastWriteTime_dwLowDateTime
= 20
129 index_WIN32_FILE_ATTRIBUTE_DATA_ftLastWriteTime_dwHighDateTime
:: Int
130 index_WIN32_FILE_ATTRIBUTE_DATA_ftLastWriteTime_dwHighDateTime
= 24
134 -- Directly against the unix library.
136 st
<- getFileStatus path
137 return $! (extractFileTime st
)
139 extractFileTime
:: FileStatus
-> ModTime
140 extractFileTime x
= posixTimeToModTime
(modificationTimeHiRes x
)
143 {- FOURMOLU_ENABLE -}
145 windowsTick
, secToUnixEpoch
:: Word64
146 windowsTick
= 10000000
147 secToUnixEpoch
= 11644473600
149 -- | Convert POSIX seconds to ModTime.
150 posixSecondsToModTime
:: Int64
-> ModTime
151 posixSecondsToModTime s
=
152 ModTime
$ ((fromIntegral s
:: Word64
) + secToUnixEpoch
) * windowsTick
154 -- | Convert 'POSIXTime' to 'ModTime'.
155 posixTimeToModTime
:: POSIXTime
-> ModTime
156 posixTimeToModTime p
=
158 ceiling (p
* 1e7
) -- 100 ns precision
159 + (secToUnixEpoch
* windowsTick
)
161 -- | Return age of given file in days.
162 getFileAge
:: FilePath -> IO Double
164 t0
<- getModificationTime file
166 return $ realToFrac (t1 `diffUTCTime` t0
) / realToFrac posixDayLength
168 -- | Return the current time as 'ModTime'.
169 getCurTime
:: IO ModTime
170 getCurTime
= posixTimeToModTime `
fmap` getPOSIXTime
-- Uses 'gettimeofday'.
172 -- | Based on code written by Neil Mitchell for Shake. See
173 -- 'sleepFileTimeCalibrate' in 'Test.Type'. Returns a pair
174 -- of microsecond values: first, the maximum delay seen, and the
175 -- recommended delay to use before testing for file modification change.
176 -- The returned delay is never smaller
177 -- than 10 ms, but never larger than 1 second.
178 calibrateMtimeChangeDelay
:: IO (Int, Int)
179 calibrateMtimeChangeDelay
=
180 withTempDirectory silent
"." "calibration-" $ \dir
-> do
181 let fileName
= dir
</> "probe"
182 mtimes
<- for
[1 .. 25] $ \(i
:: Int) -> time
$ do
183 writeFile fileName
$ show i
184 t0
<- getModTime fileName
186 writeFile fileName
$ show (i
, j
)
187 t1
<- getModTime fileName
188 unless (t0
< t1
) (spin
$ j
+ 1)
190 let mtimeChange
= maximum mtimes
191 mtimeChange
' = min 1000000 $ (max 10000 mtimeChange
) * 2
192 return (mtimeChange
, mtimeChange
')
194 time
:: IO () -> IO Int
199 return . ceiling $! (t1 `diffUTCTime` t0
) * 1e6
-- microseconds