diff --git a/launch/core/platforms/linux.py b/launch/core/platforms/linux.py index bd87786..dbca2c7 100644 --- a/launch/core/platforms/linux.py +++ b/launch/core/platforms/linux.py @@ -209,7 +209,11 @@ def apply_patch(self, patch: str, verbose: bool = False) -> bool: f.write(patch) containerpath = os.path.join(self.mnt_container, filename) - cmd = f"""git apply --reject --whitespace=nowarn {containerpath} """ + # Native Windows images may have whitespace-only source drift in + # generated files. Tolerate that drift only for Windows; Linux keeps + # strict patch matching so unrelated mismatches remain visible. + apply_flags = "--ignore-space-change --ignore-whitespace " if self.platform == "windows" else "" + cmd = f"git apply --reject {apply_flags}--whitespace=nowarn {containerpath}" res = self.send_command(cmd) self.send_command(f"rm {containerpath}") if int(res.metadata.exit_code) == 0: diff --git a/launch/core/platforms/windows.py b/launch/core/platforms/windows.py index 28c146e..b544665 100644 --- a/launch/core/platforms/windows.py +++ b/launch/core/platforms/windows.py @@ -121,6 +121,39 @@ def send_command(self, command: str, timeout: int|None = None) -> CommandResult: return CommandResult(output=output, metadata=fallback_metadata) + def apply_patch(self, patch: str, verbose: bool = False) -> bool: + """Apply patches with Windows-only whitespace drift tolerance. + + WindowsRuntime overrides the inherited Linux implementation, so the + platform-specific flags must be present here as well as in the base + runtime. Linux remains strict through LinuxRuntime.apply_patch. + """ + output_temp = "\n\n<<<<<>>>>>\n\n" + filename = f"{uuid.uuid4()}.diff" + hostpath = os.path.join(self.mnt_host, filename) + with open(hostpath, "w") as fh: + fh.write(patch) + containerpath = os.path.join(self.mnt_container, filename) + try: + cmd = ( + f"git apply --reject --ignore-space-change --ignore-whitespace " + f"--whitespace=nowarn {containerpath}" + ) + res = self.send_command(cmd) + self.send_command(f"del /f /q {containerpath}") + if int(res.metadata.exit_code) == 0: + if verbose: + print(f"{cmd} ---- Patch applied Successfully!", flush=True) + return True + if verbose: + print(output_temp.format(out=res.output), flush=True) + return False + finally: + try: + os.remove(hostpath) + except OSError: + pass + @classmethod def _start_container( cls, diff --git a/tests/runtime_test.py b/tests/runtime_test.py index 809ec3d..1813526 100644 --- a/tests/runtime_test.py +++ b/tests/runtime_test.py @@ -41,6 +41,7 @@ import os import platform as host_platform +import tempfile import warnings import docker @@ -84,6 +85,55 @@ def no_command_result(self, command, timeout=None): return None +class FakePatchMetadata: + def __init__(self, exit_code=0): + self.exit_code = exit_code + + +class FakePatchResult: + def __init__(self, exit_code=0): + self.metadata = FakePatchMetadata(exit_code) + self.output = "" + + +class PatchProbeRuntime(LinuxRuntime): + def __init__(self, platform_name): + self.platform = platform_name + self.mnt_host = tempfile.mkdtemp() + self.mnt_container = r"C:\\mnt_tmp" if platform_name == "windows" else "/mnt_tmp" + self.stopped = False + self.commands = [] + + def send_command(self, command, timeout=None): + self.commands.append(command) + return FakePatchResult() + + +def test_apply_patch_is_whitespace_tolerant_only_on_windows(): + patch = "diff --git a/a b/a\\r\\n--- a/a\\r\\n+++ b/a\\r\\n" + + class WindowsPatchProbeRuntime(WindowsRuntime): + def __init__(self): + self.platform = "windows" + self.mnt_host = tempfile.mkdtemp() + self.mnt_container = r"C:\\mnt_tmp" + self.stopped = False + self.commands = [] + + def send_command(self, command, timeout=None): + self.commands.append(command) + return FakePatchResult() + + windows_runtime = WindowsPatchProbeRuntime() + assert windows_runtime.apply_patch(patch) is True + assert "--ignore-space-change --ignore-whitespace" in windows_runtime.commands[0] + + linux_runtime = PatchProbeRuntime("linux") + assert linux_runtime.apply_patch(patch) is True + assert "--ignore-space-change" not in linux_runtime.commands[0] + assert "--ignore-whitespace" not in linux_runtime.commands[0] + + @pytest.fixture def patch_runtime_constructor_io(monkeypatch): monkeypatch.setattr(LinuxRuntime, "_start_output_thread", lambda self: None)