Skip to content

Commit

Permalink
Allow running Linux System apps for foreign targets
Browse files Browse the repository at this point in the history
  • Loading branch information
rmartin16 committed Jan 18, 2024
1 parent 1563842 commit be6f3da
Show file tree
Hide file tree
Showing 12 changed files with 1,235 additions and 166 deletions.
1 change: 1 addition & 0 deletions changes/1603.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The ``briefcase run`` command now supports the ``--target`` option to run Linux apps from within Docker for other distributions.
8 changes: 8 additions & 0 deletions src/briefcase/bootstraps/toga.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ def pyproject_table_linux_system_debian(self):
"libcairo2-dev",
# Needed to compile PyGObject wheel
"libgirepository1.0-dev",
# Needed to run the app
"gir1.2-gtk-3.0",
# "gir1.2-webkit2-4.0",
]
system_runtime_requires = [
Expand All @@ -91,6 +94,8 @@ def pyproject_table_linux_system_rhel(self):
"cairo-gobject-devel",
# Needed to compile PyGObject wheel
"gobject-introspection-devel",
# Needed to run the app
"gtk3",
]
system_runtime_requires = [
Expand All @@ -112,6 +117,9 @@ def pyproject_table_linux_system_suse(self):
"cairo-devel",
# Needed to compile PyGObject wheel
"gobject-introspection-devel",
# Needed to run the app
"gtk3", "typelib-1_0-Gtk-3_0",
# "libwebkit2gtk3", "typelib-1_0-WebKit2-4_1",
]
system_runtime_requires = [
Expand Down
340 changes: 280 additions & 60 deletions src/briefcase/integrations/docker.py

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions src/briefcase/integrations/subprocess.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import contextlib
import json
import operator
import os
Expand Down Expand Up @@ -177,6 +178,15 @@ def prepare(self):
# This is a no-op; the native subprocess environment is ready-to-use.
pass

@contextlib.contextmanager
def run_app_context(self, subprocess_kwargs: dict[str, ...]) -> dict[str, ...]:
"""A manager to wrap subprocess calls to run a Briefcase project app.
:param subprocess_kwargs: initialized keyword arguments for subprocess calls
"""
# This is a no-op; the native subprocess environment is ready-to-use.
yield subprocess_kwargs

def full_env(self, overrides: dict[str, str]) -> dict[str, str]:
"""Generate the full environment in which the command will run.
Expand Down
84 changes: 35 additions & 49 deletions src/briefcase/platforms/linux/system.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,26 @@ class LinuxSystemPassiveMixin(LinuxMixin):

@property
def use_docker(self):
# The passive mixing doesn't expose the `--target` option, as it can't use
# Docker. However, we need the use_docker property to exist so that the
# app config can be finalized in the general case.
return False
# The system backend doesn't have a literal "--use-docker" option, but
# `use_docker` is a useful flag for shared logic purposes, so evaluate
# what "use docker" means in terms of target_image.
return bool(self.target_image)

def add_options(self, parser):
super().add_options(parser)
parser.add_argument(
"--target",
dest="target",
help="Docker base image tag for the distribution to target for the build (e.g., `ubuntu:jammy`)",
required=False,
)

def parse_options(self, extra):
# The passive mixin doesn't expose the `--target` option, but if run infers
# build, we need target image to be defined.
options = super().parse_options(extra)
self.target_image = None
"""Extract the target_image option."""
options, overrides = super().parse_options(extra)
self.target_image = options.pop("target")

return options
return options, overrides

def build_path(self, app):
# Override the default build path to use the vendor name,
Expand Down Expand Up @@ -196,13 +204,6 @@ class LinuxSystemMostlyPassiveMixin(LinuxSystemPassiveMixin):
# The Mostly Passive mixin verifies that Docker exists and can be run, but
# doesn't require that we're actually in a Linux environment.

@property
def use_docker(self):
# The system backend doesn't have a literal "--use-docker" option, but
# `use_docker` is a useful flag for shared logic purposes, so evaluate
# what "use docker" means in terms of target_image.
return bool(self.target_image)

def app_python_version_tag(self, app: AppConfig):
if self.use_docker:
# If we're running in Docker, we can't know the Python3 version
Expand Down Expand Up @@ -372,22 +373,6 @@ def verify_tools(self):
if self.use_docker:
Docker.verify(tools=self.tools, image_tag=self.target_image)

def add_options(self, parser):
super().add_options(parser)
parser.add_argument(
"--target",
dest="target",
help="Docker base image tag for the distribution to target for the build (e.g., `ubuntu:jammy`)",
required=False,
)

def parse_options(self, extra):
"""Extract the target_image option."""
options, overrides = super().parse_options(extra)
self.target_image = options.pop("target")

return options, overrides

def clone_options(self, command):
"""Clone the target_image option."""
super().clone_options(command)
Expand Down Expand Up @@ -792,7 +777,7 @@ def build_app(self, app: AppConfig, **kwargs):
self.tools.subprocess.check_output(["strip", self.binary_path(app)])


class LinuxSystemRunCommand(LinuxSystemPassiveMixin, RunCommand):
class LinuxSystemRunCommand(LinuxSystemMixin, RunCommand):
description = "Run a Linux system project."
supported_host_os = {"Linux"}
supported_host_os_reason = "Linux system projects can only be executed on Linux."
Expand All @@ -809,23 +794,24 @@ def run_app(
# Set up the log stream
kwargs = self._prepare_app_env(app=app, test_mode=test_mode)

# Start the app in a way that lets us stream the logs
app_popen = self.tools.subprocess.Popen(
[os.fsdecode(self.binary_path(app))] + passthrough,
cwd=self.tools.home_path,
**kwargs,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
bufsize=1,
)
with self.tools[app].app_context.run_app_context(kwargs) as kwargs:
# Start the app in a way that lets us stream the logs
app_popen = self.tools[app].app_context.Popen(
[os.fsdecode(self.binary_path(app))] + passthrough,
cwd=self.tools.home_path,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
bufsize=1,
**kwargs,
)

# Start streaming logs for the app.
self._stream_app_logs(
app,
popen=app_popen,
test_mode=test_mode,
clean_output=False,
)
# Start streaming logs for the app.
self._stream_app_logs(
app,
popen=app_popen,
test_mode=test_mode,
clean_output=False,
)


def debian_multiline_description(description):
Expand Down
16 changes: 16 additions & 0 deletions tests/commands/new/test_build_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ def main():
"libcairo2-dev",
# Needed to compile PyGObject wheel
"libgirepository1.0-dev",
# Needed to run the app
"gir1.2-gtk-3.0",
# "gir1.2-webkit2-4.0",
]
system_runtime_requires = [
Expand All @@ -137,6 +140,8 @@ def main():
"cairo-gobject-devel",
# Needed to compile PyGObject wheel
"gobject-introspection-devel",
# Needed to run the app
"gtk3",
]
system_runtime_requires = [
Expand All @@ -156,6 +161,9 @@ def main():
"cairo-devel",
# Needed to compile PyGObject wheel
"gobject-introspection-devel",
# Needed to run the app
"gtk3", "typelib-1_0-Gtk-3_0",
# "libwebkit2gtk3", "typelib-1_0-WebKit2-4_1",
]
system_runtime_requires = [
Expand Down Expand Up @@ -1116,6 +1124,9 @@ def main():
"libcairo2-dev",
# Needed to compile PyGObject wheel
"libgirepository1.0-dev",
# Needed to run the app
"gir1.2-gtk-3.0",
# "gir1.2-webkit2-4.0",
]
system_runtime_requires = [
Expand All @@ -1134,6 +1145,8 @@ def main():
"cairo-gobject-devel",
# Needed to compile PyGObject wheel
"gobject-introspection-devel",
# Needed to run the app
"gtk3",
]
system_runtime_requires = [
Expand All @@ -1153,6 +1166,9 @@ def main():
"cairo-devel",
# Needed to compile PyGObject wheel
"gobject-introspection-devel",
# Needed to run the app
"gtk3", "typelib-1_0-Gtk-3_0",
# "libwebkit2gtk3", "typelib-1_0-WebKit2-4_1",
]
system_runtime_requires = [
Expand Down
Loading

0 comments on commit be6f3da

Please sign in to comment.