Skip to content

Commit

Permalink
feat: add status command (#133)
Browse files Browse the repository at this point in the history
  • Loading branch information
nichmor authored Jun 28, 2024
1 parent 1ed29ec commit 91f0891
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 67 deletions.
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,
)

0 comments on commit 91f0891

Please sign in to comment.