56 (6) DOC


Część ósma
Programowanie dla zaawansowanych

W tej części:


Rozdział 56.
Zarządzanie kodem źródłowym


Peter MacKinnon i Tim Parker

W tym rozdziale:

Duże projekty programistyczne, w których skład wchodzi wiele plików źródłowych tworzonych przez różnych programistów, mogą sprawiać mnóstwo kłopotów, szczególnie jeśli to właśnie Ty jesteś odpowiedzialny za zarządzanie całością projektu. Oto najczęściej występujące problemy:

Okazuje się, że śledzenie najnowszych wersji plików i szukanie w razie potrzeby wersji sprzed godziny czy też sprzed kilku dni może zająć więcej czasu niż samo programowanie.

Nawet niewielkie aplikacje zwykle tworzone są na podstawie więcej niż jednego pliku zawierającego kod źródłowy. Podczas kompilowania programów w języku C musisz posługiwać się nie tylko plikami z kodem źródłowym, ale również plikami nagłówkowymi i plikami bibliotek. Na szczęście, dla systemu Linux dostępne jest środowisko programistyczne znakomicie upraszczające większość zadań związanych z zarządzaniem kodem źródłowym.

Program make

Program make, będący prawdopodobnie najważniejszym programem użytkowym wykorzystywanym podczas tworzenia oprogramowania, pozwala określać zależności pomiędzy plikami i uaktualniać tylko te pliki, które tego wymagają. Uaktualnianie zwykle sprowadza się do kompilacji odpowiednich plików źródłowych czy też konsolidacji plików pośrednich, ale w skład tego procesu może wchodzić również usuwanie plików tymczasowych. Proces uaktualniania może być powtarzany dziesiątki razy w trakcie tworzenia aplikacji. Zamiast zajmowania się związanymi z nim zadaniami ręcznie, można zlecić je programowi make, zyskując więcej czasu na ważniejsze czynności, takie jak tworzenie samego kodu źródłowego czy oglądanie telewizji.

Program make generuje odpowiednie polecenia na podstawie pliku opisu nazywanego najczęściej makefile. Wygenerowane polecenia są następnie przetwarzane przez interpreter poleceń powłoki. Zawartość pliku makefile to w zasadzie zestaw zadań (zwanych regułami), które należy wykonać przy uaktualnianiu projektu. Definiowanie reguł sprowadza się do określenia zależności pomiędzy plikami. W przypadku tworzenia pliku wykonywalnego z kodu w języku C w systemie Linux zwykle oznacza to kompilację plików źródłowych do postaci pośredniej, a następnie konsolidację plików pośrednich (być może wraz z dodatkowymi bibliotekami) do postaci wykonywalnej. Program make potrafi również sam określić, które z plików zostały zmodyfikowane i wymagają uaktualnienia (na podstawie czasu ostatniej modyfikacji pliku).

0x01 graphic

Nazwa makefile czy Makefile jest faktycznie nazwą pliku, który program make spodziewa się znaleźć w katalogu bieżącym. Można zażądać przetwarzania pliku o innej nazwie, ale zgodnie z tradycją (a kto chciałby się spierać z ponad trzydziestoletnią tradycją?) programiści używają pliku o nazwie makefile.

Program make jest zdecydowanie najlepiej przystosowany do programowania w języku C, ale można również wykorzystywać go przy pracy z innymi językami programowania dla Linuxa, na przykład z asemblerem czy językiem FORTRAN.

Przykładowy plik makefile

Przyjrzyjmy się prostemu przykładowi zastosowania programu make.

Polecenie

$make cosnowego

powoduje, że zostanie utworzona nowa wersja programu cosnowego. W naszym przypadku będzie to plik wykonywalny, w związku z czym proces ten będzie składał się z kompilacji i konsolidacji odpowiednich plików. Plik cosnowego określa się jako plik docelowy (ang. target) programu make. Pliki pośrednie konsolidowane do jednego pliku wykonywalnego nazywane są plikami pierwotnymi (ang. dependents) pliku cosnowego. Pliki zawierające kod źródłowy są kompilowane do postaci pośredniej, są więc również (choć nie wprost) plikami pierwotnymi pliku cosnowego.

Oto lista plików, które będą potrzebne do utworzenia programu cosnowego (ich zawartość jest dla naszego przykładu nieistotna):

Jak widać, nasz projekt nie jest duży, więc nie byłoby szczególnie trudno ręcznie kompilować i konsolidować odpowiednie pliki. Zamiast tego jednak spróbujmy utworzyć odpowiedni plik makefile, pozwalający zautomatyzować te niezbyt fascynujące czynności.

Używając swojego ulubionego edytora tekstów, utwórz plik makefile o następującej treści:

cosnowego: main.o zrobto.o szybciej.o /usr/nowe/lib/swiezy.a
cc -o cosnowego main.o zrobto.o szybciej.o /usr/nowe/lib/swiezy.a
main.o: main.c
cc -c main.c
zrobto.o: zrobto.c
cc -c zrobto.c
szybciej.o: szybciej.s
as -o szybciej.o szybciej.s
sprzataj:
rm *.o
moze.h: tak.h nie.h
cp tak.h nie.h /usr/reksio/

Format pliku makefile

