module Rules.BinaryDist where

import Hadrian.Haskell.Cabal

import Context
import Expression
import Oracles.Setting
import Packages
import Settings
import Target
import Utilities

bindistRules :: Rules ()
bindistRules = do
    root <- buildRootRules
    phony "binary-dist" $ do
        -- We 'need' all binaries and libraries
        targets <- mapM pkgTarget =<< stagePackages Stage1
        need targets
        version        <- setting ProjectVersion
        targetPlatform <- setting TargetPlatformFull
        hostOs         <- setting BuildOs
        hostArch       <- setting BuildArch
        rtsDir         <- pkgIdentifier rts

        let ghcBuildDir      = root -/- stageString Stage1
            bindistFilesDir  = root -/- "bindist" -/- ghcVersionPretty
            ghcVersionPretty = "ghc-" ++ version ++ "-" ++ targetPlatform
            distDir          = hostArch ++ "-" ++ hostOs ++ "-ghc-" ++ version
            rtsIncludeDir    = ghcBuildDir -/- "lib" -/- distDir -/- rtsDir
                               -/- "include"

        -- We create the bindist directory at <root>/bindist/ghc-X.Y.Z-platform/
        -- and populate it with Stage2 build results
        createDirectory bindistFilesDir
        copyDirectory (ghcBuildDir -/- "bin") bindistFilesDir
        copyDirectory (ghcBuildDir -/- "lib") bindistFilesDir
        copyDirectory (rtsIncludeDir)         bindistFilesDir
        {- TODO: Should we ship docs?
        need ["docs"]
        copyDirectory (root -/- "docs") bindistFilesDir -}

        -- We then 'need' all the files necessary to configure and install
        -- (as in, './configure [...] && make install') this build on some
        -- other machine.
        need $ map (bindistFilesDir -/-)
                   (["configure", "Makefile"] ++ bindistInstallFiles)
        need $ map ((bindistFilesDir -/- "wrappers") -/-) ["check-api-annotations"
                   , "check-ppr", "ghc", "ghc-iserv", "ghc-pkg", "ghc-split"
                   , "ghci-script", "ghci", "haddock", "hpc", "hp2ps", "hsc2hs"
                   , "runghc"]

        -- Finally, we create the archive <root>/bindist/ghc-X.Y.Z-platform.tar.xz
        command [Cwd $ root -/- "bindist"] "tar"
            [ "-c", "--xz", "-f"
            , ghcVersionPretty <.> "tar.xz"
            , ghcVersionPretty ]

    -- Prepare binary distribution configure script
    -- (generated under <ghc root>/distrib/configure by 'autoreconf')
    root -/- "bindist" -/- "ghc-*" -/- "configure" %> \configurePath -> do
        ghcRoot <- topDirectory
        copyFile (ghcRoot -/- "aclocal.m4") (ghcRoot -/- "distrib" -/- "aclocal.m4")
        buildWithCmdOptions [] $
            target (vanillaContext Stage1 ghc) (Autoreconf $ ghcRoot -/- "distrib") [] []
        -- We clean after ourselves, moving the configure script we generated in
        -- our bindist dir
        removeFile (ghcRoot -/- "distrib" -/- "aclocal.m4")
        moveFile (ghcRoot -/- "distrib" -/- "configure") configurePath

    -- Generate the Makefile that enables the "make install" part
    root -/- "bindist" -/- "ghc-*" -/- "Makefile" %> \makefilePath ->
        writeFile' makefilePath bindistMakefile

    root -/- "bindist" -/- "ghc-*" -/- "wrappers/*" %> \wrapperPath ->
        writeFile' wrapperPath $ wrapper (takeFileName wrapperPath)

    -- Copy various configure-related files needed for a working
    -- './configure [...] && make install' workflow
    -- (see the list of files needed in the 'binary-dist' rule above, before
    -- creating the archive).
    forM_ bindistInstallFiles $ \file ->
        root -/- "bindist" -/- "ghc-*" -/- file %> \dest -> do
            ghcRoot <- topDirectory
            copyFile (ghcRoot -/- fixup file) dest

  where
    fixup f | f `elem` ["INSTALL", "README"] = "distrib" -/- f
            | otherwise                      = f

-- TODO: This list is surely incomplete -- fix this.
-- | A list of files that allow us to support a simple
-- @./configure [--prefix=PATH] && make install@ workflow.
bindistInstallFiles :: [FilePath]
bindistInstallFiles =
    [ "config.sub", "config.guess", "install-sh", "mk" -/- "config.mk.in"
    , "mk" -/- "install.mk.in", "mk" -/- "project.mk", "settings.in", "README"
    , "INSTALL" ]

