Hi,
I have a little tool for which I want to automatically set the version information at compile time. I want to run a mercurial command to obtain the version information, place it in a constant variable that can be printed at runtime.
I was able to do what I want by using staticExec as follows:
const version_info = staticExec("hg log -r . --template \"{latesttag}+{latesttagdistance} ({node}|short})\"")
This works great. However I want to put a little more logic to handle some edge cases (such as checking whether there is no tag, in which case I just want to show the revision id).
The problem started when I tried to move that code into its own function as follows:
proc get_version(): string =
result = staticExec("hg log -r . --template \"{latesttag}+{latesttagdistance} ({node}|short})\"")
const version_info = get_version()
I expected that since version_info is a const nim would be able to evaluate get_version at compile time. Instead I got the following error:
Error: 'staticExec' can only be used in compile-time context
This is probably obvious, but what am I doing wrong?
Thanks!
To better explain that answer, you have to add a {.compiletime.} pragma to procs which you want to run at compile time.
proc getVersion(): string {.compiletime.} =
result = staticExec("hg log -r . --template \"{latesttag}+{latesttagdistance} ({node}|short})\"")
const versionInfo = getVersion()
Thanks for the pointer. I did not know about that pragma. It works for the following simple case:
proc get_version(): string {.compileTime.} =
result = staticExec("hg log -r . --template \"{node|short}\"")
if result == "":
result = "unknown"
const VERSION = get_version()
However, if for example I try to use the fmt library it does work when the code is outside of a function but it doesn't when I put it in a function, even with the compileTimee pragma.
That is, the following works:
# Generate the version info from the repository
const repo_hash = staticExec("hg log -r . --template \"{node|short}\"")
when repo_hash == "":
# The mercurial call failed
const repo_version = "unknown"
else:
const repo_latesttags = staticExec("hg log -r . --template \"{latesttag}\"")
const repo_latesttag_distance = staticExec("hg log -r . --template \"{latesttagdistance}\"")
when repo_latesttags in @["", "null"]:
# No tags -> just show the revision id
const repo_version = repo_hash
else:
when repo_latesttag_distance == "0":
# Current revision is tagged
const repo_version = &"{repo_latesttags} ({repo_hash})"
when repo_latesttag_distance != "0":
when repo_latesttag_distance == "1":
# Check if the current revision is the one that "adds a tag"
# Note that this does not work if multipe tags are added on consecutive revisions
const latest_files = staticExec("hg log -r . --template \"{files}\"")
when latest_files in @["", ".hgtags"]:
const repo_version = &"{repo_latesttags} ({repo_hash})"
else:
# Current revision is not tagged nor is a "tag revision"
const repo_version = &"{repo_latesttags}+{repo_latesttag_distance} ({repo_hash})"
else:
# Current revision is not tagged
const repo_version = &"{repo_latesttags}+{repo_latesttag_distance} ({repo_hash})"
const VERSION = repo_version
echo VERSION
But the following fails:
proc get_version(): string {.compileTime.} =
# Generate the version info from the repository
const repo_hash = staticExec("hg log -r . --template \"{node|short}\"")
when repo_hash == "":
# The mercurial call failed
const repo_version = "unknown"
else:
const repo_latesttags = staticExec("hg log -r . --template \"{latesttag}\"")
const repo_latesttag_distance = staticExec("hg log -r . --template \"{latesttagdistance}\"")
when repo_latesttags in @["", "null"]:
# No tags -> just show the revision id
const repo_version = repo_hash
else:
when repo_latesttag_distance == "0":
# Current revision is tagged
const repo_version = &"{repo_latesttags} ({repo_hash})"
when repo_latesttag_distance != "0":
when repo_latesttag_distance == "1":
# Check if the current revision is the one that "adds a tag"
# Note that this does not work if multipe tags are added on consecutive revisions
const latest_files = staticExec("hg log -r . --template \"{files}\"")
when latest_files in @["", ".hgtags"]:
const repo_version = &"{repo_latesttags} ({repo_hash})"
else:
# Current revision is not tagged nor is a "tag revision"
const repo_version = &"{repo_latesttags}+{repo_latesttag_distance} ({repo_hash})"
else:
# Current revision is not tagged
const repo_version = &"{repo_latesttags}+{repo_latesttag_distance} ({repo_hash})"
return repo_version
const VERSION = get_version()
echo VERSION
Why is that? (note that I am not using result because I just did a quick refactor to test whether this works).
Also, why is the compiletime pragma necessary? Couldn't the compiler see that the function is being assigned to a const and then automatically call the function at compile time?
Thanks!
Also, why is the compiletime pragma necessary? Couldn't the compiler see that the function is being assigned to a const and then automatically call the function at compile time?
Yes, but the compiler doesn't know that the proc is only used in a compile time context. The {.compileTime.} pragma enforces that, meaning that the proc can only be used during compile time. It is possible to write the proc without the pragma, by assigning to a const first (which also means that staticExec is always called in a compile time context):
proc get_version(): string =
const res = staticExec("hg log -r . --template \"{latesttag}+{latesttagdistance} ({node}|short})\"")
result = res
const version_info = get_version()
Thank you, that works great! Simply being careful to assign the output of the staticExec calls to const variables did the trick. As a nice side effect it also fixes the issue I had with the more complex version of my function.
I have a final question which is: shouldn't the compiler be able to see that the output of get_version() is being assigned to a const variable, even if I did not use the intermediate const variable? That is, shouldn't these two functions work the same?:
# This procedure compiles fine
proc get_version_compiles_ok(): string =
const res = staticExec("hg log -r . --template \"{latesttag}+{latesttagdistance} ({node}|short})\"")
result = res
const version_info_ok = get_version_compiles_ok()
# This procedure does not compile
proc get_version_compile_failure(): string =
result = staticExec("hg log -r . --template \"{latesttag}+{latesttagdistance} ({node}|short})\"")
const version_info_fail = get_version_compile_failure()
I have a final question which is: shouldn't the compiler be able to see that the output of get_version() is being assigned to a const variable, even if I did not use the intermediate const variable?
The compiler does that, the issue is with the proc itself. Note that the error exists even if get_version_compile_failure is never called. The body of a proc that doesn't have the {.compileTime.} pragma is not a compile time context, so the compiler doesn't allow staticExec there. The left hand side of a const assignment is however a compile time context (even when inside a proc body), which is why it works.
Theoretically, the compiler could infer {.compileTime.} automatically for procs that are only called during compile time (like get_version_compile_failure), but I think it would be confusing since the programmer himself/herself can't easily infer that in a large program.
Thank you for the clarification. Adding the compiletime directive fixes it.
I would argue however that using const (instead of let) is a very strong indication that the developer wants to execute an operation at compile time. I don't think compiletime should be needed except as a check if you wanted the compiler to verify that a procedure _can be used at compile time (and fail if that is not the case).