Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pipeline/scripts/evaluate-duplicate-pr.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
Any missing prerequisite (access token, source commit) or API error falls back
to ``skipDuplicate=false`` so full validation proceeds (safe default).

Consumed by the ``EvaluateDuplicate`` stage in
Consumed by the ``SetDuplicateState`` step of the ``EvaluateRequirements`` stage in
``.pipeline/templates/validation-stages.yml``.
"""
import json
Expand Down
150 changes: 150 additions & 0 deletions .pipeline/scripts/evaluate-macos-relevance.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
#!/usr/bin/env python3
"""macOS validation relevance guard.

Determines whether a pull request's changes can affect the macOS build/test jobs.
Only a subset of the workspace has macOS-specific behavior (Apple Security.framework
TLS/crypto and GSSAPI, plus dependency manifests and pipeline definitions). When a PR
touches none of those paths, the macOS jobs add wall-clock time without adding coverage,
so this guard emits ``macRelevant=false`` to let them short-circuit.

Any missing prerequisite (target branch, git failure) or unexpected error falls back to
``macRelevant=true`` so the macOS jobs run (safe default: never skip on uncertainty).

Consumed by the ``SetMacRelevance`` step of the ``EvaluateRequirements`` stage in
``.pipeline/templates/validation-stages.yml``. The macOS jobs also always run on non-PR
builds regardless of this variable (that gating lives in the pipeline conditions).
"""
import fnmatch
import os
import subprocess
import sys

# Paths whose changes require a macOS validation run (moderate allowlist).
# fnmatch-style globs matched against repo-relative POSIX paths. Note fnmatch's
# ``*`` also spans ``/``, so a trailing ``/*`` matches arbitrarily deep subtrees.
MAC_RELEVANT_GLOBS = [
# macOS-specific security libraries: Security.framework/CommonCrypto crypto and TLS.
"mssql-tds/src/security/*",
"mssql-tds/src/connection/transport.rs",
"mssql-tds/src/connection/transport/*",
# macOS GSSAPI/Kerberos integration test.
"mssql-tds/tests/test_kerberos_gssapi.rs",
# Dependency graph / toolchain: a resolution change can break the macOS build.
"*Cargo.toml",
"Cargo.lock",
"rust-toolchain*",
# Pipeline definitions themselves (templates, scripts) — high-impact, run macOS.
".pipeline/*",
]


def set_mac_relevant(value):
print(f"##vso[task.setvariable variable=macRelevant;isOutput=true]{value}")


def matches_allowlist(path):
return any(fnmatch.fnmatch(path, pattern) for pattern in MAC_RELEVANT_GLOBS)


def main():
build_reason = os.environ.get("BUILD_REASON", "")
if build_reason != "PullRequest":
# Non-PR builds always run macOS in full; the guard is a no-op here.
print(
f"Build.Reason='{build_reason or '<empty>'}' is not PullRequest; "
"macOS jobs run in full."
)
set_mac_relevant("true")
return

target_branch = os.environ.get("SYSTEM_PULLREQUEST_TARGETBRANCH", "")
if not target_branch:
print(
"##vso[task.logissue type=warning]System.PullRequest.TargetBranch is unavailable; "
"running macOS validation."
)
set_mac_relevant("true")
return

# TargetBranch arrives as e.g. 'refs/heads/main' or 'main'; normalize to a ref
# the local clone can resolve. ADO checks out the PR merge ref with the target
# branch tip available as origin/<branch>.
short_branch = target_branch
for prefix in ("refs/heads/", "refs/remotes/origin/"):
if short_branch.startswith(prefix):
short_branch = short_branch[len(prefix):]
break

candidate_refs = [
f"origin/{short_branch}",
short_branch,
target_branch,
]

diff_output = None
used_ref = None
for ref in candidate_refs:
try:
# Three-dot diff against the merge base isolates the PR's own changes
# from unrelated commits already on the target branch.
diff_output = subprocess.check_output(
["git", "diff", "--name-only", f"{ref}...HEAD"],
stderr=subprocess.STDOUT,
text=True,
)
used_ref = ref
break
except subprocess.CalledProcessError:
continue
except OSError as exc:
print(
f"##vso[task.logissue type=warning]Unable to invoke git ({exc}); "
"running macOS validation."
)
set_mac_relevant("true")
return

if diff_output is None:
print(
"##vso[task.logissue type=warning]Could not compute PR diff against target "
f"branch '{target_branch}' (tried {candidate_refs}); running macOS validation."
)
set_mac_relevant("true")
return

changed_files = [line.strip() for line in diff_output.splitlines() if line.strip()]
print(f"Comparing against '{used_ref}'; {len(changed_files)} changed file(s).")

if not changed_files:
# An empty diff is unexpected for a PR; do not skip on that ambiguity.
print(
"##vso[task.logissue type=warning]PR diff is empty; running macOS validation."
)
set_mac_relevant("true")
return

matched = [path for path in changed_files if matches_allowlist(path)]
if matched:
preview = ", ".join(matched[:10])
suffix = "" if len(matched) <= 10 else f" (+{len(matched) - 10} more)"
print(f"macOS-relevant change(s) detected: {preview}{suffix}")
set_mac_relevant("true")
else:
print(
"No macOS-relevant paths changed; skipping macOS validation jobs for this PR."
)
set_mac_relevant("false")


if __name__ == "__main__":
# Safety net: the guard is best-effort, so any unexpected failure must fall back
# to running macOS validation (macRelevant=true) rather than skipping coverage.
try:
main()
except Exception as exc: # noqa: BLE001 - deliberate catch-all for safe fallback
print(
f"##vso[task.logissue type=warning]macOS relevance guard failed unexpectedly "
f"({type(exc).__name__}: {exc}); running macOS validation."
)
set_mac_relevant("true")
sys.exit(0)
41 changes: 30 additions & 11 deletions .pipeline/templates/validation-stages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,15 @@ parameters:
default: '2025-latest'

stages:
- stage: EvaluateDuplicate
displayName: Evaluate PR duplicate
# Requirements gate: a single PR-only stage that computes which downstream work is
# needed. Today it covers (1) duplicate-run detection and (2) macOS relevance; it is
# the home for future "smart" skip optimizations.
- stage: EvaluateRequirements
displayName: Evaluate PR requirements
condition: and(eq(variables['Build.Reason'], 'PullRequest'), eq('${{ parameters.RunFuzz }}', 'false'), eq('${{ parameters.RunLongHaul }}', 'false'))
jobs:
- job: Evaluate
displayName: Check prior successful PR validation
displayName: Evaluate PR validation requirements
pool:
name: RUST-1ES-POOL-WUS3
demands:
Expand All @@ -58,19 +61,26 @@ stages:
displayName: Check duplicate PR head commit
env:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
# Determine whether the PR touches macOS-relevant paths. Emits macRelevant;
# the macOS jobs gate on it (and always run on non-PR builds).
- bash: |
set -euo pipefail
python3 .pipeline/scripts/evaluate-macos-relevance.py
name: SetMacRelevance
displayName: Check macOS validation relevance

- stage: Build
displayName: Build Stage
dependsOn:
- EvaluateDuplicate
- EvaluateRequirements
condition: >-
and(
not(canceled()),
eq('${{ parameters.RunFuzz }}', 'false'),
eq('${{ parameters.RunLongHaul }}', 'false'),
or(
ne(variables['Build.Reason'], 'PullRequest'),
ne(dependencies.EvaluateDuplicate.outputs['Evaluate.SetDuplicateState.skipDuplicate'], 'true')
ne(dependencies.EvaluateRequirements.outputs['Evaluate.SetDuplicateState.skipDuplicate'], 'true')
)
)
jobs:
Expand Down Expand Up @@ -276,6 +286,9 @@ stages:
architecture: ARM64
- job: Build_MacOS
displayName: Build MacOS
# Skip on PRs whose diff touches no macOS-relevant paths (see
# EvaluateRequirements/SetMacRelevance). Always runs on non-PR builds.
condition: and(succeeded(), or(ne(variables['Build.Reason'], 'PullRequest'), eq(stageDependencies.EvaluateRequirements.Evaluate.outputs['SetMacRelevance.macRelevant'], 'true')))
pool:
name: Azure Pipelines
vmImage: macOS-latest
Expand Down Expand Up @@ -304,6 +317,9 @@ stages:

- job: Test_MacOS
displayName: Test MacOS
# Skip on PRs whose diff touches no macOS-relevant paths (see
# EvaluateRequirements/SetMacRelevance). Always runs on non-PR builds.
condition: and(succeeded(), or(ne(variables['Build.Reason'], 'PullRequest'), eq(stageDependencies.EvaluateRequirements.Evaluate.outputs['SetMacRelevance.macRelevant'], 'true')))
pool:
name: Azure Pipelines
vmImage: macOS-latest
Expand Down Expand Up @@ -333,8 +349,8 @@ stages:
- stage: Kerberos_Test_PR
displayName: Kerberos Test (PR)
dependsOn:
- EvaluateDuplicate
condition: and(not(canceled()), eq(variables['Build.Reason'], 'PullRequest'), eq('${{ parameters.RunFuzz }}', 'false'), eq('${{ parameters.RunLongHaul }}', 'false'), ne(dependencies.EvaluateDuplicate.outputs['Evaluate.SetDuplicateState.skipDuplicate'], 'true'))
- EvaluateRequirements
condition: and(not(canceled()), eq(variables['Build.Reason'], 'PullRequest'), eq('${{ parameters.RunFuzz }}', 'false'), eq('${{ parameters.RunLongHaul }}', 'false'), ne(dependencies.EvaluateRequirements.outputs['Evaluate.SetDuplicateState.skipDuplicate'], 'true'))
jobs:
- template: kerberos-test-template.yml
parameters:
Expand All @@ -350,7 +366,7 @@ stages:
dependsOn:
- Build
# Scope success to the Build stage explicitly. Bare succeeded() evaluates the
# whole dependency graph, so the PR-only EvaluateDuplicate stage (skipped on
# whole dependency graph, so the PR-only EvaluateRequirements stage (skipped on
# non-PR CI runs) would otherwise make this stage skip in CI.
condition: and(succeeded('Build'), ne(variables['Build.Reason'], 'PullRequest'), eq('${{ parameters.RunFuzz }}', 'false'), eq('${{ parameters.RunLongHaul }}', 'false'))
jobs:
Expand Down Expand Up @@ -464,8 +480,8 @@ stages:

- stage: Build_mssql_python
dependsOn:
- EvaluateDuplicate
condition: and(not(canceled()), eq(variables['Build.Reason'], 'PullRequest'), eq('${{ parameters.RunFuzz }}', 'false'), eq('${{ parameters.RunLongHaul }}', 'false'), ne(dependencies.EvaluateDuplicate.outputs['Evaluate.SetDuplicateState.skipDuplicate'], 'true'))
- EvaluateRequirements
condition: and(not(canceled()), eq(variables['Build.Reason'], 'PullRequest'), eq('${{ parameters.RunFuzz }}', 'false'), eq('${{ parameters.RunLongHaul }}', 'false'), ne(dependencies.EvaluateRequirements.outputs['Evaluate.SetDuplicateState.skipDuplicate'], 'true'))
displayName: Build mssql-python
jobs:
- job: Build_mssql_python_Linux
Expand All @@ -481,6 +497,9 @@ stages:

- job: Build_mssql_python_MacOS
displayName: Build mssql-python macOS (cross-repo)
# Skip on PRs whose diff touches no macOS-relevant paths (see
# EvaluateRequirements/SetMacRelevance). Always runs on non-PR builds.
condition: and(succeeded(), or(ne(variables['Build.Reason'], 'PullRequest'), eq(stageDependencies.EvaluateRequirements.Evaluate.outputs['SetMacRelevance.macRelevant'], 'true')))
pool:
name: Azure Pipelines
vmImage: macOS-latest
Expand Down Expand Up @@ -730,7 +749,7 @@ stages:
dependsOn:
- Build
# Scope success to the Build stage explicitly. Bare succeeded() evaluates the
# whole dependency graph, so the PR-only EvaluateDuplicate stage (skipped on
# whole dependency graph, so the PR-only EvaluateRequirements stage (skipped on
# non-PR CI runs) would otherwise make this stage skip in CI.
condition: and(succeeded('Build'), ne(variables['Build.Reason'], 'PullRequest'), eq('${{ parameters.RunFuzz }}', 'false'), eq('${{ parameters.RunLongHaul }}', 'false'))
jobs:
Expand Down
Loading