Lista de argumentos muito longa erro com makefile

1

Em um makefile, eu tenho

@echo "$(IGNORE_DIRS) $(CLEAN_FILES) $(CLEAN_DIRS) $(REALCLEAN_FILES)" | tr ' ' '\n' >> $@

O problema é que $(CLEAN_FILES) é muito grande, então quando eu executo o make, recebo

make: execvp: /bin/sh: Argument list too long

Estou no Xubuntu 18.10.

Edit: Eu deveria fornecer um pouco mais de contexto. O que estou trabalhando é uma regra make (estou usando o GNU make) para gerar automaticamente o arquivo .hgignore . Aqui está a regra do make em sua totalidade:

.hgignore : .hgignore_extra
    @echo "Making $@"
    @rm -f $@
    @echo "# Automatically generated by Make. Edit .hgignore_extra instead." > $@
    @tail -n +2 $< >> $@
    @echo "" >> $@
    @echo "# The following files come from the Makefile." >> $@
    @echo "syntax: glob" >> $@
    @echo "$(IGNORE_DIRS) $(CLEAN_FILES) $(CLEAN_DIRS) $(REALCLEAN_FILES)" | tr ' ' '\n' >> $@
    @chmod a-w $@
.PHONY : .hgignore

Editar 2: Por sugestão do @mosvy, eu também tentei

.hgignore : .hgignore_extra
    @echo "Making $@"
    @rm -f $@
    @echo "# Automatically generated by Make. Edit .hgignore_extra instead." > $@
    @tail -n +2 $< >> $@
    @echo "" >> $@
    @echo "# The following files come from the Makefile." >> $@
    @echo "syntax: glob" >> $@
    $(file >$@) $(foreach V,$(IGNORE_DIRS) $(CLEAN_FILES) $(CLEAN_DIRS) $(REALCLEAN_FILES),$(file >>$@,$V))
    @true
    @chmod a-w $@
.PHONY : .hgignore

Executando make .hgignore com isso, não receberei mais o erro "Argument list too long", mas o arquivo .hgignore gerado contém apenas a saída até a linha syntax: glob e nada depois disso.

    
por teerav42 09.11.2018 / 02:51

4 respostas

2

Como @schily já explicou, isso não é um problema de shell e não pode ser trabalhado com xargs , citando, dividindo em mais echo com ; , etc. Todo o texto de uma ação make é passado como argumento / s para um único execve(2) e não pode ser maior que o tamanho máximo permitido pelo sistema operacional.

Se você estiver usando o GNU make (o padrão no linux), você pode usar o seu file e foreach funções:

TEST = $(shell yes foobar | sed 200000q)

/tmp/junk:
        $(file >$@) $(foreach V,$(TEST),$(file >>$@,$V))
        @true

.PHONY: /tmp/junk

Isso imprimirá todas as palavras de $(TEST) separadas por novas linhas no arquivo denominado em $@ . É baseado em um exemplo similar do manual do make. p>

Seu Makefile provavelmente poderia ser retrabalhado em algo mais gerenciável, que não requer recursos sofisticados do GNU, mas é difícil dizer como os trechos que você postou.

Atualização:

Para o snippet exato da pergunta, algo assim poderia ser feito:

