1

I am trying to build a feature into my makefile which allows me to specify a list of libraries a particular library depends on

This will allow dependants of a library to automatically be rebuilt if that library's dependencies are rebuilt, and also have the dependencies added to the link line.

I asked a related question on SO here, and working through a given answer, I came up with the following test

uniq = $(if $1,$(firstword $1) $(call uniq,$(filter-out $(firstword $1),$1)))
expand-deps = $1 $(foreach _,$1, $(call expand-deps,$($__deps)))
make-dep-list = $(call uniq,$(call expand-deps,$1))

define make-lib
    $(warning $1_deps: $2)
    # capture the list of libraries this library depends on
    $1_deps = $2
endef

define make-bin
    # show the fully expanded list of libraries this binary depends on
    $(warning $1 dep-list: [$(call make-dep-list,$2)])
endef

#$(eval $(call make-lib, thread, log utils)) circular-dependency log->thread; thread->log
$(eval $(call make-lib, thread, utils))
$(eval $(call make-lib, log, thread))
$(eval $(call make-lib, order, log))
$(eval $(call make-lib, price, log))
$(eval $(call make-bin, test, order price))

running the above makefile yields the following results:

$ make
makefile:15:  thread_deps:  utils
makefile:16:  log_deps:  thread
makefile:17:  order_deps:  log
makefile:18:  price_deps:  log
makefile:19:  test dep-list: [order price log thread utils ]
make: *** No targets.  Stop.

It is possible for libraries to have circular dependencies.

As an example: The logging library is multi-threaded, so requires the thread library. The thread library can issue log statements, so requires the log library.

If I uncomment out the line which has a circular dependency

$(eval $(call make-lib, thread, log utils))
$(eval $(call make-lib, log, thread))

The makefile will get stuck in an infinite loop.

How can I allow users to specify circular dependencies, and break out of the infinite loop?

Community
  • 1
  • 1
Steve Lorimer
  • 27,059
  • 17
  • 118
  • 213
  • Just a point of style: it's best not to leave spaces after commas in a `$(call …)` (or indeed, after any comma in a _make_ function). Those spaces do not disappear—they end up at the start of the parameters (`$1`, `$2` etc.). This can mess you up (e.g., when concatenating identifiers). Just get out of the habit! – bobbogo Jun 15 '15 at 13:20
  • 1
    Yes, libraries can have circular deps, but it's somewhat smelly. Don't do it if you can avoid it. At link time you need to get the linker to repeat the linking until all dependencies are met. VisualC++ _link_ does this by default. _ld_ needs the libraries to be bracketed with `--start-group` and `--end-group` (if you are driving the link from _gcc_, then use something like `gcc -Wl,--start-group libs... -Wl,--end-group`). – bobbogo Jun 15 '15 at 13:28
  • The question is, in what way do these libraries depend on each other? Are they shared libraries, or static libraries? If static, they don't have a _build-time_ dependancy on each other at all (that is, you don't need liblog.a in order to successfully create libthread.a). They only have a _link-time_ dependency... so make doesn't care about this dependency, only the linker does. That means you don't need to declare prerequisite ordering between libraries, you just need to be sure they are all built before you try to link them. Getting the link line ordering correct is another issue. – MadScientist Jun 15 '15 at 14:55
  • @MadScientist Yeah I think the `%_deps` variables are only used to specify the executable link-time dependencies. – bobbogo Jun 15 '15 at 16:44
  • @MadScientist, yes you are correct, this is for getting the link line ordering correct. If you have a better suggestion on how to achieve this I'd greatly appreciate that! – Steve Lorimer Jun 15 '15 at 19:35
  • 1
    In my projects I just write out my list of libraries by hand in the order I want them. I haven't had a situation where it's seemed worthwhile to get really fancy about it. The reality is that, as bobbogo says above, if your libraries are circularly referential (and your linker is single-pass, like most UNIX linkers) then there is simply no linear, non-repeating list of libraries that can solve your problem. Either you need to use grouping or you have to list the libraries multiple times on the link line. – MadScientist Jun 15 '15 at 20:28
  • @bobbogo thanks I wasn't aware of `--start-group` and `--end-group` - am I correct in saying that with this the **order** of libraries on the link line is unimportant (it is this dance with the ordering which I am getting irritated by, hence my idea to have the order captured by libs specifying their requirements). I realise the [performance side-effect of this](http://stackoverflow.com/questions/5651869/gcc-what-are-the-start-group-and-end-group-command-line-options), however for small projects (like mine) perhaps not an issue – Steve Lorimer Jun 16 '15 at 04:43

1 Answers1

1

So, your problem is that you are recursively expanding lib_deps (say). While doing that you start expanding lib_deps again. Infinite loop (er, stack crash). To stop yourself you need to keep a list of things that you have already expanded. Falling out of functional style and keeping the answer in the global expansion (ugh!), something like:

expand-deps = \
  $(foreach _,$1, \
    $(if $(filter $_,${expansion}),, \
      $(eval expansion += $_)$(call expand-deps,${$__deps})))

make-dep-list = $(eval expansion :=)$(call expand-deps,$1)${expansion}

define make-lib
  $(warning $1_deps: $2)
  # capture the list of libraries this library depends on
  $1_deps := $2
endef

define make-bin
  # show the fully expanded list of libraries this binary depends on
  $(warning $1 dep-list: [$(call make-dep-list,$2)])
endef

$(eval $(call make-lib,thread,log utils))#circular-dependency log->thread; thread->log
#$(eval $(call make-lib,thread,utils))
$(eval $(call make-lib,log,thread))
$(eval $(call make-lib,order,log))
$(eval $(call make-lib,price,log))
$(eval $(call make-bin,test,order price))

(As an exercise you might like to rewrite this in functional style, i.e., get rid of the global $expansion replacing it with a parameter that gets passed around.)

bobbogo
  • 14,989
  • 3
  • 48
  • 57