Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dicts are confusing and need some very well-commented examples. #118

Open
ACleverDisguise opened this issue Dec 3, 2024 · 4 comments
Open
Assignees
Labels
📚 documentation Improvements or additions to documentation

Comments

@ACleverDisguise
Copy link

Using dicts comes in two flavours, and seemingly because of the really weird syntax of Makefiles using them is sometimes inobvious.

The following bit me and I still haven't actually figured out how to get around it.

So let's assume some simple files:

/* hello_impl.h */
#ifndef HELLO_IMPL_H_INCLUDED
#define HELLO_IMPL_H_INCLUDED

typedef const char *placename;

void say_hello(placename);

#endif /*HELLO_IMPL_H_INCLUDED*/
/* hello_impl.c */
#include <stdio.h>

#include "hello_impl.h"

void say_hello(placename place)
{
    printf("Hello, %s!\n", place);
}
/* hello_main.c */
#include "hello_impl.h"

int main(int argc, char** argv)
{
    for (int i = 1; i < argc; i++)
    {
        say_hello(argv[i]);
    }

    return 0;
}

A quick sanity check at the command line:

$ gcc -o hello hello_main.c hello_impl.c
$ ./hello World
Hello, World!

Now let's make a conventional Makefile

AR = ar
ARFLAGS = -rv
CC = gcc
CFLAGS = -O3

LIB = libhello.a
LIB_OBJS = hello_impl.o

EXE = hello
EXE_OBJS = hello_main.o

.PHONY : all
all : $(EXE)
	./$< World

$(EXE) : $(EXE_OBJS) $(LIB)
	@echo "$(CC) $(CFLAGS) -o $@ $^"
	$(CC) $(CFLAGS) -o $@ $^

$(LIB) : $(LIB_OBJS)
	@echo "$(AR) $(ARFLAGS) $@ $^"
	$(AR) $(ARFLAGS) $@ $^
	
# No, I don't handle .h files because I'm making a minimalist example.

This works fine:

$ make
gcc -O3   -c -o hello_main.o hello_main.c
gcc -O3   -c -o hello_impl.o hello_impl.c
ar -rv libhello.a hello_impl.o
ar -rv libhello.a hello_impl.o
r - hello_impl.o
gcc -O3 -o hello hello_main.o libhello.a
gcc -O3 -o hello hello_main.o libhello.a
./hello World
Hello, World!

If I convert this to an attempt to use a dict for managing settings , however:

.PHONY : bmakelib/bmakelib.mk
include bmakelib/bmakelib.mk

AR = ar
ARFLAGS = -rv
CC = gcc
CFLAGS = -O3

LIB = libhello.a
.PHONY : lib-config
lib-config : bmakelib.dict.define( LIBCONF )
lib-config : bmakelib.dict.set( LIBCONF,lib-objs,hello_impl.o )

EXE = hello
.PHONY : exe-config
exe-config : bmakelib.dict.define( EXECONF )
exe-config : bmakelib.dict.set( EXECONF,exe-objs,hello_main.c )

.PHONY : all
all : $(EXE)
	./$< World

$(EXE) : exe-config
$(EXE) : $(call bmakelib.dict.get( EXECONF,exe-objs )) $(LIB)
	@echo "$(CC) $(CFLAGS) -o $@ $^"
	$(CC) $(CFLAGS) -o $@ $^

$(LIB) : lib-config
$(LIB) : $(call bmakelib.dict.get( LIBCONF,lib-objs ))
	@echo "$(AR) $(ARFLAGS) $@ $^"
	$(AR) $(ARFLAGS) $@ $^

To my eyes this should be equivalent to what I had before. A bit wasteful, perhaps, in such a simple project, but to me this looks like it should work.

But it doesn't:

$ make
make: *** No rule to make target 'bmakelib.dict.set(LIBCONF,lib-objs,hello_impl.o)', needed by 'lib-config'.  Stop.

And at this point I'm stuck. I'm positive that what I want to do is possible but I have no idea how to do it. The examples given in the documentation for dict don't really cover doing a build this way (the accessing of the dict values is only in the body of a rule, not the head) and no amount of tinkering has got me something that works.

So ...

A few, well-commented examples with simplicity roughly equivalent to the above needs to be added to the documentation. Where the possible "gotchas" are (because of Make's broken syntax) need to be pointed out so that these dicts can be an asset to a Makefile.

@bahmanm
Copy link
Owner

bahmanm commented Dec 5, 2024

Thanks for writing up a detailed explanation. I'll try to use your sample and see if I can answer your question.

@bahmanm
Copy link
Owner

bahmanm commented Dec 7, 2024

OK, here's what I could figure out:

bmakelib.dict.get syntax

Mind that the correct syntax for calling get is $(call bmakelib.dict.get,DICT,KEY). The reason that it's not defined as a target, like eg put, is that it is supposed to evaluate to a value and there's not way to do so with a target.

Why it doesn't work as a prerequisite?

Make executes all prerequisites of a target in parallel. In your case define and put are run in parallel to get which unfortunately means the dict entry wouldn't be initialised by the time get is called. That's why get works in the recipe when all the prerequisites have been run.

Sadly I haven't been able to work around this limitation. Let me know if you can come up with something that works.

Mind you that you can always invoke put and define like $(call bmakelib.define,DICT) and $(call bmakelib.dict.put,KEY,VALUE) respectively.

Hope this helps.

@ACleverDisguise
Copy link
Author

It helps, but it REALLY needs to be better-documented, complete with your explanation above and a detailed example that illustrates the points. (The bug was about the documentation, after all. ;) )

@bahmanm bahmanm self-assigned this Dec 8, 2024
@bahmanm bahmanm added the 📚 documentation Improvements or additions to documentation label Dec 8, 2024
@bahmanm
Copy link
Owner

bahmanm commented Dec 8, 2024

You're right. I'll make sure this is addressed in the next release. Thanks again.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
📚 documentation Improvements or additions to documentation
Projects
None yet
Development

No branches or pull requests

2 participants