Skip to content

Commit

Permalink
Merge branch 'no-cabal-hgettext'
Browse files Browse the repository at this point in the history
  • Loading branch information
wjt committed Aug 18, 2015
2 parents 95ac81a + 0328a90 commit 62bacb9
Show file tree
Hide file tree
Showing 6 changed files with 534 additions and 66 deletions.
103 changes: 62 additions & 41 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,58 +1,79 @@
# This file has been generated -- see https://github.com/hvr/multi-ghc-travis
language: c
sudo: false

env:
# TODO
# # Fedora 22:
# - CABALVER=1.18 GHCVER=7.8.4
# 7.10.2 is not in the magic whitelist at https://github.com/travis-ci/apt-package-whitelist/blob/master/ubuntu-precise
- CABALVER=1.22 GHCVER=7.10.1
- CABALVER=head GHCVER=head
cache:
directories:
- $HOME/.cabsnap
- $HOME/.cabal/packages

before_cache:
- rm -fv $HOME/.cabal/packages/hackage.haskell.org/build-reports.log
- rm -fv $HOME/.cabal/packages/hackage.haskell.org/00-index.tar

matrix:
allow_failures:
- env: CABALVER=head GHCVER=head

addons:
apt:
sources:
# https://github.com/hvr/multi-ghc-travis
- hvr-ghc
packages:
- libpcap-dev
- libgtk-3-dev
- libcairo2-dev
# GAH: I guess env does not have any effect here
- cabal-install-1.18
- cabal-install-1.22
- cabal-install-head
- ghc-7.8.4
- ghc-7.10.1
- ghc-head
# /GAH
- happy-1.19.4
- alex-3.1.3
include:
- env: CABALVER=1.18 GHCVER=7.8.4
compiler: ": #GHC 7.8.4"
addons: {apt: {packages: [cabal-install-1.18,ghc-7.8.4,libpcap-dev,libgtk-3-dev,libcairo2-dev,happy-1.19.4,alex-3.1.3], sources: [hvr-ghc]}}
- env: CABALVER=1.22 GHCVER=7.10.1
compiler: ": #GHC 7.10.1"
addons: {apt: {packages: [cabal-install-1.22,ghc-7.10.1,libpcap-dev,libgtk-3-dev,libcairo2-dev,happy-1.19.4,alex-3.1.3], sources: [hvr-ghc]}}

before_install:
- unset CC
- export PATH=/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:/opt/happy/1.19.4/bin:/opt/alex/3.1.3/bin:$PATH

install:
# Is it just not installing those packages?
- dpkg -l
- export PATH=/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:/opt/alex/3.1.3/bin:/opt/happy/1.19.4/bin:$PATH
- cabal --version
- echo "$(ghc --version) [$(ghc --print-project-git-commit-id 2> /dev/null || echo '?')]"
- ghc-pkg list Cabal
- travis_retry cabal update
# TODO: shouldn't gtk3 et al build-depend on this? Should we do so?
- cabal install gtk2hs-buildtools
- cabal install --only-dependencies --enable-tests --enable-benchmarks
- if [ -f $HOME/.cabal/packages/hackage.haskell.org/00-index.tar.gz ];
then
zcat $HOME/.cabal/packages/hackage.haskell.org/00-index.tar.gz >
$HOME/.cabal/packages/hackage.haskell.org/00-index.tar;
fi
- travis_retry cabal update -v
- sed -i 's/^jobs:/-- jobs:/' ${HOME}/.cabal/config
- cabal install --only-dependencies --enable-tests --enable-benchmarks --flags='WithGtk2HsBuildTools' --dry -v > installplan.txt
- sed -i -e '1,/^Resolving /d' installplan.txt; cat installplan.txt

# check whether current requested install-plan matches cached package-db snapshot
- if diff -u installplan.txt $HOME/.cabsnap/installplan.txt;
then
echo "cabal build-cache HIT";
rm -rfv .ghc;
cp -a $HOME/.cabsnap/ghc $HOME/.ghc;
cp -a $HOME/.cabsnap/lib $HOME/.cabsnap/share $HOME/.cabsnap/bin $HOME/.cabal/;
else
echo "cabal build-cache MISS";
rm -rf $HOME/.cabsnap;
mkdir -p $HOME/.ghc $HOME/.cabal/lib $HOME/.cabal/share $HOME/.cabal/bin;
travis_retry cabal install --only-dependencies --enable-tests --enable-benchmarks --flags='WithGtk2HsBuildTools';
fi

# snapshot package-db on cache miss
- if [ ! -d $HOME/.cabsnap ];
then
echo "snapshotting package-db to build-cache";
mkdir $HOME/.cabsnap;
cp -a $HOME/.ghc $HOME/.cabsnap/ghc;
cp -a $HOME/.cabal/lib $HOME/.cabal/share $HOME/.cabal/bin installplan.txt $HOME/.cabsnap/;
fi

