Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
488a520
Add built executable
sumana-2705 Jun 28, 2025
ab8dc39
Remove built executable from repo and add PyInstaller .spec file
sumana-2705 Jul 3, 2025
feaf295
Update spec file.
JoeZiminski Jul 4, 2025
e93b848
Packaging with terminal working on Windows.
JoeZiminski Jul 22, 2025
b8284f3
Remove spec from gitignore.
JoeZiminski Jul 22, 2025
7caa5a2
Add macos packaging.
JoeZiminski Jul 24, 2025
ff61379
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 24, 2025
94cbea2
Tidying up on windows.
JoeZiminski Jul 25, 2025
f188a01
Working on windows.
JoeZiminski Jul 25, 2025
8b826e0
Working on macos but no installer yet.
JoeZiminski Jul 25, 2025
9ae5ded
Merge pull request #535 from sumana-2705/packaging_project
JoeZiminski Sep 2, 2025
ace948b
Start playing around with Linux.
JoeZiminski Sep 2, 2025
9e2e168
Start playing around with Linux 2.
JoeZiminski Sep 2, 2025
6685bf7
Start playing around with Linux 3.
JoeZiminski Sep 2, 2025
5b6a91c
Start playing around with Linux 4.
JoeZiminski Sep 2, 2025
a941e13
Start playing around with Linux 5.
JoeZiminski Sep 2, 2025
9c95587
Start playing around with Linux 6.
JoeZiminski Sep 2, 2025
3f1ef5c
Start playing around with Linux 7.
JoeZiminski Sep 2, 2025
acdaada
Start playing around with Linux 8.
JoeZiminski Sep 2, 2025
6d1c4d1
Working on linux.
JoeZiminski Sep 2, 2025
3848da1
Working on linux2
JoeZiminski Sep 2, 2025
a057e63
Merge branch 'cross-platform-packaging' of github.com:neuroinformatic…
JoeZiminski Sep 2, 2025
0313830
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 2, 2025
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
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
# Packaging
package/dist
package/build
package/_vendored
package/Output
package/inno_complie_script.iss

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
Expand Down Expand Up @@ -31,7 +38,6 @@ MANIFEST
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
Expand Down
5 changes: 4 additions & 1 deletion datashuttle/tui_launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,7 @@ def main() -> None:


if __name__ == "__main__":
main()
try:
main()
except:
input("hello")
33 changes: 27 additions & 6 deletions datashuttle/utils/rclone.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,30 @@
import platform
import shlex
import subprocess
import sys
import tempfile
from subprocess import CompletedProcess

from datashuttle.configs import canonical_configs
from datashuttle.utils import utils


def get_command(command: str) -> str:
""" """
if getattr(sys, "frozen", False):
# PyInstaller: binary extracted to _MEIPASS

if sys.platform == "win32":
format_command = f'"{sys._MEIPASS}/rclone.exe" {command}'
else:
format_command = f"{sys._MEIPASS}/rclone {command}"
else:
# Normal Python execution: use PATH or fixed path
format_command = f"rclone {command}" # or provide full path if needed

return format_command


def call_rclone(command: str, pipe_std: bool = False) -> CompletedProcess:
"""Call rclone with the specified command.

Expand All @@ -35,13 +52,17 @@ def call_rclone(command: str, pipe_std: bool = False) -> CompletedProcess:
subprocess.CompletedProcess with `stdout` and `stderr` attributes.

"""
command = "rclone " + command
format_command = get_command(command)

if pipe_std:
output = subprocess.run(
command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True
format_command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
shell=True,
)
else:
output = subprocess.run(command, shell=True)
output = subprocess.run(format_command, shell=True)

return output

