Skip to content

Comments

Add plugin interface for publication channels (#2687)#2701

Open
filiplajszczak wants to merge 1 commit intobeeware:mainfrom
filiplajszczak:2687-publication-channels-plugin-interface
Open

Add plugin interface for publication channels (#2687)#2701
filiplajszczak wants to merge 1 commit intobeeware:mainfrom
filiplajszczak:2687-publication-channels-plugin-interface

Conversation

@filiplajszczak
Copy link
Contributor

Introduce a BasePublicationChannel ABC and PublishCommandAPI protocol for publishing apps to distribution channels via briefcase publish, using dynamic discovery through entry points following the existing pattern for platforms and debuggers.

Publication channel plugins:

  • Register scoped by platform and format via entry points (e.g., briefcase.publication_channels.ios.xcode)
  • Implement publish_app(app, command, **options) to perform publication
  • Single installed channel auto-selects; multiple require --channel

Changes to PublishCommand:

  • Dynamic channel discovery via importlib.metadata.entry_points()
  • Auto-chain: publish triggers package if distribution artefact is missing
  • Channel classes are instantiated before use
  • Remove hardcoded s3 default and iOS/web publish_app stubs

Add placeholder channels for iOS App Store (#2697) and Google Play Store (#2698) that raise BriefcaseCommandError when invoked.

Document the publication channel plugin interface in the plugins reference and link the publish command to it. Update publish command reference to reflect the new --channel flag behavior.

Consideration: Mock(spec=...) for test doubles

In this PR I followed the project's established pattern of hand-rolled dummy subclasses, which work well and are readable. But as the plugin interfaces grow (platforms, debuggers, publication channels all share the entry point →
class → instance pipeline), it might be worth considering Mock(spec=BasePublicationChannel) as an alternative. The spec argument enforces interface parity automatically, catching mismatches between the test double and the real class without manual upkeep. pytest-mock and its mocker fixture make this particularly convenient in pytest.
My ramblings on using Mock(spec=...) for test doubles.

PR Checklist:

  • All new features have been tested
  • All new features have been documented
  • I have read the CONTRIBUTING.md file
  • I will abide by the code of conduct

Copy link
Member

@freakboy3742 freakboy3742 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is awesome - and very near flawless. I've flagged a couple of things inline - one cleanup for coverage, and one edge case that wouldn't be obvious unless you're familiar with all the edge cases of Briefcase usage.

The only other thing that stood out is a bikeshed thing that didn't become obvious until I saw all the code ... could we replace publication_channels with the simpler channels in the module name? publication_channels is a bit long and unwieldy as a name, and we're not likely to have some other concept of channel that requires the clarification (if for no other reason than -c/--channel would be ambiguous). I think it makes sense to keep the full BasePublicationChannel name - but the package doesn't need to be as long, IMHO.


# Confirm host compatibility, that all required tools are available,
# and that all app configurations are finalized.
self.finalize(apps=self.apps.values())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A Briefcase project can contain multiple apps; this will always publish all apps. This is effectively the same problem as we saw with #2635, fixed in #2651 - the fix will be almost identical here.

tools: ToolCache
dist_path: Path

def distribution_path(self, app: AppConfig) -> Path: ... # pragma: no cover
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get why this #pragma is needed... rather than adding it to the specific method, could we add this as a global coverage exclusion on @runtime_checkable, as no runtime protocol will ever be covered by tests.

@freakboy3742
Copy link
Member

Regarding the specific question about mock usage: the existing pattern that you’ve observed/followed is mostly born out of the fact that a simple mock by itself doesn’t give us enough tracking - or, at least, if we did use a mock, we’d end up with a mock that is almost as complex as the Dummy that we’ve implemented, because there needs to be side effects to some function calls etc.

That said - there are places in the code where we do use mocks; and for “simple interface adherence”, a mock might well be a better approach. Given that a Channel plugin is a relatively simple interface with a single method and some properties, it might be worth using them here. If you’re up for the experiment, then I wouldn’t oppose using them here (or in a follow up).

@filiplajszczak filiplajszczak force-pushed the 2687-publication-channels-plugin-interface branch from b26872f to 6cba735 Compare February 19, 2026 09:46
@filiplajszczak
Copy link
Contributor Author

Thanks for the review! Both items addressed:

  • Added the -a/--app flag to publish, following the same pattern as the other commands. I also extracted the app resolution logic into a BaseCommand.resolve_apps() helper and migrated create, update, build, package, and open to use it as the pattern was duplicated in all five.
  • Added @runtime_checkable to the global exclude_lines and removed the per-method # pragma: no cover.

On naming: I was torn between channel and publication_channel. Went with publication_channel since it's less generic and more descriptive. Those are names you read far more often than you type. The shorter channel is used for the CLI flag and local variables where the context already makes the meaning clear.

On Mock(spec=...): I'll experiment with that in a separate PR once this one is wrapped up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants