Exploring design for system executable targets in SwiftPM (GSoC 2026)

Hi everyone,

I’ve been exploring the idea of introducing a systemExecutable target type in SwiftPM, as described in the GSoC 2026 project ideas, to improve the user experience for SwiftPM plugins that depend on external tools.

From my understanding:

  • SwiftPM currently allows system library targets to specify providers (e.g. .brew, .apt).

  • Plugins can depend on executables built from source.

  • However, when a plugin depends on a tool installed externally (e.g. protoc, npm, clang-format), it must rely on absolute paths or manual PATH assumptions.

  • There is currently no built-in mechanism to:

    • Verify that such tools exist,

    • Emit structured diagnostics if missing,

    • Suggest installation commands.

My initial thought is to introduce something conceptually similar to SystemLibraryTarget, but for executables, for example:

.systemExecutable(
    name: "protoc",
    providers: [
        .brew(["protobuf"]),
        .apt(["protobuf-compiler"])
    ]
)

Plugins could then depend on this target, and SwiftPM would:

  1. Attempt to resolve the executable via PATH lookup.

  2. Emit a structured diagnostic if not found.

  3. Suggest install commands based on declared providers.

A few design questions I’d appreciate feedback on:

  • Should executable resolution happen during manifest evaluation, build planning, or plugin invocation?

  • Would it be better to model this as a new target type, or as an extension of existing plugin capabilities?

  • How should cross-platform differences (macOS/Linux/Windows) be handled?

  • Would supporting language-specific package managers (e.g. npm, pip) be in scope, or should this remain OS package-manager focused?

I’ve started reviewing how SystemLibraryTarget and plugin tool resolution are currently implemented, and I’d love guidance on whether this direction aligns with the intended scope of the project.

Would love any input from @cmcgee1024 or others familiar with SwiftPM plugin internals.

Thanks in advance for any feedback!

A stretch goal would be to extend this to SwiftPM's own internal lookup of various build tools, which is too ad-hoc for my taste (see ensuing discussion also). An audit of all the executables swift build is pulling in already and then running them all through some common lookup method would help too.

1 Like

Hi @Viditgupta-official,

Thanks for exploring this area. I hope that you find it interesting!

Something that I want to emphasize is that interface changes like this to critical pieces like SwiftPM require community feedback through an official pitch and proposal system. This GSoC project would be to develop a pitch and proposal to fully develop the idea so that it can be considered by the community. The success criteria is not necessarily to get it merged into the main branch as there is a risk that the community process may not accept it as-is.

I think it will be good for the pitch and proposal to showcase concrete examples that might resonate with Swift developers. When I drafted the idea I was thinking that various IDL (interface definition language) generator tools might be a good place to start. It would be good to show that the tools would otherwise be unavailable or resolved in ad-hoc or user unfriendly ways if apt/yum/npm/pip/etc. had no SwiftPM integration with the system executable mechanism. The user is left to their own devices to correct "command not found" errors.

Should executable resolution happen during manifest evaluation, build planning, or plugin invocation?

I expect that this design can follow the system library mechanism, which is done before build at the moment. This allows the user to get earliest possible feedback, and also allows it to work with the multiple build systems in place at the moment. They are called "native" and "swiftbuild" if you want to try switching between them with the "--build-system" option.

Would it be better to model this as a new target type, or as an extension of existing plugin capabilities?

I think that this will need to be an added as a new target type. It has different semantics than system libraries, executable (i.e. source code provided), library, and test/plugin/macro targets. It would be runnable (swift run), plugin targets can depend on them, and they shouldn't be linked with any products if they happen to depend on them.

How should cross-platform differences (macOS/Linux/Windows) be handled?

The expectation is that system executables behave in the same way across platforms, and that any platform-specific differences will be managed by the plugins when they assemble the command lines. I think that for many of the tools used in this way, they will often be platform neutral (e.g. written in javascript, python), or at least have a very uniform command line interface across OSes.

Would supporting language-specific package managers (e.g. npm, pip) be in scope, or should this remain OS package-manager focused?

I feel that this is an important addition for this proposal as it unlocks entire new worlds of tools and up-to-date revisions of those tools because system package managers are often lagging behind. I suggested adding npm and pip as they have some of the largest mature ecosystems of command-line tools. But, I would understand if you research these and you find that one or the other don't make much sense, or perhaps others (e.g. Go) might have a similarly large catalog of tools, or are more relevant for SwiftPM plugins.

Cheers,
Chris

1 Like

Hi Chris,

Thank you for the detailed guidance — this is incredibly helpful.

