# datahub-bq-connector-session-patch — build and distribution
#
# Artifacts land in dist/, which is git-ignored. The wheel is the deliverable: it is what
# gets installed into a DataHub executor's own interpreter via the sidecar recipe's
# `extra_pip_requirements`, alongside the executor's pinned acryl-datahub. It registers
# the `datahub_bq_connector_session_patch` source type; the production recipe keeps `type: bigquery`
# and is unaffected.
#
# Shipping a new build:
#
#     make release              # bump patch, rebuild, verify  -> dist/
#     make release BUMP=minor   # same, bumping the minor component
#     make publish              # only if a package index is configured
#
# `make build` alone rebuilds the current version without touching pyproject.toml.
#
# Every target that reads dist/ goes through `verify-dist` first, which refuses to
# proceed if dist/ holds anything other than the current version's sdist and wheel.
# Nothing is ever uploaded by a bare glob, so a stale artifact cannot ride along.

UV       ?= uv
DIST     := dist
PKG      := datahub-bq-connector-session-patch
DISTNAME := $(subst -,_,$(PKG))
BUMP     ?= patch
VENV     := .venv
PYTHON_VERSION ?= 3.11
# The acryl-datahub range this package claims to support. ACRYL pins one version for
# `test`; MATRIX_VERSIONS is what `test-matrix` walks. Both ends must be listed: the
# SupportStatus enum was renamed between 1.7.0 and 1.7.0.10, so a range spec alone
# always resolves to the newest and never exercises the oldest.
ACRYL    ?= acryl-datahub[bigquery]>=1.6,<1.8
MATRIX_VERSIONS ?= 1.6.0.17 1.7.0 1.7.0.10
VERSION   = $(shell $(UV) version --short 2>/dev/null)

# The exact artifacts for the version currently in pyproject.toml — never `dist/*`.
SDIST     = $(DIST)/$(DISTNAME)-$(VERSION).tar.gz
WHEEL     = $(DIST)/$(DISTNAME)-$(VERSION)-*.whl

.DEFAULT_GOAL := help
.PHONY: help version clean build dist verify-dist check test test-matrix gate venv published bump release publish require-uv

# `release` chains bump -> build -> check, each of which reads or rewrites the version
# and the contents of dist/. Under -j those interleave and verify-dist can stat a
# half-deleted dist/ or resolve $(VERSION) mid-bump. Ordering here is correctness.
.NOTPARALLEL:

help: ## Show this help
	@echo "$(PKG) — targets:"
	@grep -E '^[a-zA-Z_-]+:.*?## ' $(MAKEFILE_LIST) \
	  | awk 'BEGIN {FS = ":.*?## "}; {printf "  \033[36m%-12s\033[0m %s\n", $$1, $$2}'
	@echo
	@echo "  variables: BUMP=patch|minor|major  PYTHON_VERSION=$(PYTHON_VERSION)  UV=$(UV)"

require-uv: ## Fail with a readable message if the version cannot be read
	@test -n "$(VERSION)" || { \
	  echo "ERROR: could not read the project version."; \
	  echo "  '$(UV) version --short' produced nothing — is uv installed, and is this"; \
	  echo "  running from the package directory? Diagnose with: $(UV) version"; \
	  exit 1; }

version: require-uv ## Print the current version
	@echo $(VERSION)

clean: ## Remove build artifacts
	rm -rf $(DIST) build src/*.egg-info *.egg-info

build: clean ## Build sdist + wheel into dist/ (always from clean)
	$(UV) build --out-dir $(DIST)
	@echo
	@ls -l $(DIST)

dist: build ## Alias for build

verify-dist: require-uv ## Assert dist/ holds exactly the current version, nothing stale
	@test -d $(DIST) \
	  || { echo "no $(DIST)/ — run 'make build' first"; exit 1; }
	@test -f $(SDIST) \
	  || { echo "ERROR: no sdist for $(PKG) $(VERSION) in $(DIST)/ — run 'make build'"; exit 1; }
	@ls $(WHEEL) >/dev/null 2>&1 \
	  || { echo "ERROR: no wheel for $(PKG) $(VERSION) in $(DIST)/ — run 'make build'"; exit 1; }
	@stale=$$(find $(DIST) -type f ! -name '.*' \
	    ! -name '$(DISTNAME)-$(VERSION).tar.gz' \
	    ! -name '$(DISTNAME)-$(VERSION)-*.whl'); \
	  if [ -n "$$stale" ]; then \
	    echo "ERROR: $(DIST)/ holds files that are not $(PKG) $(VERSION):"; \
	    echo "$$stale" | sed 's/^/  /'; \
	    echo; \
	    echo "Refusing to continue — these would be shipped alongside the new build."; \
	    echo "Run 'make build' to rebuild from clean."; \
	    exit 1; \
	  fi

check: verify-dist ## Verify the built artifacts are well-formed and complete
	uvx twine check $(SDIST) $(WHEEL)
	@echo
	@for w in $(WHEEL); do \
	  echo "--- $$w ---"; \
	  unzip -l "$$w" | sed 's/^/  /'; \
	  echo "  entry points:"; \
	  unzip -p "$$w" '*/entry_points.txt' | sed 's/^/    /'; \
	done
	@unzip -p $(WHEEL) '*/entry_points.txt' \
	  | grep -q 'datahub.ingestion.source.plugins' \
	  || { echo "ERROR: wheel is missing the source entry point"; exit 1; }
	@echo
	@echo "OK — $(PKG) $(VERSION) registers its source entry point"