.hgignore : .hgignore_extra
    $(info Making $@)
    $(file >[email protected])
    $(file >>[email protected],# Automatically generated by Make. Edit .hgignore_extra instead.)
    $(shell tail -n 2 $< >>[email protected])
    $(file >>[email protected],)
    $(file >>[email protected],# The following files come from the Makefile.)
    $(file >>[email protected],syntax: glob)
    $(foreach L, $(IGNORE_DIRS) $(CLEAN_FILES) $(CLEAN_DIRS) $(REALCLEAN_FILES), $(file >>[email protected],$L))
    @mv -f [email protected] $@
    @chmod a-w $@
.PHONY : .hgignore

Eu mudei um pouco, então ele primeiro escreve em .hgignore.new , e se tudo correr bem, só então mova .hgignore.new para .hgignore . Você terá que alterar os espaços de recuo para as guias, porque essa interface dumb está distorcendo os espaços em branco.

    
por 09.11.2018 / 13:26
3

No UNIX , a seguinte regra se aplica:

The following data forms the initial stack of a process:

  • The sum of strlen() of all environment strings + final nul character per string
  • The sum of strlen() of all argument strings + final nul character per string
  • The environment array: n+1 environment strings * sizeof char *
  • The argv array: n+1 argument strings * sizeof char *
  • A few additional numbers

Todos esses dados não devem exceder ARG_MAX .

Em um UNIX histórico, o valor para ARG_MAX era 10240 ou 20480 bytes .

O SunOS-4.0 (publicado em dezembro de 1987) aumentou esse limite para 1MB

O Solaris-7.0 (publicado em 1997) introduziu suporte a 64 bits e para evitar um limite praticamente menor em sistemas de 64 bits (causado por maiores env e argv matrizes como resultado de um% maiorchar *) , ARG_MAX foi aumentado para 2MB para programas de 64 bits.

BTW: O SO moderno compatível com POSIX inclui suporte para o programa getconf e getconf ARG_MAX imprime o valor real. Em um Linux de 64 bits, isso retorna 2MB para que o Linux na primeira visualização pareça adotar os aprimoramentos do SunOS ....

Agora, vamos ver make :

O programa make chama comandos de Makefiles via:

sh -ce command

em que command é um argumento único que é a sequência expandida que você vê em uma linha de ação de um Makefile .

SunPro Make introduziu uma otimização no início dos anos 90:

  • Se uma linha de comando não contiver meta caracteres shell, make em si simboliza a linha de comando e chama o comando via: execv() para evitar a sobrecarga de uma chamada de shell.

Mais tarde, gmake e smake adotaram essa otimização.

smake introduziu outra otimização em 2012:

  • Se um comando shell for introduzido por um simples comando echo que termina em ; e se a seguinte linha de comando não contiver metacaracteres do shell, o echo será inlined para smake e o comando após o ; é executado via execv() para reduzir a sobrecarga de sistemas de criação modernos que normalmente usam @ para suprimir echos de comando make e usam chamadas echo simplificadas para facilitar a saída de make entender (veja, por exemplo, o sistema Schily Makefile introduzido em fevereiro de 1993).

Nenhuma dessas regras make se aplica à sua linha de comando make , pois sua linha de comando contém meta-caracteres do shell. Então, todo o seu comando é chamado via:

sh -ce command

onde command é uma única string com o tamanho total causado pelo seu makefile.

Agora parece que o kernel Linux não é compatível com UNIX nem POSIX e impõe uma limitação adicional que nunca existiu no UNIX. Esta limitação adicional parece basear-se no comprimento máximo de uma única string.

Se isso for realmente verdade, isso desqualificaria o Linux, já que impediria que o Linux fosse útil em projetos maiores gerenciados por make .

Você pensou em fazer um relatório de bug para o pessoal do kernel do Linux?

    
por 09.11.2018 / 11:00
0

A pergunta urgente em tais casos é por que você está fazendo isso em primeiro lugar? Parece que você está tentando criar algum tipo de arquivo .ignore contendo todos os arquivos que são presumivelmente a saída do programa ou globs. No primeiro caso, eu advogaria passar a saída do programa diretamente para tr (se estritamente necessário) e depois para o arquivo sem passar por uma variável intermediária. Se você estiver usando globs, use um for loop muito simples:

for file in *
do
    echo "$file" >> out.txt
done

(Escape se necessário no seu Makefile.)

Em termos muito gerais:

  • Use pipes e redireciona, em vez de variáveis, para grandes blocos de dados. Eles são realmente rápidos (já que podem ser manuseados com a mesma rapidez que qualquer programa no pipeline pode processar entrada).
  • Use xargs quando você realmente precisar.
  • Evite inútil echo s e cat s .
por 09.11.2018 / 04:29
0

Veja esta resposta , em particular esta citação:

one argument must not be longer than MAX_ARG_STRLEN (131072). This might become relevant if you generate a long call like "sh -c 'generated with long arguments'".

Por outro lado, o limite no número total de argumentos para passar é bastante alto, então, talvez, em vez disso, apenas passem-nos como argumentos separados, uma vez que o resultado final do echo será o mesmo?

Isso remove apenas o " s do seu comando original:

@echo $(IGNORE_DIRS) $(CLEAN_FILES) $(CLEAN_DIRS) $(REALCLEAN_FILES) | tr ' ' '\n' >> $@

Espero que isso seja suficiente para manter os argumentos dentro dos limites para o número de argumentos (que é bastante alto) e o tamanho de cada argumento (que não será mais um problema, já que cada argumento é agora curto.)

UPDATE: Remover as aspas não corrige isso completamente, pois make executa cada comando em um shell, usando o equivalente de sh -c '...' , em que o comando inteiro se torna um único argumento, então ele ainda estará limitado ao tamanho limite de um argumento individual, que é de 131.072 bytes no Linux na plataforma x86.

    
por 09.11.2018 / 04:28