llvm/llvm/utils/prepare-code-coverage-artifact.py

#!/usr/bin/env python

from __future__ import print_function

"""Prepare a code coverage artifact.

- Collate raw profiles into one indexed profile.
- Generate html reports for the given binaries.

Caution: The positional arguments to this script must be specified before any 
optional arguments, such as --restrict.
"""

import argparse
import glob
import os
import subprocess
import sys


def merge_raw_profiles(host_llvm_profdata, profile_data_dir, preserve_profiles):
    print(":: Merging raw profiles...", end="")
    sys.stdout.flush()
    raw_profiles = glob.glob(os.path.join(profile_data_dir, "*.profraw"))
    manifest_path = os.path.join(profile_data_dir, "profiles.manifest")
    profdata_path = os.path.join(profile_data_dir, "Coverage.profdata")
    with open(manifest_path, "w") as manifest:
        manifest.write("\n".join(raw_profiles))
    subprocess.check_call(
        [
            host_llvm_profdata,
            "merge",
            "-sparse",
            "-f",
            manifest_path,
            "-o",
            profdata_path,
        ]
    )
    if not preserve_profiles:
        for raw_profile in raw_profiles:
            os.remove(raw_profile)
    os.remove(manifest_path)
    print("Done!")
    return profdata_path


def prepare_html_report(
    host_llvm_cov, profile, report_dir, binaries, restricted_dirs, compilation_dir
):
    print(":: Preparing html report for {0}...".format(binaries), end="")
    sys.stdout.flush()
    objects = []
    for i, binary in enumerate(binaries):
        if i == 0:
            objects.append(binary)
        else:
            objects.extend(("-object", binary))
    invocation = (
        [host_llvm_cov, "show"]
        + objects
        + [
            "-format",
            "html",
            "-instr-profile",
            profile,
            "-o",
            report_dir,
            "-show-line-counts-or-regions",
            "-show-directory-coverage",
            "-Xdemangler",
            "c++filt",
            "-Xdemangler",
            "-n",
        ]
        + restricted_dirs
    )
    if compilation_dir:
        invocation += ["-compilation-dir=" + compilation_dir]
    subprocess.check_call(invocation)
    with open(os.path.join(report_dir, "summary.txt"), "wb") as Summary:
        subprocess.check_call(
            [host_llvm_cov, "report"]
            + objects
            + ["-instr-profile", profile]
            + restricted_dirs,
            stdout=Summary,
        )
    print("Done!")


def prepare_html_reports(
    host_llvm_cov,
    profdata_path,
    report_dir,
    binaries,
    unified_report,
    restricted_dirs,
    compilation_dir,
):
    if unified_report:
        prepare_html_report(
            host_llvm_cov,
            profdata_path,
            report_dir,
            binaries,
            restricted_dirs,
            compilation_dir,
        )
    else:
        for binary in binaries:
            binary_report_dir = os.path.join(report_dir, os.path.basename(binary))
            prepare_html_report(
                host_llvm_cov,
                profdata_path,
                binary_report_dir,
                [binary],
                restricted_dirs,
                compilation_dir,
            )


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("host_llvm_profdata", help="Path to llvm-profdata")
    parser.add_argument("host_llvm_cov", help="Path to llvm-cov")
    parser.add_argument(
        "profile_data_dir", help="Path to the directory containing the raw profiles"
    )
    parser.add_argument(
        "report_dir", help="Path to the output directory for html reports"
    )
    parser.add_argument(
        "binaries",
        metavar="B",
        type=str,
        nargs="*",
        help="Path to an instrumented binary",
    )
    parser.add_argument(
        "--only-merge",
        action="store_true",
        help="Only merge raw profiles together, skip report " "generation",
    )
    parser.add_argument(
        "--preserve-profiles", help="Do not delete raw profiles", action="store_true"
    )
    parser.add_argument(
        "--use-existing-profdata", help="Specify an existing indexed profile to use"
    )
    parser.add_argument(
        "--unified-report",
        action="store_true",
        help="Emit a unified report for all binaries",
    )
    parser.add_argument(
        "--restrict",
        metavar="R",
        type=str,
        nargs="*",
        default=[],
        help="Restrict the reporting to the given source paths"
        " (must be specified after all other positional arguments)",
    )
    parser.add_argument(
        "-C",
        "--compilation-dir",
        type=str,
        default="",
        help="The compilation directory of the binary",
    )
    args = parser.parse_args()

    if args.use_existing_profdata and args.only_merge:
        print("--use-existing-profdata and --only-merge are incompatible")
        exit(1)

    if args.use_existing_profdata:
        profdata_path = args.use_existing_profdata
    else:
        profdata_path = merge_raw_profiles(
            args.host_llvm_profdata, args.profile_data_dir, args.preserve_profiles
        )

    if not len(args.binaries):
        print("No binaries specified, no work to do!")
        exit(1)

    if not args.only_merge:
        prepare_html_reports(
            args.host_llvm_cov,
            profdata_path,
            args.report_dir,
            args.binaries,
            args.unified_report,
            args.restrict,
            args.compilation_dir,
        )