Expand All @@ -64,18 +85,18 @@ def call_rclone_through_script(command: str) -> CompletedProcess:
"""
system = platform.system()

command = "rclone " + command

if system == "Windows":
suffix = ".bat"
else:
suffix = ".sh"
command = "#!/bin/bash\n" + command

format_command = get_command(command)

with tempfile.NamedTemporaryFile(
mode="w", suffix=suffix, delete=False
) as tmp_script:
tmp_script.write(command)
tmp_script.write(format_command)
tmp_script_path = tmp_script.name

try:
Expand Down
Binary file added package/NeuroBlueprint_icon.ico
Binary file not shown.
79 changes: 79 additions & 0 deletions package/datashuttle.spec
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# main.spec
# -*- mode: python ; coding: utf-8 -*-
import os
import sys
import platform
from glob import glob
from pathlib import Path

# Include .tcss files
tcss_files = [
(f, os.path.join("datashuttle", "tui", "css"))
for f in glob("../datashuttle/tui/css/*.tcss")
]

# Get current conda environment prefix
env_prefix = sys.prefix

# Detect OS and set rclone path
if platform.system() == "Windows":
rclone_src = os.path.join(env_prefix, "bin", "rclone.exe")
else:
rclone_src = os.path.join(env_prefix, "bin", "rclone")

# Verify rclone exists
if not os.path.isfile(rclone_src):
raise FileNotFoundError(f"rclone binary not found at: {rclone_src}")

# Add rclone as a binary to be bundled
binaries = [(rclone_src, '.')]

a = Analysis(
['datashuttle_launcher.py'], # terminal_launcher
pathex=[os.path.abspath('..')],
binaries=binaries,
datas=tcss_files,
hiddenimports=[
'datashuttle.tui_launcher',
'datashuttle.tui.app',
'textual.widgets._tab_pane',
'textual.widgets._input',
'textual.widgets._tree_control',
],
hookspath=['hooks'],
hooksconfig={},
runtime_hooks=[],
excludes=[],
noarchive=False,
optimize=0,
)

pyz = PYZ(a.pure)

exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name='datashuttle',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)

coll = COLLECT(
exe,
a.binaries,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='datashuttle'
)
24 changes: 24 additions & 0 deletions package/datashuttle_launcher.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import sys
from pathlib import Path

# Add project root to sys.path
project_root = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(project_root))

# Import main from tui_launcher
from datashuttle.tui_launcher import main as datashuttle_main


def run():
# Simulate: datashuttle launch
sys.argv = ["datashuttle", "launch"]
datashuttle_main()


if __name__ == "__main__":
try:
run()
except Exception as e:
print(f"\nError: {e}")
finally:
input("\nPress Enter to exit...")
1 change: 1 addition & 0 deletions package/license.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
hello world
4 changes: 4 additions & 0 deletions package/notes.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
TODO:
1) write package script for macOS and organise code and outputs properly.
2) go back to windows and rework everything
3) text on linux
73 changes: 73 additions & 0 deletions package/package_linux.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import shutil
import subprocess
from pathlib import Path

import packaging_utils

WEZTERM_VERSION = packaging_utils.get_wezterm_version()
WEZTERM_FOLDERNAME = f"WezTerm-{WEZTERM_VERSION}-Ubuntu20.04.AppImage"
WEZTERM_URL = f"https://github.com/wezterm/wezterm/releases/download/{WEZTERM_VERSION}/{WEZTERM_FOLDERNAME}.zip"

# Paths
project_root = Path(__file__).parent
vendored_dir = project_root / "_vendored"

if not (vendored_dir / WEZTERM_FOLDERNAME).exists():
packaging_utils.download_wezterm(vendored_dir, WEZTERM_FOLDERNAME)

if (build_path := project_root / "build").exists():
shutil.rmtree(build_path)

if (dist_path := project_root / "dist").exists():
shutil.rmtree(dist_path)

# Step 2: Run PyInstaller builds
subprocess.run(f"pyinstaller {project_root / 'datashuttle.spec'}", shell=True)
subprocess.run(
f"pyinstaller {project_root / 'terminal_launcher_windows.spec'}",
shell=True,
)

# Paths
dist_dir = project_root / "dist"
launcher_subdir = dist_dir / "terminal_launcher_"

shutil.move(dist_dir / "terminal_launcher", launcher_subdir)

# Copy contents of dist/terminal_launcher/ into dist/
for item in launcher_subdir.iterdir():
dest = dist_dir / item.name
if item.is_dir():
if dest.exists():
shutil.rmtree(dest)
shutil.copytree(item, dest)
else:
shutil.copy2(item, dest)


# TODO COPY LICENSE

shutil.rmtree(launcher_subdir)

vendored_output_path = dist_dir / "_vendored" / "squashfs-root"

shutil.copytree(
vendored_dir / "squashfs-root",
vendored_output_path,
dirs_exist_ok=True,
symlinks=True,
copy_function=shutil.copy2,
)

shutil.copy(vendored_dir / WEZTERM_FOLDERNAME, vendored_output_path.parent)


shutil.copy(
project_root / "license.txt", dist_dir
) # TODO: NEED TO DO THIS FOR ALL
shutil.copy(project_root / "NeuroBlueprint_icon.ico", dist_dir)

shutil.copy(
project_root / "wezterm_config.lua",
vendored_output_path,
)
52 changes: 52 additions & 0 deletions package/package_macos.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import shutil
import subprocess
from pathlib import Path

import packaging_utils

WEZTERM_VERSION = packaging_utils.get_wezterm_version()
WEZTERM_FOLDERNAME = f"WezTerm-macos-{WEZTERM_VERSION}"
WEZTERM_URL = f"https://github.com/wezterm/wezterm/releases/download/{WEZTERM_VERSION}/{WEZTERM_FOLDERNAME}.zip"

# Paths
project_root = Path(__file__).parent
vendored_dir = project_root / "_vendored"

if not (vendored_dir / WEZTERM_FOLDERNAME).exists():
packaging_utils.download_wezterm(vendored_dir, WEZTERM_FOLDERNAME)

if (build_path := project_root / "build").exists():
shutil.rmtree(build_path)

if (dist_path := project_root / "dist").exists():
shutil.rmtree(dist_path)

# Step 2: Run PyInstaller builds
subprocess.run(f"pyinstaller {project_root / 'datashuttle.spec'}", shell=True)
subprocess.run(
f"pyinstaller {project_root / 'terminal_launcher_macos.spec'}", shell=True
)

app_macos_path = (
project_root / "dist" / "Datashuttle.app" / "Contents" / "Resources"
)

shutil.copytree(
vendored_dir / f"{WEZTERM_FOLDERNAME}",
app_macos_path / "_vendored" / f"{WEZTERM_FOLDERNAME}",
)

shutil.copytree(
project_root / "dist" / "datashuttle" / "_internal",
app_macos_path.parent / "Resources" / "_internal",
)

shutil.copy(
project_root / "dist" / "datashuttle" / "datashuttle",
app_macos_path.parent / "Resources",
)

shutil.copy(
project_root / "wezterm_config.lua",
app_macos_path.parent / "Resources" / "_vendored" / WEZTERM_FOLDERNAME,
)
Loading
Loading