How can I print the compile time (format = Time.local.to_s("%Y%m%d_%H%M%S")
) when the app starts ?
You mean embedding the time when the binary was built?
The macro language doesn’t expose any feature directly for this. You could run an external program (such as date
) to get the current time using macro run
.
Maybe the easiest way though is to pass in the current date via environment variable.
$ echo 'puts {{ env("BUILD_DATE") }}' > date.cr
$ BUILD_DATE=$(date +%Y%m%d_%H%M%S) crystal build date.cr
$ ./date
20240108_150602
Yes, I want to embedd the date at build time.
Unfortunately, I have to run my app on windows. Can this also be done with a pure crystal solution ? Without the need of a date.exe and without the need to set den ENV variable before calling crystal build.
I’ve done something similar with one of my projects. There’s a macro called “run” (I think?) That will compile and run a crystal file, and whatever the output of that crystal code is, is what the macro resolved to.
I came up with the following one. I haven’t tried it but it might work on Windows.
BUILD_TIME={{ `crystal eval 'puts Time.local.to_s("%Y%m%d_%H%M%S")'`.chomp.stringify }}
puts BUILD_TIME
I would like to know a better way too.
With a slight adaption it works also on linux/windows:
BUILD_TIME={{ `crystal eval "puts Time.local.to_s(\\"%Y%m%d_%H%M%S\\")"`.chomp.stringify }}
Another alternative is to use macro run. It will let you compile and run a crystal program when compiling another one. Bonus point, you don’t hardcode the compiler’s binary path
# file: compile_time.cr
puts Time.local.to_s("%Y%m%d_%H%M%S")
# file: main.cr
COMPILE_TIME = {{ run("./compile_time").stringify.strip }}
pp! COMPILE_TIME
% ./main
COMPILE_TIME # => "20240108_152905"
I am not sure if it works in windows, but I think it should.
This is what I’ve been using for my CLI applications:
BUILD_DATE = {% if flag?(:win32) %}
{{ `powershell.exe -NoProfile Get-Date -Format "yyyy-MM-dd"`.stringify.chomp }}
{% else %}
{{ `date +%F`.stringify.chomp }}
{% end %}
Probably not the nicest looking code but it’s portable and doesn’t depend on any external files or running Crystal code during compilation.
A question would be why you even need a time stamp from the build time, though.
Timestamps are very friendly for reproducible builds, see Timestamps — reproducible-builds.org.
@straight-shoota Do we have a issue for this? use crystal eval
is just an awful workaround, if none, i will create a new one.
A question would be why you even need a time stamp from the build time, though.
One usage is, user need to know current deployed website version (e.g. use git hash + date),
e.g. when visit the http://website/version
, get a string like this:
Deployed version: 3a29634 2024/06/20 18:10:58
Build date has little relevance for that. I can build a code version from 5 years ago. What good does it do to know it was built today?
Instead of build date you’d probably rather want a source date. That’s more meaningful.
No, you can build twenty times, but, you only deploy to production the twenty-first time, after done it, you can confirm your are really deployed code into production when visit /version
.
What i means is, both of them are meaningful, as a programmer, you know the git hash, but as a user, he only know the DateTime.
But, unless you’re doing stuff like embedding the compile time, all those 21 builds should be identical.
Ah, but then you have another problem when someone builds an old version and deploys that. The user will think it’s a new version as the date is later, and the programmer probably too, who can remember hashes?
Which is why someone invented versions.
Yes, so i includes both git hash and date time, but only check the date is still meaningful.
But, unless you’re doing stuff like embedding the compile time, all those 21 builds should be identical.
Of cause, that exactly the macro code does.
module College
VERSION = {{ `shards version "#{__DIR__}"`.chomp.stringify }}
DEPLOYED_VERSION = {{ `git rev-parse --short HEAD`.chomp.stringify + `crystal eval 'puts Time.local.to_s(" %Y/%m/%d %H:%M:%S")'`.chomp.stringify }}
end
I really don’t think we should be get the build time use such an ugly way.
Created a issue.
You’re using the build time as a proxy for “version” for the user. But it isn’t, your user doesn’t care when it’s built, but what’s built. So what I’m suggesting is replacing crystal eval 'puts Time.local.to_s(" %Y/%m/%d %H:%M:%S")'
with git log -1 --date=iso --format=%cd HEAD
. Then you’ll get the same date for the same code, no matter when it is built.
Good suggestion, thanks!
Although, for my case, the user
is just myself, because the deploy process need 3 step, i probably forget to restart systemd service sometimes, so, the date only a confirmation, Oh, i do it successful
.