Fix #10551 by using LIB_NAMES.
[ghc.git] / utils / ghc-cabal / Main.hs
1
2 module Main (main) where
3
4 import qualified Distribution.ModuleName as ModuleName
5 import Distribution.PackageDescription
6 import Distribution.PackageDescription.Check hiding (doesFileExist)
7 import Distribution.PackageDescription.Configuration
8 import Distribution.PackageDescription.Parse
9 import Distribution.Package
10 import Distribution.System
11 import Distribution.Simple
12 import Distribution.Simple.Configure
13 import Distribution.Simple.LocalBuildInfo
14 import Distribution.Simple.GHC
15 import Distribution.Simple.Program
16 import Distribution.Simple.Program.HcPkg
17 import Distribution.Simple.Setup (ConfigFlags(configStripLibs), fromFlag, toFlag)
18 import Distribution.Simple.Utils (defaultPackageDesc, writeFileAtomic, toUTF8)
19 import Distribution.Simple.Build (writeAutogenFiles)
20 import Distribution.Simple.Register
21 import Distribution.Text
22 import Distribution.Verbosity
23 import qualified Distribution.InstalledPackageInfo as Installed
24 import qualified Distribution.Simple.PackageIndex as PackageIndex
25
26 import Control.Exception (bracket)
27 import Control.Monad
28 import qualified Data.ByteString.Lazy.Char8 as BS
29 import Data.List
30 import Data.Maybe
31 import System.IO
32 import System.Directory
33 import System.Environment
34 import System.Exit (exitWith, ExitCode(..))
35 import System.FilePath
36
37 main :: IO ()
38 main = do hSetBuffering stdout LineBuffering
39 args <- getArgs
40 case args of
41 "hscolour" : dir : distDir : args' ->
42 runHsColour dir distDir args'
43 "check" : dir : [] ->
44 doCheck dir
45 "copy" : dir : distDir
46 : strip : myDestDir : myPrefix : myLibdir : myDocdir
47 : ghcLibWays : args' ->
48 doCopy dir distDir
49 strip myDestDir myPrefix myLibdir myDocdir
50 ("dyn" `elem` words ghcLibWays)
51 args'
52 "register" : dir : distDir : ghc : ghcpkg : topdir
53 : myDestDir : myPrefix : myLibdir : myDocdir
54 : relocatableBuild : args' ->
55 doRegister dir distDir ghc ghcpkg topdir
56 myDestDir myPrefix myLibdir myDocdir
57 relocatableBuild args'
58 "configure" : dir : distDir : dll0Modules : config_args ->
59 generate dir distDir dll0Modules config_args
60 "sdist" : dir : distDir : [] ->
61 doSdist dir distDir
62 ["--version"] ->
63 defaultMainArgs ["--version"]
64 _ -> die syntax_error
65
66 syntax_error :: [String]
67 syntax_error =
68 ["syntax: ghc-cabal configure <configure-args> -- <distdir> <directory>...",
69 " ghc-cabal install <ghc-pkg> <directory> <distdir> <destdir> <prefix> <args>...",
70 " ghc-cabal hscolour <distdir> <directory> <args>..."]
71
72 die :: [String] -> IO a
73 die errs = do mapM_ (hPutStrLn stderr) errs
74 exitWith (ExitFailure 1)
75
76 withCurrentDirectory :: FilePath -> IO a -> IO a
77 withCurrentDirectory directory io
78 = bracket (getCurrentDirectory) (setCurrentDirectory)
79 (const (setCurrentDirectory directory >> io))
80
81 -- We need to use the autoconfUserHooks, as the packages that use
82 -- configure can create a .buildinfo file, and we need any info that
83 -- ends up in it.
84 userHooks :: UserHooks
85 userHooks = autoconfUserHooks
86
87 runDefaultMain :: IO ()
88 runDefaultMain
89 = do let verbosity = normal
90 gpdFile <- defaultPackageDesc verbosity
91 gpd <- readPackageDescription verbosity gpdFile
92 case buildType (flattenPackageDescription gpd) of
93 Just Configure -> defaultMainWithHooks autoconfUserHooks
94 -- time has a "Custom" Setup.hs, but it's actually Configure
95 -- plus a "./Setup test" hook. However, Cabal is also
96 -- "Custom", but doesn't have a configure script.
97 Just Custom ->
98 do configureExists <- doesFileExist "configure"
99 if configureExists
100 then defaultMainWithHooks autoconfUserHooks
101 else defaultMain
102 -- not quite right, but good enough for us:
103 _ -> defaultMain
104
105 doSdist :: FilePath -> FilePath -> IO ()
106 doSdist directory distDir
107 = withCurrentDirectory directory
108 $ withArgs (["sdist", "--builddir", distDir])
109 runDefaultMain
110
111 doCheck :: FilePath -> IO ()
112 doCheck directory
113 = withCurrentDirectory directory
114 $ do let verbosity = normal
115 gpdFile <- defaultPackageDesc verbosity
116 gpd <- readPackageDescription verbosity gpdFile
117 case filter isFailure $ checkPackage gpd Nothing of
118 [] -> return ()
119 errs -> mapM_ print errs >> exitWith (ExitFailure 1)
120 where isFailure (PackageDistSuspicious {}) = False
121 isFailure (PackageDistSuspiciousWarn {}) = False
122 isFailure _ = True
123
124 runHsColour :: FilePath -> FilePath -> [String] -> IO ()
125 runHsColour directory distdir args
126 = withCurrentDirectory directory
127 $ defaultMainArgs ("hscolour" : "--builddir" : distdir : args)
128
129 doCopy :: FilePath -> FilePath
130 -> FilePath -> FilePath -> FilePath -> FilePath -> FilePath -> Bool
131 -> [String]
132 -> IO ()
133 doCopy directory distDir
134 strip myDestDir myPrefix myLibdir myDocdir withSharedLibs
135 args
136 = withCurrentDirectory directory $ do
137 let copyArgs = ["copy", "--builddir", distDir]
138 ++ (if null myDestDir
139 then []
140 else ["--destdir", myDestDir])
141 ++ args
142 copyHooks = userHooks {
143 copyHook = noGhcPrimHook
144 $ modHook False
145 $ copyHook userHooks
146 }
147
148 defaultMainWithHooksArgs copyHooks copyArgs
149 where
150 noGhcPrimHook f pd lbi us flags
151 = let pd'
152 | packageName pd == PackageName "ghc-prim" =
153 case library pd of
154 Just lib ->
155 let ghcPrim = fromJust (simpleParse "GHC.Prim")
156 ems = filter (ghcPrim /=) (exposedModules lib)
157 lib' = lib { exposedModules = ems }
158 in pd { library = Just lib' }
159 Nothing ->
160 error "Expected a library, but none found"
161 | otherwise = pd
162 in f pd' lbi us flags
163 modHook relocatableBuild f pd lbi us flags
164 = do let verbosity = normal
165 idts = updateInstallDirTemplates relocatableBuild
166 myPrefix myLibdir myDocdir
167 (installDirTemplates lbi)
168 progs = withPrograms lbi
169 stripProgram' = stripProgram {
170 programFindLocation = \_ _ -> return (Just strip) }
171
172 progs' <- configureProgram verbosity stripProgram' progs
173 let lbi' = lbi {
174 withPrograms = progs',
175 installDirTemplates = idts,
176 configFlags = cfg,
177 stripLibs = fromFlag (configStripLibs cfg),
178 withSharedLib = withSharedLibs
179 }
180
181 -- This hack allows to interpret the "strip"
182 -- command-line argument being set to ':' to signify
183 -- disabled library stripping
184 cfg | strip == ":" = (configFlags lbi) { configStripLibs = toFlag False }
185 | otherwise = configFlags lbi
186
187 f pd lbi' us flags
188
189 doRegister :: FilePath -> FilePath -> FilePath -> FilePath
190 -> FilePath -> FilePath -> FilePath -> FilePath -> FilePath
191 -> String -> [String]
192 -> IO ()
193 doRegister directory distDir ghc ghcpkg topdir
194 myDestDir myPrefix myLibdir myDocdir
195 relocatableBuildStr args
196 = withCurrentDirectory directory $ do
197 relocatableBuild <- case relocatableBuildStr of
198 "YES" -> return True
199 "NO" -> return False
200 _ -> die ["Bad relocatableBuildStr: " ++
201 show relocatableBuildStr]
202 let regArgs = "register" : "--builddir" : distDir : args
203 regHooks = userHooks {
204 regHook = modHook relocatableBuild
205 $ regHook userHooks
206 }
207
208 defaultMainWithHooksArgs regHooks regArgs
209 where
210 modHook relocatableBuild f pd lbi us flags
211 = do let verbosity = normal
212 idts = updateInstallDirTemplates relocatableBuild
213 myPrefix myLibdir myDocdir
214 (installDirTemplates lbi)
215 progs = withPrograms lbi
216 ghcpkgconf = topdir </> "package.conf.d"
217 ghcProgram' = ghcProgram {
218 programPostConf = \_ cp -> return cp { programDefaultArgs = ["-B" ++ topdir] },
219 programFindLocation = \_ _ -> return (Just ghc) }
220 ghcPkgProgram' = ghcPkgProgram {
221 programPostConf = \_ cp -> return cp { programDefaultArgs =
222 ["--global-package-db", ghcpkgconf]
223 ++ ["--force" | not (null myDestDir) ] },
224 programFindLocation = \_ _ -> return (Just ghcpkg) }
225 configurePrograms ps conf = foldM (flip (configureProgram verbosity)) conf ps
226
227 progs' <- configurePrograms [ghcProgram', ghcPkgProgram'] progs
228 instInfos <- dump (hcPkgInfo progs') verbosity GlobalPackageDB
229 let installedPkgs' = PackageIndex.fromList instInfos
230 let updateComponentConfig (cn, clbi, deps)
231 = (cn, updateComponentLocalBuildInfo clbi, deps)
232 updateComponentLocalBuildInfo clbi
233 = clbi {
234 componentPackageDeps =
235 [ (fixupPackageId instInfos ipid, pid)
236 | (ipid,pid) <- componentPackageDeps clbi ]
237 }
238 ccs' = map updateComponentConfig (componentsConfigs lbi)
239 lbi' = lbi {
240 componentsConfigs = ccs',
241 installedPkgs = installedPkgs',
242 installDirTemplates = idts,
243 withPrograms = progs'
244 }
245 f pd lbi' us flags
246
247 updateInstallDirTemplates :: Bool -> FilePath -> FilePath -> FilePath
248 -> InstallDirTemplates
249 -> InstallDirTemplates
250 updateInstallDirTemplates relocatableBuild myPrefix myLibdir myDocdir idts
251 = idts {
252 prefix = toPathTemplate $
253 if relocatableBuild
254 then "$topdir"
255 else myPrefix,
256 libdir = toPathTemplate $
257 if relocatableBuild
258 then "$topdir"
259 else myLibdir,
260 libsubdir = toPathTemplate "$libname",
261 docdir = toPathTemplate $
262 if relocatableBuild
263 then "$topdir/../doc/html/libraries/$pkgid"
264 else (myDocdir </> "$pkgid"),
265 htmldir = toPathTemplate "$docdir"
266 }
267
268 -- The packages are built with the package ID ending in "-inplace", but
269 -- when they're installed they get the package hash appended. We need to
270 -- fix up the package deps so that they use the hash package IDs, not
271 -- the inplace package IDs.
272 fixupPackageId :: [Installed.InstalledPackageInfo]
273 -> InstalledPackageId
274 -> InstalledPackageId
275 fixupPackageId _ x@(InstalledPackageId ipi)
276 | "builtin_" `isPrefixOf` ipi = x
277 fixupPackageId ipinfos (InstalledPackageId ipi)
278 = case stripPrefix (reverse "-inplace") $ reverse ipi of
279 Nothing ->
280 error ("Installed package ID doesn't end in -inplace: " ++ show ipi)
281 Just x ->
282 let ipi' = reverse ('-' : x)
283 f (ipinfo : ipinfos') = case Installed.installedPackageId ipinfo of
284 y@(InstalledPackageId ipinfoid)
285 | ipi' `isPrefixOf` ipinfoid ->
286 y
287 _ ->
288 f ipinfos'
289 f [] = error ("Installed package ID not registered: " ++ show ipi)
290 in f ipinfos
291
292 -- On Windows we need to split the ghc package into 2 pieces, or the
293 -- DLL that it makes contains too many symbols (#5987). There are
294 -- therefore 2 libraries, not just the 1 that Cabal assumes.
295 mangleLbi :: FilePath -> FilePath -> LocalBuildInfo -> LocalBuildInfo
296 mangleLbi "compiler" "stage2" lbi
297 | isWindows =
298 let ccs' = [ (cn, updateComponentLocalBuildInfo clbi, cns)
299 | (cn, clbi, cns) <- componentsConfigs lbi ]
300 updateComponentLocalBuildInfo clbi@(LibComponentLocalBuildInfo {})
301 = let cls' = concat [ [ LibraryName n, LibraryName (n ++ "-0") ]
302 | LibraryName n <- componentLibraries clbi ]
303 in clbi { componentLibraries = cls' }
304 updateComponentLocalBuildInfo clbi = clbi
305 in lbi { componentsConfigs = ccs' }
306 where isWindows = case hostPlatform lbi of
307 Platform _ Windows -> True
308 _ -> False
309 mangleLbi _ _ lbi = lbi
310
311 generate :: FilePath -> FilePath -> String -> [String] -> IO ()
312 generate directory distdir dll0Modules config_args
313 = withCurrentDirectory directory
314 $ do let verbosity = normal
315 -- XXX We shouldn't just configure with the default flags
316 -- XXX And this, and thus the "getPersistBuildConfig distdir" below,
317 -- aren't going to work when the deps aren't built yet
318 withArgs (["configure", "--distdir", distdir] ++ config_args)
319 runDefaultMain
320
321 lbi0 <- getPersistBuildConfig distdir
322 let lbi = mangleLbi directory distdir lbi0
323 pd0 = localPkgDescr lbi
324
325 writePersistBuildConfig distdir lbi
326
327 hooked_bi <-
328 if (buildType pd0 == Just Configure) || (buildType pd0 == Just Custom)
329 then do
330 maybe_infoFile <- defaultHookedPackageDesc
331 case maybe_infoFile of
332 Nothing -> return emptyHookedBuildInfo
333 Just infoFile -> readHookedBuildInfo verbosity infoFile
334 else
335 return emptyHookedBuildInfo
336
337 let pd = updatePackageDescription hooked_bi pd0
338
339 -- generate Paths_<pkg>.hs and cabal-macros.h
340 writeAutogenFiles verbosity pd lbi
341
342 -- generate inplace-pkg-config
343 withLibLBI pd lbi $ \lib clbi ->
344 do cwd <- getCurrentDirectory
345 let ipid = InstalledPackageId (display (packageId pd) ++ "-inplace")
346 let installedPkgInfo = inplaceInstalledPackageInfo cwd distdir
347 pd ipid lib lbi clbi
348 final_ipi = installedPkgInfo {
349 Installed.installedPackageId = ipid,
350 Installed.haddockHTMLs = []
351 }
352 content = Installed.showInstalledPackageInfo final_ipi ++ "\n"
353 writeFileAtomic (distdir </> "inplace-pkg-config") (BS.pack $ toUTF8 content)
354
355 let
356 comp = compiler lbi
357 libBiModules lib = (libBuildInfo lib, libModules lib)
358 exeBiModules exe = (buildInfo exe, ModuleName.main : exeModules exe)
359 biModuless = (maybeToList $ fmap libBiModules $ library pd)
360 ++ (map exeBiModules $ executables pd)
361 buildableBiModuless = filter isBuildable biModuless
362 where isBuildable (bi', _) = buildable bi'
363 (bi, modules) = case buildableBiModuless of
364 [] -> error "No buildable component found"
365 [biModules] -> biModules
366 _ -> error ("XXX ghc-cabal can't handle " ++
367 "more than one buildinfo yet")
368 -- XXX Another Just...
369 Just ghcProg = lookupProgram ghcProgram (withPrograms lbi)
370
371 dep_pkgs = PackageIndex.topologicalOrder (packageHacks (installedPkgs lbi))
372 forDeps f = concatMap f dep_pkgs
373
374 -- copied from Distribution.Simple.PreProcess.ppHsc2Hs
375 packageHacks = case compilerFlavor (compiler lbi) of
376 GHC -> hackRtsPackage
377 _ -> id
378 -- We don't link in the actual Haskell libraries of our
379 -- dependencies, so the -u flags in the ldOptions of the rts
380 -- package mean linking fails on OS X (it's ld is a tad
381 -- stricter than gnu ld). Thus we remove the ldOptions for
382 -- GHC's rts package:
383 hackRtsPackage index =
384 case PackageIndex.lookupPackageName index (PackageName "rts") of
385 [(_,[rts])] ->
386 PackageIndex.insert rts{
387 Installed.ldOptions = [],
388 Installed.libraryDirs = filter (not . ("gcc-lib" `isSuffixOf`)) (Installed.libraryDirs rts)} index
389 -- GHC <= 6.12 had $topdir/gcc-lib in their
390 -- library-dirs for the rts package, which causes
391 -- problems when we try to use the in-tree mingw,
392 -- due to accidentally picking up the incompatible
393 -- libraries there. So we filter out gcc-lib from
394 -- the RTS's library-dirs here.
395 _ -> error "No (or multiple) ghc rts package is registered!!"
396
397 dep_ids = map snd (externalPackageDeps lbi)
398 deps = map display dep_ids
399 dep_direct = map (fromMaybe (error "ghc-cabal: dep_keys failed")
400 . PackageIndex.lookupInstalledPackageId
401 (installedPkgs lbi)
402 . fst)
403 . externalPackageDeps
404 $ lbi
405 dep_keys
406 | packageKeySupported comp
407 = map (display . Installed.packageKey) dep_direct
408 | otherwise = deps
409 dep_ipids = map (display . Installed.installedPackageId) dep_direct
410 depNames = map (display . packageName) dep_ids
411
412 transitive_dep_ids = map Installed.sourcePackageId dep_pkgs
413 transitiveDeps = map display transitive_dep_ids
414 transitiveDepLibNames
415 | packageKeySupported comp
416 = map (\p -> packageKeyLibraryName
417 (Installed.sourcePackageId p)
418 (Installed.packageKey p)) dep_pkgs
419 | otherwise = transitiveDeps
420 transitiveDepNames = map (display . packageName) transitive_dep_ids
421
422 libraryDirs = forDeps Installed.libraryDirs
423 -- The mkLibraryRelDir function is a bit of a hack.
424 -- Ideally it should be handled in the makefiles instead.
425 mkLibraryRelDir "rts" = "rts/dist/build"
426 mkLibraryRelDir "ghc" = "compiler/stage2/build"
427 mkLibraryRelDir "Cabal" = "libraries/Cabal/Cabal/dist-install/build"
428 mkLibraryRelDir l = "libraries/" ++ l ++ "/dist-install/build"
429 libraryRelDirs = map mkLibraryRelDir transitiveDepNames
430 wrappedIncludeDirs <- wrap $ forDeps Installed.includeDirs
431 wrappedLibraryDirs <- wrap libraryDirs
432
433 let variablePrefix = directory ++ '_':distdir
434 mods = map display modules
435 otherMods = map display (otherModules bi)
436 allMods = mods ++ otherMods
437 let xs = [variablePrefix ++ "_VERSION = " ++ display (pkgVersion (package pd)),
438 variablePrefix ++ "_PACKAGE_KEY = " ++ display (pkgKey lbi),
439 -- copied from mkComponentsLocalBuildInfo
440 variablePrefix ++ "_LIB_NAME = " ++ packageKeyLibraryName (package pd) (pkgKey lbi),
441 variablePrefix ++ "_MODULES = " ++ unwords mods,
442 variablePrefix ++ "_HIDDEN_MODULES = " ++ unwords otherMods,
443 variablePrefix ++ "_SYNOPSIS =" ++ synopsis pd,
444 variablePrefix ++ "_HS_SRC_DIRS = " ++ unwords (hsSourceDirs bi),
445 variablePrefix ++ "_DEPS = " ++ unwords deps,
446 variablePrefix ++ "_DEP_KEYS = " ++ unwords dep_keys,
447 variablePrefix ++ "_DEP_IPIDS = " ++ unwords dep_ipids,
448 variablePrefix ++ "_DEP_NAMES = " ++ unwords depNames,
449 variablePrefix ++ "_TRANSITIVE_DEPS = " ++ unwords transitiveDeps,
450 variablePrefix ++ "_TRANSITIVE_DEP_LIB_NAMES = " ++ unwords transitiveDepLibNames,
451 variablePrefix ++ "_TRANSITIVE_DEP_NAMES = " ++ unwords transitiveDepNames,
452 variablePrefix ++ "_INCLUDE_DIRS = " ++ unwords (includeDirs bi),
453 variablePrefix ++ "_INCLUDES = " ++ unwords (includes bi),
454 variablePrefix ++ "_INSTALL_INCLUDES = " ++ unwords (installIncludes bi),
455 variablePrefix ++ "_EXTRA_LIBRARIES = " ++ unwords (extraLibs bi),
456 variablePrefix ++ "_EXTRA_LIBDIRS = " ++ unwords (extraLibDirs bi),
457 variablePrefix ++ "_C_SRCS = " ++ unwords (cSources bi),
458 variablePrefix ++ "_CMM_SRCS := $(addprefix cbits/,$(notdir $(wildcard " ++ directory ++ "/cbits/*.cmm)))",
459 variablePrefix ++ "_DATA_FILES = " ++ unwords (dataFiles pd),
460 -- XXX This includes things it shouldn't, like:
461 -- -odir dist-bootstrapping/build
462 variablePrefix ++ "_HC_OPTS = " ++ escape (unwords
463 ( programDefaultArgs ghcProg
464 ++ hcOptions GHC bi
465 ++ languageToFlags (compiler lbi) (defaultLanguage bi)
466 ++ extensionsToFlags (compiler lbi) (usedExtensions bi)
467 ++ programOverrideArgs ghcProg)),
468 variablePrefix ++ "_CC_OPTS = " ++ unwords (ccOptions bi),
469 variablePrefix ++ "_CPP_OPTS = " ++ unwords (cppOptions bi),
470 variablePrefix ++ "_LD_OPTS = " ++ unwords (ldOptions bi),
471 variablePrefix ++ "_DEP_INCLUDE_DIRS_SINGLE_QUOTED = " ++ unwords wrappedIncludeDirs,
472 variablePrefix ++ "_DEP_CC_OPTS = " ++ unwords (forDeps Installed.ccOptions),
473 variablePrefix ++ "_DEP_LIB_DIRS_SINGLE_QUOTED = " ++ unwords wrappedLibraryDirs,
474 variablePrefix ++ "_DEP_LIB_DIRS_SEARCHPATH = " ++ mkSearchPath libraryDirs,
475 variablePrefix ++ "_DEP_LIB_REL_DIRS = " ++ unwords libraryRelDirs,
476 variablePrefix ++ "_DEP_LIB_REL_DIRS_SEARCHPATH = " ++ mkSearchPath libraryRelDirs,
477 variablePrefix ++ "_DEP_EXTRA_LIBS = " ++ unwords (forDeps Installed.extraLibraries),
478 variablePrefix ++ "_DEP_LD_OPTS = " ++ unwords (forDeps Installed.ldOptions),
479 variablePrefix ++ "_BUILD_GHCI_LIB = " ++ boolToYesNo (withGHCiLib lbi),
480 "",
481 -- Sometimes we need to modify the automatically-generated package-data.mk
482 -- bindings in a special way for the GHC build system, so allow that here:
483 "$(eval $(" ++ directory ++ "_PACKAGE_MAGIC))"
484 ]
485 writeFile (distdir ++ "/package-data.mk") $ unlines xs
486
487 writeFileUtf8 (distdir ++ "/haddock-prologue.txt") $
488 if null (description pd) then synopsis pd
489 else description pd
490 unless (null dll0Modules) $
491 do let dll0Mods = words dll0Modules
492 dllMods = allMods \\ dll0Mods
493 dllModSets = map unwords [dll0Mods, dllMods]
494 writeFile (distdir ++ "/dll-split") $ unlines dllModSets
495 where
496 escape = foldr (\c xs -> if c == '#' then '\\':'#':xs else c:xs) []
497 wrap = mapM wrap1
498 wrap1 s
499 | null s = die ["Wrapping empty value"]
500 | '\'' `elem` s = die ["Single quote in value to be wrapped:", s]
501 -- We want to be able to assume things like <space><quote> is the
502 -- start of a value, so check there are no spaces in confusing
503 -- positions
504 | head s == ' ' = die ["Leading space in value to be wrapped:", s]
505 | last s == ' ' = die ["Trailing space in value to be wrapped:", s]
506 | otherwise = return ("\'" ++ s ++ "\'")
507 mkSearchPath = intercalate [searchPathSeparator]
508 boolToYesNo True = "YES"
509 boolToYesNo False = "NO"
510
511 -- | Version of 'writeFile' that always uses UTF8 encoding
512 writeFileUtf8 f txt = withFile f WriteMode $ \hdl -> do
513 hSetEncoding hdl utf8
514 hPutStr hdl txt