Skip to content
Open
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
6 changes: 5 additions & 1 deletion .github/workflows/release-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,11 @@ jobs:
BURR_VERSION: ${{ needs.build-artifacts.outputs.version }}
run: |
mkdir -p /tmp/sdist-extract /tmp/sdist-wheel
tar -xzf "dist/apache-burr-${BURR_VERSION}-incubating-sdist.tar.gz" \
SDIST_PATH="dist/apache-burr-${BURR_VERSION}-incubating-sdist.tar.gz"
export SOURCE_DATE_EPOCH=$(python -c \
"import sys, tarfile; print(int(tarfile.open(sys.argv[1]).next().mtime))" \
"$SDIST_PATH")
tar -xzf "$SDIST_PATH" \
-C /tmp/sdist-extract
# Find the single top-level directory the tarball extracted into
SDIST_ROOT=$(find /tmp/sdist-extract -maxdepth 1 -mindepth 1 -type d | head -1)
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/ui.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm install --ignore-scripts
- run: npm ci --ignore-scripts
- run: npm run build
# `npm test` starts vitest in watch mode, which never exits on CI.
- run: npx vitest run
- run: npm run lint:fix
- run: npm run format:fix
- run: npm run lint
- run: npm run format
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ release = [
tests = [
"pytest",
"pytest-asyncio",
"apache-burr[graphviz]",
"apache-burr[hamilton]",
"langchain_core",
"langchain_community",
Expand Down
97 changes: 73 additions & 24 deletions scripts/apache_release.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
VERSION_FILE = "pyproject.toml"
VERSION_PATTERN = r'version\s*=\s*"(\d+\.\d+\.\d+)"'
TEMPLATES_DIR = Path(__file__).resolve().parent / "templates"
PROJECT_ROOT = Path(__file__).resolve().parent.parent
DEFAULT_DOWNLOADS_URL = f"https://downloads.apache.org/incubator/{PROJECT_SHORT_NAME}/"
DEFAULT_DEV_SVN_ROOT = f"https://dist.apache.org/repos/dist/dev/incubator/{PROJECT_SHORT_NAME}"
DEFAULT_RELEASE_SVN_ROOT = (
Expand Down Expand Up @@ -692,25 +693,78 @@ def _remove_ui_build_artifacts() -> None:
print(" ✓ UI build artifacts removed")


def _build_sdist_from_git(version: str, output_dir: str = "dist") -> str:
def _release_build_environment(source_date_epoch: int) -> dict[str, str]:
"""Return the shared environment for reproducible Flit builds."""
env = os.environ.copy()
env["FLIT_USE_VCS"] = "0"
env["SOURCE_DATE_EPOCH"] = str(source_date_epoch)
return env


def _git_source_date_epoch() -> int:
"""Return the anchored HEAD commit timestamp for official release builds."""
result = _run_command(
[
"git",
"-C",
str(PROJECT_ROOT),
"-c",
"log.showSignature=false",
"show",
"-s",
"--format=%ct",
"HEAD",
],
description="Reading the source commit timestamp...",
error_message="Could not determine the HEAD commit timestamp",
)
raw_epoch = result.stdout.strip()
if not raw_epoch.isdigit():
_fail(f"Git returned an invalid commit timestamp: {raw_epoch!r}")

ambient_epoch = os.environ.get("SOURCE_DATE_EPOCH", "").strip()
if ambient_epoch and ambient_epoch != raw_epoch:
print(
" ⚠️ Ignoring ambient SOURCE_DATE_EPOCH="
f"{ambient_epoch}; using HEAD commit timestamp {raw_epoch}"
)
return int(raw_epoch)


def _environment_source_date_epoch() -> Optional[int]:
"""Return an explicitly configured rebuild epoch, treating blank as unset."""
raw_epoch = os.environ.get("SOURCE_DATE_EPOCH", "").strip()
if not raw_epoch:
return None
try:
return int(raw_epoch)
except ValueError:
_fail("SOURCE_DATE_EPOCH must be an integer Unix timestamp")


def _wheel_source_date_epoch() -> int:
"""Use an explicit rebuild epoch, or the anchored commit timestamp in Git."""
configured_epoch = _environment_source_date_epoch()
return configured_epoch if configured_epoch is not None else _git_source_date_epoch()


def _build_sdist_from_git(
version: str, output_dir: str = "dist", source_date_epoch: Optional[int] = None
) -> str:
"""Build source distribution from git using flit."""
_print_step(1, 2, "Building sdist with flit")

os.makedirs(output_dir, exist_ok=True)
_remove_ui_build_artifacts()
_check_git_working_tree()

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)
epoch = source_date_epoch if source_date_epoch is not None else _git_source_date_epoch()
_run_command(
["flit", "build", "--format", "sdist"],
description="Running flit build --format sdist...",
error_message="Failed to build sdist",
success_message="flit sdist created successfully",
env=env,
env=_release_build_environment(epoch),
)

# Find and rename sdist
Expand All @@ -734,14 +788,6 @@ 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 @@ -877,7 +923,9 @@ def _cleanup_wheel_contents(
shutil.rmtree(backup_dir)


def _build_wheel_from_current_dir(version: str, output_dir: str = "dist") -> str:
def _build_wheel_from_current_dir(
version: str, output_dir: str = "dist", source_date_epoch: Optional[int] = None
) -> str:
"""Build wheel from current directory (matches what voters do).

This is MUCH simpler than the old approach:
Expand All @@ -894,18 +942,14 @@ def _build_wheel_from_current_dir(version: str, output_dir: str = "dist") -> str
_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)
epoch = source_date_epoch if source_date_epoch is not None else _wheel_source_date_epoch()

_run_command(
["flit", "build", "--format", "wheel"],
description="",
error_message="Wheel build failed",
success_message="Wheel built successfully",
env=env,
env=_release_build_environment(epoch),
)

# Find the wheel
Expand Down Expand Up @@ -1486,21 +1530,26 @@ def cmd_all(args) -> bool:
_verify_project_root()
_validate_version(args.version)
_check_git_working_tree()
source_date_epoch = _git_source_date_epoch()

# Step 1: Git Archive
_print_step(1, 4, "Creating git archive")
_create_git_archive(args.version, args.rc_num, args.output_dir, skip_signing=skip_signing)

# Step 2: Build sdist
_print_step(2, 4, "Building sdist")
sdist_path = _build_sdist_from_git(args.version, args.output_dir)
sdist_path = _build_sdist_from_git(
args.version, args.output_dir, source_date_epoch=source_date_epoch
)
_sign_artifact(sdist_path, skip_signing=skip_signing)
if not _verify_artifact_complete(sdist_path, skip_signing=skip_signing):
_fail("sdist verification failed!")

# Step 3: Build wheel
_print_step(3, 4, "Building wheel")
wheel_path = _build_wheel_from_current_dir(args.version, args.output_dir)
wheel_path = _build_wheel_from_current_dir(
args.version, args.output_dir, source_date_epoch=source_date_epoch
)
if not _verify_wheel_with_twine(wheel_path):
_fail("Twine verification failed!")
_sign_artifact(wheel_path, skip_signing=skip_signing)
Expand Down
48 changes: 30 additions & 18 deletions scripts/verify_apache_artifacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -509,11 +509,15 @@ def verify_signatures(artifacts_dir: str, summary: VerificationSummary | None =
return all_valid


def _safe_extract_tar(tar_handle: tarfile.TarFile, extract_dir: str) -> None:
def _safe_extract_tar(
tar_handle: tarfile.TarFile,
extract_dir: str,
members: list[tarfile.TarInfo] | None = None,
) -> None:
try:
tar_handle.extractall(extract_dir, filter="data")
tar_handle.extractall(extract_dir, members=members, filter="data")
except TypeError:
tar_handle.extractall(extract_dir)
tar_handle.extractall(extract_dir, members=members)


def _build_rat_command(
Expand Down Expand Up @@ -783,14 +787,27 @@ def _release_artifact_map(artifacts_dir: str) -> dict[str, list[str]]:
}


def _extract_project_root(source_artifact: str, destination: str) -> Path:
def _extract_project_root_and_epoch(source_artifact: str, destination: str) -> tuple[Path, int]:
with tarfile.open(source_artifact, "r:gz") as tar:
_safe_extract_tar(tar, destination)
members = tar.getmembers()
if not members:
raise ValueError(f"source archive is empty: {source_artifact}")
source_date_epoch = int(members[0].mtime)
_safe_extract_tar(tar, destination, members=members)

entries = [entry for entry in Path(destination).iterdir()]
if len(entries) == 1 and entries[0].is_dir():
return entries[0]
return Path(destination)
return entries[0], source_date_epoch
return Path(destination), source_date_epoch


def _reproducible_build_environment(source_date_epoch: int) -> dict[str, str]:
"""Return the shared environment for voter rebuilds."""
env = os.environ.copy()
env["FLIT_USE_VCS"] = "0"
env["SOURCE_DATE_EPOCH"] = str(source_date_epoch)
env["PATH"] = f"{Path(sys.executable).parent}{os.pathsep}{env.get('PATH', '')}"
return env


def _load_apache_release_module(project_root: Path):
Expand Down Expand Up @@ -824,25 +841,24 @@ def _build_reproducible_wheel(
version,
output_dir,
]
env = os.environ.copy()
env["SOURCE_DATE_EPOCH"] = str(source_epoch)
env["PATH"] = f"{Path(sys.executable).parent}{os.pathsep}{env.get('PATH', '')}"
result = subprocess.run(
command,
cwd=project_root,
capture_output=True,
text=True,
check=False,
env=env,
env=_reproducible_build_environment(source_epoch),
)
output = "\n".join(item for item in [result.stdout, result.stderr] if item)
return result.returncode == 0, output


def _build_reproducible_artifacts(source_artifact: str, output_dir: str) -> tuple[bool, str]:
with tempfile.TemporaryDirectory() as temp_dir:
project_root = _extract_project_root(source_artifact, temp_dir)
source_epoch = int(os.path.getmtime(source_artifact))
try:
project_root, source_epoch = _extract_project_root_and_epoch(source_artifact, temp_dir)
except (OSError, tarfile.TarError, ValueError) as exc:
return False, f"unable to read source epoch: {exc}"
version_match = re.search(r"(\d+\.\d+\.\d+)", os.path.basename(source_artifact))
if version_match is None:
return False, f"unable to determine version from {os.path.basename(source_artifact)}"
Expand All @@ -852,17 +868,13 @@ def _build_reproducible_artifacts(source_artifact: str, output_dir: str) -> tupl
if os.path.exists(dist_dir):
shutil.rmtree(dist_dir)

env = os.environ.copy()
env["FLIT_USE_VCS"] = "0"
env["SOURCE_DATE_EPOCH"] = str(source_epoch)
env["PATH"] = f"{Path(sys.executable).parent}{os.pathsep}{env.get('PATH', '')}"
sdist_result = subprocess.run(
["flit", "build", "--format", "sdist"],
cwd=project_root,
capture_output=True,
text=True,
check=False,
env=env,
env=_reproducible_build_environment(source_epoch),
)
if sdist_result.returncode != 0:
return False, sdist_result.stderr or sdist_result.stdout
Expand Down
3 changes: 1 addition & 2 deletions telemetry/ui/.prettierrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,5 @@
"tabWidth": 2,
"printWidth": 100,
"singleQuote": true,
"trailingComma": "none",
"jsxBracketSameLine": true
"trailingComma": "none"
}
6 changes: 3 additions & 3 deletions telemetry/ui/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 6 additions & 2 deletions telemetry/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@
"build": "tsc && vite build",
"test": "vitest",
"preview": "vite preview",
"lint": "eslint .",
"lint": "eslint . --max-warnings=0",
"lint:fix": "eslint . --fix --max-warnings=0 --config ./.eslintrc.js --ignore-path ./.eslintignore",
"format:fix": "prettier --write ./**/*.{ts,tsx,css,md,json} --config ./.prettierrc.json --ignore-path ./.prettierignore",
"format": "prettier --check \"./**/*.{ts,tsx,css,md,json}\" --config ./.prettierrc.json --ignore-path ./.prettierignore",
"format:fix": "prettier --write \"./**/*.{ts,tsx,css,md,json}\" --config ./.prettierrc.json --ignore-path ./.prettierignore",
"precommit": "npm run lint:fix && npm run format",
"prepush": "npm run lint"
},
Expand Down Expand Up @@ -77,6 +78,9 @@
"*.tsx": [
"eslint --fix",
"prettier --write"
],
"*.{css,md,json}": [
"prettier --write"
]
}
}
3 changes: 2 additions & 1 deletion telemetry/ui/src/components/common/ThemeToggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ export const ThemeToggle = (props: { showLabel?: boolean }) => {
'group flex items-center gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold',
'text-gray-700 hover:bg-gray-50 hover:text-dwdarkblue',
'dark:text-gray-200 dark:hover:bg-gray-800 dark:hover:text-white'
)}>
)}
>
<Icon className="h-6 w-6 shrink-0 text-gray-400 group-hover:text-dwdarkblue dark:group-hover:text-white" />
{props.showLabel && <span>{isDark ? 'Light mode' : 'Dark mode'}</span>}
</button>
Expand Down
1 change: 0 additions & 1 deletion telemetry/ui/src/components/nav/appcontainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,6 @@ export const AppContainer = (props: { children: React.ReactNode }) => {
'flex items-center w-full text-left rounded-md p-2 gap-x-3 text-sm leading-6 font-semibold text-gray-700 dark:text-gray-200'
)}
>

<item.icon
className="h-6 w-6 shrink-0 text-gray-400 dark:text-gray-500"
aria-hidden="true"
Expand Down
Loading
Loading