slackbuilder/app/Main.hs

434 lines
19 KiB
Haskell
Raw Normal View History

module Main
( main
) where
import Data.Char (isNumber)
import Control.Applicative (Applicative(liftA2))
2023-09-03 10:26:43 +02:00
import Data.List.NonEmpty (NonEmpty(..))
2023-10-01 17:19:06 +02:00
import qualified Data.List.NonEmpty as NonEmpty
2023-09-03 10:26:43 +02:00
import Control.Monad.IO.Class (MonadIO(..))
2023-10-20 19:23:21 +02:00
import Data.Maybe (fromJust)
import qualified Data.Map as Map
import Options.Applicative (execParser)
import SlackBuilder.CommandLine
2023-08-09 20:59:42 +02:00
import SlackBuilder.Config
2023-08-15 10:33:19 +02:00
import SlackBuilder.Trans
import SlackBuilder.Updater
2023-08-09 20:59:42 +02:00
import qualified Toml
2023-10-04 22:36:19 +02:00
import qualified Data.ByteString as ByteString
2023-09-03 10:26:43 +02:00
import Data.Text (Text)
2023-08-09 20:59:42 +02:00
import qualified Data.Text as Text
2023-09-03 10:26:43 +02:00
import qualified Data.Text.IO as Text.IO
import Control.Monad.Trans.Reader (ReaderT(..), asks)
2023-08-15 10:33:19 +02:00
import SlackBuilder.Download
2023-10-05 19:24:42 +02:00
import SlackBuilder.Package (Package(..))
2023-09-03 10:26:43 +02:00
import qualified SlackBuilder.Package as Package
import Text.URI (URI(..), mkURI)
import Text.URI.QQ (uri)
import Data.Foldable (Foldable(..), for_, find)
2023-09-03 10:26:43 +02:00
import qualified Text.URI as URI
2023-10-20 19:23:21 +02:00
import System.FilePath ((</>), (<.>), dropExtension, takeBaseName, makeRelative, splitFileName)
2023-10-04 22:36:19 +02:00
import SlackBuilder.Info
import Text.Megaparsec (parse, errorBundlePretty)
2023-10-08 12:28:46 +02:00
import GHC.Records (HasField(..))
import System.Process
( CmdSpec(..)
, CreateProcess(..)
, StdStream(..)
, callProcess
, withCreateProcess
, waitForProcess
)
2023-10-20 19:23:21 +02:00
import System.Console.ANSI
( setSGR
, SGR(..)
, ColorIntensity(..)
, Color(..)
, ConsoleLayer(..)
)
import System.Directory (listDirectory, doesDirectoryExist)
import Control.Monad (filterM)
import Data.List (isPrefixOf, isSuffixOf)
2023-09-03 10:26:43 +02:00
autoUpdatable :: [Package]
autoUpdatable =
[ Package
{ latest =
let ghArguments = GhArguments{ owner = "universal-ctags", name = "ctags", transform = Nothing}
latest' = latestGitHub ghArguments pure
templateTail =
[ Package.VersionPlaceholder
, Package.StaticPlaceholder "/ctags-"
2023-09-03 10:26:43 +02:00
, Package.VersionPlaceholder
, Package.StaticPlaceholder ".tar.gz"
]
template = Package.DownloadTemplate
2023-10-01 17:19:06 +02:00
$ Package.StaticPlaceholder "https://github.com/universal-ctags/ctags/archive/"
:| templateTail
in Package.Updater latest' $ reuploadWithTemplate template []
2023-09-03 10:26:43 +02:00
, category = "development"
, name = "universal-ctags"
, downloaders = mempty
2023-10-01 17:19:06 +02:00
}
, Package
{ latest =
let packagistArguments = PackagistArguments{ vendor = "composer", name = "composer" }
latest' = latestPackagist packagistArguments
template = Package.DownloadTemplate
$ Package.StaticPlaceholder "https://getcomposer.org/download/"
:| [Package.VersionPlaceholder, Package.StaticPlaceholder "/composer.phar"]
in Package.Updater latest' $ downloadWithTemplate template
2023-10-01 17:19:06 +02:00
, category = "development"
, name = "composer"
, downloaders = mempty
2023-09-03 10:26:43 +02:00
}
2023-10-03 18:53:41 +02:00
, Package
{ latest =
let ghArguments = GhArguments
{ owner = "jitsi"
, name = "jitsi-meet-electron"
, transform = Nothing
}
latest' = latestGitHub ghArguments $ Text.stripPrefix "v"
template = Package.DownloadTemplate
$ Package.StaticPlaceholder "https://github.com/jitsi/jitsi-meet-electron/releases/download/v"
:| Package.VersionPlaceholder
: [Package.StaticPlaceholder "/jitsi-meet-x86_64.AppImage"]
in Package.Updater latest' $ downloadWithTemplate template
2023-10-03 18:53:41 +02:00
, category = "network"
, name = "jitsi-meet-desktop"
, downloaders = mempty
2023-10-03 18:53:41 +02:00
}
2023-10-04 22:36:19 +02:00
, Package
{ latest =
let ghArguments = GhArguments
{ owner = "php"
, name = "php-src"
, transform = Nothing
}
checkVersion x
2023-10-13 19:34:02 +02:00
| not $ Text.isInfixOf "RC" x
, Text.isPrefixOf "php-8.2." x = Text.stripPrefix "php-" x
2023-10-04 22:36:19 +02:00
| otherwise = Nothing
latest' = latestGitHub ghArguments checkVersion
template = Package.DownloadTemplate
$ Package.StaticPlaceholder "https://www.php.net/distributions/php-"
:| Package.VersionPlaceholder
: [Package.StaticPlaceholder ".tar.xz"]
in Package.Updater latest' $ downloadWithTemplate template
2023-10-04 22:36:19 +02:00
, category = "development"
, name = "php82"
, downloaders = mempty
2023-10-08 12:28:46 +02:00
}
, Package
{ latest =
let ghArguments = GhArguments
{ owner = "kovidgoyal"
, name = "kitty"
, transform = Nothing
}
latest' = latestGitHub ghArguments $ Text.stripPrefix "v"
templateTail =
[ Package.StaticPlaceholder "/kitty-"
, Package.VersionPlaceholder
, Package.StaticPlaceholder ".tar.xz"
]
template = Package.DownloadTemplate
$ Package.StaticPlaceholder "https://github.com/kovidgoyal/kitty/releases/download/v"
:| Package.VersionPlaceholder
: templateTail
in Package.Updater latest' $ reuploadWithTemplate template [RawCommand "go" ["mod", "vendor"]]
2023-10-08 12:28:46 +02:00
, category = "system"
, name = "kitty"
, downloaders = mempty
2023-10-04 22:36:19 +02:00
}
2023-10-13 19:34:02 +02:00
, Package
{ latest =
let ghArguments = GhArguments
{ owner = "rdiff-backup"
, name = "rdiff-backup"
, transform = Nothing
}
latest' = latestGitHub ghArguments $ Text.stripPrefix "v"
template = Package.DownloadTemplate
$ Package.StaticPlaceholder "https://github.com/rdiff-backup/rdiff-backup/releases/download/v"
:| Package.VersionPlaceholder
: Package.StaticPlaceholder "/rdiff-backup-"
: Package.VersionPlaceholder
: [Package.StaticPlaceholder ".tar.gz"]
in Package.Updater latest' $ reuploadWithTemplate template []
2023-10-13 19:34:02 +02:00
, category = "system"
, name = "rdiff-backup"
, downloaders = mempty
2023-10-13 19:34:02 +02:00
}
, Package
{ latest =
let needle = "Linux—"
textArguments = TextArguments
{ textURL = "https://help.webex.com/en-us/article/mqkve8/Webex-App-%7C-Release-notes"
, versionPicker = Text.takeWhile (liftA2 (||) (== '.') isNumber)
. Text.drop (Text.length needle)
. snd
. Text.breakOn needle
}
latest' = latestText textArguments
template = Package.DownloadTemplate $ pure
$ Package.StaticPlaceholder
"https://binaries.webex.com/WebexDesktop-Ubuntu-Official-Package/Webex.deb"
in Package.Updater latest' $ downloadWithTemplate template
, category = "network"
, name = "webex"
, downloaders = mempty
}
2023-10-20 19:23:21 +02:00
, Package
{ latest =
let ghArguments = GhArguments
{ owner = "librsync"
, name = "librsync"
, transform = Nothing
}
latest' = latestGitHub ghArguments $ Text.stripPrefix "v"
template = Package.DownloadTemplate
$ Package.StaticPlaceholder "https://github.com/librsync/librsync/archive/v"
:| Package.VersionPlaceholder
: Package.StaticPlaceholder "/librsync-"
: Package.VersionPlaceholder
: [Package.StaticPlaceholder ".tar.gz"]
in Package.Updater latest' $ reuploadWithTemplate template []
2023-10-20 19:23:21 +02:00
, category = "libraries"
, name = "librsync"
, downloaders = mempty
2023-10-20 19:23:21 +02:00
}
, Package
{ latest =
let textArguments = TextArguments
{ textURL = "https://downloads.dlang.org/releases/LATEST"
, versionPicker = Text.strip
}
latest' = latestText textArguments
template = Package.DownloadTemplate
$ Package.StaticPlaceholder "https://downloads.dlang.org/releases/2.x/"
:| Package.VersionPlaceholder
: Package.StaticPlaceholder "/dmd."
: Package.VersionPlaceholder
: [Package.StaticPlaceholder ".linux.tar.xz"]
in Package.Updater latest' $ downloadWithTemplate template
2023-10-20 19:23:21 +02:00
, category = "development"
, name = "dmd"
, downloaders = mempty
}
, Package
{ latest =
let textArguments = TextArguments
{ textURL = "https://downloads.dlang.org/releases/LATEST"
, versionPicker = Text.strip
}
latest' = latestText textArguments
template = Package.DownloadTemplate
$ Package.StaticPlaceholder "https://codeload.github.com/dlang/tools/tar.gz/v"
:| [Package.VersionPlaceholder]
in Package.Updater latest' $ reuploadWithTemplate template []
, category = "development"
, name = "d-tools"
, downloaders =
let dubArguments = GhArguments{ owner = "dlang", name = "dub", transform = Nothing}
dscannerArguments = GhArguments{ owner = "dlang-community", name = "D-Scanner", transform = Nothing }
dcdArguments = GhArguments{ owner = "dlang-community", name = "DCD", transform = Nothing }
latestDub = latestGitHub dubArguments pure
latestDscanner = latestGitHub dscannerArguments pure
latestDcd = latestGitHub dcdArguments pure
dubTemplate = Package.DownloadTemplate
$ Package.StaticPlaceholder "https://codeload.github.com/dlang/dub/tar.gz/v"
:| [Package.VersionPlaceholder]
dscannerURI = [uri|https://github.com/dlang-community/D-Scanner.git|]
dcdURI = [uri|https://github.com/dlang-community/DCD.git|]
in Map.fromList
[ ("DUB", Package.Updater latestDub $ downloadWithTemplate dubTemplate)
, ("DSCANNER", Package.Updater latestDscanner $ cloneFromGit dscannerURI "v")
, ("DCD", Package.Updater latestDcd $ cloneFromGit dcdURI "v")
]
2023-10-20 19:23:21 +02:00
}
2023-09-03 10:26:43 +02:00
]
up2Date :: SlackBuilderT ()
up2Date = for_ autoUpdatable go
where
2023-10-20 19:23:21 +02:00
go package = getAndLogLatest package
>>= mapM_ (updatePackageIfRequired package)
>> liftIO (putStrLn "")
getAndLogLatest Package{ latest = Package.Updater{ detectLatest }, name }
= liftIO (putStrLn $ Text.unpack name <> ": Retreiving the latest version.")
>> detectLatest
2023-09-03 10:26:43 +02:00
2023-10-04 22:36:19 +02:00
updatePackageIfRequired :: Package -> Text -> SlackBuilderT ()
updatePackageIfRequired package@Package{..} version = do
let packagePath = Text.unpack category </> Text.unpack name </> (Text.unpack name <.> "info")
repository' <- SlackBuilderT $ asks repository
infoContents <- liftIO $ ByteString.readFile $ repository' </> packagePath
case parse parseInfoFile packagePath infoContents of
2023-10-08 12:28:46 +02:00
Right parsedInfoFile
| version == getField @"version" parsedInfoFile ->
2023-10-20 19:23:21 +02:00
liftIO $ do
setSGR [SetColor Foreground Dull Green]
Text.IO.putStrLn
$ name <> " is up to date (Version " <> version <> ")."
setSGR [Reset]
| otherwise -> do
liftIO $ do
setSGR [SetColor Foreground Dull Yellow]
Text.IO.putStrLn
$ "A new version of "
<> name <> " " <> getField @"version" parsedInfoFile
<> " is available (" <> version <> ")."
setSGR [Reset]
updatePackage package parsedInfoFile version
2023-10-04 22:36:19 +02:00
Left errorBundle -> liftIO $ putStr $ errorBundlePretty errorBundle
updateDownload :: Package -> Package.Updater -> SlackBuilderT (Package.Download, Text)
updateDownload Package{..} Package.Updater{..} = do
latestDownloadVersion <- fromJust <$> detectLatest
result <- getVersion (Text.pack $ Text.unpack category </> Text.unpack name) latestDownloadVersion
pure (result, latestDownloadVersion)
2023-10-28 04:04:52 +02:00
cloneFromGit :: URI -> Text -> Text -> Text -> SlackBuilderT Package.Download
2023-10-28 04:04:52 +02:00
cloneFromGit repo tagPrefix packagePath version = do
repository' <- SlackBuilderT $ asks repository
let downloadFileName = URI.unRText
$ NonEmpty.last $ snd $ fromJust $ URI.uriPath repo
relativeTarball = Text.unpack packagePath
</> (dropExtension (Text.unpack downloadFileName) <> "-" <> Text.unpack version)
tarball = repository' </> relativeTarball
name' = Text.pack (takeBaseName $ Text.unpack packagePath)
checksum <- clone (URI.render repo) (Text.pack tarball) tagPrefix
uploadCommand (Text.pack relativeTarball) ("/" <> name')
(flip . flip Package.Download) (fromJust checksum) False
2023-10-28 04:04:52 +02:00
<$> liftIO (mkURI $ "https://download.dlackware.com/hosted-sources/" <> name' <> "/" <> downloadFileName)
2023-09-03 10:26:43 +02:00
downloadWithTemplate :: Package.DownloadTemplate -> Text -> Text -> SlackBuilderT Package.Download
downloadWithTemplate downloadTemplate packagePath version = do
repository' <- SlackBuilderT $ asks repository
2023-09-03 10:26:43 +02:00
uri' <- liftIO $ Package.renderDownloadWithVersion downloadTemplate version
2023-10-01 17:19:06 +02:00
let downloadFileName = URI.unRText
$ NonEmpty.last $ snd $ fromJust $ URI.uriPath uri'
relativeTarball = packagePath <> "/" <> downloadFileName
tarball = repository' </> Text.unpack relativeTarball
2023-10-01 17:19:06 +02:00
checksum <- fromJust <$> download uri' tarball
pure $ Package.Download uri' checksum False
reuploadWithTemplate :: Package.DownloadTemplate -> [CmdSpec] -> Text -> Text -> SlackBuilderT Package.Download
reuploadWithTemplate downloadTemplate commands packagePath version = do
Package.Download{ download = uri', md5sum = checksum } <- downloadWithTemplate downloadTemplate packagePath version
let downloadFileName = URI.unRText
$ NonEmpty.last $ snd $ fromJust $ URI.uriPath uri'
relativeTarball = packagePath <> "/" <> downloadFileName
download' <- handleReupload relativeTarball downloadFileName
2023-09-03 10:26:43 +02:00
pure $ Package.Download download' checksum False
2023-10-01 17:19:06 +02:00
where
name' = Text.pack $ takeBaseName $ Text.unpack packagePath
handleReupload relativeTarball downloadFileName = do
2023-10-08 12:28:46 +02:00
repository' <- SlackBuilderT $ asks repository
case commands of
[] -> uploadTarball relativeTarball downloadFileName
_ ->
2023-10-08 12:28:46 +02:00
let tarballPath = repository' </> Text.unpack relativeTarball
packedDirectory = takeBaseName $ dropExtension tarballPath
in liftIO (callProcess "tar" ["xvf", tarballPath])
>> liftIO (traverse (defaultCreateProcess packedDirectory) commands)
>> liftIO (callProcess "tar" ["Jcvf", tarballPath, packedDirectory])
>> uploadTarball relativeTarball downloadFileName
uploadTarball relativeTarball downloadFileName
= liftIO (putStrLn $ "Upload the source tarball " <> Text.unpack relativeTarball)
>> uploadCommand relativeTarball ("/" <> name')
>> liftIO (mkURI $ "https://download.dlackware.com/hosted-sources/" <> name' <> "/" <> downloadFileName)
2023-10-08 12:28:46 +02:00
defaultCreateProcess cwd' cmdSpec
= flip withCreateProcess (const . const . const waitForProcess)
$ CreateProcess
{ use_process_jobs = False
, std_out = Inherit
, std_in = NoStream
, std_err = Inherit
, new_session = False
, env = Nothing
, detach_console = False
, delegate_ctlc = False
, cwd = Just cwd'
, create_new_console = False
, create_group = False
, cmdspec = cmdSpec
, close_fds = True
, child_user = Nothing
, child_group = Nothing
}
renderAndDownload :: Package -> Text -> SlackBuilderT Package.Download
renderAndDownload Package{..} version = do
let packagePath = category <> "/" <> name
Package.Updater _ getVersion = latest
getVersion packagePath version
updatePackage :: Package -> PackageInfo -> Text -> SlackBuilderT ()
updatePackage package@Package{..} info version = do
let packagePath = category <> "/" <> name
repository' <- SlackBuilderT $ asks repository
mainDownload <- renderAndDownload package version
moreDownloads <- traverse (updateDownload package) downloaders
let allDownloads = mainDownload : toList (fst <$> moreDownloads)
let infoFilePath = repository' </> Text.unpack packagePath
</> (Text.unpack name <.> "info")
package' = info
{ version = version
, downloads = getField @"download" <$> allDownloads
, checksums = getField @"md5sum" <$> allDownloads
}
liftIO $ Text.IO.writeFile infoFilePath $ generate package'
updateSlackBuildVersion packagePath version $ snd <$> moreDownloads
commit packagePath version
2023-10-01 17:19:06 +02:00
2023-10-20 19:23:21 +02:00
findCategory :: FilePath -> IO [FilePath]
findCategory currentDirectory = do
contents <- liftIO $ listDirectory currentDirectory
case find (isSuffixOf ".info") contents of
Just _ -> pure [currentDirectory]
Nothing -> do
let contents' = (currentDirectory </>) <$> filter (not . isPrefixOf ".") contents
directories <- filterM doesDirectoryExist contents'
subCategories <- traverse findCategory directories
pure $ concat subCategories
main :: IO ()
main = do
programCommand <- execParser slackBuilderParser
2023-08-09 20:59:42 +02:00
settings <- Toml.decodeFile settingsCodec "config/config.toml"
2023-08-15 10:33:19 +02:00
latestVersion <- flip runReaderT settings
$ runSlackBuilderT
$ executeCommand programCommand
2023-10-20 19:23:21 +02:00
maybe (pure ()) Text.IO.putStrLn latestVersion
2023-08-09 20:59:42 +02:00
where
2023-08-15 10:33:19 +02:00
executeCommand = \case
2023-10-20 19:23:21 +02:00
CategoryCommand _packageName -> do
repository' <- SlackBuilderT $ asks repository
categories <- liftIO $ findCategory repository'
liftIO $ print $ splitFileName . makeRelative repository' <$> categories
pure Nothing
2023-08-15 10:33:19 +02:00
CommitCommand packagePath version ->
commit packagePath version >> pure Nothing
ExistsCommand urlPath -> pure . Text.pack . show
<$> remoteFileExists urlPath
ArchiveCommand repo nameVersion tarball tagPrefix ->
cloneAndArchive repo nameVersion tarball tagPrefix >> pure Nothing
2023-08-25 10:30:24 +02:00
DownloadCommand url target
2023-09-03 10:26:43 +02:00
| Just uri' <- mkURI url -> fmap (Text.pack . show)
<$> download uri' target
2023-08-25 10:30:24 +02:00
| otherwise -> pure Nothing
CloneCommand repo tarball tagPrefix -> fmap (Text.pack . show)
<$> clone repo tarball tagPrefix
2023-09-03 10:26:43 +02:00
DownloadAndDeployCommand uri' tarball -> fmap (Text.pack . show)
<$> downloadAndDeploy uri' tarball
Up2DateCommand -> up2Date >> pure Nothing