Skip to content
Open
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
44 changes: 34 additions & 10 deletions hackage-security/src/Hackage/Security/TUF/Layout/Index.hs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@ module Hackage.Security.TUF.Layout.Index (
) where

import Prelude
import Data.Char (ord)
import Data.Kind (Type)
import Distribution.Package
import Distribution.Text
import Distribution.Types.Version (mkVersion)
import System.FilePath.Posix (isPathSeparator)

import Hackage.Security.TUF.Paths
import Hackage.Security.TUF.Signed
Expand Down Expand Up @@ -91,17 +94,38 @@ hackageIndexLayout = IndexLayout {
fromFragments :: [String] -> IndexPath
fromFragments = rootPath . joinFragments

-- This function is called in a hot loop, take care when modifying it.
-- Especially avoid 'splitFragments' / 'splitDirectories' and other high-level helpers,
-- they are very slow.
fromPath :: IndexPath -> Maybe (Some IndexFile)
fromPath fp = case splitFragments (unrootPath fp) of
[pkg, version, _file] -> do
pkgId <- simpleParse (pkg ++ "-" ++ version)
case takeExtension fp of
".cabal" -> return $ Some $ IndexPkgCabal pkgId
".json" -> return $ Some $ IndexPkgMetadata pkgId
_otherwise -> Nothing
[pkg, "preferred-versions"] ->
Some . IndexPkgPrefs <$> simpleParse pkg
_otherwise -> Nothing
fromPath fp = case break isPathSeparator (toUnrootedFilePath (unrootPath fp)) of
(pkg, "/preferred-versions") ->
return $ Some $ IndexPkgPrefs $ mkPackageName pkg
(pkg, rest) -> case break isPathSeparator (drop 1 rest) of
(_, []) -> Nothing
(version, basename) -> do
let pkgName = mkPackageName pkg
pkgVersion = mkVersion $ readVersion version
pkgId = PackageIdentifier { pkgName, pkgVersion }
case reverse basename of
-- ".cabal" reversed
'l' : 'a' : 'b' : 'a' : 'c' : '.' : _ ->
return $ Some $ IndexPkgCabal pkgId
-- ".json" reversed
'n' : 'o' : 's' : 'j' : '.' : _ ->
return $ Some $ IndexPkgMetadata pkgId
_ -> Nothing
Comment on lines +110 to +117

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this faster than isSuffixOf?

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.

Yes, at least twice faster because you need to check for two extensions. Here is a benchmark:

#!/usr/bin/env cabal
{- cabal:
build-depends: base, deepseq, tasty-bench
default-language: GHC2021
-}

import Control.DeepSeq
import Data.List
import GHC.Generics
import Test.Tasty.Bench

data Extension = Cabal | Json | Other
  deriving (Eq, Show, Generic)

instance NFData Extension

classifyExtensionUsingIsSuffix :: FilePath -> Extension
classifyExtensionUsingIsSuffix xs
  | ".cabal" `isSuffixOf` xs = Cabal
  | ".json" `isSuffixOf` xs = Json
  | otherwise = Other

classifyExtensionUsingReverse :: FilePath -> Extension
classifyExtensionUsingReverse xs = case reverse xs of
  'l' : 'a' : 'b' : 'a' : 'c' : '.' : _ -> Cabal
  'n' : 'o' : 's' : 'j' : '.' : _ -> Json
  _ -> Other

filepaths :: [FilePath]
filepaths = ["cabal-install.cabal", "cabal-install.json"]

main :: IO ()
main = do
  print $ map classifyExtensionUsingIsSuffix filepaths == map classifyExtensionUsingReverse filepaths
  defaultMain
    [ bench "isSuffix-based" $ nf (map classifyExtensionUsingIsSuffix) filepaths
    , bench "reverse-based" $ nf (map classifyExtensionUsingReverse) filepaths
    ]

Results:

True
All
  isSuffix-based: OK
    294  ns ±  26 ns
  reverse-based:  OK
    135  ns ±  13 ns


-- Convert "3.12.1.0" to [3,12,1,0].
-- Copied from hackage-revdeps package.
readVersion :: String -> [Int]
readVersion = (\(acc, _mult, rest) -> acc : rest) . foldr go (0, 1, [])
where
go c (acc, mult, rest)
| fromIntegral d < (10 :: Word) = (acc + d * mult, mult * 10, rest)
| otherwise = (0, 1, acc : rest)
where
d = ord c - ord '0'

{-------------------------------------------------------------------------------
Utility
Expand Down
Loading