Draft Proposal: Target Specific Build Settings

@gwendal.roue Thanks for your feedback! I appreciate you bringing up these points. I do think that the PackageDescription API will evolve into an ergonomic DSL but I don't think this proposal actually brings in that complexity. This proposal purposely keeps the complexity in conditionals at minimum in order to keep the future implementation options open while providing a high value for the package authors.

1 Like

The .when seems unnecessary at that point, because we don't have any other option there. I think we should go with:

.define("FOO", configuration: .release)

1 Like

I think there is still value in .when:

  • It greatly reduces the maintaince burden of keeping every build setting in sync if the options are expanded
  • .when makes it clear that it’s a condition
1 Like

One thing I wonder about is whether it's necessary/desirable to have to list the settings per-tool.

I experimented a bit with a system like this, and came to the conclusion that it would be better to list the settings in a tool-neutral way, and then have a per-tool mapping layer which translates them into actual flags. This mapping layer could be provided by the tool, independent of any given package spec.

Having a fixed set of tools built in to the spec feels limiting.

We might also want to be able to specify the same define or a search path for multiple tools? Having to repeat it seems messy.

I think it might be more flexible to just have a clearly defined set of semantic settings, and let the tools "interpret" them. For now that interpretation could well be hard-coded inside spm, but the design would allow for more flexibility later.

You have to know to type .when( though before you get any autocompletion vs. getting the possible completions upfront when adding a setting. That seems like a bad user experience to me when there's only one option to pick.

I was actually going to suggest that we look at this syntax for consistency :slight_smile:

That's a fair point but you would still see the when condition type in the autocompletion results. It's a two-step process vs one-step learning curve but I think that's fine given the advantages of factoring the conditions into its own type and the consistency with other APIs.

What about

.define(..., when: ...)

This allows us to retain a single type to represent conditionality, while also ensuring it appears in the overload sets.

2 Likes

The proposal is to support conditionalization (is that even a word?) on platform and configuration.

On the face of it, things like platform and configuration are orthogonal, so our instinct is to make each one a separate parameter. In practice though, I think it would be easy enough to come up with a single set of flags that don't clash with each other.

Similar to my previous point about settings, I wonder if it's necessary therefore to actually fix on just those two.

Could we instead filter settings based on a single set of textual flags, giving more scope for customization or other uses?

The build process could inject some of these flags automatically - eg "release"/"debug" based on configuration, and "macOS", "iOS" etc based on platform, but the user could also inject other flags when invoking spm.

So you'd end up with something more like: .define("FOO", if: ["release"]).

Or, combining flags: .define("FOO", if: ["debug", "iOS"]).

You could extend this to tools too (if the settings were tool-neutral, as per my other comment!): .define("FOO", if: ["release", "c++"]).

As mentioned, this would also allow checking for user-specified conditions: .define("FOO", if: ["release", "internal-build"]).

What is the syntax in this case?

.define(..., when: .when(configuration: .release)) 
// or
.define(..., when: .condition(configuration: .release))

Note that arguments without label do appear in the autocompletion results so I still prefer .define(.., .when()).

The build settings that are proposed are tool specific. Things like deployment target are indeed tool neutral and are handled outside of tool specific build settings (see: SE-0236: Package Manager Platform Deployment Settings). A more flexible syntax for conditionals is out-of-scope for this proposal and will be explored with a future proposal.

Up to a point. Something like define works (to some extent) across multiple tools, and is probably harmless for any tool that doesn't understand the concept.

Something like headerSearchPath is clearly specific to languages that have headers - so it depends a bit whether you make a distinction between c, obj-c and c++ for the purpose of specifying it.

Something like frameworkSearchPath would actually be tool-neutral and could apply to all compiler tools as well as the linker.

I can see why the proposal is going for a pragmatic fixed set of tools, but fixed sets make me nervous. I'm just arguing, in an honorable tradition, for a bit more abstraction and another level of indirection :wink:

Thanks, yeah, I've seen the deployment settings proposal.

Deployment happened to be an example in the link I gave, but I can imagine many such settings.

I can see why the package manager wants to have a deep enough understanding of some of them to be able to reason about them, and that may be a decent excuse to hard-code knowledge of some settings into the manifest format.

I don't think it's going to be viable to do that for all useful settings though - unless you have an extensibility mechanism that allows individual tools to supply knowledge to spm.

That’s highly problematic; it leaves no ability to conditionally link something in the simulator vs device build.

Thanks for the feedback. The points you raised are all valid and SwiftPM will probably solve them with a future generic build settings model (and maybe extensible build tools for custom tools). This proposal is very focused towards compiler/linker specific arguments that come up very often. Think of this proposal as a way of bringing the current command-line -X* options to the manifest file but without chances of disrupting the build model due to arbitrary unsafe flags.

I wouldn't rush to classify this as highly problematic. It's just the first version of the API which can be extended. I am not sure if there are a lot of system libraries which can't be autolinked and require conditional linking on device vs simulator.

It seems like adding environment as an option would align with the targetEnvironment conditional compilation flag:

.when(platforms: [.iOS], environments: [.simulator], configuration: [.debug])

Is there an argument against doing it from the beginning?

I'd say it's the same argument as always: scope creep. The more we try to do in one go, the longer it takes to ship things and the harder it gets to evaluate the proposal.

In this case, it's not clear to me what "environment" means. Simulator vs device, sure, but does this generalise to anything else? How does "environment" interact with other concerns like "target architecture"?

2 Likes

I agree. I really want to keep this proposal super focused and avoid any feature creep, especially when we can easily extend the proposed API. Otherwise, we will miss any chance of getting this in Swift 5 timeframe.

1 Like

Overall design aside, I think it would be prudent to avoid using the unsafe prefix here for something unrelated to memory safety, which is how the term is used in Swift itself.

There are many situations where users have termed things "unsafe" when they mean "not recommended" or "undesirable"; I'm sure there are many other words that can carry the sense you're going for here.