Finish off pages

- Summary page has the list of all packages available (with details of the latest version)
- Timeline page has the list of all entries as they appear in the index

Also:
- Rebuild sdists is they are missing
This commit is contained in:
Andrea Bedini 2022-10-26 14:09:17 +08:00
parent db3fe215de
commit c0714b1b3d
12 changed files with 342 additions and 183 deletions

View File

@ -1,4 +1,3 @@
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingVia #-} {-# LANGUAGE DerivingVia #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
@ -8,41 +7,28 @@ import Codec.Archive.Tar qualified as Tar
import Codec.Archive.Tar.Entry qualified as Tar import Codec.Archive.Tar.Entry qualified as Tar
import Codec.Compression.GZip qualified as GZip import Codec.Compression.GZip qualified as GZip
import Control.Monad (unless, when) import Control.Monad (unless, when)
import Data.Aeson (object, (.=))
import Data.Aeson.Types (ToJSON)
import Data.ByteString.Lazy qualified as BL import Data.ByteString.Lazy qualified as BL
import Data.ByteString.Lazy qualified as BSL import Data.ByteString.Lazy qualified as BSL
import Data.Foldable (for_) import Data.Foldable (for_)
import Data.List (sortOn) import Data.List (sortOn)
import Data.Maybe (fromMaybe) import Data.Maybe (fromMaybe)
import Data.Ord
import Data.Text.Lazy.IO qualified as TL
import Data.Time.Clock.POSIX (POSIXTime, utcTimeToPOSIXSeconds)
import Data.Traversable (for) import Data.Traversable (for)
import Development.Shake import Development.Shake
import Development.Shake.FilePath import Development.Shake.FilePath
import Distribution.Aeson
import Distribution.Package import Distribution.Package
import Distribution.PackageDescription (GenericPackageDescription)
import Distribution.Parsec (simpleParsec) import Distribution.Parsec (simpleParsec)
import Distribution.Pretty (prettyShow) import Distribution.Pretty (prettyShow)
import Distribution.Simple.PackageDescription (readGenericPackageDescription)
import Distribution.Verbosity qualified as Verbosity
import Foliage.HackageSecurity hiding (ToJSON, toJSON) import Foliage.HackageSecurity hiding (ToJSON, toJSON)
import Foliage.Meta import Foliage.Meta
import Foliage.Meta.Aeson () import Foliage.Meta.Aeson ()
import Foliage.Options import Foliage.Options
import Foliage.Pages (indexPageTemplate, packageVersionPageTemplate) import Foliage.Pages
import Foliage.PrepareSdist import Foliage.PrepareSdist
import Foliage.PrepareSource (addPrepareSourceRule, prepareSource) import Foliage.PrepareSource (addPrepareSourceRule, prepareSource)
import Foliage.RemoteAsset (addFetchRemoteAssetRule) import Foliage.RemoteAsset (addFetchRemoteAssetRule)
import Foliage.Shake import Foliage.Shake
import Foliage.Time qualified as Time import Foliage.Time qualified as Time
import Foliage.Utils.Aeson
import GHC.Generics (Generic)
import Hackage.Security.Util.Path (castRoot, toFilePath) import Hackage.Security.Util.Path (castRoot, toFilePath)
import System.Directory qualified as IO
import Text.Mustache (renderMustache)
cmdBuild :: BuildOptions -> IO () cmdBuild :: BuildOptions -> IO ()
cmdBuild buildOptions = do cmdBuild buildOptions = do
@ -97,13 +83,15 @@ buildAction
putInfo $ "Current time set to " <> Time.iso8601Show t <> "." putInfo $ "Current time set to " <> Time.iso8601Show t <> "."
return t return t
packages <- getPackages inputDir packageVersions <- getPackageVersions inputDir
makeIndexPage currentTime outputDir packages makeSummaryPage currentTime outputDir packageVersions
for_ packages $ makePackageVersionPage inputDir outputDir makeTimelinePage currentTime outputDir packageVersions
for_ packages $ \pkgMeta@PackageVersionMeta {pkgId} -> do for_ packageVersions $ makePackageVersionPage inputDir outputDir
for_ packageVersions $ \pkgMeta@PackageVersionMeta {pkgId} -> do
let PackageIdentifier {pkgName, pkgVersion} = pkgId let PackageIdentifier {pkgName, pkgVersion} = pkgId
cabalFilePath <- maybe (originalCabalFile pkgMeta) pure (revisedCabalFile inputDir pkgMeta) cabalFilePath <- maybe (originalCabalFile pkgMeta) pure (revisedCabalFile inputDir pkgMeta)
copyFileChanged cabalFilePath (outputDir </> "index" </> prettyShow pkgName </> prettyShow pkgVersion </> prettyShow pkgName <.> "cabal") copyFileChanged cabalFilePath (outputDir </> "index" </> prettyShow pkgName </> prettyShow pkgVersion </> prettyShow pkgName <.> "cabal")
@ -129,11 +117,11 @@ buildAction
return $ cf : revcf return $ cf : revcf
) )
packages packageVersions
targetKeys <- maybeReadKeysAt "target" targetKeys <- maybeReadKeysAt "target"
metadataEntries <- metadataEntries <-
for packages $ \pkg@PackageVersionMeta {pkgId, pkgSpec} -> do for packageVersions $ \pkg@PackageVersionMeta {pkgId, pkgSpec} -> do
let PackageIdentifier {pkgName, pkgVersion} = pkgId let PackageIdentifier {pkgName, pkgVersion} = pkgId
let PackageVersionSpec {packageVersionTimestamp} = pkgSpec let PackageVersionSpec {packageVersionTimestamp} = pkgSpec
targets <- prepareIndexPkgMetadata expiryTime pkg targets <- prepareIndexPkgMetadata expiryTime pkg
@ -233,66 +221,8 @@ buildAction
timestampInfoSnapshot = snapshotInfo timestampInfoSnapshot = snapshotInfo
} }
data IndexEntry getPackageVersions :: FilePath -> Action [PackageVersionMeta]
= IndexEntryPackage getPackageVersions inputDir = do
{ indexEntryPkgId :: PackageIdentifier,
indexEntryTimestamp :: UTCTime,
indexEntryTimestampPosix :: POSIXTime,
indexEntrySource :: PackageVersionSource
}
| IndexEntryRevision
{ indexEntryPkgId :: PackageIdentifier,
indexEntryTimestamp :: UTCTime,
indexEntryTimestampPosix :: POSIXTime
}
deriving stock (Generic)
deriving (ToJSON) via MyAesonEncoding IndexEntry
makeIndexPage :: UTCTime -> FilePath -> [PackageVersionMeta] -> Action ()
makeIndexPage currentTime outputDir packages = do
let entries =
sortOn (Down . indexEntryTimestamp) $
foldMap
( \PackageVersionMeta {pkgId, pkgSpec = PackageVersionSpec {packageVersionTimestamp, packageVersionRevisions, packageVersionSource}} ->
IndexEntryPackage
{ indexEntryPkgId = pkgId,
indexEntryTimestamp = fromMaybe currentTime packageVersionTimestamp,
indexEntryTimestampPosix = utcTimeToPOSIXSeconds (fromMaybe currentTime packageVersionTimestamp),
indexEntrySource = packageVersionSource
} :
map
( \RevisionSpec {revisionTimestamp} ->
IndexEntryRevision
{ indexEntryPkgId = pkgId,
indexEntryTimestamp = revisionTimestamp,
indexEntryTimestampPosix = utcTimeToPOSIXSeconds revisionTimestamp
}
)
packageVersionRevisions
)
packages
liftIO $ do
IO.createDirectoryIfMissing True outputDir
TL.writeFile (outputDir </> "index.html") $
renderMustache indexPageTemplate $
object ["entries" .= entries]
makePackageVersionPage :: FilePath -> FilePath -> PackageVersionMeta -> Action ()
makePackageVersionPage inputDir outputDir pkgMeta@PackageVersionMeta {pkgId, pkgSpec} = do
cabalFilePath <- maybe (originalCabalFile pkgMeta) pure (revisedCabalFile inputDir pkgMeta)
pkgDesc <- readGenericPackageDescription' cabalFilePath
liftIO $ do
IO.createDirectoryIfMissing True (outputDir </> "package" </> prettyShow pkgId)
TL.writeFile (outputDir </> "package" </> prettyShow pkgId </> "index.html") $
renderMustache packageVersionPageTemplate $
object
[ "pkgSpec" .= pkgSpec,
"pkgDesc" .= jsonGenericPackageDescription pkgDesc
]
getPackages :: FilePath -> Action [PackageVersionMeta]
getPackages inputDir = do
metaFiles <- getDirectoryFiles inputDir ["*/*/meta.toml"] metaFiles <- getDirectoryFiles inputDir ["*/*/meta.toml"]
when (null metaFiles) $ do when (null metaFiles) $ do
@ -332,11 +262,6 @@ getPackages inputDir = do
return $ PackageVersionMeta pkgId pkgSpec return $ PackageVersionMeta pkgId pkgSpec
readGenericPackageDescription' :: FilePath -> Action GenericPackageDescription
readGenericPackageDescription' fp = do
need [fp]
liftIO $ readGenericPackageDescription Verbosity.silent fp
prepareIndexPkgCabal :: PackageId -> UTCTime -> FilePath -> Action Tar.Entry prepareIndexPkgCabal :: PackageId -> UTCTime -> FilePath -> Action Tar.Entry
prepareIndexPkgCabal pkgId timestamp filePath = do prepareIndexPkgCabal pkgId timestamp filePath = do
need [filePath] need [filePath]
@ -376,16 +301,3 @@ mkTarEntry contents indexFile timestamp =
anchorPath :: Path Absolute -> (RepoLayout -> RepoPath) -> FilePath anchorPath :: Path Absolute -> (RepoLayout -> RepoPath) -> FilePath
anchorPath outputDirRoot p = anchorPath outputDirRoot p =
toFilePath $ anchorRepoPathLocally outputDirRoot $ p hackageRepoLayout toFilePath $ anchorRepoPathLocally outputDirRoot $ p hackageRepoLayout
cabalFileRevisionPath :: FilePath -> PackageIdentifier -> Int -> FilePath
cabalFileRevisionPath inputDir PackageIdentifier {pkgName, pkgVersion} revisionNumber =
inputDir </> unPackageName pkgName </> prettyShow pkgVersion </> "revisions" </> show revisionNumber <.> "cabal"
originalCabalFile :: PackageVersionMeta -> Action FilePath
originalCabalFile PackageVersionMeta {pkgId, pkgSpec} = do
srcDir <- prepareSource pkgId pkgSpec
return $ srcDir </> unPackageName (pkgName pkgId) <.> "cabal"
revisedCabalFile :: FilePath -> PackageVersionMeta -> Maybe FilePath
revisedCabalFile inputDir PackageVersionMeta {pkgId, pkgSpec} = do
cabalFileRevisionPath inputDir pkgId <$> latestRevisionNumber pkgSpec

