Be able to supply values such as "-DBUILD_DATE=21/12/1953” to Swift compiler

Could these use the $ prefix in Swift code, like in the REPL?

print(“Build number \($BUILD_NUMBER), built on \($BUILD_DATE)”)
Welcome to Apple Swift version 4.2 (swiftlang-1000.11.37.1 clang-1000.11.45.1).
  1> 6
$R0: Int = 6
  2> 7
$R1: Int = 7
  3> $R0 * $R1
$R2: Int = 42
  4> 

Edit: I guess that might be confusing, since we already use #if DEBUG rather than #if $DEBUG.

Edit: Allowing -DTOLERANCE=0.9 will lead to requests for #if TOLERANCE < 1.0 and so on.

If we wanted something to distinguish them from ordinary variables, I'd favor something like #var(BUILD_NUMBER) (with the "var" part to be bikeshedded). But flat-out defining a fileprivate let constant isn't the worst idea, I suppose.

1 Like

@Chris_Lattner3, sounds like there are enough degrees of freedom to be debated on this pitch it should go through the proposal process rather than just as a bug fix. Do you agree?

@johnno1962 I've read the latest draft of your proposal at apple/swift-evolution#917.

I don't like the proposed solution, which treats -DNUMBER=10 as true within an #if NUMBER condition. I think Swift previously had some implicit conversions to Bool but they were removed. Although you could argue that compilation directives are different from normal Swift code.

Could this feature be more like:

  • the special literals such as #file and #line;
  • or the playground literals such as #colorLiteral?

For example, a compiler flag other than -D could be used:

  • either to allow #tolerance and other custom literals;
  • or #userLiteral("tolerance") where only the string argument is user-defined.

The implementation uses strtoll and strtod to validate numeric literals, which won't recognize binary 0b or octal 0o prefixes, or _ underscores used as separators. Should multiline and raw string literals be allowed? Should interpolated string literals be disallowed? Is it possible to inject arbitrary code via the compiler flag?

@jrose Could the package manager's compelling use cases be added to the Motivation section?

The current motivation (to embed information in the binary) might be solved in a different way. For example:

1 Like

Thanks for the comments @benrimmington, I’m still developing the idea but thought I’d get in the queue.

I don’t have a problem with this rule as it allows you to code guarding for the absence of the flag such as:

#if NUMBER
let number = NUMBER
#else
let number = 42
#endif

Whether compilation flags should be in the variable space or the #identifier space is up for debate. I had thought they would be better with a #prefix for a while to distinguish them. The code above made me opt for consistency with usage in #conditionals. You could go to the lengths of #userLiteral("tolerance”) though that wouldn’t be my choice.

Why not re-task the -D flag? It’s precedented and well understood.

I guess if the model is “as-if typed at that position in the source” this should work. I’ve fixed this in the implementation.

Everything is possible if it’s felt it’s a requirement except string interpolation is not allowed and prints a warning (Edit: though it can be made to work :scream:).

The code guarding is a good point, but it would look better as #if isDefined(NUMBER). Alternatively, you could have #userLiteral("NUMBER", default: 42), similar to Dictionary.subscript(_:default:).

Reusing the -D flag might be the right choice, if the proposal uses let number = NUMBER. Otherwise, custom #lowerCamelCase identifiers (see accepted syntax for SE-0034) or a #userLiteral(_:default:) are different enough to justify a new compiler flag.

