Migrate `copy_directory` away from deprecated host constraint (#588)

diff --git a/rules/private/BUILD b/rules/private/BUILD
index bd3602a..749db42 100644
--- a/rules/private/BUILD
+++ b/rules/private/BUILD
@@ -1,5 +1,5 @@
 load("//:bzl_library.bzl", "bzl_library")
-load(":copy_file_private.bzl", "is_windows")
+load(":copy_common.bzl", "is_windows")
 
 package(default_applicable_licenses = ["//:license"])
 
diff --git a/rules/private/copy_common.bzl b/rules/private/copy_common.bzl
index a8f7243..de61057 100644
--- a/rules/private/copy_common.bzl
+++ b/rules/private/copy_common.bzl
@@ -43,3 +43,17 @@
     "no-remote": "1",
     "no-cache": "1",
 }
+
+OsInfo = provider(
+    doc = "Information about the target platform's OS.",
+    fields = ["is_windows"],
+)
+
+is_windows = rule(
+    implementation = lambda ctx: OsInfo(
+        is_windows = ctx.target_platform_has_constraint(ctx.attr._windows_constraint[platform_common.ConstraintValueInfo]),
+    ),
+    attrs = {
+        "_windows_constraint": attr.label(default = "@platforms//os:windows"),
+    },
+)
diff --git a/rules/private/copy_directory_private.bzl b/rules/private/copy_directory_private.bzl
index 650e17e..45aed4b 100644
--- a/rules/private/copy_directory_private.bzl
+++ b/rules/private/copy_directory_private.bzl
@@ -18,7 +18,7 @@
 cmd.exe (on Windows).
 """
 
-load(":copy_common.bzl", "COPY_EXECUTION_REQUIREMENTS")
+load(":copy_common.bzl", "COPY_EXECUTION_REQUIREMENTS", "OsInfo")
 
 def _copy_cmd(ctx, src, dst):
     # Most Windows binaries built with MSVC use a certain argument quoting
@@ -108,7 +108,7 @@
 
 def _copy_directory_impl(ctx):
     dst = ctx.actions.declare_directory(ctx.attr.out)
-    copy_directory_action(ctx, ctx.file.src, dst, ctx.attr.is_windows)
+    copy_directory_action(ctx, ctx.file.src, dst, ctx.attr._exec_is_windows[OsInfo].is_windows)
 
     files = depset(direct = [dst])
     runfiles = ctx.runfiles(files = [dst])
@@ -120,10 +120,15 @@
     provides = [DefaultInfo],
     attrs = {
         "src": attr.label(mandatory = True, allow_single_file = True),
-        "is_windows": attr.bool(mandatory = True),
         # Cannot declare out as an output here, because there's no API for declaring
         # TreeArtifact outputs.
         "out": attr.string(mandatory = True),
+        "_exec_is_windows": attr.label(
+            default = ":is_windows",
+            # The exec transition must match the exec group of the actions, which in
+            # this case is the default exec group.
+            cfg = "exec",
+        ),
     },
 )
 
@@ -147,10 +152,6 @@
     _copy_directory(
         name = name,
         src = src,
-        is_windows = select({
-            "@bazel_tools//src/conditions:host_windows": True,
-            "//conditions:default": False,
-        }),
         out = out,
         **kwargs
     )
diff --git a/rules/private/copy_file_private.bzl b/rules/private/copy_file_private.bzl
index 702c9e4..4b18943 100644
--- a/rules/private/copy_file_private.bzl
+++ b/rules/private/copy_file_private.bzl
@@ -19,7 +19,7 @@
 '_copy_file' does not.
 """
 
-load(":copy_common.bzl", "COPY_EXECUTION_REQUIREMENTS")
+load(":copy_common.bzl", "COPY_EXECUTION_REQUIREMENTS", "OsInfo")
 
 def copy_cmd(ctx, src, dst):
     # Most Windows binaries built with MSVC use a certain argument quoting
@@ -68,7 +68,7 @@
             target_file = ctx.file.src,
             is_executable = ctx.attr.is_executable,
         )
-    elif ctx.attr._exec_is_windows[_OsInfo].is_windows:
+    elif ctx.attr._exec_is_windows[OsInfo].is_windows:
         copy_cmd(ctx, ctx.file.src, ctx.outputs.out)
     else:
         copy_bash(ctx, ctx.file.src, ctx.outputs.out)
@@ -109,20 +109,6 @@
     attrs = _ATTRS,
 )
 
-_OsInfo = provider(
-    doc = "Information about the target platform's OS.",
-    fields = ["is_windows"],
-)
-
-is_windows = rule(
-    implementation = lambda ctx: _OsInfo(
-        is_windows = ctx.target_platform_has_constraint(ctx.attr._windows_constraint[platform_common.ConstraintValueInfo]),
-    ),
-    attrs = {
-        "_windows_constraint": attr.label(default = "@platforms//os:windows"),
-    },
-)
-
 def copy_file(name, src, out, is_executable = False, allow_symlink = False, **kwargs):
     """Copies a file to another location.