PageRenderTime 53ms CodeModel.GetById 16ms RepoModel.GetById 2ms app.codeStats 0ms

/cabal-install/Distribution/Client/Index.hs

https://gitlab.com/kranium/cabal
Haskell | 218 lines | 159 code | 27 blank | 32 comment | 7 complexity | e7940203e559372ca45b31c574e82aee MD5 | raw file
  1. -----------------------------------------------------------------------------
  2. -- |
  3. -- Module : Distribution.Client.Index
  4. -- Maintainer : cabal-devel@haskell.org
  5. -- Portability : portable
  6. --
  7. -- Querying and modifying local build tree references in the package index.
  8. -----------------------------------------------------------------------------
  9. module Distribution.Client.Index (
  10. index,
  11. createEmpty,
  12. addBuildTreeRefs,
  13. removeBuildTreeRefs,
  14. listBuildTreeRefs,
  15. defaultIndexFileName
  16. ) where
  17. import qualified Distribution.Client.Tar as Tar
  18. import Distribution.Client.IndexUtils ( getSourcePackages )
  19. import Distribution.Client.PackageIndex ( allPackages )
  20. import Distribution.Client.Setup ( IndexFlags(..) )
  21. import Distribution.Client.Types ( Repo(..), LocalRepo(..)
  22. , SourcePackageDb(..)
  23. , SourcePackage(..), PackageLocation(..) )
  24. import Distribution.Client.Utils ( byteStringToFilePath, filePathToByteString
  25. , makeAbsoluteToCwd )
  26. import Distribution.Simple.Setup ( fromFlagOrDefault )
  27. import Distribution.Simple.Utils ( die, debug, notice, findPackageDesc )
  28. import Distribution.Verbosity ( Verbosity )
  29. import qualified Data.ByteString.Lazy as BS
  30. import Control.Exception ( evaluate )
  31. import Control.Monad ( liftM, when, unless )
  32. import Data.List ( (\\), nub )
  33. import Data.Maybe ( catMaybes )
  34. import System.Directory ( canonicalizePath, createDirectoryIfMissing,
  35. doesDirectoryExist, doesFileExist,
  36. renameFile )
  37. import System.FilePath ( (</>), (<.>), takeDirectory, takeExtension )
  38. import System.IO ( IOMode(..), SeekMode(..)
  39. , hSeek, withBinaryFile )
  40. -- | A reference to a local build tree.
  41. newtype BuildTreeRef = BuildTreeRef {
  42. buildTreePath :: FilePath
  43. }
  44. defaultIndexFileName :: FilePath
  45. defaultIndexFileName = "00-index.tar"
  46. -- | Entry point for the 'cabal index' command.
  47. index :: Verbosity -> IndexFlags -> FilePath -> IO ()
  48. index verbosity indexFlags path' = do
  49. let runInit = fromFlagOrDefault False (indexInit indexFlags)
  50. let refsToAdd = indexLinkSource indexFlags
  51. let runLinkSource = not . null $ refsToAdd
  52. let refsToRemove = indexRemoveSource indexFlags
  53. let runRemoveSource = not . null $ refsToRemove
  54. let runList = fromFlagOrDefault False (indexList indexFlags)
  55. unless (or [runInit, runLinkSource, runRemoveSource, runList]) $ do
  56. die "no arguments passed to the 'index' command"
  57. path <- validateIndexPath path'
  58. when runInit $ do
  59. createEmpty verbosity path
  60. when runLinkSource $ do
  61. addBuildTreeRefs verbosity path refsToAdd
  62. when runRemoveSource $ do
  63. removeBuildTreeRefs verbosity path refsToRemove
  64. when runList $ do
  65. refs <- listBuildTreeRefs verbosity path
  66. mapM_ putStrLn refs
  67. -- | Given a path, ensure that it refers to a local build tree.
  68. buildTreeRefFromPath :: FilePath -> IO (Maybe BuildTreeRef)
  69. buildTreeRefFromPath dir = do
  70. dirExists <- doesDirectoryExist dir
  71. when (not dirExists) $ do
  72. die $ "directory '" ++ dir ++ "' does not exist"
  73. _ <- findPackageDesc dir
  74. return . Just $ BuildTreeRef { buildTreePath = dir }
  75. -- | Given a tar archive entry, try to parse it as a local build tree reference.
  76. readBuildTreePath :: Tar.Entry -> Maybe FilePath
  77. readBuildTreePath entry = case Tar.entryContent entry of
  78. (Tar.OtherEntryType typeCode bs size)
  79. | (typeCode == Tar.buildTreeRefTypeCode)
  80. && (size == BS.length bs) -> Just $ byteStringToFilePath bs
  81. | otherwise -> Nothing
  82. _ -> Nothing
  83. -- | Given a sequence of tar archive entries, extract all references to local
  84. -- build trees.
  85. readBuildTreePaths :: Tar.Entries -> [FilePath]
  86. readBuildTreePaths =
  87. catMaybes
  88. . Tar.foldrEntries (\e r -> (readBuildTreePath e):r)
  89. [] error
  90. -- | Given a path to a tar archive, extract all references to local build trees.
  91. readBuildTreePathsFromFile :: FilePath -> IO [FilePath]
  92. readBuildTreePathsFromFile = liftM (readBuildTreePaths . Tar.read)
  93. . BS.readFile
  94. -- | Given a local build tree, serialise it to a tar archive entry.
  95. writeBuildTreeRef :: BuildTreeRef -> Tar.Entry
  96. writeBuildTreeRef lbt = Tar.simpleEntry tarPath content
  97. where
  98. bs = filePathToByteString path
  99. path = buildTreePath lbt
  100. -- fromRight can't fail because the path is shorter than 255 characters.
  101. tarPath = fromRight $ Tar.toTarPath True tarPath'
  102. -- Provide a filename for tools that treat custom entries as ordinary files.
  103. tarPath' = "local-build-tree-reference"
  104. content = Tar.OtherEntryType Tar.buildTreeRefTypeCode bs (BS.length bs)
  105. -- TODO: Move this to D.C.Utils?
  106. fromRight (Left err) = error err
  107. fromRight (Right a) = a
  108. -- | Check that the provided path is either an existing directory, or a tar
  109. -- archive in an existing directory.
  110. validateIndexPath :: FilePath -> IO FilePath
  111. validateIndexPath path' = do
  112. path <- makeAbsoluteToCwd path'
  113. if (== ".tar") . takeExtension $ path
  114. then return path
  115. else do dirExists <- doesDirectoryExist path
  116. unless dirExists $ do
  117. die $ "directory does not exist: '" ++ path ++ "'"
  118. return $ path </> defaultIndexFileName
  119. -- | Create an empty index file.
  120. createEmpty :: Verbosity -> FilePath -> IO ()
  121. createEmpty verbosity path = do
  122. indexExists <- doesFileExist path
  123. if indexExists
  124. then debug verbosity $ "Package index already exists: " ++ path
  125. else do
  126. debug verbosity $ "Creating the index file '" ++ path ++ "'"
  127. createDirectoryIfMissing True (takeDirectory path)
  128. -- Equivalent to 'tar cvf empty.tar --files-from /dev/null'.
  129. let zeros = BS.replicate (512*20) 0
  130. BS.writeFile path zeros
  131. -- | Add given local build tree references to the index.
  132. addBuildTreeRefs :: Verbosity -> FilePath -> [FilePath] -> IO ()
  133. addBuildTreeRefs _ _ [] =
  134. error "Distribution.Client.Index.addBuildTreeRefs: unexpected"
  135. addBuildTreeRefs verbosity path l' = do
  136. checkIndexExists path
  137. l <- liftM nub . mapM canonicalizePath $ l'
  138. treesInIndex <- readBuildTreePathsFromFile path
  139. -- Add only those paths that aren't already in the index.
  140. treesToAdd <- mapM buildTreeRefFromPath (l \\ treesInIndex)
  141. let entries = map writeBuildTreeRef (catMaybes treesToAdd)
  142. when (not . null $ entries) $ do
  143. offset <-
  144. fmap (Tar.foldrEntries (\e acc -> Tar.entrySizeInBytes e + acc) 0 error
  145. . Tar.read) $ BS.readFile path
  146. _ <- evaluate offset
  147. debug verbosity $ "Writing at offset: " ++ show offset
  148. withBinaryFile path ReadWriteMode $ \h -> do
  149. hSeek h AbsoluteSeek (fromIntegral offset)
  150. BS.hPut h (Tar.write entries)
  151. debug verbosity $ "Successfully appended to '" ++ path ++ "'"
  152. -- | Remove given local build tree references from the index.
  153. removeBuildTreeRefs :: Verbosity -> FilePath -> [FilePath] -> IO ()
  154. removeBuildTreeRefs _ _ [] =
  155. error "Distribution.Client.Index.removeBuildTreeRefs: unexpected"
  156. removeBuildTreeRefs verbosity path l' = do
  157. checkIndexExists path
  158. l <- mapM canonicalizePath l'
  159. let tmpFile = path <.> "tmp"
  160. -- Performance note: on my system, it takes 'index --remove-source'
  161. -- approx. 3,5s to filter a 65M file. Real-life indices are expected to be
  162. -- much smaller.
  163. BS.writeFile tmpFile . Tar.writeEntries . Tar.filterEntries (p l) . Tar.read
  164. =<< BS.readFile path
  165. -- This invalidates the cache, so we don't have to update it explicitly.
  166. renameFile tmpFile path
  167. debug verbosity $ "Successfully renamed '" ++ tmpFile
  168. ++ "' to '" ++ path ++ "'"
  169. where
  170. p l entry = case readBuildTreePath entry of
  171. Nothing -> True
  172. (Just pth) -> not $ any (== pth) l
  173. -- | List the local build trees that are referred to from the index.
  174. listBuildTreeRefs :: Verbosity -> FilePath -> IO [FilePath]
  175. listBuildTreeRefs verbosity path = do
  176. checkIndexExists path
  177. let repo = Repo { repoKind = Right LocalRepo
  178. , repoLocalDir = takeDirectory path }
  179. pkgIndex <- fmap packageIndex . getSourcePackages verbosity $ [repo]
  180. let buildTreeRefs = [ pkgPath | (LocalUnpackedPackage pkgPath) <-
  181. map packageSource . allPackages $ pkgIndex ]
  182. when (null buildTreeRefs) $ do
  183. notice verbosity $ "Index file '" ++ path
  184. ++ "' has no references to local build trees."
  185. return buildTreeRefs
  186. -- | Check that the package index file exists and exit with error if it does not.
  187. checkIndexExists :: FilePath -> IO ()
  188. checkIndexExists path = do
  189. indexExists <- doesFileExist path
  190. when (not indexExists) $ do
  191. die $ "index does not exist: '" ++ path ++ "'"