"./configure" equivalent for Shards

Let’s say I have a hypothetical program that I’ve written where I want a certain feature to be a compile-time option. From the compiler’s point of view, I can just do -D some_optional_feature, no problem.

But, what if this optional feature required a separate Shard as a dependency? Ideally this separate Shard dependency would only be installed if I actually wanted that feature to be enabled. Or maybe it’s something that depends on an external C library, and the Shard itself doesn’t have a mechanism to provide the C library itself. Things like Autotools or Meson or CMake can handle this when I do something like ./configure.

What’s the current way to go in the Crystal world? Just include the dependency in shard.yml anyway, then make a note in a README that “if you don’t want (or can’t) use this feature, remove it from shard.yml and use -D foo”? Or is there some higher-level build system I could use that wraps Shards? I know from the compiler’s point of view, if the code isn’t used, it won’t get included, but I’m thinking about this at a slightly different level.

There’s a possibility I may need to tackle this problem in the near future. But before I go off and create some hacky custom configure.rb script to force it to work the way I want, I wanted to check with others.

1 Like

There are no alternative build systems that can work with Shards (that I’m aware of at least), but you can add {% skip_file unless flag?(:foo) %} to the optional file(s) which would work just fine.

1 Like

Related: Feature flags for shards (like features in Rust crates)

1 Like

Maybe this is going a bit on a tangent… But it might be relevant to finding and using the right tools for the right jobs.
In my understanding of ./configure scripts they are responsible for setting the configuration how you want to build the software. But I have never seen a ./configure to directly affect or initiate dependency resolution. It usually expects the dependencies for the requested configuration to be available and only checks that they indeed are.
So I’m not sure ./configure and shards are on the same layer.

However, if you want ./configure to affect deoendency resolution, I suppose it should be pretty straightforward for ./configure to remove/add dependencies in shard.yml as appropriate.

1 Like

Yeah, Autotools won’t initiate a dependency download on its own, though you could do add it in if you really want. Meson has Wrap, which I haven’t used myself, but it seems to do like I’m thinking.

You have a good point, however, and I agree that Shards and Autotools aren’t exactly at the same level. That’s one reason I was wondering if there was some higher-level build system that I could use to wrap Shards.

But… it also dawned on me that build options would need to also be handled by downstream projects as well. Like if I had a library shard that has build options, the command line program that uses the library would also have to know how to configure the library at build time. This sounds like it could get very messy as far as hacky self-made ./configure scripts go. I think I’d either need a higher level tool, or Shards would need some sort of command line options for the Shards command itself.

Anyway, after reading the suggestions here and thinking about it, I thought about a third option: a separate Shard that adds the optional functionality by reopening classes and extending the original Shard. I’ve seen projects do this in Common Lisp (e.g. the separate clsql-sqlite3 system adds sqlite3 support to clsql), though with Shards it feels a bit more disjointed.

For non-libraries, I’d go with a Makefile + yq to conditionally add dependencies:

2 Likes

Thanks to know there is a tools name yq, and those hack use with make!

For packages that should offer some customizable aspects I would prefer to go with a plugin approach.
This can be used by the end application to choose the final package tree. Eg: a web framework with session management that can be stored in db, redis, or cookes. I would expect webframework-session-pg, webframework-session-mysql, webframework-session-redis, etc packages.

If we don’t want to have an explosion of packages it would be possible to have only a webframework-session package that has the code for all supported “plugins”. In this scenario I would not list the dependencies in the shards so they are not forced downstream when not used. shards does not support optional/peer dependencies unfortunately, so it’s hard to enforce a version constraint. We could use explicit requirements to choose the plugin easily. eg: require "webframework/session/redis" this require would fail if there is no redis package.

If we are building an end user app and we want to customize which library is used upon build time I wish there would be some project configuration file (as briefly mentioned here).

1 Like

The first versions of gi-crystal, a GObject binding generator, I tried to generate the bindings at post install time but soon I changed that since it was horrible to debug and IMO doing too much things on the back of the developer. So I changed to an approach of let the user execute a command by him/herself (the binding generator) after the shards install and refuse to compile (with a nice message) if the command was never run.

This is done here: gi-crystal/gi-crystal.cr at master · hugopl/gi-crystal · GitHub

I believe this very same approach can be used like a configure script too, you write a macro that check if a file exists and show a nice message telling the user what to do otherwise.

So the user could run the script, it would create a crystal file with the options you want and we done. However it would be even better if Crystal had a macro to set a flag, affecting flag evaluations after its declaration, e.g.:

{% set_flag(:foo) %}

If such set_flag command existed all this could be a small shard. Such shard could:

  • Copy (not compile) a crystal script to project /bin.
  • Just a require "configure" line would check if the configuration was generated already (i.e. ./bin/configure was run) and abort if it wasn’t the case.
  • The configuration script would read the project options from a YAML file and have a nice help message.

Pro:

  • Don’t touch shards and let it do what it does better.
  • Only projects that need such configuration would be affect by any possible side effect of it.
  • It seems to work and be user/developer friendly.

Cons:

  • I just thought about this now, hehehe, so I still need to think about if it was a really a good idea.
3 Likes