A long time ago in a galaxy far,
far away ...
Este exercício-programa é continuação do Exercício-Programa 2. Você deverá fazer um documento CWEB chamado Make.w ou um programa em C (puro) de nome Make.c. O seu programa utilizar a biblioteca do SGB para a criação, representaçãoe e recuperação de um `grafo de dependências', como descrito a seguir.
O executável do seu programa deverá chamar-se Make. O `M' maiúsculo em Make é para diferenciar o se programa do make do UNIX...
meu_prompt> Make <opções>
onde <opções> consiste de zero ou mais das especificações-g -s -r <goals>
em qualquer ordem.
Opção -g Caso a opção -g seja utilizada, Make deve procurar no diretório corrente um arquivo de nome MakeFile.gb. Este arquivo contém um grafo de dependências no formato simbólico (texto) do SGB. Make deve então ler este arquivo Makefile.gb e converter o grafo para a representação interna do SGB. Tudo isto corresponde a uma simples chamada da rotina restore_graph do módulo GB_SAVE do SGB.
Se a opção -g não é utilizada, Make deve procurar no diretório corrente um arquivo de nome MakeFile. O arquivo MakeFile, que tem o formato que foi descrito no Exercício-Programa 2, deve ser lido e o correspondente grafo de dependências deve ser contruído utilizando-se o módulo GB_GRAPH do SGB.
Observação 1. Isto já foi feito no Exercício-Programa 2.
Opção -s Se Make é chamado com a opção -s, então, após a criação do grafo de dependências na representação interna do SBG, Make deve converter este grafo para a representação simbólica (texto) do SGB e salvá-lo no diretório corrente num arquivo de nome MakeFile.gb. Tudo isto corresponde a uma simples chamada da rotina gb_save do módulo GB_SAVE do SGB.
Observação 2. Isto já foi feito no Exercício-Programa 2.
Observação 3. Note que não faz sentido usar as opções -g e -s simultaneamente. Se isto ocorrer Make uma mensagem de erro deve ser enviada para a saída padrão de erro (stderr) e a execução deve ser interrompida.
Opção -r Se a opção -r é usada, Make deve percorrer o grafo de dependências construído e enviar para a saída padrão (stdout) o MakeFile que gerou este grafo de dependências.
Observação. Isto já foi feito no Exercício-Programa 2.
Reconstrução dos `objetivos' (<goals>) Um objetivo (ou goal) é o nome de um target que Make deve trabalhar para que seja atualizado. Objetivos poderão ser especificados como argumentos para o Make: <goals> pode conter o nome de qualquer target do MakeFile ou MakeFile.gb que gerou o grafo de dependências. Se <goals> não for especificado, então `by default' o primeiro target do Makefile será o objetivo. Se vários objetivos forem especificados, então Make processa um após o outro, na ordem que eles aparecem como argumentos.
Para atualizar um objetivo Make deve percorrer o grafo de dependências, a partir de um vértice que corresponde a um objetivo que foi especificado ou ao primeiro target em Makefile (caso nenhum objetivo (goal) seja especificado como argumento). Ao percorrer o grafo, Make deve:
Se um ciclo é encontrado então o reconstrução do correspondente objetivo deve ser interrompida e uma mensagem de erro deve ser enviada para a saída padrão de erro (stderr).
- verificar a existência de ciclos nesta parte do grafo; e
- executar os comandos (rebuilding commands) associados com cada vértice, caso o correspondente arquivo não exista ou se este for mais velho do que algum dos arquivos do qual ele depende.
Antes de executar os comandos (rebuilding commands) para criar ou atualizar um target, Make deve certificar-se que todos os arquivos dos quais este target depende existam e estejam atualizados (up to date). Logo, Make deve executar comandos de uma maneira, digamos, `bottom-up'. Para este fim Make deve percorrer o grafo usando busca em profundidade.
Make executa comandos para reconstruir um targets que não existem ou estão desatualizado. Logo Make deve ser capaz de executar comandos de um shell, como por exemplo o comando
gcc -g -I/usr/local/sgb/include -Wall -ansi -pedantic -O2 -lgb -c Make.c
A função
int system(const char *string)
da biblioteca padrão do C, executa os comandos contidos na cadeia de caracteres string e depois continua a execução do programa corrente. Tudo se passa como se string tivesse sido digitada e executada como um comando de um shell.O conteúdo de string depende muito do sistema operacional local. (No caso de vocês é Linux.) Como exemplo trivial o comando
system("date");
faz com que o programa date seja executado; ele devolve a data e hora correntes na saída-padrão. system retorna um valor de estado, dependente do sistema, a partir do comando executado. No sistema UNIX é o valor indicado por exit ou return.O seu Make fará freqüentemente a chamada
if (system(v->commands) != 0) { fprintf(sterr,"%s: erro na reconstrução do target %s.\n", argv[0], v->name); }Um exemplo simples do uso de system está em systemfunc.w.
O sistema operacional UNIX fornece seus serviços por meio de um conjunto de chamadas do sistema (system calls), que são funções dentro do sistema operacional que podem ser chamadas por programas usuários.
O instante da última modificação de um arquivo (file modification time), expresso como o número de segundos desde a zero horas de 1o. de janeiro de 1970 GMT (Greenwich Mean Time) até o momento da modificação, pode ser obtida através da função (system call) stat (Hmmm, acho que este negócio vai estourar alguma hora... Vocês já ouviram falar do bug do ano 2037 ou coisa do tipo?). Usando esta chamada do sistema não é difícil implementar a função
long mtime (const char *filename);
que retorna o instante da última modificação do arquivo filename ou -1 se o arquivo não existe.A função
int stat(const char *name, struct stat *stbuf);
recebe um nome-de-arquivo em name e retorna toda a informação contida no inode para esse arquivo, ou -1 se houver um erro. (O inode para um arquivo é onde toda a informação sobre o arquivo (exceto seu nome) é mantida. Um entrada em um diretório geralmente consiste em somente dois itens, o nome do arquivo e um número de inode.) Assim,struct stat stbuf; Vertex *v; [...] stat (v->name, &stbuf); [...]preenche a estrutura stbuf com a informação do inode para o nome-de-arquivo v->name. A estrutura descrevendo o valor retornado por stat está em <sys/stat.h>, e se parece com (copiei de /usr/include/sys/stat.h)
/* Expanded stat structure */ struct stat { dev_t st_dev; /* dispositivo do inode */ long st_pad1[3]; /* reserve for dev expansion, */ /* sysid definition */ ino_t st_ino; /* número do inode */ mode_t st_mode; /* bits de modo */ nlink_t st_nlink; /* número de ligações do arquivo */ uid_t st_uid; /* id. do usuário dono */ gid_t st_gid; /* id. do grupo do dono */ dev_t st_rdev; /* para arquivos especiais */ long st_pad2[2]; off_t st_size; /* tamanho arq. em caracteres */ long st_pad3; /* reserve pad for future off_t expansion */ timestruc_t st_atime; /* instante do último acesso */ timestruc_t st_mtime; /* instante da última modificação <<<<<<<<< */ timestruc_t st_ctime; /* instante da criação */ long st_blksize; long st_blocks; char st_fstype[_ST_FSTYPSZ]; long st_pad4[8]; /* expansion area */ };Os tipos como dev_t e ino_t são definidos em <sys/types.h>, que também deve ser incluído em seu programa. Veja um exemplo besta do uso desta função em statsys.w.
Assim, a função mtime pode ser implementada da seguinte maneira:
long mtime (const char *filename) { struct stat bufst; if (stat(filename, &bufst) == -1) return -1; return bufst.st_mtime; }
O seu programa deve usar a biblioteca do SGB para representação interna de um grafo de dependências. Você precisará usar os módulos GB_GRAPH e GB_SAVE. Veja exemplos de como utilizar este módulos em
http://www.ime.usp.br/~coelho/grafos/sgb/exemplos/
Um vértice v (registro ou estrutura do tipo Vertex) deve conter:
O nome commands não é um nome padrão de um dos campos de um registro do tipo Vertex. Use a seguinte macro do C:
#define commands y.S
Assim, v->commands é um nome mnemônico para v->y.S.
O nome mod_time não é um nome padrão de um dos campos de um registro do tipo Vertex. Use a seguinte macro do C:
#define mod_time w.I
Assim, v->mod_time é um nome mnemônico para v->w.I.
O nome reached não é um nome padrão de um dos campos de um
registro do tipo Vertex. Use a seguinte macro do C:
#define reached x.I
ou
#define estado x.I
Assim, v->reached ou v->estado é um nome mnemônico para v->x.I.
O nome up_to_date (não me lembro para que serve, talvez não precise) não é um nome padrão de um dos campos de um registro do tipo Vertex. Use a seguinte macro do C:
#define up_to_date z.I
Assim, v->up_to_date é um nome mnemônico para v->z.I.
Note que com estas definições você deverá inicializar a string util_types com "ZZIISIZZZZZZZZ" (os dois primeiros Z's poderão ser transformados pelas rotinas de hashing para V's). Assim, não se esqueça de usar o comando:
strcpy(grafo->util_types,"ZZIISIZZZZZZZZ");Por enquanto acho que é só. De resto, sugestões são bem-vindas.
Important: Utility fields u and v of each vertex are reserved for use by the search routine when hashing is active. You can crash the system if you try to fool around with these values yourself, or if you use any subroutines that change those fields. The first two characters in the current graph's util_types field should be VV if the hash table information is to be saved by GB_SAVE.
Warning: Users of this hash scheme must preserve the number of vertices g->n in the current graph g. If g->n is changed, the hash table will be worthless, unless hash_setup is used to rehash everything.
Caution: This hash coding scheme is system-dependent, because it uses the system's character codes. If you create a graph on a machine with ASCII code and save it with GB_SAVE, and if you subsequently ship the resulting text file to some friend whose machine does not use ASCII code, your friend will have to rebuild the hash structure with hash_setup before being able to use hash_lookup successfully.
cweave, latex,
ctangle
(se você fez em CWEB) e gcc
.
meu_prompt> make Make.texgere o documento Make.tex correspondente ao seu EP3,
meu_prompt> make Make.dviproduza o Make.dvi correspondente ao seu EP3,
meu_prompt> make Make.ccrie o arquivo Make.c correspondente ao seu EP3, e
meu_prompt> make Makeproduza o executável de nome Make correspondente ao seu EP3.
meu_prompt>tar -xvf ep3.tarcrie um diretório que tenha o seu login na rede Linux como nome. Neste diretório devem estar todos os arquivos do seu EP3, inclusive o Makefile que você usou. Se você achar necessários coloque junto um arquivo 00-leia-me, onde você descreve algo que achar necessário: como limitações e bugs no seu programa