Skip to content
Merged
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
8 changes: 4 additions & 4 deletions .github/workflows/release-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,14 +107,14 @@ jobs:
uses: actions/cache@v4
with:
path: ~/.cache/apache-rat
key: apache-rat-0.16.1
key: apache-rat-0.18

- name: Download Apache RAT if not cached
if: steps.cache-rat.outputs.cache-hit != 'true'
run: |
mkdir -p ~/.cache/apache-rat
curl -fL -o ~/.cache/apache-rat/apache-rat-0.16.1.jar \
https://repo1.maven.org/maven2/org/apache/rat/apache-rat/0.16.1/apache-rat-0.16.1.jar
curl -fL -o ~/.cache/apache-rat/apache-rat-0.18.jar \
https://repo1.maven.org/maven2/org/apache/rat/apache-rat/0.18/apache-rat-0.18.jar

- name: Extract version
id: version
Expand All @@ -137,7 +137,7 @@ jobs:
- name: Run Apache RAT on source and sdist tarballs
run: |
python scripts/verify_apache_artifacts.py licenses \
--rat-jar ~/.cache/apache-rat/apache-rat-0.16.1.jar \
--rat-jar ~/.cache/apache-rat/apache-rat-0.18.jar \
--artifacts-dir dist

- name: Upload release artifacts
Expand Down
85 changes: 43 additions & 42 deletions .rat-excludes
Original file line number Diff line number Diff line change
@@ -1,45 +1,46 @@
# Apache RAT exclude patterns.
#
# IMPORTANT: RAT's -E flag matches each pattern as a regex, but only
# reliably matches against the file basename. Path-style patterns like
# 'examples/foo/bar.py' don't work, especially through symlinked dirs
# (burr/examples is a symlink to ../examples in the source tree).
# Use basename patterns. Be aware that broad basenames like 'utils.py'
# will skip license checks on every utils.py in the repo.
# IMPORTANT: these entries use RAT 0.17+ exclude-expression syntax, read via
# --input-exclude-file. Each line is a glob matched against the file path, so
# 'foo/bar.py' style patterns work, but the '**/' prefix is required to match a
# name at any depth (RAT 0.16's -E flag matched the bare basename as a regex;
# that behaviour was removed in 0.17). Use '**/<name>' for a name anywhere or
# '**/*.<ext>' for an extension. Be aware that broad basenames like
# '**/utils.py' will skip license checks on every utils.py in the repo.

# Python marker file
.*py\.typed
**/py.typed

# JSON Lines data files
.*\.jsonl
**/*.jsonl

# Git and version control config
\.gitignore
\.gitmodules
\.rat-excludes
**/.gitignore
**/.gitmodules
**/.rat-excludes

# Jupyter notebooks (JSON format, cannot practically add headers)
.*\.ipynb
**/*.ipynb

# Data files (CSV - not source code)
.*\.csv
**/*.csv

# Text files (documentation/data files, not source code)
.*\.txt
**/*.txt

# Build and tool config files
.*\.bat
robots\.txt
**/*.bat
**/robots.txt

# JSON config files (cannot contain comments)
.*\.json
**/*.json

# YAML config files in .github/ (templates with frontmatter, headers impractical)
labeler\.yml
config\.yml
bug_report\.md
feature_request\.md
PULL_REQUEST_TEMPLATE\.md
**/labeler.yml
**/config.yml
**/bug_report.md
**/feature_request.md
**/PULL_REQUEST_TEMPLATE.md

# Third-party MIT-licensed files (attributed in LICENSE).
# Most names are unique within the repo so basename matching is safe.
Expand All @@ -50,29 +51,29 @@ PULL_REQUEST_TEMPLATE\.md
# (our own ASF code with header)
# A future regression in any of those collision targets would silently pass
# RAT. Tracked as a follow-up to rename or restructure.
prompts\.py
utils\.py
animated-beam\.tsx
animated-shiny-text\.tsx
blur-fade\.tsx
border-beam\.tsx
button\.tsx
dot-pattern\.tsx
icon-cloud\.tsx
magic-card\.tsx
marquee\.tsx
number-ticker\.tsx
safari\.tsx
shimmer-button\.tsx
**/prompts.py
**/utils.py
**/animated-beam.tsx
**/animated-shiny-text.tsx
**/blur-fade.tsx
**/border-beam.tsx
**/button.tsx
**/dot-pattern.tsx
**/icon-cloud.tsx
**/magic-card.tsx
**/marquee.tsx
**/number-ticker.tsx
**/safari.tsx
**/shimmer-button.tsx

# Tutorial markdown for the AWS terraform deployment example
tutorial\.md
**/tutorial.md

