|
Finnish Linux User Group FLUG ry |
|
|
Linux-ohjelmointiTämä dokumentti on tarkoitettu ensisijaisesti niille, joilla on jo jonkinlainen käsitys C- tai C++-ohjelmoinnista, ja jotka ovat tutustumassa Linux-ohjelmointiin, mutta toivon, että artikkeli antaisi jotain myös kokeneemmille käyttäjille. Tässä artikkelissa käydään aluksi läpi yksinkertaisen C-ohjelman sekä vastaavan C++-ohjelman tekeminen GNU/Linux-ympäristössä. Artikkelissa ei keskitytä C- tai C++-ohjelmointiin sinällään, vaan siihen, millä välineillä ohjelmointi GNU-ympäristössä tapahtuu ja millä työkaluilla tätä ohjelmointiprojektia hallitaan. Artikkelissa ei sinällään ole paljoakaan erityisesti Linux-riippuvaista materiaalia. Samat ohjeet pätevät pitkälti muihinkin Unixeihin, joissa käytetään vapaita GNU-työkaluohjelmia. Dokumentissa on käytetty tyyliarkkeja (style sheets) tekstin muotoiluun. Suosittelen tyyliarkkeja tukevaa selainta, jotta koodinpätkät näkyisivät selvempinä. © Jukka Suomela 1998-1999. Ks. kopiointi. Viimeisimmät olennaiset muutokset tehty 1999-10-12. Ks. historia. Sisällysluettelo
Kulttuurishokki?DOS/Windows-maailmasta tulevalle koodaajalle Linux-ohjelmointi voi tuntua aluksi kovin vieraalta. Standardi C-kieli on sama, mutta kaikki työkalut voivat poiketa kovastikin tutuista ohjelmointivälineistä. Kun Windows-maailmassa trendi on kohti integroituja hiiriohjattuja kehitysympäristöjä, Linuxissa käytetään Unix-perinteiden mukaisesti tehokkaita komentorivityökaluja, jotka kukin hoitavat oman työnsä hyvin. Samoin esimerkiksi Windowsista tutut tavat toteuttaa graafisia käyttöliittymiä eivät välttämättä taivukaan suoraan Linux-maailmaan. Kulttuurishokki voi olla suuri, mutta suosittelen, että uhraat hiukan vapaa-aikaasi Linux-ohjelmointiin paneutumiseen. Vaikket koskaan alkaisikaan toden teolla koodaamaan Linux-sovelluksia, ei oman näkökulman avartaminen varmastikaan ole haitaksi. Linux-ympäristössä on ohjelmoijan kannalta muutamia merkittäviä etuja. Yksi merkittävimpiä on se, että lähes kaikkien Linux-pakettien mukana tulee suoraan kaikki tarvittavat ohjelmointivälineet. FSF:n ideologioiden mukainen ohjelmistojen vapaus on toteutunut erittäin hyvin Linux-järjestelmissä. Paitsi että lähes kaikki ohjelmat ovat vapaasti kopioitavissa, niihin saa myös hyvin usein lähdekoodin, joten muiden tekemiin ohjelmiin tutustuminen onnistuu helposti. Lisäksi alusta on vakaa, eikä nyrjähdä heti ensimmäisestä pieleenmenneestä kokeilusta. Tässä artikkelissa oletetaan, että Linux on sinulle edes jollain tavalla tuttu. Jos olet aivan aloittelija, kannattaa sinun varmasti ensin tutustua hiukan Linuxin perusteisiin ja peruskäyttöön. EsimerkkiprojektiTutustumme ensin käytettäviin välineisiin yksinkertaisen maailmaatervehtivän esimerkkiohjelman avulla. Esimerkkiohjelma on listattu tämän tekstin lopussa. Ohjelmasta on sekä C- että C++-versio ja tutustumme rinnan ohjelmointiin molemmilla välineillä. PerustoimetUnixin perustyökaluja ei opeteta tässä sen tarkemmin. Ohessa kuitenkin pikainen kertaus tärkeimmistä:
Jokaiselle uudelle projektille kannattaa luoda aina oma hakemisto. Hakemistot ovat halpoja, niiden kanssa on turha pihtailla. Luodaan nyt siis kotihakemistoon uusi hakemisto "hw" (mkdir hw), siirrytään sinne (cd hw) ja luodaan tuohon hakemistoon alihakemistot "hello" ja "world" (mkdir hello world). Opasteet
Manuaalisivut ovat perinteinen tapa toteuttaa komentojen opasteet Unix-maailmassa. Manuaaleista löytyy tietoa paitsi komennoista, myös esimerkiksi monista kirjastofunktioista - kokeile esimerkiksi komentoa man fgets. Info-sivut ovat lähinnä GNU-maailman kummajaisia. Info-dokumentit eroavat man-sivuista käyttäjän kannalta lähinnä siinä, että info-sivut ovat hypertekstiä (voivat sisältää linkkejä aivan kuten weppisivutkin) kun taas man-sivuilla ei varsinaisia linkkejä ole. EditointiemacsEtenkin ohjelmoijien keskuudessa emacs on varmasti kaikkein suosituin editori. Emacs on erittäin monipuolinen (ja samalla myös valitettavan raskas) editori. Emacsin näppäintoiminnot eivät välttämättä aukea aivan heti, mutta kärsivällisyys palkitsee. Kun emacsin peruskäytön on kerran opetellut, se auttaa takuulla niin ohjelmoinnissa kuin muissakin editointitarpeissa. Komennolla emacs helloworld.c emacs käynnistyy ja avaa komentorivillä kerrotun tiedoston muokattavaksi. Emacs on normaali kokoruutueditori. Näppäilyllä C-x C-s (eli painat Control-näppäimen kanssa ensin x:ää ja sitten s:ää) voit tallentaa tiedoston ja näppäilyllä C-x C-c sulkea emacsin. Yksi emacsin lukuisista toiminnoista on info-lukija, jolla voi selailla info-sivuja samaan tapaan kuin info-komennollakin. Tämä toiminto käynnistyy näppäilemällä C-h i (siis painat Control-näppäimen kanssa h:ta, vapautata Control-näppäimen ja painat sitten i:tä). Info-sivuilta löytyy myös emacsin omat opasteet. Emacs tarjoaa lohtua myös Windows-maailman graafisiin editoreihin ja hiiren käyttöön tottuneille. X-ympäristössä emacs avautuu omaan ikkunaansa ja tarjoaa käyttäjälle mm. hiirellä käytettävät valikot, tekstin maalaamisen hiirellä ja monia muita toimintoja. Myös XEmacsia kannattanee kokeilla. XEmacs on GNU Emacsin (eli sen "tavallisen" emacsin) pohjalta kehitetty, pitkälti samankaltainen editori. XEmacs tarjoaa mm. hiukan aloittelijaystävällisemmän tavan muokata editorin asetuksia. Se, kumpi kaksikosta on parempi, jääkööt jokaisen itsensä ratkaistavaksi... XEmacs käynnistyy komennolla xemacs. Lisätietoja emacsin käytöstä löytyy esimerkiksi TKK:n Emacs-oppaasta. viToinen koulukunta emacs-käyttäjien ohella on vi-käyttäjät. Jos emacs tuntuu aluksi hiukan kryptiseltä, vi on takuulla sitä. Vi on kuitenkin tehokas osaavissa käsissä, ja vi löytyy lähes miltä tahansa Unix-koneelta, jonka eteen ikinä joudut. Linuxin mukana tulee tyypillisesti erittäin monipuolinen ja kätevä vi:n versio, vim. Allekirjoittanut on entinen emacsoija ja nykyinen vim-käännynnäinen. Molemmilla maailmoilla on etunsa, mutta vimin keveys ajoi minun kohdallani voiton. Jos kiinnostus heräsi (tai jos onnistuit käynnistämään uteliaisuuttasi vi:n, etkä keksi, miten pääset siitä pois...), käy tutustumassa Vi Lovers Home Page:en. Eikö ole mitään yksinkertaisempaa?Tyypillisen Linux-paketin mukana tulee toki paljon muitakin editoreita. Esimerkkejä näistä ovat pico, jed ja joe. Näistä erityisesti pico on hyvin aloittelijaystävällinen, mutta vakavampaan käyttöön esimerkiksi ohjelmoijan editorina siitä ei käytännössä ole. Näitä muitakin vaihtoehtoja kannattaa silti kokeilla, jos aloittelevan Linux-ohjelmoijan ura uhkaa kompastua emacsin ja vi:n kummallisuuksiin. Lopulta editorin valinnan ratkaisee kuitenkin omat mieltymykset. Kannattaa kuitenkin muistaa, että opetteluun uhratut tunnit tulevat nopeasti takaisin, kunhan on oppinut hyödyntämään monipuolisen editorin tehokkaita toimintoja. Kääntäminen ja linkkausPerinteisesti ohjelmien kääntäminen lähdekoodista ajettavaksi ohjelmaksi suoritetaan kahdessa vaiheessa:
Seuraavassa tutustutaan siihen, mitä näissä vaiheissa tapahtuu ja miten se käytännössä tehdään GNU/Linux-ympäristössä. GNU-projektin C-kääntäjä gccGNU-projektin C-kääntäjä, gcc, on ylivoimaisesti yleisin C-kääntäjä Linux-ympäristössä. gcc sisältää sekä C- että C++-kääntäjän. Se, käännetäänkö C- vai C++-ohjelmaa, riippuu kääntämiskomennosta ja tiedoston päätteestä. Varminta on noudattaa seuraavia sääntöjä:
helloworld:in kääntäminen ja linkkausTutkitaan seuraavaksi vaiheittain, kuinka saamme ensin käännettyä lähdekoodit objektitiedostoiksi ja sen jälkeen linkitettyä ne.
Mitä siis teimme?Käännösvaiheessa käytettiin -c-vipua. Tämä kertoo gcc:lle, että haluamme ainoastaan kääntää ohjelman objektitiedostoksi, emme linkittää. Käännösvaiheessa gcc tulostaa virheilmoituksia, jos se havaitsee C-koodissa syntaksivirheitä - virhetilanteessa yksinkertaisesti korjaat virheellisen lähdekoodin ja yrität käännöstä uudelleen. Kuten hakemistolistauksesta näimme, käännöksen tuloksena syntyi kustakin lähdekooditiedostosta abc.c (tai abc.cc) yksi objektitiedosto, joka on nimetty automaattisesti tyyliin abc.o. Objektitiedosto sisältää kaikki vastaavassa lähdekooditiedostossa määritellyt funktiot ja muuttujat konekielelle käännettynä. Nyt siis esimerkiksi tiedosto helloworld.o sisältää main-funktion määrittelyn. Samassa objektitiedostossa on puolestaan viittaukset funktioihin hello ja world. Vastaavasti tiedosto hello/hello.o sisältää hello-funktion määrittelyn ja edelleen viittauksen funktioon printf. Nyt pitäisi saada palapeli koottua. Tähän käytetään linkkeriä, joka, kuten jo mainittua, löytyy myös gcc:stä. Linkityskomennossa yksinkertaisesti kerrotaan, mitkä objektitiedostot linkitetään yhteen. -o helloworld-valitsin ei ole välttämätön, sillä ainoastaan kerrotaan, että tuloksena syntyvä ohjelma tallennetaan tiedostoon helloworld eikä tiedostoon a.out, mikä on oletusarvo. Linkkeri luo ensimmäisen itse tehdyn Linux-ohjelmasi ja voit ajaa sen kuten minkä tahansa muunkin ohjelman komennolla ./helloworld. (./ alussa on yleensä tarpeen, koska normaalisti hakupolussa ei ole nykyistä hakemistoa, eikä käyttöjärjestelmä tällöin myöskään etsi ajettavaa ohjelmaa nykyisestä hakemistosta.) Ohjelma toimii loistavasti ja tervehtii koko maailmaa. Nautimme hetken kansainvälisestä hurmoksesta. SalaisuusOk, myönnetään, gcc osaa kyllä tehdä itse suoraan sekä käännöksen että linkityksen. Voit kokeilla komentoa gcc helloworld.c hello/hello.c world/world.c -o helloworld tai g++ helloworld.cc hello/hello.cc world/world.cc -o helloworld. Sisäisesti gcc tekee tällöinkin aivan samoin kuin edellä on esitetty. Tämä ainoastaan säästää kirjoitusvaivaa käyttäjältä. Tietenkään ei ole myöskään mitään pakkoa jakaa ohjelmaa useisiin pieniin lähdetiedostoihin. Kaiken voisi kyllä pitää yhdessä mammuttimaisessa tiedostossa. Pienten, erikseen käännettävien lähdekoodien käyttö sekä käännöksen ja linkkauksen ajatuksen sisäistäminen on joka tapauksessa erittäin hyödyllistä:
Jäljempänä kirjastojen käsittelyn yhteydessä tutustumme lisää siihen, miten voit modularisoida ohjelmaasi edelleen. Suosittelen lämpimästi seuraavaa kokeilua:
Palaamme siis motivoituneina tylsän teoriapuolen kimppuun. Tongimme tarkemmin, mitä linkityksessä tapahtuukaan. Pintaraapaisu linkityksen ja kirjastojen sielunelämäänLinkkeri käy läpi jokaisen objektitiedoston, linkittää ne yhteen ja selvittelee objektien väliset viittaukset. helloworld.o-tiedostoa ihmetellessään linkkeri huomaa viittauksen symboliin hello. Tämä ongelma ratkeaa helposti, symboli löytyy toisesta objektitiedostosta, hello/hello.o, joka myös linkitetään mukaan. Entä sitten printf? Tuotahan ei löydy mistään objektitiedostosta. Kyse on kuitenkin standardista C-kielen funktiosta ja niinpä funktio löytyy C-kirjastosta (/lib/libc.*). gcc linkittää aina oletuksena C-kirjaston mukaan. Niinpä ei meidän tarvitse erikseen murehtia C-kielen vakiofunktioiden löytymisestä. C++-ohjelmia g++-komennolla käännettäessä mukaan tulevat myös automaattisesti tarvittavat C++-vakiokirjastot. Dynaaminen linkitysC-kirjasto linkitetään useimmissa Linux-järjestelmissä ohjelman mukaan itseasiassa dynaamisesti. Tämä tarkoittaa käytännössä sitä, että C-kirjastosta ei liitetä mitään osia itse käännettyyn ohjelmaan, vaan ohjelmaa käynnistettäessä ladataan automaattisesti tarvittava kirjasto. Monissa Linux-järjestelmissä voit komennolla ldd helloworld tutkia, onko ohjelma linkitetty dynaamisesti ja jos on, mitä kirjastoja dynaamisesti ladataan. Tyypillisesti listauksessa esiintyy jokin tämänkaltainen rivi: libc.so.6 => /lib/libc.so.6, joka yksinkertaisesti tarkoittaa sitä, että ohjelmaa ladattaessa ladataan libc.so-kirjaston versio 6, ja tässä järjestelmässä se löytyy paikasta /lib/libc.so.6. Jos koneellasi on sekä kirjasto libxyz.a että kirjasto libxyz.so.3.2.1, ensimmäinen on staattisesti linkitettävä ja jälkimmäinen on dynaamisesti linkitettävä. Linkkeri linkittää oletuksena dynaamisesti, jos dynaaminen kirjasto löytyy ja dynaaminen linkitys on kyseisessä järjestelmässä tuettu. Vaikkei kirjastoa linkitettäisikään dynaamisesti, ei tarvitse pelätä, että koko C-kirjasto liitettäisiin oman ohjelmaasi mukaan. Kirjastosta linkitetään vain tarpeelliset osat. Muita kirjastojaPian kuitenkin huomaat, ettei pelkkä vakio C-kirjasto riitä pitkälle. Jo osa tavallisista C-funktioista on peruskirjaston ulkopuolella. Matemaattiset funktiot eivät löydykään libc:stä, vaan erillisestä matematiikkakirjastosta libm:stä. Tällöin on linkityskomennon yhteydessä kerrottava, että tuokin kirjasto pitää ottaa mukaan. Tämä tehdään vipusella -lm, jossa m viittaa kirjaston nimeen ilman lib-etuliitettä ja .a- tai .so.*-päätettä. Vastaavasti curses-kirjastoa käytettäessä joudut kertomaan -lncurses-valitsimella, että haluat linkittää mukaan libncurses.*-kirjaston. Mutta entä se #include?Tärkeää on ymmärtää, että C-kielisessä lähdekoodissa oleva rivi #include <stdio.h> tarkoittaa ainoastaan sitä, että ohjelmaa käännettäessä käydään lukemassa mukaan tiedosto /usr/include/stdio.h. Tuolta tiedostosta löytyy mm. printf-funktion prototyyppi, joka kertoo, että tuollainen funktio on olemassa ja kertoo funktion parametrien tyypit sekä paluuarvon. Tuossa header-tiedostosta ei ole kerrottu sitä, miten funktio on toteutettu. Funktion toteutus löytyy edellämainitusta C-kirjastosta, joka tulee mukaan vasta linkitysvaiheessa. Tilanne on aivan sama kuin meidän oman ohjelmamme hello-funktion kanssa. hello/hello.h-headerissa on kerrottu tuon funktion prototyyppi. Näin pääohjelmaa kääntäessään C-kääntäjä tietää, että tuonniminen funktio on todellakin olemassa ja että se ei ota mitään argumentteja. Itse funktion toteutus sen sijaan on hello/hello.o-tiedostossa (joka puolestaan käännettiin hello/hello.c-lähdekoodista). Esimerkiksi pääohjelman helloworld.c voi kääntää objektitiedostoksi, vaikkei hello/hello.o-tiedostoa olisi oikeasti olemassakaan. Sen sijaan linkittäminen kokonaiseksi ohjelmaksi ei onnistu, elleivät kaikki palaset loksahda paikoilleen. Jokaisen funktion ja muuttujan on löydyttävä jostain mukaan linkitettävästä objektitiedostosta tai kirjastosta. Aivan samoin, jos ohjelmoit cursesilla, sinun on #include:n avulla kerrottava kääntäjälle, mistä löytyvät funktioiden prototyypit, mutta sen lisäksi on kerrottava linkkerille, mistä kirjastosta löytyvät itse funktiot. Omien kirjastojen luominenKirjasto on yksinkertaisesti vain kokoelma objektitiedostoja tietyllä tavalla organisoituna. Voit myös rakentaa omia kirjastojasi. Tällöin sinun ei tarvitse joka kerta valita mukaan linkitettävissä kaikkia yksittäisiä objektitiedostoja, vaan voit yksinkertaisesti valita kokonaisen kirjaston linkitettäväksi mukaan. Kirjastojen kohdalla erityisen mukavaa on se, että niiden avulla on helppo hyödyntää kerran kirjoitettuja ohjelman osia muissa projekteissa (aivan kuten kerran kirjoitettua curses-kirjastoa on helppo hyödyntää kaikissa tekstiruudun käsittelyä tarvitsevissa projekteissa). Erittäin keinotekoinen esimerkkiOletetaanpa, että olet esimerkkiprojektissamme kehittänyt
hello-hakemistossa olevaa tervehdyksenpuolikkaan tulostavaa
funktiota pitemmälle. Huomaamme, että olemme kasanneet erittäin hyödyllisen ohjelmanpalesen, joka tulostaa lokalisoidun tervehdyksen alun. Tuotahan olisi mukava käyttää jossain toisessakin projektissa, vaikkapa siinä seuraavassa hellouser-ohjelmassa, joka ei tervehdikään enää koko maailmaa vaan käyttäjää. Riesana on kuitenkin se, että nyt joutuisimme tuossa toisessakin projektissa ottamaan mukaan kaikki tähän liittyvät objektitiedostot: hello.o, american.o ja strings.o. Lisäksi hallittavuus kärsii: Kun seuraavan kerran laajennamme tuota
RatkaisuKaunis ratkaisu ongelmaan olisi tehdä kaikista hello-hakemiston objektitiedostoista yksi kirjasto, libhello.a. Edut ovat ilmeiset. Koko ohjelmaa kääntäessämme riittää, että otamme mukaan tuon libhello.a-kirjaston, siinä kaikki.
Isossa projektissa voitaisiin jättää esimerkiksi hello-kirjaston kehittäminen kokonaan yhdelle henkilölle. Hän koostaisi kirjaston haluamallaan tavalla palasista ja muut projektin kimpussa työskentelevät ainoastaan hyödyntäisivät tuota kirjastoa. Rajapinta - funktioiden prototyypit sekä mahdolliset vakiot,
tyypit, structit ja C++:n tapauksessa luokat - esiteltäisiin
esimerkiksi hello.h-otsikkotiedostossa ja itse toteutus
olisi koottu yhteen kirjastoon, libhello.a:han. Käyttö
olisi siis aivan yhtä helppoa kuin vaikkapa curses-kirjaston
tapauksessa. Lähdekoodiin otetaan mukaan header-tiedosto tarvittaessa
( Tärkeää on huomata, että vaikka hello-kirjastoa kehitettäisiin, ei muita projektin osia tarvitsisi kääntää uudestaan eikä niitä varsinkaan tarvitsisi millään tavalla muuttaa. Riittäisi, että muut objektitiedostot ja kirjastot vain linkitetään uudestaan uuden kirjaston kanssa. ar, ranlib ja nmOman kirjaston tekeminen voi tuntua oudolta, mutta seuraavilla kahdella komennolla pärjää GNU-ympäristössä:
Ensimmäinen komento kokoaa kirjaston, jälkimmäinen luo kirjastoon linkitystä nopeuttavan hakemiston. Lopputuloksena syntyy libhello.a-kirjasto, jonka voi
linkittää vaikkapa helloworld-ohjelmamme mukaan. Komennolla nm -s libhello.a voit ihmetellä, mitä kirjasto on syönyt. Listasta
näkee, mitä symboleja kirjasto sisältää missäkin objektitiedostossa ja
toisaalta, mihin symboleihin mistäkin objektitiedostosta viitataan
(esimerkiksi Lisätietoja ar, ranlib ja nm -ohjelmista sekä näiden valitsimista kannattaa tonkia ohjelmien man- ja info-sivuilta. Oman kirjaston käyttöLinkittämisen voisi tehdä aivan samalla tavalla kuin aiemmin kerrottiin muiden kirjastojen kohdalla, yksinkertaisesti lisäämällä -lhello kääntäjän komentoriville. Tämä kuitenkin vaatisi, että oma kirjastomme libhello.a löytyisi niistä hakemistoista, joista linkkeri kirjastoja yleensä etsii (esim. /lib ja /usr/lib). Voimme kuitenkin kertoa kääntäjälle, että kirjastoja etsitään myös tuosta hello-hakemistosta -L-vipusella. Koko linkitysrivi on siis:
Toinen tapa on yksinkertaisesti kirjoittaa kirjasto muiden objektitiedostojen sekaan:
Kuten aiemmin todettiin, kirjastosta linkitetään mukaan vain
tarpeelliset osat. Käytännössä linkkerit ovat aina sen verran
älykkäitä, että linkittävät mukaan kustakin kirjastosta vain
tarvittavat objektitiedostot. Tämänkin vuoksi hello-kirjaston
koostaminen erillisistä objektitiedostoista on järkevää: jos
esimerkiksi jossain ohjelmassasi tarvitset hello-kirjastosta
ainoastaan Helpotusta projektin rakentamiseenOlemme edellä käyneet joukon komentoja, joiden avulla voidaan kääntää lähdekoodeista objektitiedostoja, koota objektitiedostoja kirjastoiksi ja linkittää objektitiedostoista ja kirjastoista edelleen lopullisia, ajettavia ohjelmia. Kaikkien käännöskomentojen kirjoittelu käsin on tietenkin ikävää puuhaa. Onneksi tähän ei ole tarvetta. Tutustumme seuraavaksi erittäin hyödylliseen ohjelmaan make:en, jonka avulla projektienhallinta helpottuu huomattavasti. makeRiippuvuudetEsimerkkiprojektissamme optimaalinen tapa kääntää koko ohjelma on seuraava:
Jokaisessa vaiheessa tutkiminen on hyvin suoraviivaista. Esimerkiksi objektitiedosto hello/hello.o käännetään tiedostosta hello/hello.c. Lähdekoodissa on puolestaan #include:lla otettu mukaan myös tiedostot hello/hello.h sekä helloworld.h. Muutos missä tahansa noista kolmesta lähdetiedostosta vaatii siis objektitiedoston uudelleenkääntämisen. Sanotaankin, että kohde hello/hello.o riippuu tiedostoista hello/hello.c, hello/hello.h ja helloworld.h. Vastaavasti esimerkiksi linkitysvaiheessa ohjelma on linkitettävä uudelleen, jos joku objektitiedostoista on muuttunut. Niinpä valmis ohjelma, helloworld, riippuu tiedostoista helloworld.o, hello/hello.o ja world/world.o. Seuraavassa on listattu kaikki (C-kielisen) esimerkkiprojektin kohteet sekä mistä lähdetiedostoista ne riippuvat:
Kun riippuvuudet on listattu, on jo helppo automatisoida projektin rakentaminen. Logiikka on yksinkertainen:
On tärkeää ymmärtää, että jos kohdetiedosto a riippuu lähdetiedostoista b ja c, tarkoittaa se sitä, että tiedosto a voidaan milloin tahansa luoda tyhjästä käyttämällä pelkästään tiedostoja b ja c pohjana. Se, että tiedoston b luomiseen voidaan tarvita muita tiedostoja, kerrotaan tiedoston b riippuvuuksissa. MakefileKun riippuvuudet on kasassa, voidaan alkaa miettiä sitä, millä komennoilla kohdetiedostot saadaan luotua lähdetiedostoista. Tässä tapauksessa riippuvuudet ja komennot ovat seuraavia:
Kun edelläkuvattu lista kirjoitetaan määrättyyn muotoon, saadaan make-ohjelman ymmärtämä Makefile:
On erittäin tärkeää huomata, että Makefile:n rakenteen tulee olla täsmälleen oikea:
Huomaa, että sisennys on Makefile:ssä osa syntaksia ja sisennys on tehtävä nimenomaan tabulaattorilla, ei välilyönneillä. make:n käyttömake-ohjelman käyttö on äärimmäisen yksinkertaista. Kun Makefile on tehty, riittää, että käynnistämme ohjelman komennolla make:
make toimii seuraavasti:
Rakentaminen puolestaan tapahtuu seuraavasti:
Huomaa, että rakentaminen on rekursiivista: yhden kohteen luomiseen tarvitaan tietyt lähdetiedostot, niiden luomiseen puolestaan toiset lähdetiedostot jne. Esimerkkitapauksessa make tekee siis seuraavat asiat:
Jos mitään ei tarvinnut tehdä, make tulostaa ilmoituksen make: `helloworld' is up to date. Muutoin make tulostaa kaikki komennot, mitä se suorittaa. Kokeile! Muuta esimerkiksi tiedosto hello/hello.c hiukan ja aja tämän jälkeen make uudestaan. Nyt tulostuksen pitäisi näyttää suunnilleen tältä:
make suoritti siis ainoastaan tarvittavan käännöksen ja linkityksen. Jos nyt käynnistät make:n heti uudelleen, tulostuksen pitäisi näyttää jälleen tältä:
make huomasi, että kohde kaikkine riippuvuuksineen on kunnossa, eikä siis tehnyt mitään. Edut make:n käytössä ovat ilmeisiä. Riittää, että luot kerran Makefile:n ja tämän jälkeen joka kerta yhdellä make-komennolla saat rakennettua koko ohjelman kuntoon. KohteetVoit komentorivillä kertoa, minkä kohteen haluat rakentaa. Hyvin usein tätä hyödynnetään esimerkiksi ohjelmien asennuksessa: Makefile:een luodaan kohde install, jossa on komentoina kyseisen ohjelman asentamiseen tarvittavat komennot. Näin ohjelman voi asentaa yksinkertaisesti komennolla make install. Toinen esimekki on kohde clean, joka tyypillisesti poistaa kaikki objektitiedostot, käännöstulokset, core-tiedostot ja vastaavat. Lisää seuraavat rivit Makefile:n loppuun ja kokeile:
MuuttujatMakefileissä ei koskaan kannata "kovakoodata" komentojen nimiä, parametreja ja vastaavia. Sen sijaan tulee käyttää muuttujia:
Etuja muuttujien käytössä on lukuisia, tärkeimpänä se, että ohjelmaa käännettäessä voidaan vaihtaa käytettävät komennot ja vipuset helposti suoraan komentoriviltä. Kokeile esimerkiksi komentoa make CFLAGS=-O2. GNU-projektin make määrittelee itse suuren joukon muuttujia, myös edellämainitut CC, CFLAGS, LDFLAGS ja RM, joten noiden oletusarvojen antaminen itse ei ole aivan välttämätöntä. Täydellinen lista muuttujista löytyy make:n info-sivuilta. Näiden lisäksi voit luonnollisesti itsekin määritellä mitä tahansa muuttujia. Omat muuttujat kannattaa kirjoittaa pienillä kirjaimilla erotuksena LDFLAGS:n kaltaisista make:n itsensä määrittelemistä ja tunnistamista muuttujista. Implisiittiset säännötEnnenkuin ryntäät kirjoittamaan nerokkaita omia muuttujia, kannattaa tutustua myös GNU make:n tarjoamiin implisiittisiin sääntöihin. Jos Makefile:n kohteelle ei ole määritelty mitään sääntöä, make käyttää omia oletusarvojaan. Esimerkiksi kohdetiedosto abc.o rakennetaan lähdetiedostosta abc.c implisiittisellä säännöllä $(CC) -c $(CPPFLAGS) $(CFLAGS) abc.c -o abc.o. Jälleen info-sivuihin tutustuminen kannattaa. Niinpä tälle projektille riittää GNU-ympäristössä jopa näin yksinkertainen Makefile:
Luetellaan siis pelkät lähteet ja kohteet, make tekee loput. Yksinkertaista.
Rekursiiviset Makefile:tAina, kun projektiin tulee lisää tiedostoja, joudut luonnollisesti päivittämään Makefile:ä. Kun projekti paisuu suuremmaksi, kannattaa harkita myös Makefile:jen jakamista omiin alihakemistoihin. Tällöin päähakemiston Makefile vastaa ainoastaan koko ohjelman linkittämisestä. Kussakin alihakemistossa on oma Makefile, jossa on ohjeet kyseisen modulin, kirjaston tms. rakentamisesta. Erityisen hyödyllistä tällainen jako on silloin, kun samaa projektia työstää useampi henkilö: yhden kirjaston rakentaja voi itse huolehtia myös oman Makefile:n luomisesta ja päivittämisestä omassa alihakemistossaan ja päätason Makefile:ä ei tarvitse muuttaa lainkaan. Seuraavassa on esitetty hyvin yksinkertainen esimerkki rekursiivisten Makefile:jen käytöstä esimerkkiprojektissamme: Makefile:
# Määritellään muuttuja, jota käytetään myöhemmin.
OBJS = helloworld.o hello/hello.o world/world.o
# Oletuskohteena all, joka riippuu kolmesta lähteestä. Aluksi
# alihakemistot, viimeisenä varsinainen ohjelma.
all: module-hello module-world helloworld
# helloworld on pääohjelma, joka linkitetään objektitiedostoista.
helloworld: $(OBJS)
gcc -o helloworld $(OBJS)
# Päätason Makefile:n vastuulla on ainoastaan pääohjelman luonti.
helloworld.o: helloworld.c helloworld.h
gcc -c helloworld.c
# Kaikki muu hoidetaan alihakemistoissa.
# Ensin alihakemisto hello.
# Koska kohteella module-hello ei ole määritelty mitään riippuvuuksia,
# se luodaan joka kerta. Niinpä joka kerta, kun luodaan kohde all
# luodaan, luodaan myös module-hello.
# Tässä Makefile:ssä ei tarvitse välittää lainkaan siitä, miten
# hello/hello.o oikeasti luodaan, tässä ainoastaan siirrytään
# alihakemistoon ja kutsutaan siellä make:a. Tiedostossa hello/Makefile
# kerrotaan sitten, mitä kaikkea hello/hello.o:n luomiseen oikeasti
# tarvitaan.
# Muuttuja MAKE viittaa yksinkertaisesti make-ohjelmaan.
# Info-dokumenteista löytyy tarkempi selvitys muuttujasta.
module-hello:
cd hello && $(MAKE)
# Ja sama temppu myös alihakemistolle world.
module-world:
cd world && $(MAKE)
# Kerrotaan, että kohteet module-hello ja module-world eivät ole
# oikeasti mitään tiedostoja...
.PHONY: module-hello module-world
hello/Makefile:
hello.o: hello.c hello.h ../helloworld.h
gcc -c hello.c
world/Makefile:
world.o: world.c world.h ../helloworld.h
gcc -c world.c
Nyt jos esimerkiksi world/world.o vaatii yhden uuden include-tiedoston, riittää, että korjataan riippuvuudet world/Makefile:ssä. Päätason Makefile:een ei tarvitse koskea lainkaan: world/Makefile:
world.o: world.c world.h ../helloworld.h uusi_tiedosto.h
gcc -c world.c
Rekursiiviset Makefile:t kannattaa yrittää pääpiirteissään ymmärtää, sillä ainakin isoihin projekteihin tutustuessaan niihin törmää usein. Lisäksi niitä käytetään yleisesti jäljempänä esiteltävän automake-työkalun kanssa. Rekursio voi osoittautua hyödylliseksi ja käteväksi omissa laajemmissa projekteissa, mutta sen edut eivät suinkaan ole yksikäsitteisiä. Peter Millerin teksti Recursive Make Considered Harmful voi olla terveellistä luettavaa. Tekstistä löytyy esimerkkejä muista vaihtoehdoista. LisätietojaLisää make:n käytöstä, hyvä esimerkki Makefile:stä sekä monia hyödyllisiä vihjeitä voit lukea Lars Wirzeniuksen make-ohjeesta. Info-dokumenteista löytyy tarvittaessa lisätietoja. make on monipuolinen työkalu, jonka avulla voi hoitaa hyvinkin mutkikkaita asioita ja hallita hyvin laajoja projekteja. Kaikkea ei kuitenkaan ole pakko tehdä itse. Mutkikkaammissa projekteissa huomattavasti helpompaa voi olla autoconf:n ja automake:n käyttö. Portattavuuden ohella nämä työkalut auttavat myös Makefile:n luomisessa. Näihin tutustutaan seuraavissa kappaleissa. autoconfautoconf on erittäin hyödyllinen työkalu. Viimeistään siinä vaiheessa, kun aiot julkaista tekemäsi ohjelman laajemman piirin käyttöön, sinun kannattaa ottaa autoconf käyttöön. Kaikki Unix-ympäristöt eivät ole täsmälleen samanlaisia. Portattavuudessa voi törmätä useisiin eri ongelmiin, esimerkiksi:
autoconf on työkalu, jonka avulla luodaan helposti portattava ohjelma. GNU-maailmassa autoconf:sta on tullut erittäin suosittu väline ja käytännössä kaikissa ohjelmistoissa käytetäänkin autoconf:ia ohjelmiston konfiguroitiin. Ohjelmiston asentajan kannalta autoconf:n avulla tuotettu paketti on - ainakin teoriassa - erittäin helposti käännettävissä ja asennettavissa:
Olennainen vaihe on konfigurointiskriptin ajaminen. Tuo skriptin on lähes aina luotu autoconf-työkalulla. Skripti sisältää joukon tarkistuksia, joilla selvitetään, millaisessa ympäristössä ohjelmaa ollaan kääntämässä. Tarkistusten pohjalta autoconf luo ohjelman kääntämisessä tarvittavat Makefile:t ja muut tiedostot. automakePelkän autoconf:n käyttö on hyvin hankalaa aloittelijalle. Ohjelmoija joutuu kirjoittamaan ja ylläpitämään hyvinkin mutkikkaita Makefile.in-tiedostoja, joiden perusteella configure luo varsinaiset Makefile:t. automake nimensä mukaisesti automatisoi Makefile.in:en luomista. Ohjelmoijan tarvitsee kirjoittaa vain yksinkertaiset rungot ja automake tekee raa'an työn. Seuraavassa on tiivistetty esimerkki siitä, millä tavalla esimerkkiprojektissa voisi automake/autoconf -yhdistelmän ottaa käyttöön. Lähes minkä tahansa asian voi toteuttaa automake/autoconf -systeemissä hyvin monilla eri tavoilla. Tässä esitettävä ei ole missään nimessä ainoa, suositeltava eikä edes välttämättä hyvä tapa. Tässä esimerkissä käytetään aiemmin mainittuja kirjastoja. Jokaisesta projektin alihakemistosta luodaan oma kirjasto ja päätasolla ne sitten linkitetään yhteen pääohjelman kanssa. Tämä lähestymistapa toimii erityisen näppärästi automake:n kanssa. Kirjastojen muuttaminen myöhemmin dynaamisesti linkitettäviksi onnistuu erillisen libtool-työkalun avulla varsin portattavasti. KäyttöönottoPoista kaikki vanhat Makefile:t (tai mieluummin siirrä ne muualle talteen...). Luo hakemisto aux aputiedostoille. Luo tiedosto configure.in:
Luo tiedosto Makefile.am:
Luo tiedosto hello/Makefile.am:
Luo vastaavasti tiedosto hello/Makefile.am. Luo tiedostot NEWS, README, AUTHORS ja ChangeLog, jos noita ei vielä ole, ja kirjoita niihin jotain hyödyllistäkin... Aja aclocal. Tuloksena hakemistoon syntyy tiedosto aclocal.m4. Aja autoconf, tämä puolestaan luo configure-skriptin. Aja autoheader, joka luo config.h.in-tiedoston. Aja automake -a, joka luo Makefile.in-tiedostot Makefile.am-tiedostojen perusteella ja lisäksi asentaa joukon tarvittavia tiedostoja. Aja ./configure, joka luo varsinaiset Makefile:t Makefile.in-tiedostoista. Aja make. Edellämainittu työ pitää tehdä vain yhden kerran. Loppu onkin sitten helpompaa. KäyttöKun muokkailet tulevaisuudessa ohjelmaasi, riittää uudelleenkääntämiseen pelkkä make. Luonnollisesti, jos luot esimerkiksi uuden hakemiston, joudut muokkaamaan tiedostoja configure.in ja Makefile.am vastaavasti sekä luomaan uuteen hakemistoon Makefile.am-tiedoston. Ok, entäs sitten?Kaiken pitäisi nyt toimia hienosti. Lähdekoodeista käännetään objektikoodit, niistä kootaan kirjastot ja lopulta linkitetään lopullinen helloworld-ohjelma. Syntyneet Makefile:t ovat täynnä hyödyllisiä toimintoja. Esimerkiksi make clean ja make install toimivat kuten missä tahansa GNU-ohjelmassa. Asennuskohteen voit määritellä configure:n --prefix -vivulla jne. Voit jopa luoda valmiin tar + gzip -asennuspaketin yksinkertaisesti komentamalla make dist, tuloksena paketti nimeltä helloworld-1.0.tar.gz. Tuo paketti sisältää kaikki tarvittavat tiedostot. Käyttäjän tarvitsee vain purkaa paketti, sanoa ./configure; make; make install ja ohjelmasi kääntyy ja asentuu. Käyttäjällä itsellään ei tarvitse olla mitään autoconf/automake -työkaluja, kaikki tarvittavat skriptit liitetään jakelupaketin mukaan. Erittäin hyödyllinen toiminto on make distcheck. Tuo paitsi luo jakelupaketin, myös kokeilee sen purkamista uuteen hakemistoon, konfiguroimista, kääntämistä ja jopa asentamista ja lopuksi vielä siivoaa jälkensä. Tuota kannattaa ehdottomasti käyttää ennen paketin levittämistä. Kaikkien muiden herkkujen lisäksi eri lähdekoodien väliset riippuvuudet hoidetaan automaattisesti. Kokeile! Muokkaa jotain tiedostoa ja sano uudelleen make. Järjestelmä itse pitää huolen siitä, että esimerkiksi configure luodaan ja/tai ajetaan aina tarvittaessa uudelleen. Varsinainen konfigurointiTyökalujen olennaisinta hyötyä, eri systeemien erilaisten piirteiden tunnistamista automaattisesti, ei näin yksinkertaisessa esimerkissä pysty helposti esittelemään ja ylläolevassa esimerkissä konfigurointi rajoittui muutamien hyvin yksinkertaisten piirteiden tunnistamiseen. Esimerkissä configure hoiti lähinnä käytettävän C-kääntäjän, ranlib-ohjelman ja install-ohjelman tunnistamisen. Nämä tarkistukset määriteltiin configure.in:n AC_PROG_... -määreillä. Hiukan kehittyneemmän esimerkin saa header-tiedostojen tunnistuksesta. Jos haluat käyttää esimerkiksi errno-muuttujaa, törmäät ongelmaan: joissain systeemeissä tuo muuttuja määritellään standardinmukaisesti errno.h-tiedostossa, joissain ei. Haluaisit tutkia automaattisesti, löytyykö tuota header-tiedostoa vai ei. Lisää seuraava määre configure.in-tiedostoon: AC_CHECK_HEADERS(errno.h). Tämän jälkeen configure tutkii, löytyykö tuota header-tiedostoa, ja jos löytyy, määrittelee HAVE_ERRNO_H-makron. Makromäärittelyt tallennetaan AM_CONFIG_HEADER-määreen osoittamaan tiedostoon. Tuo tiedosto pitää luonnollisesti ottaa #include:lla mukaan, jotta määrittelyt näkyisivät. Tiedosto luodaan configure-skriptiä ajettaessa ja se sisältää tavallisia C-esikääntäjän makroja tyyliin #define HAVE_ERRNO_H 1. Ohjelmakoodi voisi siis olla jotain tällaista:
Tutkimme siis, onko HAVE_ERRNO_H-makro määritelty, ja jos on, otamme vastaavan otsikkotiedoston mukaan. Jos makroa ei ole määritelty, ei otsikkotiedostoakaan löydy, ja joudumme esittelemään errno-muuttujan itse. Elämä ilman config.h:taJos et olisi määritellyt AM_CONFIG_HEADER-riviä, makromäärittelyt annettaisiin C-kääntäjälle suoraan komentorivillä. Tällöin komentoriville lisättäisiin automaattisesti tämänkaltaisia parametreja: -DHAVE_ERRNO_H=1. Lopputulos olisi aivan sama, makrot olisivat C-esikääntäjän nähtävissä aivan kuin ne olisi määritelty #define-direktiivillä. Tämä tapa on kuitenkin ongelmallisempi: paitsi, että käännöskomennoista tulee hyvinkin mutkikkaita ja pitkiä ja siten hankalasti luettavia, voi joissain ympäristöissä komentorivin suurin sallittu pituus ylittyä. Niinpä AM_CONFIG_HEADER kannattaa aina määritellä. Omat konfigurointivivutVoit myös luoda omia vipuja configure-skriptiin. Vipuja on kahdenlaisia:
Lisätäänpä ohjelmaamme oma piirre "foo", joka on kytkettävissä päälle vivulla --enable-foo. Aluksi lisätään lähdekoodiin ehdollinen kääntäminen:
Manuaalisesti käännettäessä foo-toiminto saataisiin mukaan lisäämällä C-kääntäjän komentoriville vipu -DFOO, joka määrittelisi makron FOO. Muokataanpa nyt tiedostoa configure.in, jotta saadaan sama toiminto hoidettua --enable-foo -vivulla:
Nyt voitkin kokeilla komentoja ./configure --enable-foo; make clean; make ja ./configure --disable-foo; make clean; make. Kokeile myös ./configure --help. Hiukan sielunelämääKannattaa vilkaista automaattisesti luotavaa configure-skriptiä. AC_ARG_ENABLE-makrohässäkkä puretaan normaalin shelliskriptin osaksi. Makron eri argumentit sijoitellaan m4-makroprosessorin ohjaamina oikeisiin paikkoihin skriptissä. if test ... -osuus puolestaan ei ole minkään makron sisällä, joten se jää sellaisenaan skriptin osaksi. Vertailu muokkaa $CPPFLAGS-ympäristömuuttujaa. Makefile-tiedostoja luotaessa configure lopulta korvaa Makefile.in-tiedostoissa olleet @CPPFLAGS@-merkkijonot $CPPFLAGS-muuttujan arvolla. Mistäs ne @CPPFLAGS@-merkkijonot sitten ovat Makefile.in-tiedostoihin tulleet? Vastaus on luonnollisesti automake. Yksinkertaisen Makefile.am-tiedoston pohjalta automake luo hyvin monipuolisen Makefile.in-tiedoston, jossa on mm. lukuisia muuttujamäärittelyitä. Ne asiat, mitkä selvitetään configure-skriptiä ajettaessa, on korvattu Makefile.in-tiedostoissa @...@-merkkijonoilla. Aivan samalla tavalla AC_PROG_CC-makro laajenee shelliskriptiksi, joka tutkii, mitä kääntäjää käytetään. Sen perusteella asetetaan mm. $CC-ympäristömuuttuja. Lopulta Makefile:a luodessaan skripti korvaa Makefile.in:ssä olevan @CC@-merkkijonon tuolla varsinaisella käännöskomennolla. @CC@-merkkijono taas on automake:n tuotoksia, yksi osa niitä lukuisia sääntöjä, joiden avulla kääntäminen hoidetaan. Kannattaa huomata vertailun kirjoittaminen muodossa x$foo = xtrue. Tämä lienee varmimpia tapoja kirjoittaa shelliskripteissä vertailuita. Näin vältetään paitsi mahdolliset ongelmat tyhjien merkkijonojen kanssa, myös erityisesti ongelmat erikoismerkeillä alkavien merkkijonojen kanssa. Esimerkiksi -:lla alkavat merkkijonot saatetaan jossain test:n versiossa tulkita väärin. Tutki rohkeasti eri tiedostojen sisältöä, kyllä kokonaisuus vähitellen aukeaa... C++-projektitautomake tukee hyvin myös C++-projekteja. C++-projekteissa pitää tehdä configure.in-tiedostoon seuraavat muutokset:
KirjastotNormaalia ohjelmaa luotaessa Makefile.am:ssä on tyypillisesti tämänkaltaiset rivit:
bin_PROGRAMS viittaa normaalien binäärien joukkoon (esim. /usr/local/bin) asennettaviin ohjelmiin. Edellä on käytetty myös tämänkaltaisia sääntöjä:
Tässä noinst_LIBRARIES tarkoittaa kirjastoja, joita ei asenneta minnekään. Kyseistä kirjastoa käytettiin vain apuna ohjelmaa luotaessa. Jos haluamme, että kirjasto asennetaan, jotta muutkin ohjelmat voisivat tuota kirjastoa käyttää, joudutaan sääntöä hiukan muokkaamaan:
Nyt kirjasto asennetaan muiden kirjastojen joukkoon (esim. /usr/local/lib). Kirjastojen yhteydessä halutaan monesti myös asentaa samalla kirjaston käytössä tarvittavat otsikkotiedostot. Muutetaanpa sääntöä vielä hiukan:
Nyt halutut otsikkotiedostot asentuvat oikealle paikalleen (esim. /usr/local/include). Huomaa, että otsikkotiedostoja ei tarvitse luetella kuin yhdessä paikassa. Riippuvuudethan selvitetään automaattisesti ja otsikkotiedostojen luetteleminen SOURCES-säännön kohdalla on ainoastaan sitä varten, että automake tietäisi, mitä kaikkia tiedostoja levityspakettiin kuuluukaan. EsimerkkejäX:ää vaativan ohjelman konfigurointi onnistuu lisäämällä tämänkaltainen pätkä configure.in:iin. Tämä esimerkki hoitaa kaiken työn, myös tarvittavien vipusten lisäämisen linkityskomentoon.
curses:ia vaativan ohjelman voi puolestaan konfiguroida vaikkapa näin. Ensin configure.in:
Ja sitten itse ohjelmakoodi:
Jos sama otsikkotiedosto voi esiintyä lukuisilla eri nimillä, voit käyttää tämänkaltaista sääntöä:
Näin etsiminen lopetetaan heti, kun jokin otsikkotiedostoista löytyy. Vihjeitäconfig.cache kannattaa poistaa ennenkuin yrittää uudelleenkonfigurointia muuttuneessa ympäristössä. Joskus ison remontin yhteydessä voi olla syytä manuaalisesti ajaa uudelleen edellämainitut aclocal, autoconf, autoheader ja automake -a. Lisätietoja löytyy autoconf:n ja automake:n manuaaleista. Molempien lukeminen - tai ainakin selailu - on hyvin suositeltavaa. Suosittelen lämpimästi, että käyt lukemassa kaikkien edellä esiteltyjen makrojen kuvaukset manuaaleista. Jos haluat lisätietoja jostain AM_-alkuisesta konfigurointimakrosta, se löytyy automake:n manuaaleista. Sen sijaan AC_-alkuisten kohdalla kannattaa useimmiten vilkaista molempia oppaita, sillä automake tarjoaa lisätoimintoja joihinkin autoconf:n makroihin. WWW-selaimen hakutoimintoa kannattaa hyödyntää ahkerasti... Edellämainittujen makrojen lisäksi kannattanee tutustua ainakin AC_TYPE_-alkuisiin makroihin ja AC_CHECK_FUNCS-makroon. Kannattaa myös huomata, että joihinkin yksittäisiinkin tarpeisiin on olemassa valmiina käteviä makroja, esimerkiksi AC_FUNC_SELECT_ARGTYPES. VersionhallintaLaajoissa projekteissa versionhallintaan tulee kiinnittää erityistä huomiota. Suosittelen lämpimästi, että tutustut oikeisiin versionhallintatyökaluihin ennenkuin aloitat isoa projektia. Alkuun päässee hyvin rcs:llä. Usean ohjelmoijan projekteissa voi cvs olla hyödyllinen. Ensimmäisessä ohjelmointiprojektissa järeät versionhallintatyökalut eivät tietenkään ole välttämättömiä. Muutamalla perustyökalulla pääsee jo pitkälle ja niistä voi olla hyötyä muuallakin kuin ohjelmoinnissa. diffdiff-ohjelman perusajatus on etsiä kahdesta tiedostosta erot ja listata ne. Otetaanpa esimerkki. Olet työstämässä uutta versiota helloworld-projektista. Vanha versio 1.0 on hakemistossa hw-1.0 ja uusi versio 1.1 on hakemistossa hw-1.1 (hakemiston nimeä voi vaihtaa mv-komennolla ja kokonainen hakemisto on helpointa kopioida komennolla cp -a hw-1.0 hw-1.1). Olet muokannut uudessa versiossa tiedostoa hello/hello.c hiukan:
Voit helposti tutkia diff-ohjelmalla, mitkä ovatkaan noiden kahden tiedoston erot. Käyttö on hyvin yksinkertaista: diff vanha_tiedosto uusi_tiedosto.
Tulostus on hyvin yksinkertainen: 8. rivillä on vasemmanpuoleisessa (<) tiedostossa Hello ja oikeanpuoleisessa (>) Hi. Suosittelen kuitenkin, että heti alussa opettelet ns. unified diff -muodon käytön. Ainoa ero on -u -vivun lisääminen:
Nyt tulostus on paljon monipuolisempi. Listauksessa näytetään vanhan ja uuden tiedoston nimet ja aikaleimat. Erityisen tärkeää on myös se, että näytetään muutama rivi myös muutoskohdan ympäriltä. Ne rivit, mitkä ovat tulleet uuteen versioon lisää, on merkitty +:lla ja ne, mitkä ovat poistuneet, on merkitty -:lla. diff-komentoa voidaan käyttää myös kokonaisiin hakemistopuihin. Muokataanpa vielä uutta versiota vielä hiukan ja ajetaan sitten diff vivulla -r, jolloin käydään kokonaiset hakemistot läpi:
Osaatkin jo lukea diff:n tulostusta, joten näet helposti, mikä toinen muutos on uudessa versiossa tehty: Makefilessä olevaan käännöskomentoon on lisätty -O-vipu parantamaan gcc:n suorittamia optimointeja. Jos sinulla oli ongelmia esimerkkikomentojen kokeilemisessa, varmistu, ettei hakemistoissa ole lojumassa binääritiedostoja (ohjelmatiedostot ja objektitiedostot) eikä varmuuskopioita. Monet editorit luovat automaattisesti varmuuskopion muokattaessa tiedostoa, varmuuskopion nimi on yleensä alkuperäisen tiedoston nimi + tilde (~). patchSeuraavaksi tulee varsinainen versionhallintaan liittyvä oivallus: Ylläolevasta diff-komennon listauksesta näkee yhdellä kertaa kätevästi kaikki versioiden 1.0 ja 1.1 väliset erot. Tallennetaanpa nuo erot tiedostoon: diff -ur hw-1.0 hw-1.1 > hw-1.0-1.1-diff Nyt meillä on tiedostossa hw-1.0-1.1-diff ns. patch versiosta 1.0 versioon 1.1 eli selostus siitä, mitä pitää muuttaa, jotta voisimme päivittää version 1.0 versioksi 1.1. Hyvin usein puhutaan suomessakin patcheista, niillä tarkoitetaan juuri tällaisia tiedostoja, joissa on tietyllä tavalla lueteltuna ohjelman lähdekoodin eri versioiden väliset erot. Patchit ovat käytännöllisiä, koska niiden koko on yleensä huomattavasti pienempi kuin koko lähdekoodin koko. Luonnollisesti tähän päivittämiseen on olemassa valmis työkalu. Myös työkalu on nimeltään patch. Työkalu osaa lukea esimerkiksi unified diff -muotoisia (eli diff -u -komennolla luotuja) patcheja. patch:n käyttöLähdetään tilanteesta, jossa meillä on käytettävissä version 1.0 lähdekoodi sekä tuo patch-tiedosto. Olkoot tuo patch-tiedosto kotihakemistossa nimellä hw-1.0-1.1-diff. Ennen patchin käyttöä kannattaa vilkaista patchin sisältöä ja katsoa, miten hakemistot on patchissa määritelty. Tässä tapauksessa nähdään, että patchissa on viitattu hakemistoihin hw-1.0/.... Esimerkiksi päätason Makefile on patchissa hw-1.0/Makefile. Nämä patcheihin tallennetut polut ovat ainoa hiukan hankala asia patch:n käytössä. Meidän on valittava, kuinka monta "tasoa" haluamme noista hakemistopoluista unohtaa. Useimmiten oikea valinta on 0 tai 1. Yleensä patch alkaa valittaa puuttuvista tiedostoista, jos valitsemme väärin. Tämä "unohdettavien hakemistotasojen" määrä kerrotaan patch-komennolle -p-vivulla. Tutkitaanpa aluksi, mitä tehdään, jos lähdekoodi on esimerkiksi hakemistossa ~/hw. Huomataan, että hakemisto on muu, kuin mitä patchiin on tallennettu. Tässä tapauksessa haluamme unohtaa yhden tason: pätkän hw-1.0/ polkujen alussa. Lisäksi meidän on siirryttävä tuohon ~/hw-hakemistoon ennen patchin asentamista. Näin esimerkiksi viittaus päätason Makefileen ei osoitakaan tiedostoon hw-1.0/Makefile vaan pelkästään tiedostoon Makefile nykyisessä hakemistossa. Koska nykyinen hakemisto on ~/hw, lopputulos on se, että patchaus kohdistuu tiedostoon ~/hw/Makefile aivan kuten pitikin. Komennot menevät siis näin:
Annetaan vielä selvyyden vuoksi toinen esimerkki: Jos meillä olisi versio 1.0 hakemistossa ~/hw-1.0 (eli samassa hakemistossa kuin missä se oli patchin luojallakin), voisimme suoraan kotihakemistosta asentaa patchin -p0-vivulla. Nythän meidän ei tarvitsisi unohtaa yhtään tasoa hakemistoista, vaan esimerkiksi päätason Makefile löytyisi nykyiseen hakemistoon nähden paikasta hw-1.0/Makefile aivan kuten patchissa lukeekin. Tällöin voisimme siis komentaa:
Kerran vielä, pojat:
patch-komento on monipuolinen. Esimerkin vuoksi voisi mainita vivun -R, jonka avulla voit soveltaa patchia väärin päin: jos sinulla on uusi versio, voit patchata sen vanhaksi versioksi. Tätä vipusta voi käyttää myös silloin, jos joku on onnistunut hölmöilemään ja kirjoittanut diff-komennossa vanhan ja uuden hakemiston väärin päin. rcsdiff- ja patch-komentojen avulla voit hoitaa yksinkertaista versionhallintaa käsityönä. Jossain vaiheessa tulee kuitenkin mieleen, että tietokone voisi hoitaa likaisen työn. rcs on yksinkertainen versionhallintatyökalu. rcs:n avulla voit automaattisesti hallita tiedostojen eri versioita. rcs on kevyt ja helppokäyttöinen työkalu ja soveltuu jossain määrin myös pienen työryhmän käyttöön usean henkilön kesken jaetun projektin versionhallintaan. Yleistä käsitteistäKäydäänpä ensin läpi muutamia käsitteitä, joihin törmää kaikissa dokumenttienhallintajärjestelmissä:
Tyypillisesti missä tahansa dokumenttienhallintajärjestelmässä peruskäyttö sujuu näin:
rcs:n komennotrcs:ssä check in hoidetaan komennolla ci ja check out vastaavasti komennolla co. Aloittelija pärjääkin jo noilla kahdella komennolla. rcs:n käyttöönottorcs:n käyttö ei sinällään välttämättä vaadi mitään valmisteluita. Suositeltavaa on kuitenkin luoda hakemisto RCS kaikkiin niihin hakemistoihin, joissa pidetään rcs:llä hallittavia tiedostoja. rcs pitää versionhallintatiedot automaattisesti RCS-hakemistossa, jos sellainen on olemassa. Näin versionhallintatiedot eivät ole lojumassa muiden tiedostojen seassa. Luodaan siis projektissamme tarvittavat RCS-hakemistot:
Tiedostojen vienti rcs:äänci-komennolla voi kirjata sisään paitsi olemassaolevien tiedostojen uuden version, myös kokonaan uuden tiedoston. Esimerkiksi Makefile:n saa vietyä järjestelmään yksinkertaisesti näin:
ci huomasi, että tiedostoa Makefile ei ollut vielä viety järjestelmään. Niinpä luodaan ensimmäinen versio ja pyydetään kuvausta tiedostosta. Huomaa, että Makefile häviää nykyisestä hakemistosta. Se on tallennettuna (rcs:n sisäisessä muodossa) RCS-hakemistoon. rcs:ään viedyn tiedoston muokkaaminenKun haluat muokata Makefile:ä, se pitää ensin uloskirjata:
co-komennon vipu -l lukitsee tiedoston. Tällöin kukaan muu ei voi samanaikaisesti hakea tiedostoa muokattavaksi. Tiedosto on nyt muokattavissasi. Pelkällä co-komennolla saisit tiedostosta otettua kopion, jota voit katsella, muttet muokata. Joissain dokumenttienhallintajärjestelmissä tätä toimintoa kutsutaan copy out -toiminnoksi erotuksena check out -toiminnosta. Nyt voit tehdä tiedostoon haluamasi muutokset, vaikkapa tuon diff:n yhteydessä esimerkkinä käytetyn -O-vivun lisäämisen. Voit halutessasi katsoa rcsdiff-komennolla, mitä muutoksia olet tehnyt. Oletuksena rcsdiff vertaa uloskirjattua versiota viimeisimpään sisäänkirjattuun versioon.
Hyvältä näyttää, juuri se muutos, mitä pitikin tehdä. Kirjataanpa siis tämä uusi versio tiedostosta sisään, jälleen yksinkertaisella ci-komennolla:
Komennolla rlog Makefile voit katsoa tiedoston versiohistoriaa. Kokeile! Kokeile tehdä vielä yksi uusi versio. Yksinkertaisilla co -l Makefile ja ci Makefile -komennoilla tämä onnistuu. Vanhan version hakeminenco-komennolla voit hakea myös vanhoja versioita tiedostoista. Kokeile komentoa co -r1.1 Makefile. VihjeitäSe, että rcs-järjestelmään viedyt tiedostot poistuvat alkuperäisestä hakemistostaan, on monessa tilanteessa vähemmän toivottavaa. Kuten jo todettu, komennolla co Makefile saat tiedostosta haettua takaisin kopion. Kannattaa myös huomata, että tämänkin jälkeen co -l Makefile toimii aivan ongelmitta, vain lukua varten otettu kopio korvautuu lukitulla, muokattavissa olevalla kopiolla. ci -u tiedosto puolestaan toimii käytännössä samoin kuin ci tiedosto; co tiedosto. Niinpä voit korvata co -l + editointi + ci -rutiinin co -l + editointi + ci -u -rutiinilla. Kaikki toimii kuten ennenkin, mutta tiedostoista on aina kopiot alkuperäisillä paikoillaan. rcs osaa paljon muutakin hyödyllistä. Kokeile lisätä seuraavanlainen kommenttilohko jonkin tiedoston alkuun ja tee tämän jälkeen tiedostolle muutaman kerran sisään- ja uloskirjaus. Katso tämän jälkeen tiedoston sisältöä ja ylläty iloisesti. co:n manuaalista löytyy lisää vastaavia avainsanoja.
RCS-hakemiston sisältöä kannattaa myös vilkaista. Huomaa, että rcs tallentaa uusista versioista pelkästään muutokset. Niinpä isoistakin tiedostoista voi huoletta pitää useita versioita tallella. Kannattaa lukea lisää manuaalisivuilta rcsintro, ci, co, rcs ja rcsdiff. Esimerkiksi ci -l on monissa tilanteissa hyvin kätevä. Emacs tarjoaa näppäriä aputoimintoja rcs:n käyttöön. Valikoiden vilkuilu auttaa alkuun. LoppusanatOlisi mukava kuulla palautetta tästä tekstistä. Löysitkö virheitä? Oliko tästä hyötyä? Vai onko tästä mielestäsi enemmän haittaa kuin hyötyä aloittelijoille? Lähetä rohkeasti palautetta sähköpostitse osoitteella suo@iki.fi. Kiitos jo etukäteen palautteestasi! Historia
Kopiointi© Jukka Suomela 1998-1999. FLUG:lla on oikeus julkaista tämä teksti muuttamattomana WWW-sivuillaan. Tätä tekstiä saa tulostaa ja monistaa epäkaupallisiin opetustarkoituksiin Opetusministeriön alaisissa oppilaitoksissa. Teksti on kopioitava joko muuttamattomana tai muutosten tulee erottua selvästi alkuperäisestä tekstistä. Lähde ja kirjoittaja on mainittava ja tällaisesta käytöstä on ilmoitettava minulle, ilmoitus sähköpostitse riittää. KiitoksetSuuret kiitokset kaikille palautetta lähettäneille! Erityisesti haluan kiittää seuraavia arvokkaasta palautteesta:
LiitteetEsimerkkiohjelmaTässä tutoriaalissa käytettävät esimerkkiohjelmat on listattu ohessa. Koska projekti on näinkin valtava, se on jaettu hallittavuuden helpottamiseksi useisiin eri lähdetiedostoihin.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||