-- | This auxiliary function gives us a top-level 'Filepath' that we can 'need'
-- for all libraries and programs that are needed for a complete build.
-- For libraries, it returns the path to the @.conf@ file in the package
-- database. For programs, it returns the path to the compiled executable.
pkgTarget :: Package -> Action FilePath
pkgTarget pkg
    | isLibrary pkg = pkgConfFile (vanillaContext Stage1 pkg)
    | otherwise     = programPath =<< programContext Stage1 pkg

-- TODO: Augment this Makefile to match the various parameters that the current
-- bindist scripts support.
-- | A trivial Makefile that only takes @$prefix@ into account, and not e.g
-- @$datadir@ (for docs) and other variables, yet.
bindistMakefile :: String
bindistMakefile = unlines
    [ "MAKEFLAGS += --no-builtin-rules"
    , ".SUFFIXES:"
    , ""
    , "include mk/install.mk"
    , "include mk/config.mk"
    , ""
    , ".PHONY: default"
    , "default:"
    , "\t@echo 'Run \"make install\" to install'"
    , "\t@false"
    , ""
    , "#------------------------------------------------------------------------------"
    , "# INSTALL RULES"
    , ""
    , "# Hacky function to check equality of two strings"
    , "# TODO : find if a better function exists"
    , "eq=$(and $(findstring $(1),$(2)),$(findstring $(2),$(1)))"
    , ""
    , "define installscript"
    , "# $1 = package name"
    , "# $2 = wrapper path"
    , "# $3 = bindir"
    , "# $4 = ghcbindir"
    , "# $5 = Executable binary path"
    , "# $6 = Library Directory"
    , "# $7 = Docs Directory"
    , "# $8 = Includes Directory"
    , "# We are installing wrappers to programs by searching corresponding wrappers."
    , "# If wrapper is not found, we are attaching the common wrapper to it "
    , "# This implementation is a bit hacky and depends on consistency of program"
    , "# names. For hadrian build this will work as programs have a consistent "
    , "# naming procefure. This file is tested on Linux(Ubuntu)"
    , "# TODO : Check implementation in other distributions"
    , "\trm -f $2"
    , "\t$(CREATE_SCRIPT) $2"
    , "\t@echo \"#!$(SHELL)\" >>  $2"
    , "\t@echo \"exedir=\\\"$4\\\"\" >> $2"
    , "\t@echo \"exeprog=\\\"$1\\\"\" >> $2"
    , "\t@echo \"executablename=\\\"$5\\\"\" >> $2"
    , "\t@echo \"bindir=\\\"$3\\\"\" >> $2"
    , "\t@echo \"libdir=\\\"$6\\\"\" >> $2"
    , "\t@echo \"docdir=\\\"$7\\\"\" >> $2"
    , "\t@echo \"includedir=\\\"$8\\\"\" >> $2"
    , "\t@echo \"\" >> $2 "
    , "\tcat wrappers/$1 >> $2"
    , "\t$(EXECUTABLE_FILE) $2 ;"
    , "endef"
    , ""
    , "# QUESTION : should we use shell commands?"
    , ""
    , "# Due to the fact that package database is configured relatively"
    , "# We do not change the relative paths of executables and libraries"
    , "# But instead use wrapper scripts whenever necessary"
    , "LIBPARENT = $(shell dirname $(libdir))"
    , "GHCBINDIR = \"$(LIBPARENT)/bin\""
    , ""
    , ".PHONY: install"
    , "install: install_bin install_lib install_includes"
    , ""
    , "# Check if we need to install docs"
    , "ifeq \"DOCS\" \"YES\""
    , "install: install_docs"
    , "endif"
    , ""
    , "# If the relative path of binaries and libraries are altered, we will need to"
    , "# install additional wrapper scripts at bindir."
    , "ifneq \"$(LIBPARENT)/bin\" \"$(bindir)\""
    , "install: install_wrappers"
    , "endif"
    , ""
    , "# We need to install binaries relative to libraries."
    , "BINARIES = $(wildcard ./bin/*)"
    , "install_bin:"
    , "\t@echo \"Copying Binaries to $(GHCBINDIR)\""
    , "\t$(INSTALL_DIR) \"$(GHCBINDIR)\""
    , "\tfor i in $(BINARIES); do \\"
    , "\t\tcp -R $$i \"$(GHCBINDIR)\"; \\"
    , "\tdone"
    , "\t@echo \"Copying and installing ghci\""
    , "\trm -f $(GHCBINDIR)/dir"
    , "\t$(CREATE_SCRIPT) $(GHCBINDIR)/ghci"
    , "\t@echo \"#!$(SHELL)\" >>  $(GHCBINDIR)/ghci"
    , "\tcat wrappers/ghci-script >> $(GHCBINDIR)/ghci"
    , "\t$(EXECUTABLE_FILE) $(GHCBINDIR)/ghci"
    , ""
    , "LIBRARIES = $(wildcard ./lib/*)"
    , "install_lib:"
    , "\t@echo \"Copying libraries to $(libdir)\""
    , "\t$(INSTALL_DIR) \"$(libdir)\""
    , "\tfor i in $(LIBRARIES); do \\"
    , "\t\tcp -R $$i \"$(libdir)/\"; \\"
    , "\tdone"
    , ""
    , "INCLUDES = $(wildcard ./include/*)"
    , "install_includes:"
    , "\t@echo \"Copying libraries to $(includedir)\""
    , "\t$(INSTALL_DIR) \"$(includedir)\""
    , "\tfor i in $(INCLUDES); do \\"
    , "\t\tcp -R $$i \"$(includedir)/\"; \\"
    , "\tdone"
    , ""
    , "DOCS = $(wildcard ./docs/*)"
    , "install_docs:"
    , "\t@echo \"Copying libraries to $(docdir)\""
    , "\t$(INSTALL_DIR) \"$(docdir)\""
    , "\tfor i in $(DOCS); do \\"
    , "\t\tcp -R $$i \"$(docdir)/\"; \\"
    , "\tdone"
    , ""
    , "BINARY_NAMES=$(shell ls ./bin/)"
    , "install_wrappers:"
    , "\t@echo \"Installing Wrapper scripts\""
    , "\t$(INSTALL_DIR) \"$(bindir)\""
    , "\t$(foreach p, $(BINARY_NAMES),\\"
    , "\t\t$(call installscript,$p,$(bindir)/$p,$(bindir),$(GHCBINDIR),$(GHCBINDIR)/$p,$(libdir),$(docdir),$(includedir)))"
    , ""
    , "# END INSTALL"
    , "# -----------------------------------------------------------------------------" ]

