파일
rocm-systems/projects/rocprofiler-compute/utils/run-ci.py
T

334 라인
11 KiB
Python
실행 파일

#!/usr/bin/env python3
##############################################################################
# MIT License
#
# Copyright (c) 2025 Advanced Micro Devices, Inc. All Rights Reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
##############################################################################
"""
CI script to upload coverage XML files to CDash
"""
import argparse
import os
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path
def detect_repo_structure():
"""Detect if in monorepo or standalone structure"""
cwd = Path.cwd()
if "projects/rocprofiler-compute" in str(cwd):
parts = cwd.parts
idx = parts.index("rocprofiler-compute")
if idx > 0 and parts[idx - 1] == "projects":
monorepo_root = Path(*parts[: idx - 1])
project_root = monorepo_root / "projects" / "rocprofiler-compute"
return True, monorepo_root, project_root
if (cwd / "CMakeLists.txt").exists():
with open(cwd / "CMakeLists.txt") as f:
content = f.read()
if (
"project(rocprofiler-compute" in content
or "project(\n rocprofiler-compute" in content
):
return False, None, cwd
return False, None, cwd
def create_ctest_script(args):
"""Generate a CTest script for uploading coverage data"""
script_content = f"""
cmake_minimum_required(VERSION 3.19)
set(CTEST_PROJECT_NAME "{args.project_name}")
set(CTEST_NIGHTLY_START_TIME "01:00:00 UTC")
set(CTEST_DROP_SITE_CDASH TRUE)
if(CMAKE_VERSION VERSION_GREATER 3.14)
set(CTEST_SUBMIT_URL "{args.submit_url}")
else()
set(CTEST_DROP_METHOD "https")
set(CTEST_DROP_SITE "{args.cdash_host}")
set(CTEST_DROP_LOCATION "{args.submit_path}")
endif()
set(CTEST_SITE "{args.site}")
set(CTEST_BUILD_NAME "{args.build_name}")
set(CTEST_SOURCE_DIRECTORY "{args.source_dir}")
set(CTEST_BINARY_DIRECTORY "{args.binary_dir}")
file(MAKE_DIRECTORY "${{CTEST_BINARY_DIRECTORY}}")
if(NOT EXISTS "${{CTEST_BINARY_DIRECTORY}}/CMakeCache.txt")
file(WRITE "${{CTEST_BINARY_DIRECTORY}}/CMakeCache.txt"
"CMAKE_PROJECT_NAME:STATIC={args.project_name}\\n")
endif()
file(WRITE "${{CTEST_BINARY_DIRECTORY}}/CTestConfig.cmake" "
set(CTEST_PROJECT_NAME \\"{args.project_name}\\")
set(CTEST_NIGHTLY_START_TIME \\"01:00:00 UTC\\")
if(CMAKE_VERSION VERSION_GREATER 3.14)
set(CTEST_SUBMIT_URL \\"{args.submit_url}\\")
else()
set(CTEST_DROP_METHOD \\"https\\")
set(CTEST_DROP_SITE \\"{args.cdash_host}\\")
set(CTEST_DROP_LOCATION \\"{args.submit_path}\\")
endif()
set(CTEST_DROP_SITE_CDASH TRUE)
")
ctest_start({args.mode})
set(COVERAGE_SRC_FILE "{args.coverage_file}")
set(COVERAGE_DEST_FILE "${{CTEST_BINARY_DIRECTORY}}/coverage.xml")
if(NOT EXISTS "${{COVERAGE_SRC_FILE}}")
message(FATAL_ERROR "Coverage file not found: ${{COVERAGE_SRC_FILE}}")
endif()
file(COPY "${{COVERAGE_SRC_FILE}}" DESTINATION "${{CTEST_BINARY_DIRECTORY}}")
get_filename_component(SRC_FILENAME "${{COVERAGE_SRC_FILE}}" NAME)
if(NOT SRC_FILENAME STREQUAL "coverage.xml")
file(RENAME "${{CTEST_BINARY_DIRECTORY}}/${{SRC_FILENAME}}"
"${{COVERAGE_DEST_FILE}}")
endif()
message(STATUS "Processing coverage file: ${{COVERAGE_DEST_FILE}}")
file(MAKE_DIRECTORY "${{CTEST_BINARY_DIRECTORY}}/Testing")
file(MAKE_DIRECTORY "${{CTEST_BINARY_DIRECTORY}}/Testing/CoverageInfo")
file(COPY "${{COVERAGE_DEST_FILE}}"
DESTINATION "${{CTEST_BINARY_DIRECTORY}}/Testing/CoverageInfo")
ctest_coverage()
ctest_submit(PARTS Coverage
RETRY_COUNT 3
RETRY_DELAY 5
CAPTURE_CMAKE_ERROR submit_error)
if(submit_error)
message(FATAL_ERROR "Failed to submit coverage to CDash. "
"Error code: ${{submit_error}}")
endif()
message(STATUS "Successfully submitted coverage to CDash")
"""
return script_content
def main():
if not os.getenv("CI"):
print("WARNING: CDash upload should normally only be done from CI/CD")
response = input("Continue anyway? (y/N): ")
if response.lower() != "y":
print("Aborted.")
return 1
parser = argparse.ArgumentParser(
description="Upload coverage XML to CDash for rocprofiler-compute"
)
# required
parser.add_argument(
"--coverage-file", required=True, help="Path to coverage XML file"
)
parser.add_argument("--build-name", required=True, help="Build name for CDash")
# optional
parser.add_argument(
"--project-name", default="rocprofiler-compute", help="CDash project name"
)
parser.add_argument(
"--submit-url",
default="https://my.cdash.org/submit.php?project=rocprofiler-compute",
help="CDash submission URL",
)
parser.add_argument(
"--cdash-host", default="my.cdash.org", help="CDash host (for older CMake)"
)
parser.add_argument(
"--submit-path",
default="/submit.php?project=rocprofiler-compute",
help="CDash submission path (for older CMake)",
)
parser.add_argument(
"--site",
default=None,
help="Site name for CDash (auto-detected if not provided)",
)
parser.add_argument(
"--source-dir",
default=None,
help="Source directory path (auto-detected if not provided)",
)
parser.add_argument(
"--binary-dir",
default=None,
help="Binary directory path (defaults to source-dir/build)",
)
parser.add_argument(
"--mode",
default="Experimental",
choices=["Experimental", "Nightly", "Continuous"],
help="CTest dashboard mode",
)
parser.add_argument(
"--dry-run", action="store_true", help="Generate script but don't execute"
)
args = parser.parse_args()
is_monorepo, monorepo_root, project_root = detect_repo_structure()
if not args.source_dir:
args.source_dir = str(project_root)
if not args.binary_dir:
args.binary_dir = os.path.join(args.source_dir, "build")
if not args.site:
if is_monorepo:
args.site = f"Monorepo-{os.uname().nodename}"
else:
args.site = os.uname().nodename
coverage_path = Path(args.coverage_file)
if not coverage_path.is_absolute():
if not coverage_path.exists():
coverage_path = project_root / args.coverage_file
if not coverage_path.exists():
print(f"ERROR: Coverage file not found: {coverage_path}", file=sys.stderr)
print(f" Searched in: {Path(args.coverage_file).absolute()}")
print(f" And in: {project_root / args.coverage_file}")
return 1
try:
import xml.etree.ElementTree as ET
tree = ET.parse(coverage_path)
root = tree.getroot()
if root.tag != "coverage":
print(
f"ERROR: File does not appear to be a coverage XML file "
f"(root tag: {root.tag})",
file=sys.stderr,
)
return 1
line_rate = float(root.get("line-rate", 0)) * 100
print(f"Line Coverage: {line_rate:.1f}%")
except Exception as e:
print(f"ERROR: Could not parse coverage XML file: {e}", file=sys.stderr)
return 1
args.coverage_file = str(coverage_path.absolute())
args.source_dir = str(Path(args.source_dir).absolute())
args.binary_dir = str(Path(args.binary_dir).absolute())
print(f"Repository type: {'Monorepo' if is_monorepo else 'Standalone'}")
print(f"Project root: {project_root}")
print(f"Uploading coverage file: {args.coverage_file}")
print(f"Build name: {args.build_name}")
print(f"Project: {args.project_name}")
print(f"Site: {args.site}")
print(f"Submit URL: {args.submit_url}")
# Ensure build directory exists
os.makedirs(args.binary_dir, exist_ok=True)
# Copy CTestConfig.cmake if it exists in source
source_ctest_config = Path(args.source_dir) / "CTestConfig.cmake"
if source_ctest_config.exists():
shutil.copy2(source_ctest_config, Path(args.binary_dir) / "CTestConfig.cmake")
print("Copied CTestConfig.cmake to build directory")
script_content = create_ctest_script(args)
if args.dry_run:
print("\nGenerated CTest script:")
print("=" * 50)
print(script_content)
return 0
try:
with tempfile.NamedTemporaryFile(mode="w", suffix=".cmake", delete=False) as f:
f.write(script_content)
script_path = f.name
print(f"\nScript written to: {script_path}")
original_dir = os.getcwd()
os.chdir(args.source_dir)
cmd = ["ctest", "-S", script_path, "-V"]
print(f"Executing: {' '.join(cmd)}")
print(f"Working directory: {os.getcwd()}")
result = subprocess.run(cmd, capture_output=True, text=True)
if result.stdout:
print("\nSTDOUT:")
print(result.stdout)
if result.stderr:
print("\nSTDERR:")
print(result.stderr)
os.chdir(original_dir)
if result.returncode != 0:
print(f"\nCTest failed with return code: {result.returncode}")
return result.returncode
print("\n✅ Coverage successfully uploaded to CDash!")
cdash_url = args.submit_url.replace(
"/submit.php?project=", "/index.php?project="
)
print(f"View results at: {cdash_url}")
return 0
except Exception as e:
print(f"Error executing CTest script: {e}", file=sys.stderr)
import traceback
traceback.print_exc()
return 1
finally:
if "script_path" in locals():
try:
os.unlink(script_path)
except Exception:
pass
if __name__ == "__main__":
sys.exit(main())