Search code examples
pythoncglibspawn

How to g_spawn (GLib) a Python script from a C program


I am trying to use GLib to spawn a python script process from within a C program. I can easily do this when the Python script has been compiled into an executable via PyInstaller by using:

// Set the python executable string based on the OS
gchar* python_executable;

#ifdef __MSYS__
python_executable = "../gtk_simple_plot/dist/plot/plot.exe";
g_print("Using MSYS2\n");
#else
python_executable = "../gtk_simple_plot/dist/plot/./plot";
g_print("Not Using MSYS2\n");
#endif

// Create argv for the python executable
gchar* argv[] = {python_executable, NULL};

GPid transition_pid;
GError* err = NULL;

int stdoutFD;
int stderrFD;

// Spawn the python program to plot
g_spawn_async_with_pipes (NULL, // Inherit parent's working directory
argv,                           // argv from above
NULL,                           // Don't set any additional environment variables
G_SPAWN_DO_NOT_REAP_CHILD,      // Default spawn settings
NULL,                           // No setup function prior to executing
NULL,                           // No data passed to setup function
&transition_pid,                // Store child pid
NULL,                           // No standard input file descriptor
&stdoutFD,                      // Standard output file descriptor
&stderrFD,                      // Standard error file descriptor
&err);                          // Store error

if (err != NULL)
{
  g_error ("Spawning child failed: %s", err->message);
  return;
}

However, I am running into some issues which I believe are coming from PyInstaller, so I want to just spawn the Python script, rather than the executable. The issue I am having stems from not being aware of what to pass to argv[]. The GLib page on spawning processes states that the g_spawn family of functions act like a cross-platform fork() and exec():

https://developer.gnome.org/glib/stable/glib-Spawning-Processes.html

I found this Stack Overflow link that states what is necessary for argv[] using an execlp() to spawn a Python script from a C program:

How to execute a Python program from C in Linux

There, the accepted answer states that the arguments should be as follows:

execlp("python3", "python3", "name_of_script.py", (char*)0);
//        ^ program to find
//                    ^ name to set in argv[0]
//                                  ^ script name to put in argv[1] so script is run

However, I have tried the following and none of them work:

(1) gchar* argv[] = {"python3 ../gtk_simple_plot/plot.py", NULL};
(2) gchar* argv[] = {"python3", "../gtk_simple_plot/plot.py", NULL};
(3) gchar* argv[] = {"python3", "../gtk_simple_plot/plot.py", (char*)0};
(4) gchar* argv[] = {"python3", "python3", "../gtk_simple_plot/plot.py", (char*)0};

All resulting in the same error:

Spawning child failed: Failed to execute child process (No such file or directory)

Clearly I am overlooking something (probably simple). Any assistance would be greatly appreciated.


Solution

  • The solution is using:

    gchar* argv[] = {"python3", "../gtk_simple_plot/plot.py", NULL};
    

    with the addition of G_SPAWN_SEARCH_PATH in the default spawn settings, such that the g_spawn call now looks like this:

    // Spawn the python program to plot
    spawn_success = 
    g_spawn_async_with_pipes (NULL,                     // Inherit parent's working directory
    argv,                                               // argv from above
    NULL,                                               // Don't set any additional environment variables
    G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH,    // Default spawn settings
    NULL,                                               // No setup function prior to executing
    NULL,                                               // No data passed to setup function
    &transition_pid,                                    // Store child pid
    NULL,                                               // No standard input file descriptor
    &stdoutFD,                                          // Standard output file descriptor
    &stderrFD,                                          // Standard error file descriptor
    &err);                                              // Store error
    

    This flag specifies that argv[0] need not be an absolute path, it will be looked for in the user's PATH.