MAC0328 Algoritmos em Grafos

Este projeto foi elaborado pelo professor Francisco Reverbel

Terceiro Exercício-Programa. Make: Epílogo

A long time ago in a galaxy far,
far away ...


Objetivos

  1. Mais manipulação da representação de um grafo através de listas de adjacências.
  2. Mais treino no uso de algumas funções básicas do Stanford GraphBase.
  3. Implementar um algoritmo que percorre um grafo através de busca em profundidade.
  4. Uso de busca em profundidade para detecção de circuitos e ordenação topológica.

Descrição

Introdução

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...

Comportamento do Make

O programa Make poderá ser chamado em uma linha de comando do shell da seguinte maneira:

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).

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.

Execução de comandos do shell

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 header da função system está em <stdlib.h>.

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.

Instante da última modificação de um arquivo

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;
}

Representaçao interna do grafo de dependências

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:

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.

Fazendo a busca por um vértice

O seu programa fará várias vezes a busca por um vértice, dado o seu nome. As seguintes rotinas podem ser usadas para isto. Elas fazem parte do módulo GB_GRAPH do SGB.

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.


Entrega e Prazos

  1. Este exercício-programa vale 20 pontos.
  2. Como sempre, somente entregue seu programa se o mesmo não apresentar erros de compilação. O processo de compilação aqui deve ser entendido como todo o processo cweave, latex, ctangle (se você fez em CWEB) e gcc.
  3. Junto com o seu programa você deve entregar um Makefile de tal forma que
    meu_prompt> make Make.tex
    gere o documento Make.tex correspondente ao seu EP3,
    meu_prompt> make Make.dvi
    produza o Make.dvi correspondente ao seu EP3,
    meu_prompt> make Make.c
    crie o arquivo Make.c correspondente ao seu EP3, e
    meu_prompt> make Make
    produza o executável de nome Make correspondente ao seu EP3.
  4. O Panda não costuma aceitar a entrega de vários arquivos. Por isto, você deve criar um arquivo ep3.tar.gz com todo o seu trabalho. Espera-se que
    meu_prompt>tar -xvf ep3.tar
    crie 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
  5. O seu EP3 deve ser depósitado no panda até o 24:00 do dia 22 ABR 2003.

[ Página principal de algoritmos em grafos. | Exercícios-programas]
Last modified: Mon Apr 14 08:49:54 EST 2003