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

Hi,

I’ve encountered a couple of situations where I’d like to have been able to provide values from the command line/build system and be able to access then in code for example, a build date or version number.

The obvious thing to do is pick up the convention of -DNAME=VALUE been i’ve not able to use what were preprocessing parameters from source code. The implementation I have in mind would be a virtual global variable in all source compiled with the option with the name specified that would always be a String. In terms of implementation it would likely be an alias to some form of literal rather than generate symbols.

Is there any interest in this idea?

John

1 Like

That’s a good idea. Now I sometimes use environment variables to achieve a distinct but similar purpose.

I’d like to try to move this pitch forward if possible if the implementation is not too contentious.

For me, it’s an omission that you’re not allowed to specify values for Swift compilation flags:

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

import Foundation

print(“Build number \(BUILD_NUMBER), built on \(BUILD_DATE)”)

An implementation is ready where these parameters would be string/int/float literals used as you would a global variable which are substituted into the source as if the user had typed them at that point. This is a bit of a departure for Swift as these are in effect aliases for which there is no existing precedent in the language but for this one case I think they should be allowed to bridge Swift with the command line world.

How does this sound?

2 Likes

/cc @jrose

I'm not sure it should be the same mechanism as -D, and I'm leery about making build products depend more on invocation arguments, but Ankit has shown me some compelling use cases in the past and the workarounds the package manager has had to use, so if you're going to drive the feature then great!

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.

Terms of Service

Privacy Policy

Cookie Policy