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

Use pyinstaller #446

Merged
merged 21 commits into from
Jul 16, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/general.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
- name: Install Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
python-version: '3.11'
- name: Install Node
uses: actions/setup-node@v3
with:
Expand All @@ -50,7 +50,7 @@ jobs:
- name: Install Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
python-version: '3.11'
- name: Install Node
uses: actions/setup-node@v3
with:
Expand Down Expand Up @@ -90,7 +90,7 @@ jobs:
- name: Install Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
python-version: '3.11'
- name: Install Node
uses: actions/setup-node@v3
with:
Expand Down
54 changes: 11 additions & 43 deletions build.py
Original file line number Diff line number Diff line change
@@ -1,63 +1,31 @@
import os
import shutil
import sys
import tarfile

import fsspec
import PyInstaller.__main__


def build_assets():
source = "desktop/assets"
target = "build"

print(f"[assets] Copying '{source}' to '{target}'")

os.makedirs(target, exist_ok=True)
for name in os.listdir(source):
shutil.copy(f"{source}/{name}", target)

print(f"[assets] Copied '{source}' to '{target}'")


def build_runner():
cache = ".cache"
target = "build/runner"

os.makedirs(cache, exist_ok=True)
shutil.rmtree(target, ignore_errors=True)

datemark = "20230826"
basepath = "https://github.com/indygreg/python-build-standalone/releases/download"
filetype = "x86_64-unknown-linux-gnu-install_only"
if sys.platform == "darwin":
filetype = "x86_64-apple-darwin-install_only"
if sys.platform == "win32":
filetype = "x86_64-pc-windows-msvc-shared-install_only"
filename = f"cpython-3.10.13+{datemark}-{filetype}.tar.gz"

if not os.path.exists(f"{cache}/{filename}"):
local = fsspec.filesystem("file")
remote = fsspec.filesystem("http")
with local.open(f"{cache}/{filename}", "wb") as file_to:
with remote.open(f"{basepath}/{datemark}/{filename}", "rb") as file_from:
file_to.write(file_from.read())

with tarfile.open(f"{cache}/{filename}", "r:gz") as tar:
tar.extractall(cache)
shutil.move(f"{cache}/python", target)

print(f"[runner] Copied 'runner' to '{target}'")


def build_server():
source = "server"
target = "build/server"

shutil.rmtree(target, ignore_errors=True)
shutil.copytree(source, target)
"""Build an executable file for the FastAPI server."""

print(f"[server] Copied '{source}' to '{target}'")
print("[server] Creating executable file for FastAPI server")
PyInstaller.__main__.run([
'server/__main__.py',
'--collect-all', 'frictionless', # Frictionless depends on data files
'--distpath', 'build/server', # Output the file to the build folder
'--log-level', 'WARN'
])


