haskus-system-build-1.1: src/apps/Haskus/Apps/System/Build/Linux.hs
{-# LANGUAGE LambdaCase #-}
module Haskus.Apps.System.Build.Linux
( linuxMain
, linuxBuild
, linuxKernelFile
, linuxDownloadTarball
, linuxCheckTarball
, linuxMakeTarballName
, linuxMakeTarballPath
)
where
import Data.Text (Text)
import qualified Data.Text as Text
import Control.Monad
import System.FilePath
import System.Directory
import System.IO.Temp
import Numeric (showHex)
import Data.Word
import Haskus.Apps.System.Build.Config
import Haskus.Apps.System.Build.Utils
import Haskus.Utils.Flow
linuxMain :: LinuxConfig -> IO ()
linuxMain config = do
case linuxSource config of
LinuxGit {} -> do
failWith "Building Linux from GIT is not supported for now"
LinuxTarball version -> do
tgtfp <- linuxMakeReleasePath config
tgtker <- linuxKernelFile config
let
tgtmod = tgtfp </> "modules"
tgtmod' = tgtfp </> "modules_tmp"
-- check if we already have the built kernel
unlessM (doesFileExist tgtker) $ do
-- check if we already have the tarball; otherwise download it
linuxCheckTarball version >>= \case
False -> do
linuxDownloadTarball version
-- re-check (for safety)
linuxCheckTarball version >>= \case
False -> failWith "Unable to download Linux sources"
True -> return ()
True -> return ()
tarballPath <- linuxMakeTarballPath version
withSystemTempDirectory "haskus-system-build" $ \fp -> do
-- untar
showStep "Unpacking Linux archive..."
untar tarballPath fp
let fp2 = fp </> ("linux-"++Text.unpack version)
-- configure
showStep "Configuring Linux..."
linuxConfigure config fp2
-- build
showStep "Building Linux..."
linuxBuild config fp2
createDirectoryIfMissing True tgtmod'
-- copy the kernel, modules and firmwares
showStep "Copying kernel..."
copyFile (fp2 </> "arch" </> "x86" </> "boot" </> "bzImage") tgtker
showStep "Copying modules..."
shellInErr fp2 ("make modules_install INSTALL_MOD_PATH="++tgtmod') $
failWith "Cannot copy Linux modules"
-- modules are usually in lib/modules/<kernel_version> but for some
-- versions the path is incorrect
-- e.g. "5.0" => "/lib/modules/5.0.0"
-- So we try to find the only directory in "/lib/modules" instead
let
modPathPrefix = tgtmod' </> "lib" </> "modules"
tgtModPath = modPathPrefix </> Text.unpack version
doesDirectoryExist tgtModPath >>= \case
True -> renameDirectory tgtModPath tgtmod
False -> listDirectory modPathPrefix >>= \case
[r] -> renameDirectory (modPathPrefix </> r) tgtmod
[] -> return ()
rs -> putStrLn $ "Warning: found several module directories!?\n" ++ show rs
removeDirectory (tgtmod' </> "lib" </> "modules")
removeDirectory (tgtmod' </> "lib")
removeDirectory tgtmod'
removeFile (tgtmod </> "source")
removeFile (tgtmod </> "build")
showStep "Copying .config file..."
copyFile (fp2 </> ".config") (tgtfp </> ".config")
-- | Build Linux tree in the given directory
linuxBuild :: LinuxConfig -> FilePath -> IO ()
linuxBuild config path = do
let shell' = shellInErr path
shell' ("make " ++ Text.unpack (linuxMakeArgs config))
$ fail "Unable to build Linux"
-- | Configure Linux tree in the given directory
linuxConfigure :: LinuxConfig -> FilePath -> IO ()
linuxConfigure config path = do
let shell' = shellInErr path
-- make default configuration for the arch
shell' "make x86_64_defconfig"
$ fail "Unable to build Linux default configuration"
-- enable/disable/module options
let opts = linuxOptions config
forM_ (enableOptions opts) $ \opt ->
shell' ("./scripts/config -e "++ Text.unpack opt)
$ fail $ "Unable to enable Linux option: " ++ Text.unpack opt
forM_ (disableOptions opts) $ \opt ->
shell' ("./scripts/config -d "++ Text.unpack opt)
$ fail $ "Unable to disable Linux option: " ++ Text.unpack opt
forM_ (moduleOptions opts) $ \opt ->
shell' ("./scripts/config -m "++ Text.unpack opt)
$ fail $ "Unable to modularize Linux option: " ++ Text.unpack opt
-- fixup config
shell' "make olddefconfig"
$ fail "Unable to adjust Linux configuration"
-- | Make Linux archive name
linuxMakeTarballName :: Text -> FilePath
linuxMakeTarballName version = "linux-"++Text.unpack version++".tar.xz"
-- | Make Linux archive path
linuxMakeTarballPath :: Text -> IO FilePath
linuxMakeTarballPath version = do
p <- getDownloadPath
return (p </> linuxMakeTarballName version)
-- | Make Linux release path
linuxMakeReleasePath :: LinuxConfig -> IO FilePath
linuxMakeReleasePath config = do
p <- getAppDir
let hash = showHex (fromIntegral (linuxConfigHash config) :: Word64) ""
let d = p </> "linux" </> hash
createDirectoryIfMissing True d
return d
linuxKernelFile :: LinuxConfig -> IO FilePath
linuxKernelFile config = do
rp <- linuxMakeReleasePath config
return (rp </> "linux.bin")
-- | Download a Linux tarball from kernel.org
linuxDownloadTarball :: Text -> IO ()
linuxDownloadTarball version = do
let
src = "https://cdn.kernel.org/pub/linux/kernel/v"
++ Text.unpack (head (Text.splitOn (Text.pack ".") version))
++ ".x/linux-"
++ Text.unpack version
++ ".tar.xz"
-- download
showStep $ "Downloading Linux "++Text.unpack version++"..."
tgtDir <- getDownloadPath
download src (tgtDir </> linuxMakeTarballName version)
-- check signature
-- TODO
-- | Check if we already have a tarball
linuxCheckTarball :: Text -> IO Bool
linuxCheckTarball version = do
tgtDir <- getDownloadPath
doesFileExist (tgtDir </> linuxMakeTarballName version)