View File

@ -32,6 +32,8 @@ module Foliage.Meta
UTCTime, UTCTime,
latestRevisionNumber, latestRevisionNumber,
consolidateRanges, consolidateRanges,
cabalFileRevisionPath,
revisedCabalFile,
) )
where where
@ -43,32 +45,21 @@ import Data.Ord (Down (Down))
import Data.Text (Text) import Data.Text (Text)
import Data.Text qualified as T import Data.Text qualified as T
import Data.Time.LocalTime (utc, utcToZonedTime, zonedTimeToUTC) import Data.Time.LocalTime (utc, utcToZonedTime, zonedTimeToUTC)
import Development.Shake.Classes import Development.Shake.Classes (Binary, Hashable, NFData)
( Binary,
Hashable,
NFData,
)
import Distribution.Aeson () import Distribution.Aeson ()
import Distribution.Parsec (simpleParsec) import Distribution.Parsec (simpleParsec)
import Distribution.Pretty (prettyShow) import Distribution.Pretty (prettyShow)
import Distribution.Types.Orphans () import Distribution.Types.Orphans ()
import Distribution.Types.PackageId (PackageIdentifier) import Distribution.Types.PackageId (PackageIdentifier (..))
import Distribution.Types.PackageName (unPackageName)
import Distribution.Types.Version (Version) import Distribution.Types.Version (Version)
import Distribution.Types.VersionRange import Distribution.Types.VersionRange (VersionRange, anyVersion, intersectVersionRanges, notThisVersion)
( VersionRange, import Distribution.Version (isAnyVersion, isNoVersion, simplifyVersionRange)
anyVersion,
intersectVersionRanges,
notThisVersion,
)
import Distribution.Version
( isAnyVersion,
isNoVersion,
simplifyVersionRange,
)
import Foliage.Time (UTCTime) import Foliage.Time (UTCTime)
import GHC.Generics (Generic) import GHC.Generics (Generic)
import Network.URI (URI, parseURI) import Network.URI (URI, parseURI)
import Network.URI.Orphans () import Network.URI.Orphans ()
import System.FilePath ((<.>), (</>))
import Toml (TomlCodec, (.=)) import Toml (TomlCodec, (.=))
import Toml qualified import Toml qualified
@ -245,4 +236,13 @@ data PackageVersionMeta = PackageVersionMeta
{ pkgId :: PackageIdentifier, { pkgId :: PackageIdentifier,
pkgSpec :: PackageVersionSpec pkgSpec :: PackageVersionSpec
} }
deriving (Show, Eq)
deriving (Generic) deriving (Generic)
cabalFileRevisionPath :: FilePath -> PackageIdentifier -> Int -> FilePath
cabalFileRevisionPath inputDir PackageIdentifier {pkgName, pkgVersion} revisionNumber =
inputDir </> unPackageName pkgName </> prettyShow pkgVersion </> "revisions" </> show revisionNumber <.> "cabal"
revisedCabalFile :: FilePath -> PackageVersionMeta -> Maybe FilePath
revisedCabalFile inputDir PackageVersionMeta {pkgId, pkgSpec} = do
cabalFileRevisionPath inputDir pkgId <$> latestRevisionNumber pkgSpec