if __name__ == "__main__":
build_assets()
build_runner()
build_server()
20 changes: 1 addition & 19 deletions client/loading.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,6 @@
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
<script>
window?.opendataeditor.ensureLogs((message) => {
if (message == "python") {
var element = document.getElementById("python")
var loader = document.getElementById("loader")
element.classList.toggle('pending')
element.removeChild(loader)
var next = document.getElementById("requirements")
next.appendChild(loader)
}
if (message == "requirements") {
var element = document.getElementById("requirements")
var loader = document.getElementById("loader")
element.classList.toggle('pending')
element.removeChild(loader)
var next = document.getElementById("server")
next.appendChild(loader)
}
if (message == "server") {
var element = document.getElementById("server")
var loader = document.getElementById("loader")
Expand Down Expand Up @@ -61,9 +45,7 @@
<h1 style="text-align: center;">Open Data Editor is loading</h1>
<p style="text-align: center;">The first time launching Open Data Editor needs to ensure requirements are installed.</p>
<ul id="logs">
<li class="pending" id="python">Ensure python is installed <span id="loader" class="loader"></span></li>
<li class="pending" id="requirements">Ensure python dependencies are installed</li>
<li class="pending" id="server">Ensure server is running</li>
<li class="pending" id="server">Starting backend Python server<span id="loader" class="loader"></span></li>
</ul>
</div>
</section>
Expand Down
69 changes: 0 additions & 69 deletions desktop/python.ts

This file was deleted.

22 changes: 0 additions & 22 deletions desktop/resources.ts

This file was deleted.

20 changes: 13 additions & 7 deletions desktop/server.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
import { spawnFile } from './system'
import cp from 'child_process'
import * as settings from './settings'
import log from 'electron-log'

export async function runServer() {
log.info(`[Run FastAPI server on port 4040`)
log.info(`[FastAPI Server] Running backend server on port 4040`)

// Start production server
const proc = spawnFile(
settings.PYTHON_TARGET,
['-m', 'server', settings.APP_TMP, '--port', `4040`],
process.resourcesPath
)
const proc = cp.spawn(settings.SERVER_EXEC, [settings.APP_TMP])

proc.stdout.on('data', (data) => log.info(`[FastAPI Server stdout] ${data.toString().trim()}`))
proc.stderr.on('data', (data) => log.error(`[FastAPI Server stderr] ${data.toString().trim()}`))
proc.on('close', (code) => {
log.info('[FastAPI Server]', { message: `child process exited with code ${code}` })
})
process.on('exit', () => {
log.info('[FastAPI Server]', { message: `exiting child process on node exit` })
proc.kill()
})
return proc
}

17 changes: 9 additions & 8 deletions desktop/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,19 @@ import { join } from 'path'

export const WIN = process.platform === 'win32'
export const HOME = os.homedir()
export const APP_NAME = 'opendataeditor'
export const APP_USER_MODEL_ID = 'org.opendataeditor'

// DIST point to the folder where the application will be installed in the OS
// opt in Linux and AppData in Windows
export const DIST = process.resourcesPath
export const DIST_PYTHON = join(DIST, 'runner')
export const DIST_SERVER = join(DIST, 'server')

export const APP_NAME = 'opendataeditor'
export const APP_USER_MODEL_ID = 'org.opendataeditor'
// Folder in the OS where we will store data generated by the application
// Example: /home/<user>/.opendataeditor
export const APP_HOME = join(HOME, `.${APP_NAME}`)
export const APP_PYTHON = join(APP_HOME, 'runner')
export const APP_PYTHON_VENV = join(APP_HOME, 'python')

// APP_TMP will be the folder to upload files the first time the user opens the Application
export const APP_TMP = join(APP_HOME, 'tmp')

export const PYTHON_SOURCE = join(APP_PYTHON, WIN ? 'python.exe' : 'bin/python3')
export const PYTHON_TARGET = join(APP_PYTHON_VENV, WIN ? 'Scripts\\python.exe' : 'bin/python3')
// Path to the FastAPI executable file to be run by the Desktop Application
export const SERVER_EXEC = join(DIST, 'server/__main__', WIN ? '__main__.exe': '__main__')
pdelboca marked this conversation as resolved.
Show resolved Hide resolved
28 changes: 0 additions & 28 deletions desktop/system.ts

This file was deleted.

12 changes: 2 additions & 10 deletions desktop/window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import { resolve, join } from 'path'
import { is } from '@electron-toolkit/utils'
import log from 'electron-log'
import * as server from './server'
import * as python from './python'
import * as resources from './resources'
import EventEmitter from 'events'

const loadingEvents = new EventEmitter()
Expand All @@ -14,7 +12,7 @@ const loadingEvents = new EventEmitter()
import icon from './assets/icon.png?asset'

export async function createWindow() {
// Create the browser window.

var splashWindow = new BrowserWindow({
width: 500,
height: 500,
Expand Down Expand Up @@ -58,19 +56,13 @@ export async function createWindow() {

// Open the DevTools.
// mainWindow.webContents.openDevTools()

if (!is.dev) {
log.info('Opening loading.html')
splashWindow.loadFile(resolve(__dirname, '..', 'client', 'loading.html'))
splashWindow.center()
log.info('## Start server')
await resources.ensurePython()
await python.ensurePythonVirtualEnvironment()
splashWindow?.webContents.send('ensureLogs', "python")
await python.ensurePythonRequirements()
splashWindow?.webContents.send('ensureLogs', "requirements")
await server.runServer()
splashWindow?.webContents.send('ensureLogs', "server")
}

loadingEvents.emit('finished')
Expand Down
7 changes: 0 additions & 7 deletions electron-builder.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,8 @@ files:
- 'build/client/**/*'
- 'build/desktop/**/*'
extraResources:
- from: 'pyproject.toml'
to: 'pyproject.toml'
- from: 'build/runner'
to: 'runner'
- from: 'build/server'
to: 'server'
filter:
- '**/*'
- '!**/__pycache__'

# Linux

Expand Down
4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ dependencies = [
"pygithub==1.59.1",
# Zenodo
"pyzenodo3==1.0.2",
# PyInstaller
"pyinstaller==6.8.0"
]

[tool.setuptools]
Expand All @@ -45,7 +47,7 @@ path = "package.json"
pattern = '"version": "(?P<version>.*)"'

[tool.hatch.envs.default]
python = "3.10"
python = "3.11"
pdelboca marked this conversation as resolved.
Show resolved Hide resolved
dependencies = [
"ruff",
"httpx",
Expand Down
Loading
Loading