Identifica erroneamente que a regra foi bem-sucedida devido ao arquivo de tamanho 0 gerado pelo redirecionamento de saída

5

Em um makefile, tenho várias regras que se parecem com isso:

out.txt: foo.sh input.txt
  ./foo.sh -i input.txt > out.txt

Se foo.sh falhar, então out.txt será criado como um arquivo de tamanho 0. Se eu rodar make novamente, ele assumirá erroneamente que o arquivo out.txt foi criado com sucesso e não executará a regra novamente.

Qual é o jeito certo de lidar com esse tipo de cenário?

    
por Lorin Hochstein 06.07.2011 / 19:12

4 respostas

11

Você pode solicitar que exclua o arquivo de destino se a regra falhar, definindo um alvo especial chamado .DELETE_ON_ERROR . Não precisa fazer nada nem ter dependências, então adicione isso ao seu makefile:

.DELETE_ON_ERROR:

Então você recebe o seguinte:

$ cat Makefile
.DELETE_ON_ERROR:
foo:
    false > foo

$ make
false > foo
make: *** [foo] Error 1
make: *** Deleting file 'foo'
zsh: exit 2     make

$ stat foo
stat: cannot stat 'foo': No such file or directory

De Erros nas receitas :

Usually when a recipe line fails, if it has changed the target file at all, the file is corrupted and cannot be used—or at least it is not completely updated. Yet the file's time stamp says that it is now up to date, so the next time make runs, it will not try to update that file. The situation is just the same as when the shell is killed by a signal; see Interrupts. So generally the right thing to do is to delete the target file if the recipe fails after beginning to change the file. make will do this if .DELETE_ON_ERROR appears as a target. This is almost always what you want make to do, but it is not historical practice; so for compatibility, you must explicitly request it.

    
por 06.07.2011 / 19:27
7

Sempre que possível, as regras de makefile devem primeiro criar seu destino com um nome temporário e, em seguida, movê-lo para o lugar. Dessa forma, se o processo de criação for interrompido por algum motivo, não haverá um arquivo de destino parcialmente gravado que não possa ser distinguido de um arquivo totalmente gravado.

out.txt: foo.sh input.txt
        ./foo.sh -i input.txt >[email protected]
        mv -f [email protected] $@

mv -f [email protected] $@ é uma expressão comum do makefile.

A resposta de Juliano mostra uma variante onde o nome do arquivo temporário é gerado dinamicamente. A geração de nome dinâmico é necessária se houver mais de um processo que gera o mesmo destino ou se o diretório puder ser gravado por outros usuários. Isso é muito raro no caso de uma árvore de compilação (se esses forem problemas, muito do que está acontecendo em um makefile típico seria interrompido), portanto, a complexidade extra geralmente não é necessária.

    
por 06.07.2011 / 19:53
2

Já que parece que foo.sh é algo que você escreveu (ou é gerado por algo que você escreveu), eu consideraria adicionar um -o flag a ele, que leva o nome do arquivo de saída. Então você não precisa depender do comportamento de redirecionamento de E / S padrão. Seu script pode limpar arquivos de saída parciais ou, possivelmente, até mesmo evitar a criação do arquivo, se você puder detectar o erro com antecedência suficiente.

    
por 06.07.2011 / 20:47
1

Suponho que foo.sh retorne corretamente diferente de zero no erro. Então você deve fazer um temporário, e apenas sobrescrever a saída em sucesso.

tmp=$$(mktemp) && ./foo.sh input.txt > $$tmp && mv $$tmp $@ || rm -f $$tmp && false
    
por 06.07.2011 / 19:27