Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  run_clang_tidy.py

  Sprache: Python
 

#!/usr/bin/env python3
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

"""Run clang-tidy on NSS source files using compile_commands.json."""

import argparse
import json
import os
import re
import subprocess
import sys
from concurrent.futures import ThreadPoolExecutor, as_completed

EXTENSIONS = {".c"".cc"".cpp"".cxx"}

DEFAULT_EXCLUDES = {
    "gtests/google_test",
    "lib/sqlite",
    "lib/zlib",
    "lib/freebl/verified",
}


def find_compile_commands(repo_root):
    """Locate compile_commands.json in common build output locations."""
    candidates = [
        os.path.join(repo_root, "out""Debug""compile_commands.json"),
        os.path.join(repo_root, "out""Release""compile_commands.json"),
    ]
    for path in candidates:
        if os.path.isfile(path):
            return path
    return None


def ensure_compile_commands(repo_root):
    """Build NSS and generate compile_commands.json if missing.

    Returns the path to compile_commands.json, or None on failure.
    """
    cc_path = find_compile_commands(repo_root)
    if cc_path:
        return cc_path

    target_dir = os.path.join(repo_root, "out""Debug")
    cc_path = os.path.join(target_dir, "compile_commands.json")

    print("compile_commands.json not found — building NSS...", file=sys.stderr)
    result = subprocess.run([os.path.join(repo_root, "build.sh")], cwd=repo_root)
    if result.returncode != 0:
        print("error: build.sh failed", file=sys.stderr)
        return None

    print("Generating compile_commands.json...", file=sys.stderr)
    with open(cc_path, "w"as f:
        result = subprocess.run(
            ["ninja""-C", target_dir, "-t""compdb"], stdout=f, cwd=repo_root
        )
    if result.returncode != 0:
        print("error: ninja compdb failed", file=sys.stderr)
        return None

    return cc_path


def load_translation_units(
    compile_commands_path, repo_root, include_paths=None, exclude_paths=None, files=None
):
    """Load and filter translation units from compile_commands.json.

    Args:
        compile_commands_path: Path to compile_commands.json.
        repo_root: Repository root for computing relative paths.
        include_paths: If set, only include files under these prefixes.
        exclude_paths: Exclude files matching these prefixes.
        files: If set, only include these specific files.

    Returns:
        List of (file, directory, command) tuples.
    """
    with open(compile_commands_path) as f:
        entries = json.load(f)
    excludes = DEFAULT_EXCLUDES | set(exclude_paths or [])

    units = []
    seen = set()
    for entry in entries:
        src = entry.get("file""")
        directory = entry.get("directory""")
        # Resolve to repo-relative path for filtering.
        if os.path.isabs(src):
            abs_src = src
        else:
            abs_src = os.path.normpath(os.path.join(directory, src))
        rel = os.path.relpath(abs_src, repo_root)

        ext = os.path.splitext(rel)[1]
        if ext not in EXTENSIONS:
            continue

        if any(rel.startswith(ex) for ex in excludes):
            continue

        if include_paths and not any(rel.startswith(p) for p in include_paths):
            continue

        if files is not None:
            # Match against both absolute and relative paths.
            if rel not in files and abs_src not in files and src not in files:
                continue

        # Deduplicate: a file may appear multiple times in
        # compile_commands.json (e.g. compiled for different targets).
        # Only analyse each source file once.
        if rel in seen:
            continue
        seen.add(rel)

        units.append((src, directory, entry))

    return units


def parse_diff(diff_text):
    """Parse unified diff output into a dict of {file: [[start, end], ...]}.

    Only tracks added/modified line ranges (the ``+`` side of the diff) so
    that we can tell clang-tidy which lines are "interesting".
    """
    file_lines = {}
    current_file = None
    hunk_re = re.compile(r"^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@")

    for line in diff_text.splitlines():
        # Detect the destination filename.
        if line.startswith("+++ b/"):
            current_file = line[6:]
            continue
        if line.startswith("+++ "):
            # hg-style: +++ b/path or +++ path
            current_file = line[4:].lstrip("b/")
            continue

        m = hunk_re.match(line)
        if m and current_file is not None:
            start = int(m.group(1))
            count = int(m.group(2)) if m.group(2is not None else 1
            if count == 0:
                # Pure deletion hunk — no new lines.
                continue
            end = start + count - 1
            file_lines.setdefault(current_file, []).append([start, end])

    return file_lines


def build_line_filter(changed_lines, repo_root, compile_commands_path):
    """Build a clang-tidy --line-filter JSON string from *changed_lines*.

    *changed_lines* is the dict returned by :func:`parse_diff`.
    *compile_commands_path* is used to determine the build directory so
    that paths in the filter match the ``file`` entries that clang-tidy
    sees from compile_commands.json.
    Returns a JSON string suitable for ``--line-filter=...``, or *None*
    if there are no entries.
    """
    if not changed_lines:
        return None
    # clang-tidy matches --line-filter names against the "file" field in
    # compile_commands.json.  For NSS those are typically relative to the
    # build directory (e.g. "../../lib/foo/bar.c").  Construct the same
    # relative paths so the filter actually matches.
    build_dir = os.path.dirname(os.path.abspath(compile_commands_path))
    entries = []
    for path, ranges in changed_lines.items():
        abs_path = os.path.normpath(os.path.join(repo_root, path))
        rel_path = os.path.relpath(abs_path, build_dir)
        entries.append({"name": rel_path, "lines": ranges})
    return json.dumps(entries, separators=(","":"))


def get_diff(repo_root, base_rev=None):
    """Obtain a unified diff from the VCS in *repo_root*.

    If *base_rev* is given it is used as the base revision; otherwise the
    working-directory changes are diffed.
    """
    if os.path.isdir(os.path.join(repo_root, ".hg")):
        cmd = ["hg""diff""-U0"]
        if base_rev:
            cmd += ["--rev", base_rev]
    elif os.path.isdir(os.path.join(repo_root, ".git")):
        if base_rev:
            cmd = ["git""diff""-U0", base_rev]
        else:
            cmd = ["git""diff""-U0""HEAD"]
    else:
        return None

    result = subprocess.run(cmd, capture_output=True, text=True, cwd=repo_root)
    if result.returncode != 0:
        return None
    return result.stdout


def run_one(
    clang_tidy, src, directory, entry, checks, fix, fail_on_warnings, line_filter=None
):
    """Run clang-tidy on a single translation unit."""
    cmd = [clang_tidy, "-p", directory]

    if checks:
        cmd += ["--checks=" + checks]

    if fix:
        cmd += ["--fix"]

    if fail_on_warnings:
        cmd += ["--warnings-as-errors=*"]

    if line_filter:
        cmd += ["--line-filter=" + line_filter]

    cmd.append(src)

    result = subprocess.run(cmd, capture_output=True, text=True, cwd=directory)
    return src, result


def main(argv=None):
    parser = argparse.ArgumentParser(description="Run clang-tidy on NSS source files")
    parser.add_argument(
        "-p",
        "--compile-commands",
        help="Path to compile_commands.json (auto-detected if omitted)",
    )
    parser.add_argument(
        "-j",
        "--jobs",
        type=int,
        default=os.cpu_count(),
        help="Number of parallel jobs (default: CPU count)",
    )
    parser.add_argument(
        "--clang-tidy", default="clang-tidy", help="Path to clang-tidy binary"
    )
    parser.add_argument(
        "--checks", help="Override checks (passed to clang-tidy --checks)"
    )
    parser.add_argument(
        "--include",
        action="append",
        default=[],
        help="Only analyse files under these path prefixes",
    )
    parser.add_argument(
        "--exclude",
        action="append",
        default=[],
        help="Exclude files under these path prefixes",
    )
    parser.add_argument(
        "--fail-on-warnings", action="store_true", help="Treat all warnings as errors"
    )
    parser.add_argument(
        "--fix", action="store_true", help="Apply suggested fixes in-place"
    )
    parser.add_argument(
        "--build",
        action="store_true",
        help="Build NSS and generate compile_commands.json if missing",
    )
    parser.add_argument(
        "--diff",
        action="store_true",
        help="Only report diagnostics on changed lines (uses VCS diff)",
    )
    parser.add_argument(
        "--diff-base",
        help="Base revision for --diff (default: working-directory changes)",
    )
    parser.add_argument(
        "files", nargs="*", help="Specific files to check (default: all matching files)"
    )

    args = parser.parse_args(argv)

    # Locate repo root (parent of automation/).
    repo_root = os.path.dirname(
        os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    )

    # Find compile_commands.json.
    if args.compile_commands:
        cc_path = args.compile_commands
    elif args.build:
        cc_path = ensure_compile_commands(repo_root)
    else:
        cc_path = find_compile_commands(repo_root)

    if not cc_path or not os.path.isfile(cc_path):
        print(
            "error: compile_commands.json not found.  "
            "Build first with: ./build.sh (or pass --build)",
            file=sys.stderr,
        )
        return 1

    # Build a line filter when --diff is requested, and restrict the
    # file set to only files that appear in the diff.
    line_filter = None
    diff_files = None
    if args.diff or args.diff_base:
        diff_text = get_diff(repo_root, args.diff_base)
        if diff_text is None:
            print("error: could not obtain diff from VCS", file=sys.stderr)
            return 1
        changed_lines = parse_diff(diff_text)
        line_filter = build_line_filter(changed_lines, repo_root, cc_path)
        diff_files = set(os.path.normpath(f) for f in changed_lines)

    include_paths = args.include or None
    file_set = set(os.path.normpath(f) for f in args.files) if args.files else None

    # When diffing, restrict to changed files (intersect with any
    # explicit file list if one was provided).
    if diff_files is not None:
        if file_set is not None:
            file_set = file_set & diff_files
        else:
            file_set = diff_files

    units = load_translation_units(
        cc_path,
        repo_root,
        include_paths=include_paths,
        exclude_paths=args.exclude,
        files=file_set,
    )

    if not units:
        if file_set:
            print("No matching translation units for the given files.", file=sys.stderr)
            return 0
        print("No translation units found.", file=sys.stderr)
        return 1

    print("Running clang-tidy on {} file(s)...".format(len(units)), file=sys.stderr)

    jobs = args.jobs

    failures = []
    with ThreadPoolExecutor(max_workers=jobs) as pool:
        futures = {
            pool.submit(
                run_one,
                args.clang_tidy,
                src,
                directory,
                entry,
                args.checks,
                args.fix,
                args.fail_on_warnings,
                line_filter,
            ): src
            for src, directory, entry in units
        }

        for future in as_completed(futures):
            src, result = future.result()
            # Print any output (diagnostics) from clang-tidy.
            if result.stdout:
                print(result.stdout, end="")
            if result.stderr:
                print(result.stderr, end="", file=sys.stderr)
            if result.returncode != 0:
                failures.append(src)

    if failures:
        print(
            "\nclang-tidy found issues in {} file(s):".format(len(failures)),
            file=sys.stderr,
        )
        for f in sorted(failures):
            print("  {}".format(f), file=sys.stderr)
        return 1

    print("clang-tidy: no issues found.", file=sys.stderr)
    return 0


if __name__ == "__main__":
    sys.exit(main())

Messung V0.5 in Prozent
C=95 H=87 G=90

¤ Dauer der Verarbeitung: 0.4 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=277311
#Domains=752002