I agree with you, @benrimmington. I think to use the flag values in the actual Swift code (not just as a precompiler macro #if thing) they should be accessed via some literal identifier.

I think the spellings #flag() or #flagValue() are pretty descriptive, since they are "values passed in by a flag" and there's nothing in that namespace yet. I can't imagine what else might be called that.

I like the idea of having the option to provide a default value to skirt around complier errors.

I could even see allowing today's -D flags to be accessed as boolean values this way. Basically a shorthand for this…

#if MY_FLAG
let myFlag = true
#else
let myFlag = false
#endif

Which doesn't do much other than let you use flags more conveniently in places like function parameters and large expressions.

But to be entirely honest, I am not totally sold this proposal yet. My (mildly self-serving) reason: using compiler flags only lets you supply very simple data — nothing structured. I think my proposed data/string literal would be more broadly applicable and fill in this "hole" as well.

OK, I’m coming around to the Swiftier and more explicit syntax,

It’ll be more work but by no means impossible and raises a couple of other possibilities not least the default and better error reporting.

The value of the ”NUMBER" command line flag could now be sub-parsed as an expression “as if typed at that point” rather than just a literal, Would this be considered a feature or a bug? - (and it should be constrained to literals only)

Looking at the #string/dataLiteral proposal, there is definitely a use case to embed resources in a Linux binary (for example), but there is a bit of a flaw in that the swift compiler does not have a model for a project’s layout so would not be able to find ”long_query.sql” on it’s own.

Perhaps this proposal could be extended so that if the name of the parameter was prefixed by @ it would parse and inject the file pointed to by the parameter’s value rather than the value itself:

let sql = #userLiteral(“@LONG_SQL_FILE")

A decision could be made as to whether such a file referenced by a parameter would be any literal (or perhaps expression) in Swift syntax or always the body of a raw string with encoding utf-8.

Thinking out loud, there isn’t currently a Data literal in the compiler but the following code could be used for base64 encoded resources with only a slight size penalty and run time overhead.

let image = Data(base64Encoded: #userLiteral(“@IMAGE_BASE64_FILE"))

Before changing your proposal or implementation, would the package manager's use cases be of interest?

I think the values should be constrained to numeric and string literals only. For comparison, the clang importer will ignore complex #define macros; although perhaps it should use the constexpr rules from C++ if possible?

For the other proposal, could the compiler be given a resources search path? Xcode already has HEADER_SEARCH_PATHS, etc.

@jrose, do you have any input on how a proposal like this might have been useful for SwiftPM?

I agree, though I’d also include booleans.

A SwiftPM person would be better to answer this. @Aciid?

There are currently two cases where a feature like this will be useful for SwiftPM:

  • The first one is embedding the version number for SwiftPM executables at build time. The current workaround for this requires going though a C target so we can use -DNAME=VALUE but that is very awkward to do.

  • The second case is for conditionally compiling some code to produce the PackageDescription runtime libraries. Currently, SwiftPM vends two runtime libraries v4 and v4.2. There is minimal difference in the APIs so they're produced from the same source code with a little bit of conditional compiling by passing -DPACKAGE_DESCRIPTION_<version> during the build (example). This works fine but it would be great if we can pass -DPACKAGE_DESCRIPTION=<version> instead and do conditional checks like this:

struct Foo {
  #if PACKAGE_DESCRIPTION == "4"
    let foo: Int
  #elseif PACKAGE_DESCRIPTION == "4.2"
    let foo: String
  #else
    let foo: Bool
  #endif
}
2 Likes

This proposal would help you out with the first case. The second case is a little more problematic as it might be #if #userLiteral(“PACKAGE_DESCRIPTION”) == “4” which wouldn’t be all bad or we could make an exception that the longhand is not required for #conditionals if all values were set using -D. Perhaps constexpr can help us out with == and other operators in the longer term.

One gripe I have with the C way of passing something to the compiler and then just being able to use that anywhere is the implicitness. You can't really know what defines you can pass without good docs or combing through the code, hoping people at least adhere to naming conventions.

Something like #userLiteral would help, but just to explore a different direction, and there may be good reasons this might not even be possible: How about allowing an annotation on let (only those lets in static contexts, not instance members or sth like that), let's call it @buildParameter for now, that allows you to declare and potentially provide a default value for a constant that you can then use like a normal constant (plus in #if etc. through constexpr, I guess).

You could pass values for these constants via some flag (-P apiVersion="lol", -P enableExperimentalApi=true, with anything that does not match the type of the constant leading to a compile-time error. It would be cool to be able to do things like pass a set of flags, or use enums, but I'm not sure how feasible that is atm. Might be a nice future direction when constexpr becomes sufficiently capable.

Examples:

// no default value, won't compile without passing something
@buildParameter let apiVersion: String
// default value, maybe we could just do `@buildParameter let foo: Bool = false`,
// but that might be weird because it looks like foo would always be false
@buildParameter(default: false) let enableExperimentalApi: Bool
3 Likes

I’ve pushed a new version of the proposal absorbing some of the recent discussion and ideas. The #flag identifier is still up for bike shedding and I’ve opted not to add a separate command line option to the compiler in the interests of simplicity.

I’ve completed the implementation which is available as a toolchain and updated the proposal.

Here is the current version of the introduction:

Introduction

At present, the Swift compiler frontend is able to accept "custom compiler flags" on invocation using the -DFLAG_NAME option. These flags are currently used only in #if conditionals and it is not possible to supply values. This proposal puts forward a simple implementation where values can be supplied using the established -DFLAG_NAME=VALUE convention where VALUE can be integer, float, string or boolean literals. To support this, comparison operators ==, !=, > etc. are added to the implementation of the conditional compilation directive and the following are now valid statements.

#if TOLERANCE > 0.9
#if THRESHOLD != 0.0
#if VERSION == "4.2" && TOLERANCE

When the compiler is invoked using these options, a new construct #flagValue("FLAG_NAME"[, default: <expression>]) can be used in Swift expressions to refer to the literal value of the option as if it had been typed at that position in the source. As an example, it would be possible to invoke the compiler as follows:

$ swift -DBUILD_NUMBER=1234 -DTOLERANCE=0.9 -DBUILD_DATE='"Sun 23 Sep 2018"’ file.swift

One can then refer to these variables from the code:

print("Build number \(#flagValue("BUILD_NUMBER")), built on \(#flagValue("BUILD_DATE"))")

Finally, as a refinement for embedding larger resources in executables, if the FLAG_NAME argument (or the option's value) starts with an @ the value of the flag will be used as the path to a file which contains utf-8 encoded string data that will be read in during compilation and used the body of a raw string literal as if the file was read in. For example:

let sql = #flagValue(“@LONG_SQL_FILE")

For a binary resource you could use:

let image = Data(base64Encoded: #flagValue(“@IMAGE_BASE64_FILE"))

These simple changes combine to provide a powerful bridge between the build system/command line and the Swift language.

6 Likes

I’ve just come across this pitch and I am totally in favor. I’ve recently encountered this need in to get the name of the target that is being compiled. Previous Objective-C code used this approach in our codebase and since Swift doesn’t have this feature at the moment, some workarounds were necessary. Is there a concrete plan to start the review process on the proposal?

Anybody knows about status of this proposal?

Still queued up for review as far as I know which is a shame as it's probably the least contentious of the three outstanding proposals I highlighted a year ago.

1 Like

This proposal is old. But it seems very useful. How about discuss about this proposal again?

1 Like