Debug
[ghc.git] / Jenkinsfile
1 #!groovy
2
3 /*
4   Jenkins dependencies:
5    * Pipeline Utility steps plugin
6
7   Linux (Debian) worker dependencies:
8    * xutil-dev curl automake autoconf libtool python3 python3-sphinx, llvm-4.0
9
10    Requires approvals for:
11    * new net.sf.json.JSONObject
12
13 */
14
15 import net.sf.json.JSONObject
16
17 properties(
18   [
19     parameters(
20       [
21         booleanParam(name: 'build_docs', defaultValue: false, description: 'build and upload documentation'),
22         booleanParam(name: 'nightly', defaultValue: false, description: 'are we building a nightly?'),
23         booleanParam(name: 'runNofib', defaultValue: false, description: 'run nofib and archive results')
24       ])
25   ])
26
27
28 stage("Build source distribution") {
29   node(label: 'linux') {
30     stage("Checking out tree") {
31       checkout scm
32       sh """
33          git submodule update --init --recursive
34          mk/get-win32-tarballs.sh fetch all
35          """
36     }
37     stage("Configuring tree") {
38       sh """
39          ./boot
40          ./configure
41          """
42     }
43     stage("Build tarballs") {
44       def version = getMakeValue('make', 'ProjectVersion')
45       sh "make sdist"
46       sh "mv sdistprep/ghc-${version}-src.tar.xz ghc-src.tar.xz"
47       sh "mv sdistprep/ghc-${version}-testsuite.tar.xz ghc-testsuite.tar.xz"
48       sh "mv sdistprep/ghc-${version}-windows-extra-src.tar.xz ghc-win32-tarballs.tar.xz"
49
50       def json = new JSONObject()
51       json.put('dirName', "ghc-${version}" as String)
52       json.put('commit', resolveCommitSha('HEAD'))
53       writeJSON(file: 'src-dist.json', json: json)
54
55       def src_dist_files = 'ghc-src.tar.xz,ghc-win32-tarballs.tar.xz,src-dist.json'
56       stash(name: 'source-dist', includes: src_dist_files)
57       stash(name: 'testsuite-dist', includes: 'ghc-testsuite.tar.xz')
58       archiveArtifacts artifacts: src_dist_files
59       archiveArtifacts artifacts: 'ghc-testsuite.tar.xz'
60     }
61   }
62 }
63
64 parallel (
65   "linux x86-64"       : {
66     node(label: 'linux && amd64') {
67       buildAndTestGhc(targetTriple: 'x86_64-linux-gnu')
68       if (params.build_docs) {
69         updateReadTheDocs()
70         updateUsersGuide()
71       }
72     }
73   },
74   "linux x86-64 -> aarch64 unreg" : {
75     node(label: 'linux && amd64') {buildAndTestGhc(crossCompiling: true, targetTriple: 'aarch64-linux-gnu', unreg: true)}
76   },
77   "linux x86-64 -> aarch64" : {
78     node(label: 'linux && amd64') {buildGhc(crossCompiling: true, targetTriple: 'aarch64-linux-gnu')}
79     node(label: 'linux && aarch64') {testGhc(targetTriple: 'aarch64-linux-gnu')}
80   },
81   "aarch64"            : {
82     node(label: 'linux && aarch64') {buildGhc(targetTriple: 'aarch64-linux-gnu')}
83   },
84   "freebsd"            : {
85     node(label: 'freebsd && amd64') {
86       buildGhc(targetTriple: 'x86_64-portbld-freebsd11.0', makeCmd: 'gmake', disableLargeAddrSpace: true)
87     }
88   },
89   // Requires cygpath plugin?
90   "windows 64"         : {
91     node(label: 'windows && amd64') {
92       withMingw('MINGW64') { buildAndTestGhc(targetTriple: 'x86_64-w64-mingw32') }
93     }
94   },
95   "windows 32"         : {
96     node(label: 'windows && amd64') {
97       withMingw('MINGW32') { buildAndTestGhc(targetTriple: 'x86_64-pc-msys') }
98     }
99   },
100   /*
101   "osx"                : {
102     node(label: 'darwin') {buildGhc(targetTriple: 'x86_64-apple-darwin16.0.0')}
103   }
104   */
105 )
106
107 if (params.runNofib) {
108   node(label: 'linux && amd64 && perf') {
109     nofib(targetTriple: 'x86_64-linux-gnu')
110   }
111 }
112
113
114 def withMingw(String msystem, Closure f) {
115   // Derived from msys2's /etc/msystem
116   String msysRoot = 'C:\\msys64'
117   String carch, prefix, ghcPath
118   home = sh(script: 'echo -n $HOME', returnStdout: true)
119   if (msystem == 'MINGW32') {
120     prefix = "${msysRoot}\\mingw32"
121     carch = 'i686'
122     ghcPath = "${home}/ghc-8.2.1-i386/bin"
123   } else if (msystem == 'MINGW64') {
124     prefix = "${msysRoot}\\mingw64"
125     carch = 'x86_64'
126     ghcPath = "${home}/ghc-8.2.1-x86_64/bin"
127   } else {
128     fail
129   }
130   String chost = "${carch}-w64-mingw32"
131
132   withEnv(["MSYSTEM=${msystem}",
133            "PATH+mingw=${prefix}\\bin",
134            "PATH+ghc=${ghcPath}",
135            "MSYSTEM_PREFIX=${prefix}",
136            "MSYSTEM_CARCH=${carch}",
137            "MSYSTEM_CHOST=${chost}",
138            "MINGW_CHOST=${chost}",
139            "MINGW_PREFIX=${prefix}",
140            "MINGW_PACKAGE_PREFIX=mingw-w64-${carch}",
141            "CONFIG_SITE=${prefix}/etc/config.site"
142           ], f)
143 }
144
145 def installPackages(String[] pkgs) {
146   sh "cabal install -j${env.THREADS} --with-compiler=`pwd`/inplace/bin/ghc-stage2 --package-db=`pwd`/inplace/lib/package.conf.d ${pkgs.join(' ')}"
147 }
148
149 def buildAndTestGhc(params) {
150   buildGhc(params)
151   testGhc(params)
152 }
153
154 def buildGhc(params) {
155   String targetTriple = params?.targetTriple
156   boolean crossCompiling = params?.crossCompiling ?: false
157   boolean unreg = params?.unreg ?: false
158   boolean disableLargeAddrSpace = params?.disableLargeAddrSpace ?: false
159   String makeCmd = params?.makeCmd ?: 'make'
160
161   withGhcSrcDist() {
162     echo '${targetTriple}'
163     echo '${params}'
164     stage('Configure') {
165       sh 'echo $PATH'
166       sh "which ghc"
167
168       def speed = 'NORMAL'
169       if (params.nightly) {
170         speed = 'SLOW'
171       }
172       build_mk = """
173                 Validating=YES
174                 ValidateSpeed=${speed}
175                 ValidateHpc=NO
176                 BUILD_DPH=NO
177                 """
178       if (crossCompiling) {
179         build_mk += """
180                     # Cross compiling
181                     HADDOCK_DOCS=NO
182                     BUILD_SPHINX_HTML=NO
183                     BUILD_SPHINX_PDF=NO
184                     INTEGER_LIBRARY=integer-simple
185                     WITH_TERMINFO=NO
186                     """
187       }
188       writeFile(file: 'mk/build.mk', text: build_mk)
189
190       def configure_opts = []
191       if (crossCompiling) {
192         configure_opts += '--target=${targetTriple}'
193       }
194       if (disableLargeAddrSpace) {
195         configure_opts += '--disable-large-address-space'
196       }
197       if (unreg) {
198         configure_opts += '--enable-unregisterised'
199       }
200       sh "./configure ${configure_opts.join(' ')}"
201     }
202
203     stage('Build') {
204       sh "${makeCmd} -j${env.THREADS}"
205     }
206
207     stage('Prepare binary distribution') {
208       sh "${makeCmd} binary-dist"
209       def json = new JSONObject()
210       def tarPath = getMakeValue(makeCmd, 'BIN_DIST_PREP_TAR_COMP')
211       def tarName = sh(script: "basename ${tarPath}", returnStdout: true).trim()
212       json.put('tarName', tarName)
213       json.put('dirName', getMakeValue(makeCmd, 'BIN_DIST_NAME'))
214       json.put('ghcVersion', getMakeValue(makeCmd, 'ProjectVersion'))
215       json.put('targetPlatform', getMakeValue(makeCmd, 'TARGETPLATFORM'))
216       echo "${json}"
217       writeJSON(file: 'bindist.json', json: json)
218       // Write a file so we can easily file the tarball and bindist directory later
219       stash(name: "bindist-${targetTriple}", includes: "bindist.json,${tarName}")
220       archiveArtifacts artifacts: tarName
221     }
222   }
223 }
224
225 String getMakeValue(String makeCmd, String value) {
226   return sh(script: "${makeCmd} -s echo! VALUE=${value}", returnStdout: true)
227 }
228
229 def withTempDir(String name, Closure f) {
230   sh """
231      rm -Rf ${name} || true
232      mkdir ${name}
233      """
234   try {
235     dir(name) {
236       f()
237     }
238   } finally {
239     sh "rm -Rf ${name}"
240   }
241 }
242
243 def withGhcSrcDist(Closure f) {
244   withTempDir('src-dist') {
245     stage('Unpack source distribution') {
246       unstash(name: "source-dist")
247       sh 'tar -xf ghc-src.tar.xz'
248       sh 'tar -xf ghc-win32-tarballs.tar.xz'
249     }
250
251     def metadata = readJSON file: 'src-dist.json'
252     dir(metadata.dirName) {
253       f()
254     }
255   }
256 }
257
258 def withGhcBinDist(String targetTriple, Closure f) {
259   withTempDir('bin-dist') {
260     unstash "bindist-${targetTriple}"
261     unstash "testsuite-dist"
262     def metadata = readJSON file: "bindist.json"
263     sh "tar -xf ${metadata.tarName}"
264     sh "tar -xf ghc-testsuite.tar.xz"
265     try {
266       dir(metadata.dirName) {
267         f()
268       }
269     } finally {
270       sh "rm -R ${metadata.dirName}"
271     }
272   }
273 }
274
275 def testGhc(params) {
276   String targetTriple = params?.targetTriple
277   // See Note [Spaces in TEST_HC]
278   String makeCmd = params?.makeCmd ?: 'make'
279   String instDir="${pwd()}/bindisttest/install   dir"
280   String testGhc="${instDir}/bin/ghc"
281
282   withGhcBinDist(targetTriple) {
283     stage('Configure') {
284       if (isUnix()) {
285           sh "./configure --prefix=\"${instDir}\""
286           sh "${makeCmd} install"
287       } else {
288           sh "mkdir -p \"${instDir}\""
289           sh "cp -a * \"${instDir}\""
290       }
291     }
292
293     stage('Install testsuite dependencies') {
294       if (params.nightly) {
295         def pkgs = ['mtl', 'parallel', 'parsec', 'primitive', 'QuickCheck',
296                     'random', 'regex-compat', 'syb', 'stm', 'utf8-string',
297                     'vector']
298         installPkgs pkgs
299       }
300     }
301
302     stage('Run testsuite') {
303       def target = 'test'
304       if (params.nightly) {
305         target = 'slowtest'
306       }
307       sh "${makeCmd} -Ctestsuite/tests LOCAL=0 BINDIST=YES THREADS=${env.THREADS} TEST_HC=\"${testGhc}\" JUNIT_FILE=../../testsuite.xml ${target}"
308       junit 'testsuite.xml'
309     }
310   }
311 }
312
313 def nofib(params) {
314   String targetTriple = params?.targetTriple
315   String makeCmd = params?.makeCmd ?: 'make'
316   withGhcBinDist(targetTriple) {
317     stage('Run nofib') {
318       installPkgs(['regex-compat'])
319       sh """
320          cd nofib
321          ${makeCmd} clean
322          ${makeCmd} boot
323          ${makeCmd} >../nofib.log 2>&1
324          """
325       archiveArtifacts artifacts: 'nofib.log'
326     }
327   }
328 }
329
330 def resolveCommitSha(String ref) {
331   return sh(script: "git rev-parse ${ref}", returnStdout: true).trim()
332 }
333
334 // Push update to ghc.readthedocs.org.
335 // Expects to be sitting in a build source tree.
336 def updateReadTheDocs() {
337   git clone 'git@github.com:bgamari/ghc-users-guide'
338   def commit = resolveCommitSha('HEAD')
339   sh """
340      export GHC_TREE=\$(pwd)
341      cd ghc-users-guide
342      ./export.sh
343      git commit -a -m \"Update to ghc commit ${commit}\" || true
344      git push
345      """
346 }
347
348 // Push update to downloads.haskell.org/~ghc/master/doc.
349 // Expects to be sitting in a configured source tree.
350 def updateUsersGuide() {
351   sh "${makeCmd} html haddock EXTRA_HADDOCK_OPTS=--hyperlinked-sources"
352   sh '''
353      out="$(mktemp -d)"
354      mkdir -p $out/libraries
355
356      cp -R docs/users_guide/build-html/users_guide $out/users-guide
357      for d in libraries/*; do
358          if [ ! -d $d/dist-install/doc ]; then continue; fi
359          mkdir -p $out/libraries/$(basename $d)
360          cp -R $d/dist-install/doc/*/* $out/libraries/\$(basename \$d)
361      done
362      cp -R libraries/*/dist-install/doc/* $out/libraries
363      chmod -R ugo+r $out
364
365      rsync -az $out/ downloads.haskell.org:public_html/master
366      rm -R $out
367      '''
368 }