-
Notifications
You must be signed in to change notification settings - Fork 71
/
Copy pathTravis.hs
681 lines (589 loc) · 31 KB
/
Travis.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
-- | Take configuration, produce 'Travis'.
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# OPTIONS_GHC -Wno-unused-matches #-}
module HaskellCI.Travis (
makeTravis,
travisHeader,
) where
import HaskellCI.Prelude
import qualified Data.Map.Strict as M
import qualified Data.Set as S
import qualified Distribution.Fields.Pretty as C
import qualified Distribution.Package as C
import qualified Distribution.Pretty as C
import qualified Distribution.Types.VersionRange as C
import qualified Distribution.Version as C
import Cabal.Project
import HaskellCI.Auxiliary
import HaskellCI.Compiler
import HaskellCI.Config
import HaskellCI.Config.ConstraintSet
import HaskellCI.Config.Doctest
import HaskellCI.Config.Folds
import HaskellCI.Config.HLint
import HaskellCI.Config.Installed
import HaskellCI.Config.Jobs
import HaskellCI.Config.PackageScope
import HaskellCI.Config.Validity
import HaskellCI.HeadHackage
import HaskellCI.Jobs
import HaskellCI.List
import HaskellCI.MonadErr
import HaskellCI.Package
import HaskellCI.Sh
import HaskellCI.ShVersionRange
import HaskellCI.Tools
import HaskellCI.Travis.Yaml
import HaskellCI.VersionInfo
-------------------------------------------------------------------------------
-- Travis header
-------------------------------------------------------------------------------
travisHeader :: Bool -> [String] -> [String]
travisHeader insertVersion argv =
[ "This Travis job script has been generated by a script via"
, ""
, " haskell-ci " ++ unwords [ "'" ++ a ++ "'" | a <- argv ]
, ""
, "To regenerate the script (for example after adjusting tested-with) run"
, ""
, " haskell-ci regenerate"
, ""
, "For more information, see https://github.com/haskell-CI/haskell-ci"
, ""
] ++
if insertVersion then
[ "version: " ++ haskellCIVerStr
, ""
] else []
-------------------------------------------------------------------------------
-- Generate travis configuration
-------------------------------------------------------------------------------
{-
Travis CI–specific notes:
* We use -j2 for parallelism, as Travis' virtual environments use 2 cores, per
https://docs.travis-ci.com/user/reference/overview/#virtualisation-environment-vs-operating-system.
-}
makeTravis
:: [String]
-> Config
-> Project URI Void Package
-> JobVersions
-> Either HsCiError Travis -- TODO: writer
makeTravis argv config@Config {..} prj jobs@JobVersions {..} = do
-- before caching: clear some redundant stuff
beforeCache <- runSh $ when cfgCache $ do
sh "rm -fv $CABALHOME/packages/hackage.haskell.org/build-reports.log"
comment "remove files that are regenerated by 'cabal update'"
sh "rm -fv $CABALHOME/packages/hackage.haskell.org/00-index.*" -- legacy
sh "rm -fv $CABALHOME/packages/hackage.haskell.org/*.json" -- TUF meta-data
sh "rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.cache"
sh "rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.tar"
sh "rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.tar.idx"
sh "rm -rfv $CABALHOME/packages/head.hackage" -- if we cache, it will break builds.
-- before install: we set up the environment, install GHC/cabal on OSX
beforeInstall <- runSh $ do
-- Validity checks
checkConfigValidity config jobs
-- This have to be first
when anyGHCJS $ sh $ unlines
[ "if echo $CC | grep -q ghcjs; then"
, " GHCJS=true; GHCJSARITH=1;"
, "else"
, " GHCJS=false; GHCJSARITH=0;"
, "fi"
]
-- Adjust $HC
sh "HC=$(echo \"/opt/$CC/bin/ghc\" | sed 's/-/\\//')"
sh "WITHCOMPILER=\"-w $HC\""
shForJob RangeGHCJS "HC=${HC}js"
shForJob RangeGHCJS "WITHCOMPILER=\"--ghcjs ${WITHCOMPILER}js\""
-- Needed to work around haskell/cabal#6214
sh "HADDOCK=$(echo \"/opt/$CC/bin/haddock\" | sed 's/-/\\//')"
unless (null macosVersions) $ do
sh $ "if [ \"$TRAVIS_OS_NAME\" = \"osx\" ]; then HADDOCK=$(echo $HADDOCK | sed \"s:^/opt:$HOME/.ghc-install:\"); fi"
-- Hack: happy needs ghc. Let's install version matching GHCJS.
-- At the moment, there is only GHCJS-8.4, so we install GHC-8.4.4
when anyGHCJS $ do
shForJob RangeGHCJS $ "PATH=\"/opt/ghc/8.4.4/bin:$PATH\""
sh "HCPKG=\"$HC-pkg\""
sh "unset CC"
-- cabal
sh "CABAL=/opt/ghc/bin/cabal"
sh "CABALHOME=$HOME/.cabal"
-- PATH
sh "export PATH=\"$CABALHOME/bin:$PATH\""
-- rootdir is useful for manual script additions
sh "TOP=$(pwd)"
-- macOS installing
let haskellOnMacos = "https://haskell.futurice.com/haskell-on-macos.py"
unless (null macosVersions) $ do
sh $ "if [ \"$TRAVIS_OS_NAME\" = \"osx\" ]; then curl " ++ haskellOnMacos ++ " | python3 - --make-dirs --install-dir=$HOME/.ghc-install --cabal-alias=3.2.0.0 install cabal-install-3.2.0.0 ${TRAVIS_COMPILER}; fi"
sh' [2034,2039] "if [ \"$TRAVIS_OS_NAME\" = \"osx\" ]; then HC=$HOME/.ghc-install/ghc/bin/$TRAVIS_COMPILER; WITHCOMPILER=\"-w $HC\"; HCPKG=$HOME/.ghc-install/ghc/bin/${TRAVIS_COMPILER}/ghc/ghc-pkg; CABAL=$HOME/.ghc-install/ghc/bin/cabal; fi"
-- HCNUMVER, numeric HC version, e.g. ghc 7.8.4 is 70804 and 7.10.3 is 71003
sh "HCNUMVER=$(${HC} --numeric-version|perl -ne '/^(\\d+)\\.(\\d+)\\.(\\d+)(\\.(\\d+))?$/; print(10000 * $1 + 100 * $2 + ($3 == 0 ? $5 != 1 : $3))')"
sh "echo $HCNUMVER"
-- verbose in .cabal/config is not respected
-- https://github.com/haskell/cabal/issues/5956
sh "CABAL=\"$CABAL -vnormal+nowrap\""
-- SC2039: In POSIX sh, set option pipefail is undefined. Travis is bash, so it's fine :)
sh' [2039, 3040] "set -o pipefail"
sh "TEST=--enable-tests"
shForJob (invertCompilerRange $ Range cfgTests) "TEST=--disable-tests"
sh "BENCH=--enable-benchmarks"
shForJob (invertCompilerRange $ Range cfgBenchmarks) "BENCH=--disable-benchmarks"
sh "HEADHACKAGE=false"
shForJob (Range cfgHeadHackage \/ RangePoints (S.singleton GHCHead)) "HEADHACKAGE=true"
-- create ~/.cabal/config
sh "rm -f $CABALHOME/config"
cat "$CABALHOME/config"
[ "verbose: normal +nowrap +markoutput" -- https://github.com/haskell/cabal/issues/5956
, "remote-build-reporting: anonymous"
, "write-ghc-environment-files: never"
, "remote-repo-cache: $CABALHOME/packages"
, "logs-dir: $CABALHOME/logs"
, "world-file: $CABALHOME/world"
, "extra-prog-path: $CABALHOME/bin"
, "symlink-bindir: $CABALHOME/bin"
, "installdir: $CABALHOME/bin"
, "build-summary: $CABALHOME/logs/build.log"
, "store-dir: $CABALHOME/store"
, "install-dirs user"
, " prefix: $CABALHOME"
, "repository hackage.haskell.org"
, " url: http://hackage.haskell.org/"
]
-- Add head.hackage repository to ~/.cabal/config
-- (locally you want to add it to cabal.project)
unless (S.null headGhcVers) $ sh $ unlines $
[ "if $HEADHACKAGE; then"
] ++
lines (catCmd Double "$CABALHOME/config" headHackageRepoStanza) ++
[ "fi"
]
-- in install step we install tools and dependencies
install <- runSh $ do
sh "${CABAL} --version"
sh "echo \"$(${HC} --version) [$(${HC} --print-project-git-commit-id 2> /dev/null || echo '?')]\""
when anyGHCJS $ do
sh "node --version"
sh "echo $GHCJS"
-- Cabal jobs
for_ (cfgJobs >>= cabalJobs) $ \n ->
sh $ "echo 'jobs: " ++ show n ++ "' >> $CABALHOME/config"
-- GHC jobs + ghc-options
for_ (cfgJobs >>= ghcJobs) $ \m -> do
shForJob (Range $ C.orLaterVersion (C.mkVersion [7,8])) $ "GHCJOBS=-j" ++ show m
cat "$CABALHOME/config"
[ "program-default-options"
, " ghc-options: $GHCJOBS +RTS -M6G -RTS"
]
-- output config for debugging purposes
sh "cat $CABALHOME/config"
-- remove project own cabal.project files
sh "rm -fv cabal.project cabal.project.local cabal.project.freeze"
-- Update hackage index.
sh "travis_retry ${CABAL} v2-update -v"
-- Install doctest
let doctestVersionConstraint
| C.isAnyVersion (cfgDoctestVersion cfgDoctest) = ""
| otherwise = " --constraint='doctest " ++ C.prettyShow (cfgDoctestVersion cfgDoctest) ++ "'"
when doctestEnabled $
shForJob (Range (cfgDoctestEnabled cfgDoctest) /\ doctestJobVersionRange) $
cabal $ "v2-install $WITHCOMPILER --ignore-project -j2 doctest" ++ doctestVersionConstraint
-- Install hlint
let hlintVersionConstraint
| C.isAnyVersion (cfgHLintVersion cfgHLint) = ""
| otherwise = " --constraint='hlint " ++ C.prettyShow (cfgHLintVersion cfgHLint) ++ "'"
when (cfgHLintEnabled cfgHLint) $ do
let forHLint = shForJob (hlintJobVersionRange allVersions cfgHeadHackage (cfgHLintJob cfgHLint))
if cfgHLintDownload cfgHLint
then do
-- install --dry-run and use perl regex magic to find a hlint version
-- -v is important
forHLint $ "HLINTVER=$(cd /tmp && (${CABAL} v2-install -v $WITHCOMPILER --dry-run hlint " ++ hlintVersionConstraint ++ " | perl -ne 'if (/\\bhlint-(\\d+(\\.\\d+)*)\\b/) { print \"$1\"; last; }')); echo \"HLint version $HLINTVER\""
forHLint $ "if [ ! -e $HOME/.hlint/hlint-$HLINTVER/hlint ]; then " ++ unwords
[ "echo \"Downloading HLint version $HLINTVER\";"
, "mkdir -p $HOME/.hlint;"
, "curl --write-out 'Status Code: %{http_code} Redirects: %{num_redirects} Total time: %{time_total} Total Dsize: %{size_download}\\n' --silent --location --output $HOME/.hlint/hlint-$HLINTVER.tar.gz \"https://github.com/ndmitchell/hlint/releases/download/v$HLINTVER/hlint-$HLINTVER-x86_64-linux.tar.gz\";"
, "tar -xzv -f $HOME/.hlint/hlint-$HLINTVER.tar.gz -C $HOME/.hlint;"
, "fi"
]
forHLint "mkdir -p $CABALHOME/bin && ln -sf \"$HOME/.hlint/hlint-$HLINTVER/hlint\" $CABALHOME/bin/hlint"
forHLint "hlint --version"
else forHLint $ cabal $ "v2-install $WITHCOMPILER --ignore-project -j2 hlint" ++ hlintVersionConstraint
-- Install cabal-plan (for ghcjs tests)
when (anyGHCJS && cfgGhcjsTests) $ do
shForJob RangeGHCJS $ cabal "v2-install -w ghc-8.4.4 --ignore-project -j2 cabal-plan --constraint='cabal-plan ^>=0.6.0.0' --constraint='cabal-plan +exe'"
-- Install happy
when anyGHCJS $ for_ cfgGhcjsTools $ \t ->
shForJob RangeGHCJS $ cabal $ "v2-install -w ghc-8.4.4 --ignore-project -j2" ++ C.prettyShow t
-- create cabal.project file
generateCabalProject False
-- autoreconf
for_ pkgs $ \Pkg{pkgDir} ->
sh $ "if [ -f \"" ++ pkgDir ++ "/configure.ac\" ]; then (cd \"" ++ pkgDir ++ "\" && autoreconf -i); fi"
-- dump install plan
sh $ cabal "v2-freeze $WITHCOMPILER ${TEST} ${BENCH}"
sh "cat cabal.project.freeze | sed -E 's/^(constraints: *| *)//' | sed 's/any.//'"
sh "rm cabal.project.freeze"
-- Install dependencies
when cfgInstallDeps $ do
-- install dependencies
sh $ cabalTW "v2-build $WITHCOMPILER ${TEST} ${BENCH} --dep -j2 all"
-- install dependencies for no-test-no-bench
shForJob (Range cfgNoTestsNoBench) $ cabalTW "v2-build $WITHCOMPILER --disable-tests --disable-benchmarks --dep -j2 all"
-- 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 <- runSh $ do
sh "DISTDIR=$(mktemp -d /tmp/dist-test.XXXX)"
-- sdist
foldedSh FoldSDist "Packaging..." cfgFolds $ do
sh $ cabal "v2-sdist all"
-- unpack
foldedSh FoldUnpack "Unpacking..." cfgFolds $ do
sh "mv dist-newstyle/sdist/*.tar.gz ${DISTDIR}/"
sh "cd ${DISTDIR} || false" -- fail explicitly, makes SC happier
sh "find . -maxdepth 1 -type f -name '*.tar.gz' -exec tar -xvf '{}' \\;"
sh "find . -maxdepth 1 -type f -name '*.tar.gz' -exec rm '{}' \\;"
for_ pkgs $ \Pkg{pkgName} -> do
sh $ pkgNameDirVariable' pkgName ++ "=\"$(find . -maxdepth 1 -type d -regex '.*/" ++ pkgName ++ "-[0-9.]*')\""
generateCabalProject True
when (anyGHCJS && cfgGhcjsTests) $ sh $ unlines $
[ "pkgdir() {"
, " case $1 in"
] ++
[ " " ++ pkgName ++ ") echo " ++ pkgNameDirVariable pkgName ++ " ;;"
| Pkg{pkgName} <- pkgs
] ++
[ " esac"
, "}"
]
-- build no-tests no-benchmarks
unless (equivVersionRanges C.noVersion cfgNoTestsNoBench) $ foldedSh FoldBuild "Building..." cfgFolds $ do
comment "this builds all libraries and executables (without tests/benchmarks)"
shForJob (Range cfgNoTestsNoBench) $ cabal "v2-build $WITHCOMPILER --disable-tests --disable-benchmarks all"
-- build everything
foldedSh FoldBuildEverything "Building with tests and benchmarks..." cfgFolds $ do
comment "build & run tests, build benchmarks"
sh $ cabal "v2-build $WITHCOMPILER ${TEST} ${BENCH} all --write-ghc-environment-files=always"
-- cabal v2-test fails if there are no test-suites.
foldedSh FoldTest "Testing..." cfgFolds $ do
shForJob (RangeGHC /\ Range (cfgTests /\ cfgRunTests) /\ hasTests) $
cabal $ "v2-test $WITHCOMPILER ${TEST} ${BENCH} all" ++ testShowDetails
when cfgGhcjsTests $ shForJob (RangeGHCJS /\ hasTests) $ unwords
[ "cabal-plan list-bins '*:test:*' | while read -r line; do"
, "testpkg=$(echo \"$line\" | perl -pe 's/:.*//');"
, "testexe=$(echo \"$line\" | awk '{ print $2 }');"
, "echo \"testing $textexe in package $textpkg\";"
, "(cd \"$(pkgdir $testpkg)\" && nodejs \"$testexe\".jsexe/all.js);"
, "done"
]
-- doctest
when doctestEnabled $ foldedSh FoldDoctest "Doctest..." cfgFolds $ do
let doctestOptions = unwords $ cfgDoctestOptions cfgDoctest
sh $ "$CABAL v2-build $WITHCOMPILER ${TEST} ${BENCH} all --dry-run"
unless (null $ cfgDoctestFilterEnvPkgs cfgDoctest) $ do
-- cabal-install mangles unit ids on the OSX,
-- removing the vowels to make filepaths shorter
let manglePkgNames :: String -> [String]
manglePkgNames n
| null macosVersions = [n]
| otherwise = [n, filter notVowel n]
where
notVowel c = notElem c ("aeiou" :: String)
let filterPkgs = intercalate "|" $ concatMap (manglePkgNames . C.unPackageName) $ cfgDoctestFilterEnvPkgs cfgDoctest
sh $ "perl -i -e 'while (<ARGV>) { print unless /package-id\\s+(" ++ filterPkgs ++ ")-\\d+(\\.\\d+)*/; }' .ghc.environment.*"
for_ pkgs $ \Pkg{pkgName,pkgGpd,pkgJobs} ->
when (C.mkPackageName pkgName `notElem` cfgDoctestFilterSrcPkgs cfgDoctest) $ do
for_ (doctestArgs pkgGpd) $ \args -> do
let args' = unwords args
let vr = Range (cfgDoctestEnabled cfgDoctest)
/\ doctestJobVersionRange
/\ RangePoints pkgJobs
unless (null args) $ shForJob vr $
"(cd " ++ pkgNameDirVariable pkgName ++ " && doctest " ++ doctestOptions ++ " " ++ args' ++ ")"
-- hlint
when (cfgHLintEnabled cfgHLint) $ foldedSh FoldHLint "HLint.." cfgFolds $ do
let "" <+> ys = ys
xs <+> "" = xs
xs <+> ys = xs ++ " " ++ ys
prependSpace "" = ""
prependSpace xs = " " ++ xs
let hlintOptions = prependSpace $ maybe "" ("-h ${TOP}/" ++) (cfgHLintYaml cfgHLint) <+> unwords (cfgHLintOptions cfgHLint)
for_ pkgs $ \Pkg{pkgName,pkgGpd,pkgJobs} -> do
for_ (hlintArgs pkgGpd) $ \args -> do
let args' = unwords args
unless (null args) $
shForJob (hlintJobVersionRange allVersions cfgHeadHackage (cfgHLintJob cfgHLint) /\ RangePoints pkgJobs) $
"(cd " ++ pkgNameDirVariable pkgName ++ " && hlint" ++ hlintOptions ++ " " ++ args' ++ ")"
-- cabal check
when cfgCheck $ foldedSh FoldCheck "cabal check..." cfgFolds $ do
for_ pkgs $ \Pkg{pkgName,pkgJobs} -> shForJob (RangePoints pkgJobs) $
"(cd " ++ pkgNameDirVariable pkgName ++ " && ${CABAL} -vnormal check)"
-- haddock
when (hasLibrary && not (equivVersionRanges C.noVersion cfgHaddock)) $
foldedSh FoldHaddock "haddock..." cfgFolds $
shForJob (RangeGHC /\ Range cfgHaddock) $ cabal $ "v2-haddock $WITHCOMPILER " ++ withHaddock ++ " ${TEST} ${BENCH} all"
-- unconstained build
-- Have to build last, as we remove cabal.project.local
unless (equivVersionRanges C.noVersion cfgUnconstrainted) $
foldedSh FoldBuildInstalled "Building without installed constraints for packages in global-db..." cfgFolds $ do
shForJob (Range cfgUnconstrainted) "rm -f cabal.project.local"
shForJob (Range cfgUnconstrainted) $ cabal "v2-build $WITHCOMPILER --disable-tests --disable-benchmarks all"
-- and now, as we don't have cabal.project.local;
-- we can test with other constraint sets
unless (null cfgConstraintSets) $ do
comment "Constraint sets"
sh "rm -f cabal.project.local"
for_ cfgConstraintSets $ \cs -> do
let name = csName cs
let shForCs = shForJob (Range (csGhcVersions cs))
let shForCs' r = shForJob (Range (csGhcVersions cs) /\ r)
let testFlag = if csTests cs then "--enable-tests" else "--disable-tests"
let benchFlag = if csBenchmarks cs then "--enable-benchmarks" else "--disable-benchmarks"
let constraintFlags = map (\x -> "--constraint='" ++ x ++ "'") (csConstraints cs)
let allFlags = unwords (testFlag : benchFlag : constraintFlags)
foldedSh' FoldConstraintSets name ("Constraint set " ++ name) cfgFolds $ do
shForCs $ cabal $ "v2-build $WITHCOMPILER " ++ allFlags ++ " --dependencies-only -j2 all"
shForCs $ cabal $ "v2-build $WITHCOMPILER " ++ allFlags ++ " all"
when (csRunTests cs) $
shForCs' hasTests $ cabal $ "v2-test $WITHCOMPILER " ++ allFlags ++ " all --test-show-details=direct"
when (hasLibrary && csHaddock cs) $
shForCs $ cabal $ "v2-haddock $WITHCOMPILER " ++ withHaddock ++ " " ++ allFlags ++ " all"
-- At the end, we allow some raw travis scripts
unless (null cfgRawTravis) $ do
comment "Raw travis commands"
traverse_ sh
[ l
| l <- lines cfgRawTravis
, not (null l)
]
-- assemble travis configuration
return Travis
{ travisLanguage = "c"
, travisUbuntu = cfgUbuntu
, travisGit = TravisGit
{ tgSubmodules = cfgSubmodules
}
, travisCache = TravisCache
{ tcDirectories = buildList $ when cfgCache $ do
item "$HOME/.cabal/packages"
item "$HOME/.cabal/store"
item "$HOME/.hlint"
-- on OSX ghc is installed in $HOME so we can cache it
-- independently of linux
when (cfgCache && not (null macosVersions)) $ do
item "$HOME/.ghc-install"
}
, travisBranches = TravisBranches
{ tbOnly = cfgOnlyBranches
}
, travisNotifications = TravisNotifications
{ tnIRC = justIf (not $ null cfgIrcChannels) $ TravisIRC
{ tiChannels = cfgIrcChannels
, tiSkipJoin = True
, tiTemplate =
[ "\x0313" ++ projectName ++ "\x03/\x0306%{branch}\x03 \x0314%{commit}\x03 %{build_url} %{message}"
]
, tiNick = cfgIrcNickname
, tiPassword = cfgIrcPassword
}
, tnEmail = cfgEmailNotifications
}
, travisServices = buildList $ do
when cfgPostgres $ item "postgresql"
, travisAddons = TravisAddons
{ taApt = TravisApt [] []
, taPostgres = if cfgPostgres then Just "10" else Nothing
, taGoogleChrome = cfgGoogleChrome
}
, travisMatrix = TravisMatrix
{ tmInclude = buildList $ do
let tellJob :: Bool -> CompilerVersion -> ListBuilder TravisJob ()
tellJob osx gv = do
let cvs = dispCabalVersion $ correspondingCabalVersion cfgCabalInstallVersion gv
let gvs = dispGhcVersion gv
-- https://docs.travis-ci.com/user/installing-dependencies/#adding-apt-sources
let hvrppa :: TravisAptSource
hvrppa = TravisAptSourceLine ("deb http://ppa.launchpad.net/hvr/ghc/ubuntu " ++ C.prettyShow cfgUbuntu ++ " main") (Just "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x063dab2bdc0b3f9fcebc378bff3aeacef6f88286")
let ghcjsAptSources :: [TravisAptSource]
ghcjsAptSources | not (isGHCJS gv) = []
| otherwise =
[ TravisAptSourceLine ("deb http://ppa.launchpad.net/hvr/ghcjs/ubuntu " ++ C.prettyShow cfgUbuntu ++ " main") Nothing
, TravisAptSourceLine ("deb https://deb.nodesource.com/node_10.x " ++ C.prettyShow cfgUbuntu ++ " main") (Just "https://deb.nodesource.com/gpgkey/nodesource.gpg.key")
]
let ghcjsPackages :: [String]
ghcjsPackages = case maybeGHCJS gv of
Just v -> [ "ghc-" ++ C.prettyShow v', "nodejs" ] where
-- TODO: partial maximum
v' = maximum $ filter (`C.withinRange` C.withinVersion v) $ knownGhcVersions
Nothing -> []
item TravisJob
{ tjCompiler = gvs
, tjOS = if osx then "osx" else "linux"
, tjEnv = case gv of
GHC v -> M.lookup v cfgEnv
_ -> Nothing
, tjAddons = TravisAddons
{ taApt = TravisApt
{ taPackages = gvs : ("cabal-install-" ++ cvs) : ghcjsPackages ++ S.toList cfgApt
, taSources = hvrppa : ghcjsAptSources
}
, taPostgres = Nothing
, taGoogleChrome = False
}
}
for_ (reverse $ S.toList linuxVersions) $ tellJob False
for_ (reverse $ S.toList macosVersions) $ tellJob True
, tmAllowFailures =
[ TravisAllowFailure $ dispGhcVersion compiler
| compiler <- toList allVersions
, previewGHC cfgHeadHackage compiler || maybeGHC False (`C.withinRange` cfgAllowFailures) compiler
]
}
, travisBeforeCache = beforeCache
, travisBeforeInstall = beforeInstall
, travisInstall = install
, travisScript = script
}
where
Auxiliary {..} = auxiliary config prj jobs
justIf True x = Just x
justIf False _ = Nothing
-- TODO: should this be part of MonadSh ?
foldedSh label = foldedSh' label ""
anyGHCJS = any isGHCJS allVersions
-- https://github.com/travis-ci/docs-travis-ci-com/issues/949#issuecomment-276755003
-- https://github.com/travis-ci/travis-rubies/blob/9f7962a881c55d32da7c76baefc58b89e3941d91/build.sh#L38-L44
-- https://github.com/travis-ci/travis-build/blob/91bf066/lib/travis/build/shell/dsl.rb#L58-L63
foldedSh' :: Fold -> String -> String -> Set Fold -> ShM () -> ShM ()
foldedSh' label sfx plabel labels block
| label `S.notMember` labels = commentedBlock plabel block
| otherwise = case runSh block of
Left err -> throwErr err
Right shs
| all isComment shs -> pure ()
| otherwise -> ShM $ \shs1 -> Right $
( shs1
. (Comment plabel :)
. (Sh ("echo '" ++ plabel ++ "' && echo -en 'travis_fold:start:" ++ label' ++ "\\\\r'") :)
. (shs ++)
. (Sh ("echo -en 'travis_fold:end:" ++ label' ++ "\\\\r'") :)
-- return ()
, ()
)
where
label' | null sfx = showFold label
| otherwise = showFold label ++ "-" ++ sfx
-- GHC versions which need head.hackage
headGhcVers :: Set CompilerVersion
headGhcVers = S.filter (previewGHC cfgHeadHackage) allVersions
cabal :: String -> String
cabal cmd = "${CABAL} " ++ cmd
cabalTW :: String -> String
cabalTW cmd = "travis_wait 40 ${CABAL} " ++ cmd
forJob :: CompilerRange -> String -> Maybe String
forJob vr cmd
| all (`compilerWithinRange` vr) allVersions = Just cmd
| not $ any (`compilerWithinRange` vr) allVersions = Nothing
| otherwise = Just $ unwords
[ "if"
, compilerVersionPredicate allVersions vr
, "; then"
, cmd
, "; fi"
]
shForJob :: CompilerRange -> String -> ShM ()
shForJob vr cmd = maybe (pure ()) sh (forJob vr cmd)
-- catForJob vr fp contents = shForJob vr (catCmd Double fp contents)
generateCabalProject :: Bool -> ShM ()
generateCabalProject dist = do
comment "Generate cabal.project"
sh "rm -rf cabal.project cabal.project.local cabal.project.freeze"
sh "touch cabal.project"
sh $ unlines
[ cmd
| pkg <- pkgs
, let p | dist = pkgNameDirVariable (pkgName pkg)
| otherwise = pkgDir pkg
, cmd <- toList $ forJob (RangePoints $ pkgJobs pkg) $
"echo \"packages: " ++ p ++ "\" >> cabal.project"
]
case cfgErrorMissingMethods of
PackageScopeNone -> pure ()
PackageScopeLocal -> for_ pkgs $ \Pkg{pkgName,pkgJobs} -> do
shForJob (Range (C.orLaterVersion (C.mkVersion [8,2])) /\ RangePoints pkgJobs) $
"echo 'package " ++ pkgName ++ "' >> cabal.project"
shForJob (Range (C.orLaterVersion (C.mkVersion [8,2])) /\ RangePoints pkgJobs) $
"echo ' ghc-options: -Werror=missing-methods' >> cabal.project"
PackageScopeAll -> do
sh "echo 'package *' >> cabal.project"
sh "echo ' ghc-options: -Werror=missing-methods' >> cabal.project"
cat "cabal.project" $ lines $ C.showFields' (const []) (const id) 2 extraCabalProjectFields
-- If using head.hackage, allow building with newer versions of GHC boot libraries.
-- Note that we put this in a cabal.project file, not ~/.cabal/config, in order to avoid
-- https://github.com/haskell/cabal/issues/7291.
unless (S.null headGhcVers) $ sh $ unlines $
[ "if $HEADHACKAGE; then"
, "echo \"allow-newer: $($HCPKG list --simple-output | sed -E 's/([a-zA-Z-]+)-[0-9.]+/*:\\1,/g')\" >> $CABALHOME/config"
, "fi"
]
-- also write cabal.project.local file with
-- @
-- constraints: base installed
-- constraints: array installed
-- ...
--
-- omitting any local package names
case normaliseInstalled cfgInstalled of
InstalledDiff pns -> sh $ unwords
[ "for pkg in $($HCPKG list --simple-output); do"
, "echo $pkg"
, "| sed 's/-[^-]*$//'"
, "| (grep -vE -- " ++ re ++ " || true)"
, "| sed 's/^/constraints: /'"
, "| sed 's/$/ installed/'"
, ">> cabal.project.local; done"
]
where
pns' = S.map C.unPackageName pns `S.union` foldMap (S.singleton . pkgName) pkgs
re = "'^(" ++ intercalate "|" (S.toList pns') ++ ")$'"
InstalledOnly pns | not (null pns') -> sh' [2043] $ unwords
[ "for pkg in " ++ unwords (S.toList pns') ++ "; do"
, "echo \"constraints: $pkg installed\""
, ">> cabal.project.local; done"
]
where
pns' = S.map C.unPackageName pns `S.difference` foldMap (S.singleton . pkgName) pkgs
-- otherwise: nothing
_ -> pure ()
sh "cat cabal.project || true"
sh "cat cabal.project.local || true"
-- Needed to work around haskell/cabal#6214
withHaddock :: String
withHaddock = "--with-haddock $HADDOCK"
data Quotes = Single | Double
escape :: Quotes -> String -> String
escape Single xs = "'" ++ concatMap f xs ++ "'" where
f '\0' = ""
f '\'' = "'\"'\"'"
f x = [x]
escape Double xs = show xs
catCmd :: Quotes -> FilePath -> [String] -> String
catCmd q fp contents = unlines
[ "echo " ++ escape q l ++ replicate (maxLength - length l) ' ' ++ " >> " ++ fp
| l <- contents
]
where
maxLength = foldl' (\a l -> max a (length l)) 0 contents
{-
-- https://travis-ci.community/t/multiline-commands-have-two-spaces-in-front-breaks-heredocs/2756
catCmd fp contents = unlines $
[ "cat >> " ++ fp ++ " << HEREDOC" ] ++
contents ++
[ "HEREDOC" ]
-}
cat :: FilePath -> [String] -> ShM ()
cat fp contents = sh $ catCmd Double fp contents