View File

@ -1,6 +1,7 @@
{-# LANGUAGE DerivingVia #-} {-# LANGUAGE DerivingVia #-}
{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE StandaloneDeriving #-}
{-# OPTIONS_GHC -Wno-orphans #-} {-# OPTIONS_GHC -Wno-orphans #-}
{-# LANGUAGE InstanceSigs #-}
module Foliage.Meta.Aeson where module Foliage.Meta.Aeson where
@ -30,7 +31,9 @@ instance ToJSON PackageVersionSource where
genericToJSON genericToJSON
defaultOptions defaultOptions
{ sumEncoding = ObjectWithSingleField { sumEncoding = ObjectWithSingleField
, omitNothingFields = True
} }
instance ToJSON URI where instance ToJSON URI where
toJSON :: URI -> Value
toJSON = toJSON . show toJSON = toJSON . show

View File

@ -1,17 +1,140 @@
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TemplateHaskell #-}
module Foliage.Pages module Foliage.Pages
( indexPageTemplate, ( summaryPageTemplate,
timelinePageTemplate,
packageVersionPageTemplate, packageVersionPageTemplate,
makeSummaryPage,
makePackageVersionPage,
makeTimelinePage,
) )
where where
import Data.Aeson (KeyValue ((.=)), ToJSON, object)
import Data.Function (on)
import Data.List (groupBy, sortOn)
import Data.Maybe (fromMaybe, listToMaybe)
import Data.Ord (Down (Down))
import Data.Text.Lazy.IO.Utf8 qualified as TL
import Data.Time (UTCTime)
import Data.Time.Clock.POSIX (POSIXTime, utcTimeToPOSIXSeconds)
import Development.Shake (Action, liftIO)
import Distribution.Aeson (jsonGenericPackageDescription)
import Distribution.Package (PackageIdentifier (pkgName, pkgVersion))
import Distribution.Pretty (prettyShow)
import Foliage.Meta (PackageVersionMeta (..), PackageVersionSource, PackageVersionSpec (PackageVersionSpec, packageVersionRevisions, packageVersionSource, packageVersionTimestamp), RevisionSpec (RevisionSpec, revisionTimestamp), revisedCabalFile)
import Foliage.Meta.Aeson ()
import Foliage.Shake (originalCabalFile, readGenericPackageDescription')
import Foliage.Utils.Aeson (MyAesonEncoding (..))
import GHC.Generics (Generic)
import System.Directory qualified as IO
import System.FilePath ((</>))
import Text.Mustache (Template) import Text.Mustache (Template)
import Text.Mustache.Compile.TH (compileMustacheDir) import Text.Mustache.Compile.TH (compileMustacheDir)
import Text.Mustache.Render (renderMustache)
indexPageTemplate :: Template data SummaryPageEntry = SummaryPageEntry
indexPageTemplate = $(compileMustacheDir "index" "templates") { summaryPageEntryPkgId :: PackageIdentifier,
summaryPageEntryTimestamp :: UTCTime,
summaryPageEntryTimestampPosix :: POSIXTime,
summaryPageEntrySource :: PackageVersionSource,
summaryPageEntryRevision :: Maybe RevisionSpec
}
deriving stock (Generic)
deriving (ToJSON) via MyAesonEncoding SummaryPageEntry
makeSummaryPage :: UTCTime -> FilePath -> [PackageVersionMeta] -> Action ()
makeSummaryPage currentTime outputDir packageVersions = do
let packages =
sortOn summaryPageEntryPkgId $
map
( ( \PackageVersionMeta {pkgId, pkgSpec = PackageVersionSpec {packageVersionTimestamp, packageVersionRevisions, packageVersionSource}} ->
SummaryPageEntry
{ summaryPageEntryPkgId = pkgId,
summaryPageEntryTimestamp = fromMaybe currentTime packageVersionTimestamp,
summaryPageEntryTimestampPosix = utcTimeToPOSIXSeconds (fromMaybe currentTime packageVersionTimestamp),
summaryPageEntrySource = packageVersionSource,
summaryPageEntryRevision = listToMaybe packageVersionRevisions
}
)
. head
. sortOn (Down . pkgVersion . pkgId)
)
$ groupBy ((==) `on` (pkgName . pkgId)) packageVersions
liftIO $ do
IO.createDirectoryIfMissing True outputDir
IO.createDirectoryIfMissing True (outputDir </> "summary")
TL.writeFile (outputDir </> "summary" </> "index.html") $
renderMustache summaryPageTemplate $
object ["packages" .= packages]
data TimelinePageEntry
= TimelinePageEntryPackage
{ timelinePageEntryPkgId :: PackageIdentifier,
timelinePageEntryTimestamp :: UTCTime,
timelinePageEntryTimestampPosix :: POSIXTime,
timelinePageEntrySource :: PackageVersionSource
}
| TimelinePageEntryRevision
{ timelinePageEntryPkgId :: PackageIdentifier,
timelinePageEntryTimestamp :: UTCTime,
timelinePageEntryTimestampPosix :: POSIXTime
}
deriving stock (Generic)
deriving (ToJSON) via MyAesonEncoding TimelinePageEntry
makeTimelinePage :: UTCTime -> FilePath -> [PackageVersionMeta] -> Action ()
makeTimelinePage currentTime outputDir packageVersions = do
let entries =
sortOn (Down . timelinePageEntryTimestamp) $
foldMap
( \PackageVersionMeta {pkgId, pkgSpec = PackageVersionSpec {packageVersionTimestamp, packageVersionRevisions, packageVersionSource}} ->
TimelinePageEntryPackage
{ timelinePageEntryPkgId = pkgId,
timelinePageEntryTimestamp = fromMaybe currentTime packageVersionTimestamp,
timelinePageEntryTimestampPosix = utcTimeToPOSIXSeconds (fromMaybe currentTime packageVersionTimestamp),
timelinePageEntrySource = packageVersionSource
} :
map
( \RevisionSpec {revisionTimestamp} ->
TimelinePageEntryRevision
{ timelinePageEntryPkgId = pkgId,
timelinePageEntryTimestamp = revisionTimestamp,
timelinePageEntryTimestampPosix = utcTimeToPOSIXSeconds revisionTimestamp
}
)
packageVersionRevisions
)
packageVersions
liftIO $ do
IO.createDirectoryIfMissing True outputDir
IO.createDirectoryIfMissing True (outputDir </> "timeline")
TL.writeFile (outputDir </> "timeline" </> "index.html") $
renderMustache timelinePageTemplate $
object ["entries" .= entries]
makePackageVersionPage :: FilePath -> FilePath -> PackageVersionMeta -> Action ()
makePackageVersionPage inputDir outputDir pkgMeta@PackageVersionMeta {pkgId, pkgSpec} = do
cabalFilePath <- maybe (originalCabalFile pkgMeta) pure (revisedCabalFile inputDir pkgMeta)
pkgDesc <- readGenericPackageDescription' cabalFilePath
liftIO $ do
IO.createDirectoryIfMissing True (outputDir </> "package" </> prettyShow pkgId)
TL.writeFile (outputDir </> "package" </> prettyShow pkgId </> "index.html") $
renderMustache packageVersionPageTemplate $
object
[ "pkgSpec" .= pkgSpec,
"pkgDesc" .= jsonGenericPackageDescription pkgDesc
]
summaryPageTemplate :: Template
summaryPageTemplate = $(compileMustacheDir "summary" "templates")
timelinePageTemplate :: Template
timelinePageTemplate = $(compileMustacheDir "timeline" "templates")
packageVersionPageTemplate :: Template packageVersionPageTemplate :: Template
packageVersionPageTemplate = $(compileMustacheDir "packageVersion" "templates") packageVersionPageTemplate = $(compileMustacheDir "packageVersion" "templates")

View File

@ -11,15 +11,15 @@ module Foliage.PrepareSdist
where where
import Control.Monad (when) import Control.Monad (when)
import Data.Binary qualified as Binary
import Data.ByteString qualified as BS import Data.ByteString qualified as BS
import Data.ByteString.Lazy qualified as BSL import Data.ByteString.Lazy qualified as BSL
import Development.Shake import Development.Shake
import Development.Shake.Classes import Development.Shake.Classes
import Development.Shake.FilePath import Development.Shake.FilePath
import Development.Shake.Rule import Development.Shake.Rule
import Distribution.Client.HashValue (HashValue, hashValue, showHashValue) import Distribution.Client.HashValue (HashValue, hashValue, readFileHashValue, showHashValue)
import Distribution.Client.SrcDist (packageDirToSdist) import Distribution.Client.SrcDist (packageDirToSdist)
import Distribution.Compat.Binary (decode, encode)
import Distribution.Package (packageId) import Distribution.Package (packageId)
import Distribution.Simple.PackageDescription (readGenericPackageDescription) import Distribution.Simple.PackageDescription (readGenericPackageDescription)
import Distribution.Verbosity qualified as Verbosity import Distribution.Verbosity qualified as Verbosity
@ -28,6 +28,7 @@ import Foliage.Meta ()
import GHC.Generics (Generic) import GHC.Generics (Generic)
import Hackage.Security.Util.Path (toFilePath) import Hackage.Security.Util.Path (toFilePath)
import System.Directory qualified as IO import System.Directory qualified as IO
import System.IO.Error (tryIOError)
newtype PrepareSdistRule = PrepareSdistRule FilePath newtype PrepareSdistRule = PrepareSdistRule FilePath
deriving (Show, Eq, Generic) deriving (Show, Eq, Generic)
@ -42,28 +43,32 @@ addPrepareSdistRule :: Path Absolute -> Rules ()
addPrepareSdistRule outputDirRoot = addBuiltinRule noLint noIdentity run addPrepareSdistRule outputDirRoot = addBuiltinRule noLint noIdentity run
where where
run :: PrepareSdistRule -> Maybe BS.ByteString -> RunMode -> Action (RunResult FilePath) run :: PrepareSdistRule -> Maybe BS.ByteString -> RunMode -> Action (RunResult FilePath)
run (PrepareSdistRule _srcDir) (Just old) RunDependenciesSame = run (PrepareSdistRule srcDir) (Just old) RunDependenciesSame = do
let (_, path) = load old let (hvExpected, path) = load old
in return $ RunResult ChangedNothing old path
run (PrepareSdistRule srcDir) old _ = do -- Check of has of the sdist, if the sdist is still there and it is
cabalFile <- do -- indeed what we expect, signal that nothing changed. Otherwise
getDirectoryFiles srcDir ["*.cabal"] >>= \case -- warn the user and proceed to recompute.
[f] -> pure f ehvExisting <- liftIO $ tryIOError $ readFileHashValue path
fs -> fail $ "Invalid srcDir: " ++ srcDir ++ ". It contains multiple cabal files: " ++ unwords fs case ehvExisting of
(hv, path) <- traced "cabal sdist" $ do Right hvExisting
gpd <- readGenericPackageDescription Verbosity.normal (srcDir </> cabalFile) | hvExisting == hvExpected ->
let pkgId = packageId gpd return RunResult {runChanged = ChangedNothing, runStore = old, runValue = path}
packagePath = repoLayoutPkgTarGz hackageRepoLayout pkgId Right hvExisting -> do
path = toFilePath $ anchorRepoPathLocally outputDirRoot packagePath putWarn $ "Changed " ++ path ++ " (expecting hash " ++ showHashValue hvExpected ++ " found " ++ showHashValue hvExisting ++ "). I will rebuild it."
IO.createDirectoryIfMissing True (takeDirectory path) run (PrepareSdistRule srcDir) (Just old) RunDependenciesChanged
sdist <- packageDirToSdist Verbosity.normal gpd srcDir Left _e -> do
BSL.writeFile path sdist putWarn $ "Unable to read " ++ path ++ ". I will rebuild it."
return (hashValue sdist, path) run (PrepareSdistRule srcDir) (Just old) RunDependenciesChanged
run (PrepareSdistRule srcDir) old _mode = do
-- create the sdist distribution
(hv, path) <- makeSdist srcDir
let new = save (hv, path) let new = save (hv, path)
let changed = case old of let changed = case fmap ((== hv) . fst . load) old of
Just old' | fst (load old') == hv -> ChangedRecomputeSame Just True -> ChangedRecomputeSame
_ -> ChangedRecomputeDiff _differentOrMissing -> ChangedRecomputeDiff
when (changed == ChangedRecomputeSame) $ when (changed == ChangedRecomputeSame) $
putInfo $ "Wrote " ++ path ++ " (same hash " ++ showHashValue hv ++ ")" putInfo $ "Wrote " ++ path ++ " (same hash " ++ showHashValue hv ++ ")"
@ -73,8 +78,24 @@ addPrepareSdistRule outputDirRoot = addBuiltinRule noLint noIdentity run
return $ RunResult {runChanged = changed, runStore = new, runValue = path} return $ RunResult {runChanged = changed, runStore = new, runValue = path}
makeSdist srcDir = do
cabalFile <- do
getDirectoryFiles srcDir ["*.cabal"] >>= \case
[f] -> pure f
fs -> fail $ "Invalid srcDir: " ++ srcDir ++ ". It contains multiple cabal files: " ++ unwords fs
traced "cabal sdist" $ do
gpd <- readGenericPackageDescription Verbosity.normal (srcDir </> cabalFile)
let pkgId = packageId gpd
packagePath = repoLayoutPkgTarGz hackageRepoLayout pkgId
path = toFilePath $ anchorRepoPathLocally outputDirRoot packagePath
IO.createDirectoryIfMissing True (takeDirectory path)
sdist <- packageDirToSdist Verbosity.normal gpd srcDir
BSL.writeFile path sdist
return (hashValue sdist, path)
save :: (HashValue, FilePath) -> BS.ByteString save :: (HashValue, FilePath) -> BS.ByteString
save = BSL.toStrict . encode save = BSL.toStrict . Binary.encode
load :: BS.ByteString -> (HashValue, FilePath) load :: BS.ByteString -> (HashValue, FilePath)
load = decode . BSL.fromStrict load = Binary.decode . BSL.fromStrict

View File

@ -48,11 +48,11 @@ addFetchRemoteAssetRule cacheDir = addBuiltinRule noLint noIdentity run
let oldETag = fromMaybe BS.empty old let oldETag = fromMaybe BS.empty old
newETag <- newETag <-
withTempFile $ \fp -> do withTempFile $ \fp -> traced "curl" $ do
liftIO $ BS.writeFile fp oldETag BS.writeFile fp oldETag
liftIO $ createDirectoryIfMissing True (takeDirectory path) createDirectoryIfMissing True (takeDirectory path)
cmd_ Shell ["curl", "--silent", "--location", "--etag-compare", fp, "--etag-save", fp, "--output", path, show uri] cmd_ Shell ["curl", "--silent", "--location", "--etag-compare", fp, "--etag-save", fp, "--output", path, show uri]
liftIO $ BS.readFile fp BS.readFile fp
let changed = if newETag == oldETag then ChangedRecomputeSame else ChangedRecomputeDiff let changed = if newETag == oldETag then ChangedRecomputeSame else ChangedRecomputeDiff
return $ RunResult {runChanged = changed, runStore = newETag, runValue = path} return $ RunResult {runChanged = changed, runStore = newETag, runValue = path}

View File

@ -2,14 +2,22 @@ module Foliage.Shake
( computeFileInfoSimple', ( computeFileInfoSimple',
readKeysAt, readKeysAt,
readPackageVersionSpec', readPackageVersionSpec',
readGenericPackageDescription',
originalCabalFile,
) )
where where
import Data.Traversable (for) import Data.Traversable (for)
import Development.Shake import Development.Shake
import Development.Shake.FilePath import Development.Shake.FilePath
import Distribution.Simple.PackageDescription
import Distribution.Types.GenericPackageDescription
import Distribution.Types.PackageId
import Distribution.Types.PackageName
import Distribution.Verbosity qualified as Verbosity
import Foliage.HackageSecurity import Foliage.HackageSecurity
import Foliage.Meta import Foliage.Meta
import Foliage.PrepareSource
computeFileInfoSimple' :: FilePath -> Action FileInfo computeFileInfoSimple' :: FilePath -> Action FileInfo
computeFileInfoSimple' fp = do computeFileInfoSimple' fp = do
@ -28,3 +36,13 @@ readPackageVersionSpec' :: FilePath -> Action PackageVersionSpec
readPackageVersionSpec' fp = do readPackageVersionSpec' fp = do
need [fp] need [fp]
liftIO $ readPackageVersionSpec fp liftIO $ readPackageVersionSpec fp
readGenericPackageDescription' :: FilePath -> Action GenericPackageDescription
readGenericPackageDescription' fp = do
need [fp]
liftIO $ readGenericPackageDescription Verbosity.silent fp
originalCabalFile :: PackageVersionMeta -> Action FilePath
originalCabalFile PackageVersionMeta {pkgId, pkgSpec} = do
srcDir <- prepareSource pkgId pkgSpec
return $ srcDir </> unPackageName (pkgName pkgId) <.> "cabal"

View File

@ -45,6 +45,7 @@ executable foliage
base >=4.14.3.0 && <4.18, base >=4.14.3.0 && <4.18,
aeson >=2.0.3.0 && <2.2, aeson >=2.0.3.0 && <2.2,
base64 >=0.4.2.3 && <0.5, base64 >=0.4.2.3 && <0.5,
binary,
bytestring >=0.10.12.0 && <0.12, bytestring >=0.10.12.0 && <0.12,
Cabal >=3.8 && <3.9, Cabal >=3.8 && <3.9,
Cabal-syntax >=3.8 && <3.9, Cabal-syntax >=3.8 && <3.9,

View File

@ -19,8 +19,8 @@
<div class="container px-4 py-5"> <div class="container px-4 py-5">
<nav aria-label="breadcrumb"> <nav aria-label="breadcrumb">
<ol class="breadcrumb"> <ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/">Home</a></li> <li class="breadcrumb-item"><a href="/timeline/">Timeline</a></li>
<li class="breadcrumb-item"><a href="/?q={{name}}">{{name}}</a></li> <li class="breadcrumb-item"><a href="/timeline/?q={{name}}">{{name}}</a></li>
<li class="breadcrumb-item active" aria-current="page">{{version}}</li> <li class="breadcrumb-item active" aria-current="page">{{version}}</li>
</ol> </ol>
</nav> </nav>

View File

@ -1,5 +1,4 @@
{{#TarballSource}} {{#TarballSource}}
<dt>Tarball</dt>
<dd><a href="{{tarballSourceURI}}">{{tarballSourceURI}}</a></dd> <dd><a href="{{tarballSourceURI}}">{{tarballSourceURI}}</a></dd>
{{#subdir}} {{#subdir}}
<dt>Subdir</dt> <dt>Subdir</dt>
@ -7,7 +6,6 @@
{{/subdir}} {{/subdir}}
{{/TarballSource}} {{/TarballSource}}
{{#GitHubSource}} {{#GitHubSource}}
<dt>GitHub</dt>
<dd> <dd>
{{#subdir}} {{#subdir}}
<a href="https://github.com/{{githubRepo}}/tree/{{githubRev}}/{{subdir}}">https://github.com/{{githubRepo}}</a> <a href="https://github.com/{{githubRepo}}/tree/{{githubRev}}/{{subdir}}">https://github.com/{{githubRepo}}</a>

View File

@ -12,66 +12,66 @@
<link rel="stylesheet" href="https://cdn.datatables.net/v/bs5/dt-1.12.1/datatables.min.css" integrity="sha384-4+3RMUungd+Oh0juS2Jl3yQ7mPlVfsgr10NLSsqltLXIECi9ExY4d1cTtmf9aN8E" crossorigin="anonymous"> <link rel="stylesheet" href="https://cdn.datatables.net/v/bs5/dt-1.12.1/datatables.min.css" integrity="sha384-4+3RMUungd+Oh0juS2Jl3yQ7mPlVfsgr10NLSsqltLXIECi9ExY4d1cTtmf9aN8E" crossorigin="anonymous">
<script src="https://cdn.datatables.net/v/bs5/jq-3.6.0/dt-1.12.1/datatables.min.js" integrity="sha384-eU1uLDC5C4YCIouMauJZjbrnSmIiICWWAPdnZjRkNnuDxG+eJFN/EhW5GlKPAIVl" crossorigin="anonymous"></script> <script src="https://cdn.datatables.net/v/bs5/jq-3.6.0/dt-1.12.1/datatables.min.js" integrity="sha384-eU1uLDC5C4YCIouMauJZjbrnSmIiICWWAPdnZjRkNnuDxG+eJFN/EhW5GlKPAIVl" crossorigin="anonymous"></script>
<title> <title>
Repository's content Index summary
</title> </title>
</head> </head>
<body> <body>
<div class="container px-4 py-5"> <div class="container px-4 py-5">
<nav aria-label="breadcrumb"> <nav aria-label="breadcrumb">
<ol class="breadcrumb"> <ol class="breadcrumb">
<li class="breadcrumb-item active" aria-current="page"><a href="/">Home</a></li> <li class="breadcrumb-item active" aria-current="page"><a href="/summary/">Summary</a></li>
</ol> </ol>
</nav> </nav>
<h1 class="py-5"> <h1 class="py-5">
Repository's entries Index summary
</h1> </h1>
<table id="entries" class="table table-hover"> <p>
This page lists the latest version and revision of all packages in the index.
</p>
<table class="table table-hover">
<thead> <thead>
<tr> <tr>
<th>Package identifier</th> <th>Package</th>
<th>Type</th> <th>Added on</th>
<th>Timestamp</th>
<th>Source</th> <th>Source</th>
<th>Revision</th>
</th> </th>
</thead> </thead>
<tbody> <tbody>
{{#entries}} {{#packages}}
{{#IndexEntryPackage}}
<tr> <tr>
<td class="col-sm-2"><a href="package/{{indexEntryPkgId}}">{{indexEntryPkgId}}</a></td> <td class="col-sm-2"><a href="/package/{{summaryPageEntryPkgId}}">{{summaryPageEntryPkgId}}</a></td>
<td class="col-sm-1">package</td> <td class="col-sm-3" data-order="{{summaryPageEntryTimestampPosix}}">{{summaryPageEntryTimestamp}}</td>
<td class="col-sm-3" data-order="{{indexEntryTimestampPosix}}">{{indexEntryTimestamp}}</td>
<td class="col-sm-6"> <td class="col-sm-6">
<dl class="row"> <dl class="row">
{{#indexEntrySource}} {{#summaryPageEntrySource}}
{{> packageVersionSource}} {{> packageVersionSource}}
{{/indexEntrySource}} {{/summaryPageEntrySource}}
</dl> </dl>
</td> </td>
<td class="col-sm-3">
{{#summaryPageEntryRevision}}
Last revision at {{revisionTimestamp}}
{{/summaryPageEntryRevision}}
{{^summaryPageEntryRevision}}
No revision
{{/summaryPageEntryRevision}}
</td>
</tr> </tr>
{{/IndexEntryPackage}} {{/packages}}
{{#IndexEntryRevision}}
<tr>
<td>{{indexEntryPkgId}}</td>
<td>revision</td>
<td data-order="{{indexEntryTimestampPosix}}">{{indexEntryTimestamp}}</td>
<td></td>
</tr>
{{/IndexEntryRevision}}
{{/entries}}
</tbody> </tbody>
</table> </table>
</div> </div>
<script> <script>
$(document).ready(function () { $(document).ready(function () {
const urlParams = new URLSearchParams(location.search); const table = $('table').DataTable({
$('#entries').DataTable({ order: [[0, 'asc']],
order: [[2, 'desc']], lengthMenu: [ [50, 100, 500, -1], [50, 100, 500, "All"] ]
lengthMenu: [ [50, 100, 500, -1], [50, 100, 500, "All"] ], })
search: { const urlParams = new URLSearchParams(location.search)
search: urlParams.has('q') ? urlParams.get('q') : null, if (urlParams.has('q')) {
} table.search(urlParams.get('q')).draw()
}); }
}); });
</script> </script>
</body> </body>

View File

@ -0,0 +1,83 @@
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.1.3/css/bootstrap.min.css" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.1.3/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
<!-- DataTables -->
<link rel="stylesheet" href="https://cdn.datatables.net/v/bs5/dt-1.12.1/datatables.min.css" integrity="sha384-4+3RMUungd+Oh0juS2Jl3yQ7mPlVfsgr10NLSsqltLXIECi9ExY4d1cTtmf9aN8E" crossorigin="anonymous">
<!-- <script src="https://cdn.datatables.net/v/bs5/jq-3.6.0/dt-1.12.1/datatables.min.js" integrity="sha384-eU1uLDC5C4YCIouMauJZjbrnSmIiICWWAPdnZjRkNnuDxG+eJFN/EhW5GlKPAIVl" crossorigin="anonymous"></script>
-->
<script src="https://cdn.datatables.net/v/bs5/jq-3.6.0/dt-1.12.1/datatables.js"></script>
<title>
Index timeline
</title>
</head>
<body>
<div class="container px-4 py-5">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item active" aria-current="page"><a href="/timeline/">Timeline</a></li>
</ol>
</nav>
<h1 class="py-5">
Index timeline
</h1>
<p>
This page lists all package versions and revisions in the index.
</p>
<table class="table table-hover">
<thead>
<tr>
<th>Package</th>
<th>Version/Revision</th>
<th>Added on</th>
<th>Source</th>
</th>
</thead>
<tbody>
{{#entries}}
{{#TimelinePageEntryPackage}}
<tr>
<td class="col-sm-2"><a href="/package/{{timelinePageEntryPkgId}}">{{timelinePageEntryPkgId}}</a></td>
<td class="col-sm-1">Version</td>
<td class="col-sm-3" data-order="{{timelinePageEntryTimestampPosix}}">{{timelinePageEntryTimestamp}}</td>
<td class="col-sm-6">
<dl class="row">
{{#timelinePageEntrySource}}
{{> packageVersionSource}}
{{/timelinePageEntrySource}}
</dl>
</td>
</tr>
{{/TimelinePageEntryPackage}}
{{#TimelinePageEntryRevision}}
<tr>
<td>{{timelinePageEntryPkgId}}</td>
<td>Revision</td>
<td data-order="{{timelinePageEntryTimestampPosix}}">{{timelinePageEntryTimestamp}}</td>
<td></td>
</tr>
{{/TimelinePageEntryRevision}}
{{/entries}}
</tbody>
</table>
</div>
<script>
$(document).ready(function () {
const table = $('table').DataTable({
order: [[2, 'desc']],
lengthMenu: [ [50, 100, 500, -1], [50, 100, 500, "All"] ]
})
const urlParams = new URLSearchParams(location.search)
if (urlParams.has('q')) {
table.search(urlParams.get('q')).draw()
}
});
</script>
</body>
</html>