wrapper :: FilePath -> String
wrapper "ghc"         = ghcWrapper
wrapper "ghc-pkg"     = ghcPkgWrapper
wrapper "ghci"        = ghciWrapper
wrapper "ghci-script" = ghciScriptWrapper
wrapper "haddock"     = haddockWrapper
wrapper "hsc2hs"      = hsc2hsWrapper
wrapper "runghc"      = runGhcWrapper
wrapper _             = commonWrapper

-- | Wrapper scripts for different programs. Common is default wrapper.

ghcWrapper :: String
ghcWrapper = "exec \"$executablename\" -B\"$libdir\" ${1+\"$@\"}\n"

ghcPkgWrapper :: String
ghcPkgWrapper = unlines
    [ "PKGCONF=\"$libdir/package.conf.d\""
    , "exec \"$executablename\" --global-package-db \"$PKGCONF\" ${1+\"$@\"}" ]

ghciWrapper :: String
ghciWrapper = "exec \"$executablename\" --interactive \"$@\"\n"

haddockWrapper :: String
haddockWrapper = "exec \"$executablename\" -B\"$libdir\" -l\"$libdir\" ${1+\"$@\"}\n"

commonWrapper :: String
commonWrapper = "exec \"$executablename\" ${1+\"$@\"}\n"

hsc2hsWrapper :: String
hsc2hsWrapper = unlines
    [ "HSC2HS_EXTRA=\"--cflag=-fno-stack-protector --lflag=-fuse-ld=gold\""
    , "tflag=\"--template=$libdir/template-hsc.h\""
    , "Iflag=\"-I$includedir/\""
    , "for arg do"
    , "    case \"$arg\" in"
    , "# On OS X, we need to specify -m32 or -m64 in order to get gcc to"
    , "# build binaries for the right target. We do that by putting it in"
    , "# HSC2HS_EXTRA. When cabal runs hsc2hs, it passes a flag saying which"
    , "# gcc to use, so if we set HSC2HS_EXTRA= then we don't get binaries"
    , "# for the right platform. So for now we just don't set HSC2HS_EXTRA="
    , "# but we probably want to revisit how this works in the future."
    , "#        -c*)          HSC2HS_EXTRA=;;"
    , "#        --cc=*)       HSC2HS_EXTRA=;;"
    , "        -t*)          tflag=;;"
    , "        --template=*) tflag=;;"
    , "        --)           break;;"
    , "    esac"
    , "done"
    , "exec \"$executablename\" ${tflag:+\"$tflag\"} $HSC2HS_EXTRA ${1+\"$@\"} \"$Iflag\"" ]

runGhcWrapper :: String
runGhcWrapper = "exec \"$executablename\" -f \"$exedir/ghc\" ${1+\"$@\"}\n"

-- | We need to ship ghci executable, which basically just calls ghc with
-- | --interactive flag.
ghciScriptWrapper :: String
ghciScriptWrapper = unlines
    [ "DIR=`dirname \"$0\"`"
    , "executable=\"$DIR/ghc\""
    , "exec $executable --interactive \"$@\"" ]
