diff --git a/.coderabbit.yaml b/.coderabbit.yaml new file mode 100644 index 0000000..91bbeaf --- /dev/null +++ b/.coderabbit.yaml @@ -0,0 +1,65 @@ +# Copyright 2023-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of NVIDIA CORPORATION nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json + +# Pilot configuration (verification on this repository only; fleet rollout +# is decided after evaluation). Requires the CodeRabbit GitHub App to be +# installed on the organization. +language: "en-US" + +reviews: + profile: "chill" + request_changes_workflow: false + high_level_summary: true + poem: false + review_status: true + auto_review: + enabled: true + # Drafts included during the pilot so the open TRI-1100 PR gets + # reviewed; revisit (likely false) before any fleet rollout. + drafts: true + path_instructions: + - path: ".github/workflows/*.yml" + instructions: | + These workflows are consumed org-wide (reusable workflows pinned by + tag). Review github-script blocks for correctness and injection + safety, and flag any behavior change that would require a new tag + and consumer rev bumps. + - path: "tools/*.py" + instructions: | + This tooling runs as a centralized pre-commit hook in consumer + repositories with cwd set to the consumer repo root. It must never + modify LICENSE files and must keep license headers out of .github/ + templates. + - path: "ISSUE_TEMPLATE/**" + instructions: | + Org-wide inherited defaults. Issue reporting must keep routing to + the server repository; frontmatter must stay the first line of any + template file. + +chat: + auto_reply: true diff --git a/.github/workflows/conventional-pr-check.yml b/.github/workflows/conventional-pr-check.yml new file mode 100644 index 0000000..523a3a7 --- /dev/null +++ b/.github/workflows/conventional-pr-check.yml @@ -0,0 +1,42 @@ +# Copyright 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of NVIDIA CORPORATION nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# Self-check: run this repository's own reusable conventional-pr workflow +# on its own pull requests (local path reference uses the PR's version). + +name: conventional-pr-check + +on: + pull_request: + types: [opened, edited, synchronize, reopened] + +jobs: + conventional-pr: + permissions: + pull-requests: write + issues: write + contents: read + uses: ./.github/workflows/conventional-pr.yml diff --git a/.github/workflows/conventional-pr.yml b/.github/workflows/conventional-pr.yml new file mode 100644 index 0000000..f6b9e9a --- /dev/null +++ b/.github/workflows/conventional-pr.yml @@ -0,0 +1,181 @@ +# Copyright 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of NVIDIA CORPORATION nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# Reusable workflow: validates the PR title against the Conventional Commits +# format (: ), labels the PR with its commit type (org-wide +# color scheme enforced), and labels cherry-pick PRs (detected via the +# `git cherry-pick -x` commit trailer or a title/branch hint). +# Call from a repo with: +# +# jobs: +# conventional-pr: +# uses: triton-inference-server/.github/.github/workflows/conventional-pr.yml@v1.1.0 +# +# The caller must grant `permissions: pull-requests: write`. + +name: conventional-pr + +on: + workflow_call: + +jobs: + validate-and-label: + runs-on: ubuntu-latest + permissions: + pull-requests: write + issues: write + contents: read + steps: + - name: Validate PR title and apply type label + uses: actions/github-script@v7 + with: + script: | + // Conventional-commit type -> human-readable label, org-wide color, + // and description. Titles are validated against the type tokens; + // the friendlier label is what gets applied. + const TYPES = { + build: { label: 'build', color: 'c5def5', + description: 'Build system or external dependencies (build: PRs)' }, + chore: { label: 'chore', color: 'fef2c0', + description: 'Maintenance work, no production code change (chore: PRs)' }, + ci: { label: 'CI/CD', color: 'bfd4f2', + description: 'Continuous integration and workflow changes (ci: PRs)' }, + docs: { label: 'documentation', color: '0075ca', + description: 'Improvements or additions to documentation (docs: PRs)' }, + feat: { label: 'feature', color: 'a2eeef', + description: 'New feature or capability (feat: PRs)' }, + fix: { label: 'fix', color: 'd73a4a', + description: 'Bug fix (fix: PRs)' }, + perf: { label: 'performance', color: 'fbca04', + description: 'Performance improvement (perf: PRs)' }, + refactor: { label: 'refactor', color: '5319e7', + description: 'Code change that neither fixes a bug nor adds a feature (refactor: PRs)' }, + revert: { label: 'revert', color: 'b60205', + description: 'Reverts a previous change (revert: PRs)' }, + style: { label: 'code style', color: 'f9d0c4', + description: 'Formatting and style-only changes (style: PRs)' }, + test: { label: 'testing', color: '1d76db', + description: 'Adding or correcting tests (test: PRs)' }, + }; + const CHERRY = { label: 'cherry-pick', color: '006b75', + description: 'Cherry-picked from another branch' }; + const types = Object.keys(TYPES); + const typeLabels = Object.values(TYPES).map((t) => t.label); + const { owner, repo } = context.repo; + const number = context.payload.pull_request.number; + + // Create the label if missing, otherwise enforce the org color and + // description. + async function ensureLabel({ label: name, color, description }) { + try { + await github.rest.issues.createLabel( + { owner, repo, name, color, description }); + } catch (e) { + if (e.status !== 422) throw e; // 422 = already exists + const { data } = await github.rest.issues.getLabel({ owner, repo, name }); + if (data.color.toLowerCase() !== color || + (data.description || '') !== description) { + await github.rest.issues.updateLabel( + { owner, repo, name, color, description }); + } + } + } + + // 1. Validate the PR title (the squash-merge commit title). + const title = context.payload.pull_request.title; + const match = title.match(/^(\w+)(\([^)]*\))?!?: .+/); + if (!match || !types.includes(match[1])) { + core.setFailed( + `PR title "${title}" does not follow the Conventional Commits ` + + `format "<type>: <Title>" with type one of: ${types.join(', ')}. ` + + 'See https://www.conventionalcommits.org/'); + return; + } + + // 2. Derive the full type set: the title plus every conforming + // commit subject in the PR (merge commits are ignored). + const commits = await github.paginate(github.rest.pulls.listCommits, + { owner, repo, pull_number: number, per_page: 100 }); + const derived = new Set([match[1]]); + for (const c of commits) { + const subject = c.commit.message.split('\n', 1)[0]; + if (/^Merge /.test(subject)) continue; + const m = subject.match(/^(\w+)(\([^)]*\))?!?: .+/); + if (m && types.includes(m[1])) derived.add(m[1]); + else core.warning(`Commit ${c.sha.slice(0, 9)} subject is not ` + + `a conventional commit: "${subject}"`); + } + + const { data: current } = await github.rest.issues.listLabelsOnIssue( + { owner, repo, issue_number: number }); + // Fail-safe: no derivable type and no manually assigned type label. + if (derived.size === 0 && + !current.some((l) => typeLabels.includes(l.name))) { + core.setFailed( + 'No conventional commit type could be determined from the PR ' + + 'title or its commits, and no type label is assigned.'); + return; + } + + // 3. Apply one label per derived type; drop stale auto-managed + // labels (incl. legacy raw-token names from earlier versions). + const wanted = [...derived].map((t) => TYPES[t].label); + for (const t of derived) await ensureLabel(TYPES[t]); + for (const l of current) { + if ((typeLabels.includes(l.name) || types.includes(l.name)) && + !wanted.includes(l.name)) { + await github.rest.issues.removeLabel( + { owner, repo, issue_number: number, name: l.name }); + } + } + await github.rest.issues.addLabels( + { owner, repo, issue_number: number, labels: wanted }); + core.info(`Labeled: ${wanted.join(', ')}.`); + + // 4. Cherry-pick detection: the standard `git cherry-pick -x` + // trailer in any commit, an explicit hint in the title/branch, or + // a trailing "(#N)" squash-merge reference to a DIFFERENT PR (the + // signature of cherry-picking an already squash-merged commit). + // Additive only - a manually applied label is never removed. + const trailer = /cherry[- ]?picked from commit [0-9a-f]{7,40}/i; + const hint = /cherry[- ]?pick/i; + const prRef = /\(#(\d+)\)$/; + const refersToOtherPr = (subject) => { + const m = subject.trim().match(prRef); + return m !== null && Number(m[1]) !== number; + }; + const isCherryPick = + commits.some((c) => trailer.test(c.commit.message)) || + hint.test(title) || + hint.test(context.payload.pull_request.head.ref) || + refersToOtherPr(title) || + commits.some((c) => refersToOtherPr(c.commit.message.split('\n', 1)[0])); + if (isCherryPick) { + await ensureLabel(CHERRY); + await github.rest.issues.addLabels( + { owner, repo, issue_number: number, labels: [CHERRY.label] }); + core.info('Cherry-pick detected; labeled as "cherry-pick".'); + } diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml new file mode 100644 index 0000000..cd8680b --- /dev/null +++ b/.github/workflows/pre-commit.yml @@ -0,0 +1,54 @@ +# Copyright 2023-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of NVIDIA CORPORATION nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +name: pre-commit + +on: + pull_request: + +jobs: + pre-commit: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v5.0.0 + with: + fetch-depth: 2 + - uses: actions/setup-python@v6.0.0 + - uses: actions/cache@v4 + with: + path: ~/.cache/pre-commit + key: pre-commit-${{ runner.os }}-${{ hashFiles('.pre-commit-config.yaml') }} + - name: Run pre-commit on the files modified by the PR + # Null-delimited so paths with spaces survive; --no-run-if-empty + # handles deletion-only PRs; deleted paths are filtered out before + # being handed to pre-commit. + run: | + python -m pip install --quiet pre-commit + git diff --name-only -z --diff-filter=d HEAD^1 HEAD \ + | xargs -0 --no-run-if-empty \ + pre-commit run --show-diff-on-failure --color=always --files diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..3c4e9bb --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,100 @@ +# Copyright 2023-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of NVIDIA CORPORATION nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +default_install_hook_types: [pre-commit, commit-msg] + +repos: +- repo: https://github.com/PyCQA/isort + rev: 5.12.0 + hooks: + - id: isort + additional_dependencies: [toml] +- repo: https://github.com/psf/black + rev: 23.1.0 + hooks: + - id: black + types_or: [python, cython] +- repo: https://github.com/PyCQA/flake8 + rev: 7.3.0 + hooks: + - id: flake8 + args: ["--max-line-length=88", "--select=C,E,F,W,B,B950", "--extend-ignore=E203,E501"] + types_or: [python, cython] +- repo: https://github.com/pre-commit/mirrors-clang-format + rev: v16.0.5 + hooks: + - id: clang-format + types_or: [c, c++, cuda, proto, textproto, java] + args: ["-fallback-style=none", "-style=file", "-i"] +- repo: https://github.com/codespell-project/codespell + rev: v2.2.4 + hooks: + - id: codespell + additional_dependencies: [tomli] + args: ["--toml", "pyproject.toml"] + exclude: (?x)^(.*stemmer.*|.*stop_words.*|^CHANGELOG.md$) +# Validates commit messages against the Conventional Commits format +# (<commit_type>: <title>). Replaces the manual commit-type checklist that +# used to live in the PR template. +- repo: https://github.com/compilerla/conventional-pre-commit + rev: v4.4.0 + hooks: + - id: conventional-pre-commit + stages: [commit-msg] + args: [build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test] +# More details about these pre-commit hooks here: +# https://pre-commit.com/hooks.html +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: check-case-conflict + - id: check-executables-have-shebangs + - id: check-merge-conflict + - id: check-json + - id: check-toml + - id: check-yaml + - id: check-shebang-scripts-are-executable + - id: end-of-file-fixer + types_or: [c, c++, cuda, proto, textproto, java, python] + - id: mixed-line-ending + - id: requirements-txt-fixer + - id: trailing-whitespace + +# This repository defines the centralized add-license hook; run the in-tree +# version directly so CI validates the hook code being merged. Consumer +# repositories reference it via `repo: .../.github` + a pinned tag. +- repo: local + hooks: + - id: add-license + name: Add License + entry: tools/add_copyright.py + language: script + stages: [pre-commit] + verbose: true + require_serial: true + # GitHub issue/PR templates must start with YAML frontmatter; keep + # license headers out of them. + exclude: ^(ISSUE_TEMPLATE/|PULL_REQUEST_TEMPLATE|pull_request_template) diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml new file mode 100644 index 0000000..55576fa --- /dev/null +++ b/.pre-commit-hooks.yaml @@ -0,0 +1,37 @@ +# Copyright 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of NVIDIA CORPORATION nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +- id: add-license + name: Add License + entry: tools/add_copyright.py + language: script + stages: [pre-commit] + verbose: true + require_serial: true + # GitHub issue/PR templates must start with YAML frontmatter; keep + # license headers out of them. Workflows and other .github/ files ARE + # processed. + exclude: ^\.github/(ISSUE_TEMPLATE/|PULL_REQUEST_TEMPLATE|pull_request_template) diff --git a/ISSUE_TEMPLATE/config.yml b/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..1fb3f37 --- /dev/null +++ b/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Bug report or feature request + url: https://github.com/triton-inference-server/server/issues/new/choose + about: Please report issues for all Triton Inference Server components in the server repository. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8464654 --- /dev/null +++ b/LICENSE @@ -0,0 +1,25 @@ +Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of NVIDIA CORPORATION nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/PULL_REQUEST_TEMPLATE/pull_request_template_external_contrib.md b/PULL_REQUEST_TEMPLATE/pull_request_template_external_contrib.md new file mode 100644 index 0000000..c5a3bb8 --- /dev/null +++ b/PULL_REQUEST_TEMPLATE/pull_request_template_external_contrib.md @@ -0,0 +1,51 @@ +#### What does the PR do? +<!-- Describe your pull request here. Please read the text below the line, and make sure you follow the checklist.--> + +#### Checklist +- [ ] I have read the [Contribution guidelines](https://github.com/triton-inference-server/server/blob/main/CONTRIBUTING.md) and signed the [Contributor License +Agreement](https://github.com/NVIDIA/triton-inference-server/blob/master/Triton-CCLA-v1.pdf) +- [ ] PR title reflects the change and is of format `<commit_type>: <Title>` +- [ ] Changes are described in the pull request. +- [ ] Related issues are referenced. +- [ ] Populated [github labels](https://docs.github.com/en/issues/using-labels-and-milestones-to-track-work/managing-labels) field +- [ ] Added [test plan](#test-plan) and verified test passes. +- [ ] Verified that the PR passes existing CI. +- [ ] I ran pre-commit locally (`pre-commit install, pre-commit run --all`) +- [ ] Verified copyright is correct on all changed files. +- [ ] Added _succinct_ git squash message before merging [ref](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). +- [ ] All template sections are filled out. +- [ ] Optional: Additional screenshots for behavior/output changes with before/after. + +#### Commit Type: +Check the [conventional commit type](https://github.com/angular/angular/blob/22b96b9/CONTRIBUTING.md#type) +box here and add the label to the github PR. +- [ ] build +- [ ] chore +- [ ] ci +- [ ] docs +- [ ] feat +- [ ] fix +- [ ] perf +- [ ] refactor +- [ ] revert +- [ ] style +- [ ] test + +#### Related PRs: +<!-- Related PRs from other Repositories --> + +#### Where should the reviewer start? +<!-- call out specific files that should be looked at closely --> + +#### Test plan: +<!-- list steps to verify feature works --> +<!-- were e2e tests added?--> + +#### Caveats: +<!-- any limitations or possible things missing from this PR --> + +#### Background +<!-- e.g. what led to this change being made. this is optional extra information to help the reviewer --> + +#### Related Issues: (use one of the action keywords Closes / Fixes / Resolves / Relates to) +- closes GitHub issue: #xxx diff --git a/PULL_REQUEST_TEMPLATE/pull_request_template_internal_contrib.md b/PULL_REQUEST_TEMPLATE/pull_request_template_internal_contrib.md new file mode 100644 index 0000000..42b1bb9 --- /dev/null +++ b/PULL_REQUEST_TEMPLATE/pull_request_template_internal_contrib.md @@ -0,0 +1,36 @@ +#### What does the PR do? +<!-- Describe your pull request here. Please read the text below the line, and make sure you follow the checklist.--> + +#### Checklist +- [ ] PR title reflects the change and is of format `<commit_type>: <Title>` +- [ ] Changes are described in the pull request. +- [ ] Related issues are referenced. +- [ ] Populated [github labels](https://docs.github.com/en/issues/using-labels-and-milestones-to-track-work/managing-labels) field +- [ ] Added [test plan](#test-plan) and verified test passes. +- [ ] Verified that the PR passes existing CI. +- [ ] Verified copyright is correct on all changed files. +- [ ] Added _succinct_ git squash message before merging [ref](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). +- [ ] All template sections are filled out. +- [ ] Optional: Additional screenshots for behavior/output changes with before/after. + +#### Related PRs: +<!-- Related PRs from other Repositories --> + +#### Where should the reviewer start? +<!-- call out specific files that should be looked at closely --> + +#### Test plan: +<!-- list steps to verify --> +<!-- were e2e tests added?--> + +- CI Pipeline ID: +<!-- Only Pipeline ID and no direct link here --> + +#### Caveats: +<!-- any limitations or possible things missing from this PR --> + +#### Background +<!-- e.g. what led to this change being made. this is optional extra information to help the reviewer --> + +#### Related Issues: (use one of the action keywords Closes / Fixes / Resolves / Relates to) +- closes GitHub issue: #xxx diff --git a/pull_request_template.md b/pull_request_template.md new file mode 100644 index 0000000..0787dcb --- /dev/null +++ b/pull_request_template.md @@ -0,0 +1,13 @@ +Thanks for submitting a PR to Triton! +Please go the the `Preview` tab above this description box and select the appropriate sub-template: + +* [PR description template for Triton Engineers](?expand=1&template=pull_request_template_internal_contrib.md) +* [PR description template for External Contributors](?expand=1&template=pull_request_template_external_contrib.md) + +If you already created the PR, please replace this message with one of +* [External contribution template](https://raw.githubusercontent.com/triton-inference-server/server/main/.github/PULL_REQUEST_TEMPLATE/pull_request_template_external_contrib.md) +* [Internal contribution template](https://raw.githubusercontent.com/triton-inference-server/server/main/.github/PULL_REQUEST_TEMPLATE/pull_request_template_internal_contrib.md) + +and fill it out. + + diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..e4c246b --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,48 @@ +# Copyright 2023-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of NVIDIA CORPORATION nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +[tool.codespell] +# note: pre-commit passes explicit lists of files here, which this skip file list doesn't override - +# this is only to allow you to run codespell interactively +skip = "./.git,./.github" +# ignore short words, and typename parameters like OffsetT +ignore-regex = "\\b(.{1,4}|[A-Z]\\w*T)\\b" +# use the 'clear' dictionary for unambiguous spelling mistakes +builtin = "clear" +# disable warnings about binary files and wrong encoding +quiet-level = 3 + +[tool.isort] +profile = "black" +use_parentheses = true +multi_line_output = 3 +include_trailing_comma = true +force_grid_wrap = 0 +ensure_newline_before_comments = true +line_length = 88 +balanced_wrapping = true +indent = " " +skip = ["build"] diff --git a/tools/__pycache__/add_copyright.cpython-312-pytest-9.1.1.pyc b/tools/__pycache__/add_copyright.cpython-312-pytest-9.1.1.pyc new file mode 100644 index 0000000..5ce0490 Binary files /dev/null and b/tools/__pycache__/add_copyright.cpython-312-pytest-9.1.1.pyc differ diff --git a/tools/add_copyright.py b/tools/add_copyright.py new file mode 100755 index 0000000..ff17802 --- /dev/null +++ b/tools/add_copyright.py @@ -0,0 +1,419 @@ +#!/usr/bin/env python3 +# Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of NVIDIA CORPORATION nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +import argparse +import os +import re +import subprocess +import sys +from datetime import datetime +from typing import Callable, Dict, Optional, Sequence + +current_year = str(datetime.now().year) + +COPYRIGHT_YEAR_PAT = re.compile( + r"Copyright( \(c\))? (\d{4})?-?(\d{4}), NVIDIA CORPORATION" +) + +LICENSE_TEXT = "" + + +def get_repo_root() -> str: + try: + result = subprocess.run( + ["git", "rev-parse", "--show-toplevel"], + capture_output=True, + text=True, + check=True, + ) + return result.stdout.strip() + except (FileNotFoundError, subprocess.CalledProcessError): + return os.getcwd() + + +def get_license_path() -> str: + return os.path.join(get_repo_root(), "LICENSE") + + +def has_copyright(content: str) -> bool: + return COPYRIGHT_YEAR_PAT.search(content) + + +def update_copyright_year( + path: str, content: Optional[str] = None, disallow_range: bool = False +) -> str: + """ + Updates the copyright year in the provided file. + If the copyright is not present in the file, this function has no effect. + """ + if content is None: + with open(path, "r") as f: + content = f.read() + + match = COPYRIGHT_YEAR_PAT.search(content) + min_year = match.groups()[1] or match.groups()[2] + + new_copyright = f"Copyright{match.groups()[0] or ''} " + if min_year < current_year and not disallow_range: + new_copyright += f"{min_year}-{current_year}" + else: + new_copyright += f"{current_year}" + new_copyright += ", NVIDIA CORPORATION" + + updated_content = COPYRIGHT_YEAR_PAT.sub(new_copyright, content) + + if content != updated_content: + with open(path, "w") as f: + f.write(updated_content) + + +def get_license(license_path: Optional[str] = None) -> str: + """ + Returns the contents of the LICENSE file. + + Note: The LICENSE file itself is never modified by this hook; its + copyright year is maintained manually by each repository. If the file + carries an NVIDIA copyright whose highest year is not the current year, + the hook fails so the year gets updated in a deliberate commit. + """ + license_path = license_path or get_license_path() + + with open(license_path, "r") as license_file: + text = license_file.read() + + match = COPYRIGHT_YEAR_PAT.search(text) + if match and match.groups()[2] != current_year: + raise SystemExit( + f"ERROR: the LICENSE copyright year is stale " + f"({match.group(0).strip()!r}); its highest year must be " + f"{current_year}. This hook never modifies LICENSE files - " + "update the year in a deliberate commit." + ) + return text + + +def load_license_text() -> None: + global LICENSE_TEXT + LICENSE_TEXT = get_license() + + +# +# Header manipulation helpers +# + + +def prefix_lines(content: str, prefix: str) -> str: + # NOTE: This could have been done via `textwrap.indent`, but we're not actually indenting, + # so it seems semantically wrong to do that. + return prefix + f"\n{prefix}".join(content.splitlines()) + + +def insert_after(regex: str) -> Callable[[str, str], str]: + """ + Builds a callback that will insert a provided header after + the specified regular expression. If the expression is not + found in the file contents, the header will be inserted at the + beginning of the file. + + Args: + regex: The regular expression to match. + + Returns: + A callable that can be used as the `add_header` argument to `update_or_add_header`. + """ + + def add_header(header: str, content: str) -> str: + match = re.match(regex, content) + + if match is None: + return header + "\n" + content + + insertion_point = match.span()[-1] + + return content[:insertion_point] + f"{header}\n" + content[insertion_point:] + + return add_header + + +def update_or_add_header( + path: str, header: str, add_header: Optional[Callable[[str, str], str]] = None +): + """ + Updates in place or adds a new copyright header to the specified file. + + Args: + path: The path of the file. + header: The contents of the copyright header. + add_header: A callback that receives the copyright header and file contents and + controls how the contents of the file are updated. By default, the copyright + header is prepended to the file. + """ + with open(path, "r") as f: + content = f.read() + + if has_copyright(content): + update_copyright_year(path, content) + return + + add_header = add_header or (lambda header, content: header + "\n" + content) + + content = add_header(header, content) + + # As a sanity check, make sure we didn't accidentally add the copyright header + # twice, or add a new header when one was already present. + if len(COPYRIGHT_YEAR_PAT.findall(content)) != 1: + print( + f"WARNING: Something went wrong while processing: {path}!\n" + "Please check if the copyright header was included twice or wasn't added at all. " + ) + + with open(path, "w") as f: + f.write(content) + + +# Each file type requires slightly different handling when inserting the copyright +# header. For example, for C++ files, the header must be prefixed with `//` and for +# shell scripts, it must be prefixed with `#` and must be inserted *after* the shebang. +# +# This mapping stores callables that return whether a handler wants to process a specified +# file based on the path along with callables that will accept the file path and update +# it with the copyright header. +FILE_TYPE_HANDLERS: Dict[Callable[[str], bool], Callable[[str], None]] = {} + + +# +# Path matching callables +# These allow registered functions to more easily specify what kinds of +# paths they should be applied to. +# +def has_ext(exts: Sequence[str]): + def has_ext_impl(path: str): + _, ext = os.path.splitext(path) + return ext in exts + + return has_ext_impl + + +def basename_is(expected_path: str): + return lambda path: os.path.basename(path) == expected_path + + +def path_contains(expected: str): + return lambda path: expected in path + + +def any_of(*funcs: Sequence[Callable[[str], bool]]): + return lambda path: any(func(path) for func in funcs) + + +# +# File handlers for different types of files. +# Many types of files require very similar handling - those are combined where possible. +# + + +def register(match: Callable[[str], bool]): + def register_impl(func): + FILE_TYPE_HANDLERS[match] = func + return func + + return register_impl + + +@register( + any_of( + has_ext([".py", ".pyi", ".sh", ".bash", ".yaml", ".pbtxt"]), + basename_is("CMakeLists.txt"), + path_contains("Dockerfile"), + ) +) +def py_or_shell_like(path): + update_or_add_header( + path, + prefix_lines(LICENSE_TEXT, "# "), + # Insert the header *after* the shebang. + # NOTE: This could break if there is a shebang-like pattern elsewhere in the file. + # In that case, this could be edited to check only the first line of the file (after removing whitespace). + insert_after(r"#!(.*)\n"), + ) + + +@register(has_ext([".cc", ".h"])) +def cpp(path): + update_or_add_header(path, prefix_lines(LICENSE_TEXT, "// ")) + + +@register(has_ext([".tpl"])) +def tpl(path): + update_or_add_header(path, "{{/*\n" + prefix_lines(LICENSE_TEXT, "# ") + "\n*/}}") + + +@register(has_ext([".html", ".md"])) +def html_md(path): + update_or_add_header(path, "<!--\n" + prefix_lines(LICENSE_TEXT, "# ") + "\n-->") + + +@register(has_ext([".rst"])) +def rst(path): + update_or_add_header(path, prefix_lines(LICENSE_TEXT, ".. ")) + + +def add_copyrights(paths): + load_license_text() + + for path in paths: + # Special case: LICENSE file only needs year update + if os.path.basename(path) == "LICENSE": + update_copyright_year(path) + continue + + for match, handler in FILE_TYPE_HANDLERS.items(): + if match(path): + handler(path) + break + else: + print( + f"WARNING: No handler registered for file: {path}. Please add a new handler to {__file__}!" + ) + + # Don't automatically 'git add' changes for now, make it more clear which + # files were changed and have ability to see 'git diff' on them. + # Note that this means the hook will modify files and then cancel the commit, which you will then + # have to manually make again. + # subprocess.run(["git", "add"] + paths) + + print(f"Processed copyright headers for {len(paths)} file(s).") + + +def main() -> int: + parser = argparse.ArgumentParser( + description="Adds copyright headers to source files" + ) + parser.add_argument("files", nargs="*") + + args, _ = parser.parse_known_args() + + license_path = get_license_path() + if not os.path.isfile(license_path): + print(f"ERROR: LICENSE file not found at {license_path}", file=sys.stderr) + return 1 + + add_copyrights(args.files) + return 0 + + +if __name__ == "__main__": + # sys.exit is important here to avoid the test-related imports below during normal execution. + sys.exit(main()) + + +# +# Integration Tests +# +import tempfile # noqa: E402 (test-section import, deliberately mid-file) + +import pytest # noqa: E402 (test-section import, deliberately mid-file) + + +# Processes provided text through the copyright hook by writing it to a temporary file. +def process_text(content, extension): + with tempfile.NamedTemporaryFile("w+", suffix=extension) as f: + f.write(content) + f.flush() + + add_copyrights([f.name]) + + f.seek(0) + return f.read() + + +# We use this slightly weird hack to make sure the copyright hook does not do a text replacement +# of the parameters in the test, since they look exactly like copyright headers. +def make_copyright_text(text): + return f"Copyright {text}" + + +@pytest.mark.parametrize( + "content, expected", + [ + # Convert to range if the year that's already present is older than the current year. + ( + make_copyright_text("(c) 2018, NVIDIA CORPORATION"), + make_copyright_text(f"(c) 2018-{current_year}, NVIDIA CORPORATION"), + ), + ( + make_copyright_text("2018, NVIDIA CORPORATION"), + make_copyright_text(f"2018-{current_year}, NVIDIA CORPORATION"), + ), + # No effect if the year is current: + ( + make_copyright_text(f"(c) {current_year}, NVIDIA CORPORATION"), + make_copyright_text(f"(c) {current_year}, NVIDIA CORPORATION"), + ), + ( + make_copyright_text(f"{current_year}, NVIDIA CORPORATION"), + make_copyright_text(f"{current_year}, NVIDIA CORPORATION"), + ), + # If there is already a range, update the upper bound of the range: + ( + make_copyright_text("(c) 2018-2023, NVIDIA CORPORATION"), + make_copyright_text(f"(c) 2018-{current_year}, NVIDIA CORPORATION"), + ), + ], +) +def test_copyright_update(content, expected): + # We don't really care about the extension here - just needs to be something the hook will recognize. + assert process_text(content, ".py") == expected + + +@pytest.mark.parametrize( + "content, extension, expected", + [ + ("", ".cc", f"// {make_copyright_text(f'(c) {current_year}')}"), + ("", ".h", f"// {make_copyright_text(f'(c) {current_year}')}"), + ("", ".py", f"# {make_copyright_text(f'(c) {current_year}')}"), + ("", ".sh", f"# {make_copyright_text(f'(c) {current_year}')}"), + # Make sure copyright comes after shebangs + ( + "#!/bin/python\n", + ".py", + f"#!/bin/python\n# {make_copyright_text(f'(c) {current_year}')}", + ), + ( + "#!/bin/bash\n", + ".sh", + f"#!/bin/bash\n# {make_copyright_text(f'(c) {current_year}')}", + ), + ], +) +def test_adding_new_copyrights(content, extension, expected): + assert process_text(content, extension).startswith(expected) + + +def test_license_has_current_year(): + load_license_text() + # LICENSE file should have the current year (either as single year or end of range) + assert f"{current_year}, NVIDIA CORPORATION" in LICENSE_TEXT