I’ll focus on developing concrete examples (IDL tools and similar plugin-driven workflows) to better illustrate the user experience gap, and align the design with the system library resolution mechanism so that executable validation happens before build.

I’ll also explore npm and pip integration in more depth and evaluate how they could fit cleanly into the providers model.

I’ll follow up with a refined pitch draft incorporating this direction.

Thanks again!

Hi all,

Based on the earlier discussion and feedback (especially around resolution timing and scope), I’ve refined the proposal for introducing a systemExecutable target type.

This draft clarifies:

  • Target graph semantics

  • Resolution timing (before build planning, aligned with systemLibrary)

  • Provider model (including npm and pip)

  • Explicit non-goals to keep scope controlled

I’d especially appreciate feedback on:

  1. Whether resolution before build planning is the correct integration point.

  2. Whether restricting dependencies to plugin targets is the right constraint.

  3. Whether PATH-only resolution is sufficient for the initial version.

  4. Whether including npm and pip providers is appropriate for initial scope.


Full draft below:

[Pitch] Add systemExecutable Target Type to SwiftPM

Introduction

This proposal introduces a new target type in Swift Package Manager (SwiftPM) named systemExecutable. This target enables packages to declaratively specify dependencies on externally installed command-line tools required by plugins.

The goal of this proposal is to improve developer experience, increase build determinism, and provide structured diagnostics for plugin-driven workflows that rely on external tooling.


Motivation

SwiftPM models Swift source dependencies declaratively in Package.swift, but external executable tools required by plugins remain implicit and unmanaged. This asymmetry creates friction in modern plugin-based workflows and weakens the declarative guarantees SwiftPM otherwise provides.

Common examples include:

  • Protocol Buffers (protoc) code generation

  • OpenAPI / Swagger generators

  • GraphQL schema compilers

  • IDL and code-generation tools distributed via npm or pip

Today, these tools are typically resolved via PATH lookup during plugin execution. If the executable is missing, users encounter a late-stage “command not found” error without structured guidance.

This results in:

  • Non-deterministic builds

  • Fragile CI pipelines

  • Poor onboarding experience

  • Repeated manual setup documentation

  • Ad-hoc PATH assumptions

SwiftPM provides structured dependency modelling for Swift code, but executable tool dependencies remain invisible and undeclared.


Proposed Solution

Introduce a new systemExecutable target type that allows packages to declaratively specify external executable dependencies.

Example manifest syntax:

.systemExecutable(
    name: "protoc",
    providers: [
        .brew(["protobuf"]),
        .apt(["protobuf-compiler"]),
        .npm(["@bufbuild/protoc"])
    ]
)

Plugin targets may depend on these targets:

.plugin(
    name: "GenerateProtos",
    capability: .buildTool(),
    dependencies: ["protoc"]
)

Detailed Design

Target Graph Semantics

A systemExecutable target:

  • Does not produce artifacts

  • Does not participate in linking

  • Exists solely to validate the presence of an external executable

Only plugin targets may depend on systemExecutable targets.

If a non-plugin target depends on one, SwiftPM should emit a manifest error.

This models executable tools as declarative, pre-build dependencies rather than runtime assumptions.


Resolution Timing

Executable resolution occurs before the build phase, during target graph construction or build planning. This mirrors the behaviour of systemLibrary targets.

Resolution happens prior to plugin invocation to ensure early, deterministic failure.


Resolution Algorithm

For each declared systemExecutable target:

  1. SwiftPM attempts to locate the executable using standard PATH resolution.

  2. If the executable is found, resolution succeeds.

  3. If the executable is not found, SwiftPM emits a structured diagnostic including installation suggestions derived from declared providers.

  4. The build process is halted prior to plugin invocation.

This proposal does not introduce:

  • Automatic installation

  • Version validation

  • Custom resolution strategies


Provider Model

Providers extend the existing system library provider concept to include both OS-level and language-specific ecosystems:

  • .brew([String])

  • .apt([String])

  • .yum([String])

  • .npm([String])

  • .pip([String])

Providers are advisory only and are used solely to generate installation suggestions in diagnostics.


Cross-Platform Behaviour

systemExecutable targets are platform-neutral.

SwiftPM is responsible only for validating executable presence. Platform-specific command-line differences remain the responsibility of the plugin implementation.

Provider suggestions may vary by operating system.


User Experience Comparison