Załóżmy, że wszystkie wspomniane pliki znajdują się w tym samym katalogu, co plik makefile; co zyskaliśmy, tworząc go? Każdy plik makefile (więc również ten, który przed chwilą utworzyłeś) składa się z pewnej liczby wpisów. Przykładowy plik składa się z sześciu wpisów. Pierwszy wiersz każdego wpisu definiuje zależności pomiędzy plikami. Pozwala on określić (po dwukropku), jakie pliki są niezbędne do utworzenia pliku (obiektu) docelowego o nazwie zapisanej przed dwukropkiem. Następne wiersze określają zestaw poleceń, które zostaną wykonane przez program make, gdy zajdzie potrzeba uaktualnienia danego pliku docelowego. Pojedynczy wpis ma więc postać:

plik docelowy: pliki potrzebne do jego utworzenia
(TAB) lista poleceń

Wiersze określające polecenia, które mają zostać wykonane, muszą rozpoczynać się od znaku tabulacji - jest to jedna z reguł składniowych pliku makefile. Wierszy określających polecenia może być więcej, ale każdy z nich jest wykonywany oddzielnie, w swoim własnym środowisku. Wynika stąd, że następujące polecenia:

cd gdzies
mv *.c gdzieindziej

nie zadziałają zgodnie z oczekiwaniami. Aby zapobiec tego rodzaju sytuacjom, należy polecenia, które mają zostać wykonane wspólnie, zapisywać w jednym wierszu, rozdzielając je średnikami. Można to zrobić na dwa sposoby:

plik docelowy: pliki potrzebne do jego utworzenia
polecenie1; polecenie2; polecenie3;...

albo:

plik docelowy: pliki potrzebne do jego utworzenia
polecenie1; \
polecenie2; \
polecenie3;

i tak dalej. Jeśli używasz drugiego sposobu, ukośnik musi być ostatnim znakiem wiersza, dzięki czemu zapobiegnie on interpretacji znaku końca wiersza.

0x01 graphic

Można również określić kilka rodzajów zależności pomiędzy plikami, definiując kilka wpisów dla tej samej nazwy pliku docelowego. Program make ma o wiele większe możliwości, niż opisane w tym rozdziale, ale prawdopodobnie nie będą Ci one potrzebne, dopóki nie będziesz pracował z naprawdę dużymi projektami, uwikłanymi w mnóstwo wzajemnych zależności.

Pierwszy wpis naszego pliku makefile jest kluczowy dla utworzenia pliku wykonywalnego cosnowego. Stwierdza on, że plik cosnowego może zostać uaktualniony w przypadku, gdy wszystkie pliki pośrednie i pliki biblioteki, od których jest on zależny, istnieją i którykolwiek z nich jest nowszy niż ostatnia wersja pliku cosnowego. Oczywiście, jeśli plik cosnowego nie istnieje, również zostaną podjęte wszystkie działania prowadzące do jego utworzenia. Najpierw program make sprawdzi, czy któryś z plików pierwotnych wymaganych do utworzenia pliku cosnowego wymaga uaktualnienia i, w razie potrzeby, podejmie odpowiednie działania. Proces ten powtarzany jest rekurencyjnie - program make sprawdza zależności dla plików docelowych na kolejnych poziomach, zgodnie z danymi zapisanymi w pliku makefile.

Ostatni wpis jest dość nietypowy - powoduje on skopiowanie plików tak.h i nie.h (zależnych w jakiś sposób od pliku moze.h) do katalogu domowego użytkownika reksio pod warunkiem, że zostały one zmodyfikowane. Takie rozwiązanie może być przydatne na przykład w przypadku, gdy reksio pracuje nad jakimś programem związanym z naszym projektem i powinien zawsze mieć dostęp do najnowszej wersji plików nagłówkowych. Jak widać, program make może być wykorzystany do celów innych niż tylko kompilowanie i konsolidacja programów - potrafi on wykonywać różne polecenia w oparciu o zadeklarowane zależności.

W naszym pliku makefile zdefiniowany jest również plik docelowy o nazwie sprzataj, którego uaktualnienie nie powoduje kompilowania żadnych plików. Plik ten nie wymaga istnienia żadnych plików pierwotnych - dla programu make nie stanowi to żadnego problemu. Jeśli w katalogu bieżącym nie ma pliku o nazwie sprzataj, program make wykona wszystkie polecenia powiązane z tym identyfikatorem - a więc usunie pliki pośrednie (z rozszerzeniem .o). Plik sprzataj, choć dość nietypowy (ponieważ z założenia nie jest nigdy tworzony), jest traktowany na równi z wszystkimi pozostałymi obiektami docelowymi.

Jeśli wydasz polecenie

$ make cosnowego

program make wygeneruje serię poleceń, które spowodują uaktualnienie wszystkich plików pierwotnych pliku docelowego cosnowego, a następnie uaktualni sam ten plik. Wszystkie przetwarzane polecenia są wyświetlane na ekranie. Taki sam efekt da wpisanie polecenia

$ make

ponieważ program make wywołany bez argumentów uaktualnia pierwszy plik docelowy napotkany w pliku makefile. Przetwarzane polecenia są wyświetlane na ekranie, a po wystąpieniu ewentualnego błędu działanie programu make jest wstrzymywane.

Jeśli wszystkie pliki pierwotne i plik cosnowego nie wymagają uaktualnienia, żadne polecenia nie zostaną wykonane i program make wygeneruje komunikat:

'cosnowego' is up to date

Parametrem programu make może być dowolna nazwa (lub nazwy) pliku docelowego zdefiniowanego w pliku makefile. Poszczególne pliki docelowe są uaktualniane w takiej kolejności, w jakiej pojawiły się w wierszu poleceń, z zachowaniem wszystkich reguł określonych w pliku makefile. Jeśli podana nazwa pliku docelowego nie zostanie odnaleziona w pliku makefile, zostanie wygenerowany komunikat:

$ make cosinnego
make: Don't know how to make cosinnego. Stop.

Tworzenie kilku wersji programu

Załóżmy, że chcesz utworzyć dwie wersje programu cosnowego, w większości oparte na tym samym kodzie źródłowym, ale korzystające z różnych bibliotek. Biblioteki te są zapisane w plikach źródłowych w języku C o nazwach zrobto.c i zrobtamto.c. Zamiast tworzyć oddzielne pliki makefile dla obu wersji programu, można po prostu zdefiniować dwie wersje pliku docelowego, tworzone w nieco odmienny sposób. Plik makefile miałby wówczas postać (pierwsze dwa wiersze są komentarzem, oznaczonym za pomocą symbolu #):

# Plik makefile sluzacy do tworzenia
# dwoch wersji programu cosnowego
cosnowego1: main.o zrobto.o szybciej.o /usr/nowe/lib/swiezy.a
cc -o cosnowego main.o zrobto.o szybciej.o /usr/nowe/lib/swiezy.a
cosnowego2: main.o zrobtamto.o szybciej.o /usr/nowe/lib/swiezy.a
cc -o cosnowego main.o zrobto.o szybciej.o /usr/nowe/lib/swiezy.a
main.o: main.c
cc -c main.c
zrobto.o: zrobto.c
cc -c zrobto.c
zrobtamto.o: zrobto.c
cc -c zrobtamto.c
szybciej.o: szybciej.s
as -o szybciej.o szybciej.s
sprzataj:
rm *.o
moze.h: tak.h nie.h
cp tak.h nie.h /usr/reksio/

Dzięki takiemu rozwiązaniu za pomocą jednego pliku makefile można utworzyć dwie różne wersje programu. Polecenie

$ make cosnowego1

powoduje utworzenie wersji wykonywalnej programu, używającej funkcji zapisanych w pliku zrobto.c. Program korzystający z funkcji zapisanych w pliku zrobtamto.c może zostać skompilowany za pomocą polecenia

$ make cosnowego2

Wymuszanie uaktualnienia

Możliwe jest wymuszenie uaktualnienia pliku docelowego lub też wymuszenie zaniechania jego uaktualnienia. Przykładem sytuacji, w której możesz chcieć zabronić ponownej kompilacji plików, może być skopiowanie plików źródłowych do nowego katalogu. Taka operacja powoduje zmianę czasu ostatniej modyfikacji plików, choć w rzeczywistości nie wymagają one rekompilacji. Jeśli chcesz uaktualnić czas modyfikacji plików docelowych określonych w pliku makefile, możesz posłużyć się programem touch lub uruchomić program make z opcją -t.

0x01 graphic

Jeśli chcesz przetestować swój plik makefile, możesz uruchomić program make z opcją -n. Spowoduje to wyświetlenie wszystkich poleceń, które byłyby wykonane, ale bez ich faktycznego wykonywania. Wyświetlone zostaną również komunikaty o ewentualnych błędach składniowych. Dzięki takiemu rozwiązaniu nie trzeba czekać na zakończenie trwającej czasem dość długo kompilacji.

Makropolecenia

Program make pozwala również na definiowanie makropoleceń, rozwijanych do pełnej postaci przed wykonaniem poleceń zapisanych w pliku makefile. Definicje makropoleceń mają następującą postać:

nazwa_makra = tekst

Pole tekst może zawierać na przykład nazwę pliku, katalogu, programu, który należy uruchomić, czy dowolny inny tekst. Może to również być lista plików lub stała napisowa ujęta w podwójny cudzysłów. Oto przykłady definicji makropoleceń:

LIBFILES = /usr/nowe/lib/swiezy.a
posrednie = main.o zrobto.o
CC = /usr/bin/cc
1wersja = "To jest pierwsza wersja programu cosnowego"
OPTIONS =

Zgodnie z powszechnie przyjętą konwencją, nazwy makropoleceń składają się tylko z wielkich liter, choć - jak widać w powyższym przykładzie - nie jest to wymagane. Zauważ, że w definicji makropolecenia OPTIONS po znaku równości nie występuje żaden tekst. Oznacza to, że w miejsce jego wystąpienia nie zostanie wstawiony żaden ciąg znaków. Podobnie potraktowane zostanie użycie makropolecenia, które w ogóle nie zostało zdefiniowane.

W definicjach makropoleceń można używać innych makropoleceń, na przykład tak:

KSIAZKA_DIR = /usr/book/
MOJE_ROZDZ = ${KSIAZKA_DIR}/reksio/ksiazka

Makropolecenia muszą być definiowane przed ich użyciem w wierszu określającym zależności pomiędzy plikami, ale mogą odnosić się do siebie wzajemnie w dowolnym porządku.

Program make rozpoznaje również kilka predefiniowanych makropoleceń, ułatwiających użycie najczęściej wykorzystywanych programów. Kompilator języka C jest zdefiniowany w makropoleceniu CC, natomiast znaczniki, z którymi będzie on uruchomiony, w makropoleceniu CFLAGS.

Wywołanie makropolecenia wymaga otoczenia jego nazwy nawiasami klamrowymi i poprzedzenia nawiasu otwierającego symbolem dolara ($). Oto plik makefile z poprzedniego przykładu, w którym wykorzystano możliwość definiowania makropoleceń:

# Pora przecwiczyc makropolecenia
CC = /usr/bin/cc
AS = /usr/bin/as
OBJS = main.o zrobto.o szybciej.o
TN = tak.h nie.h
#
LIB_DIR = /usr/nowe/lib
LIB_FILES = ${LIB_DIR}/swiezy.a
cosnowego: ${OBJS} ${LIB_FILES}
${CC} -o cosnowego ${OBJS} ${LIB_FILES}
main.o: main.c
${CC} -c main.c
zrobto.o: zrobto.c
${CC} -c zrobto.c
szybciej.o: szybciej.s
${AS} -o szybciej.o szybciej.s
sprzataj:
rm *.o
moze.h: ${TN}
cp tak.h nie.h /usr/reksio/

W taki sam sposób jak makropolecenia traktowane są nazwy zmiennych środowiskowych, o ile są one zdefiniowane w tym samym środowisku, w którym został uruchomiony program make. Jeśli na przykład w powłoce C zdefiniowana jest zmienna środowiskowa o nazwie KOPIA

$ setenv KOPIA /usr/nowe/backup
można użyć jej w pliku makefile. Definicja
INNA_KOPIA = ${KOPIA}/zeszly_tydz

spowoduje więc, że makropolecenie INNA_KOPIA będzie równoważne tekstowi

/usr/nowe/backup/zeszly_tydz

Możliwe jest dalsze zmniejszenie rozmiaru pliku makefile. Nie trzeba na przykład określać położenia kompilatorów języka C i asemblera - są one znane programowi make. Można również używać dwóch wewnętrznych makropoleceń o nazwach $@ i $?. Makropolecenie $@ jest zawsze równoważne nazwie aktualnie przetwarzanego pliku docelowego, natomiast w skład makropolecenia $? wchodzą nazwy wszystkich plików pierwotnych nowszych od bieżącego pliku docelowego. Oba te makropolecenia mogą być używane tylko w wierszach określających polecenia, które mają zostać wykonane w celu uaktualnienia pliku. Przykładowy wpis:

cosnowego: ${OBJS} ${LIB_FILES}
${CC} -o $@ ${OBJS} ${LIB_FILES}

jest więc przy wywołaniu polecenia make cosnowego równoważny następującemu:

/usr/bin/cc -o cosnowego main.o zrobto.o szybciej.o /usr/nowe/lib

*/swiezy.a

Makropolecenie $? ma jeszcze większe możliwości. Można je na przykład zastosować do kopiowania plików tak.h i nie.h do katalogu domowego użytkownika reksio tylko w przypadku, gdy zostały one zmodyfikowane. Zapisane w pliku makefile polecenie

moze.h: ${TN}
cp $? /usr/reksio/

będzie więc równoważne poleceniu

cp nie.h /usr/reksio/

w przypadku, gdy od czasu ostatniej aktualizacji programu cosnowego zmodyfikowany zostanie tylko plik nie.h; natomiast w przypadku, gdy oba pliki nie.h i tak.h zostaną zmodyfikowane, zostanie ono rozwinięte do postaci

cp tak.h nie.h /usr/reksio/

Używając podanych wyżej informacji, można nieco zredukować rozmiar pliku makefile:

# Nowa wersja pliku makefile
OBJS = main.o zrobto.o szybciej.o
TN = tak.h nie.h
LIB_DIR = /usr/nowe/lib
LIB_FILES = ${LIB_DIR}/swiezy.a
cosnowego: ${OBJS} ${LIB_FILES}
${CC} -o $@ ${OBJS} ${LIB_FILES}
main.o: main.c
${CC} -c $?
zrobto.o: zrobto.c
${CC} -c $?
szybciej.o: szybciej.s
${AS} -o $@ $?
sprzataj:
rm *.o
moze.h: ${TN}
cp $? /usr/reksio/

Reguły przyrostkowe

Jak wspomniano wcześniej w podrozdziale „Makropolecenia”, program make nie wymaga określenia reguł dla każdego z plików docelowych z osobna. Ponieważ program ten został zaprojektowany z myślą o uproszczeniu pracy związanej z tworzeniem oprogramowania w systemie Linux, wie on, w jaki sposób działają kompilatory (w szczególności kompilator języka C). Program make wie na przykład, że kompilator języka C spodziewa się otrzymania pliku źródłowego w języku C (którego nazwa ma rozszerzenie .c) i na jego podstawie generuje plik pośredni z rozszerzeniem .o. Tego typu reguła (nazywana regułą przyrostkową) oparta jest na rozszerzeniu nazwy pliku - na jego podstawie podejmowana jest decyzja o tym, jakie polecenia mają zostać wykonane.

Program make opiera swoje działanie na wielu zdefiniowanych wewnętrznie regułach przyrostkowych; większość z nich określa sposoby kompilacji kodu źródłowego i konsolidacji plików pośrednich. Oto reguły przyrostkowe stosowane domyślnie w Twoim pliku makefile:

. SUFFIXES: .o .c .s
.c.o:
${CC} ${CFLAGS} -c $<
.s.o:
${AS} ${ASFLAGS} -o $@ $<

Pierwszy wiersz określa, dla jakich rozszerzeń plików należy zastosować reguły przyrostkowe jeśli żadne reguły nie zostały zdefiniowane bezpośrednio. Drugi wiersz nakazuje programowi make uruchomić kompilację plików z rozszerzeniem .c w przypadku, gdy odpowiadające im pliki z rozszerzeniem .o są nieaktualne. Trzeci pełni funkcję taką, jak drugi, ale dla plików asemblera z rozszerzeniem .s. Makropolecenie $< ma funkcję zbliżoną do omówionego wcześniej $?, ale może być używane tylko w regułach przyrostkowych. Reprezentuje ono nazwę aktualnie opracowywanego pliku docelowego.

Dzięki regułom przyrostkowym zadanie programisty ogranicza się do wprowadzenia do pliku makefile nazw wszystkich plików pośrednich; program make sam zajmuje się resztą. Jeśli plik main.o jest nieaktualny, program make automatycznie skompiluje plik main.c. Taka sama reguła odnosi się do pliku szybciej.o. Po uaktualnieniu plików pośrednich można przystąpić do uaktualnienia pliku cosnowego.

Możliwe jest również tworzenie własnych reguł przyrostkowych, dzięki czemu można wymusić podejmowanie innych niż domyślne działań. Jeśli na przykład po skompilowaniu pliki powinny również zostać skopiowane do oddzielnego katalogu, można w pliku makefile umieścić następującą regułę:

.c.o:
${CC} ${CFLAGS} -c $<
cp $@ kopia

Makropolecenie $@, jak już wiesz, odpowiada nazwie aktualnie opracowywanego pliku docelowego. W powyższym poleceniu plik docelowy ma rozszerzenie .o, natomiast plik pierwotny - .c.

Spróbujmy przepisać nasz plik makefile (już po raz ostatni), korzystając z reguł przyrostkowych.

# Ostatnia wersja
OBJS = main.o zrobto.o szybciej.o
TN = tak.h nie.h
LIB_FILES = /usr/nowe/lib/swiezy.a
cosnowego: ${OBJS} ${LIB_FILES}
${CC} -o $@ ${OBJS} ${LIB_FILES}
sprzataj:
rm *.o
moze.h: ${TN}
cp $? /usr/reksio/

Powyższy plik makefile działa dokładnie tak samo, jak pierwsza z przedstawionych wersji - program cosnowego można skompilować wydając polecenie

$ make cosnowego

Można również skompilować jeden z jego fragmentów, na przykład tak:

$ make szybciej.o

Program make został w tym rozdziale omówiony bardzo pobieżnie. Jeśli chcesz dowiedzieć się czegoś więcej o jego możliwościach, powinieneś przejrzeć dotyczące go strony man.

RCS

Innym bardzo ważnym aspektem tworzenia oprogramowania jest zarządzanie kodem źródłowym w trakcie jego ewoluowania. Bez względu na rodzaj tworzonej aplikacji, często zdarza się, że jej opracowywanie jest kontynuowane i pojawiają się nowe, poprawione i ulepszone wersje. Przy tworzeniu większych aplikacji pracuje zwykle kilku programistów, co dodatkowo komplikuje zagadnienie zarządzania kodem źródłowym. Jeśli nie zostanie zastosowany żaden program zajmujący się tymi zadaniami, bardzo łatwo zagubić się przy aktualizowaniu wersji plików. Może to prowadzić do sytuacji, w której modyfikacje są tracone albo powtórnie opracowywane przez różnych programistów. Na szczęście w systemie Linux z pomocą przychodzą takie programy, jak RCS (Revision Control System, system kontroli wersji).

RCS to tylko jeden z licznych programów tego typu - istnieje również wiele innych; niektóre z nich są darmowe, inne komercyjne. Pierwszym UNIX-owym programem do zarządzania kodem źródłowym nadającym się do poważniejszych zastosowań był SCCS (Source Code Control System), używany zresztą po dziś dzień. RCS jest systemem o nieco większych możliwościach, implementującym rozwiązania, których brakowało w SCCS. Systemy RCS dostępne są w wersjach dla większości platform, włączając wszystkie wersje UNIX-a, DOS, Windows i Windows NT. Jeśli pracujesz nad projektem tworzonym przez większą liczbę programistów, przyda Ci się wersja pozwalająca na pracę w sieci, o nazwie CVS (Concurrent Versions System). CVS jest dołączany do wielu wersji Linuxa. Polecenia używane w systemie CVS różnią się od poleceń wykorzystywanych podczas pracy z RCS, ale ponieważ większość czytelników nie będzie na razie potrzebowała wersji sieciowej, w tym rozdziale skoncentrujemy się na systemie RCS. Jeśli gryzie Cię ciekawość, z wersjami Linuxa wyposażonymi w system CVS dostarczane są też odpowiednie strony man.

Program RCS rejestruje informacje o wersjach plików, kontrolując dostęp do nich. Każdy użytkownik próbujący zmodyfikować określony plik musi zarejestrować się w systemie RCS i podać powód modyfikacji. RCS zapisuje te informacje i dane o wprowadzonych poprawkach w osobnym pliku. Ponieważ poprawki są rejestrowane w pliku innym niż oryginalny, można w razie potrzeby z łatwością wrócić do pierwotnej wersji kodu źródłowego. Takie rozwiązanie pozwala również zaoszczędzić miejsce na dysku, ponieważ nie trzeba wykonywać kopii całych plików. Jest to szczególnie widoczne przy modyfikacjach obejmujących tylko kilka wierszy kodu źródłowego, natomiast mało przydaje się, gdy istnieje tylko kilka znacznie różniących się między sobą wersji programu.

Delty

Zestaw modyfikacji zapisywanych przez system RCS do odpowiedniego pliku nazywany jest deltą. Numer wersji może mieć dwie formy. Pierwsza z nich składa się z numeru wydania i numeru poziomu. Numer wydania zmieniany jest zwykle po wprowadzeniu jakichś znaczących zmian w kodzie źródłowym. Po utworzeniu pliku RCS, otrzymuje on numer 1.1, oznaczający pierwszy poziom pierwszego wydania. RCS będzie następnie przy wprowadzaniu modyfikacji odpowiednio zwiększał numer poziomu (na 1.2, 1.3 itd.). Można również wymusić zmianę numeru wydania programu.

Druga forma numeru wersji programu również zawiera numer wydania i poziom, ale dodatkowo zawiera jeszcze numer kolejny poprawki. Można używać go, jeśli na przykład opracowałeś program, co do którego klient zgłasza zastrzeżenia, ale kolejnych poprawionych wersji nie chcesz numerować tak, jakby były wersjami „oficjalnymi”. Choć następna wersja może również zawierać wprowadzone poprawki, jej wydanie może zostać opóźnione przez prace nad dodaniem nowej funkcjonalności programu. Z tego powodu czasem warto dodać do numeru wersji nowy składnik, zwiększany wraz z wprowadzaniem poprawek. Jeśli na przykład planowałeś wydanie wersji o numerach 3.1, 3.2, 3.3, 3.4 itd., a w którymś momencie zorientowałeś się, że w wersji 3.3 jest błąd, który wymaga wydania nowej wersji, nie zawierającej jeszcze rozwiązań planowanych w wersji 3.4, możesz wydać wersję o numerze 3.3.1.1, następnie 3.3.1.2 itd.

0x01 graphic

Zwykle każdy nowy numer wersji powinien określać kompletny zestaw poprawek. Oznacza to, że kod w każdej z wersji powinien być przetestowany i powinny zostać z niego usunięte wszystkie błędy. Nie powinieneś zmieniać numeru wersji z 2.0 na 3.0 tylko dlatego, że usunąłeś kilka błędów w wersji 2.0. Główne numery wersji są zmieniane po dodaniu do programów nowych funkcji, a nie po skorygowaniu występujących usterek.

0x01 graphic

Czy istnieją programy nie zawierające błędów? Na pewno nie są ich pozbawione większe aplikacje, w których niektóre usterki ujawniają się dopiero po scaleniu fragmentów kodu tworzonych przez różnych programistów. Celem programisty powinno być usunięcie wszystkich błędów w tworzonej przez niego części programu. Choć prawdopodobnie nigdy nie uda się wyeliminować wszystkich usterek, można ograniczyć ich liczbę, dzięki czemu łatwiej będzie wykrywać i usuwać pozostałe.

Tworzenie pliku RCS

Załóżmy, że chcesz pracować nad programem, którego kod źródłowy zapisany w pliku loty.c ma postać:

/* Plik przykladowy dla systemu RCS
#include <stdio.h>
main()
{
printf("Program najwyższego lotu...\n");
}

Pierwszym krokiem powinno być utworzenie katalogu RCS:

$mkdir RCS

W tym katalogu będą przechowywane pliki systemu RCS. Następnie należy poinformować ten system, że dany plik ma być nadzorowany. Służy do tego polecenie ci (ang. check-in):

$ ci loty.c

Po jego wydaniu należy wprowadzić odpowiedni komentarz dotyczący modyfikacji - wraz z innymi informacjami zostanie on zapisany w pliku o nazwie loty.c,v w katalogu RCS. Zawartość oryginalnego pliku zostanie skopiowana do pliku oznaczonego numerem wersji 1.1. Po wydaniu kolejnego polecenia ci system RCS usunie kopię roboczą z katalogu RCS.

Odzyskiwanie pliku RCS

Jeśli chcesz uzyskać kopię pliku nadzorowanego przez RCS, powinieneś użyć polecenia co (ang. check-out). Polecenie to wydane bez żadnych argumentów daje wersję pliku przeznaczoną tylko do odczytu, której nie można edytować. Jeżeli chcesz modyfikować otrzymany plik, powinieneś użyć opcji -l:

$ co -l loty.c

Po zakończeniu modyfikowania pliku można ponownie załączyć nadzorowanie go za pomocą polecenia ci. Zostaniesz poproszony o komentarz dotyczący wprowadzonych zmian. Nadzorowany plik zostanie oznaczony numerem wersji 1.2.

Oznaczenia numeru wersji stosowane przez system RCS składają się z numeru wydania, poziomu (ang. level) i dwóch składników określających numer poprawki (ang. branch i sequence). Polecenia RCS operują domyślnie na najświeższej wersji pliku, ale można wymusić użycie innej wersji. Jeśli na przykład najnowsza wersja pliku loty.c ma numer 2.7, a modyfikacje, które wprowadziłeś, są na tyle znaczące, że postanowiłeś zmienić numer wydania, powinieneś wydać polecenie ci z opcją -r i numerem nowego wydania:

$ ci -r3 loty.c

Powyższe polecenie spowoduje oznaczenie pliku numerem wersji 3.1. Można również otworzyć nową gałąź poprawek rozpoczynającą się na poziomie 2.7 za pomocą polecenia:

$ ci -r2.7.1 loty.c

Polecenie rcs z opcją -o służy do usuwania niepotrzebnych, nieaktualnych wersji plików. Aby na przykład usunąć wersję o numerze 2.6, powinieneś wydać polecenie

$ rcs -o2.6 loty.c

Słowa kluczowe

Częścią pliku nadzorowanego przez system RCS mogą być również słowa kluczowe. Służą one do osadzania w pliku takich informacji, jak dane o autorze, data utworzenia poszczególnych wersji pliku itp. - dane te mogą być odczytane za pomocą polecenia ident. Słowa kluczowe należy wprowadzać bezpośrednio do aktualnie opracowywanej kopii pliku. Przy wydawaniu poleceń ci i co do słów kluczowych systemu RCS dołączane są odpowiednie wartości. Słowa kluczowe należy otaczać z obu stron symbolami dolara:

$słowo_kluczowe$

Są one przekształcane do postaci:

$słowo_kluczowe: wartość $

Oto niektóre ze słów kluczowych rozpoznawanych przez system RCS:

$Author$ użytkownik, który jest autorem danej wersji;

$Date$ data i czas zarejestrowania wersji;

$Log$ wszystkie komentarze odnoszące się do danego pliku;

$Revision$ numer wersji pliku.

Jeśli słowa te byłyby używane w pliku loty.c, polecenie ident mogłoby zwrócić na przykład takie informacje:

$ ident loty.c
$Author: reksio $
$Date: 97/01/15 23:15:32 $
$Log: loty.c,v $
# Revision 1.2 97/01/15 23:15:32 reksio
# Drobne modyfikacje
#
# Revision 1.1 97/01/15 23:10:12 reksio
# Utworzenie pliku loty.c
#
$Revision: 1.2 $

Uzyskiwanie informacji o wersji z pliku RCS

Czasem zamiast informacji opartych na słowach kluczowych RCS bardziej potrzebne są ogólniejsze, podsumowujące dane, podawane przez polecenie rlog z opcją -t:

$ rlog -t loty.c
RCS file: loty.c,v; Working file: loty.c
head: 3.2
locks: reksio: 2.1; strict
access list: rick tim
symbolic names:
comment leader: " * "
total revisions: 10;
description:
Tak... Program najwyzszego lotu...
=================================================================

Pole head zawiera informację o numerze ostatniej wersji pliku. W polu locks zawarte są identyfikatory użytkowników, którzy modyfikowali plik oraz typ stosowanego zabezpieczenia (bezpośredni czy pośredni dla właściciela pliku RCS). Pole access list zawiera identyfikatory użytkowników, którzy mają prawo tworzenia delt danego pliku - jak je zmienić, dowiesz się w następnym podrozdziale.

Dostęp do plików RCS

Jedną z ważniejszych funkcji systemu RCS jest ograniczanie dostępu do tych plików, które nie powinny być modyfikowane przez danego użytkownika. RCS przechowuje listę użytkowników uprawnionych do modyfikacji każdego pliku z osobna. Początkowo lista ta jest pusta, co oznacza, że wszyscy użytkownicy mogą wprowadzać poprawki do nadzorowanego pliku. Aby umożliwić modyfikowanie pliku tylko określonym użytkownikom lub grupom, należy wydać polecenie rcs z opcją -a. Przykładowe polecenie

$ rcs -arick,tim loty.c

spowoduje, że prawo modyfikacji pliku loty.c będą mieli tylko użytkownicy o identyfikatorach tim i rick. Jeśli zmienisz zdanie i uznasz, że rick nie powinien jednak wtrącać się do tworzenia Twojego wspaniałego programu, możesz zabronić mu dostępu do niego za pomocą opcji -e programu rcs:

$ rcs -erick loty.c

Jeśli w przypływie paranoi stwierdzisz, że nikt nie powinien mieć możliwości modyfikowania Twojego programu, możesz po prostu go zablokować (co uniemożliwi wprowadzanie zmian nawet właścicielowi pliku). Aby zablokować modyfikowanie drugiego wydania programu loty.c, wydaj polecenie:

$ rcs -e -L2 loty.c

Właściciel pliku może wprowadzić modyfikacje tylko po jawnym zablokowaniu pliku, zarówno przy wydawaniu polecenia ci, jak i co.

Porównywanie wersji
i łączenie poprawek

System RCS umożliwia porównywanie poszczególnych wersji plików, co pozwala łatwo zorientować się, jakie zmiany zostały wprowadzone. Dzięki takiemu rozwiązaniu można bezpiecznie łączyć poprawki wprowadzane do pliku przez różne osoby. Do wyświetlania różnic pomiędzy poszczególnymi wersjami pliku nadzorowanego przez RCS bądź też pomiędzy którąś z wersji pliku a wersją najnowszą służy polecenie rcsdiff. Polecenie

$ rcsdiff -r1.2 -r1.5 loty.c

powoduje wyświetlenie różnic pomiędzy wersjami 1.2 i 1.5 pliku loty.c, na przykład tak:

RCS file: loty.c,v
retrieving revision 1.1
rdiff -r 1.2 -r1.5 loty.c
6a7,8
>
> /* Choc w zasadzie nie jest szczegolnie ciekawy */

Powyższe dane wskazują, że poszczególne wersje różnią się tylko tym, że po wierszu szóstym zostały dodane dwa nowe wiersze (które zostały wyświetlone). Jeśli chcesz sprawdzić, czym pierwotna wersja pliku (określona w polu head) różni się od wersji zarejestrowanej ostatnio, wydaj polecenie:

$ rcsdiff loty.c

Po stwierdzeniu, że pomiędzy poprawkami wniesionymi przez Ciebie i innych programistów nie ma potencjalnych konfliktów, możesz zdecydować się na połączenie poszczególnych wersji. Służy do tego polecenie rcsmerge. Składnia tego polecenia wymaga podania nazwy jednego lub dwóch plików, określających wersje, które należy połączyć i trzeciej nazwy, określającej nazwę pliku roboczego (w poniższym przykładzie - loty.c). Wydanie polecenia

$ rcsmerge -r1.3 -r1.6 loty.c

powoduje wygenerowanie następujących komunikatów:

RCS file: loty.c,v
retrieving version 1.3
retrieving version 1.6
Merging differences between 1.3 and 1.6 into loty.c

Jeśli wprowadzone modyfikacje kolidują ze sobą, program rcsmerge wstawia do pliku obie wersje, oznaczając odpowiednio ich pochodzenie. Zaistniały konflikt trzeba rozwiązać ręcznie, edytując powstały plik przed ponownym nakazaniem nadzorowania go przez system RCS.

0x01 graphic

Porządek, w jakim podawane są wersje plików, które należy połączyć, jest istotny. Jeśli po opcji -r najpierw podany zostanie numer późniejszej wersji, wszystkie wprowadzone w niej zmiany (w stosunku do wersji wcześniejszej) zostaną wycofane.

Praca z programem make
i systemem RCS

Program make jest przystosowany do współpracy z systemem RCS, wraz z którym tworzy kompletne pod wieloma względami środowisko programistyczne. Mimo wszystko jednak używanie obu tych programów równocześnie może stwarzać pewne problemy, szczególnie jeśli nad projektem pracuje kilku programistów. Ujawniają się one szczególnie na etapie testowania programu - wtedy programista potrzebuje stabilnych, nie zmieniających się wersji plików. Testowanie może stwarzać większe trudności natury organizacyjnej niż jakakolwiek inna faza tworzenia projektu. Dla pojedynczego programisty jednak problemy te nie mają żadnego znaczenia, a współpraca programu make i systemu RCS znakomicie ułatwia tworzenie aplikacji, szczególnie w systemie Linux.

Aby umożliwić opracowywanie plików RCS przez program make, należy w pliku makefile zdefiniować regułę przyrostkową dla rozszerzenia ,v. Pliki z rozszerzeniem ,v są właśnie plikami systemu RCS. Jeśli pliki nadzorowane przez system RCS mają być kompilowane za pomocą kompilatora języka C, w pliku makefile powinny znaleźć się następujące reguły:

CO = co
.c,v.o:
${CO} $<
${CC} ${CFLAGS} -c $*.c
- rm -f $*.c

Makropolecenie CO jest równoważne poleceniu co. Makropolecenie $*.c jest niezbędne, ponieważ program make automatycznie usuwa rozszerzenie .c. Myślnik poprzedzający polecenie rm powoduje, że program make będzie kontynuował działanie nawet w przypadku, gdy wykonanie polecenia rm zakończy się błędem. Jeśli na przykład system RCS nadzorowałby plik o nazwie main.c, program make wygenerowałby następujące polecenia:

co main.c
cc -O -c main.c
rm -f main.c

Podsumowanie

Programy make i RCS są najważniejszymi aplikacjami służącymi do zarządzania procesem tworzenia oprogramowania. Program make potrafi generować polecenia kompilujące, konsolidujące i służące do wykonywania innych czynności o podobnym charakterze. Dzięki temu, że pozwala on na ustalanie zależności pomiędzy poszczególnymi plikami, możliwe jest kompilowanie tylko tych fragmentów projektu, które wymagają uaktualnienia. System RCS to zestaw programów służących do zarządzania kodem źródłowym, dzięki którym kilku programistów może bez kłopotów pracować nad pojedynczym projektem. Zachowuje on dane o poszczególnych modyfikacjach kodu źródłowego.

Inną korzyścią wynikającą ze stosowania systemu RCS jest oszczędność miejsca zajmowanego przez projekt na dysku twardym. System CVS jest rozszerzeniem systemu RCS, pozwalającym na automatyczne łączenie kilku wersji pliku. Dzięki temu możliwa jest równoległa praca kilku programistów nad jednym plikiem źródłowym, choć system CVS nie zwalnia ich od konieczności rozwiązywania występujących konfliktów.

Po zapoznaniu się z informacjami dotyczącymi systemów zarządzania kodem źródłowym, możesz przejść do rozdziałów omawiających poszczególne języki programowania.

Jeśli chcesz zastosować system RCS i program make do tworzenia programów w języku C lub C++, przejdź do rozdziału 26. „Programowanie w języku C” lub 27. „Programowanie w języku C++”.

W rozdziale 30. „Inne kompilatory” znajdziesz informacje o innych językach programowania dostępnych dla systemów linuxowych.

Rozdział 31. „Smalltalk/X” omawia opartą na interfejsie graficznym wersję popularnego języka obiektowego Smalltalk.

834 E:\Moje dokumenty\HELION\Linux Unleashed\Indeks\56.DOC

E:\Moje dokumenty\HELION\Linux Unleashed\Indeks\56.DOC 833

834 Część VIII Programowanie dla zaawansowanych

Rozdzia³ 56. Zarządzanie kodem źródłowym 833



Wyszukiwarka

Podobne podstrony:
15 (56) DOC
FIZYK 56 DOC
F 56 1 (4) DOC
FIZ3 56 (2) DOC
F 56 2 (10) DOC
F 56 1 (14) DOC
56 57 1 DOC
56 57 DOC
europejski system energetyczny doc
Dz U 09 56 461 Warunki Techniczne zmiany
abc 56 58 Frezarki
KLASA 1 POZIOM ROZSZERZONY doc Nieznany

więcej podobnych podstron