| #!/bin/sh -e |
| |
| # Upload a created tarball to Coverity Scan, as per |
| # https://scan.coverity.com/projects/qemu/builds/new |
| |
| # This work is licensed under the terms of the GNU GPL version 2, |
| # or (at your option) any later version. |
| # See the COPYING file in the top-level directory. |
| # |
| # Copyright (c) 2017-2020 Linaro Limited |
| # Written by Peter Maydell |
| |
| # Note that this script will automatically download and |
| # run the (closed-source) coverity build tools, so don't |
| # use it if you don't trust them! |
| |
| # This script assumes that you're running it from a QEMU source |
| # tree, and that tree is a fresh clean one, because we do an in-tree |
| # build. (This is necessary so that the filenames that the Coverity |
| # Scan server sees are relative paths that match up with the component |
| # regular expressions it uses; an out-of-tree build won't work for this.) |
| # The host machine should have as many of QEMU's dependencies |
| # installed as possible, for maximum coverity coverage. |
| |
| # To do an upload you need to be a maintainer in the Coverity online |
| # service, and you will need to know the "Coverity token", which is a |
| # secret 8 digit hex string. You can find that from the web UI in the |
| # project settings, if you have maintainer access there. |
| |
| # Command line options: |
| # --dry-run : run the tools, but don't actually do the upload |
| # --docker : create and work inside a container |
| # --docker-engine : specify the container engine to use (docker/podman/auto); |
| # implies --docker |
| # --update-tools-only : update the cached copy of the tools, but don't run them |
| # --no-update-tools : do not update the cached copy of the tools |
| # --tokenfile : file to read Coverity token from |
| # --version ver : specify version being analyzed (default: ask git) |
| # --description desc : specify description of this version (default: ask git) |
| # --srcdir : QEMU source tree to analyze (default: current working dir) |
| # --results-tarball : path to copy the results tarball to (default: don't |
| # copy it anywhere, just upload it) |
| # --src-tarball : tarball to untar into src dir (default: none); this |
| # is intended mainly for internal use by the Docker support |
| # |
| # User-specifiable environment variables: |
| # COVERITY_TOKEN -- Coverity token (default: looks at your |
| # coverity.token config) |
| # COVERITY_EMAIL -- the email address to use for uploads (default: |
| # looks at your git coverity.email or user.email config) |
| # COVERITY_BUILD_CMD -- make command (default: 'make -jN' where N is |
| # number of CPUs as determined by 'nproc') |
| # COVERITY_TOOL_BASE -- set to directory to put coverity tools |
| # (default: /tmp/coverity-tools) |
| # |
| # You must specify the token, either by environment variable or by |
| # putting it in a file and using --tokenfile. Everything else has |
| # a reasonable default if this is run from a git tree. |
| |
| check_upload_permissions() { |
| # Check whether we can do an upload to the server; will exit the script |
| # with status 1 if the check failed (usually a bad token); |
| # will exit the script with status 0 if the check indicated that we |
| # can't upload yet (ie we are at quota) |
| # Assumes that COVERITY_TOKEN, PROJNAME and DRYRUN have been initialized. |
| |
| echo "Checking upload permissions..." |
| |
| if ! up_perm="$(wget https://scan.coverity.com/api/upload_permitted --post-data "token=$COVERITY_TOKEN&project=$PROJNAME" -q -O -)"; then |
| echo "Coverity Scan API access denied: bad token?" |
| exit 1 |
| fi |
| |
| # Really up_perm is a JSON response with either |
| # {upload_permitted:true} or {next_upload_permitted_at:<date>} |
| # We do some hacky string parsing instead of properly parsing it. |
| case "$up_perm" in |
| *upload_permitted*true*) |
| echo "Coverity Scan: upload permitted" |
| ;; |
| *next_upload_permitted_at*) |
| if [ "$DRYRUN" = yes ]; then |
| echo "Coverity Scan: upload quota reached, continuing dry run" |
| else |
| echo "Coverity Scan: upload quota reached; stopping here" |
| # Exit success as this isn't a build error. |
| exit 0 |
| fi |
| ;; |
| *) |
| echo "Coverity Scan upload check: unexpected result $up_perm" |
| exit 1 |
| ;; |
| esac |
| } |
| |
| |
| build_docker_image() { |
| # build docker container including the coverity-scan tools |
| echo "Building docker container..." |
| # TODO: This re-unpacks the tools every time, rather than caching |
| # and reusing the image produced by the COPY of the .tgz file. |
| # Not sure why. |
| tests/docker/docker.py --engine ${DOCKER_ENGINE} build \ |
| -t coverity-scanner -f scripts/coverity-scan/coverity-scan.docker \ |
| --extra-files scripts/coverity-scan/run-coverity-scan \ |
| "$COVERITY_TOOL_BASE"/coverity_tool.tgz |
| } |
| |
| update_coverity_tools () { |
| # Check for whether we need to download the Coverity tools |
| # (either because we don't have a copy, or because it's out of date) |
| # Assumes that COVERITY_TOOL_BASE, COVERITY_TOKEN and PROJNAME are set. |
| |
| mkdir -p "$COVERITY_TOOL_BASE" |
| cd "$COVERITY_TOOL_BASE" |
| |
| echo "Checking for new version of coverity build tools..." |
| wget https://scan.coverity.com/download/cxx/linux64 --post-data "token=$COVERITY_TOKEN&project=$PROJNAME&md5=1" -O coverity_tool.md5.new |
| |
| if ! cmp -s coverity_tool.md5 coverity_tool.md5.new; then |
| # out of date md5 or no md5: download new build tool |
| # blow away the old build tool |
| echo "Downloading coverity build tools..." |
| rm -rf coverity_tool coverity_tool.tgz |
| wget https://scan.coverity.com/download/cxx/linux64 --post-data "token=$COVERITY_TOKEN&project=$PROJNAME" -O coverity_tool.tgz |
| if ! (cat coverity_tool.md5.new; echo " coverity_tool.tgz") | md5sum -c --status; then |
| echo "Downloaded tarball didn't match md5sum!" |
| exit 1 |
| fi |
| |
| if [ "$DOCKER" != yes ]; then |
| # extract the new one, keeping it corralled in a 'coverity_tool' directory |
| echo "Unpacking coverity build tools..." |
| mkdir -p coverity_tool |
| cd coverity_tool |
| tar xf ../coverity_tool.tgz |
| cd .. |
| mv coverity_tool.md5.new coverity_tool.md5 |
| fi |
| fi |
| rm -f coverity_tool.md5.new |
| cd "$SRCDIR" |
| |
| if [ "$DOCKER" = yes ]; then |
| build_docker_image |
| fi |
| } |
| |
| |
| # Check user-provided environment variables and arguments |
| DRYRUN=no |
| UPDATE=yes |
| DOCKER=no |
| |
| while [ "$#" -ge 1 ]; do |
| case "$1" in |
| --dry-run) |
| shift |
| DRYRUN=yes |
| ;; |
| --no-update-tools) |
| shift |
| UPDATE=no |
| ;; |
| --update-tools-only) |
| shift |
| UPDATE=only |
| ;; |
| --version) |
| shift |
| if [ $# -eq 0 ]; then |
| echo "--version needs an argument" |
| exit 1 |
| fi |
| VERSION="$1" |
| shift |
| ;; |
| --description) |
| shift |
| if [ $# -eq 0 ]; then |
| echo "--description needs an argument" |
| exit 1 |
| fi |
| DESCRIPTION="$1" |
| shift |
| ;; |
| --tokenfile) |
| shift |
| if [ $# -eq 0 ]; then |
| echo "--tokenfile needs an argument" |
| exit 1 |
| fi |
| COVERITY_TOKEN="$(cat "$1")" |
| shift |
| ;; |
| --srcdir) |
| shift |
| if [ $# -eq 0 ]; then |
| echo "--srcdir needs an argument" |
| exit 1 |
| fi |
| SRCDIR="$1" |
| shift |
| ;; |
| --results-tarball) |
| shift |
| if [ $# -eq 0 ]; then |
| echo "--results-tarball needs an argument" |
| exit 1 |
| fi |
| RESULTSTARBALL="$1" |
| shift |
| ;; |
| --src-tarball) |
| shift |
| if [ $# -eq 0 ]; then |
| echo "--src-tarball needs an argument" |
| exit 1 |
| fi |
| SRCTARBALL="$1" |
| shift |
| ;; |
| --docker) |
| DOCKER=yes |
| DOCKER_ENGINE=auto |
| shift |
| ;; |
| --docker-engine) |
| shift |
| if [ $# -eq 0 ]; then |
| echo "--docker-engine needs an argument" |
| exit 1 |
| fi |
| DOCKER=yes |
| DOCKER_ENGINE="$1" |
| shift |
| ;; |
| *) |
| echo "Unexpected argument '$1'" |
| exit 1 |
| ;; |
| esac |
| done |
| |
| if [ -z "$COVERITY_TOKEN" ]; then |
| COVERITY_TOKEN="$(git config coverity.token)" |
| fi |
| if [ -z "$COVERITY_TOKEN" ]; then |
| echo "COVERITY_TOKEN environment variable not set" |
| exit 1 |
| fi |
| |
| if [ -z "$COVERITY_BUILD_CMD" ]; then |
| NPROC=$(nproc) |
| COVERITY_BUILD_CMD="make -j$NPROC" |
| echo "COVERITY_BUILD_CMD: using default '$COVERITY_BUILD_CMD'" |
| fi |
| |
| if [ -z "$COVERITY_TOOL_BASE" ]; then |
| echo "COVERITY_TOOL_BASE: using default /tmp/coverity-tools" |
| COVERITY_TOOL_BASE=/tmp/coverity-tools |
| fi |
| |
| if [ -z "$SRCDIR" ]; then |
| SRCDIR="$PWD" |
| fi |
| |
| PROJNAME=QEMU |
| TARBALL=cov-int.tar.xz |
| |
| if [ "$UPDATE" = only ]; then |
| # Just do the tools update; we don't need to check whether |
| # we are in a source tree or have upload rights for this, |
| # so do it before some of the command line and source tree checks. |
| |
| if [ "$DOCKER" = yes ] && [ ! -z "$SRCTARBALL" ]; then |
| echo --update-tools-only --docker is incompatible with --src-tarball. |
| exit 1 |
| fi |
| |
| update_coverity_tools |
| exit 0 |
| fi |
| |
| if [ ! -e "$SRCDIR" ]; then |
| mkdir "$SRCDIR" |
| fi |
| |
| cd "$SRCDIR" |
| |
| if [ ! -z "$SRCTARBALL" ]; then |
| echo "Untarring source tarball into $SRCDIR..." |
| tar xvf "$SRCTARBALL" |
| fi |
| |
| echo "Checking this is a QEMU source tree..." |
| if ! [ -e "$SRCDIR/VERSION" ]; then |
| echo "Not in a QEMU source tree?" |
| exit 1 |
| fi |
| |
| # Fill in defaults used by the non-update-only process |
| if [ -z "$VERSION" ]; then |
| VERSION="$(git describe --always HEAD)" |
| fi |
| |
| if [ -z "$DESCRIPTION" ]; then |
| DESCRIPTION="$(git rev-parse HEAD)" |
| fi |
| |
| if [ -z "$COVERITY_EMAIL" ]; then |
| COVERITY_EMAIL="$(git config coverity.email)" |
| fi |
| if [ -z "$COVERITY_EMAIL" ]; then |
| COVERITY_EMAIL="$(git config user.email)" |
| fi |
| |
| # Otherwise, continue with the full build and upload process. |
| |
| check_upload_permissions |
| |
| if [ "$UPDATE" != no ]; then |
| update_coverity_tools |
| fi |
| |
| # Run ourselves inside docker if that's what the user wants |
| if [ "$DOCKER" = yes ]; then |
| # Put the Coverity token into a temporary file that only |
| # we have read access to, and then pass it to docker build |
| # using a volume. A volume is enough for the token not to |
| # leak into the Docker image. |
| umask 077 |
| SECRETDIR=$(mktemp -d) |
| if [ -z "$SECRETDIR" ]; then |
| echo "Failed to create temporary directory" |
| exit 1 |
| fi |
| trap 'rm -rf "$SECRETDIR"' INT TERM EXIT |
| echo "Created temporary directory $SECRETDIR" |
| SECRET="$SECRETDIR/token" |
| echo "$COVERITY_TOKEN" > "$SECRET" |
| echo "Archiving sources to be analyzed..." |
| ./scripts/archive-source.sh "$SECRETDIR/qemu-sources.tgz" |
| ARGS="--no-update-tools" |
| if [ "$DRYRUN" = yes ]; then |
| ARGS="$ARGS --dry-run" |
| fi |
| echo "Running scanner..." |
| # If we need to capture the output tarball, get the inner run to |
| # save it to the secrets directory so we can copy it out before the |
| # directory is cleaned up. |
| if [ ! -z "$RESULTSTARBALL" ]; then |
| ARGS="$ARGS --results-tarball /work/cov-int.tar.xz" |
| fi |
| # Arrange for this docker run to get access to the sources with -v. |
| # We pass through all the configuration from the outer script to the inner. |
| export COVERITY_EMAIL COVERITY_BUILD_CMD |
| tests/docker/docker.py run -it --env COVERITY_EMAIL --env COVERITY_BUILD_CMD \ |
| -v "$SECRETDIR:/work" coverity-scanner \ |
| ./run-coverity-scan --version "$VERSION" \ |
| --description "$DESCRIPTION" $ARGS --tokenfile /work/token \ |
| --srcdir /qemu --src-tarball /work/qemu-sources.tgz |
| if [ ! -z "$RESULTSTARBALL" ]; then |
| echo "Copying results tarball to $RESULTSTARBALL..." |
| cp "$SECRETDIR/cov-int.tar.xz" "$RESULTSTARBALL" |
| fi |
| echo "Docker work complete." |
| exit 0 |
| fi |
| |
| TOOLBIN="$(cd "$COVERITY_TOOL_BASE" && echo $PWD/coverity_tool/cov-analysis-*/bin)" |
| |
| if ! test -x "$TOOLBIN/cov-build"; then |
| echo "Couldn't find cov-build in the coverity build-tool directory??" |
| exit 1 |
| fi |
| |
| export PATH="$TOOLBIN:$PATH" |
| |
| cd "$SRCDIR" |
| |
| echo "Nuking build directory..." |
| rm -rf +build |
| mkdir +build |
| cd +build |
| |
| echo "Configuring..." |
| # We configure with a fixed set of enables here to ensure that we don't |
| # accidentally reduce the scope of the analysis by doing the build on |
| # the system that's missing a dependency that we need to build part of |
| # the codebase. |
| ../configure --disable-modules --enable-sdl --enable-gtk \ |
| --enable-opengl --enable-vte --enable-gnutls \ |
| --enable-nettle --enable-curses --enable-curl \ |
| --audio-drv-list=oss,alsa,sdl,pa --enable-virtfs \ |
| --enable-vnc --enable-vnc-sasl --enable-vnc-jpeg --enable-png \ |
| --enable-xen --enable-brlapi \ |
| --enable-linux-aio --enable-attr \ |
| --enable-cap-ng --enable-trace-backends=log --enable-spice --enable-rbd \ |
| --enable-libusb --enable-usb-redir \ |
| --enable-libiscsi --enable-libnfs --enable-seccomp \ |
| --enable-tpm --enable-libssh --enable-lzo --enable-snappy --enable-bzip2 \ |
| --enable-numa --enable-rdma --enable-smartcard --enable-virglrenderer \ |
| --enable-mpath --enable-glusterfs \ |
| --enable-virtfs --enable-zstd |
| |
| echo "Running cov-build..." |
| rm -rf cov-int |
| mkdir cov-int |
| cov-build --dir cov-int $COVERITY_BUILD_CMD |
| |
| echo "Creating results tarball..." |
| tar cvf - cov-int | xz > "$TARBALL" |
| |
| if [ ! -z "$RESULTSTARBALL" ]; then |
| echo "Copying results tarball to $RESULTSTARBALL..." |
| cp "$TARBALL" "$RESULTSTARBALL" |
| fi |
| |
| echo "Uploading results tarball..." |
| |
| if [ "$DRYRUN" = yes ]; then |
| echo "Dry run only, not uploading $TARBALL" |
| exit 0 |
| fi |
| |
| curl --form token="$COVERITY_TOKEN" --form email="$COVERITY_EMAIL" \ |
| --form file=@"$TARBALL" --form version="$VERSION" \ |
| --form description="$DESCRIPTION" \ |
| https://scan.coverity.com/builds?project="$PROJNAME" |
| |
| echo "Done." |