Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion assets/css/3-screens/package.css
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,21 @@
display: flex;
flex-direction: column;
gap: var(--package-layout-gap);
}
}

.systemDependencies {
display: grid;
grid-template-columns: max-content max-content 1fr;
gap: var(--space-gutter-small) var(--space-gutter);
}

.systemDependencies > li {
display: grid;
grid-column: 1 / -1;
grid-template-columns: subgrid;
align-items: center;
}
Comment on lines +13 to +24

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not super confident about this grid layout…


.systemDependencies-kind {
justify-self: start;
}
3 changes: 3 additions & 0 deletions changelog.d/329
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
synopsis: Show the system dependencies of each component on the dependencies page
issues: #1384
prs: #1385
1 change: 1 addition & 0 deletions flora.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,7 @@ test-suite flora-test
tasty,
tasty-hunit,
text,
text-display,
time,
uuid,
vector,
Expand Down
39 changes: 39 additions & 0 deletions src/datatypes/Distribution/Orphans/ConfVar.hs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,18 @@ module Distribution.Orphans.ConfVar where
import Data.Aeson (camelTo2)
import Data.Aeson.TH
import Data.Dynamic (Typeable)
import Data.Text (Text)
import Data.Text qualified as Text
import Data.Text.Display
import Database.PostgreSQL.Simple.FromField (FromField (..), fromJSONField)
import Database.PostgreSQL.Simple.ToField (ToField (..), toJSONField)
import Deriving.Aeson
import Distribution.Pretty (prettyShow)
import Distribution.System (Arch, OS)
import Distribution.Types.Condition
import Distribution.Types.ConfVar
import Distribution.Types.Flag (unFlagName)
import Distribution.Version (isAnyVersion)