# The range here must match MIN/MAX_ACRYL_DATAHUB in src/datahub_bq_connector_session_patch/patch.py and
# the `test` extra in pyproject.toml — this patch reaches into private internals, so
# "tested against" is the only guarantee it has.
test: ## Run the test suite against a real acryl-datahub (no venv setup needed)
	PYTHONPATH=src $(UV) run --no-project --python 3.11 \
	  --with '$(ACRYL)' --with pytest --with pyyaml \
	  pytest tests/ -q

# Run the suite against EVERY acryl-datahub version in the supported range, not just
# whichever one a range spec resolves to.
#
# 0.2.0 shipped broken to a 1.7.0 executor because of exactly this gap: `make test` used
# '>=1.6,<1.8', uv resolved 1.7.0.10, and SupportStatus.BETA exists only there. On 1.7.0
# the module raised AttributeError at import and the registry reported nothing more
# useful than "disabled due to an error in initialization".
test-matrix: ## Run the suite against each supported acryl-datahub (MATRIX_VERSIONS=...)
	@failed=""; \
	for v in $(MATRIX_VERSIONS); do \
	  echo "=== acryl-datahub $$v ==="; \
	  PYTHONPATH=src $(UV) run --no-project --quiet --python 3.11 \
	    --with "acryl-datahub[bigquery]==$$v" --with pytest --with pyyaml \
	    pytest tests/ -q 2>&1 | tail -3; \
	  if [ $${pipestatus[1]:-$$?} -ne 0 ]; then failed="$$failed $$v"; fi; \
	done; \
	if [ -n "$$failed" ]; then echo; echo "FAILED on:$$failed"; exit 1; fi; \
	echo; echo "OK — all of $(MATRIX_VERSIONS)"

# The acceptance gate from the design doc: the same scenario must FAIL on stock
# acryl-datahub and PASS patched. A green `gate` is what proves the patch still bites
# after an acryl-datahub bump — `test` alone would stay green if upstream fixed the
# defect out from under us, and then this package would be dead weight.
gate: ## Prove the defect still exists on stock and that the patch still fixes it
	@echo "--- stock acryl-datahub (must FAIL) ---"
	@PYTHONPATH=src $(UV) run --no-project --python 3.11 \
	  --with 'acryl-datahub[bigquery]>=1.6,<1.8' --with pytest \
	  python tests/test_session_patch.py; \
	  status=$$?; \
	  if [ $$status -eq 0 ]; then \
	    echo; echo "ERROR: stock acryl-datahub PASSED — upstream may have fixed this."; \
	    echo "Re-read the design doc before shipping; this package may be obsolete."; \
	    exit 1; \
	  fi; \
	  echo "  (exit $$status — correct: the defect is present)"
	@echo
	@echo "--- patched (must PASS) ---"
	@PYTHONPATH=src $(UV) run --no-project --python 3.11 \
	  --with 'acryl-datahub[bigquery]>=1.6,<1.8' --with pytest \
	  python tests/test_session_patch.py --patched \
	  || { echo; echo "ERROR: the patch no longer recovers the read."; exit 1; }
	@echo
	@echo "OK — defect reproduced on stock, fixed patched"

# A project-local .venv, for interactive work and for the tools/ scripts — which need
# real BigQuery credentials and so cannot run under `uv run --no-project`. Henrik's zsh
# auto_venv chpwd hook activates it on cd into this directory.
#
# `test` and `gate` deliberately do NOT use it: they build a throwaway environment per
# run, so a stale or hand-modified .venv can never make them pass.
venv: ## Create .venv (python 3.11) with the package installed editable + test extras
	$(UV) venv $(VENV) --python $(PYTHON_VERSION)
	VIRTUAL_ENV=$(VENV) $(UV) pip install -e '.[test]'
	@echo
	@echo "Created $(VENV) — cd out and back in to activate, or: source $(VENV)/bin/activate"

# Is a version already out there? PyPI releases are IMMUTABLE, so this is the question
# `verify-dist` cannot answer: it checks that dist/ holds the current version and nothing
# foreign, but not whether that version was already uploaded — or whether what was
# uploaded still matches what is in dist/.
#
#   make published                 # the version in pyproject.toml
#   make published VERSION=0.1.0   # any other version
#
# Exit codes make it usable as a guard: 0 published (hashes match where comparable),
# 1 not published, 2 could not determine, 3 published but the local build DIFFERS.
# 2 is deliberately distinct from 1 — an unreachable PyPI is not clearance to publish.
#
#   make published || make publish          # publish only if it is not already out
published: require-uv ## Is VERSION on PyPI, and does it match dist/? (VERSION=x.y.z)
	@python3 tools/pypi_status.py \
	  --package $(PKG) --version $(VERSION) --dist $(DIST)

bump: require-uv ## Bump the version in pyproject.toml (BUMP=patch|minor|major)
	@echo "$(PKG): $(VERSION) -> bumping $(BUMP)"
	$(UV) version --frozen --bump $(BUMP)

release: test-matrix gate bump build check ## Test, bump the version, rebuild, and verify
	@echo
	@echo "$(PKG) $(VERSION) built and verified. Artifacts:"
	@ls -1 $(DIST)
	@echo
	@echo "Commit the pyproject.toml version bump, then 'make publish' if you have an index."

publish: check ## Upload the current version to a package index (UV_PUBLISH_URL / _TOKEN)
	@echo
	@echo "Will upload exactly these files:"
	@ls -1 $(SDIST) $(WHEEL) | sed 's/^/  /'
	@printf "Publish $(PKG) $(VERSION) to %s? [y/N] " "$${UV_PUBLISH_URL:-the default index}"; \
	  read ans; [ "$$ans" = "y" ] || { echo "aborted"; exit 1; }
	$(UV) publish $(SDIST) $(WHEEL)
