Created
October 20, 2011 13:15
-
-
Save jodonoghue/1301115 to your computer and use it in GitHub Desktop.
Building a C/C++ library from Cabal
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- | |
-- Modified version of the wxcore Cabal setup.hs file which builds the C++ code in a buildhook | |
-- so that we have some control over how the linking works. | |
-- | |
import Control.Monad (when) | |
import Data.List (foldl', intercalate, nub, lookup) | |
import Data.Maybe (fromJust) | |
import Distribution.PackageDescription | |
import Distribution.Simple | |
import Distribution.Simple.LocalBuildInfo (LocalBuildInfo, localPkgDescr, withPrograms, buildDir) | |
import Distribution.Simple.Program (ConfiguredProgram (..), lookupProgram, runProgram, simpleProgram) | |
import Distribution.Simple.Setup (ConfigFlags, BuildFlags) | |
import Distribution.System (OS (..), Arch (..), buildOS, buildArch) | |
import Distribution.Verbosity (normal, verbose) | |
import System.Cmd (system) | |
import System.Directory (createDirectoryIfMissing, doesFileExist, getCurrentDirectory, getModificationTime) | |
import System.Environment (getEnv) | |
import System.FilePath.Posix ((</>), (<.>), replaceExtension, takeFileName, dropFileName, addExtension) | |
import System.IO.Unsafe (unsafePerformIO) | |
import System.Process (readProcess) | |
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- | |
main :: IO () | |
main = defaultMainWithHooks simpleUserHooks { confHook = myConfHook, buildHook = myBuildHook } | |
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- | |
sourceDirectory :: FilePath | |
eiffelDirectory :: FilePath | |
includeDirectory :: FilePath | |
wxcoreDirectory :: FilePath | |
sourceDirectory = "src" | |
eiffelDirectory = sourceDirectory </> "eiffel" | |
includeDirectory = sourceDirectory </> "include" | |
wxcoreDirectory = sourceDirectory </> "haskell/Graphics/UI/WXCore" | |
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- | |
wxcoreIncludeFile :: FilePath | |
wxcoreIncludeFile = includeDirectory </> "wxc.h" | |
eiffelFiles :: [FilePath] | |
eiffelFiles = | |
map ((<.> "e") . (eiffelDirectory </>)) names | |
where | |
names = ["wxc_defs", "wx_defs", "stc"] | |
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- | |
-- | Extend the default configuration hook so that wxdirect is executed over the files from which | |
-- certain wxCore Haskell source files are generated, and use wx-config to determine the compile | |
-- and link environment for wxWidgets. | |
-- Note that we have to add the wxc DLL here as it becomes part of our build dependencies, even | |
-- though we haven't built it yet. | |
myConfHook (pkg0, pbi) flags = do | |
createDirectoryIfMissing True wxcoreDirectory | |
system $ "wxdirect -t --wxc " ++ sourceDirectory ++ " -o " ++ wxcoreDirectory ++ " " ++ wxcoreIncludeFile | |
system $ "wxdirect -i --wxc " ++ sourceDirectory ++ " -o " ++ wxcoreDirectory ++ " " ++ wxcoreIncludeFile | |
system $ "wxdirect -c --wxc " ++ sourceDirectory ++ " -o " ++ wxcoreDirectory ++ " " ++ wxcoreIncludeFile | |
system $ "wxdirect -d --wxc " ++ sourceDirectory ++ " -o " ++ wxcoreDirectory ++ " " ++ intercalate " " eiffelFiles | |
wx <- fmap parseWxConfig (readProcess "wx-config" ["--libs", "--cppflags"] "") | |
lbi <- confHook simpleUserHooks (pkg0, pbi) flags | |
let lpd = localPkgDescr lbi | |
let lib = fromJust (library lpd) | |
let libbi = libBuildInfo lib | |
let custom_bi = customFieldsBI libbi | |
-- Lookup the DLLs we need to add to our extra-libs from x-dll-name and x-dll-extra-libs | |
let all_dlls = parseDLLs ["x-dll-name", "x-dll-extra-libraries"] custom_bi | |
let libbi' = libbi | |
{ extraLibDirs = extraLibDirs libbi ++ extraLibDirs wx | |
, extraLibs = extraLibs libbi ++ extraLibs all_dlls ++ extraLibs wx | |
, ldOptions = ldOptions libbi ++ ldOptions wx | |
, frameworks = frameworks libbi ++ frameworks wx | |
, includeDirs = includeDirs libbi ++ includeDirs wx | |
, ccOptions = ccOptions libbi ++ ccOptions wx ++ ["-DwxcREFUSE_MEDIACTRL"] | |
} | |
let lib' = lib { libBuildInfo = libbi' } | |
let lpd' = lpd { library = Just lib' } | |
return $ lbi { localPkgDescr = lpd' } | |
-- | Determine the name(s) of DLLs used when linking to wxcore. These are added to | |
-- the extraLibs stanza of the build information. | |
parseDLLs :: [String] -> [(String, String)] -> BuildInfo | |
parseDLLs x_stanzas bi = | |
buildBI emptyBuildInfo dlls | |
where | |
dlls = concat $ map (\e -> (lines . fromJust) (lookup e bi)) x_stanzas | |
buildBI bi (w:ws) = buildBI (bi { extraLibs = w : extraLibs bi }) ws | |
buildBI bi [] = bi | |
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- | |
-- | Parse the output of wx-config. Note that, like wx-config itself, this | |
-- function only works for gcc or other UNix-like compilers. | |
parseWxConfig :: String -> BuildInfo | |
parseWxConfig s = | |
helper emptyBuildInfo (words s) | |
where | |
helper b ("-framework":w:ws) = helper (b { frameworks = w : frameworks b }) ws | |
helper b (w:ws) = helper (f b w) ws | |
helper b [] = b | |
f b w = | |
case w of | |
('-':'L':v) -> b { extraLibDirs = v : extraLibDirs b } | |
('-':'l':v) -> b { extraLibs = v : extraLibs b } | |
('-':'I':v) -> b { includeDirs = v : includeDirs b } | |
('-':'D':_) -> b { ccOptions = w : ccOptions b } | |
_ -> b | |
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- | |
-- | Extend the standard build hook to build a shared library for wxc - this will statically link | |
-- any libraries which are unavailable as shared variants. This is mainly a work-around for the | |
-- fact that GHCi needs to load shared libraries at run-time, and that the Windows MinGW environment | |
-- is shipped with only a static version of libstdc++. | |
-- TODO: Does not currently create the build output directory. | |
myBuildHook pkg_descr local_bld_info user_hooks bld_flags = | |
do | |
-- Extract the custom fields customFieldsPD where field name is x-cpp-dll-sources | |
let lib = fromJust (library pkg_descr) | |
lib_bi = libBuildInfo lib | |
custom_bi = customFieldsBI lib_bi | |
dll_name = fromJust (lookup "x-dll-name" custom_bi) | |
dll_srcs = (lines . fromJust) (lookup "x-dll-sources" custom_bi) | |
dll_libs = (lines . fromJust) (lookup "x-dll-extra-libraries" custom_bi) | |
cc_opts = ccOptions lib_bi | |
ld_opts = ldOptions lib_bi | |
inc_dirs = includeDirs lib_bi | |
lib_dirs = extraLibDirs lib_bi | |
libs = extraLibs lib_bi | |
bld_dir = buildDir local_bld_info | |
progs = withPrograms local_bld_info | |
gcc = fromJust (lookupProgram (simpleProgram "gcc") progs) | |
ver = (pkgVersion . package) pkg_descr | |
-- Compile C/C++ sources - output directory is dist/build/src/cpp | |
putStrLn "Building wxc" | |
objs <- mapM (compileCxx gcc cc_opts inc_dirs bld_dir) dll_srcs | |
-- Link C/C++ sources as a DLL - output directory is dist/build | |
putStrLn "Linking wxc" | |
linkSharedLib gcc ld_opts lib_dirs (libs ++ dll_libs) objs ver bld_dir dll_name | |
-- Remove C/C++ source code from the hooked build (don't change libs) | |
putStrLn "Invoke default build hook" | |
buildHook simpleUserHooks pkg_descr local_bld_info user_hooks bld_flags | |
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- | |
-- | Return any compiler options required to support shared library creation | |
osCompileOpts :: [String] -- ^ Platform-specific compile options | |
osCompileOpts = | |
case buildOS of | |
Windows -> ["-DBUILD_DLL"] | |
OSX -> ["-fPIC"] | |
_ -> ["-fPIC"] | |
-- | Return any linker options required to support shared library creation | |
linkCxxOpts :: Version -- ^ Version information to be used for Unix shared libraries | |
-> FilePath -- ^ Directory in which library will be built | |
-> String -- ^ Name of the shared library | |
-> [String] -- ^ List of options which can be applied to 'runProgram' | |
linkCxxOpts ver out_dir basename = | |
let dll_pathname = normalisePath (out_dir </> addExtension basename ".dll") | |
implib_pathname = normalisePath (out_dir </> "lib" ++ addExtension basename ".a") in | |
case buildOS of | |
Windows -> ["--dll", "-shared", "-o " ++ addExtension basename ".dll", | |
"-Wl,--out-implib," ++ "lib" ++ addExtension basename ".a", | |
"-Wl,--export-all-symbols"] | |
OSX -> ["-dynamiclib", | |
"-o " ++ out_dir </> addExtension basename ".so", | |
"-Wl,undefined,dynamic_lookup"] | |
_ -> ["-shared", | |
"-Wl,-soname,lib" ++ basename ++ ".so." ++ maj_ver, | |
"-o " ++ out_dir </> "lib" ++ basename ++ ".so." ++ full_ver] | |
where | |
maj_ver = (show . head . versionBranch) ver | |
(a:b:c:d:_) = versionBranch ver | |
full_ver = (show a) ++ "." ++ (show b) ++ "." ++ (show c) ++ "." ++ (show d) | |
-- | Compile a single source file using the configured gcc, if the object file does not yet | |
-- exist, or is older than the source file. | |
-- TODO: Does not do dependency resolution properly | |
compileCxx :: ConfiguredProgram -- ^ Program used to perform C/C++ compilation (gcc) | |
-> [String] -- ^ Compile options provided by Cabal and wxConfig | |
-> [String] -- ^ Include paths provided by Cabal and wxConfig | |
-> FilePath -- ^ Base output directory | |
-> FilePath -- ^ Path to source file | |
-> IO FilePath -- ^ Path to generated object code | |
compileCxx gcc opts incls out_path cxx_src = | |
do | |
let includes = map ("-I" ++) incls | |
out_path' = normalisePath out_path | |
cxx_src' = normalisePath cxx_src | |
out_file = out_path' </> dropFileName cxx_src </> replaceExtension (takeFileName cxx_src) ".o" | |
out = ["-c", cxx_src', "-o", out_file] | |
opts' = opts ++ osCompileOpts | |
do_it <- needsCompiling cxx_src out_file | |
when do_it $ createDirectoryIfMissing True (dropFileName out_file) >> | |
runProgram verbose gcc (includes ++ opts' ++ out) | |
return out_file | |
-- | Return True if obj does not exist or is older than src. | |
-- Real dependency checking would be nice here... | |
needsCompiling :: FilePath -- ^ Path to source file | |
-> FilePath -- ^ Path to object file | |
-> IO Bool -- ^ True if compilation required | |
needsCompiling src obj = | |
do | |
has_obj <- doesFileExist obj | |
if has_obj | |
then do | |
mtime_src <- getModificationTime src | |
mtime_obj <- getModificationTime obj | |
if mtime_obj < mtime_src then return True else return False | |
else | |
return True | |
-- | Create a dynamically linked library using the configured ld. | |
linkSharedLib :: ConfiguredProgram -- ^ Program used to perform linking | |
-> [String] -- ^ Linker options supplied by Cabal | |
-> [FilePath] -- ^ Library directories | |
-> [String] -- ^ Libraries | |
-> [String] -- ^ Objects | |
-> Version -- ^ wxCore version (wxC has same version number) | |
-> FilePath -- ^ Directory in which library will begenerated | |
-> String -- ^ Name of the shared library | |
-> IO () | |
linkSharedLib gcc opts lib_dirs libs objs ver out_dir dll_name = | |
do | |
cwd <- getCurrentDirectory | |
let lib_dirs' = map (\d -> "-L" ++ normalisePath d) lib_dirs | |
out_dir' = normalisePath out_dir | |
opts' = opts ++ linkCxxOpts ver (cwd </> out_dir') dll_name | |
objs' = map normalisePath objs | |
libs' = ["-lstdc++"] ++ map ("-l" ++) libs | |
runProgram verbose gcc (opts' ++ objs' ++ lib_dirs' ++ libs') | |
-- | The 'normalise' implementation in System.FilePath does not meet the requirements of | |
-- calling and/or running external programs on Windows particularly well as it does not | |
-- normalise the '/' character to '\\'. The problem is that some MinGW programs do not | |
-- like to see paths with a mixture of '/' and '\\'. Sine we are calling out to these, | |
-- we require a stricter normalisation. | |
normalisePath :: FilePath -> FilePath | |
normalisePath = case buildOS of | |
Windows -> dosifyFilePath | |
_ -> unixifyFilePath | |
-- | Replace a character in a String with some other character | |
replace :: Char -- ^ Character to replace | |
-> Char -- ^ Character with which to replace | |
-> String -- ^ String in which to replace | |
-> String -- ^ Transformed string | |
replace old new = map replace' | |
where | |
replace' elem = if elem == old then new else elem | |
unixifyFilePath = replace '\\' '/' | |
dosifyFilePath = replace '/' '\\' | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: wxcore | |
version: 0.12.1.7 | |
license: LGPL | |
license-file: LICENSE | |
author: Daan Leijen | |
maintainer: wxhaskell-devel@lists.sourceforge.net | |
category: GUI, User interfaces | |
synopsis: wxHaskell core | |
description: | |
wxHaskell is a portable and native GUI library for Haskell. It is | |
built on top of wxWidgets, a comprehensive C++ library that is | |
portable across all major GUI platforms, including GTK, Windows, | |
X11, and MacOS X. This version works with wxWidgets 2.8 only. | |
homepage: http://haskell.org/haskellwiki/WxHaskell | |
cabal-version: >= 1.2 | |
build-type: Custom | |
extra-tmp-files: | |
src/haskell/Graphics/UI/WXCore/WxcClassInfo.hs | |
src/haskell/Graphics/UI/WXCore/WxcClassTypes.hs | |
src/haskell/Graphics/UI/WXCore/WxcClasses.hs | |
src/haskell/Graphics/UI/WXCore/WxcClassesAL.hs | |
src/haskell/Graphics/UI/WXCore/WxcClassesMZ.hs | |
src/haskell/Graphics/UI/WXCore/WxcDefs.hs | |
extra-source-files: | |
src/cpp/stc_gen.cpp | |
src/eiffel/wx_defs.e | |
src/eiffel/wxc_defs.e | |
src/eiffel/stc.e | |
src/include/db.h | |
src/include/dragimage.h | |
src/include/eljgrid.h | |
src/include/ewxw_def.h | |
src/include/graphicscontext.h | |
src/include/managed.h | |
src/include/mediactrl.h | |
src/include/previewframe.h | |
src/include/printout.h | |
src/include/sound.h | |
src/include/stc.h | |
src/include/stc_gen.h | |
src/include/textstream.h | |
src/include/wrapper.h | |
src/include/wxc.h | |
src/include/wxc_glue.h | |
src/include/wxc_types.h | |
flag splitBase | |
description: use new split base | |
default: True | |
library | |
hs-source-dirs: | |
src/haskell | |
include-dirs: | |
src/include | |
x-dll-sources: | |
src/cpp/apppath.cpp | |
src/cpp/db.cpp | |
src/cpp/dragimage.cpp | |
src/cpp/eljaccelerator.cpp | |
src/cpp/eljartprov.cpp | |
src/cpp/eljbitmap.cpp | |
src/cpp/eljbrush.cpp | |
src/cpp/eljbusyinfo.cpp | |
src/cpp/eljbutton.cpp | |
src/cpp/eljcalendarctrl.cpp | |
src/cpp/eljcaret.cpp | |
src/cpp/eljcheckbox.cpp | |
src/cpp/eljchecklistbox.cpp | |
src/cpp/eljchoice.cpp | |
src/cpp/eljclipboard.cpp | |
src/cpp/eljcoldata.cpp | |
src/cpp/eljcolour.cpp | |
src/cpp/eljcolourdlg.cpp | |
src/cpp/eljcombobox.cpp | |
src/cpp/eljconfigbase.cpp | |
src/cpp/eljcontrol.cpp | |
src/cpp/eljctxhelp.cpp | |
src/cpp/eljcursor.cpp | |
src/cpp/eljdataformat.cpp | |
src/cpp/eljdatetime.cpp | |
src/cpp/eljdc.cpp | |
src/cpp/eljdcsvg.cpp | |
src/cpp/eljdialog.cpp | |
src/cpp/eljdirdlg.cpp | |
src/cpp/eljdnd.cpp | |
src/cpp/eljdrawing.cpp | |
src/cpp/eljevent.cpp | |
src/cpp/eljfiledialog.cpp | |
src/cpp/eljfilehist.cpp | |
src/cpp/eljfindrepldlg.cpp | |
src/cpp/eljfont.cpp | |
src/cpp/eljfontdata.cpp | |
src/cpp/eljfontdlg.cpp | |
src/cpp/eljframe.cpp | |
src/cpp/eljgauge.cpp | |
src/cpp/eljgrid.cpp | |
src/cpp/eljhelpcontroller.cpp | |
src/cpp/eljicnbndl.cpp | |
src/cpp/eljicon.cpp | |
src/cpp/eljimage.cpp | |
src/cpp/eljimagelist.cpp | |
src/cpp/eljlayoutconstraints.cpp | |
src/cpp/eljlistbox.cpp | |
src/cpp/eljlistctrl.cpp | |
src/cpp/eljlocale.cpp | |
src/cpp/eljlog.cpp | |
src/cpp/eljmask.cpp | |
src/cpp/eljmdi.cpp | |
src/cpp/eljmenu.cpp | |
src/cpp/eljmenubar.cpp | |
src/cpp/eljmessagedialog.cpp | |
src/cpp/eljmime.cpp | |
src/cpp/eljminiframe.cpp | |
src/cpp/eljnotebook.cpp | |
src/cpp/eljpalette.cpp | |
src/cpp/eljpanel.cpp | |
src/cpp/eljpen.cpp | |
src/cpp/eljprintdlg.cpp | |
src/cpp/eljprinting.cpp | |
src/cpp/eljprocess.cpp | |
src/cpp/eljradiobox.cpp | |
src/cpp/eljradiobutton.cpp | |
src/cpp/eljrc.cpp | |
src/cpp/eljregion.cpp | |
src/cpp/eljregioniter.cpp | |
src/cpp/eljsash.cpp | |
src/cpp/eljscrollbar.cpp | |
src/cpp/eljscrolledwindow.cpp | |
src/cpp/eljsingleinst.cpp | |
src/cpp/eljsizer.cpp | |
src/cpp/eljslider.cpp | |
src/cpp/eljspinctrl.cpp | |
src/cpp/eljsplitterwindow.cpp | |
src/cpp/eljstaticbox.cpp | |
src/cpp/eljstaticline.cpp | |
src/cpp/eljstatictext.cpp | |
src/cpp/eljstatusbar.cpp | |
src/cpp/eljsystemsettings.cpp | |
src/cpp/eljtextctrl.cpp | |
src/cpp/eljtimer.cpp | |
src/cpp/eljtipwnd.cpp | |
src/cpp/eljtoolbar.cpp | |
src/cpp/eljvalidator.cpp | |
src/cpp/eljwindow.cpp | |
src/cpp/eljwizard.cpp | |
src/cpp/ewxw_main.cpp | |
src/cpp/extra.cpp | |
src/cpp/graphicscontext.cpp | |
src/cpp/image.cpp | |
src/cpp/managed.cpp | |
src/cpp/mediactrl.cpp | |
src/cpp/previewframe.cpp | |
src/cpp/printout.cpp | |
src/cpp/sckaddr.cpp | |
src/cpp/socket.cpp | |
src/cpp/sound.cpp | |
src/cpp/stc.cpp | |
src/cpp/std.cpp | |
src/cpp/taskbaricon.cpp | |
src/cpp/textstream.cpp | |
src/cpp/treectrl.cpp | |
src/cpp/wrapper.cpp | |
x-dll-name: wxc | |
x-dll-extra-libraries: | |
exposed-modules: | |
Graphics.UI.WXCore | |
Graphics.UI.WXCore.Controls | |
Graphics.UI.WXCore.Db | |
Graphics.UI.WXCore.Defines | |
Graphics.UI.WXCore.Dialogs | |
Graphics.UI.WXCore.DragAndDrop | |
Graphics.UI.WXCore.Draw | |
Graphics.UI.WXCore.Events | |
Graphics.UI.WXCore.Frame | |
Graphics.UI.WXCore.Image | |
Graphics.UI.WXCore.Layout | |
Graphics.UI.WXCore.Print | |
Graphics.UI.WXCore.Process | |
Graphics.UI.WXCore.Types | |
Graphics.UI.WXCore.WxcClassInfo | |
Graphics.UI.WXCore.WxcClassTypes | |
Graphics.UI.WXCore.WxcClasses | |
Graphics.UI.WXCore.WxcClassesAL | |
Graphics.UI.WXCore.WxcClassesMZ | |
Graphics.UI.WXCore.WxcDefs | |
Graphics.UI.WXCore.WxcObject | |
Graphics.UI.WXCore.WxcTypes | |
build-depends: | |
bytestring, | |
filepath, | |
parsec, | |
stm, | |
wxdirect >= 0.12.1.3, | |
directory, | |
old-time, | |
time | |
if flag(splitBase) | |
build-depends: | |
array >= 0.2 && < 0.4, | |
base >= 4 && < 5, | |
containers >= 0.2 && < 0.4 | |
else | |
build-depends: | |
array >= 0.1 && < 0.3, | |
base >= 3 && < 4, | |
containers >= 0.1 && < 0.3 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment