diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index d1a9c326..f18929dd 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -91,17 +91,20 @@ jobs: matrix: python: ["3.9"] steps: - - uses: actions/checkout@v4 + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} - name: Install dependencies - run: pip install toml loguru tox + run: pip install tox - name: Build the package run: | export GUIDELLM_BUILD_TYPE=dev - export GUIDELLM_BUILD_NUMBER=${{ github.event.pull_request.number }} + export GUIDELLM_BUILD_ITERATION=${{ github.event.pull_request.number }} tox -e build - name: Upload build artifacts id: artifact-upload @@ -112,15 +115,23 @@ jobs: compression-level: 6 if-no-files-found: error retention-days: 30 + - name: Generate GitHub App token + id: app-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.GH_NM_REDHAT_AUTOMATION_APP_ID }} + private-key: ${{ secrets.GH_NM_REDHAT_AUTOMATION_APP_PRIVATE_KEY }} - name: Comment Install instructions uses: actions/github-script@v7 with: - github-token: ${{ secrets.GITHUB_TOKEN }} + github-token: ${{ steps.app-token.outputs.token }} script: | github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - body: `Build artifacts (.whl and .tar.gz) are available for download for up to 30 days. - They are located at ${{ steps.artifact-upload.outputs.artifact-url }}` + body: `📦 **Build Artifacts Available** + The build artifacts (\`.whl\` and \`.tar.gz\`) have been successfully generated and are available for download: ${{ steps.artifact-upload.outputs.artifact-url }}. + They will be retained for **up to 30 days**. + ` }) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index fc4f2bca..020cc2bd 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -60,17 +60,19 @@ jobs: matrix: python: ["3.9"] steps: - - uses: actions/checkout@v4 + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} - name: Install dependencies - run: pip install toml loguru tox + run: pip install tox - name: Build the package run: | export GUIDELLM_BUILD_TYPE=nightly - export GUIDELLM_BUILD_NUMBER=${{ github.run_number }} tox -e build - name: Find wheel artifact id: find-asset-whl @@ -103,4 +105,4 @@ jobs: retention-days: 30 - name: Log artifact location run: | - echo "Artifacts uploaded to: https://api.github.com/repos/neuralmagic/guidellm/actions/artifacts/${{ steps.artifact-upload.outputs.artifact-id }}" + echo "Artifacts uploaded to: ${{ steps.artifact-upload.outputs.artifact-url }}" diff --git a/.github/workflows/release-candidate.yml b/.github/workflows/release-candidate.yml index c751701d..007dfa4f 100644 --- a/.github/workflows/release-candidate.yml +++ b/.github/workflows/release-candidate.yml @@ -64,17 +64,19 @@ jobs: matrix: python: ["3.9"] steps: - - uses: actions/checkout@v4 + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} - name: Install dependencies - run: pip install toml loguru tox + run: pip install tox - name: Build the package run: | - export GUIDELLM_BUILD_TYPE=release_candidate - export GUIDELLM_BUILD_NUMBER=${{ github.run_number }} + export GUIDELLM_BUILD_TYPE=candidate tox -e build - name: Upload build artifacts id: artifact-upload @@ -87,7 +89,7 @@ jobs: retention-days: 30 - name: Log artifact location run: | - echo "Artifacts uploaded to: https://api.github.com/repos/neuralmagic/guidellm/actions/artifacts/${{ steps.artifact-upload.outputs.artifact-id }}" + echo "Artifacts uploaded to: ${{ steps.artifact-upload.outputs.artifact-url }}" - name: Push wheel to PyPI uses: neuralmagic/nm-actions/actions/publish-whl@v1.0.0 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fbc25279..c3b6a639 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,17 +12,19 @@ jobs: matrix: python: ["3.9"] steps: - - uses: actions/checkout@v4 + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} - name: Install dependencies - run: pip install toml loguru tox + run: pip install tox - name: Build the package run: | export GUIDELLM_BUILD_TYPE=release - export GUIDELLM_BUILD_NUMBER=${{ github.run_number }} tox -e build - name: Upload build artifacts id: artifact-upload @@ -35,7 +37,7 @@ jobs: retention-days: 90 - name: Log artifact location run: | - echo "Artifacts uploaded to: https://api.github.com/repos/neuralmagic/guidellm/actions/artifacts/${{ steps.artifact-upload.outputs.artifact-id }}" + echo "Artifacts uploaded to: Artifacts uploaded to: ${{ steps.artifact-upload.outputs.artifact-url }}" - name: Push wheel to PyPI uses: neuralmagic/nm-actions/actions/publish-whl@v1.0.0 with: diff --git a/.gitignore b/.gitignore index f610940f..d4186ed6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +# build version files +src/guidellm/version.txt +src/guidellm/version.py + # Output files benchmarks.json benchmarks.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9743354a..7e417f3c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -26,14 +26,14 @@ repos: pydantic_settings, pyyaml, respx, - requests, rich, + setuptools, + setuptools-git-versioning, transformers, # dev dependencies pytest, pydantic_settings, - requests-mock, # types types-click, diff --git a/pyproject.toml b/pyproject.toml index af93ae55..5869638e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools >= 61.0", "wheel", "build"] +requires = ["setuptools >= 61.0", "setuptools-git-versioning>=2.0,<3"] build-backend = "setuptools.build_meta" @@ -16,14 +16,32 @@ include = ["*"] # ************************************************ [project] +dynamic = ["version"] name = "guidellm" -version = "0.3.0" description = "Guidance platform for deploying and managing large language models." readme = { file = "README.md", content-type = "text/markdown" } requires-python = ">=3.9.0,<4.0" -license = { file = "LICENSE" } -authors = [ { name = "Neuralmagic, Inc." } ] -urls = { homepage = "https://github.com/neuralmagic/guidellm" } +license = "Apache-2.0" +license-files = ["LICENSE"] +authors = [ { name = "Red Hat" } ] +keywords = [ + "ai", + "benchmarking", + "deep-learning", + "deployment", + "evaluation", + "guidance", + "inference", + "language-models", + "large-language-model", + "llm", + "machine-learning", + "model-benchmark", + "model-evaluation", + "nlp", + "performance", + "vllm", +] dependencies = [ "click", "datasets", @@ -36,13 +54,17 @@ dependencies = [ "pydantic>=2.0.0", "pydantic-settings>=2.0.0", "pyyaml>=6.0.0", - "requests", "rich", "transformers", ] [project.optional-dependencies] dev = [ + # build + "build>=1.0.0", + "setuptools>=61.0", + "setuptools-git-versioning>=2.0,<3", + # general and configurations "pre-commit~=3.5.0", "scipy~=1.10", @@ -56,7 +78,6 @@ dev = [ "pytest-cov~=5.0.0", "pytest-mock~=3.14.0", "pytest-rerunfailures~=14.0", - "requests-mock~=1.12.1", "respx~=0.22.0", # code quality @@ -76,6 +97,12 @@ dev = [ "types-toml", ] +[project.urls] +homepage = "https://github.com/neuralmagic/guidellm" +source = "https://github.com/neuralmagic/guidellm" +issues = "https://github.com/neuralmagic/guidellm/issues" +docs = "https://github.com/neuralmagic/guidellm/tree/main/docs" + [project.entry-points.console_scripts] guidellm = "guidellm.__main__:cli" @@ -104,7 +131,7 @@ exclude = ["venv", ".tox"] follow_imports = 'silent' [[tool.mypy.overrides]] -module = ["datasets.*"] +module = ["datasets.*", "transformers.*", "setuptools.*", "setuptools_git_versioning.*"] ignore_missing_imports=true diff --git a/setup.py b/setup.py new file mode 100644 index 00000000..009cf362 --- /dev/null +++ b/setup.py @@ -0,0 +1,126 @@ +import os +import re +from pathlib import Path +from typing import Optional, Union + +from packaging.version import Version +from setuptools import setup +from setuptools_git_versioning import count_since, get_branch, get_sha, get_tags + +LAST_RELEASE_VERSION = Version("0.0.0") +TAG_VERSION_PATTERN = re.compile(r"^v(\d+\.\d+\.\d+)$") + + +def get_last_version_diff() -> tuple[Version, Optional[str], Optional[int]]: + """ + Get the last version, last tag, and the number of commits since the last tag. + If no tags are found, return the last release version and None for the tag/commits. + + :returns: A tuple containing the last version, last tag, and number of commits since + the last tag. + """ + tagged_versions = [ + (Version(match.group(1)), tag) + for tag in get_tags(root=Path(__file__).parent) + if (match := TAG_VERSION_PATTERN.match(tag)) + ] + tagged_versions.sort(key=lambda tv: tv[0]) + last_version, last_tag = ( + tagged_versions[-1] if tagged_versions else (LAST_RELEASE_VERSION, None) + ) + commits_since_last = ( + count_since(last_tag + "^{commit}", root=Path(__file__).parent) + if last_tag + else None + ) + + return last_version, last_tag, commits_since_last + + +def get_next_version( + build_type: str, build_iteration: Optional[Union[str, int]] +) -> tuple[Version, Optional[str], int]: + """ + Get the next version based on the build type and iteration. + - build_type == release: take the last version and add a post if build iteration + - build_type == candidate: increment to next minor, add 'rc' with build iteration + - build_type == nightly: increment to next minor, add 'a' with build iteration + - build_type == alpha: increment to next minor, add 'a' with build iteration + - build_type == dev: increment to next minor, add 'dev' with build iteration + + :param build_type: The type of build (release, candidate, nightly, alpha, dev). + :param build_iteration: The build iteration number. If None, defaults to the number + of commits since the last tag or 0 if no commits since the last tag. + :returns: A tuple containing the next version, the last tag the version is based + off of (if any), and the final build iteration used. + """ + version, tag, commits_since_last = get_last_version_diff() + + if not build_iteration and build_iteration != 0: + build_iteration = commits_since_last or 0 + elif isinstance(build_iteration, str): + build_iteration = int(build_iteration) + + if build_type == "release": + if commits_since_last: + # add post since we have commits since last tag + version = Version(f"{version.base_version}.post{build_iteration}") + return version, tag, build_iteration + + # not in release pathway, so need to increment to target next release version + version = Version(f"{version.major}.{version.minor + 1}.0") + + if build_type == "candidate": + # add 'rc' since we are in candidate pathway + version = Version(f"{version}.rc{build_iteration}") + elif build_type in ["nightly", "alpha"]: + # add 'a' since we are in nightly or alpha pathway + version = Version(f"{version}.a{build_iteration}") + else: + # assume 'dev' if not in any of the above pathways + version = Version(f"{version}.dev{build_iteration}") + + return version, tag, build_iteration + + +def write_version_files() -> tuple[Path, Path]: + """ + Write the version information to version.txt and version.py files. + version.txt contains the version string. + version.py contains the version plus additional metadata. + + :returns: A tuple containing the paths to the version.txt and version.py files. + """ + build_type = os.getenv("GUIDELLM_BUILD_TYPE", "dev").lower() + version, tag, build_iteration = get_next_version( + build_type=build_type, + build_iteration=os.getenv("GUIDELLM_BUILD_ITERATION"), + ) + module_path = Path(__file__).parent / "src" / "guidellm" + version_txt_path = module_path / "version.txt" + version_py_path = module_path / "version.py" + + with version_txt_path.open("w") as file: + file.write(str(version)) + + with version_py_path.open("w") as file: + file.writelines( + [ + f'version = "{version}"\n', + f'build_type = "{build_type}"\n', + f'build_iteration = "{build_iteration}"\n', + f'git_commit = "{get_sha()}"\n', + f'git_branch = "{get_branch()}"\n', + f'git_last_tag = "{tag}"\n', + ] + ) + + return version_txt_path, version_py_path + + +setup( + setuptools_git_versioning={ + "enabled": True, + "version_file": str(write_version_files()[0]), + } +) diff --git a/tox.ini b/tox.ini index d59f8caf..201a4e4d 100644 --- a/tox.ini +++ b/tox.ini @@ -66,14 +66,11 @@ description = Build the project deps = build setuptools - wheel - loguru - toml + setuptools-git-versioning setenv = GUIDELLM_BUILD_TYPE = {env:GUIDELLM_BUILD_TYPE:dev} - GUIDELLM_BUILD_NUMBER = {env:GUIDELLM_BUILD_NUMBER:0} + GUIDELLM_BUILD_ITERATION = {env:GUIDELLM_BUILD_ITERATION:} commands = - python utils/inject_build_props.py python -m build diff --git a/utils/__init__.py b/utils/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/utils/inject_build_props.py b/utils/inject_build_props.py deleted file mode 100644 index f76a93eb..00000000 --- a/utils/inject_build_props.py +++ /dev/null @@ -1,79 +0,0 @@ -import os -import re -from datetime import datetime -from pathlib import Path - -import toml -from loguru import logger - - -def get_build_type(): - return os.getenv("GUIDELLM_BUILD_TYPE", "dev") - - -def get_build_number(): - return os.getenv("GUIDELLM_BUILD_NUMBER", "0") - - -def construct_project_name_and_version(build_type, build_number, current_version): - if not re.match(r"^\d+\.\d+\.\d+$", current_version): - raise ValueError( - f"Version '{current_version}' does not match the " - f"semantic versioning pattern '#.#.#'", - ) - - if build_type == "dev": - version = f"{current_version}.dev{build_number}" - elif build_type == "nightly": - date_str = datetime.now().strftime("%Y%m%d") - version = f"{current_version}.a{date_str}" - elif build_type == "release_candidate": - date_str = datetime.now().strftime("%Y%m%d") - version = f"{current_version}.rc{date_str}" - elif build_type == "release": - version = current_version - else: - raise ValueError(f"Unknown build type: {build_type}") - - return "guidellm", version - - -def update_pyproject_toml(project_name, version): - try: - with Path("pyproject.toml").open() as file: - data = toml.load(file) - - data["project"]["name"] = project_name - data["project"]["version"] = version - - with Path("pyproject.toml").open("w") as file: - toml.dump(data, file) - - logger.info( - f"Updated project name to: {project_name} and version to: {version}", - ) - except (FileNotFoundError, toml.TomlDecodeError) as e: - logger.error(f"Error reading or writing pyproject.toml: {e}") - raise - - -def main(): - build_type = get_build_type() - build_number = get_build_number() - - with Path("pyproject.toml").open() as file: - pyproject_data = toml.load(file) - - current_version = pyproject_data["project"]["version"] - project_name, version = construct_project_name_and_version( - build_type, - build_number, - current_version, - ) - - if build_type != "release": - update_pyproject_toml(project_name, version) - - -if __name__ == "__main__": - main()