Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add status command #133

Merged
merged 6 commits into from
Jun 28, 2024
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
51 changes: 0 additions & 51 deletions src/repror/cli/check_recipe.py

This file was deleted.

19 changes: 12 additions & 7 deletions src/repror/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from rich.live import Live

from repror.internals.config import load_config
from repror.internals.db import get_rebuild_data
from repror.internals.print import print
from repror.internals import patch_database

Expand All @@ -21,8 +22,7 @@
from . import generate_html as html
from . import setup_rattler_build as setup
from . import rebuild_recipe as rebuild
from . import check_recipe
from .utils import pixi_root_cli
from .utils import pixi_root_cli, platform_name, platform_version, reproducible_table

from ..internals.options import global_options

Expand Down Expand Up @@ -103,7 +103,8 @@ def build_recipe(
recipes_to_build, Path(tmp_dir), force, patch, actions_url
)
print("Verifying if rebuilds are reproducible...")
check_recipe.check(recipes_to_build)
builds = get_rebuild_data(recipe_names, platform_name(), platform_version())
print(reproducible_table(builds))


@app.command()
Expand Down Expand Up @@ -171,7 +172,11 @@ def setup_rattler_build():


@app.command()
def check(recipe_names: Annotated[Optional[list[str]], typer.Argument()] = None):
"""Check if recipe name[s] is reproducible, by verifying it's build and rebuild hash."""
recipes_to_check = build.recipes_for_names(recipe_names)
check_recipe.check(recipes_to_check)
def status(
recipe_names: Annotated[Optional[list[str]], typer.Argument()] = None,
platform: Annotated[str, typer.Option()] = platform.system().lower(),
):
"""Check if recipe name[s] is reproducible for your platform, by verifying it's build and rebuild hash."""
recipe_names = [recipe.name for recipe in build.recipes_for_names(recipe_names)]
builds = get_rebuild_data(recipe_names, platform)
print(reproducible_table(recipe_names, builds, platform))
16 changes: 12 additions & 4 deletions src/repror/cli/generate_html.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@
from pathlib import Path
from jinja2 import Environment, FileSystemLoader

from repror.internals.db import BuildState, get_rebuild_data, get_total_successful_builds_and_rebuilds
from repror.internals.db import (
BuildState,
get_rebuild_data,
get_total_successful_builds_and_rebuilds,
)
from repror.internals.git import github_api
from repror.internals.print import print

Expand Down Expand Up @@ -103,18 +107,22 @@ def rerender_html(root_folder: Path, update_remote: bool = False):
timestamps = [end_of_day + timedelta(days=i) for i in range(0, 10)]
by_platform = defaultdict(list)
for build in builds:

# Do it in the loop so that we do this only once per platform
# and we dont have to hardcode the platforms
if build.platform_name not in counts_per_platform:
counts = [get_total_successful_builds_and_rebuilds(build.platform_name, before_time=time) for time in timestamps]
counts = [
get_total_successful_builds_and_rebuilds(
build.platform_name, before_time=time
)
for time in timestamps
]
counts_per_platform[build.platform_name] = {
# Total successful builds
"builds": [count.builds for count in counts],
# Total reproducible builds
"rebuilds": [count.rebuilds for count in counts],
# Total builds = builds + failed builds
"total_builds": [count.total_builds for count in counts]
"total_builds": [count.total_builds for count in counts],
}

if build.state == BuildState.FAIL:
Expand Down
53 changes: 53 additions & 0 deletions src/repror/cli/utils.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import platform
from rich.table import Table
from rich.text import Text
from ..internals.db import Build, Rebuild
Expand Down Expand Up @@ -55,3 +56,55 @@ def rebuild_to_table(rebuild: Rebuild) -> Table:
rebuild.actions_url,
)
return table


def reproducible_table(
recipe_names: list[str], builds: list[Build], platform: str
) -> Table:
"""Converts a a list of Build instance to a rich table that shows the reproducibility of the builds"""
cols = [
"Name",
"Platform",
"Is Repro",
]
table = Table(
*cols, title=f"Are we repro ? Total recipes in our queue: {len(recipe_names)}"
)

build_map = {build.recipe_name: build for build in builds}

for name in recipe_names:
build_by_name = build_map.get(name)
if not build_by_name:
table.add_row(name, platform, "Not build yet")
continue

if not build_by_name.rebuilds:
table.add_row(
build_by_name.recipe_name,
build_by_name.platform_name,
"Not rebuild yet",
)
continue

rebuild_hash = (
build_by_name.rebuilds[-1].rebuild_hash if build_by_name.rebuilds else None
)
is_same = build_by_name.build_hash == rebuild_hash if rebuild_hash else False
table.add_row(
build_by_name.recipe_name,
build_by_name.platform_name,
"Yes" if is_same else "No",
)

return table


def platform_name() -> str:
"""Get the platform name."""
return platform.system().lower()


def platform_version() -> str:
"""Get the platform version."""
return platform.release()
4 changes: 3 additions & 1 deletion src/repror/internals/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ class RattlerBuildConfig(BaseModel):

class ConfigYaml(BaseModel):
repositories: list[RemoteRepository]
rattler_build: Optional[RattlerBuildConfig] = Field(alias="rattler-build", serialization_alias="rattler-build")
rattler_build: Optional[RattlerBuildConfig] = Field(
alias="rattler-build", serialization_alias="rattler-build"
)
local: list[LocalRecipe]


Expand Down
24 changes: 20 additions & 4 deletions src/repror/internals/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,8 +280,11 @@ def save(build: Build | Rebuild | Recipe):
session.commit()


# Function to query the database and return rebuild data
def get_rebuild_data() -> Sequence[Build]:
# Function to query the database and return latest rebuild data
def get_rebuild_data(
recipe_names: Optional[list[str]] = None,
platform: Optional[str] = None,
) -> Sequence[Build]:
with get_session() as session:
# Subquery to get the latest build per platform
latest_build_subquery = (
Expand All @@ -290,6 +293,15 @@ def get_rebuild_data() -> Sequence[Build]:
.group_by(Build.recipe_name)
.order_by(col(Build.timestamp).desc())
)
if platform:
latest_build_subquery = latest_build_subquery.where(
Build.platform_name == platform
)

if recipe_names:
latest_build_subquery = latest_build_subquery.where(
or_(col(Build.recipe_name).in_(recipe_names))
)

# Main query to get the latest builds
all_group_builds = session.exec(latest_build_subquery).all()
Expand Down Expand Up @@ -359,12 +371,16 @@ def get_total_successful_builds_and_rebuilds(
Rebuild.state == BuildState.SUCCESS,
)

total_builds_query = select(func.count(col(Build.id))).join(subquery, (col(Build.id) == subquery.c.id))
total_builds_query = select(func.count(col(Build.id))).join(
subquery, (col(Build.id) == subquery.c.id)
)

# Execute the queries and count the results
successful_builds_count = session.exec(successful_builds_query).one()
successful_rebuilds_count = session.exec(successful_rebuilds_query).one()
total_builds: int = session.exec(total_builds_query).one()
return SuccessfulBuildsAndRebuilds(
successful_builds_count, successful_rebuilds_count, total_builds=total_builds
successful_builds_count,
successful_rebuilds_count,
total_builds=total_builds,
)
Loading