# Here starts the actual work to be performed for the package under test;
# any command which exits with a non-zero exit code causes the build to fail.
script:
- if [ -f configure.ac ]; then autoreconf -i; fi
- cabal configure --enable-tests --enable-benchmarks -v2
- cabal build
- cabal test
- cabal configure --enable-tests --enable-benchmarks -v2 # -v2 provides useful information for debugging
- cabal build # this builds all libraries and executables (including tests/benchmarks)
- ./dist/setup/setup test
- cabal check
- cabal sdist
- cabal sdist # tests that a source-distribution can be generated

# Check that the resulting source distribution can be built & installed.
# If there are no other `.tar.gz` files in `dist`, this can be even simpler:
# `cabal install --force-reinstalls dist/*-*.tar.gz`
- SRC_TGZ=$(cabal info . | awk '{print $2;exit}').tar.gz &&
(cd dist && cabal install --force-reinstalls "$SRC_TGZ")

# EOF
220 changes: 220 additions & 0 deletions GetText.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
-- | This library extends the Distribution with internationalization support.
--
-- It performs two functions:
--
-- * compiles and installs PO files to the specified directory
--
-- * tells the application where files were installed to make it able
-- to bind them to the code
--
-- Each PO file will be placed to the
-- @{datadir}\/locale\/{loc}\/LC_MESSAGES\/{domain}.mo@ where:
--
-- [@datadir@] Usually @prefix/share@ but could be different, depends
-- on system.
--
-- [@loc@] Locale name (language code, two characters). This module
-- supposes, that each PO file has a base name set to the proper
-- locale, e.g. @de.po@ is the German translation of the program, so
-- this file will be placed under @{datadir}\/locale\/de@ directory
--
-- [@domain@] Program domain. A unique identifier of single
-- translational unit (program). By default domain will be set to the
-- package name, but its name could be configured in the @.cabal@ file.
--
-- The module defines following @.cabal@ fields:
--
-- [@x-gettext-domain-name@] Name of the domain. One ofmore
-- alphanumeric characters separated by hyphens or underlines. When
-- not set, package name will be used.
--
-- [@x-gettext-po-files@] List of files with translations. Could be
-- used a limited form of wildcards, e.g.: @x-gettext-po-files:
-- po/*.po@
--
-- [@x-gettext-domain-def@] Name of the macro, in which domain name
-- will be passed to the program. Default value is
-- @__MESSAGE_CATALOG_DOMAIN__@
--
-- [@x-gettext-msg-cat-def@] Name of the macro, in which path to the
-- message catalog will be passed to the program. Default value is
-- @__MESSAGE_CATALOG_DIR__@
--
-- The last two parameters are used to send configuration data to the
-- code during its compilation. The most common usage example is:
--
--
-- > ...
-- > prepareI18N = do
-- > setLocale LC_ALL (Just "")
-- > bindTextDomain __MESSAGE_CATALOG_DOMAIN__ (Just __MESSAGE_CATALOG_DIR__)
-- > textDomain __MESSAGE_CATALOG_DOMAIN__
-- >
-- > main = do
-- > prepareI18N
-- > ...
-- >
-- > ...
--
--
-- /NOTE:/ files, passed in the @x-gettext-po-files@ are not
-- automatically added to the source distribution, so they should be
-- also added to the @extra-source-files@ parameter, along with
-- translation template file (usually @message.pot@)
--
-- /WARNING:/ sometimes, when only configuration targets changes, code
-- will not recompile, thus you should execute @cabal clean@ to
-- cleanup the build and restart it again from the configuration. This
-- is temporary bug, it will be fixed in next releases.
--
-- /TODO:/ this is lifted verbatim (modulo other /TODO/s) from hgettext's
-- Distribution.Simple.I18N.GetText partly to expose individual hooks and
-- partly to avoid the /cabal configure/-time dependency. For the latter,
-- see https://github.com/fpco/stackage/issues/746
--

module GetText
(
-- | /TODO:/ upstream exporting the individual hooks?
installPOFiles,

-- | /TODO:/ upstream generating GetText_foo.hs rather than exporting these?
getDomainNameDefault,
getPackageName,
targetDataDir,

installGetTextHooks,
gettextDefaultMain
) where

import Distribution.Simple
import Distribution.Simple.Setup as S
import Distribution.Simple.LocalBuildInfo
import Distribution.PackageDescription
import Distribution.Simple.Configure
import Distribution.Simple.InstallDirs as I
import Distribution.Simple.Utils

import Language.Haskell.Extension

import Control.Monad
import Control.Arrow (second)
import Data.Maybe (listToMaybe, maybeToList, fromMaybe)
import Data.List (unfoldr,nub,null)
import System.FilePath
import System.Directory
import System.Process

-- | Default main function, same as
--
-- > defaultMainWithHooks $ installGetTextHooks simpleUserHooks
--
gettextDefaultMain :: IO ()
gettextDefaultMain = defaultMainWithHooks $ installGetTextHooks simpleUserHooks

-- | Installs hooks, used by GetText module to install
-- PO files to the system. Previous won't be disabled
--
installGetTextHooks :: UserHooks -- ^ initial user hooks
-> UserHooks -- ^ patched user hooks
installGetTextHooks uh = uh{
confHook = \a b ->
(confHook uh) a b >>=
return . updateLocalBuildInfo,

postInst = \a b c d ->
(postInst uh) a b c d >>
installPOFiles a b c d
}


updateLocalBuildInfo :: LocalBuildInfo -> LocalBuildInfo
updateLocalBuildInfo l =
let sMap = getCustomFields l
[domDef, catDef] = map ($ sMap) [getDomainDefine, getMsgCatalogDefine]
dom = getDomainNameDefault sMap (getPackageName l)
tar = targetDataDir l
[catMS, domMS] = map (uncurry formatMacro) [(domDef, dom), (catDef, tar)]
in (appendCPPOptions [domMS,catMS] . appendExtension [EnableExtension CPP]) l

installPOFiles :: Args -> InstallFlags -> PackageDescription -> LocalBuildInfo -> IO ()
installPOFiles _ _ _ l =
let sMap = getCustomFields l
destDir = targetDataDir l
dom = getDomainNameDefault sMap (getPackageName l)
installFile file = do
let fname = takeFileName file
let bname = takeBaseName fname
let targetDir = destDir </> bname </> "LC_MESSAGES"
-- ensure we have directory destDir/{loc}/LC_MESSAGES
createDirectoryIfMissing True targetDir
system $ "msgfmt --output-file=" ++
(targetDir </> dom <.> "mo") ++
" " ++ file
in do
filelist <- getPoFilesDefault sMap
-- copy all whose name is in the form of dir/{loc}.po to the
-- destDir/{loc}/LC_MESSAGES/dom.mo
-- with the 'msgfmt' tool
mapM_ installFile filelist

forBuildInfo :: LocalBuildInfo -> (BuildInfo -> BuildInfo) -> LocalBuildInfo
forBuildInfo l f =
let a = l{localPkgDescr = updPkgDescr (localPkgDescr l)}
updPkgDescr x = x{library = updLibrary (library x),
executables = updExecs (executables x)}
updLibrary Nothing = Nothing
updLibrary (Just x) = Just $ x{libBuildInfo = f (libBuildInfo x)}
updExecs x = map updExec x
updExec x = x{buildInfo = f (buildInfo x)}
in a

appendExtension :: [Extension] -> LocalBuildInfo -> LocalBuildInfo
appendExtension exts l =
forBuildInfo l updBuildInfo
where updBuildInfo x = x{defaultExtensions = updExts (defaultExtensions x)}
updExts s = nub (s ++ exts)

appendCPPOptions :: [String] -> LocalBuildInfo -> LocalBuildInfo
appendCPPOptions opts l =
forBuildInfo l updBuildInfo
where updBuildInfo x = x{cppOptions = updOpts (cppOptions x)}
updOpts s = nub (s ++ opts)

formatMacro name value = "-D" ++ name ++ "=" ++ (show value)

targetDataDir :: LocalBuildInfo -> FilePath
targetDataDir l =
let dirTmpls = installDirTemplates l
prefix' = prefix dirTmpls
data' = datadir dirTmpls
dataEx = I.fromPathTemplate $ I.substPathTemplate [(PrefixVar, prefix')] data'
in dataEx ++ "/locale"

getPackageName :: LocalBuildInfo -> String
getPackageName = fromPackageName . packageName . localPkgDescr
where fromPackageName (PackageName s) = s

getCustomFields :: LocalBuildInfo -> [(String, String)]
getCustomFields = customFieldsPD . localPkgDescr

findInParametersDefault :: [(String, String)] -> String -> String -> String
findInParametersDefault al name def = (fromMaybe def . lookup name) al

getDomainNameDefault :: [(String, String)] -> String -> String
getDomainNameDefault al d = findInParametersDefault al "x-gettext-domain-name" d

getDomainDefine :: [(String, String)] -> String
getDomainDefine al = findInParametersDefault al "x-gettext-domain-def" "__MESSAGE_CATALOG_DOMAIN__"

getMsgCatalogDefine :: [(String, String)] -> String
getMsgCatalogDefine al = findInParametersDefault al "x-gettext-msg-cat-def" "__MESSAGE_CATALOG_DIR__"

getPoFilesDefault :: [(String, String)] -> IO [String]
getPoFilesDefault al = toFileList $ findInParametersDefault al "x-gettext-po-files" ""
where toFileList "" = return []
toFileList x = liftM concat $ mapM matchFileGlob $ split' x
-- from Blow your mind (HaskellWiki)
-- splits string by newline, space and comma
split' x = concatMap lines $ concatMap words $ unfoldr (\b -> fmap (const . (second $ drop 1) . break (==',') $ b) . listToMaybe $ b) x

3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,6 @@ maintainer-make-release: bustle.cabal dist/build/autogen/version.txt
git tag -s -m 'Bustle '`cat dist/build/autogen/version.txt` \
bustle-`cat dist/build/autogen/version.txt`
make maintainer-binary-tarball

.travis.yml: bustle.cabal make_travis_yml.hs
./make_travis_yml.hs $< libpcap-dev libgtk-3-dev libcairo2-dev happy-1.19.4 alex-3.1.3 > $@
Loading

0 comments on commit 62bacb9

Please sign in to comment.