Improve packaging of third-party Python 3 packages.
[arvados-dev.git] / jenkins / run-build-packages.sh
1 #!/bin/bash
2
3
4 read -rd "\000" helpmessage <<EOF
5 $(basename $0): Build Arvados packages and (optionally) upload them.
6
7 Syntax:
8         WORKSPACE=/path/to/arvados $(basename $0) [options]
9
10 Options:
11
12 --upload               Upload packages (default: false)
13 --scp-user USERNAME    Scp user for apt server (only required when --upload is specified)
14 --apt-server HOSTNAME  Apt server hostname (only required when --upload is specified)
15 --debug                Output debug information (default: false)
16
17 WORKSPACE=path         Path to the Arvados source tree to build packages from
18
19 EOF
20
21 EXITCODE=0
22 CALL_FREIGHT=0
23
24 DEBUG=0
25 UPLOAD=0
26
27 while [[ -n "$1" ]]
28 do
29     arg="$1"; shift
30     case "$arg" in
31         --help)
32             echo >&2 "$helpmessage"
33             echo >&2
34             exit 1
35             ;;
36         --scp-user)
37             APTUSER="$1"; shift
38             ;;
39         --apt-server)
40             APTSERVER="$1"; shift
41             ;;
42         --debug)
43             DEBUG=1
44             ;;
45         --upload)
46             UPLOAD=1
47             ;;
48         *)
49             echo >&2 "$0: Unrecognized option: '$arg'. Try: $0 --help"
50             exit 1
51             ;;
52     esac
53 done
54
55 # Sanity checks
56 if [[ "$UPLOAD" != '0' && ("$APTUSER" == '' || "$APTSERVER" == '') ]]; then
57   echo >&2 "$helpmessage"
58   echo >&2
59   echo >&2 "Error: please specify --scp-user and --apt-server if --upload is set"
60   echo >&2
61   exit 1
62 fi
63
64 # Sanity check
65 if ! [[ -n "$WORKSPACE" ]]; then
66   echo >&2 "$helpmessage"
67   echo >&2
68   echo >&2 "Error: WORKSPACE environment variable not set"
69   echo >&2
70   exit 1
71 fi
72
73 # Test for fpm
74 fpm --version >/dev/null 2>&1
75
76 if [[ "$?" != 0 ]]; then
77   echo >&2 "$helpmessage"
78   echo >&2
79   echo >&2 "Error: fpm not found"
80   echo >&2
81   exit 1
82 fi
83
84 if ! easy_install3 --version >/dev/null; then
85   cat >&2 <<EOF
86 $helpmessage
87
88 Error: easy_install3 (from python3-setuptools) not found
89
90 EOF
91   exit 1
92 fi
93
94 if [[ "$DEBUG" != 0 ]]; then
95   echo "Workspace is $WORKSPACE"
96 fi
97
98 version_from_git() {
99   # Generates a version number from the git log for the current working
100   # directory, and writes it to stdout.
101   local git_ts git_hash
102   declare $(TZ=UTC git log -n1 --first-parent --max-count=1 \
103       --format=format:"git_ts=%ct git_hash=%h" .)
104   echo "0.1.$(date -ud "@$git_ts" +%Y%m%d%H%M%S).$git_hash"
105 }
106
107 timestamp_from_git() {
108   # Generates a version number from the git log for the current working
109   # directory, and writes it to stdout.
110   local git_ts git_hash
111   declare $(TZ=UTC git log -n1 --first-parent --max-count=1 \
112       --format=format:"git_ts=%ct git_hash=%h" .)
113   echo "$git_ts"
114 }
115
116 handle_python_package () {
117   # This function assumes the current working directory is the python package directory
118   if [[ "$UPLOAD" != 0 ]]; then
119     # Make sure only to use sdist - that's the only format pip can deal with (sigh)
120     if [[ "$DEBUG" != 0 ]]; then
121       python setup.py sdist upload
122     else
123       python setup.py -q sdist upload
124     fi
125   else
126     # Make sure only to use sdist - that's the only format pip can deal with (sigh)
127     if [[ "$DEBUG" != 0 ]]; then
128       python setup.py sdist
129     else
130       python setup.py -q sdist
131     fi
132   fi
133 }
134
135 # Build debs for everything
136 build_and_scp_deb () {
137   # The package source.  Depending on the source type, this can be a
138   # path, or the name of the package in an upstream repository (e.g.,
139   # pip).
140   PACKAGE=$1
141   shift
142   # The name of the package to build.  Defaults to $PACKAGE.
143   PACKAGE_NAME=${1:-$PACKAGE}
144   shift
145   # Optional: the vendor of the package.  Should be "Curoverse, Inc." for
146   # packages of our own software.  Passed to fpm --vendor.
147   VENDOR=$1
148   shift
149   # The type of source package.  Passed to fpm -s.  Default "python".
150   PACKAGE_TYPE=${1:-python}
151   shift
152
153   if [ "python3" = "$PACKAGE_TYPE" ]; then
154       # fpm does not actually support this package type.  Instead we recognize
155       # it as a convenience shortcut to add several necessary arguments to
156       # fpm's command line later.
157       PACKAGE_TYPE=python
158       set -- "$@" --python-bin python3 --python-easyinstall easy_install3 \
159           --python-package-name-prefix python3 --depends python3
160   fi
161   # Optional: the package version number.  Passed to fpm -v.
162   VERSION=$1
163   shift
164
165   declare -a COMMAND_ARR=("fpm" "--maintainer=Ward Vandewege <ward@curoverse.com>" "-s" "$PACKAGE_TYPE" "-t" "deb" "-x" "usr/local/lib/python2.7/dist-packages/tests")
166
167   if [[ "$PACKAGE_NAME" != "$PACKAGE" ]]; then
168     COMMAND_ARR+=('-n' "$PACKAGE_NAME")
169   fi
170
171   if [[ "$VENDOR" != "" ]]; then
172     COMMAND_ARR+=('--vendor' "$VENDOR")
173   fi
174
175   if [[ "$VERSION" != "" ]]; then
176     COMMAND_ARR+=('-v' "$VERSION")
177   fi
178
179   # Append remaining function arguments directly to fpm's command line.
180   for i; do
181     COMMAND_ARR+=("$i")
182   done
183
184   COMMAND_ARR+=("$PACKAGE")
185
186   if [[ "$DEBUG" != 0 ]]; then
187     echo
188     echo "${COMMAND_ARR[@]}"
189     echo
190   fi
191
192   FPM_RESULTS=$("${COMMAND_ARR[@]}")
193   FPM_EXIT_CODE=$?
194
195   FPM_PACKAGE_NAME=''
196   if [[ $FPM_RESULTS =~ ([A-Za-z0-9_\-.]*\.deb) ]]; then
197     FPM_PACKAGE_NAME=${BASH_REMATCH[1]}
198   fi
199
200   if [[ "$FPM_PACKAGE_NAME" == "" ]]; then
201     EXITCODE=1
202     echo "Error: $PACKAGE: Unable to figure out package name from fpm results:"
203     echo
204     echo $FPM_RESULTS
205     echo
206   else
207     if [[ ! $FPM_RESULTS =~ "File already exists" ]]; then
208       if [[ "$FPM_EXIT_CODE" != "0" ]]; then
209         echo "Error building debian package for $1:\n $FPM_RESULTS"
210       else
211         if [[ "$UPLOAD" != 0 ]]; then
212           scp -P2222 $FPM_PACKAGE_NAME $APTUSER@$APTSERVER:tmp/
213           CALL_FREIGHT=1
214         fi
215       fi
216     else
217       echo "Debian package $FPM_PACKAGE_NAME exists, not rebuilding"
218     fi
219   fi
220 }
221
222 if [[ -f /etc/profile.d/rvm.sh ]]; then
223   source /etc/profile.d/rvm.sh
224 fi
225
226 # Make all files world-readable -- jenkins runs with umask 027, and has checked
227 # out our git tree here
228 chmod o+r "$WORKSPACE" -R
229
230 # More cleanup - make sure all executables that we'll package are 755
231 find -type d -name 'bin' |xargs -I {} find {} -type f |xargs -I {} chmod 755 {}
232
233 # Now fix our umask to something better suited to building and publishing
234 # gems and packages
235 umask 0022
236
237 if [[ "$DEBUG" != 0 ]]; then
238   echo "umask is" `umask`
239 fi
240
241 # Perl packages
242 if [[ "$DEBUG" != 0 ]]; then
243   echo -e "\nPerl packages\n"
244 fi
245
246 if [[ "$DEBUG" != 0 ]]; then
247   PERL_OUT=/dev/stdout
248 else
249   PERL_OUT=/dev/null
250 fi
251
252 cd "$WORKSPACE/sdk/perl"
253
254 if [[ -e Makefile ]]; then
255   make realclean >"$PERL_OUT"
256 fi
257 find -maxdepth 1 \( -name 'MANIFEST*' -or -name 'libarvados-perl_*.deb' \) \
258     -delete
259 rm -rf install
260
261 perl Makefile.PL >"$PERL_OUT" && \
262     make install PREFIX=install INSTALLDIRS=perl >"$PERL_OUT" && \
263     build_and_scp_deb install/=/usr libarvados-perl "Curoverse, Inc." dir \
264       "$(version_from_git)"
265
266 # Ruby gems
267 if [[ "$DEBUG" != 0 ]]; then
268   echo
269   echo "Ruby gems"
270   echo
271 fi
272
273 if type rvm-exec 2>/dev/null; then
274   FPM_GEM_PREFIX=$(rvm-exec system gem environment gemdir)
275 else
276   FPM_GEM_PREFIX=$(gem environment gemdir)
277 fi
278
279 cd "$WORKSPACE"
280 cd sdk/ruby
281 # clean up old packages
282 find -maxdepth 1 \( -name 'arvados-*.gem' -or -name 'rubygem-arvados_*.deb' \) \
283     -delete
284
285 if [[ "$DEBUG" != 0 ]]; then
286   gem build arvados.gemspec
287 else
288   # -q appears to be broken in gem version 2.2.2
289   gem build arvados.gemspec -q >/dev/null
290 fi
291
292 if [[ "$UPLOAD" != 0 ]]; then
293   # publish new gem
294   gem push arvados-*gem
295 fi
296
297 build_and_scp_deb arvados-*.gem "" "Curoverse, Inc." gem "" \
298     --prefix "$FPM_GEM_PREFIX"
299
300 # Build arvados-cli GEM
301 cd "$WORKSPACE"
302 cd sdk/cli
303 # clean up old gems
304 rm -f arvados-cli*gem
305
306 if [[ "$DEBUG" != 0 ]]; then
307   gem build arvados-cli.gemspec
308 else
309   # -q appears to be broken in gem version 2.2.2
310   gem build arvados-cli.gemspec -q >/dev/null
311 fi
312
313 if [[ "$UPLOAD" != 0 ]]; then
314   # publish new gem
315   gem push arvados-cli*gem
316 fi
317
318 # Python packages
319 if [[ "$DEBUG" != 0 ]]; then
320   echo
321   echo "Python packages"
322   echo
323 fi
324
325 cd "$WORKSPACE"
326
327 cd sdk/python
328 handle_python_package
329
330 cd ../../services/fuse
331 handle_python_package
332
333 cd ../../services/nodemanager
334 handle_python_package
335
336 if [[ ! -d "$WORKSPACE/debs" ]]; then
337   mkdir -p $WORKSPACE/debs
338 fi
339
340 # Arvados-src
341 # We use $WORKSPACE/src-build-dir as the clean directory from which to build the src package
342 if [[ ! -d "$WORKSPACE/src-build-dir" ]]; then
343   mkdir "$WORKSPACE/src-build-dir"
344   cd "$WORKSPACE"
345   if [[ "$DEBUG" != 0 ]]; then
346     git clone https://github.com/curoverse/arvados.git src-build-dir
347   else
348     git clone -q https://github.com/curoverse/arvados.git src-build-dir
349   fi
350 fi
351
352 cd "$WORKSPACE/src-build-dir"
353 # just in case, check out master
354 if [[ "$DEBUG" != 0 ]]; then
355   git checkout master
356   git pull
357   # go into detached-head state
358   git checkout `git log --format=format:%h -n1 .`
359 else
360   git checkout -q master
361   git pull -q
362   # go into detached-head state
363   git checkout -q `git log --format=format:%h -n1 .`
364 fi
365
366 # Build arvados src deb package
367 cd "$WORKSPACE"
368 PKG_VERSION=$(version_from_git)
369 cd $WORKSPACE/debs
370 build_and_scp_deb $WORKSPACE/src-build-dir/=/usr/local/arvados/src arvados-src 'Curoverse, Inc.' 'dir' "$PKG_VERSION" "--exclude=usr/local/arvados/src/.git" "--url=https://arvados.org" "--license=GNU Affero General Public License, version 3.0" "--description=The Arvados source code" "--architecture=all"
371
372 # clean up, check out master and step away from detached-head state
373 cd "$WORKSPACE/src-build-dir"
374 if [[ "$DEBUG" != 0 ]]; then
375   git checkout master
376 else
377   git checkout -q master
378 fi
379
380 # Keep
381 export GOPATH=$(mktemp -d)
382 mkdir -p "$GOPATH/src/git.curoverse.com"
383 ln -sfn "$WORKSPACE" "$GOPATH/src/git.curoverse.com/arvados.git"
384
385 # keepstore
386 cd "$GOPATH/src/git.curoverse.com/arvados.git/services/keepstore"
387 PKG_VERSION=$(version_from_git)
388 go get "git.curoverse.com/arvados.git/services/keepstore"
389 cd $WORKSPACE/debs
390 build_and_scp_deb $GOPATH/bin/keepstore=/usr/bin/keepstore keepstore 'Curoverse, Inc.' 'dir' "$PKG_VERSION" "--url=https://arvados.org" "--license=GNU Affero General Public License, version 3.0" "--description=Keepstore is the Keep storage daemon, accessible to clients on the LAN"
391
392 # Get GO SDK version
393 cd "$GOPATH/src/git.curoverse.com/arvados.git/sdk/go"
394 GO_SDK_VERSION=$(version_from_git)
395 GO_SDK_TIMESTAMP=$(timestamp_from_git)
396
397 # keepproxy
398 cd "$GOPATH/src/git.curoverse.com/arvados.git/services/keepproxy"
399 KEEPPROXY_VERSION=$(version_from_git)
400 KEEPPROXY_TIMESTAMP=$(timestamp_from_git)
401
402 if [[ "$GO_SDK_TIMESTAMP" -gt "$KEEPPROXY_TIMESTAMP" ]]; then
403   PKG_VERSION=$GO_SDK_VERSION
404 else
405   PKG_VERSION=$KEEPPROXY_VERSION
406 fi
407
408 go get "git.curoverse.com/arvados.git/services/keepproxy"
409 cd $WORKSPACE/debs
410 build_and_scp_deb $GOPATH/bin/keepproxy=/usr/bin/keepproxy keepproxy 'Curoverse, Inc.' 'dir' "$PKG_VERSION" "--url=https://arvados.org" "--license=GNU Affero General Public License, version 3.0" "--description=Keepproxy makes a Keep cluster accessible to clients that are not on the LAN"
411
412 # datamanager
413 cd "$GOPATH/src/git.curoverse.com/arvados.git/services/datamanager"
414 DATAMANAGER_VERSION=$(version_from_git)
415 DATAMANAGER_TIMESTAMP=$(timestamp_from_git)
416
417 if [[ "$GO_SDK_TIMESTAMP" -gt "$DATAMANAGER_TIMESTAMP" ]]; then
418   PKG_VERSION=$GO_SDK_VERSION
419 else
420   PKG_VERSION=$DATAMANAGER_VERSION
421 fi
422
423 go get "git.curoverse.com/arvados.git/services/datamanager"
424 cd $WORKSPACE/debs
425 build_and_scp_deb $GOPATH/bin/datamanager=/usr/bin/arvados-data-manager arvados-data-manager 'Curoverse, Inc.' 'dir' "$PKG_VERSION" "--url=https://arvados.org" "--license=GNU Affero General Public License, version 3.0" "--description=Datamanager ensures block replication levels, reports on disk usage and determines which blocks should be deleted when space is needed."
426
427 # arv-git-httpd
428 cd "$GOPATH/src/git.curoverse.com/arvados.git/services/arv-git-httpd"
429 ARVGITHTTPD_VERSION=$(version_from_git)
430 ARVGITHTTPD_TIMESTAMP=$(timestamp_from_git)
431
432 if [[ "$GO_SDK_TIMESTAMP" -gt "$ARVGITHTTPD_TIMESTAMP" ]]; then
433   PKG_VERSION=$GO_SDK_VERSION
434 else
435   PKG_VERSION=$ARVGITHTTPD_VERSION
436 fi
437
438 go get "git.curoverse.com/arvados.git/services/arv-git-httpd"
439 cd $WORKSPACE/debs
440 build_and_scp_deb $GOPATH/bin/arv-git-httpd=/usr/bin/arvados-git-httpd arvados-git-httpd 'Curoverse, Inc.' 'dir' "$PKG_VERSION" "--url=https://arvados.org" "--license=GNU Affero General Public License, version 3.0" "--description=Provides authenticated http access to Arvados-hosted git repositories."
441
442 # crunchstat
443 cd "$GOPATH/src/git.curoverse.com/arvados.git/services/crunchstat"
444 PKG_VERSION=$(version_from_git)
445 go get "git.curoverse.com/arvados.git/services/crunchstat"
446 cd $WORKSPACE/debs
447 build_and_scp_deb $GOPATH/bin/crunchstat=/usr/bin/crunchstat crunchstat 'Curoverse, Inc.' 'dir' "$PKG_VERSION" "--url=https://arvados.org" "--license=GNU Affero General Public License, version 3.0" "--description=Crunchstat gathers cpu/memory/network statistics of running Crunch jobs"
448
449 # The Python SDK
450 # Please resist the temptation to add --no-python-fix-name to the fpm call here
451 # (which would remove the python- prefix from the package name), because this
452 # package is a dependency of arvados-fuse, and fpm can not omit the python-
453 # prefix from only one of the dependencies of a package...  Maybe I could
454 # whip up a patch and send it upstream, but that will be for another day. Ward,
455 # 2014-05-15
456 cd $WORKSPACE/debs
457 # Python version numbering is obscure. Strip dashes and replace them with dots
458 # to match our other version numbers. Cf. commit 4afcb8c, compliance with PEP-440.
459 build_and_scp_deb $WORKSPACE/sdk/python python-arvados-python-client 'Curoverse, Inc.' 'python' "$(awk '($1 == "Version:"){ gsub(/-/,".",$2); print $2 }' $WORKSPACE/sdk/python/arvados_python_client.egg-info/PKG-INFO)" "--url=https://arvados.org" "--description=The Arvados Python SDK"
460
461 # The FUSE driver
462 # Please see comment about --no-python-fix-name above; we stay consistent and do
463 # not omit the python- prefix first.
464 cd $WORKSPACE/debs
465 # Python version numbering is obscure. Strip dashes and replace them with dots
466 # to match our other version numbers. Cf. commit 4afcb8c, compliance with PEP-440.
467 build_and_scp_deb $WORKSPACE/services/fuse python-arvados-fuse 'Curoverse, Inc.' 'python' "$(awk '($1 == "Version:"){ gsub(/-/,".",$2); print $2 }' $WORKSPACE/services/fuse/arvados_fuse.egg-info/PKG-INFO)" "--url=https://arvados.org" "--description=The Keep FUSE driver"
468
469 # The node manager
470 cd $WORKSPACE/debs
471 # Python version numbering is obscure. Strip dashes and replace them with dots
472 # to match our other version numbers. Cf. commit 4afcb8c, compliance with PEP-440.
473 build_and_scp_deb $WORKSPACE/services/nodemanager arvados-node-manager 'Curoverse, Inc.' 'python' "$(awk '($1 == "Version:"){ gsub(/-/,".",$2); print $2}' $WORKSPACE/services/nodemanager/arvados_node_manager.egg-info/PKG-INFO)" "--url=https://arvados.org" "--description=The Arvados node manager"
474
475 # The Docker image cleaner
476 cd $WORKSPACE/debs
477 build_and_scp_deb $WORKSPACE/services/dockercleaner arvados-docker-cleaner 'Curoverse, Inc.' 'python3' "$(awk '($1 == "Version:"){print $2}' $WORKSPACE/services/dockercleaner/arvados_docker_cleaner.egg-info/PKG-INFO)" "--url=https://arvados.org" "--description=The Arvados Docker image cleaner"
478
479 # A few dependencies
480 for deppkg in python-gflags pyvcf google-api-python-client oauth2client \
481       pyasn1 pyasn1-modules rsa uritemplate httplib2 ws4py virtualenv \
482       pykka apache-libcloud requests six pyexecjs jsonschema ciso8601 \
483       pycrypto backports.ssl_match_hostname; do
484     build_and_scp_deb "$deppkg"
485 done
486 # Python 3 dependencies
487 for deppkg in docker-py six requests; do
488     # The empty string is the vendor argument: these aren't Curoverse software.
489     build_and_scp_deb "$deppkg" "python3-$deppkg" "" python3
490 done
491
492 # cwltool from common-workflow-language. We use this in arv-run-pipeline-instance.
493 # We use $WORKSPACE/common-workflow-language as the clean directory from which to build the cwltool package
494 if [[ ! -d "$WORKSPACE/common-workflow-language" ]]; then
495   mkdir "$WORKSPACE/common-workflow-language"
496   cd "$WORKSPACE"
497   if [[ "$DEBUG" != 0 ]]; then
498     git clone https://github.com/common-workflow-language/common-workflow-language.git common-workflow-language
499   else
500     git clone -q https://github.com/common-workflow-language/common-workflow-language.git common-workflow-language
501   fi
502 fi
503
504 cd "$WORKSPACE/common-workflow-language"
505 if [[ "$DEBUG" != 0 ]]; then
506   git checkout master
507   git pull
508 else
509   git checkout -q master
510   git pull -q
511 fi
512
513 cd reference
514 handle_python_package
515 CWLTOOL_VERSION=`git log --first-parent --max-count=1 --format='format:0.1.%ct.%h'`
516
517 # Build cwltool package
518 cd $WORKSPACE/debs
519 # Python version numbering is obscure. Strip dashes and replace them with dots
520 # to match our other version numbers. Cf. commit 4afcb8c, compliance with PEP-440.
521 build_and_scp_deb $WORKSPACE/common-workflow-language/reference cwltool 'Common Workflow Language Working Group' 'python' "$(awk '($1 == "Version:"){ gsub(/-/,".",$2); print $2 }' $WORKSPACE/common-workflow-language/reference/cwltool.egg-info/PKG-INFO)"
522
523 # Finally, publish the packages, if necessary
524 if [[ "$UPLOAD" != 0 && "$CALL_FREIGHT" != 0 ]]; then
525   ssh -p2222 $APTUSER@$APTSERVER -t "cd tmp && ls -laF *deb && freight add *deb apt/wheezy && freight cache && rm -f *deb"
526 else
527   if [[ "$UPLOAD" != 0 ]]; then
528     echo "No new packages generated. No freight run necessary."
529   fi
530 fi
531
532 # clean up temporary GOPATH
533 rm -rf "$GOPATH"
534
535 exit $EXITCODE