Skip to content

Commit 7a5bfca

Browse files
committed
use temporary directory instead of managing the local feedstock dir manually
1 parent 6dcb67e commit 7a5bfca

File tree

10 files changed

+177
-50
lines changed

10 files changed

+177
-50
lines changed

conda_forge_tick/auto_tick.py

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import logging
55
import os
66
import random
7-
import shutil
87
import textwrap
98
import time
109
import traceback
@@ -30,7 +29,7 @@
3029
from conda_forge_tick.cli_context import CliContext
3130
from conda_forge_tick.deploy import deploy
3231
from conda_forge_tick.contexts import (
33-
GIT_CLONE_DIR,
32+
ClonedFeedstockContext,
3433
FeedstockContext,
3534
MigratorSessionContext,
3635
)
@@ -151,7 +150,7 @@ def _get_pre_pr_migrator_attempts(attrs, migrator_name, *, is_version):
151150

152151
def _prepare_feedstock_repository(
153152
backend: GitPlatformBackend,
154-
context: FeedstockContext,
153+
context: ClonedFeedstockContext,
155154
branch: str,
156155
base_branch: str,
157156
) -> bool:
@@ -196,7 +195,7 @@ def _prepare_feedstock_repository(
196195

197196
def _commit_migration(
198197
cli: GitCli,
199-
context: FeedstockContext,
198+
context: ClonedFeedstockContext,
200199
commit_message: str,
201200
allow_empty_commits: bool = False,
202201
raise_commit_errors: bool = True,
@@ -252,7 +251,7 @@ class _RerenderInfo:
252251

253252

254253
def _run_rerender(
255-
git_cli: GitCli, context: FeedstockContext, suppress_errors: bool = False
254+
git_cli: GitCli, context: ClonedFeedstockContext, suppress_errors: bool = False
256255
) -> _RerenderInfo:
257256
logger.info("Rerendering the feedstock")
258257

@@ -398,7 +397,7 @@ def _handle_solvability_error(
398397

399398

400399
def _check_and_process_solvability(
401-
migrator: Migrator, context: FeedstockContext, base_branch: str
400+
migrator: Migrator, context: ClonedFeedstockContext, base_branch: str
402401
) -> bool:
403402
"""
404403
If the migration needs a solvability check, perform the check. If the recipe is not solvable, handle the error
@@ -442,20 +441,48 @@ def get_spoofed_closed_pr_info() -> PullRequestInfoSpecial:
442441
)
443442

444443

445-
def run(
444+
def run_with_tmpdir(
446445
context: FeedstockContext,
447446
migrator: Migrator,
448447
rerender: bool = True,
449448
base_branch: str = "main",
450449
dry_run: bool = False,
451450
**kwargs: typing.Any,
451+
) -> tuple[MigrationUidTypedDict, dict] | tuple[Literal[False], Literal[False]]:
452+
"""
453+
For a given feedstock and migration run the migration in a temporary directory that will be deleted after the
454+
migration is complete.
455+
456+
The parameters are the same as for the `run` function. The only difference is that you pass a FeedstockContext
457+
instance instead of a ClonedFeedstockContext instance.
458+
459+
The exceptions are the same as for the `run` function.
460+
"""
461+
with context.reserve_clone_directory() as cloned_context:
462+
return run(
463+
context=cloned_context,
464+
migrator=migrator,
465+
rerender=rerender,
466+
base_branch=base_branch,
467+
dry_run=dry_run,
468+
**kwargs,
469+
)
470+
471+
472+
def run(
473+
context: ClonedFeedstockContext,
474+
migrator: Migrator,
475+
rerender: bool = True,
476+
base_branch: str = "main",
477+
dry_run: bool = False,
478+
**kwargs: typing.Any,
452479
) -> tuple[MigrationUidTypedDict, dict] | tuple[Literal[False], Literal[False]]:
453480
"""For a given feedstock and migration run the migration
454481
455482
Parameters
456483
----------
457-
context: FeedstockContext
458-
The node attributes
484+
context: ClonedFeedstockContext
485+
The current feedstock context, already containing information about a temporary directory for the feedstock.
459486
migrator: Migrator instance
460487
The migrator to run on the feedstock
461488
rerender : bool
@@ -513,7 +540,6 @@ def run(
513540
logger.critical(
514541
f"Failed to migrate {context.feedstock_name}, {context.attrs.get('pr_info', {}).get('bad')}",
515542
)
516-
shutil.rmtree(context.local_clone_dir)
517543
return False, False
518544

519545
# We raise an exception if we don't plan to rerender and wanted an empty commit.
@@ -607,8 +633,6 @@ def run(
607633
context.attrs, migrator_name, is_version=is_version_migration
608634
)
609635

610-
logger.info("Removing feedstock dir")
611-
shutil.rmtree(context.local_clone_dir)
612636
return migration_run_data["migrate_return_value"], pr_lazy_json
613637

614638

@@ -690,7 +714,7 @@ def _run_migrator_on_feedstock_branch(
690714
attrs,
691715
base_branch,
692716
migrator,
693-
fctx,
717+
fctx: FeedstockContext,
694718
dry_run,
695719
mctx,
696720
migrator_name,
@@ -702,7 +726,7 @@ def _run_migrator_on_feedstock_branch(
702726
fctx.attrs["new_version"] = attrs.get("version_pr_info", {}).get(
703727
"new_version", None
704728
)
705-
migrator_uid, pr_json = run(
729+
migrator_uid, pr_json = run_with_tmpdir(
706730
context=fctx,
707731
migrator=migrator,
708732
rerender=migrator.rerender,
@@ -1013,7 +1037,6 @@ def _run_migrator(migrator, mctx, temp, time_per, dry_run):
10131037
dump_graph(mctx.graph)
10141038

10151039
with filter_reprinted_lines("rm-tmp"):
1016-
eval_cmd(["rm", "-rf", f"{GIT_CLONE_DIR}/*"])
10171040
for f in glob.glob("/tmp/*"):
10181041
if f not in temp:
10191042
try:

conda_forge_tick/contexts.py

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1+
from __future__ import annotations
2+
13
import os
4+
import tempfile
25
import typing
6+
from collections.abc import Iterator
7+
from contextlib import contextmanager
38
from dataclasses import dataclass
49
from pathlib import Path
510

@@ -12,9 +17,6 @@
1217
from conda_forge_tick.migrators_types import AttrsTypedDict
1318

1419

15-
GIT_CLONE_DIR = Path("feedstocks").resolve()
16-
17-
1820
if os.path.exists("all_feedstocks.json"):
1921
with open("all_feedstocks.json") as f:
2022
DEFAULT_BRANCHES = load(f).get("default_branches", {})
@@ -32,10 +34,10 @@ class MigratorSessionContext:
3234
dry_run: bool = True
3335

3436

35-
@dataclass
37+
@dataclass(frozen=True)
3638
class FeedstockContext:
3739
feedstock_name: str
38-
attrs: "AttrsTypedDict"
40+
attrs: AttrsTypedDict
3941
_default_branch: str = None
4042

4143
@property
@@ -45,10 +47,6 @@ def default_branch(self):
4547
else:
4648
return self._default_branch
4749

48-
@default_branch.setter
49-
def default_branch(self, v):
50-
self._default_branch = v
51-
5250
@property
5351
def git_repo_owner(self) -> str:
5452
return "conda-forge"
@@ -64,13 +62,6 @@ def git_href(self) -> str:
6462
"""
6563
return f"https://github.com/{self.git_repo_owner}/{self.git_repo_name}"
6664

67-
@property
68-
def local_clone_dir(self) -> Path:
69-
"""
70-
The local path to the feedstock repository.
71-
"""
72-
return GIT_CLONE_DIR / self.git_repo_name
73-
7465
@property
7566
def automerge(self) -> bool | str:
7667
"""
@@ -100,3 +91,29 @@ def check_solvable(self) -> bool:
10091
{},
10192
False,
10293
)
94+
95+
@contextmanager
96+
def reserve_clone_directory(self) -> Iterator[ClonedFeedstockContext]:
97+
"""
98+
Reserve a temporary directory for the feedstock repository that will be available within the context manager.
99+
The returned context object will contain the path to the feedstock repository in local_clone_dir.
100+
After the context manager exits, the temporary directory will be deleted.
101+
"""
102+
with tempfile.TemporaryDirectory() as tmpdir:
103+
local_clone_dir = Path(tmpdir) / self.git_repo_name
104+
local_clone_dir.mkdir()
105+
yield ClonedFeedstockContext(
106+
**self.__dict__,
107+
local_clone_dir=local_clone_dir,
108+
)
109+
110+
111+
@dataclass(frozen=True, kw_only=True)
112+
class ClonedFeedstockContext(FeedstockContext):
113+
"""
114+
A FeedstockContext object that has reserved a temporary directory for the feedstock repository.
115+
"""
116+
117+
# Implementation Note: Keep this class frozen or there will be consistency issues if someone modifies
118+
# a ClonedFeedstockContext object in place - it will not be reflected in the original FeedstockContext object.
119+
local_clone_dir: Path

conda_forge_tick/migration_runner.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
import os
44
import shutil
55
import tempfile
6+
from pathlib import Path
67

7-
from conda_forge_tick.contexts import FeedstockContext
8+
from conda_forge_tick.contexts import ClonedFeedstockContext
89
from conda_forge_tick.lazy_json_backends import LazyJson, dumps
910
from conda_forge_tick.os_utils import (
1011
chmod_plus_rwX,
@@ -221,11 +222,14 @@ def run_migration_local(
221222
- pr_body: The PR body for the migration.
222223
"""
223224

224-
feedstock_ctx = FeedstockContext(
225+
# it would be better if we don't re-instantiate ClonedFeedstockContext ourselves and let
226+
# FeedstockContext.reserve_clone_directory be the only way to create a ClonedFeedstockContext
227+
feedstock_ctx = ClonedFeedstockContext(
225228
feedstock_name=feedstock_name,
226229
attrs=node_attrs,
230+
_default_branch=default_branch,
231+
local_clone_dir=Path(feedstock_dir),
227232
)
228-
feedstock_ctx.default_branch = default_branch
229233
recipe_dir = os.path.join(feedstock_dir, "recipe")
230234

231235
data = {

conda_forge_tick/migrators/arch.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import networkx as nx
66

7-
from conda_forge_tick.contexts import FeedstockContext
7+
from conda_forge_tick.contexts import ClonedFeedstockContext, FeedstockContext
88
from conda_forge_tick.make_graph import (
99
get_deps_from_outputs_lut,
1010
make_outputs_lut_from_graph,
@@ -213,7 +213,7 @@ def migrate(
213213
def pr_title(self, feedstock_ctx: FeedstockContext) -> str:
214214
return "Arch Migrator"
215215

216-
def pr_body(self, feedstock_ctx: FeedstockContext) -> str:
216+
def pr_body(self, feedstock_ctx: ClonedFeedstockContext) -> str:
217217
body = super().pr_body(feedstock_ctx)
218218
body = body.format(
219219
dedent(
@@ -384,7 +384,7 @@ def migrate(
384384
def pr_title(self, feedstock_ctx: FeedstockContext) -> str:
385385
return "ARM OSX Migrator"
386386

387-
def pr_body(self, feedstock_ctx: FeedstockContext) -> str:
387+
def pr_body(self, feedstock_ctx: ClonedFeedstockContext) -> str:
388388
body = super().pr_body(feedstock_ctx)
389389
body = body.format(
390390
dedent(

conda_forge_tick/migrators/broken_rebuild.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import networkx as nx
44

5+
from conda_forge_tick.contexts import ClonedFeedstockContext
56
from conda_forge_tick.migrators.core import Migrator
67

78
BROKEN_PACKAGES = """\
@@ -380,7 +381,7 @@ def migrate(self, recipe_dir, attrs, **kwargs):
380381
self.set_build_number(os.path.join(recipe_dir, "meta.yaml"))
381382
return super().migrate(recipe_dir, attrs)
382383

383-
def pr_body(self, feedstock_ctx) -> str:
384+
def pr_body(self, feedstock_ctx: ClonedFeedstockContext) -> str:
384385
body = super().pr_body(feedstock_ctx)
385386
body = body.format(
386387
"""\

conda_forge_tick/migrators/core.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import dateutil.parser
1111
import networkx as nx
1212

13-
from conda_forge_tick.contexts import FeedstockContext
13+
from conda_forge_tick.contexts import ClonedFeedstockContext, FeedstockContext
1414
from conda_forge_tick.lazy_json_backends import LazyJson
1515
from conda_forge_tick.make_graph import make_outputs_lut_from_graph
1616
from conda_forge_tick.path_lengths import cyclic_topological_sort
@@ -455,7 +455,9 @@ def migrate(
455455
"""
456456
return self.migrator_uid(attrs)
457457

458-
def pr_body(self, feedstock_ctx: FeedstockContext, add_label_text=True) -> str:
458+
def pr_body(
459+
self, feedstock_ctx: ClonedFeedstockContext, add_label_text=True
460+
) -> str:
459461
"""Create a PR message body
460462
461463
Returns

conda_forge_tick/migrators/migration_yaml.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
import networkx as nx
1212

13-
from conda_forge_tick.contexts import FeedstockContext
13+
from conda_forge_tick.contexts import ClonedFeedstockContext, FeedstockContext
1414
from conda_forge_tick.feedstock_parser import PIN_SEP_PAT
1515
from conda_forge_tick.make_graph import get_deps_from_outputs_lut
1616
from conda_forge_tick.migrators.core import GraphMigrator, Migrator, MiniMigrator
@@ -280,7 +280,7 @@ def migrate(
280280

281281
return super().migrate(recipe_dir, attrs)
282282

283-
def pr_body(self, feedstock_ctx: "FeedstockContext") -> str:
283+
def pr_body(self, feedstock_ctx: ClonedFeedstockContext) -> str:
284284
body = super().pr_body(feedstock_ctx)
285285
if feedstock_ctx.feedstock_name == "conda-forge-pinning":
286286
additional_body = (
@@ -534,7 +534,7 @@ def migrate(
534534

535535
return super().migrate(recipe_dir, attrs)
536536

537-
def pr_body(self, feedstock_ctx: "FeedstockContext") -> str:
537+
def pr_body(self, feedstock_ctx: ClonedFeedstockContext) -> str:
538538
body = (
539539
"This PR has been triggered in an effort to update the pin for"
540540
" **{name}**. The current pinned version is {current_pin}, "

conda_forge_tick/migrators/replacement.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import networkx as nx
88

9-
from conda_forge_tick.contexts import FeedstockContext
9+
from conda_forge_tick.contexts import ClonedFeedstockContext, FeedstockContext
1010
from conda_forge_tick.migrators.core import Migrator
1111

1212
if typing.TYPE_CHECKING:
@@ -127,7 +127,7 @@ def migrate(
127127
self.set_build_number(os.path.join(recipe_dir, "meta.yaml"))
128128
return super().migrate(recipe_dir, attrs)
129129

130-
def pr_body(self, feedstock_ctx: FeedstockContext) -> str:
130+
def pr_body(self, feedstock_ctx: ClonedFeedstockContext) -> str:
131131
body = super().pr_body(feedstock_ctx)
132132
body = body.format(
133133
"I noticed that this recipe depends on `%s` instead of \n"

0 commit comments

Comments
 (0)