diff --git a/CHANGELOG.md b/CHANGELOG.md index 4204e5e3..cd98f331 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,12 @@ Types of changes # Latch SDK Changelog +## 2.50.0 - 2024-08-02 + +### Added + +* Add git commit information to version string if exists + ## 2.49.10 - 2024-08-02 ### Fixed diff --git a/latch/resources/workflow.py b/latch/resources/workflow.py index 14f84a22..06b47b33 100644 --- a/latch/resources/workflow.py +++ b/latch/resources/workflow.py @@ -1,10 +1,10 @@ import inspect +import os from dataclasses import is_dataclass from textwrap import dedent from typing import Callable, Dict, Union, get_args, get_origin import click -import os from flytekit import workflow as _workflow from flytekit.core.workflow import PythonFunctionWorkflow @@ -98,6 +98,15 @@ def decorator(f: Callable): ) raise click.exceptions.Exit(1) + git_hash = os.environ.get("GIT_COMMIT_HASH") + is_dirty = os.environ.get("GIT_IS_DIRTY") + + if git_hash is not None: + metadata._non_standard["git_commit_hash"] = git_hash + metadata._non_standard["git_is_dirty"] = ( + False if is_dirty is None else is_dirty == "True" + ) + _inject_metadata(f, metadata) # note(aidan): used for only serialize_in_container diff --git a/latch_cli/centromere/ctx.py b/latch_cli/centromere/ctx.py index 468fa4e6..07d656f1 100644 --- a/latch_cli/centromere/ctx.py +++ b/latch_cli/centromere/ctx.py @@ -75,6 +75,9 @@ class _CentromereCtx: internal_ip: Optional[str] = None username: Optional[str] = None + git_commit_hash: Optional[str] = None + git_is_dirty: bool = False + def __init__( self, pkg_root: Path, @@ -298,10 +301,42 @@ def __init__( ) self.version = self.version.strip() + try: + from git import GitError, Repo + + repo = Repo(pkg_root) + self.git_commit_hash = repo.head.commit.hexsha + self.git_is_dirty = repo.is_dirty() + except ImportError: + # rahul: import will fail if `git` is not installed locally + pass + except GitError: + pass + except Exception as e: + click.secho( + "WARN: Exception occurred while getting git hash from" + f" {self.pkg_root}: {e}", + fg="yellow", + ) + if not self.disable_auto_version: - hash = hash_directory(self.pkg_root) - self.version = f"{self.version}-{hash[:6]}" - click.echo(f" {self.version}\n") + hash = "" + + if self.git_commit_hash is not None: + hash += f"-{self.git_commit_hash[:6]}" + if self.git_is_dirty: + click.secho( + dedent(""" + The git repository is dirty. The version will be suffixed + with '-wip' until the changes are committed or removed. + """), + fg="yellow", + ) + hash += "-wip" + + hash += f"-{hash_directory(self.pkg_root)[:6]}" + + self.version = f"{self.version}{hash}" if self.nucleus_check_version(self.version, self.workflow_name): click.secho( diff --git a/latch_cli/services/register/register.py b/latch_cli/services/register/register.py index ff67dec3..289cdabd 100644 --- a/latch_cli/services/register/register.py +++ b/latch_cli/services/register/register.py @@ -395,7 +395,9 @@ def register( from ...snakemake.serialize import generate_jit_register_code from ...snakemake.workflow import build_jit_register_wrapper - sm_jit_wf = build_jit_register_wrapper(cache_tasks) + sm_jit_wf = build_jit_register_wrapper( + cache_tasks, ctx.git_commit_hash, ctx.git_is_dirty + ) generate_jit_register_code( sm_jit_wf, ctx.pkg_root, diff --git a/latch_cli/services/register/utils.py b/latch_cli/services/register/utils.py index d8aa2aa4..a2a473fd 100644 --- a/latch_cli/services/register/utils.py +++ b/latch_cli/services/register/utils.py @@ -19,6 +19,7 @@ ) import boto3 +import click import docker import requests from latch_sdk_config.latch import config @@ -116,7 +117,10 @@ def upload_image(ctx: _CentromereCtx, image_name: str) -> List[str]: def serialize_pkg_in_container( - ctx: _CentromereCtx, image_name: str, serialize_dir: str, wf_name_override: Optional[str] = None + ctx: _CentromereCtx, + image_name: str, + serialize_dir: str, + wf_name_override: Optional[str] = None, ) -> Tuple[List[str], str]: assert ctx.dkr_client is not None @@ -124,6 +128,13 @@ def serialize_pkg_in_container( if wf_name_override is not None: _env["LATCH_WF_NAME_OVERRIDE"] = wf_name_override + if ctx.git_commit_hash is not None: + click.secho( + f"Tagging workflow version with git commit {ctx.git_commit_hash}", fg="blue" + ) + _env["GIT_COMMIT_HASH"] = ctx.git_commit_hash + _env["GIT_IS_DIRTY"] = str(ctx.git_is_dirty) + _serialize_cmd = ["make", "serialize"] container = ctx.dkr_client.create_container( f"{ctx.dkr_repo}/{image_name}", diff --git a/latch_cli/snakemake/serialize.py b/latch_cli/snakemake/serialize.py index dc2641fd..85df65d0 100644 --- a/latch_cli/snakemake/serialize.py +++ b/latch_cli/snakemake/serialize.py @@ -470,7 +470,9 @@ def generate_jit_register_code( from latch_cli.services.register.utils import import_module_by_path meta = Path("{metadata_path}") / "__init__.py" - import_module_by_path(meta) + if meta.exists(): + import_module_by_path(meta) + import latch_metadata sys.stdout.reconfigure(line_buffering=True) diff --git a/latch_cli/snakemake/workflow.py b/latch_cli/snakemake/workflow.py index 004f382f..9f95f880 100644 --- a/latch_cli/snakemake/workflow.py +++ b/latch_cli/snakemake/workflow.py @@ -311,7 +311,12 @@ def interface_to_parameters( class JITRegisterWorkflow(WorkflowBase, ClassStorageTaskResolver): out_parameter_name = "o0" # must be "o0" - def __init__(self, cache_tasks: bool = False): + def __init__( + self, + cache_tasks: bool = False, + git_commit_hash: Optional[str] = None, + git_is_dirty: bool = False, + ): self.cache_tasks = cache_tasks assert metadata._snakemake_metadata is not None @@ -336,6 +341,15 @@ def __init__(self, cache_tasks: bool = False): desc = about_page_path.read_text() + if git_commit_hash is not None: + click.secho( + f"Tagging workflow version with git commit {git_commit_hash}", fg="blue" + ) + metadata._snakemake_metadata._non_standard["git_commit_hash"] = ( + git_commit_hash + ) + metadata._snakemake_metadata._non_standard["git_is_dirty"] = git_is_dirty + docstring = Docstring( f"{display_name}\n\n{desc}\n\n" + str(metadata._snakemake_metadata) ) @@ -1014,8 +1028,12 @@ def execute(self, **kwargs): return exception_scopes.user_entry_point(self._workflow_function)(**kwargs) -def build_jit_register_wrapper(cache_tasks: bool = False) -> JITRegisterWorkflow: - wrapper_wf = JITRegisterWorkflow(cache_tasks) +def build_jit_register_wrapper( + cache_tasks: bool = False, + git_commit_hash: Optional[str] = None, + git_is_dirty: bool = False, +) -> JITRegisterWorkflow: + wrapper_wf = JITRegisterWorkflow(cache_tasks, git_commit_hash, git_is_dirty) out_parameter_name = wrapper_wf.out_parameter_name python_interface = wrapper_wf.python_interface diff --git a/setup.py b/setup.py index f90da649..5fda16d1 100644 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ setup( name="latch", - version="v2.49.10", + version="v2.50.0", author_email="kenny@latch.bio", description="The Latch SDK", packages=find_packages(), @@ -47,6 +47,7 @@ "latch-sdk-gql==0.0.6", "latch-sdk-config==0.0.4", "python-dateutil>=2.8", + "GitPython==3.1.40", # for old latch develop, to be removed "aioconsole==0.6.1", "asyncssh==2.13.2",