diff --git a/bin/extract.hs b/bin/extract.hs
--- a/bin/extract.hs
+++ b/bin/extract.hs
@@ -10,10 +10,11 @@
 import System.Environment (getArgs)
 import System.Exit
 import System.IO (hPutStrLn, stderr)
+import System.FilePath
 
 main :: IO ()
 main = do
-  createDirectoryIfMissing True "src/Skylighting/Syntax"
+  createDirectoryIfMissing True $ "src" </> "Skylighting" </> "Syntax"
   files <- getArgs
   (errs, syntaxes) <- partitionEithers <$> mapM parseSyntaxDefinition files
   mapM_ (hPutStrLn stderr) errs
@@ -41,8 +42,9 @@
   let newcabal = unlines $ top ++ ("  other-modules:" : autogens) ++ bottom
   writeFile "skylighting.cabal" newcabal
 
-  putStrLn "Writing src/Skylighting/Syntax.hs"
-  writeFile "src/Skylighting/Syntax.hs" $ unlines (
+  let ssyn = "src" </> "Skylighting" </> "Syntax.hs"
+  putStrLn $ "Writing " ++ ssyn
+  writeFile ssyn $ unlines (
      [ "{-# LANGUAGE OverloadedStrings #-}"
      , "-- | Provides syntax highlighting definitions."
      , "-- THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT IT MANUALLY."
@@ -89,6 +91,6 @@
 
 toPathName :: Syntax -> String
 toPathName s =
-  "src/Skylighting/Syntax/" ++
-  map (\c -> if c == '.' then '/' else c)
+  "src" </> "Skylighting" </> "Syntax" </>
+  map (\c -> if c == '.' then pathSeparator else c)
       (Text.unpack (sShortname s)) ++ ".hs"
diff --git a/changelog.md b/changelog.md
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,15 @@
 # Revision history for skylighting and skylighting-core
 
+## 0.8.2.3 -- 2019-10-03
+
+  * Tests: ensure we read test files as UTF-8.
+
+  * Fix compiler warnings around Monoid imports.
+
+  * Add MonadFail constraint (allowing compilation with ghc 8.8.x).
+
+  * Add cabal.project and move CI from Travis to GitHub Actions.
+
 ## 0.8.2.2 -- 2019-09-30
 
   * Remove upper bound on regex-base and require
diff --git a/skylighting-core.cabal b/skylighting-core.cabal
--- a/skylighting-core.cabal
+++ b/skylighting-core.cabal
@@ -1,5 +1,5 @@
 name:                skylighting-core
-version:             0.8.2.2
+version:             0.8.2.3
 synopsis:            syntax highlighting library
 description:         Skylighting is a syntax highlighting library.
                      It derives its tokenizers from XML syntax
diff --git a/src/Skylighting/Format/ANSI.hs b/src/Skylighting/Format/ANSI.hs
--- a/src/Skylighting/Format/ANSI.hs
+++ b/src/Skylighting/Format/ANSI.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE CPP               #-}
 {-# LANGUAGE OverloadedStrings #-}
 module Skylighting.Format.ANSI (
          formatANSI
@@ -5,11 +6,13 @@
 import Control.Monad (mplus)
 import qualified Data.Map as Map
 import Data.Maybe (fromMaybe, isNothing, maybeToList)
-import Data.Monoid
 import Data.Text (Text)
 import qualified Data.Text as Text
 import Skylighting.Types
 import qualified System.Console.ANSI.Codes as ANSI
+#if !MIN_VERSION_base(4,11,0)
+import Data.Semigroup
+#endif
 
 formatANSI :: FormatOptions -> Style -> [SourceLine] -> Text
 formatANSI opts sty = (beforeText <>)
diff --git a/src/Skylighting/Format/HTML.hs b/src/Skylighting/Format/HTML.hs
--- a/src/Skylighting/Format/HTML.hs
+++ b/src/Skylighting/Format/HTML.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE CPP #-}
 module Skylighting.Format.HTML (
       formatHtmlInline
     , formatHtmlBlock
@@ -6,12 +7,14 @@
 
 import Data.List (intersperse, sort)
 import qualified Data.Map as Map
-import Data.Monoid ((<>))
 import qualified Data.Text as Text
 import Skylighting.Types
 import Text.Blaze.Html
 import qualified Text.Blaze.Html5 as H
 import qualified Text.Blaze.Html5.Attributes as A
+#if !MIN_VERSION_base(4,11,0)
+import Data.Semigroup
+#endif
 
 -- | Format tokens using HTML spans inside @code@ tags. For example,
 -- A @KeywordTok@ is rendered as a span with class @kw@.
diff --git a/src/Skylighting/Format/LaTeX.hs b/src/Skylighting/Format/LaTeX.hs
--- a/src/Skylighting/Format/LaTeX.hs
+++ b/src/Skylighting/Format/LaTeX.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE CPP                 #-}
 {-# LANGUAGE OverloadedStrings   #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 module Skylighting.Format.LaTeX (
@@ -10,11 +11,13 @@
 import Data.Char (isSpace)
 import Data.List (sort)
 import qualified Data.Map as Map
-import Data.Monoid
 import Data.Text (Text)
 import qualified Data.Text as Text
 import Skylighting.Types
 import Text.Printf
+#if !MIN_VERSION_base(4,11,0)
+import Data.Semigroup
+#endif
 
 formatLaTeX :: Bool -> [SourceLine] -> Text
 formatLaTeX inline = Text.intercalate (Text.singleton '\n')
diff --git a/src/Skylighting/Loader.hs b/src/Skylighting/Loader.hs
--- a/src/Skylighting/Loader.hs
+++ b/src/Skylighting/Loader.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE CPP #-}
 -- | This module provides routines to load syntax definitions from disk
 -- files.
 module Skylighting.Loader ( loadSyntaxFromFile
@@ -8,12 +9,13 @@
 import Control.Monad (filterM, foldM)
 import Control.Monad.Except (ExceptT(ExceptT), runExceptT)
 import Control.Monad.IO.Class (liftIO)
-import Data.Monoid ((<>))
 import System.Directory (listDirectory, doesFileExist)
 import System.FilePath ((</>), takeExtension)
-
 import Skylighting.Types (SyntaxMap, Syntax)
 import Skylighting.Parser (addSyntaxDefinition, parseSyntaxDefinition)
+#if !MIN_VERSION_base(4,11,0)
+import Data.Semigroup
+#endif
 
 syntaxFileExtension :: String
 syntaxFileExtension = ".xml"
diff --git a/src/Skylighting/Regex.hs b/src/Skylighting/Regex.hs
--- a/src/Skylighting/Regex.hs
+++ b/src/Skylighting/Regex.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE CPP                 #-}
 {-# LANGUAGE DeriveDataTypeable  #-}
 {-# LANGUAGE DeriveGeneric       #-}
 {-# LANGUAGE OverloadedStrings   #-}
@@ -25,6 +26,9 @@
 import System.IO.Unsafe (unsafePerformIO)
 import Text.Printf
 import Text.Regex.PCRE.ByteString
+#if !MIN_VERSION_base(4,13,0)
+import Control.Monad.Fail (MonadFail)
+#endif
 
 -- | An exception in compiling or executing a regex.
 newtype RegexException = RegexException String
@@ -103,5 +107,5 @@
 encodeToText :: BS.ByteString -> Text.Text
 encodeToText = TE.decodeUtf8 . Base64.encode
 
-decodeFromText :: (Monad m) => Text.Text -> m BS.ByteString
+decodeFromText :: (Monad m, MonadFail m) => Text.Text -> m BS.ByteString
 decodeFromText = either fail return . Base64.decode . TE.encodeUtf8
diff --git a/src/Skylighting/Tokenizer.hs b/src/Skylighting/Tokenizer.hs
--- a/src/Skylighting/Tokenizer.hs
+++ b/src/Skylighting/Tokenizer.hs
@@ -1,4 +1,5 @@
 {-# OPTIONS_GHC -fno-warn-missing-methods #-}
+{-# LANGUAGE CPP                   #-}
 {-# LANGUAGE DeriveFunctor         #-}
 {-# LANGUAGE FlexibleInstances     #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
@@ -22,7 +23,6 @@
 import Data.Char (isAlphaNum, isAscii, isLetter, isPrint, isSpace, ord)
 import qualified Data.Map as Map
 import Data.Maybe (catMaybes)
-import Data.Monoid
 import qualified Data.Set as Set
 import Data.Text (Text)
 import qualified Data.Text as Text
@@ -31,6 +31,9 @@
 import Skylighting.Regex
 import Skylighting.Types
 import Text.Printf (printf)
+#if !MIN_VERSION_base(4,11,0)
+import Data.Semigroup
+#endif
 
 newtype ContextStack = ContextStack{ unContextStack :: [Context] }
   deriving (Show)
diff --git a/test/test-skylighting.hs b/test/test-skylighting.hs
--- a/test/test-skylighting.hs
+++ b/test/test-skylighting.hs
@@ -16,6 +16,7 @@
 import System.Environment (getArgs)
 import System.FilePath
 import System.Exit (exitFailure)
+import System.IO (hSetEncoding, utf8, openFile, IOMode(..))
 import Test.QuickCheck
 import Test.Tasty
 import Test.Tasty.Golden.Advanced (goldenTest)
@@ -25,6 +26,12 @@
 
 import Skylighting.Core
 
+readTextFile :: FilePath -> IO Text
+readTextFile fp = do
+  h <- openFile fp ReadMode
+  hSetEncoding h utf8
+  Text.hGetContents h
+
 tokToText :: Token -> Text
 tokToText (_, s) = s
 
@@ -49,7 +56,7 @@
   inputs <- filter (\fp -> take 1 fp /= ".")
          <$> getDirectoryContents ("test" </> "cases")
   allcases <- mapM (fmap (Text.take 240)
-                    . Text.readFile . (("test" </> "cases") </>)) inputs
+                    . readTextFile . (("test" </> "cases") </>)) inputs
   args <- getArgs
   let regen = "--accept" `elem` args
   defaultTheme <- BL.readFile ("test" </> "default.theme")
@@ -207,9 +214,9 @@
   goldenTest testname getExpected getActual
       (compareValues referenceFile) updateGolden
   where testname = lang ++ " tokenizing of " ++ inpFile
-        getExpected = Text.readFile referenceFile
+        getExpected = readTextFile referenceFile
         getActual = do
-          code <- Text.readFile (casesdir </> inpFile)
+          code <- readTextFile (casesdir </> inpFile)
           syntax <- case lookupSyntax (Text.pack lang) sMap of
                          Just s  -> return s
                          Nothing -> fail $
