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

Add support for console apps #1781

Merged
merged 26 commits into from
Jun 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
f1924d7
Add changenote.
freakboy3742 May 6, 2024
8031f94
Add an option to configure console apps.
freakboy3742 May 7, 2024
5b730bc
Disable log streaming when running console apps.
freakboy3742 May 7, 2024
f42099d
Add a wrapper class for describing macOS signing identities.
freakboy3742 May 7, 2024
1445a4f
Switch packaging code to use SigningIdentity.
freakboy3742 May 7, 2024
c28f51e
Initial implementation of .pkg format generation.
freakboy3742 May 7, 2024
b55182b
Add removal changenote for macOS app format.
freakboy3742 May 7, 2024
480e5a0
Restructure packaging tests to make format-specific test modules.
freakboy3742 May 8, 2024
000f312
Make the default packaging format for macOS dependent on the app type.
freakboy3742 May 8, 2024
a6b5f8a
Pass BRIEFCASE_DEBUG to the runtime environment if in verbose mode.
freakboy3742 May 8, 2024
9946967
Tweaked the execution of console apps.
freakboy3742 May 9, 2024
b84f2d8
Add tests for pkg building.
freakboy3742 May 9, 2024
bb1dabf
Improve spacing in multiline comments.
freakboy3742 May 9, 2024
5b6eb74
Correct handling of flatpak debug mode.
freakboy3742 May 10, 2024
0f3a490
Add a UUID5 template filter to generate GUIDs for Windows.
freakboy3742 May 10, 2024
03ce00d
Use a different executable name for Windows console apps.
freakboy3742 May 10, 2024
4715144
Rename/strip the stub binary as part of the build step.
freakboy3742 May 11, 2024
4f38437
Rename the windows stub binary in the build step.
freakboy3742 May 11, 2024
9969b4b
Add bootstrap and automation bootstrap for console apps.
freakboy3742 May 11, 2024
96eb010
Merge branch 'main' into cmdline-app
freakboy3742 May 27, 2024
aff0c97
Apply suggestions from code review
freakboy3742 May 30, 2024
b402e32
Clarify the effect of enabling console_app
freakboy3742 May 30, 2024
77fc7ed
Merge remote-tracking branch 'origin/main' into cmdline-app
mhsmith Jun 2, 2024
ee6eebd
Normalize the Xcode docs.
freakboy3742 Jun 3, 2024
232f902
Merge branch 'main' into cmdline-app
freakboy3742 Jun 4, 2024
9c0f09c
Ensure return status of subprocesses is checked.
freakboy3742 Jun 4, 2024
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
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:
mhsmith marked this conversation as resolved.
Show resolved Hide resolved

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
Loading