Pitch: Ability to declare executable targets in SwiftPM manifests (to support @main)

I'd like to add support for @main in Swift Packages, and would be interested in hearing thoughts about the following approach.


The Swift Package Manager doesn’t currently provide a way for a package manifest to declare that a target is the main module of an executable. Instead, SwiftPM infers this by looking for a source file named main.swift (or main.c , main.cpp , etc) in the source directory of the target.

Knowing unambiguously whether or not a target is intended to be executable is important, because it affects the flags that are passed to the compiler at build time. It also affects the quality of diagnostics, such as detecting product declarations that mistakenly include multiple or no executable targets in an executable product.

Relying on specially named source files also doesn’t work when using @main to specify the entry point of an executable. In addition, there are ergonomic problems with using specially named source files (e.g. SR-1379) that would be addressed by being able to explicitly declare a target as being executable in the manifest.

Proposal

The most straightforward approach would be allow a target to be marked as executable in the manifest. This could take the form of either a parameter to the target type or a new target type. There is already an established pattern of using the type itself to denote the kind of target being declared (e.g. testTarget as a specialization of target ), so the most natural approach seems to be to add a new executableTarget type for this purpose.

Using a separate target type in the manifest would also support any future differences between the parameters for an executable target and a library target.

Details

This proposal introduces a new target type as a peer of target , testTarget , systemTarget , and binaryTarget . The new target type would have the same parameters as the target and testTarget types.

Compiler flags

The Swift Package Manager is already passing -parse-as-library to targets that are not marked as executable, and doesn’t pass that flag to targets that are. Since the Swift compiler still treats the source file name main.swift as special, SwiftPM may need to pass additional flags if a target has both a main.swift file that does not contain @main but does contain a differently named source file that does contain it.

Backward compatibility

The executableTarget type would only be available for package manifests that specify a tools version newer that 5.3. Package manifests specifying tools versions 5.3 or older would behave exactly as before.

Alternatives Considered

Adding a parameter to the target type

Another approach would be to add another parameter to the existing target type. That type already has many parameters, however, and it seems ill advised to add more. Using a separate target type in the manifest would also support any future differences between the parameters for an executable target and a library target.

Scanning source files for @main

Another approach would be to scan source files for occurrences of @main , and to use that to infer whether a target is an executable target. This seems even more subtle than basing the detection of executable targets on a filename, however, making any mistakes in package authoring even harder to detect.

17 Likes

It sounds like the right thing to do.

I’m in favour of this. While we’re here can we improve the diagnostics around having a test target depend on an executable one? This is the way the default package produced by swift package init --type=executable works and it leads to utterly baffling linker errors, instead of a nice clear error that says “executable targets cannot be tested, consider breaking your logic into a library target instead”.

Ideally we’d also fix the default package structure but I don’t want to attach too much unrelated work to this!

5 Likes

I agree that adding this as a new target type seems like the right call. It's pretty hard to discover implicit target setup requirements like the main.swift naming convention if you're not copying directly from the swift package init template, and I've always found it a bit strange that there was a distinction between regular targets and test targets, but not library and executable ones. I think this change would be worth it even if it didn't also enable @main usage!

3 Likes

What I'd like to see to this end is to actually enable testing of executable targets directly

2 Likes

I think that’s great, but I don’t think it should prevent us improving the status quo today. If we’re going to continue for another release with this situation, we should provide a good error message, instead of a baffling one. The good error message can even suggest that this is a temporary limitation instead of a permanent one, but having a clear error is vastly preferable to an opaque one.

2 Likes

Absolutely agreed about the diagnostics. The case you mention is already fixable in SwiftPM, but additionally, a lot of the suboptimal error messages are because SwiftPM has no reliable way to know whether the author intended the target as an executable, and so the resulting error message is confusing.

I fully support this change and look forward to trying it in action. Enabling @main in SwiftPM targets would be amazing!