Aspect Current Behaviour With systemExecutable
Tool Declaration Implicit Declarative in Package.swift
Resolution Timing During plugin execution Before build planning
Failure Mode Generic “command not found” Structured diagnostic with install suggestions
CI Reliability Environment-dependent Deterministic validation
Onboarding Manual setup documentation Guided installation suggestions

This elevates external executable tools to first-class declarative dependencies.


Impact on Existing Packages

This proposal is fully additive and does not modify the behavior of existing packages.

Packages that do not declare systemExecutable targets are unaffected.

Plugins currently relying on implicit PATH resolution may optionally migrate to systemExecutable targets for improved diagnostics.


Alternatives Considered

Status Quo (PATH-only Resolution)

Rejected because:

  • Failures occur late

  • Diagnostics lack structure

  • CI behaviour becomes non-deterministic

  • External tool dependencies remain invisible

Extending Plugin Targets Directly

Rejected because:

  • Executable dependencies have distinct semantics

  • A dedicated target type better aligns with systemLibrary precedent

Automatic Tool Installation

Rejected due to:

  • Security concerns

  • Scope expansion

  • Ecosystem complexity

Version Management

Rejected for initial scope due to:

  • Ecosystem-specific complexity

  • Significant design expansion


Security Considerations

This proposal does not execute, download, or install external software.

It only validates executable presence and emits advisory diagnostics.

SwiftPM does not modify system state or elevate privileges. Existing trust boundaries remain unchanged.


Future Directions

Possible future enhancements include:

  • Version validation

  • Explicit path configuration

  • Swift SDK bundle integration

  • Additional provider ecosystems

These are intentionally out of scope for the initial proposal.


Implementation Overview

Implementation will require:

  • Adding a new systemExecutable target type to the manifest model

  • Extending target graph construction to validate executable presence before build

  • Implementing PATH-based lookup logic

  • Extending provider enums for additional ecosystems

  • Emitting structured diagnostics consistent with systemLibrary targets

  • Adding test coverage across both build systems

No changes to the Swift compiler are required.


Questions for Feedback

I would especially appreciate feedback on:

  1. Whether resolution timing before build planning aligns with SwiftPM architecture expectations.

  2. Whether restricting dependencies to plugin targets is the right constraint.

  3. Whether supporting npm and pip providers is appropriate for the initial scope.

  4. Whether PATH-only resolution is sufficient for v1.

Thank you for taking the time to review this proposal.


3 Likes

@Viditgupta-official overall this looks good.

It's good that you've found a concrete example with protoc. It is capable of generating C++ code, and so it should be possible to create a concrete example in the final proposal involving a C++ target. This should be confirmed that this can work. Another example or two will be good to reinforce the new capability.

I think that the npm package in the example doesn't have a protoc command in it. There might be another one that does, such as this one: https://www.npmjs.com/package/protoc

Whether resolution timing before build planning aligns with SwiftPM architecture expectations.

I believe that this aligns well given the fact that there are two major build systems at the moment in SwiftPM.

Whether restricting dependencies to plugin targets is the right constraint.

Yes, I believe that this is the right choice at the moment. There are other types of target dependencies that aren't blocked right now with results that surprise users. It will be good to restrict to the intended workflow at the beginning, and it can be relaxed later as more use cases are discovered.

Whether supporting npm and pip providers is appropriate for the initial scope.

I think that this is crucial for this proposal. These are two places where a significant number of tools can be found. They are user-level and package level package managers like SwiftPM, so these can achieve even better integration level over time.

For example, the user could be prompted to npm install -g <pkg> and SwiftPM could go ahead and do that on behalf of the user without escalating privilege to root. If there is a package.json or requirements.txt, then SwiftPM can install the tool in the local package's scope and add the npm/pip local tool paths to the PATH to include the tools as necessary. There would be no interference at all with other packages outside of the current working directory (and ancestors), so no impact to other projects the user is managing.

Note that this deeper integration can be a stretch goal for this project. Getting the basic warning mechanism in-place with recommended commands for the user to run is still the primary goal.

Whether PATH-only resolution is sufficient for v1.

Yes, I think that PATH is the most general way that tools can be combined together into a single lookup mechanism. The build system can make use of it. SwiftPM can do a level of checking with guidance if things are missing.

Cheers,
Chris

2 Likes

Thanks for the detailed feedback!

I’ll update the example to use the npm protoc package you mentioned and verify that a concrete example involving C++ code generation works correctly. I’ll also add another example to reinforce the capability.

I agree that deeper npm/pip integration could be an interesting stretch goal, but for the initial scope I’ll focus on the PATH-based resolution and warning mechanism with install guidance.

Thanks again for the guidance!

1 Like