Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement pipe API for executed processes IO redirection. #89206

Merged
merged 1 commit into from
Mar 28, 2024

Conversation

bruvzg
Copy link
Member

@bruvzg bruvzg commented Mar 6, 2024

  • Implement OS.execute_with_pipe method to run process with redirected stdio.
    • Allow reading process output incrementally (from a thread, since pipes are blocking).
    • Allow independently reading stdout and stderr.
    • Allow writing to process stdin.
    • IO use FileAccess API, instead of limiting it to the String.
  • Implement pipe://* path handling for creation of named pipes.
    • Mostly side product of the API, but can be useful for something.

Implemented on Windows and Unix (tested on macOS, but should work on Linux/Android as well).

Samples - click to expand

Process redirect:

var pipe
var thread


func _ready():
	# start terminal and open pipes
	var info
	if OS.get_name() == "Windows":
		info = OS.execute_with_pipe("cmd.exe", [])
	else:
		info = OS.execute_with_pipe("/bin/zsh", [])
	pipe = ret["stdio"]
	print(ret)
	thread = Thread.new()
	thread.start(_thread_func)
	get_window().close_requested.connect(clean_func)


func _thread_func():
	# read stdin and add to TextEdit.
	while pipe.is_open() and pipe.get_error() == OK:
		_add_char.call_deferred(char(pipe.get_8()))


func _add_char(c):
	$TextEdit.text += c
	$TextEdit.scroll_vertical = $TextEdit.get_v_scroll_bar().max_value


func _on_line_edit_text_submitted(new_text: String) -> void:
	# send command to stdin.
	var cmd = new_text + "\n"
	var buffer = cmd.to_utf8_buffer()
	pipe.store_buffer(buffer)
	$LineEdit.text = ""


func clean_func():
	# close pipe and cleanup.
	pipe.close()
	thread.wait_to_finish()

Named pipe (server):

	var pipe_nw = FileAccess.open("pipe://test_pipe_3425r72347hjkv", FileAccess.WRITE)
	pipe_nw.store_64(234334); # depending on platform, might block until client connected

Named pipe (client):

	var pipe_nr = FileAccess.open("pipe://test_pipe_3425r72347hjkv", FileAccess.READ)
	var r = pipe_nr.get_64();
	print(r)

doc/classes/OS.xml Outdated Show resolved Hide resolved
doc/classes/OS.xml Outdated Show resolved Hide resolved
doc/classes/OS.xml Outdated Show resolved Hide resolved
Copy link
Member

@KoBeWi KoBeWi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested on Windows and it works great.

@akien-mga akien-mga modified the milestones: 4.x, 4.3 Mar 26, 2024
core/os/os.h Outdated Show resolved Hide resolved
drivers/unix/file_access_unix_pipe.h Outdated Show resolved Hide resolved
drivers/unix/file_access_unix_pipe.h Outdated Show resolved Hide resolved
drivers/windows/file_access_windows_pipe.h Outdated Show resolved Hide resolved
core/core_bind.cpp Outdated Show resolved Hide resolved
core/core_bind.cpp Outdated Show resolved Hide resolved
…d stdio.

Implement `pipe://*` path handling for creation of named pipes.
@akien-mga akien-mga merged commit 1260ee6 into godotengine:master Mar 28, 2024
16 checks passed
@akien-mga
Copy link
Member

Thanks!

@KoBeWi
Copy link
Member

KoBeWi commented Apr 1, 2024

Unless I'm missing something, there is no way to get the result code from finished process 🤔

@silverkorn
Copy link

Unless I'm missing something, there is no way to get the result code from finished process 🤔

Maybe check tiny-process-library techniques?

@time-killer-games
Copy link
Contributor

Thank you so very much for this! Life saver!!! :D

@wyattbiker
Copy link

wyattbiker commented Jun 10, 2024

PLEASE IGNORE THIS COMMENT. I TESTED AGAIN (NO UPDATES NOR CHANGES) AND FOR WHATEVER REASON, IT"S WORKING. I''LEAVE COMMENT IN CASE IT COMES BACK. THANKS

Hi I am testing on Linux : Kubuntu 23.10, KDE Plasma Version: 5.27.8

Using the samples, I can get the named pipes to work. However I cannot get the Process Redirect to work.
(Also in the sample array ret["stdio"] should be info["stdio"])

I changed the execute command to /usr/bin/bash -c uname for Linux. It does transfer the output to TextEdit when I run it. However when I type something in LineEdit and press Enter, the _on_line_edit_text_submitted(new_text: String) executes, but then the application just terminates does not even go to clean_func. No message. Any ideas?

Here is the code I run:

extends Control

var pipe
var thread

func _ready():
	$TextEdit.text = "Textedit "
	$LineEdit.text = "Lineedit "
	# start terminal and open pipes
	var info
	if OS.get_name() == "Windows":
		info = OS.execute_with_pipe("cmd.exe", [])
	else:
		var args = ["-c","uname"]
		info = OS.execute_with_pipe("/usr/bin/bash", args)
		pass
	pipe = info["stdio"]
	print(info)
	thread = Thread.new()
	thread.start(_thread_func)
	get_window().close_requested.connect(clean_func)

func _thread_func():
	# read stdin and add to TextEdit.
	while pipe.is_open() and pipe.get_error() == OK:
		_add_char.call_deferred(char(pipe.get_8()))

func _add_char(c):
	print(c)
	$TextEdit.text += c
	$TextEdit.scroll_vertical = $TextEdit.get_v_scroll_bar().max_value

func _on_line_edit_text_submitted(new_text: String) -> void:
	# send command to stdin.
	var cmd = new_text + "\n"
	var buffer = cmd.to_utf8_buffer()
	pipe.store_buffer(buffer)
	$LineEdit.text = ""

func clean_func():
	# close pipe and cleanup.
	pipe.close()
	thread.wait_to_finish()

image

@wyattbiker
Copy link

wyattbiker commented Jun 11, 2024

PLEASE IGNORE THIS COMMENT. I TESTED AGAIN (NO UPDATES NOR CHANGES) AND FOR WHATEVER REASON, IT"S WORKING. I''LEAVE COMMENT IN CASE IT COMES BACK. THANKS

Additional Info:

I get this error in the output when it hits a breakpoint right after `info = OS.execute_with_pipe("/usr/bin/bash", args)

Godot Engine v4.3.beta1.official.a4f2ea91a - https://godotengine.org
Vulkan 1.3.255 - Forward+ - Using Device #0: Intel - Intel(R) Graphics (ADL GT2)

  editor/editor_properties.cpp:1328 - Cannot reliably represent '-9223372004306975481' in the inspector, value is too large.
  editor/editor_properties.cpp:1328 - Cannot reliably represent '-9223372004290198016' in the inspector, value is too large.
  editor/editor_properties.cpp:1328 - Cannot reliably represent '-9223372004306975481' in the inspector, value is too large.
  editor/editor_properties.cpp:1328 - Cannot reliably represent '-9223372004290198016' in the inspector, value is too large.

Also info is { "stdio": <FileAccess#-9223372004306975481>, "stderr": <FileAccess#-9223372004290198016>, "pid": 3668695 }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants