Skip to content

Commit

Permalink
Merge pull request #1781 from freakboy3742/cmdline-app
Browse files Browse the repository at this point in the history
Add support for console apps
  • Loading branch information
mhsmith authored Jun 5, 2024
2 parents fd89e3a + 9c0f09c commit 8e9a8f6
Show file tree
Hide file tree
Showing 53 changed files with 3,404 additions and 1,380 deletions.
1 change: 1 addition & 0 deletions automation/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ dynamic = ["version", "dependencies"]

[project.entry-points."briefcase.bootstraps"]
"Toga Automation" = "automation.bootstraps.toga:TogaAutomationBootstrap"
"Console Automation" = "automation.bootstraps.console:ConsoleAutomationBootstrap"
"PySide6 Automation" = "automation.bootstraps.pyside6:PySide6AutomationBootstrap"
"Pygame Automation" = "automation.bootstraps.pygame:PygameAutomationBootstrap"

Expand Down
16 changes: 16 additions & 0 deletions automation/src/automation/bootstraps/console.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from automation.bootstraps import BRIEFCASE_EXIT_SUCCESS_SIGNAL, EXIT_SUCCESS_NOTIFY
from briefcase.bootstraps import ConsoleBootstrap


class ConsoleAutomationBootstrap(ConsoleBootstrap):
def app_source(self):
return f"""\
import time
def main():
time.sleep(2)
print("{EXIT_SUCCESS_NOTIFY}")
print("{BRIEFCASE_EXIT_SUCCESS_SIGNAL}")
"""
1 change: 1 addition & 0 deletions changes/1184.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
macOS now supports the generation of ``.pkg`` installers as a packaging format.
1 change: 1 addition & 0 deletions changes/1729.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
If ``run`` is executed directly after a ``create`` when using an ``app`` template (macOS or Windows), the implied ``build`` step is now correctly identified.
1 change: 1 addition & 0 deletions changes/1781.removal.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The macOS ``app`` packaging format has been renamed ``zip`` for consistency with Windows, and to reflect the format of the output artefact.
1 change: 1 addition & 0 deletions changes/556.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Briefcase can now package command line apps.
10 changes: 10 additions & 0 deletions docs/reference/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,16 @@ on an app with a formal name of "My App" would remove:
3. Any ``.exe`` file in ``path`` or its subdirectories.
4. The file ``My App/content/extra.doc``.

``console_app``
~~~~~~~~~~~~~~~

A Boolean describing if the app is a console app, or a GUI app. Defaults to ``False``
(producing a GUI app). This setting has no effect on platforms that do not support a
console mode (e.g., web or mobile platforms). On platforms that do support console apps,
the resulting app will write output directly to ``stdout``/``stderr`` (rather than
writing to a system log), creating a terminal window to display this output (if the
platform allows).

``exit_regex``
~~~~~~~~~~~~~~

Expand Down
11 changes: 7 additions & 4 deletions docs/reference/platforms/macOS/app.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,14 @@ By default, apps will be both signed and notarized when they are packaged.
Packaging format
================

Briefcase supports two packaging formats for a macOS ``.app`` bundle:
Briefcase supports three packaging formats for a macOS app:

1. A DMG that contains the ``.app`` bundle (the default output of ``briefcase package
macOS``, or by using ``briefcase package macOS -p dmg``); or
2. A zipped ``.app`` folder (using ``briefcase package macOS -p app``).
1. A DMG that contains the ``.app`` bundle (using ``briefcase package macOS -p dmg``).
2. A zipped ``.app`` folder (using ``briefcase package macOS -p zip``).
3. A ``.pkg`` installer (using ``briefcase package macOS -p pkg``).

``.pkg`` is the *required* format for console apps. ``.dmg`` is the
default format for GUI apps.

Icon format
===========
Expand Down
12 changes: 8 additions & 4 deletions docs/reference/platforms/macOS/xcode.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,15 @@ By default, apps will be both signed and notarized when they are packaged.
Packaging format
================

Briefcase supports two packaging formats for a macOS Xcode project:
Briefcase supports three packaging formats for a macOS Xcode project:

1. A DMG that contains the ``.app`` bundle (the default output of ``briefcase package
macOS Xcode``, or by using ``briefcase package macOS Xcode -p dmg``); or
2. A zipped ``.app`` folder (using ``briefcase package macOS Xcode -p app``).
1. A DMG that contains the ``.app`` bundle (using ``briefcase package macOS Xcode -p
dmg``).
2. A zipped ``.app`` folder (using ``briefcase package macOS Xcode -p zip``).
3. A ``.pkg`` installer (using ``briefcase package macOS Xcode -p pkg``).

``.pkg`` is the *required* format for console apps. ``.dmg`` is the
default format for GUI apps.

Icon format
===========
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ briefcase = "briefcase.__main__:main"

[project.entry-points."briefcase.bootstraps"]
Toga = "briefcase.bootstraps.toga:TogaGuiBootstrap"
Console = "briefcase.bootstraps.console:ConsoleBootstrap"
PySide6 = "briefcase.bootstraps.pyside6:PySide6GuiBootstrap"
Pygame = "briefcase.bootstraps.pygame:PygameGuiBootstrap"

Expand Down
1 change: 1 addition & 0 deletions src/briefcase/bootstraps/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from briefcase.bootstraps.base import BaseGuiBootstrap # noqa: F401
from briefcase.bootstraps.console import ConsoleBootstrap # noqa: F401
from briefcase.bootstraps.pygame import PygameGuiBootstrap # noqa: F401
from briefcase.bootstraps.pyside6 import PySide6GuiBootstrap # noqa: F401
from briefcase.bootstraps.toga import TogaGuiBootstrap # noqa: F401
119 changes: 119 additions & 0 deletions src/briefcase/bootstraps/console.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
from briefcase.bootstraps.base import BaseGuiBootstrap


class ConsoleBootstrap(BaseGuiBootstrap):
display_name_annotation = "does not support iOS/Android/Web deployment"

def app_source(self):
return """\
def main():
# Your app logic goes here
print("Hello, World.")
"""

def app_start_source(self):
return """\
from {{ cookiecutter.module_name }}.app import main
if __name__ == "__main__":
main()
"""

def pyproject_table_briefcase_app_extra_content(self):
return """
console_app = true
requires = [
]
test_requires = [
{% if cookiecutter.test_framework == "pytest" %}
"pytest",
{% endif %}
]
"""

def pyproject_table_macOS(self):
return """\
universal_build = true
requires = [
]
"""

def pyproject_table_linux(self):
return """\
requires = [
]
"""

def pyproject_table_linux_system_debian(self):
return """\
system_requires = [
# Add any system packages needed at build the app here
]
system_runtime_requires = [
# Add any system packages needed at runtime here
]
"""

def pyproject_table_linux_system_rhel(self):
return """\
system_requires = [
# Add any system packages needed at build the app here
]
system_runtime_requires = [
# Add any system packages needed at runtime here
]
"""

def pyproject_table_linux_system_suse(self):
return """\
system_requires = [
# Add any system packages needed at build the app here
]
system_runtime_requires = [
# Add any system packages needed at runtime here
]
"""

def pyproject_table_linux_system_arch(self):
return """\
system_requires = [
# Add any system packages needed at build the app here
]
system_runtime_requires = [
# Add any system packages needed at runtime here
]
"""

def pyproject_table_linux_flatpak(self):
return """\
flatpak_runtime = "org.freedesktop.Platform"
flatpak_runtime_version = "23.08"
flatpak_sdk = "org.freedesktop.Sdk"
"""

def pyproject_table_windows(self):
return """\
requires = [
]
"""

def pyproject_table_iOS(self):
return """\
supported = false
"""

def pyproject_table_android(self):
return """\
supported = false
"""

def pyproject_table_web(self):
return """\
supported = false
"""
2 changes: 1 addition & 1 deletion src/briefcase/bootstraps/pygame.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@


class PygameGuiBootstrap(BaseGuiBootstrap):
display_name_annotation = "does not support iOS/Android deployment"
display_name_annotation = "does not support iOS/Android/Web deployment"

def app_source(self):
return """\
Expand Down
2 changes: 1 addition & 1 deletion src/briefcase/bootstraps/pyside6.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@


class PySide6GuiBootstrap(BaseGuiBootstrap):
display_name_annotation = "does not support iOS/Android deployment"
display_name_annotation = "does not support iOS/Android/Web deployment"

def app_source(self):
return """\
Expand Down
11 changes: 11 additions & 0 deletions src/briefcase/commands/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,17 @@ def binary_path(self, app) -> Path:
:param app: The app config
"""

def binary_executable_path(self, app) -> Path:
"""The path to the actual binary object for the app in the output format.
For most platforms, this will be the same as the binary path. However, for
platforms that use an "executable bundle" (e.g., macOS), this will be actual
binary that is embedded in the bundle.
:param app: The app config
"""
return self.binary_path(app)

def briefcase_toml(self, app: AppConfig) -> dict[str, ...]:
"""Load the ``briefcase.toml`` file provided by the app template.
Expand Down
72 changes: 46 additions & 26 deletions src/briefcase/commands/dev.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,33 +129,49 @@ def run_dev_app(
# Add in the environment settings to get Python in the state we want.
env.update(self.DEV_ENVIRONMENT)

app_popen = self.tools.subprocess.Popen(
[
# Do not add additional switches for sys.executable; see DEV_ENVIRONMENT
sys.executable,
"-c",
(
"import runpy, sys;"
"sys.path.pop(0);"
f"sys.argv.extend({passthrough!r});"
f'runpy.run_module("{main_module}", run_name="__main__", alter_sys=True)'
),
],
env=env,
encoding="UTF-8",
cwd=self.tools.home_path,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
bufsize=1,
)
cmdline = [
# Do not add additional switches for sys.executable; see DEV_ENVIRONMENT
sys.executable,
"-c",
(
"import runpy, sys;"
"sys.path.pop(0);"
f"sys.argv.extend({passthrough!r});"
f'runpy.run_module("{main_module}", run_name="__main__", alter_sys=True)'
),
]

# Start streaming logs for the app.
self._stream_app_logs(
app,
popen=app_popen,
test_mode=test_mode,
clean_output=False,
)
# Console apps must operate in non-streaming mode so that console input can
# be handled correctly. However, if we're in test mode, we *must* stream so
# that we can see the test exit sentinel
if app.console_app and not test_mode:
self.logger.info("=" * 75)
self.tools.subprocess.run(
cmdline,
env=env,
encoding="UTF-8",
cwd=self.tools.home_path,
bufsize=1,
stream_output=False,
)
else:
app_popen = self.tools.subprocess.Popen(
cmdline,
env=env,
encoding="UTF-8",
cwd=self.tools.home_path,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
bufsize=1,
)

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

def get_environment(self, app, test_mode: bool):
# Create a shell environment where PYTHONPATH points to the source
Expand All @@ -172,6 +188,10 @@ def get_environment(self, app, test_mode: bool):
if self.platform == "windows": # pragma: no branch
env["PYTHONMALLOC"] = "default" # pragma: no-cover-if-not-windows

# If we're in verbose mode, put BRIEFCASE_DEBUG into the environment
if self.logger.is_debug:
env["BRIEFCASE_DEBUG"] = "1"

return env

def __call__(
Expand Down
Loading

0 comments on commit 8e9a8f6

Please sign in to comment.