diff --git a/conda_forge_tick/make_migrators.py b/conda_forge_tick/make_migrators.py index ca3b823a5..dd11e9ffd 100644 --- a/conda_forge_tick/make_migrators.py +++ b/conda_forge_tick/make_migrators.py @@ -74,7 +74,7 @@ make_from_lazy_json_data, skip_migrator_due_to_schema, ) -from conda_forge_tick.migrators.arch import OSXArm +from conda_forge_tick.migrators.arch import OSXArm, WinArm64 from conda_forge_tick.migrators.migration_yaml import ( MigrationYamlCreator, create_rebuild_graph, @@ -232,6 +232,15 @@ def add_arch_migrate(migrators: MutableSequence[Migrator], gx: nx.DiGraph) -> No ), ) + with fold_log_lines("making win-arm64 migrator"): + migrators.append( + WinArm64( + graph=total_graph, + pr_limit=PR_LIMIT, + name="arm64 win addition", + ), + ) + def add_rebuild_migration_yaml( migrators: MutableSequence[Migrator], diff --git a/conda_forge_tick/migrators/__init__.py b/conda_forge_tick/migrators/__init__.py index 488948d2d..1779e1e0f 100644 --- a/conda_forge_tick/migrators/__init__.py +++ b/conda_forge_tick/migrators/__init__.py @@ -1,5 +1,5 @@ # flake8: noqa -from .arch import ArchRebuild, OSXArm +from .arch import ArchRebuild, OSXArm, WinArm64 from .broken_rebuild import RebuildBroken from .conda_forge_yaml_cleanup import CondaForgeYAMLCleanup from .core import ( diff --git a/conda_forge_tick/migrators/arch.py b/conda_forge_tick/migrators/arch.py index 9b744d6b6..1a84cfa56 100644 --- a/conda_forge_tick/migrators/arch.py +++ b/conda_forge_tick/migrators/arch.py @@ -413,3 +413,150 @@ def pr_body(self, feedstock_ctx: ClonedFeedstockContext) -> str: def remote_branch(self, feedstock_ctx: FeedstockContext) -> str: return super().remote_branch(feedstock_ctx) + "_arm_osx" + + +class WinArm64(GraphMigrator): + """ + A Migrator that add win-arm64 builds to feedstocks + """ + + migrator_version = 1 + rerender = True + # We purposefully don't want to bump build number for this migrator + bump_number = 0 + ignored_packages = {} + arches = {"win_arm64": "win_64"} + + def __init__( + self, + graph: nx.DiGraph = None, + name: Optional[str] = None, + pr_limit: int = 0, + piggy_back_migrations: Optional[Sequence[MiniMigrator]] = None, + target_packages: Optional[Sequence[str]] = None, + effective_graph: nx.DiGraph = None, + _do_init: bool = True, + ): + if _do_init: + if target_packages is None: + # We are constraining the scope of this migrator + with open( + os.path.join( + os.environ["CONDA_PREFIX"], + "share", + "conda-forge", + "migrations", + "win_arm64.txt", + ) + ) as f: + target_packages = set(f.read().split()) + + if "outputs_lut" not in graph.graph: + graph.graph["outputs_lut"] = make_outputs_lut_from_graph(graph) + + # rebuild the graph to only use edges from the arm and power requirements + graph2 = nx.create_empty_copy(graph) + for node, attrs in graph.nodes(data="payload"): + for plat_arch in self.arches: + deps = set().union( + *attrs.get( + f"{plat_arch}_requirements", + attrs.get("requirements", {}), + ).values() + ) + for dep in get_deps_from_outputs_lut( + deps, graph.graph["outputs_lut"] + ): + graph2.add_edge(dep, node) + pass + + graph = graph2 + target_packages = set(target_packages) + if target_packages: + target_packages.add("python") # hack that is ~harmless? + _cut_to_target_packages(graph, target_packages) + + # filter out stub packages and ignored packages + _filter_stubby_and_ignored_nodes(graph, self.ignored_packages) + + if not hasattr(self, "_init_args"): + self._init_args = [] + + if not hasattr(self, "_init_kwargs"): + self._init_kwargs = { + "graph": graph, + "name": name, + "pr_limit": pr_limit, + "piggy_back_migrations": piggy_back_migrations, + "target_packages": target_packages, + "effective_graph": effective_graph, + "_do_init": False, + } + + super().__init__( + graph=graph, + pr_limit=pr_limit, + check_solvable=False, + piggy_back_migrations=piggy_back_migrations, + effective_graph=effective_graph, + ) + + assert not self.check_solvable, "We don't want to check solvability for aarch!" + self.target_packages = target_packages + self.name = name + + if _do_init: + self._reset_effective_graph() + + def filter(self, attrs: "AttrsTypedDict", not_bad_str_start: str = "") -> bool: + if super().filter(attrs): + return True + muid = frozen_to_json_friendly(self.migrator_uid(attrs)) + for arch in self.arches: + configured_arch = ( + attrs.get("conda-forge.yml", {}).get("provider", {}).get(arch) + ) + if configured_arch: + return muid in _sanitized_muids( + attrs.get("pr_info", {}).get("PRed", []), + ) + else: + return False + + def migrate( + self, recipe_dir: str, attrs: "AttrsTypedDict", **kwargs: Any + ) -> "MigrationUidTypedDict": + with pushd(recipe_dir + "/.."): + self.set_build_number("recipe/meta.yaml") + with open("conda-forge.yml") as f: + y = yaml_safe_load(f) + if "provider" not in y: + y["provider"] = {} + for k, v in self.arches.items(): + if k not in y["provider"]: + y["provider"][k] = v + + with open("conda-forge.yml", "w") as f: + yaml_safe_dump(y, f) + + return super().migrate(recipe_dir, attrs, **kwargs) + + def pr_title(self, feedstock_ctx: FeedstockContext) -> str: + return "Windows ARM Migrator" + + def pr_body(self, feedstock_ctx: ClonedFeedstockContext) -> str: + body = super().pr_body(feedstock_ctx) + body = body.format( + dedent( + """\ + This feedstock is being rebuilt as part of the windows arm migration. + + **Feel free to merge the PR if CI is all green, but please don't close it + without reaching out the the ARM Windows team first at @conda-forge/help-win-arm64.** + """, + ), + ) + return body + + def remote_branch(self, feedstock_ctx: FeedstockContext) -> str: + return super().remote_branch(feedstock_ctx) + "_arm_win" diff --git a/conda_forge_tick/models/pr_info.py b/conda_forge_tick/models/pr_info.py index dba07b01d..02c1043a4 100644 --- a/conda_forge_tick/models/pr_info.py +++ b/conda_forge_tick/models/pr_info.py @@ -53,6 +53,7 @@ class MigratorName(StrEnum): VERSION = "Version" ARCH_REBUILD = "ArchRebuild" OSX_ARM = "OSXArm" + WIN_ARM64 = "WinArm64" MIGRATION_YAML = "MigrationYaml" REBUILD = "Rebuild" BLAS_REBUILD = "BlasRebuild" diff --git a/conda_forge_tick/status_report.py b/conda_forge_tick/status_report.py index c2f835e54..811dc0d2b 100644 --- a/conda_forge_tick/status_report.py +++ b/conda_forge_tick/status_report.py @@ -29,6 +29,7 @@ OSXArm, Replacement, Version, + WinArm64, ) from conda_forge_tick.os_utils import eval_cmd from conda_forge_tick.path_lengths import cyclic_topological_sort @@ -437,6 +438,7 @@ def main() -> None: mgconf.get("longterm", False) or isinstance(migrator, ArchRebuild) or isinstance(migrator, OSXArm) + or isinstance(migrator, WinArm64) ): longterm_status[migrator_name] = f"{migrator.name} Migration Status" else: diff --git a/tests/test_migrator_to_json.py b/tests/test_migrator_to_json.py index 4b0650fc4..5d3bdbabf 100644 --- a/tests/test_migrator_to_json.py +++ b/tests/test_migrator_to_json.py @@ -267,3 +267,30 @@ def test_migrator_to_json_osx_arm(): ] assert isinstance(migrator2, conda_forge_tick.migrators.OSXArm) assert dumps(migrator2.to_lazy_json_data()) == lzj_data + + +def test_migrator_to_json_win_arm64(): + gx = nx.DiGraph() + gx.add_node("conda", reqs=["python"], payload={}, blah="foo") + + migrator = conda_forge_tick.migrators.WinArm64( + target_packages=["python"], + graph=gx, + pr_limit=5, + name="arm64 win addition", + ) + + data = migrator.to_lazy_json_data() + pprint.pprint(data) + lzj_data = dumps(data) + print("lazy json data:\n", lzj_data) + assert data["__migrator__"] is True + assert data["class"] == "WinArm64" + assert data["name"] == "arm64_win_addition" + + migrator2 = make_from_lazy_json_data(loads(lzj_data)) + assert [pgm.__class__.__name__ for pgm in migrator2.piggy_back_migrations] == [ + pgm.__class__.__name__ for pgm in migrator.piggy_back_migrations + ] + assert isinstance(migrator2, conda_forge_tick.migrators.WinArm64) + assert dumps(migrator2.to_lazy_json_data()) == lzj_data