...
 
Commits (55)
image: registry.gitlab.com/brinkervii/grapejuice-ci-image/master
grapejuice:test:
stage: test
script:
- export PYTHONPATH=$(pwd)/src
- virtualenv -p $(which python3) ./venv
- source ./venv/bin/activate
- pip install -r requirements.txt
- pip install -r dev_requirements.txt
- pytest tests
grapejuice:debian_install:
stage: test
image: debian:buster
script:
- apt update
- apt upgrade -y
- find artifacts -iname \*.deb -exec apt install -y ./{} \;
grapejuice:debian:
stage: build
artifacts:
expire_in: 1 year
paths:
- artifacts/
script:
- virtualenv -p $(which python3) ./venv
- source ./venv/bin/activate
- pip install -r requirements.txt
- python3 setup.py package_debian
- mkdir artifacts
- cp dist/debian/* artifacts
......@@ -7,6 +7,8 @@ SRC_DIR=$PROJECT_DIR/src
export PYTHONPATH=$SRC_DIR:$PATH
cd "$PROJECT_DIR" || exit
source "$PROJECT_DIR/venv/bin/activate"
exec python -m grapejuice "[email protected]"
if [ -d "$PROJECT_DIR"/venv ]; then
source "$PROJECT_DIR/venv/bin/activate"
fi
exec python3 -m grapejuice "[email protected]"
......@@ -7,6 +7,8 @@ SRC_DIR=$PROJECT_DIR/src
export PYTHONPATH=$SRC_DIR:$PYTHONPATH
cd "$PROJECT_DIR" || exit
source "$PROJECT_DIR"/venv/bin/activate
exec python -m grapejuiced "[email protected]"
if [ -d "$PROJECT_DIR"/venv ]; then
source "$PROJECT_DIR"/venv/bin/activate
fi
exec python3 -m grapejuiced "[email protected]"
......@@ -7,6 +7,8 @@ SRC_DIR=$PROJECT_DIR/src
export PYTHONPATH=$SRC_DIR:$PATH
cd "$PROJECT_DIR" || exit
source "$PROJECT_DIR/venv/bin/activate"
exec python "$SRC_DIR/grapejuice/uninstall.py" "[email protected]"
if [ -d "$PROJECT_DIR"/venv ]; then
source "$PROJECT_DIR/venv/bin/activate"
fi
exec python3 "$SRC_DIR/grapejuice/uninstall.py" "[email protected]"
#!/usr/bin/env python3
import os
import site
import subprocess
import sys
REQUIRED_MAJOR = 3
REQUIRED_MINOR = 7
K_GRAPEJUICE_INSTALL_PREFIX = "GRAPEJUICE_INSTALL_PREFIX"
K_GRAPEJUICE_IS_PACKAGING = "GRAPEJUICE_IS_PACKAGING"
K_GRAPEJUICE_PACKAGE_PREFIX = "GRAPEJUICE_PACKAGE_PREFIX"
def get_install_prefix():
if K_GRAPEJUICE_INSTALL_PREFIX in os.environ:
assert K_GRAPEJUICE_PACKAGE_PREFIX in os.environ, f"{K_GRAPEJUICE_PACKAGE_PREFIX} must be present in the " \
f"environment for a packaging job to work "
os.environ[K_GRAPEJUICE_IS_PACKAGING] = "yes" # Assume we are packaging
print("! The installation script is assuming that grapejuice is being packaged, file assocations will NOT be "
"made !")
return os.getenv(K_GRAPEJUICE_INSTALL_PREFIX)
here = os.path.dirname(__file__)
site.addsitedir(os.path.join(here, "src"))
try:
from grapejuice_common.variables import xdg_data_home
return xdg_data_home()
except ImportError as e:
raise e
def perform_install():
import sys
import subprocess
import os
install_prefix = get_install_prefix()
assert os.path.exists(install_prefix), f"The install prefix directory '{install_prefix}' does not exist! Please " \
f"create it if you are absolutely sure this is the right path "
os.environ[K_GRAPEJUICE_INSTALL_PREFIX] = install_prefix
print("! Using the install prefix at ", install_prefix)
if "VIRTUAL_ENV" in os.environ:
print("! Detected VIRTUAL_ENV, finding system Python interpreter...")
......
#!/usr/bin/env bash
if [ -z ${GRAPEJUICE_INSTALL_PREFIX+x} ]; then
echo "GRAPEJUICE_INSTALL_PREFIX is not set, quitting..."
exit 1
fi
if [ ! -d "$GRAPEJUICE_INSTALL_PREFIX" ]; then
echo "GRAPEJUICE_INSTALL_PREFIX ($GRAPEJUICE_INSTALL_PREFIX) is not a directory, quitting..."
exit 1
fi
try_deactivate() {
if command -v deactivate; then
deactivate
......@@ -43,7 +53,7 @@ if [[ ! -f "$PYTHON" ]]; then
fi
"$PYTHON" --version || python_failed
APPLICATION_DIR=$HOME/.local/share/grapejuice
APPLICATION_DIR=$GRAPEJUICE_INSTALL_PREFIX/grapejuice
mkdir -p "$APPLICATION_DIR"
cp -frax . "$APPLICATION_DIR"
export PYTHONPATH=$APPLICATION_DIR/src
......@@ -66,6 +76,14 @@ deactivate
./bin/grapejuice post_install
case "$GRAPEJUICE_IS_PACKAGING" in
"yes")
echo "Removing virtualenv since we're packaging"
rm -rf ./venv
;;
esac
cd "$OLD_CWD" || exit 1
echo
......
......@@ -9,9 +9,9 @@ requirements_path = os.path.join(project_path, "requirements.txt")
sys.path.insert(0, src_path)
from setuptools import setup, find_packages
from setuptools import setup, find_packages, Command
from grapejuice.__init__ import __version__ as grapejuice_version
import grapejuice_packaging.metadata as metadata
with open(readme_path, "r") as fp:
long_description = fp.read()
......@@ -19,19 +19,38 @@ with open(readme_path, "r") as fp:
with open(requirements_path, "r") as fp:
requirements = [r.lstrip().rstrip() for r in fp.readlines()]
class PackageDebian(Command):
description = "Package Grapejuice for debian"
user_options = []
def initialize_options(self):
pass
def finalize_options(self):
pass
def run(self):
from grapejuice_packaging.__main__ import main as packaging_main
packaging_main([sys.argv[0], "debian"])
setup(
name="grapejuice",
version=grapejuice_version,
description="A simple wine+roblox management application",
author=metadata.author_name,
author_email=metadata.author_email,
version=metadata.package_version,
description=metadata.package_description,
license=metadata.package_license,
long_description=long_description,
long_description_content_type="text/markdown",
url="https://gitlab.com/brinkervii/grapejuice",
url=metadata.package_repository,
classifiers=[
"Development Status :: 4 - Beta",
'Programming Language :: Python :: 3.7'
],
keywords=["grapejuice wine roblox studio"],
packages=find_packages("src"),
packages=find_packages("src", exclude=("grapejuice_packaging",)),
package_dir={"": "src"},
include_package_data=True,
python_requires=">=3.7",
......@@ -41,5 +60,8 @@ setup(
"grapejuice=grapejuice.__main__:main",
"grapejuiced=grapejuiced.__main__:main"
]
},
cmdclass={
"package_debian": PackageDebian
}
)
import glob
import os
import shutil
import subprocess
from typing import Dict
from grapejuice_common import variables
from grapejuice_common.pid_file import daemon_pid_file
from grapejuice_common.settings import settings
EXCLUSIONS = ["venv", ".idea", ".git", "__pycache__", "wineprefix", ".gitignore"]
K_GRAPEJUICE_EXECUTABLE = "GRAPEJUICE_EXECUTABLE"
DESKTOP_STUDIO = "roblox-studio.desktop"
DESKTOP_PLAYER = "roblox-player.desktop"
DESKTOP_GRAPEJUICE = "grapejuice.desktop"
MIME_RBXL = "x-roblox-rbxl.xml"
MIME_RBXLX = "x-roblox-rbxlx.xml"
PROTOCOL_ASSOC = {
"x-scheme-handler/roblox-studio": DESKTOP_STUDIO,
"x-scheme-handler/roblox-player": DESKTOP_PLAYER
}
FILE_ASSOC = {
"application/x-roblox-rbxl": DESKTOP_STUDIO,
"application/x-roblox-rbxlx": DESKTOP_STUDIO
}
def copy_files(target):
source = variables.src_dir()
if not os.path.exists(target):
os.makedirs(target)
def log(*args):
print("!!", *args)
for f in os.listdir(source):
if f in EXCLUSIONS:
continue
src_fp = os.path.join(source, f)
dest_fp = os.path.join(target, f)
def invoke_xdg_mime() -> bool:
return not variables.is_packaging()
if os.path.exists(dest_fp):
shutil.rmtree(dest_fp, ignore_errors=True)
if os.path.isfile(src_fp):
shutil.copyfile(src_fp, dest_fp)
elif os.path.isdir(src_fp):
shutil.copytree(src_fp, dest_fp)
def get_desktop_install_path(desktop_name):
return os.path.join(variables.xdg_applications_dir(), desktop_name)
def run_shell_script(cwd, script):
old_cwd = os.getcwd()
os.chdir(cwd)
def install_desktop_files(environ: Dict):
assert isinstance(environ, dict)
os.spawnlp(os.P_WAIT, "bash", "bash", script)
desktop_assets = variables.desktop_assets_dir()
os.chdir(old_cwd)
for desktop_name in os.listdir(desktop_assets):
target_path = get_desktop_install_path(desktop_name)
source_path = os.path.join(desktop_assets, desktop_name)
target_directory = os.path.dirname(target_path)
os.makedirs(target_directory, exist_ok=True)
def install_packages(target):
run_shell_script(target, "install-packages.sh")
log("Installing desktop file", source_path, "to", target_path)
def process_line(line):
for k, v in environ.items():
search_ptn = "$" + k
if search_ptn in line:
line = line.replace(search_ptn, v)
def set_bits(target):
run_shell_script(target, "set-bits.sh")
return line
with open(source_path, "r") as source_fp:
with open(target_path, "w+") as target_fp:
output_lines = list(map(process_line, source_fp.readlines()))
target_fp.writelines(output_lines)
def install_desktop_file(source, install_target, target_dir):
name = os.path.basename(source)
def update_mime_database():
if not invoke_xdg_mime():
return
log("Updating MIME database")
xdg_mime = variables.xdg_mime_dir()
os.makedirs(xdg_mime, exist_ok=True)
subprocess.check_call(["update-mime-database", xdg_mime])
def install_mime_def(src, target_dir):
log("Installing MIME definition", src, "into the directory", target_dir)
name = os.path.basename(src)
target = os.path.join(target_dir, name)
assert target
if os.path.exists(target):
os.remove(target)
with open(source, "r") as source_file:
lines = []
for line in source_file.readlines():
lines.append(line.replace("$APPLICATION_DIR", install_target))
shutil.copy(src, target)
with open(target, "w+") as target_file:
target_file.writelines(lines)
def install_mime_files():
mime_source = variables.mime_xml_assets_dir()
mime_packages = variables.xdg_mime_packages()
def desktop_entries():
return [DESKTOP_GRAPEJUICE, DESKTOP_STUDIO, DESKTOP_PLAYER]
log("Installing MIME XML files from", mime_source, "into", mime_packages)
os.makedirs(mime_packages, exist_ok=True)
def install_desktop_files(install_target):
applications_dir = variables.xdg_applications_dir()
if not os.path.exists(applications_dir):
os.makedirs(applications_dir)
for file in glob.glob(os.path.join(mime_source, "*")):
install_mime_def(file, mime_packages)
for entry in desktop_entries():
install_desktop_file(
os.path.join(variables.assets_dir(), entry),
install_target,
applications_dir
)
update_mime_database()
def install_mime_def(src, target_dir):
name = os.path.basename(src)
def mime_assoc(desktop, mime_type):
if not invoke_xdg_mime():
return
target = os.path.join(target_dir, name)
if os.path.exists(target):
os.remove(target)
log("MIME assoc", desktop, mime_type)
shutil.copy(src, target)
subprocess.check_call(["xdg-mime", "default", desktop, mime_type])
def update_mime_database():
xdg_mime = variables.xdg_mime_dir()
if not os.path.exists(xdg_mime):
os.makedirs(xdg_mime)
def update_protocol_handlers():
log("Updating protocol handlers")
os.spawnlp(os.P_WAIT, "update-mime-database", "update-mime-database", xdg_mime)
for scheme, desktop in PROTOCOL_ASSOC.items():
mime_assoc(desktop, scheme)
def mime_files():
return [MIME_RBXL, MIME_RBXLX]
def update_file_associations():
log("Updating file associations")
for scheme, desktop in FILE_ASSOC.items():
mime_assoc(desktop, scheme)
def install_mime_files():
pkgs = variables.xdg_mime_packages()
if not os.path.exists(pkgs):
os.makedirs(pkgs)
for f in mime_files():
install_mime_def(os.path.join(variables.assets_dir(), f), pkgs)
def install_icons():
icons = glob.glob(os.path.join(variables.icons_assets_dir(), "**"), recursive=True)
icons = list(filter(os.path.isfile, icons))
update_mime_database()
common_prefix = variables.icons_assets_dir()
rel_icons = list(map(lambda path: os.path.relpath(path, common_prefix), icons))
def mime_assoc(desktop, mime_type):
os.spawnlp(os.P_WAIT, "xdg-mime", "xdg-mime", "default", desktop, mime_type)
for icon in rel_icons:
src = os.path.join(common_prefix, icon)
dst = os.path.join(variables.xdg_icons(), icon)
log("Installing icon", src, dst)
dst_parent = os.path.dirname(dst)
os.makedirs(dst_parent, exist_ok=True)
def update_protocol_handlers():
mime_assoc(DESKTOP_STUDIO, "x-scheme-handler/roblox-studio")
mime_assoc(DESKTOP_PLAYER, "x-scheme-handler/roblox-player")
shutil.copy(src, dst)
def update_file_associations():
mime_assoc(DESKTOP_STUDIO, "application/x-roblox-rbxl")
mime_assoc(DESKTOP_STUDIO, "application/x-roblox-rbxlx")
def post_install():
assert variables.K_GRAPEJUICE_INSTALL_PREFIX in os.environ
def application_dir():
if variables.is_packaging():
return os.path.join(variables.packaging_prefix(), "grapejuice")
return variables.system_application_dir()
def grapejuice_executable():
if K_GRAPEJUICE_EXECUTABLE in os.environ:
return os.environ[K_GRAPEJUICE_EXECUTABLE]
return os.path.join(application_dir(), "bin", "grapejuice")
environ = {
variables.K_GRAPEJUICE_INSTALL_PREFIX: variables.installation_prefix(),
"APPLICATION_DIR": application_dir(),
K_GRAPEJUICE_EXECUTABLE: grapejuice_executable(),
"GRAPEJUICE_ICON": "grapejuice",
"STUDIO_ICON": "grapejuice-roblox-studio",
"PLAYER_ICON": "grapejuice-roblox-player"
}
def post_install(install_target=variables.application_dir()):
pid_file = daemon_pid_file()
pid_file.kill()
install_mime_files()
install_desktop_files(install_target)
install_icons()
install_desktop_files(environ)
update_protocol_handlers()
update_file_associations()
settings.performed_post_install = True
settings.save()
def install_main(install_target=variables.application_dir()):
abs_install_target = os.path.abspath(install_target)
copy_files(abs_install_target)
install_packages(abs_install_target)
set_bits(abs_install_target)
install_mime_files()
install_desktop_files(abs_install_target)
update_protocol_handlers()
update_file_associations()
import os
from grapejuice import update, background
from grapejuice import background
from grapejuice.tasks import DisableMimeAssociations, ApplyDLLOverrides, InstallRoblox, DeployAssociations, \
GraphicsModeOpenGL, SandboxWine, RunRobloxStudio, ExtractFastFlags
from grapejuice.update import update_and_reopen
from grapejuice.update.update_provider import provider as update_provider
from grapejuice_common import variables, robloxctrl
from grapejuice_common import winectrl, version
from grapejuice_common.errors import NoWineError
......@@ -149,10 +149,10 @@ class MainWindowHandlers:
"to the instructions in the Grapejuice git repository. The upgrade will begin after you close this "
"dialog.")
update.update_and_reopen()
update_provider.do_update()
def reinstall(self, *_):
update_and_reopen()
update_provider.reinstall()
def deploy_assocs(self, *_):
run_task_once(DeployAssociations, generic_already_running)
......@@ -171,7 +171,8 @@ class MainWindow(WindowBase):
MainWindowHandlers()
)
self.update_status_label().set_text("Checking for updates...")
self.update_status_label.set_text("Checking for updates...")
self.update_update_related_buttons()
self.update_update_status()
background.tasks.tasks_changed.add_listener(self.on_tasks_changed)
......@@ -179,11 +180,26 @@ class MainWindow(WindowBase):
self.on_tasks_changed()
@property
def deploy_associations_button(self):
return self.builder.get_object("deploy_associations_button")
@property
def reinstall_button(self):
return self.builder.get_object("reinstall_button")
@property
def update_status_label(self):
return self.builder.get_object("update_status_label")
@property
def update_button(self):
return self.builder.get_object('update_button')
return self.builder.get_object("update_button")
def update_update_related_buttons(self):
if not update_provider.can_update():
self.deploy_associations_button.hide()
self.reinstall_button.hide()
def update_update_status(self):
w = self
......@@ -193,23 +209,29 @@ class MainWindow(WindowBase):
super().__init__("Checking for a newer version of Grapejuice")
def run(self) -> None:
if version.update_available():
s = "This version of Grapejuice is out of date\n{} -> {}".format(
str(version.local_version()),
str(version.cached_remote_version)
)
w.update_status_label().set_text(s)
w.update_button().show()
else:
local_ver = version.local_version()
if local_ver > version.cached_remote_version:
s = "This version of Grapejuice is from the future\n{}".format(str(local_ver))
if update_provider.can_update():
if version.update_available():
s = "This version of Grapejuice is out of date\n{} -> {}".format(
str(version.local_version()),
str(version.cached_remote_version)
)
w.update_status_label.set_text(s)
w.update_button.show()
else:
s = "Grapejuice is up to date\n{}".format(str(local_ver))
local_ver = version.local_version()
if local_ver > version.cached_remote_version:
s = "This version of Grapejuice is from the future\n{}".format(str(local_ver))
else:
s = "Grapejuice is up to date\n{}".format(str(local_ver))
w.update_status_label().set_text(s)
w.update_button().hide()
w.update_status_label.set_text(s)
w.update_button.hide()
else:
s = f"Running Grapejuice {version.local_version()}"
w.update_status_label.set_text(s)
w.update_button.hide()
self.finish()
......
......@@ -8,14 +8,14 @@ import grapejuice_common.variables as variables
def uninstall_desktop_files():
for entry in install.desktop_entries():
for entry in os.listdir(variables.desktop_assets_dir()):
file = os.path.join(variables.xdg_applications_dir(), entry)
if os.path.exists(file) and os.path.isfile(file):
os.remove(file)
def uninstall_mime_files():
for mime in install.mime_files():
for mime in os.listdir(variables.mime_xml_assets_dir()):
file = os.path.join(variables.xdg_mime_packages(), mime)
if os.path.exists(file) and os.path.isfile(file):
......@@ -23,7 +23,7 @@ def uninstall_mime_files():
def remove_application_dir():
shutil.rmtree(variables.application_dir(), ignore_errors=True)
shutil.rmtree(variables.system_application_dir(), ignore_errors=True)
def uninstall_main():
......
import os
from abc import ABC, abstractmethod
from grapejuice.update import legacy_update
from grapejuice_common.dist_info import DistributionType, dist_info
K_DISTRIBUTION_TYPE = "GRAPEJUICE_DISTRIBUTION_TYPE"
class UpdateProvider(ABC):
@staticmethod
@abstractmethod
def can_update():
raise NotImplementedError()
@staticmethod
def should_version_check():
return False
def do_update(self):
pass
def reinstall(self):
pass
class SourceUpdateProvider(UpdateProvider):
def can_update(self):
return True
def reinstall(self):
legacy_update.update_and_reopen()
def do_update(self):
legacy_update.update_and_reopen()
class SystemPackageUpdateProvider(UpdateProvider):
def can_update(self):
return False
UPDATE_PROVIDER_MAPPING = {
DistributionType.source: SourceUpdateProvider,
DistributionType.system_package: SystemPackageUpdateProvider
}
def get_distribution_type():
if K_DISTRIBUTION_TYPE in os.environ:
return os.environ[K_DISTRIBUTION_TYPE]
return dist_info.distribution_type
provider: UpdateProvider = UPDATE_PROVIDER_MAPPING[get_distribution_type()]()
[Desktop Entry]
Version=1.1
Version=1.0
Type=Application
Name=Grapejuice
Comment=Manage Roblox
Icon=$APPLICATION_DIR/src/grapejuice_common/assets/grapejuice.svg
Exec=$APPLICATION_DIR/bin/grapejuice gui
Actions=
Icon=$GRAPEJUICE_ICON
Exec=$GRAPEJUICE_EXECUTABLE gui
Categories=Development;
StartupWMClass=Grapejuice
[Desktop Entry]
Version=1.1
Version=1.0
Type=Application
Name=Roblox Player
NoDisplay=true
OnlyShowIn=X-None;
Comment=Play roblox games!
Icon=$APPLICATION_DIR/src/grapejuice_common/assets/roblox-player.svg
Exec=$APPLICATION_DIR/bin/grapejuice player "%u"
Actions=
Icon=$PLAYER_ICON
Exec=$GRAPEJUICE_EXECUTABLE player "%u"
MimeType=x-scheme-handler/roblox-player;
Categories=Game;
StartupWMClass=RobloxPlayer.exe
[Desktop Entry]
Version=1.1
Version=1.0
Type=Application
Name=Roblox Studio
Comment=Develop Roblox games
Icon=$APPLICATION_DIR/src/grapejuice_common/assets/roblox-studio.svg
Exec=$APPLICATION_DIR/bin/grapejuice studio "%u%f"
Actions=
Icon=$STUDIO_ICON
Exec=$GRAPEJUICE_EXECUTABLE studio "%u%f"
Categories=Development;
StartupWMClass=RobloxStudioLauncherBeta.exe
MimeType=x-scheme-handler/roblox-studio
{
"distribution_type": "source"
}
......@@ -4,6 +4,7 @@
<requires lib="gtk+" version="3.20"/>
<object class="GtkAboutDialog" id="grapejuice_about">
<property name="can_focus">False</property>
<property name="icon">../icons/hicolor/scalable/apps/grapejuice.svg</property>
<property name="type_hint">dialog</property>
<property name="program_name">Grapejuice</property>
<property name="version">version_string</property>
......@@ -12,7 +13,7 @@
<property name="license" translatable="yes">This program comes with absolutely no warranty.
See the &lt;a href="https://www.gnu.org/licenses/gpl-3.0.html"&gt;GNU General Public Licence, version 3 or later&lt;/a&gt; for details.</property>
<property name="authors">BrinkerVII &lt;[email protected]&gt;</property>
<property name="logo">../grapejuice-128.png</property>
<property name="logo">../icons/hicolor/128x128/apps/grapejuice.png</property>
<property name="wrap_license">True</property>
<property name="license_type">gpl-3-0</property>
<signal name="close" handler="close_about" swapped="no"/>
......
......@@ -9,7 +9,7 @@
<property name="window_position">center</property>
<property name="default_width">550</property>
<property name="default_height">300</property>
<property name="icon">../grapejuice.svg</property>
<property name="icon">../icons/hicolor/scalable/apps/grapejuice.svg</property>
<child type="titlebar">
<object class="GtkHeaderBar" id="fast_flag_editor_header">
<property name="visible">True</property>
......
......@@ -4,7 +4,7 @@
<requires lib="gtk+" version="3.20"/>
<object class="GtkWindow" id="fast_flag_warning">
<property name="can_focus">False</property>
<property name="icon">../grapejuice.svg</property>
<property name="icon">../icons/hicolor/scalable/apps/grapejuice.svg</property>
<signal name="destroy" handler="on_close" swapped="no"/>
<child type="titlebar">
<object class="GtkHeaderBar">
......
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 -->
<!-- Generated with glade 3.22.2 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkImage" id="icon_deploy_assocs">
......@@ -74,7 +74,7 @@
<property name="height_request">16</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="pixbuf">../roblox-studio-24.png</property>
<property name="pixbuf">../icons/hicolor/24x24/apps/grapejuice-roblox-studio.png</property>
</object>
<object class="GtkImage" id="icon_sandbox">
<property name="visible">True</property>
......@@ -176,7 +176,7 @@
<property name="resizable">False</property>
<property name="default_width">550</property>
<property name="default_height">300</property>
<property name="icon">../grapejuice.svg</property>
<property name="icon">../icons/hicolor/scalable/apps/grapejuice.svg</property>
<property name="startup_id">Grapejuice</property>
<signal name="destroy" handler="on_destroy" swapped="no"/>
<child type="titlebar">
......@@ -204,7 +204,7 @@
<property name="can_focus">False</property>
<property name="margin_top">3</property>
<property name="margin_bottom">3</property>
<property name="pixbuf">../grapejuice-24.png</property>
<property name="pixbuf">../icons/hicolor/24x24/apps/grapejuice.png</property>
</object>
</child>
</object>
......@@ -312,7 +312,7 @@
</packing>
</child>
<child>
<object class="GtkBox">
<object class="GtkBox" id="update_area">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
......@@ -389,7 +389,7 @@
</packing>
</child>
<child>
<object class="GtkButton">
<object class="GtkButton" id="reinstall_button">
<property name="label" translatable="yes">Reinstall Grapejuice</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
......@@ -405,7 +405,7 @@
</packing>
</child>
<child>
<object class="GtkButton">
<object class="GtkButton" id="deploy_associations_button">
<property name="label" translatable="yes">Deploy Associations</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
......@@ -600,9 +600,6 @@
<property name="position">2</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">True</property>
......
<?xml version="1.0" encoding="UTF-8"?>
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
<mime-type type="application/x-roblox-rbxl">
<comment>Roblox Place</comment>
<glob pattern="*.rbxl"/>
</mime-type>
<mime-type type="application/x-roblox-rbxlx">
<comment>Roblox XML Place</comment>
<glob pattern="*.rbxlx"/>
......
<?xml version="1.0" encoding="UTF-8"?>
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
<mime-type type="application/x-roblox-rbxl">
<comment>Roblox Place</comment>
<glob pattern="*.rbxl"/>
</mime-type>
</mime-info>
......@@ -2,7 +2,6 @@ import os
import time
from dbus import DBusException
from packaging import version
import grapejuice_common.dbus_config as dbus_config
from grapejuice_common.pid_file import daemon_pid_file
......@@ -75,7 +74,7 @@ class DBusConnection:
self.proxy.InstallRoblox()
def _spawn_daemon(self):
os.spawnlp(os.P_NOWAIT, "python", "python", "-m", "grapejuiced", "daemonize")
os.spawnlp(os.P_NOWAIT, "python3", "python3", "-m", "grapejuiced", "daemonize")
def version(self):
return self.proxy.Version()
......
import json
import os
from grapejuice_common import variables
class DistributionType:
source = "source"
system_package = "system_package"
class DistributionInfo:
def __init__(self, path: str = None):
if path is None:
path = os.path.join(variables.assets_dir(), "dist_info.json")
self._path = path
with open(self._path, "r") as fp:
self._info = json.load(fp)
def __getattr__(self, item):
if item in self._info:
return self._info[item]
return super().__getattribute__(item)
@property
def distribution_type(self):
k = "distribution_type"
assert k in self._info
v = self._info[k]
assert isinstance(v, str)
return v
@distribution_type.setter
def distribution_type(self, v: str):
assert v in DistributionType.__dict__.values()
self._info["distribution_type"] = v
def write(self):
with open(self._path, "w+") as fp:
json.dump(self._info, fp)
dist_info = DistributionInfo()
......@@ -4,6 +4,13 @@ import subprocess
from grapejuice_common.errors import NoWineError
HERE = os.path.abspath(os.path.dirname(__file__))
K_GRAPEJUICE_INSTALL_PREFIX = "GRAPEJUICE_INSTALL_PREFIX"
K_GRAPEJUICE_IS_PACKAGING = "GRAPEJUICE_IS_PACKAGING"
K_GRAPEJUICE_PACKAGE_PREFIX = "GRAPEJUICE_PACKAGE_PREFIX"
def is_packaging() -> bool:
return K_GRAPEJUICE_IS_PACKAGING in os.environ and os.environ[K_GRAPEJUICE_IS_PACKAGING].lower() == "yes"
def ensure_dir(p):
......@@ -17,22 +24,55 @@ def home():
return os.environ["HOME"]
def application_dir():
def system_application_dir():
p = os.path.dirname(src_dir())
assert os.path.exists(p)
return p
def user_application_dir():
return os.path.join(xdg_data_home(), "grapejuice")
def packaging_prefix():
if is_packaging():
return os.environ[K_GRAPEJUICE_PACKAGE_PREFIX]
return installation_prefix()
def assets_dir():
search_locations = [
os.path.join(HERE, "assets"),
os.path.join(src_dir(), "assets"),
os.path.join(os.getcwd(), "assets"),
os.path.join(application_dir(), "assets")
os.path.join(system_application_dir(), "assets")
]
for p in search_locations:
if os.path.exists(p):
return p
raise RuntimeError("Could not find assets directory")
def desktop_assets_dir():
return os.path.join(assets_dir(), "desktop")
def mime_xml_assets_dir():
return os.path.join(assets_dir(), "mime_xml")
def icons_assets_dir():
return os.path.join(assets_dir(), "icons")
def installation_prefix():
if K_GRAPEJUICE_INSTALL_PREFIX in os.environ:
return os.environ[K_GRAPEJUICE_INSTALL_PREFIX]
return os.path.dirname(system_application_dir())
def src_dir():
return os.path.abspath(os.path.dirname(HERE))
......@@ -99,7 +139,7 @@ def src_init_py():
def wineprefix_dir():
return os.path.join(application_dir(), "wineprefix")
return os.path.join(user_application_dir(), "wineprefix")
def wine_drive_c():
......@@ -146,6 +186,10 @@ def xdg_mime_packages():
return os.path.join(xdg_mime_dir(), "packages")
def xdg_icons():
return os.path.join(xdg_data_home(), "icons")
def xdg_config_home():
if "XDG_CONFIG_HOME" in os.environ:
config_home = os.environ["XDG_CONFIG_HOME"]
......@@ -160,6 +204,9 @@ def xdg_config_home():
def xdg_data_home():
if is_packaging():
return installation_prefix()
if "XDG_DATA_HOME" in os.environ:
data_home = os.environ["XDG_DATA_HOME"]
if data_home and os.path.exists(data_home) and os.path.isdir(data_home):
......
import argparse
import os
import sys
from grapejuice_packaging.debian import DebianPlatform
from grapejuice_packaging.packaging_platform import Platform
from grapejuice_packaging.pypi import PyPIPlatform
HERE = os.path.dirname(__file__)
SRC_DIRECTORY = os.path.dirname(HERE)
PROJECT_DIRECTORY = os.path.dirname(SRC_DIRECTORY)
os.chdir(PROJECT_DIRECTORY)
os.environ["GRAPEJUICE_IS_PACKAGING"] = "yes"
def main(in_args=None):
if in_args is None:
in_args = sys.argv
parser = argparse.ArgumentParser(prog="grapejuice_packaging", description="Package grapejuice")
subparsers = parser.add_subparsers(title="Distributions")
parser_debian = subparsers.add_parser("debian")
parser_debian.set_defaults(platform=DebianPlatform)
parser_pypi = subparsers.add_parser("pypi")
parser_pypi.set_defaults(platform=PyPIPlatform)
args = parser.parse_args(in_args[1:])
assert hasattr(args, "platform")
platform: Platform = args.platform()
return platform.run()
if __name__ == '__main__':
sys.exit(main())
import glob
import os
import shutil
import subprocess
from datetime import datetime
import grapejuice_packaging.metadata as metadata
from grapejuice_common.dist_info import DistributionInfo, DistributionType
from grapejuice_packaging import distribution_detect
from grapejuice_packaging.directory_stack import DirectoryStack
from grapejuice_packaging.packaging_platform import Platform
ARCHITECTURE = "amd64"
STANDARDS_VERSION = "3.9.6"
DEBHELPER_COMPAT = 10
VERSION = f"{metadata.package_version}"
PACKAGE_VERSION = f"{VERSION}_{ARCHITECTURE}"
PREFIX = "/usr/lib"
PYTHON_DIST_PACKAGES_DIR = f"{PREFIX}/python3/dist-packages"
MAINTAINER = f"{metadata.author_name} <{metadata.author_email}>"
PACKAGE_FILENAME = f"{metadata.package_name}i{PACKAGE_VERSION}.deb"
DEBIAN_SECTION = "python"
DEBIAN_PRIORITY = "optional"
DEBIAN_DISTRIBUTION = "unstable"
DEBIAN_URGENCY = "medium"
FIELD_SOURCE = ("Source", metadata.package_name)
FIELD_MAINTAINER = ("Maintainer", MAINTAINER)
FIELD_STANDARDS_VERSION = ("Standards-Version", STANDARDS_VERSION)
FIELD_ARCHITECTURE = ("Architecture", ARCHITECTURE)
FIELD_BUILD_DEPENDS = ("Build-Depends", [
"debhelper",
"python3",
"python3-pip",
"python3-virtualenv",
"git", "unzip"
])
CONTROL_FIELDS = [
FIELD_SOURCE,
("Section", DEBIAN_SECTION),
("Priority", DEBIAN_PRIORITY),
FIELD_MAINTAINER,
FIELD_BUILD_DEPENDS,
FIELD_STANDARDS_VERSION,
None,
("Package", metadata.package_name),
FIELD_ARCHITECTURE,
("Depends", [
"python3 (>= 3.7~)",
"python3-certifi",
"python3-dbus",
"python3-packaging",
"python3-psutil",
"python3-urllib3",
"python3-wget",
"python3-gi",
"libcairo2",
"libgirepository-1.0-1",
"libgtk-3-0",
"libgtk-3-bin",
"libdbus-1-3"
]),
("Homepage", metadata.package_repository),
("Description", metadata.package_description + "\n Generated by grapejuice_packaging")
]
COPYRIGHT_FIELDS = [
("Format", "https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/"),
("Upstream-Name", metadata.project_name),
("Upstream-Contact", MAINTAINER),
FIELD_SOURCE,
None,
("Files", "*"),
("Copyright", f"2019-{datetime.now().year} {metadata.author_name}"),
("License", metadata.package_license)
]
RULES = """#!/usr/bin/make -f
%:
\tdh [email protected]
override_dh_shlibdeps:
\ttrue
"""
def fields_to_string(fields):
def process_field(field):
if field is None:
return ""
assert isinstance(field, tuple) or isinstance(field, list)
assert len(field) == 2
key, value = field
if isinstance(value, list):
value = ", ".join(value)
return f"{key}: {str(value)}"
return "\n".join(list(map(process_field, fields)))
class DebianPlatform(Platform):
def __init__(self):
self._containment_dish = os.path.join("/", "tmp", "grapejuice-debian")
self._directory_stack = DirectoryStack()
@property
def _package_root(self):
return os.path.join(self._containment_dish, f"{metadata.package_name}-{PACKAGE_VERSION}")
@property
def _debian_directory(self):
p = os.path.join(self._package_root, "debian")
os.makedirs(p, exist_ok=True)
return p
@property
def _packages_directory(self):
p = os.path.join(self._package_root, "packages")
os.makedirs(p, exist_ok=True)
return p
@property
def _post_install_directory(self):
path = os.path.join(self._package_root, "post-install")
os.makedirs(path, exist_ok=True)
return path
@property
def _dist_directory(self):
path = os.path.join(Platform.path_dist(), "debian")
os.makedirs(path, exist_ok=True)
return path
def _write_compat(self):
with open(os.path.join(self._debian_directory, "compat"), "w+") as fp:
fp.write(str(int(DEBHELPER_COMPAT)))
def _write_install(self):
lines = []
for package_name in os.listdir(self._packages_directory):
lines.append(f"packages/{package_name} {PYTHON_DIST_PACKAGES_DIR}\n")
lines.append(f"post-install/* {self.package_prefix}\n")
with open(os.path.join(self._debian_directory, "install"), "w+") as fp:
fp.writelines(lines)
def _write_control(self):
with open(os.path.join(self._debian_directory, "control"), "w+") as fp:
fp.write(fields_to_string(CONTROL_FIELDS))
def _write_copyright(self):
with open(os.path.join(self._debian_directory, "copyright"), "w+") as fp:
fp.write(fields_to_string(COPYRIGHT_FIELDS))
def _write_files_file(self):
with open(os.path.join(self._debian_directory, "files"), "w+") as fp:
fp.write(" ".join([PACKAGE_FILENAME, DEBIAN_SECTION, DEBIAN_PRIORITY]))
def _write_rules(self):
path = os.path.join(self._debian_directory, "rules")
with open(path, "w+") as fp:
fp.write(RULES)
def _write_changelog(self):
lines = [f"{metadata.package_name} ({VERSION}) {DEBIAN_DISTRIBUTION}; urgency={DEBIAN_URGENCY}\n", "\n"]
try:
changelog = subprocess.check_output(["git", "shortlog"], timeout=1) \
.decode("UTF - 8").strip().split("\n")
except subprocess.TimeoutExpired as e:
changelog = ["Automatically generated package"]
print(str(e))
for changelog_line in list(map(lambda s: " * " + s, changelog)):
lines.append(changelog_line + "\n")
lines.append("\n")
date_str = datetime.now().strftime("%a, %d %b %Y %H:%M:%S") + " +0000"
lines.append(f" -- {MAINTAINER} {date_str}\n")
with open(os.path.join(self._debian_directory, "changelog"), "w+") as fp:
fp.writelines(lines)
def _build_unsigned_package(self):
self._directory_stack.pushd(self._package_root)
subprocess.check_call(["debuild", "-uc", "-us"])
self._directory_stack.popd()
def _unwheel_packages(self):
for wheel in Platform.list_wheels():
shutil.copy(wheel, self._packages_directory)
cwd = self._directory_stack.pushd(self._packages_directory)
for file in os.listdir(cwd):
subprocess.check_call(["unzip", file])
os.remove(os.path.join(cwd, file))
self._directory_stack.popd()
def _update_dist_info(self):
path = os.path.join(self._packages_directory, "grapejuice_common", "assets", "dist_info.json")
dist_info = DistributionInfo(path)
dist_info.distribution_type = DistributionType.system_package
dist_info.write()
def _move_out(self):
for file in filter(os.path.isfile, glob.glob(os.path.join(self._containment_dish, "**"))):
shutil.move(file, self._dist_directory)
def _clean(self):
if os.path.exists(self._containment_dish):
shutil.rmtree(self._containment_dish)
def before_package(self):