diff --git a/pyproject.toml b/pyproject.toml index 2c657cc..c3333ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,7 @@ +[build-system] +requires = ["setuptools>=77"] +build-backend = "setuptools.build_meta" + [project] name = "mldebug_xdp" version = "0.1.0" diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..9f2958e --- /dev/null +++ b/setup.py @@ -0,0 +1,93 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright (C) 2024-2025 Advanced Micro Devices, Inc. All rights reserved. + +""" +Stamps the git commit into the built package. All metadata lives in pyproject.toml. +""" + +from datetime import datetime, timezone +from pathlib import Path + +import os +import subprocess + +from setuptools import setup +from setuptools.command.build_py import build_py +from setuptools.command.sdist import sdist + +ROOT = Path(__file__).parent.resolve() + + +def _git(*args): + """ + Run a git command in the source tree, returning "" on any failure. + """ + try: + out = subprocess.run( + ["git", "-C", str(ROOT), *args], capture_output=True, text=True, check=True, timeout=5 + ) + except (OSError, subprocess.SubprocessError): + return "" + return out.stdout.strip() + + +def _commit(): + """ + Full HEAD commit. Falls back to GITHUB_SHA when building without a .git dir. + """ + commit = _git("rev-parse", "HEAD") or os.environ.get("GITHUB_SHA", "") + if not commit: + return "" + # Scoped to tracked *.py: the LFS binaries under bin/ and backend/ show as modified + # after a smudge, and sdist leaves an untracked copy of the tree behind while it runs. + if _git("status", "--porcelain", "-uno", "--", "*.py"): + commit += "-dirty" + return commit + + +def _write_stamp(stamp, version): + """ + Write _build_info.py, or keep the existing one when the commit is unknown. + An unknown commit means we are building from an sdist, which already carries + the stamp written when the sdist itself was built. + """ + commit = _commit() + if not commit: + print(f"[WARNING] no git commit found, leaving {stamp} as is") + return + build_date = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%SZ") + # sdist hard links files out of the source tree, so replace instead of writing in place. + stamp.unlink(missing_ok=True) + stamp.write_text( + '"""Generated at build time by setup.py."""\n\n' + f'VERSION = "{version}"\n' + f'COMMIT = "{commit}"\n' + f'BUILD_DATE = "{build_date}"\n', + encoding="utf-8", + ) + print(f"[INFO] stamped {stamp} with commit '{commit}'") + + +class BuildPyStamped(build_py): + """ + Stamp the build tree after the sources are copied, leaving the source tree untouched. + """ + + def run(self): + super().run() + stamp = Path(self.build_lib) / "mldebug" / "_build_info.py" + _write_stamp(stamp, self.distribution.get_version()) + + +class SdistStamped(sdist): + """ + Stamp the sdist so a wheel built from it keeps the commit. + """ + + def make_release_tree(self, base_dir, files): + super().make_release_tree(base_dir, files) + stamp = Path(base_dir) / "src" / "mldebug" / "_build_info.py" + _write_stamp(stamp, self.distribution.get_version()) + + +setup(cmdclass={"build_py": BuildPyStamped, "sdist": SdistStamped}) diff --git a/src/mldebug/_build_info.py b/src/mldebug/_build_info.py new file mode 100644 index 0000000..c62fb5f --- /dev/null +++ b/src/mldebug/_build_info.py @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright (C) 2024-2025 Advanced Micro Devices, Inc. All rights reserved. + +""" +Build stamp. setup.py overwrites this inside the built package. +Values stay empty when running from a source tree. +""" + +VERSION = "" +COMMIT = "" +BUILD_DATE = "" diff --git a/src/mldebug/mldebug_cli.py b/src/mldebug/mldebug_cli.py index 2c54fd5..eae3ed5 100644 --- a/src/mldebug/mldebug_cli.py +++ b/src/mldebug/mldebug_cli.py @@ -31,7 +31,7 @@ create_run_flags, ) from mldebug.interactive_prompt import InteractivePrompt -from mldebug.utils import setup_logger, close_logger, is_windows +from mldebug.utils import LOGGER, setup_logger, close_logger, is_windows, version_string def _apply_unsupported_kernels_from_args(args): @@ -220,6 +220,12 @@ def app(): top_msg = "AIE Debug for VAIML.\nDefault data dump mode is binary. Files have 8byte header specifying total bytes." p = argparse.ArgumentParser(description=top_msg, formatter_class=RawTextHelpFormatter) + p.add_argument( + "--version", + action="version", + version=version_string(), + help="Show version, commit ID and exit.\n", + ) p.add_argument( "-b", "--buffer_info", @@ -410,6 +416,7 @@ def app(): ) args = p.parse_args() setup_logger(args) + LOGGER.log(f"[INFO] {version_string()}") if not check_args(args): print("Argument check failed") diff --git a/src/mldebug/utils.py b/src/mldebug/utils.py index 0623dd0..2574653 100644 --- a/src/mldebug/utils.py +++ b/src/mldebug/utils.py @@ -5,13 +5,14 @@ Helpful utilities like logging """ -from pathlib import Path - import os import platform import sys import threading import time +from pathlib import Path + +from mldebug import _build_info class Logger: @@ -106,6 +107,18 @@ def close_logger(): LOGGER.close() +def version_string(): + """ + One line banner: version, commit and build date of the installed package. + """ + if not _build_info.COMMIT: + return "mldebug (running from source, no build stamp)" + return ( + f"mldebug {_build_info.VERSION} (commit {_build_info.COMMIT})" + f" built {_build_info.BUILD_DATE}" + ) + + class Version: """ Simple versioning class for (major, minor) version management.