Reputation: 1101
So far (on my mac) I have managed to package my flask app into a single .app file using pyInstaller and can successfully package electron into one .app file. Now I would like to be able to package the flask executable and electron app together into one executable.
I have tried what some other stack overflow posts suggested and used the child_process module to spawn the flask .app, however that gave me the below error:
Uncaught Exception:
Error: spawn ../server/dist/server.app ENOENT
at _errnoException (util.js:1024:11)
at Process.ChildProcess._handle.onexit (internal/child_process.js:190:19)
at onErrorNT (internal/child_process.js:372:16)
at _combinedTickCallback (internal/process/next_tick.js:138:11)
at process._tickCallback (internal/process/next_tick.js:180:9)
Here is my electron entry point code that caused this error:
const electron = require('electron');
const app = electron.app;
const BrowserWindow = electron.BrowserWindow;
const isDev = require('electron-is-dev');
const path = require('path');
const childSpawn = require('child_process').spawn;
let mainWindow;
const createWindow = () => {
childSpawn('../server/dist/server.app');
mainWindow = new BrowserWindow({ width: 900, height: 680 });
mainWindow.loadURL(isDev ? 'http://localhost:3000' : `file://${path.join(__dirname, '../build/index.html')}`);
app.setAboutPanelOptions({
applicationName: 'app_name',
applicationVersion: '0.0.1',
})
mainWindow.on('closed', () => mainWindow = null);
}
app.on('ready', createWindow);
app.on('window-all-closed', () => {
app.quit();
});
app.on('activate', () => {
if (mainWindow === null) {
createWindow();
}
});
However, if that were to work, I don't see how I could bundle the flask server together with the electron app into one executable?
I'd appreciate some help from someone who has successfully done this.
Upvotes: 4
Views: 4574
Reputation: 66
The packaged flask .app is an executable, it cannot be spawned as a child process. You will have to execute the file using execFile. I referred this Following is the snippet I referred from the site.
Some people are asking for the packaging. This is easy: apply the knowledge of how to package Python applications and Electron applications.
Useing PyInstaller.
Run the following in the terminal:
pyinstaller pycalc/api.py --distpath pycalcdist
rm -rf build/
rm -rf api.spec
If everything goes well, the pycalcdist/api/ folder should show up, as well as the executable inside that folder. This is the complete independent Python executable that could be moved to somewhere else.
Attention: the independent Python executable has to be generated! Because the target machine we want to distribute to may not have correct Python shell and/or required Python libraries. It’s almost impossible to just copy the Python source codes.
This is tricky because of the Python executable.
In the above example code, I write
// part of main.js
let script = path.join(__dirname, 'pycalc', 'api.py')
pyProc = require('child_process').spawn('python', [script, port])
Electron doesn’t provide functions to check whether the app is under distributed or not (at least I don’t find it). So I use a workaround here: check whether the Python executable has been generated or not.
In main.js, add the following functions:
// main.js
const PY_DIST_FOLDER = 'pycalcdist'
const PY_FOLDER = 'pycalc'
const PY_MODULE = 'api' // without .py suffix
const guessPackaged = () => {
const fullPath = path.join(__dirname, PY_DIST_FOLDER)
return require('fs').existsSync(fullPath)
}
const getScriptPath = () => {
if (!guessPackaged()) {
return path.join(__dirname, PY_FOLDER, PY_MODULE + '.py')
}
if (process.platform === 'win32') {
return path.join(__dirname, PY_DIST_FOLDER, PY_MODULE, PY_MODULE + '.exe')
}
return path.join(__dirname, PY_DIST_FOLDER, PY_MODULE, PY_MODULE)
}
And change the function createPyProc to this:
// main.js
// the improved version
const createPyProc = () => {
let script = getScriptPath()
let port = '' + selectPort()
if (guessPackaged()) {
pyProc = require('child_process').execFile(script, [port])
} else {
pyProc = require('child_process').spawn('python', [script, port])
}
if (pyProc != null) {
//console.log(pyProc)
console.log('child process success on port ' + port)
}
}
The key point is, check whether the *dist folder has been generated or not. If generated, it means we are in “production” mode, execFile the executable directly; otherwise, spawn the script using a Python shell.
Upvotes: 5