Skip to content

Commit

Permalink
Merge pull request #2996 from regro/update-ver-container
Browse files Browse the repository at this point in the history
feat: add code to update recipe version
  • Loading branch information
beckermr committed Sep 4, 2024
2 parents 065d353 + 4261e05 commit b9404fe
Show file tree
Hide file tree
Showing 4 changed files with 226 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,4 @@ versions/*
pixi.lock
pixi.toml
.ruff_cache/
conda-lock.yml
71 changes: 71 additions & 0 deletions conda_forge_tick/container_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,54 @@ def _migrate_feedstock(*, feedstock_name, default_branch, attrs, input_kwargs):
return data


def _update_version(*, version, hash_type):
from conda_forge_feedstock_ops.os_utils import (
chmod_plus_rwX,
get_user_execute_permissions,
reset_permissions_with_user_execute,
sync_dirs,
)

from conda_forge_tick.update_recipe.version import (
_update_version_feedstock_dir_local,
)

logger = logging.getLogger("conda_forge_tick.container")

with tempfile.TemporaryDirectory() as tmpdir:
input_fs_dir = glob.glob("/cf_feedstock_ops_dir/*-feedstock")
assert len(input_fs_dir) == 1, f"expected one feedstock, got {input_fs_dir}"
input_fs_dir = input_fs_dir[0]
logger.debug(
f"input container feedstock dir {input_fs_dir}: {os.listdir(input_fs_dir)}"
)
input_permissions = os.path.join(
"/cf_feedstock_ops_dir",
f"permissions-{os.path.basename(input_fs_dir)}.json",
)
with open(input_permissions) as f:
input_permissions = json.load(f)

fs_dir = os.path.join(tmpdir, os.path.basename(input_fs_dir))
sync_dirs(input_fs_dir, fs_dir, ignore_dot_git=True, update_git=False)
logger.debug(f"copied container feedstock dir {fs_dir}: {os.listdir(fs_dir)}")

reset_permissions_with_user_execute(fs_dir, input_permissions)

updated, errors = _update_version_feedstock_dir_local(
fs_dir,
version,
hash_type,
)
data = {"updated": updated, "errors": errors}

data["permissions"] = get_user_execute_permissions(fs_dir)
sync_dirs(fs_dir, input_fs_dir, ignore_dot_git=True, update_git=False)
chmod_plus_rwX(input_fs_dir, recursive=True, skip_on_error=True)

return data


def _get_latest_version(*, attrs, sources):
from conda_forge_tick.update_upstream_versions import (
all_version_sources,
Expand Down Expand Up @@ -666,5 +714,28 @@ def check_solvable(log_level, timeout, verbosity, additional_channels, build_pla
)


@cli.command(name="update-version")
@log_level_option
@click.option("--version", type=str, required=True, help="The version to update to.")
@click.option(
"--hash-type",
type=str,
required=True,
help="The type of hash to use.",
)
def update_version(
log_level,
version,
hash_type,
):
return _run_bot_task(
_update_version,
log_level=log_level,
existing_feedstock_node_attrs=None,
version=version,
hash_type=hash_type,
)


if __name__ == "__main__":
cli()
125 changes: 125 additions & 0 deletions conda_forge_tick/update_recipe/version.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,32 @@
import collections.abc
import hashlib
import io
import json
import logging
import os
import pprint
import re
import shutil
import tempfile
import traceback
from typing import Any, MutableMapping

import jinja2
import jinja2.sandbox
from conda_forge_feedstock_ops.container_utils import (
get_default_log_level_args,
run_container_operation,
should_use_container,
)
from conda_forge_feedstock_ops.os_utils import (
chmod_plus_rwX,
get_user_execute_permissions,
reset_permissions_with_user_execute,
sync_dirs,
)

from conda_forge_tick.hashing import hash_url
from conda_forge_tick.lazy_json_backends import loads
from conda_forge_tick.recipe_parser import CONDA_SELECTOR, CondaMetaYAML
from conda_forge_tick.url_transforms import gen_transformed_urls
from conda_forge_tick.utils import sanitize_string
Expand Down Expand Up @@ -379,6 +395,115 @@ def _try_to_update_version(cmeta: Any, src: str, hash_type: str):
return updated_version, errors


def update_version_feedstock_dir(
feedstock_dir, version, hash_type="sha256", use_container=None
):
"""Update the version in a recipe.
Parameters
----------
feedstock_dir : str
The feedstock directory w/ the recipe to update.
version : str
The version of the recipe.
hash_type : str, optional
The kind of hash used on the source. Default is sha256.
use_container : bool, optional
Whether to use a container to run the version parsing.
If None, the function will use a container if the environment
variable `CF_FEEDSTOCK_OPS_IN_CONTAINER` is 'false'. This feature can be
used to avoid container in container calls.
Returns
-------
updated : bool
If the recipe was updated, True, otherwise False.
errors : str of str
A set of strings giving any errors found when updating the
version. The set will be empty if there were no errors.
"""
if should_use_container(use_container=use_container):
return _update_version_feedstock_dir_containerized(
feedstock_dir,
version,
hash_type,
)
else:
return _update_version_feedstock_dir_local(
feedstock_dir,
version,
hash_type,
)


def _update_version_feedstock_dir_local(feedstock_dir, version, hash_type):
with open(os.path.join(feedstock_dir, "recipe", "meta.yaml")) as f:
raw_meta_yaml = f.read()
updated_meta_yaml, errors = update_version(
raw_meta_yaml, version, hash_type=hash_type
)
if updated_meta_yaml is not None:
with open(os.path.join(feedstock_dir, "recipe", "meta.yaml"), "w") as f:
f.write(updated_meta_yaml)

return updated_meta_yaml is not None, errors


def _update_version_feedstock_dir_containerized(feedstock_dir, version, hash_type):
with tempfile.TemporaryDirectory() as tmpdir:
tmp_feedstock_dir = os.path.join(tmpdir, os.path.basename(feedstock_dir))
sync_dirs(
feedstock_dir, tmp_feedstock_dir, ignore_dot_git=True, update_git=False
)

perms = get_user_execute_permissions(feedstock_dir)
with open(
os.path.join(tmpdir, f"permissions-{os.path.basename(feedstock_dir)}.json"),
"w",
) as f:
json.dump(perms, f)

chmod_plus_rwX(tmpdir, recursive=True)

logger.debug(f"host feedstock dir {feedstock_dir}: {os.listdir(feedstock_dir)}")
logger.debug(
f"copied host feedstock dir {tmp_feedstock_dir}: {os.listdir(tmp_feedstock_dir)}"
)

args = [
"conda-forge-tick-container",
"update-version",
"--version",
version,
"--hash-type",
hash_type,
]
args += get_default_log_level_args(logger)

data = run_container_operation(
args,
mount_readonly=False,
mount_dir=tmpdir,
json_loads=loads,
)

sync_dirs(
tmp_feedstock_dir,
feedstock_dir,
ignore_dot_git=True,
update_git=False,
)
reset_permissions_with_user_execute(feedstock_dir, data["permissions"])

# When tempfile removes tempdir, it tries to reset permissions on subdirs.
# This causes a permission error since the subdirs were made by the user
# in the container. So we remove the subdir we made before cleaning up.
shutil.rmtree(tmp_feedstock_dir)

data.pop("permissions", None)
return data["updated"], data["errors"]


def update_version(raw_meta_yaml, version, hash_type="sha256"):
"""Update the version in a recipe.
Expand Down
29 changes: 29 additions & 0 deletions tests/test_container_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
from conda_forge_tick.provide_source_code import provide_source_code_containerized
from conda_forge_tick.rerender_feedstock import rerender_feedstock
from conda_forge_tick.solver_checks import is_recipe_solvable
from conda_forge_tick.update_recipe.version import update_version_feedstock_dir
from conda_forge_tick.update_upstream_versions import (
all_version_sources,
get_latest_version_containerized,
Expand Down Expand Up @@ -735,3 +736,31 @@ def test_migration_runner_run_migration_containerized_version(
actual_output = f.read()
assert actual_output == output
assert m.filter(pmy) is True


@pytest.mark.skipif(
not (HAVE_CONTAINERS and HAVE_TEST_IMAGE), reason="containers not available"
)
@flaky
def test_container_tasks_update_version_feedstock_dir():
with tempfile.TemporaryDirectory() as tmpdir:
fs_dir = os.path.join(tmpdir, "mpich-feedstock")
rp_dir = os.path.join(fs_dir, "recipe")
os.makedirs(rp_dir, exist_ok=True)
with open(os.path.join(rp_dir, "meta.yaml"), "w") as f:
with open(os.path.join(YAML_PATH, "version_mpich.yaml")) as fp:
f.write(fp.read())

updated, errors = update_version_feedstock_dir(
fs_dir, "4.1.1", use_container=True
)
assert updated
assert not errors

with open(os.path.join(rp_dir, "meta.yaml")) as f:
actual_output = f.read()

with open(os.path.join(YAML_PATH, "version_mpich_correct.yaml")) as fp:
output = fp.read()

assert actual_output == output

0 comments on commit b9404fe

Please sign in to comment.