Makefilet ovat varsin kätevä tapa automatisoida erilaisia prosesseja. Monesti myös tulee tarvetta käyttää erilaisia ympäristömuuttujia Makefileissä. Niiden käytössä kuitenkin piilee iso riski, joka voi pahimmillaan maksaa kaikki datasi. Esittelen tässä joitakin keinoja, joilla voit varmistaa, että ympäristömuuttujat todella ovat määriteltyjä.
Otetaan ensi yksi motivoiva ja havainnollistava esimerkki, millä tavalla asiat voivat mennä ikävällä tavalla pieleen vääränlaisten oletusten vuoksi.
Esimerkki. Skripti generoi automaattisesti asioita build
-hakemistoon, joka
on jonkinlaisen juurihakemiston alihakemisto. Esimerkiksi:
APP_ROOT=/opt/app
APP_BUILD_DIR=build
Makefilessä voi sitten olla make clean
, joka huolehtii build-hakemiston
poistamisesta.
(... muuta koodia ...)
clean:
rm -rf ${APP_ROOT}/${APP_BUILD_DIR}
(... muuta koodia ...)
Skripti näyttää sopivan viattomalta. Tästähän näkee heti että clean
-target
poistaa tiedostot hakemistosta /opt/app/build
:
rm -rf /opt/app/build
Mutta mitä tapahtuu, jos ympäristömuuttujia APP_ROOT
ja APP_BUILD_DIR
ei
ole määritelty? Harmittoman näköinen komento saakin aivan uusia piirteitä:
rm -rf /
Yllätys oli suuri, kun pääkäyttäjän oikeuksilla skripti ajettiin ilman että
ympäristömuuttujia oli määritelty. Teknisesti, clean
-target tuhosi kyllä
build
hakemiston mutta siinä vaiheessa kun joku huomasi, että komento ottaa
liian kauan, oli skripti kerennyt jo tuhota puolet kiintolevyn sisällöstä.
Esimerkki mukaili erästä lukemaani artikkelia, missä suurinpiirtien näin oli päässyt käymään. Tätä voi itsekin kokeilla, hieman vaarattomalla komennolla tosin:
clean:
@echo "Poistetaan hakemisto ${APP_ROOT}/${APP_BUILD_DIR}"
make clean
Poistetaan hakemisto /
Ei mennyt ihan nappiin! Tästä ei sysadminille kiitosta tulisi. Jos ympäristömuuttujat asettaa, komento toimii sillä tavalla kuin pitääkin.
APP_ROOT=/opt/app APP_BUILD_DIR=build make clean
Poistetaan hakemisto /opt/app/build
Tarinan opetus on, että automatisoiduissa prosesseissa ei kannata luottaa siihen, että skriptissä käytettävät ympäristömuuttujat ovat asetettuja. Riippuu siitä, mitä on tekemässä, minkäasteista tuhoa vääränlaiset oletukset aiheuttavat. Voidaanko tehdä paremmin? Kyllä voidaan. Ympäristömuuttujat nimittäin voidaan tarkastaa, ja mikäli niitä ei ole asetettu, poistutaan skriptistä turvallisesti.
Yksi tapa on määritellä if-ehto Makefile
-tiedoston alkuun. Ehdolla
tarkastetaan, onko ympäristömuuttuja asetettu, ja heitetään virhe, mikäli näin
ei ole. Modifikaatio olisi siis seuraava:
ifeq ($(APP_ROOT),)
$(error APP_ROOT is not set)
endif
ifeq ($(APP_BUILD_DIR),)
$(error APP_BUILD_DIR is not set)
endif
clean:
@echo "Poistetaan hakemisto ${APP_ROOT}/${APP_BUILD_DIR}"
Nyt tämä toimii kuten pitääkin. Kutsutaan ensin ympäristömuuttujia:
make clean
Makefile:2: *** APP_ROOT is not set. Stop.
Asetetaan vain toinen ympäristömuuttuja:
APP_ROOT=/opt/app make clean
Makefile:6: *** APP_BUILD_DIR is not set. Stop.
Kun asetetaan molemmat ympäristömuuttujat, komento ajetaan:
APP_ROOT=/opt/app APP_BUILD_DIR=build make clean
Poistetaan hakemisto /opt/app/build
Aina ei ole mahdollista tai järkevää vaatia, että kaikki ympäristömuuttujat ovat
määriteltyjä, mikäli niitä käytetään vain jossakin tietyssä targetissa.
Ympäristömuuttujat voidaan tarkastaa test
-komennolla, joka palauttaa vivulla
-n "<tekstiä>
arvon 0, mikäli tekstiä löytyy, ja arvon 1, mikäli tekstiä ei
lyödy.
test -n "tekstiä"
echo "Paluukoodi: $?"
test -n ""
echo "Paluukoodi: $?"
Paluukoodi: 0
Paluukoodi: 1
Tällä tavalla toteutettuna Makefilen clean-target on:
clean:
test -n "${APP_ROOT}" # APP_ROOT
test -n "${APP_BUILD_DIR}" # APP_BUILD_DIR
@echo "Poistetaan hakemisto ${APP_ROOT}/${APP_BUILD_DIR}"
make clean
test -n "" # APP_ROOT
make: *** [Makefile:10: clean] Error 1
APP_ROOT=/opt/app make clean
test -n "/opt/app" # APP_ROOT
test -n "" # APP_BUILD_DIR
make: *** [Makefile:11: clean] Error 1
APP_ROOT=/opt/app APP_BUILD_DIR=build make clean
test -n "/opt/app" # APP_ROOT
test -n "build" # APP_BUILD_DIR
Poistetaan hakemisto /opt/app/build
Tässä vielä etuna on se, että ympäristömuuttujat tulostuvat prosessin aikana.
Mikäli se ei ole tarpeellista, ne voi prefiksata @
-merkillä.
Tätä lähestymistapaa voi parantaa muutamilla eri tavoilla. Ensinnäkin, ympäristömuuttujien vaatimuksista voi tehdä omat reseptit, jolloin niitä voi lisätä useampaan kohteeseen riippuvuutena:
require-APP_ROOT:
@ if test "$(APP_ROOT)" = ""; then \
echo "Ympäristömuuttuja APP_ROOT ei määritelty!"; \
exit 1; \
fi
require-APP_BUILD_DIR:
@ if test "$(APP_BUILD_DIR)" = ""; then \
echo "Ympäristömuuttuja APP_BUILD_DIR ei määritelty!"; \
exit 1; \
fi
build: require-APP_ROOT require-APP_BUILD_DIR
@echo "Buildataan projekti hakemistoon ${APP_ROOT}/${APP_BUILD_DIR}"
clean: require-APP_ROOT require-APP_BUILD_DIR
@echo "Poistetaan hakemisto ${APP_ROOT}/${APP_BUILD_DIR}"
make clean
Ympäristömuuttuja APP_ROOT ei määritelty!
make: *** [Makefile:10: require-APP_ROOT] Error 1
APP_ROOT=/opt/app make clean
Ympäristömuuttuja APP_BUILD_DIR ei määritelty!
make: *** [Makefile:8: require-APP_BUILD_DIR] Error 1
APP_ROOT=/opt/app APP_BUILD_DIR=build make clean
Poistetaan hakemisto /opt/app/build
Makefile
toimii, mutta siinä on toistoa. Käyttämällä Makefilen syntaksiin
kuuluvia automaattisia muuttujia voidaan tarpeeton toisto
poistaa.
require-%:
@ if test "$(${*})" = ""; then \
echo "Ympäristömuuttuja $* ei määritelty!"; \
exit 1; \
fi
build: require-APP_ROOT require-APP_BUILD_DIR
@echo "Buildataan projekti hakemistoon ${APP_ROOT}/${APP_BUILD_DIR}"
clean: require-APP_ROOT require-APP_BUILD_DIR
@echo "Poistetaan hakemisto ${APP_ROOT}/${APP_BUILD_DIR}"
Yhteenveto
Prosesseja automatisoivissa skripteissä ympäristömuuttujan puuttuminen voi johtaa ennalta-arvaamattomiin ja katastrofaalisiin virheisiin. Mikäli ympäristömuuttujaa ei ole määritelty skriptin sisällä, pitäisi sen olemassaolo aina erikseen tarkastaa. Ympäristömuuttujien tarkastuksen voi tehdä monella eri tavalla. Tärkeintä on, että sen tekee jollakin tavalla.