# SVG files (third-party logos and graphics, headers impractical)
.*\.svg
**/*.svg

# Image files (binary, cannot contain headers)
.*\.png
.*\.gif
.*\.ico
.*\.jpg
**/*.png
**/*.gif
**/*.ico
**/*.jpg
58 changes: 48 additions & 10 deletions scripts/apache_release.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import shutil
import subprocess
import sys
import tempfile
from typing import NoReturn, Optional

# --- Configuration ---
Expand Down Expand Up @@ -428,6 +429,9 @@ def _build_sdist_from_git(version: str, output_dir: str = "dist") -> str:

env = os.environ.copy()
env["FLIT_USE_VCS"] = "0"
source_epoch = _source_date_epoch(version, output_dir)
if source_epoch is not None:
env["SOURCE_DATE_EPOCH"] = str(source_epoch)
_run_command(
["flit", "build", "--format", "sdist"],
description="Running flit build --format sdist...",
Expand Down Expand Up @@ -457,6 +461,14 @@ def _build_sdist_from_git(version: str, output_dir: str = "dist") -> str:
return apache_sdist


def _source_date_epoch(version: str, output_dir: str = "dist") -> Optional[int]:
"""Use the source archive timestamp when available so local rebuilds are comparable."""
source_archive = os.path.join(output_dir, f"apache-burr-{version}-incubating-src.tar.gz")
if os.path.exists(source_archive):
return int(os.path.getmtime(source_archive))
return None


# ============================================================================
# Step 3: Build Wheel (SIMPLIFIED!)
# ============================================================================
Expand Down Expand Up @@ -509,14 +521,15 @@ def _build_ui_artifacts() -> None:
_fail(f"UI build directory is empty: {ui_build_dir}")


def _prepare_wheel_contents() -> tuple[bool, bool, Optional[str]]:
"""Handle burr/examples symlink: replace with real files for wheel."""
def _prepare_wheel_contents() -> tuple[bool, bool, Optional[str], list[tuple[str, str]]]:
"""Prepare wheel contents and temporarily remove files excluded from the sdist."""
burr_examples_dir = "burr/examples"
source_examples_dir = "examples"
removed_files: list[tuple[str, str]] = []

if not os.path.exists(source_examples_dir):
print(f" ⚠️ {source_examples_dir} not found")
return (False, False, None)
return (False, False, None, removed_files)

# Check if burr/examples is a symlink (should be in dev repo)
was_symlink = False
Expand Down Expand Up @@ -553,11 +566,25 @@ def _prepare_wheel_contents() -> tuple[bool, bool, Optional[str]]:
shutil.copytree(src_path, dest_path, dirs_exist_ok=True)
print(f" ✓ Copied {example_dir}")

return (True, was_symlink, symlink_target)


def _cleanup_wheel_contents(was_symlink: bool, symlink_target: Optional[str]) -> None:
"""Restore burr/examples symlink after wheel build."""
# Keep wheel contents aligned with the sdist so rebuild verification compares like-for-like artifacts.
excluded_wheel_files = [
"burr/tracking/server/s3/deployment/terraform/.gitignore",
]
for path in excluded_wheel_files:
if os.path.exists(path):
backup_dir = tempfile.mkdtemp(prefix="apache-release-wheel-")
backup_path = os.path.join(backup_dir, os.path.basename(path))
os.replace(path, backup_path)
removed_files.append((path, backup_path))
print(f" ✓ Temporarily excluded {path}")

return (True, was_symlink, symlink_target, removed_files)


def _cleanup_wheel_contents(
was_symlink: bool, symlink_target: Optional[str], removed_files: list[tuple[str, str]]
) -> None:
"""Restore temporary wheel-build changes after the wheel build finishes."""
burr_examples_dir = "burr/examples"

if os.path.exists(burr_examples_dir):
Expand All @@ -568,6 +595,14 @@ def _cleanup_wheel_contents(was_symlink: bool, symlink_target: Optional[str]) ->
os.symlink(symlink_target, burr_examples_dir)
print(" ✓ Symlink restored")

for original_path, backup_path in removed_files:
if os.path.exists(backup_path):
os.replace(backup_path, original_path)
print(f" Restored {original_path}")
backup_dir = os.path.dirname(backup_path)
if os.path.isdir(backup_dir):
shutil.rmtree(backup_dir)


def _build_wheel_from_current_dir(version: str, output_dir: str = "dist") -> str:
"""Build wheel from current directory (matches what voters do).
Expand All @@ -581,13 +616,16 @@ def _build_wheel_from_current_dir(version: str, output_dir: str = "dist") -> str
_build_ui_artifacts()

_print_step(2, 3, "Preparing wheel contents")
copied, was_symlink, symlink_target = _prepare_wheel_contents()
copied, was_symlink, symlink_target, removed_files = _prepare_wheel_contents()

_print_step(3, 3, "Building wheel with flit")

try:
env = os.environ.copy()
env["FLIT_USE_VCS"] = "0"
source_epoch = _source_date_epoch(version, output_dir)
if source_epoch is not None:
env["SOURCE_DATE_EPOCH"] = str(source_epoch)

_run_command(
["flit", "build", "--format", "wheel"],
Expand All @@ -612,7 +650,7 @@ def _build_wheel_from_current_dir(version: str, output_dir: str = "dist") -> str
finally:
# Always restore symlinks
if copied:
_cleanup_wheel_contents(was_symlink, symlink_target)
_cleanup_wheel_contents(was_symlink, symlink_target, removed_files)


def _verify_wheel(wheel_path: str) -> bool:
Expand Down
Loading
Loading