O GNU make oferece muitos recursos avançados, incluindo diversas funções úteis. No entanto, ele não contém uma linguagem de programação completa e, por isso, tem limitações. Às vezes essas limitações podem ser superadas chamando outro programa por meio da função shell, embora esse método muitas vezes seja ineficiente.
Nos casos em que os recursos embutidos do GNU make não são suficientes para suas necessidades, há duas opções para estender o make. Uma delas, nos sistemas em que estiver disponível, é utilizar o GNU Guile como linguagem de script embutida (veja Integração com o GNU Guile). A outra, nos sistemas que oferecem suporte a objetos carregáveis dinamicamente, é escrever sua própria extensão em qualquer linguagem (que possa ser compilada em um objeto desse tipo) e carregá-la para fornecer recursos estendidos (veja Carregando objetos dinâmicos).
O GNU make pode ser compilado com suporte ao GNU Guile como linguagem de extensão embutida. O Guile implementa a linguagem Scheme. Uma apresentação do GNU Guile, da linguagem Scheme e de seus recursos está além do escopo deste manual: consulte a documentação do GNU Guile e do Scheme.
Você pode determinar se o make contém suporte ao Guile examinando a variável .FEATURES; ela conterá a palavra guile se o suporte ao Guile estiver disponível.
A integração com o Guile fornece uma nova função do make: a função guile. A função guile recebe um argumento, que é primeiro expandido pelo make da maneira usual e depois passado ao avaliador do GNU Guile. O resultado do avaliador é convertido em uma string e usado como a expansão da função guile no makefile.
Além disso, o GNU make expõe procedimentos Guile para uso em scripts Guile.
Existe apenas um "tipo de dado" no make: a string. O GNU Guile, por outro lado, oferece uma rica variedade de tipos de dados diferentes. Um aspecto importante da interface entre o make e o GNU Guile é a conversão dos tipos de dados do Guile em strings do make.
Essa conversão é relevante em duas situações. Uma é quando um makefile invoca a função guile para avaliar uma expressão Guile: o resultado dessa avaliação precisa ser convertido em uma string do make para que possa ser avaliado posteriormente pelo make. A outra é quando um script Guile invoca um dos procedimentos expostos pelo make: o argumento fornecido ao procedimento precisa ser convertido em uma string.
A conversão dos tipos do Guile em strings do make é a seguinte:
#fFalso (False) é convertido na string vazia. Nas condicionais do make, a string vazia é considerada falsa.
#tVerdadeiro (True) é convertido na string "#t". Nas condicionais do make, qualquer string não vazia é considerada verdadeira.
symbolnumberUm símbolo ou número é convertido na representação em string desse símbolo ou número.
characterUm caractere imprimível é convertido no mesmo caractere.
stringUma string contendo apenas caracteres imprimíveis é convertida na mesma string.
listUma lista é convertida recursivamente de acordo com as regras acima. Isso implica que qualquer lista estruturada será achatada (ou seja, um resultado "'(a b (c d) e)" será convertido na string do make "a b c d e").
otherQualquer outro tipo do Guile resulta em erro. Em versões futuras do make, outros tipos do Guile poderão ser convertidos.
A tradução de "#f" (para a string vazia) e de "#t" (para a string não vazia "#t") foi projetada para permitir que você use resultados booleanos do Guile diretamente como condições booleanas do make. Por exemplo:
$(if $(guile (access? "myfile" R_OK)),$(info myfile exists))
Como consequência dessas regras de conversão, você precisa levar em conta o resultado do seu script Guile, pois esse resultado será convertido em uma string e analisado pelo make. Se não houver um resultado natural para o script (ou seja, se o script existir apenas por seus efeitos colaterais), você deve adicionar "#f" como expressão final, para evitar erros de sintaxe no makefile.
Além da função guile disponível nos makefiles, o make expõe alguns procedimentos para uso em seus scripts Guile. Na inicialização, o make cria um novo módulo Guile, gnu make, e exporta os procedimentos a seguir como interfaces públicas desse módulo:
gmk-expandEste procedimento recebe um único argumento, que é convertido em uma string. A string é expandida pelo make usando as regras normais de expansão do make. O resultado da expansão é convertido em uma string Guile e fornecido como o resultado do procedimento.
gmk-evalEste procedimento recebe um único argumento, que é convertido em uma string. A string é avaliada pelo make como se fosse um makefile. Esse é o mesmo recurso disponível por meio da função eval (veja A função eval). O resultado do procedimento gmk-eval é sempre a string vazia.
Observe que gmk-eval não é exatamente o mesmo que usar gmk-expand com a função eval. Neste último caso, a string avaliada é expandida duas vezes: primeiro por gmk-expand e, em seguida, pela função eval.
Aqui está um exemplo bem simples que usa o GNU Guile para gerenciar a escrita em um arquivo. Esses procedimentos Guile simplesmente abrem um arquivo, permitem escrever nele (uma string por linha) e o fecham. Observe que, como não podemos armazenar valores complexos como portas (ports) do Guile em variáveis do make, manteremos a porta como uma variável global no interpretador Guile.
Você pode criar funções Guile facilmente usando define/endef para criar um script Guile e, em seguida, usando a função guile para internalizá-lo:
define GUILEIO ;; Uma biblioteca de E/S Guile simples para o GNU Make (define MKPORT #f) (define (mkopen name mode) (set! MKPORT (open-file name mode)) #f) (define (mkwrite s) (display s MKPORT) (newline MKPORT) #f) (define (mkclose) (close-port MKPORT) #f) #f endef # Internaliza as funções de E/S do Guile $(guile $(GUILEIO))
Se você tiver uma quantidade significativa de código de suporte Guile, talvez valha a pena mantê-lo em um arquivo separado (por exemplo, guileio.scm) e carregá-lo no makefile usando a função guile:
$(guile (load "guileio.scm"))
Uma vantagem desse método é que, ao editar guileio.scm, seu editor entenderá que esse arquivo contém sintaxe Scheme em vez de sintaxe de makefile.
Agora você pode usar essas funções Guile para criar arquivos. Suponha que você precise operar sobre uma lista muito grande, que não cabe na linha de comando, mas o utilitário que você está usando também aceita a lista como entrada:
prog: $(PREREQS)
@$(guile (mkopen "tmp.out" "w")) \
$(foreach X,$^,$(guile (mkwrite "$(X)"))) \
$(guile (mkclose))
$(LINK) < tmp.out
É claro que um conjunto mais completo de procedimentos de manipulação de arquivos é possível. Você poderia, por exemplo, manter vários arquivos de saída ao mesmo tempo, escolhendo um símbolo para cada um e usando-o como chave de uma tabela de hash, em que o valor é uma porta, e então retornando o símbolo para ser armazenado em uma variável do make.
|
Muitos sistemas operacionais oferecem um mecanismo para carregar objetos compilados dinamicamente. Se o seu sistema oferece esse mecanismo, o GNU make pode utilizá-lo para carregar objetos dinâmicos em tempo de execução, fornecendo novos recursos que podem então ser invocados pelo seu makefile.
A diretiva load é usada para carregar um objeto dinâmico. Uma vez carregado o objeto, uma função de "configuração" (setup) será invocada para permitir que o objeto se inicialize e registre novos recursos no GNU make. Um objeto dinâmico pode incluir novas funções do make, por exemplo, e a função de "configuração" as registraria no sistema de tratamento de funções do GNU make.
Os objetos são carregados no GNU make colocando-se a diretiva load no seu makefile. A sintaxe da diretiva load é a seguinte:
load object-file …
ou:
load object-file(symbol-name) …
O arquivo object-file é carregado dinamicamente pelo GNU make. Se object-file não incluir um caminho de diretório, ele é procurado primeiro no diretório atual. Se não for encontrado lá, ou se incluir um caminho de diretório, então os caminhos específicos do sistema serão pesquisados. Se o carregamento falhar por qualquer motivo, o make exibirá uma mensagem e encerrará.
Se o carregamento for bem-sucedido, o make invocará uma função de inicialização.
Se symbol-name for fornecido, ele será usado como o nome da função de inicialização.
Se nenhum symbol-name for fornecido, o nome da função de inicialização é criado tomando-se o nome de base do arquivo object-file, até o primeiro caractere que não seja válido em um nome de símbolo (caracteres alfanuméricos e sublinhados são válidos em nomes de símbolos). A esse prefixo será acrescentado o sufixo _gmk_setup.
Mais de um arquivo objeto pode ser carregado com uma única diretiva load, e ambos os formatos de argumento de load podem ser usados na mesma diretiva.
A função de inicialização receberá o nome do arquivo e o número da linha onde a operação load foi invocada. Ela deve retornar um valor do tipo int, que deve ser 0 em caso de falha e diferente de 0 em caso de sucesso. Se o valor de retorno for -1, então o GNU Make não tentará reconstruir o arquivo objeto (veja Como os objetos carregados são refeitos).
Por exemplo:
load ../mk_funcs.so
Isso carregará o objeto dinâmico ../mk_funcs.so. Depois que o objeto for carregado, o make invocará a função mk_funcs_gmk_setup (presumidamente definida pelo objeto compartilhado).
Por outro lado:
load ../mk_funcs.so(init_mk_func)
Isso carregará o objeto dinâmico ../mk_funcs.so. Depois que o objeto for carregado, o make invocará a função init_mk_func.
Independentemente de quantas vezes um arquivo objeto apareça em uma diretiva load, ele será carregado (e sua função de configuração será invocada) apenas uma vez.
Depois que um objeto for carregado com sucesso, seu nome de arquivo é acrescentado à variável .LOADED.
Se você preferir que a falha ao carregar um objeto dinâmico não seja relatada como erro, pode usar a diretiva -load em vez de load. O GNU make não falhará e nenhuma mensagem será gerada se um objeto não for carregado. O objeto que falhou ao carregar não é adicionado à variável .LOADED, que pode então ser consultada para determinar se o carregamento foi bem-sucedido.
Os objetos carregados passam pelo mesmo procedimento de refazer que os makefiles (veja Como os makefiles são refeitos). Se algum objeto carregado for recriado, então o make começará do zero, lerá novamente todos os makefiles e recarregará os arquivos objeto. Não é necessário que o objeto carregado faça nada especial para oferecer suporte a isso.
Cabe ao autor do makefile fornecer as regras necessárias para reconstruir o objeto carregado.
|
Para serem úteis, os objetos carregados precisam ser capazes de interagir com o GNU make. Essa interação inclui tanto as interfaces que o objeto carregado fornece aos makefiles quanto as interfaces que o make fornece ao objeto carregado para manipular a operação do make.
A interface entre os objetos carregados e o make é definida pelo arquivo de cabeçalho C gnumake.h. Todo objeto carregado escrito em C deve incluir esse arquivo de cabeçalho. Qualquer objeto carregado que não seja escrito em C precisará implementar a interface definida nesse arquivo de cabeçalho.
Normalmente, um objeto carregado registra uma ou mais novas funções do GNU make usando a rotina gmk_add_function a partir de sua função de configuração. As implementações dessas funções do make podem fazer uso das rotinas gmk_expand e gmk_eval para realizar suas tarefas e, opcionalmente, retornar uma string como resultado da expansão da função.
Toda extensão dinâmica deve definir o símbolo global plugin_is_GPL_compatible para declarar que foi licenciada sob uma licença compatível com a GPL. Se esse símbolo não existir, o make emite um erro fatal e encerra ao tentar carregar sua extensão.
O tipo declarado do símbolo deve ser int. No entanto, ele não precisa estar em nenhuma seção alocada específica. O código apenas declara que o símbolo existe no escopo global. Algo como isto é suficiente:
int plugin_is_GPL_compatible;
gmk_flocEsta estrutura representa um par nome de arquivo/localização. Ela é fornecida ao definir itens, de modo que o GNU make possa, se necessário, informar mais tarde ao usuário onde a definição ocorreu.
Atualmente há apenas uma maneira de os makefiles invocarem as operações fornecidas pelo objeto carregado: por meio da interface de chamada de função do make. Um objeto carregado pode registrar uma ou mais novas funções, que então podem ser invocadas de dentro do makefile da mesma forma que qualquer outra função.
Use gmk_add_function para criar uma nova função do make. Seus argumentos são os seguintes:
nameO nome da função. É isso que o makefile deve usar para invocar a função. O nome deve ter entre 1 e 255 caracteres e só pode conter caracteres alfanuméricos, ponto ("."), traço ("-") e sublinhado ("_"). Não pode começar com um ponto.
func_ptrUm ponteiro para uma função que o make invocará quando expandir a função em um makefile. Essa função deve ser definida pelo objeto carregado.
min_argsO número mínimo de argumentos que a função aceitará. Deve estar entre 0 e 255. O GNU make verificará isso e falhará antes de invocar func_ptr se a função for invocada com poucos argumentos.
max_argsO número máximo de argumentos que a função aceitará. Deve estar entre 0 e 255. O GNU make verificará isso e falhará antes de invocar func_ptr se a função for invocada com argumentos demais. Se o valor for 0, então qualquer número de argumentos é aceito. Se o valor for maior que 0, então deve ser maior ou igual a min_args.
flagsSinalizadores que especificam como esta função operará; os sinalizadores desejados devem ser combinados com OR. Se o sinalizador GMK_FUNC_NOEXPAND for fornecido, então os argumentos da função não serão expandidos antes de a função ser chamada; caso contrário, eles serão expandidos primeiro.
Uma função registrada no make deve corresponder ao tipo gmk_func_ptr. Ela será invocada com três parâmetros: name (o nome da função), argc (o número de argumentos da função) e argv (um vetor de ponteiros para os argumentos da função). O último ponteiro (ou seja, argv[argc]) será nulo (0).
O valor de retorno da função é o resultado da expansão da função. Se a função não expandir para nada, o valor de retorno pode ser nulo. Caso contrário, ele deve ser um ponteiro para uma string criada com gmk_alloc. Uma vez que a função retorna, o make passa a ser o dono dessa string e a liberará no momento apropriado; ela não pode ser acessada pelo objeto carregado.
Há alguns recursos exportados pelo GNU make para uso pelos objetos carregados. Normalmente, eles seriam executados de dentro da função de configuração e/ou das funções registradas via gmk_add_function, para recuperar ou modificar os dados com os quais o make trabalha.
gmk_expandEsta função recebe uma string e a expande usando as regras de expansão do make. O resultado da expansão é retornado em um buffer de string terminado por nulo. Cabe ao chamador a responsabilidade de chamar gmk_free com um ponteiro para o buffer retornado quando terminar.
gmk_evalEsta função recebe um buffer e o avalia como um trecho de sintaxe de makefile. Esta função pode ser usada para definir novas variáveis, novas regras etc. É equivalente a usar a função eval do make.
Observe que há uma diferença entre gmk_eval e chamar gmk_expand sobre uma string usando a função eval. Neste último caso, a string será expandida duas vezes: uma por gmk_expand e outra pela função eval. Usando gmk_eval, o buffer é expandido no máximo uma vez (quando é lido pelo analisador do make).
Alguns sistemas adotam esquemas de gerenciamento de memória diferentes. Por isso, você nunca deve passar memória que tenha alocado diretamente para qualquer função do make, nem deve tentar liberar diretamente qualquer memória que lhe tenha sido retornada por uma função do make. Em vez disso, use as funções gmk_alloc e gmk_free.
Em particular, a string retornada ao make por uma função registrada usando gmk_add_function deve ser alocada usando gmk_alloc, e a string retornada pela função gmk_expand do make deve ser liberada (quando não for mais necessária) usando gmk_free.
gmk_allocRetorna um ponteiro para um buffer recém-alocado. Esta função sempre retorna um ponteiro válido; se não houver memória suficiente, o make encerra. gmk_alloc não inicializa a memória alocada.
gmk_freeLibera um buffer que lhe foi retornado pelo make. Uma vez que a função gmk_free retorna, a string deixa de ser válida. Se NULL for passado a gmk_free, nenhuma operação é realizada.
Suponha que quiséssemos escrever uma nova função do GNU make que criasse um arquivo temporário e retornasse seu nome. Gostaríamos que nossa função recebesse um prefixo como argumento. Primeiro, podemos escrever a função em um arquivo mk_temp.c:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <gnumake.h>
int plugin_is_GPL_compatible;
char *
gen_tmpfile(const char *nm, int argc, char **argv)
{
int fd;
/* Calcula o tamanho do nome de arquivo e aloca espaço para ele. */
int len = strlen (argv[0]) + 6 + 1;
char *buf = gmk_alloc (len);
strcpy (buf, argv[0]);
strcat (buf, "XXXXXX");
fd = mkstemp(buf);
if (fd >= 0)
{
/* Não vaza o descritor de arquivo. */
close (fd);
return buf;
}
/* Falha. */
fprintf (stderr, "mkstemp(%s) failed: %s\n", buf, strerror (errno));
gmk_free (buf);
return NULL;
}
int
mk_temp_gmk_setup (const gmk_floc *floc)
{
printf ("mk_temp plugin loaded from %s:%lu\n", floc->filenm, floc->lineno);
/* Registra a função com o nome make "mk-temp". */
gmk_add_function ("mk-temp", gen_tmpfile, 1, 1, 1);
return 1;
}
Em seguida, escreveremos um Makefile que pode construir esse objeto compartilhado, carregá-lo e usá-lo:
all:
@echo Temporary file: $(mk-temp tmpfile.)
load mk_temp.so
mk_temp.so: mk_temp.c
$(CC) -shared -fPIC -o $@ $<
No MS-Windows, devido às peculiaridades de como os objetos compartilhados são produzidos, o compilador precisa varrer a biblioteca de importação (import library) produzida ao construir o make, normalmente chamada libgnumake-version.dll.a, em que version é a versão da API do objeto carregado. Assim, a receita (recipe) para produzir um objeto compartilhado ficará, no Windows, assim (supondo que a versão da API seja 1):
mk_temp.dll: mk_temp.c
$(CC) -shared -o $@ $< -lgnumake-1
Agora, quando você executar o make, verá algo como:
$ make mk_temp plugin loaded from Makefile:4 cc -shared -fPIC -o mk_temp.so mk_temp.c Temporary filename: tmpfile.A7JEwd