import Distribution.Orphans.CompilerFlavor ()
import Distribution.Orphans.PackageFlag ()
Expand All @@ -24,3 +30,36 @@ $(deriveJSON defaultOptions{fieldLabelModifier = camelTo2 '_', sumEncoding = Obj

instance (ToJSON c, Typeable c) => ToField (Condition c) where toField = toJSONField
instance (FromJSON c, Typeable c) => FromField (Condition c) where fromField = fromJSONField

instance Display ConfVar where
displayBuilder = \case
OS os -> displayBuilder ("os(" <> Text.pack (prettyShow os) <> ")")
Arch arch -> displayBuilder ("arch(" <> Text.pack (prettyShow arch) <> ")")
PackageFlag flagName -> displayBuilder ("flag(" <> Text.pack (unFlagName flagName) <> ")")
Impl compiler range ->
displayBuilder ("impl(" <> display compiler <> (if isAnyVersion range then "" else " " <> display range) <> ")")

data ConditionContext
= TopLevel
| InConjunction
| InDisjunction
| InNegation
deriving stock (Eq)

instance Display (Condition ConfVar) where
displayBuilder = displayBuilder . go TopLevel
where
go :: ConditionContext -> Condition ConfVar -> Text
go _ (Var confVar) = display confVar
go _ (Lit b) = if b then "true" else "false"
go _ (CNot condition) = "!" <> go InNegation condition
go context (CAnd left right) =
parenthesiseUnless (context `elem` [TopLevel, InConjunction]) $
go InConjunction left <> " && " <> go InConjunction right
go context (COr left right) =
parenthesiseUnless (context `elem` [TopLevel, InDisjunction]) $
go InDisjunction left <> " || " <> go InDisjunction right
Comment on lines +49 to +61

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pity that Cabal-syntax doesn't seem to export those.


parenthesiseUnless :: Bool -> Text -> Text
parenthesiseUnless True text = text
parenthesiseUnless False text = "(" <> text <> ")"
66 changes: 58 additions & 8 deletions src/web/FloraWeb/Components/PackageListItem.hs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module FloraWeb.Components.PackageListItem
)
where

import Control.Monad (unless)
import Data.Foldable (forM_, traverse_)
import Data.Map qualified as Map
import Data.Text (Text)
Expand All @@ -22,6 +23,11 @@ import Lucid
import Flora.Model.Component.Types (CanonicalComponent (..), ComponentType (..))
import Flora.Model.Package.Types (ElemRating (..), Namespace, PackageInfoWithExecutables (..), PackageName (..))
import Flora.Model.Requirement (ComponentDependencies, DependencyInfo (..))
import Flora.Model.SystemLibrary.Types
( ComponentSystemDependencies
, SystemDependencyInfo (..)
, SystemLibraryKind (..)
)
import FloraWeb.Components.Icons qualified as Icon
import FloraWeb.Components.PackageCard (PackageCardProps (..), packageCard)
import FloraWeb.Components.Utils
Expand Down Expand Up @@ -86,10 +92,10 @@ packageWithExecutableListItem PackageInfoWithExecutables{namespace, name, synops
Icon.terminal
toHtml element

requirementListItem :: UTCTime -> ComponentDependencies -> FloraHTML
requirementListItem now allComponentDeps =
requirementListItem :: UTCTime -> ComponentDependencies -> ComponentSystemDependencies -> FloraHTML
requirementListItem now allComponentDeps allSystemDeps =
forM_ [minBound .. maxBound] $ \componentType -> do
let components = filter (\(component, _) -> component.componentType == componentType) (Map.toList allComponentDeps)
let components = filter (\(component, _) -> component.componentType == componentType) (Map.toList allDeps)
case components of
[]
| componentType == Library ->
Expand All @@ -103,21 +109,65 @@ requirementListItem now allComponentDeps =
uncurry (componentTitle (componentType == Library)) firstComponent
traverse_ (uncurry (componentTitle False)) rest
where
allDeps :: Map.Map CanonicalComponent (Vector DependencyInfo, Vector SystemDependencyInfo)
allDeps =
Map.unionWith
(<>)
((,Vector.empty) <$> allComponentDeps)
((Vector.empty,) <$> allSystemDeps)

categoryTitle :: ComponentType -> FloraHTML
categoryTitle = h3_ [class_ "title-3"] . toHtml . componentCategoryName

componentTitle :: Bool -> CanonicalComponent -> Vector DependencyInfo -> FloraHTML
componentTitle isOpen component componentDeps = do
componentTitle :: Bool -> CanonicalComponent -> (Vector DependencyInfo, Vector SystemDependencyInfo) -> FloraHTML
componentTitle isOpen component (componentDeps, systemDeps) = do
let open = if isOpen then [open_ ""] else mempty
systemCount =
if Vector.null systemDeps
then ""
else ", " <> display (Vector.length systemDeps) <> " system"
details_ ([class_ "details--nobody"] <> open) $ do
summary_ [class_ "package-component"] $
h4_ [class_ "inline-block text-large color-raise"] $ do
toHtml component.componentName
span_ [class_ "text-small color-secondary"] $
toHtml $
" (" <> display (Vector.length componentDeps) <> " dependencies)"
ul_ [class_ "flow", role_ "list"] $
traverse_ (componentListItems now) componentDeps
" (" <> display (Vector.length componentDeps) <> " dependencies" <> systemCount <> ")"
unless (Vector.null componentDeps) $
ul_ [class_ "flow", role_ "list"] $
traverse_ (componentListItems now) componentDeps
unless (Vector.null systemDeps) $
systemDependencies systemDeps

systemDependencies :: Vector SystemDependencyInfo -> FloraHTML
systemDependencies systemDeps =
div_ [class_ "flow"] $ do
h5_ [class_ "text-small color-secondary"] "System dependencies"
ul_ [class_ "systemDependencies", role_ "list"] $
traverse_ systemDependencyListItem systemDeps

systemDependencyListItem :: SystemDependencyInfo -> FloraHTML
systemDependencyListItem SystemDependencyInfo{kind, name, requirement, condition = mCondition} =
li_ $ do
span_ [class_ ("badge systemDependencies-kind " <> kindBadgeClass kind)] $ do
span_ [class_ "sr-only"] "Kind: "
toHtml (display kind)
span_ [class_ "cluster"] $ do
span_ [class_ "color-raise"] (toHtml (display name))
forM_ requirement $ \range ->
span_ [class_ "color-secondary text-small"] $ do
span_ [class_ "sr-only"] "Version: "
toHtml range
forM_ mCondition $ \condition ->
span_ [class_ "text-small systemDependencies-condition"] $ do
span_ [class_ "sr-only"] "Condition: "
code_ [class_ "badge"] (toHtml (display condition))

kindBadgeClass :: SystemLibraryKind -> Text
kindBadgeClass = \case
PkgConfig -> "badge--info"
ExtraLibrary -> "badge--brand"
Framework -> "badge--valid"

componentCategoryName :: ComponentType -> Text
componentCategoryName = \case
Expand Down
8 changes: 6 additions & 2 deletions src/web/FloraWeb/Pages/Server/Packages.hs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import Flora.Model.PackageMaintainer.Query qualified as Query
import Flora.Model.PackageUploader.Query qualified as Query
import Flora.Model.Release.Query qualified as Query
import Flora.Model.Release.Types
import Flora.Model.SystemLibrary.Query qualified as Query
import Flora.Model.User (User)
import Flora.Monad
import FloraWeb.Common.Auth
Expand Down Expand Up @@ -390,9 +391,11 @@ showVersionDependenciesHandler (Headers session _) packageNamespace packageName
{ title = display packageNamespace <> " › " <> display packageName <> " › dependencies — Flora.pm"
, description = "Dependencies of " <> display packageNamespace <> display packageName
}
releaseDependencies <-
(releaseDependencies, systemDependencies) <-
withReadOnlyPool pool $
Query.getAllRequirements release.releaseId
(,)
<$> Query.getAllRequirements release.releaseId
<*> Query.getSystemDependencies release.releaseId

now <- Time.currentTime
isLatestViableRelease <- isLatestRelease package.packageId release.version
Expand All @@ -405,6 +408,7 @@ showVersionDependenciesHandler (Headers session _) packageNamespace packageName
numberOfDependents
package
releaseDependencies
systemDependencies
isLatestViableRelease

showChangelogHandler
Expand Down
6 changes: 4 additions & 2 deletions src/web/FloraWeb/Pages/Templates/Packages.hs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
import Flora.Model.PackageGroup.Types
import Flora.Model.Release.Types
import Flora.Model.Requirement
import Flora.Model.SystemLibrary.Types (ComponentSystemDependencies)
import FloraWeb.Components.AdvisoryListItem
import FloraWeb.Components.Icons qualified as Icons
import FloraWeb.Components.PackageCard
Expand Down Expand Up @@ -125,9 +126,10 @@
-> Word
-> Package
-> ComponentDependencies
-> ComponentSystemDependencies
-> Bool
-> FloraHTML
showDependencies now numberOfReleases latestRelease numberOfDependencies numberOfDependents package componentsInfo isLatestViableRelease = do
showDependencies now numberOfReleases latestRelease numberOfDependencies numberOfDependents package componentsInfo systemDependencies isLatestViableRelease = do
presentationHeader
numberOfReleases
latestRelease
Expand All @@ -140,7 +142,7 @@
isLatestViableRelease
section_ [class_ "wrapper inset-large flow", id_ "content"] $ do
h2_ [class_ "title-2"] "Dependencies"
requirementListItem now componentsInfo
requirementListItem now componentsInfo systemDependencies

listVersions
:: Release
Expand Down Expand Up @@ -380,7 +382,7 @@
rangedVersion = "^>=" <> majMin
majMin :: Doc
majMin =
if List.head (versionNumbers version) == 0

Check warning on line 385 in src/web/FloraWeb/Pages/Templates/Packages.hs

View workflow job for this annotation

GitHub Actions / Backend_tests (9.10.3, ubuntu-24.04)

In the use of ‘head’
then pretty $ mkVersion $ List.take 3 $ versionNumbers version
else pretty $ mkVersion $ List.take 2 $ versionNumbers version

Expand Down
43 changes: 43 additions & 0 deletions test/Flora/TemplateSpec.hs
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
module Flora.TemplateSpec where

import Data.Text.Display (display)
import Distribution.Compiler (CompilerFlavor (GHC))
import Distribution.System (Arch (I386, X86_64), OS (FreeBSD, Linux, Windows))
import Distribution.Types.Condition (Condition (..))
import Distribution.Types.ConfVar (ConfVar (..))
import Distribution.Types.Flag (mkFlagName)
import Distribution.Version qualified as Version
import RequireCallStack

import Distribution.Orphans.ConfVar ()
import Flora.Model.Package.Types
import Flora.TestUtils
import FloraWeb.Links qualified as Links
Expand All @@ -13,6 +20,7 @@ spec =
"templates"
[ testThis "Generate a link to a package + version" testGenerateVersionedPackageLink
, testThis "Generate a link to a namespace" testGenerateNamespaceLink
, testThis "Render cabal conditions" testRenderCondition
]

testGenerateVersionedPackageLink :: RequireCallStack => TestEff ()
Expand All @@ -28,3 +36,38 @@ testGenerateNamespaceLink = do
let namespace = Namespace "local-hackage"
let generatedLink = Links.renderLink $ Links.namespaceLink namespace 2
assertEqual_ "packages/@local-hackage?page=2" generatedLink

testRenderCondition :: RequireCallStack => TestEff ()
testRenderCondition = do
let flag = Var . PackageFlag . mkFlagName
assertEqual_
"flag(pkg-config) && !os(windows) && !arch(i386)"
(display (CAnd (flag "pkg-config") (CAnd (CNot (Var (OS Windows))) (CNot (Var (Arch I386))))))
assertEqual_
"(os(linux) || os(freebsd)) && flag(journald) && (flag(metal) || flag(gpu))"
( display
( CAnd
(COr (Var (OS Linux)) (Var (OS FreeBSD)))
(CAnd (flag "journald") (COr (flag "metal") (flag "gpu")))
)
)
assertEqual_
"flag(a) || (flag(b) && flag(c)) || flag(d)"
(display (COr (COr (flag "a") (CAnd (flag "b") (flag "c"))) (flag "d")))
assertEqual_
"flag(static) && !(os(windows) && arch(x86_64)) && !flag(openssl-pkgconfig)"
( display
( CAnd
(flag "static")
(CAnd (CNot (CAnd (Var (OS Windows)) (Var (Arch X86_64)))) (CNot (flag "openssl-pkgconfig")))
)
)
assertEqual_
"!impl(ghc >=9.6) || impl(ghc)"
( display
( COr
(CNot (Var (Impl GHC (Version.orLaterVersion (Version.mkVersion [9, 6])))))
(Var (Impl GHC Version.anyVersion))
)
)
assertEqual_ "true" (display (Lit @ConfVar True))
Loading