4
votes

I have a reasonably large project (4272 .o files) and I can't get it to link with GNU Make. I run into make: /bin/sh: Argument list too long. This is a Qt 5 project that uses qmake to generate the makefile.

I know there are lots of questions about this, but I don't know how to apply any of the solutions to my problem. I'm also not totally sure why I'm running into this at the linking step. The error I get is:

make: /bin/sh: Argument list too long

The makefile entry for linking my project looks like this:

build/debug/my_target/my_target:  $(OBJECTS)  
    @test -d build/debug/my_target/ || mkdir -p build/debug/my_target/
    $(LINK) $(LFLAGS) -o $(TARGET) $(OBJECTS) $(OBJCOMP) $(LIBS)

which expands to something like:

@echo linking /build/debug/my_target/my_target && clang++ -ccc-gcc-name g++ -lc++ -L/path/to/licensing/lib -Wl,-rpath,/path/to/qt/lib -Wl,-rpath-link,/path/to/qt/lib -o build/debug/my_target/my_target build/debug/my_target/obj/object1.o build/debug/my_target/obj/object2.o ... build/debug/my_target/obj/object4272.o  ... [ a bunch of moc_X.o ] ... [ a bunch of libs ] -lGL -lpthread -no-pie

This is pretty long. But here's where it gets weird: when I put the expanded command after the @echo linking build/debug/my_target/my_target && into a shell script, and it runs. The shell script is 202,420 characters (including the #!/bin/sh line). Also, if I get rid of the @echo ... && part of the command I can run make and linking works.

Another workaround: if I manually edit my makefile so that the linking command contains build/debug/my_target/*.o instead of $(OBJECTS) it works:

build/debug/my_target/my_target:  $(OBJECTS)  
    @test -d build/debug/my_target/ || mkdir -p build/debug/my_target/
    $(LINK) $(LFLAGS) -o $(TARGET) build/debug/my_target/*.o $(OBJCOMP) $(LIBS)

I don't think I can get qmake to do this, though, so I'm stuck manually editing my makefile unless I can find another solution.

Answers to similar problems seem to focus on line breaks and how they're handled in makefiles. My shell script only has two lines (one after #!/bin/sh and one after the actual command). Also, one solution that people have come up with (for example this one) uses a for loop to iteratively run a command on each argument. I'm not sure how I could apply this here, since (I think) I need all those object files in my linker command.

How does @echo cause the max argument length to be exceeded?

Questions I originally asked that aren't really relevant: (Note: as originally posted this question missed the @echo at the beginning of the linking command. That seems to be the answer to "why is this happening" and as such I don't really need to know the answer to the second question, which is answered in the first comment in any case).

  1. Why is this happening? How is it that make is running into this error with a command that I can apparently run in a shell script?
  2. How can I get around this if there's no way to run my command as an iterative series of shorter commands?

Various details about my system that might be relevant:

  • I'm running a fairly up-to-date Arch Linux system, kernel 5.8.10
  • ARG_MAX value is 2097152, the output from xargs --show-limits is:
Your environment variables take up 2343 bytes
POSIX upper limit on argument length (this system): 2092761
POSIX smallest allowable upper limit on argument length (all systems): 4096
Maximum length of command we could actually use: 2090418
Size of command buffer we are actually using: 131072
Maximum parallelism (--max-procs must be no greater): 2147483647
  • ulimit -s output: 8192 (I've tried setting this to much larger values, e.g. ulimit -s 65536 without success, which maybe isn't surprising since ARG_MAX appears to be much larger than the linker command).
  • GNU Make version is 4.3
  • clang/clang++ version is 10.0.1
  • Qt version is 5.15.1 (I'm fairly certain this isn't relevant, we've just switched our project over from 5.9.6 and I had the same problem then as well).
2
You could have the rule write the script and run it. Or you could bind those object files into static libraries, a handful at a time, then link against those. But what happens if you put echo in front of that $(LINK) ... command? Do you get the same error? That might at least tell you whether it's the shell or the compiler complaining.Beta
Huh. Yeah, I do, but your question made me realize what the problem is, and I missed something critical in the link command: it's preceded with @echo. Getting rid of this solves it, but I don't understand why. I'll edit the question accordingly.rainbowgoblin
Linux's ARG_MAX applies to the sum of command-line and environment; variables set by make might be pushing you over the limit. It is possible to pass GCC arguments by file, e.g. echo arg1 arg2 ... >args; gcc @args which would bypass environment limitations, but I have no idea how to get qmake to produce a Makefile using that. Can you just try to build in a directory with a shorter name?ephemient
@ephemient And because they're passed to sh -c as a single argument, make's commands are much more limited than ARG_MAX, because Linux also imposes a much lower limit on the length of a single string from argv or envp (<128k IIRC).user414777
@user414777 I didn't mean to literally use echo ... from within Makefile, I agree that would be pointless. (Although I can see how what I wrote could be misconstrued.) I had forgotten about MAX_ARG_STRLEN which is the single argument limit you talk about - it's 131072. Either way, the problem lies in figuring out how to get qmake to take care of that.ephemient

2 Answers

4
votes

Just FYI, the reason removing the echo fixes the problem (this is what I was going to suggest as well) is that when you remove the special shell operator && and just have a simple command invocation with no shell features like multiple commands, special quoting, globbing, etc. then make uses the "fast path" to invoke your command.

That is, if make can determine that the shell would do nothing special with your command, other than run it, make will skip invoking the shell and instead run your command directly.

In that case you will not run up against the single-argument limit because it doesn't use the /bin/sh -c '...' form.

Of course, this can be a little magical and inflexible since you have to be careful to ensure no special shell operations are ever included in your link line. But if you can ensure this then it should solve your problem.

2
votes

Why is this happening? How is it that make is running into this error with a command that I can apparently run in a shell script?

Because make is running the shell commands (recipes) by passing them as a single argument to /bin/sh -c, and not only will that run into the OS's limit on command line arguments + environment variables, but also into the much lower limit that Linux is imposing on a single string from the command line or environment, which is usually 128k bytes.

How can I get around this if there's no way to run my command as an iterative series of shorter commands?

As suggested by @ephemient can use the @arglist argument of gcc or ld (which directs it to take its arguments from a file), and use the file function of GNU make to create that arglist file, which being internal to make, will not run into any that OS limit.