forked from input-output-hk/foliage
-
Notifications
You must be signed in to change notification settings - Fork 0
/
PrepareSource.hs
145 lines (125 loc) 路 5.32 KB
/
PrepareSource.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
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE TypeFamilies #-}
module Foliage.PrepareSource where
import Control.Monad (when)
import Data.ByteString qualified as BS
import Data.Foldable (for_)
import Development.Shake
import Development.Shake.Classes
import Development.Shake.Rule
import Distribution.Pretty (prettyShow)
import Distribution.Types.PackageId
import Distribution.Types.PackageName (unPackageName)
import Foliage.FetchURL (fetchURL)
import Foliage.GitClone (gitClone)
import Foliage.Meta
import Foliage.UpdateCabalFile (rewritePackageVersion)
import GHC.Generics
import Network.URI (URI (..))
import System.Directory qualified as IO
import System.FilePath ((<.>), (</>))
data PrepareSourceRule = PrepareSourceRule PackageId PackageVersionSpec
deriving (Eq, Generic)
deriving (Hashable, Binary, NFData)
instance Show PrepareSourceRule where
show (PrepareSourceRule pkgId pkgSpec) =
"prepareSource "
++ prettyShow pkgId
++ " "
++ show pkgSpec
type instance RuleResult PrepareSourceRule = FilePath
prepareSource :: PackageId -> PackageVersionSpec -> Action FilePath
prepareSource pkgId pkgMeta = apply1 $ PrepareSourceRule pkgId pkgMeta
addPrepareSourceRule :: FilePath -> FilePath -> Rules ()
addPrepareSourceRule inputDir cacheDir = addBuiltinRule noLint noIdentity run
where
run :: PrepareSourceRule -> Maybe BS.ByteString -> RunMode -> Action (RunResult FilePath)
run (PrepareSourceRule pkgId pkgMeta) _old mode = do
let PackageIdentifier{pkgName, pkgVersion} = pkgId
let PackageVersionSpec{packageVersionSource, packageVersionForce} = pkgMeta
let srcDir = cacheDir </> unPackageName pkgName </> prettyShow pkgVersion
case mode of
RunDependenciesSame ->
return $ RunResult ChangedNothing BS.empty srcDir
RunDependenciesChanged -> do
-- FIXME too much rework?
-- this action only depends on the tarball and the package metadata
-- delete everything inside the package source tree
liftIO $ do
-- FIXME this should only delete inside srcDir but apparently
-- also deletes srcDir itself
removeFiles srcDir ["//*"]
IO.createDirectoryIfMissing True srcDir
case packageVersionSource of
URISource (URI{uriScheme, uriPath}) mSubdir | uriScheme == "file:" -> do
tarballPath <- liftIO $ IO.makeAbsolute uriPath
extractFromTarball tarballPath mSubdir srcDir
URISource uri mSubdir -> do
tarballPath <- fetchURL uri
extractFromTarball tarballPath mSubdir srcDir
GitHubSource repo rev mSubdir -> do
repoDir <- gitClone repo
copyGitWorktree repoDir rev mSubdir srcDir
let patchesDir = inputDir </> unPackageName pkgName </> prettyShow pkgVersion </> "patches"
hasPatches <- doesDirectoryExist patchesDir
when hasPatches $ do
patchfiles <- getDirectoryFiles patchesDir ["*.patch"]
for_ patchfiles $ \patchfile -> do
let patch = patchesDir </> patchfile
cmd_ Shell (Cwd srcDir) (FileStdin patch) "patch -p1"
when packageVersionForce $ do
let cabalFilePath = srcDir </> unPackageName pkgName <.> "cabal"
putInfo $ "Updating version in cabal file" ++ cabalFilePath
liftIO $ rewritePackageVersion cabalFilePath pkgVersion
return $ RunResult ChangedRecomputeDiff BS.empty srcDir
extractFromTarball tarballPath mSubdir outDir = do
withTempDir $ \tmpDir -> do
cmd_
[ "tar"
, -- Extract files from an archive
"--extract"
, -- Filter the archive through gunzip
"--gunzip"
, -- Use archive file
"--file"
, tarballPath
, -- Change to DIR before performing any operations
"--directory"
, tmpDir
]
ls <-
-- remove "." and ".."
filter (not . all (== '.'))
-- NOTE: Don't let shake look into tmpDir! it will cause
-- unnecessary rework because tmpDir is always new
<$> liftIO (IO.getDirectoryContents tmpDir)
-- Special treatment of top-level directory: we remove it
let byPassSingleTopLevelDir = case ls of [l] -> (</> l); _ -> id
applyMSubdir = case mSubdir of Just s -> (</> s); _ -> id
srcDir = applyMSubdir $ byPassSingleTopLevelDir tmpDir
copyDirectoryContents srcDir outDir
-- | Copy package source from a git repository using 'git worktree'.
copyGitWorktree :: FilePath -> GitHubRev -> Maybe FilePath -> FilePath -> Action ()
copyGitWorktree repoDir rev mSubdir outDir = do
withTempDir $ \tmpDir -> do
command_ [Cwd repoDir] "git" ["worktree", "add", tmpDir, show rev]
command_ [Cwd tmpDir] "git" ["submodule", "update", "--init"]
let packageDir = maybe tmpDir (tmpDir </>) mSubdir
copyDirectoryContents packageDir outDir
command_ [Cwd repoDir] "git" ["worktree", "prune"]
-- | Copy all contents from one directory to another.
copyDirectoryContents :: FilePath -> FilePath -> Action ()
copyDirectoryContents source destination =
cmd_
[ "cp"
, -- copy directories recursively
"--recursive"
, -- treat DEST as a normal file
"--no-target-directory"
, -- always follow symbolic links in SOURCE
"--dereference"
, source
, destination
]