Program fordítás Linuxon

A kezdő Linux felhasználók – a terminál használata mellett – idegenkedve tekintenek a programok forrásból történő telepítésére. Úgy vélem ez leginkább tudatlanságból fakadó előítélet csupán. Így jelen írásomban ezzel a kérdéssel foglalkozok. Igyekszem majd gyakorlati példákon át bemutatni, hogyan is zajlik ez a folyamat a valós életben. Bízom benne, hogy a cikk végére érve a kétkedők is felismerik: ez a bonyolult folyamat sem olyan komplikált, mint amilyennek első ránézésre látszik. Remélem hasznos olvasmány lesz a nyílt forrású rendszerek iránt érdeklődőknek.

Mikor ne?

OSI logo
OSI logo

Mielőtt elmélyülnénk a téma elméleti és gyakorlati kérdéseiben, nem árt tisztázni néhány dolgot. Elsősorban azt, mikor nem célszerű programfordításhoz folyamodni. Mivel a legtöbb ember számára a számítógép eszköz és nem cél, így érdemes a felesleges és sok esetben körülményes munkát megspórolni.

A Linux terjesztési módjából (disztribúció) adó lehetőség, hogy könnyen és gyorsan juthatunk programokhoz. Minden terjesztésnek van saját csomagkezelése és program gyűjteménye, így a programok telepítését érdemes lehetőleg ezekre bízni. A könnyű és gyors telepítésen kívül a csomagkezelés számos előnyt biztosít a felhasználóknak. Ilyen a frissítések kezelése és a programok központi nyilvántartása.

Összegezve axiómának is elfogadhatjuk, hogy amíg egy program elérhető csomagként, addig felesleges fordítással bajlódni. Viszont ha a keresett programot nem találjuk meg a disztribúció hivatalos tárolóiban, érdemes nem hivatalos tárolókkal is próbálkozni (3rd party repo). Ne csüggedjünk, ha esetleg ezekkel sem jártunk sikerrel. Mivel gyakran előfordul, hogy a program készítői csinálnak csomagot a legnépszerűbb disztribúciókhoz. Látogassunk el tehát a kérdéses program weboldalára. Esetleg a freshmeat vagy soruceforge oldalakra. Amennyiben itt sem találtunk megfelelő csomagok máris léphetünk a következő szintre.

Mikor?

Hogy végre a témáról is írjak, nézzük milyen esetekben szükséges forrásból telepíteni. Először is – ahogy fentebb már említettem – nem találtunk csomagok. Esetleg találtunk csomagot, de az nem felel meg az elképzeléseinknek, mert valamilyen okból egy újabb programverzióra van szükségünk.

Netán egy-két funkciót elfelejtettek beleforgatni a disztribútorok. Sebaj! Fordítsuk hát le magunk!

Áááácsi! Mi a fene ez az egész?

Nos úgy érzem, itt megint tennünk kell egy kis kitérőt, mivel néhány alapfogalmat nagyvonalúan elfelejtettem megmagyarázni. Lássuk, melyek ezek:

Csomag (Package): a Linux disztribúciókhoz mellékelt programok terjesztése csomagok formájában történik. Számos jelentős formátuma ismert, ilyenek az .rpm csomagok (Fedora, Mandriva, SuSE, RedHat disztribúcióknál), .deb csomagok (Ubuntu, Debian) és a .tgz csomagok (Slackware, Zenwalk)

Csomagkezelő (Packager): az a grafikus vagy konzolos program, mely a csomagok letöltését, telepítését, firssítését, nyilvántartását és eltávolítását végzi. Legismertebb ezek közül a gyakorlatilag minden disztribúción működésre bírható Synaptic.

Repó (Repo, repository, vagy csomagkönyvtár/raktár): olyan ftp, vagy http szerveken elhelyezett könyvtárak, melyekben a mellékelt csomagok sokaságát – mit sokaságát: garmadáját – találhatjuk.

Forráskód (source code): a nyílt forrású programok (open source software) jellemzője, hogy a program futtatható állományain kívül elérhetővé teszik a program forrását, vagyis azt a kódsorozatot amit a programozó leírt. Ezek sokszor önmagukban nem használhatóak, mivel a számítógép számára értelmezhetetlen parancsokat tartalmaznak. Hogy futtathatóak legyenek, le kell őket fordítani gépi kódra. Ez a fordítási feladatot (compiling) látja el a fordító (compiler).

Lássunk már hozzá!

Ennyi bevezető után ideje komolyra fordítani a szót. Az írásban először a fordítás elméleti kérdéseit tisztázom, majd a második részben néhány konkrét fordítási feladatot is bemutatok.

A fordítás első lépése természetesen a kiszemelt program beszerzése.
Látogassuk hát meg a kiszemelt program weboldalát, és töltsük le a forráskódot. A forrás általában .tar.gz vagy tar.bz2 formátumba van tömörítve. Töltsük le tetszőleges helyre – például a /home/ könyvtárunkba, majd tömörítsük ki. A forráskódok szabványos helye a legtöbb disztribúció esetén a /usr/src/ vagy a /usr/local/src/ könyvtár. Persze máshova is kitömöríthetjük, de érdemes linuxos fájlrendszert választani.

Kicsomagolás tar programmal:

tar -xzvf csomag-verzio.tar.gz

A tar programról részletes leírást a manual oldalaiban találunk.

man tar
tar --help

Hogyha sikerült kicsomagolni a forrásállományokat a következő lépésben két fontos dolgot kell ellenőriznünk. A egyik a készítők utasításai a fordításra nézve, a másik a függőségek ellenőrzése.

Fordítási utasítások:

I want GNUA kitömörített forráskönyvtárban találunk két fontos fájlt – legtöbb esetben, ha nem trehány a készítő – a README és INSTALL text állományokat. Ezeket nyissuk meg tetszőleges szövegszerkesztővel és olvassuk át őket. Amennyiben az INSTALL fájl ezt tartalmazza, egyszerű dolgunk van, mivel ez egy szokványos leírás a telepítés menetéről. Nem is érdemes részletesen átolvasni. Viszont ha eltér ettől, és külön utasításokat tartalmaz a fordításra vonatkozóan, mindenképpen ezeket az utasításokat kövessük! A fordításra, és függőségekre vonatkozó információkat természetesen megtaláljuk a program weboldalán is könnyen elérhető helyen a wikiben vagy a dokumentumok között installation vagy compileing címszóval. Időként sajnos előfordul, hogy a két információ ellentmond egymásnak. Ilyenkor érdemes a forráskönyvtárban találtat hitelesnek tekinteni.

Függőségek ellenőrzése:

A fordításhoz mindenekelőtt szükségünk lesz a fordítóra. A legtöbb GNU/Linuxos program C vagy C++ nyelven íródott, így a GNU Compiler Collection nevű programcsomagra, alias: gcc. Ez egy fodító gyűjtemény C és C++ illetve még néhány más nyelven íródott programok fordításához. Ezen kívül elengedhetetlen még az make program telepítése. Ezekkel egyszerűbb a bonyolult programok fordítása, illetve a legtöbb programot ezek használatával lehet lefordítani. Ezek beszerzése igen egyszerű, mivel gyakorlatilag az összes disztribúcióhoz mellékelik csomagban.

Ha a fordító megvan, már csak a kérdéses programhoz szükséges függőségeket kell telepíteni. Most tegyünk itt egy kis kitérőt a függőségek kérdésének tisztázása érdekében.

A GNU/Linux disztribúciók csomagkezelésének felépítéséből adódik a függőségek rendszere. Ennek lényege, hogy egy megosztott könyvtárat (lib), vagy bizonyos programokat más programok is használhatnak működésük közben. De ezek hiányában nem működnek. A legtöbb program tehát függ más programok meglététől. Néhány példával bemutatva ez a jelenség, hogy közérthetőbb legyen:

– Egy program – mondjuk a méltán népszerű amarok lejátszó – KDE ablakkezelővel működik. Tehát csak a KDE-lib (függvénykönyvtárak) megléte mellett hajlandó működni. (Mellékesen ez magyarázza azt is, hogy miért tud működni más ablakkezelővel is.)

– Az mplayer-gui grafikus felület a közkedvelt mplayer médialejátszóhoz. Mplayer telepítése nélkül – teljesen nyilvánvaló módon – működésképtelen.

Sokszor szemére vetik a GNU/Linux disztribúcióknak, hogy túl bonyolult, és körülményes a függőségi rendszer, átláthatatlan pókhálót alkot. Nos valójában ez csak részben igaz. Mivel a függőségek rendszere pusztán néhány szintű – legtöbb esetben 2-3 maximum 5-6 – valamint a csomagkezelő rendszer átveszi a felhasználótól a függőségek kezelését, így távolról sem olyan bonyolult, mint amilyennek látszik.

Visszatérve a fordításra, a fordítandó programnak is lehetnek függőségei. Ezek tételesen fel vannak sorolva a már megismert textfájlokban illetve a program weboldalán. Elő hát a csomagkezelővel és telepítsük a szükséges függőségeket. De vigyázat! Nem elég a programokat telepíteni, szükségesek – már ha vannak – lesznek a development fájlok is! A függőségeket verziószámmal adják meg. Tehát ha például azt látjuk, hogy a kérdéses programnak az egyik függősége:

qt4 >= 4.3

akkor 4.3-as qt4-re vagy újabb verzióra és qt4-dev csomagokra lesz szükség.

Érdemes megjegyezni, hogy nem minden függőség kötelező. Vannak opcionálisak is, melyek hiányában a program működőképes ugyan, de néhány funkcióról le kell mondanunk. Ezekről tájékoztatást szintén a már megismert helyeken olvashatunk.

Konfiguráció – configure script:

Ennyi előkészület után elérkeztünk a fordítás első igazi lépéséhez, ez a konfiguráció. A forrás állapotú programot számtalan eltérő rendszeren kívánják használni, így valami módon biztosítani kellett a hordozhatóságot. Gondoljunk csak bele: az egyes GNU/Linux disztribúciók kiadásról-kiadásra eltérnek egymástól, ráadásul az egyes disztribúciók is különbözhetnek egymástól. Arról nem is beszélve, hogy más operációs rendszerek – mint az egyes BSD disztribúciók, vagy akár az MS Windowsok – egészen más felépítésűek. Mégis futtathatóak ugyan azok a programok. Mint például a Firefox böngésző. Hogy ez az átjárhatóság biztosított legyen, a programmal fordítás előtt meg kell ismertetni azt a környezetet, amiben működni fog. Ennek első lépését a configure szkript végzi. A configure tulajdonképpen egy bash szkript, ami megvizsgálja a rendszert, számba veszi a függőségeket.

Elindítható a

./configure

paranccsal.

Amennyiben nem talál egy szükséges függőséget, hibát jelez, és leáll. Ez esetben újra vissza kell térni a korábbi függőség teljesítés részére a procedúrának.

A configure szkript egy másik hasznos lehetőséget biztosít, méghozzá azt, hogy magát a lefordítandó programot is konfigurálhatjuk igényeink szerint. Már amennyire ezt a program összetettsége és felépítése lehetővé teszi. Ezen kívül befolyásolhatjuk is a működését. Bizonyos esetekben például más könyvtárat adhatunk meg neki, mint amit automatikusan beállít, illetve bizonyos esetekben szükség is van a közbeavatkozásra. Olyan helyzetekre gondolok, amikor – bár szabályosan telepítettünk minden függőséget – nem találja a szükséges programokat. Ekkor kézzel kell megmutatnunk neki, hol vannak a hiányolt függőségek.

Ezekről részletes leírást a

./configure --help

parancs kiadásával kaphatunk.
Ilyen speciális esetekre később még mutatok néhány példát.

Az alkotás folyamata – make:

Amikor a configure szkript végre hibátlanul lefutott, elérkeztünk történetünk csúcspontjához, a fordításhoz. Azok a kedves olvasóim, akik idáig eljutottak, vendégeim egy hideg italra a közelgő tikkasztó nyáron. A configure szkript futásának végén létrejönnek a Makefile állományok. Ezeket egy kitöltött kérdőívhez tudnám hasonlítani. A kérdőív kitöltését a configure szkript végezte. A kitöltetlen kérdőív a Makefile.in. Az make program a kérdőívre adott válaszok alapján elvégezteti a fordítást a gcc fordítóval.

Nincs más dolgunk tehát, mint kiadni a

make

parancsot, és hátradőlve megvárni míg a program lefordul.

Ez egy roppant hosszadalmas folyamat. A program nagyságától és bonyolultságától valamint a számítógép teljesítményétől függően pár perctől akár órákig is eltarthat. A folyamat végén a forráskönyvtárban létrejönnek a program futtatható állományai és a futásához szükséges egyéb állományok.

A make program a fordíttatáson kívül ellát még néhány fontos feladatot. A fordítást követően a fájlrendszerben a megfelelő helyre másolja a lefordított programállományokat.

make install

parancs hatására.

Ez tulajdonképpen a telepítés. De a létrejött bináris állományokat le is töröltethetjük vele – mondjuk akkor, ha csak utólag vettük észre, hogy valamit nem fordítottunk bele – a

make clean

parancs kiadásával. Valamint a már feltelepített program eltávolítását is ez a program végzi. Ehhez a forrás állományban állva a

make uninstall

parancsot kell kiadnunk.

23202321945

Ha mindezzel végeztünk, hőn áhított programunk már ott lapul a fájlrendszerben alig várva, hogy munkára foghassuk. Most nézzünk néhány tanulságos példát arról, hogy néz ki ez a folyamat a való életben. Remélhetőleg így megvilágosodnak a most talán még homályos foltok is.

Példák:

Avi-ogm info

AVI-OGM info
AVI-OGM info

Az avi-ogm info egy viszonylag egyszerű program. Avi, mp3 és egyéb média fájlokról olvas be adatokat.

A readmeben ezek a lényegi információk:
Függőségek:

- Requirement :
        - gtk+-2.4 or >
        - gtkmm-2.4 or >
        - libogg-1.1 or >
        - libvorbis-1.0 or >
        - libxml2-2.0 or >
        - ffmpeg

Fordítás:

 './configure' (Help : './configure --help')
        'make'
        'make install'

Így egyszerű dolgom volt. Függőségek beszerzése után a configure szkript simán lefutott:

./configure
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for gawk... gawk
checking whether make sets $(MAKE)... yes
checking whether to enable maintainer-specific portions of Makefiles... no
checking for style of include used by make... GNU
checking for gcc... gcc


Itt a configure a rendszer felépítését vizsgálja.

checking for FFMPEG... yes
checking for GTK... yes
checking for GLIB... yes
checking for GTKMM... yes
checking for OGGVORBIS... yes
checking for XML... yes


Majd a függőségeket ellenőrzi.

config.status: creating Makefile
config.status: creating po/Makefile.in
config.status: WARNING:  po/Makefile.in.in seems to ignore the --datarootdir setting
config.status: creating src/Makefile
config.status: creating pixmaps/Makefile
config.status: creating config.h
config.status: executing depfiles commands
config.status: executing po-directories commands
config.status: creating po/POTFILES
config.status: creating po/Makefile


Rendben létrejöttek a Makefile állományok.

make
make all-recursive
make[1]: Entering directory `/usr/src/avi-ogminfo-2.0.5'
Making all in po
make[2]: Entering directory `/usr/src/avi-ogminfo-2.0.5/po'
make[2]: Nothing to be done for `all'.
make[2]: Leaving directory `/usr/src/avi-ogminfo-2.0.5/po'
Making all in src
make[2]: Entering directory `/usr/src/avi-ogminfo-2.0.5/src'
if g++ -DHAVE_CONFIG_H -I. -I. -I..  -I.. -DLOCALEDIR=\""/usr/local/share/locale"\"   -g -O2 -I/usr/include/ffmpeg


Fordul éppen.

make[2]: Entering directory `/usr/src/avi-ogminfo-2.0.5/pixmaps'
make[2]: Nothing to be done for `all'.
make[2]: Leaving directory `/usr/src/avi-ogminfo-2.0.5/pixmaps'
make[2]: Entering directory `/usr/src/avi-ogminfo-2.0.5'
make[2]: Nothing to be done for `all-am'.
make[2]: Leaving directory `/usr/src/avi-ogminfo-2.0.5'
make[1]: Leaving directory `/usr/src/avi-ogminfo-2.0.5'


Sikeresen lefordult.

make install
Making install in po
make[1]: Entering directory `/usr/src/avi-ogminfo-2.0.5/po'
/bin/sh ../mkinstalldirs /usr/local/share
installing fr.gmo as /usr/local/share/locale/fr/LC_MESSAGES/avi-ogminfo.mo
...
make[2]: Nothing to be done for `install-exec-am'.
test -z "/usr/local/share/applications" || mkdir -p -- "/usr/local/share/applications"
 /usr/bin/install -c -m 644 'Avi-OgmInfo.desktop' '/usr/local/share/applications/Avi-OgmInfo.desktop'
make[2]: Leaving directory `/usr/src/avi-ogminfo-2.0.5'
make[1]: Leaving directory `/usr/src/avi-ogminfo-2.0.5'
[root@localhost avi-ogminfo-2.0.5]#


Majd fel is települt, munkára kész.

Transmission

22153546694

A második példa a transmission nevű torrent kliens, melynek fordítása már beavatkozást igényel.

Configuration:

        Source code location:    .
        Compiler:                g++
        Build libtransmission:   yes
        Build Daemon:            yes
        Build BeOS client:       no
        Build GTK+ client:       no
        Build OS X client:       no
        Build wxWidgets client:  no


A configure szkrip lefutása után ez fogad. Csakhogy ez nekem így nem jó, szükségem lenne még a GTK+ kliensre is.

A configure help átnézése után ezt találtam:

./configure --help
`configure' configures transmission 1.06 to adapt to many kinds of systems.
Usage: ./configure [OPTION]... [VAR=VALUE]...
...
Optional Packages:
  --with-PACKAGE[=ARG]    use PACKAGE [ARG=yes]
  --without-PACKAGE       do not use PACKAGE (same as --with-PACKAGE=no)
  --with-gnu-ld           assume the C compiler uses GNU ld [default=no]
  --with-pic              try to use only PIC/non-PIC objects [default=use
                          both]
  --with-tags[=TAGS]      include additional configurations [automatic]
  --with-gtk              Build gtk client
  --with-wxdir=PATH       Use uninstalled version of wxWidgets in PATH
  --with-wx-config=CONFIG wx-config script to use (optional)
  --with-wx-prefix=PREFIX Prefix where wxWidgets is installed (optional)
  --with-wx-exec-prefix=PREFIX
                          Exec prefix where wxWidgets is installed (optional)
  --with-wx               Build wxWidgets client


Tehát külön kell megmondani neki.

./configure --with-gtk

Így már csinál nekem grafikus felületet. Szépen lefordul és települ is gond nélkül.

Smplayer

SMPlayer
SMPlayer

Utolsó példámban a nagyszerű médialejátszó, az mplayer egyik legkiválóbb grafikus felületét az smplayer fordítását mutatom be, mivel némileg eltért az eddig bemutatottaktól.

A forrás könyvtárában talált install.txt állomány egészen más, mint amivel eddig találkozhattunk. Annak áttanulmányozása után úgy határoztam, hogy qt4-el fogom lefordítani. Ehhez a dokumentum tanulsága szerint csak a make parancsot kellett kiadni.

Type "make". With a little bit of lucky, that's all.

Jópofa.

make
./get_svn_revision.sh
cd src && qmake  && DATA_PATH=\\\"/usr/local/share/smplayer\\\" CONF_PATH=\\\"/usr/local/etc/smplayer\\\" TRANSLATION_PATH=\\\"/usr/local/share/smplayer/translations\\\" DOC_PATH=\\\"/usr/local/share/doc/packages/smplayer\\\" THEMES_PATH=\\\"/usr/local/share/smplayer/themes\\\" SHORTCUTS_PATH=\\\"/usr/local/share/smplayer/shortcuts\\\" make
WARNING: Found potential symbol conflict of inputdvddirectory.cpp (inputdvddirectory.cpp) in SOURCES
WARNING: Found potential symbol conflict of inputdvddirectory.h (inputdvddirectory.h) in HEADERS
WARNING: Found potential symbol conflict of filepropertiesdialog.cpp (filepropertiesdialog.cpp) in SOURCES
WARNING: Found potential symbol conflict of filepropertiesdialog.h (filepropertiesdialog.h) in HEADERS
WARNING: Found potential symbol conflict of eqslider.cpp (eqslider.cpp) in SOURCES
WARNING: Found potential symbol conflict of eqslider.h (eqslider.h) in HEADERS
...
Makefile:1327: warning: ignoring old commands for target `.moc/moc_prefadvanced.cpp'
Makefile:1426: warning: overriding commands for target `.moc/moc_about.cpp'
Makefile:1363: warning: ignoring old commands for target `.moc/moc_about.cpp'
Makefile:1429: warning: overriding commands for target `.moc/moc_inputmplayerversion.cpp'
Makefile:1360: warning: ignoring old commands for target `.moc/moc_inputmplayerversion.cpp'
/usr/lib/qt-3.3/bin/uic inputdvddirectory.ui -o .ui/inputdvddirectory.h
uic: File generated with too recent version of Qt Designer (4.0 vs. 3.3.8b)
make[1]: *** [.ui/inputdvddirectory.h] Error 1
make[1]: Leaving directory `/usr/src/smplayer-0.6.0rc2/src'
make: *** [src/smplayer] Error 2


Nem volt szerencsém. Ezt tisztességesen elvarningol.

Az install.txt további tanulmányozása után választ kaptam a problémára:

But it may fail. Don't worry.

If it fails it is probably because the Qt 3 qmake has been used instead of
the Qt 4 one. It seems that some distros (ubuntu for example) have renamed that
tool to qmake-qt4. Others may have installed in another directory.
Look at the contents of the qt4-devel package (or whatever its name is) and
find out where it is.

Now type something like this (just examples):

make QMAKE=qmake-qt4
or
make QMAKE=/usr/share/qt4/bin/qmake

Tehát máshogy kell közelíteni a problémához. Nem a make programot használja, hame a qt4-es qmaket.

make QMAKE=qmake-qt4

Így már lefordul, települ és szépen el is indul.

Ennyit dióhéjban erről a rendkívül hasznos eljárásról. Persze a téma ennél nagyságrendekkel összetettebb, de úgy vélem a kezdeti sikerek eléréséhez ennyi ismeret már elegendő lehet. Az itt bemutatott példák egészen máshogy nézhetnek ki más renszereken. Remélem sikerült egy kicsit közelebb hozni ezt a témát az érdeklődőknek. Kívánok mindenkinek sikerekben gazdag program forgatást.

6 hozzászólás


  1. Üdv! Lenne egy kérdésem, de az sajnos nem passzolt sehova ezért ide írom:
    Leszedtem egy tar.gz kész programcsomagot (azaz minden készen van benne, pl a bin, home satöbbi mappa tartalma) de Ubuntu alatt ezt nem lehet feltenni “simán”, duplakatt-telepítés módszerrel. Esetleg el tudnád mondani hogy
    a, hogy tudnám átalakítani .deb csomagba VAGY
    b, hogy tudnám kicsomagolni /-ba?


  2. Szia!

    Remélem még olvasod a választ. Így egy lefordított programnak tűnik. Elég gyakori, hogy így terjesztenek programokat.
    Első körben én a ~/programneve könyvtárba kitömöríteném, és ./bináris módszerrel próbálkoznék.

    De így látatlanban elég nehéz, ha nem titok akkor dobhatnál egy linket, hogy mi is az.

    Egyébként nem csomag, tehát nem csomagkezelővel kell telepíteni.
    Lefordítani sem kell. .deb csomagot csinálhatsz bináris programból is, de szerencsésebb forgatni hozzá. Már ha elérhető a forrás.

    Egyszóval: link? :)


  3. Sajnos a forrás nem elérhető mibel ez a Firefox 3.7 lenne… ezt a ./bináris módszert nem ismerem, ismertetnéd velem? És igen ez egy előre lefordított program de sajnos nem adtak telepítési útmutatókat…


  4. Már a 3.7 kinn van?
    http://ftp.mozilla.org/pub/mozilla.org/firefox/nightly/latest-trunk/
    innen töltötted le?
    Ezt csak simán kitömöríted egy arra alkalmas könyvtárba.
    Nem tudom a te disztródon hová szokás, első körben a /home/sajátuser könyvtáradba érdemes.
    (Vagy /usr/local esetleg a /opt útvonalra.)
    Utána terminálba navigálj el oda, ahol a futtatható állomány van és
    ./firefox
    paranccsal indul. Vagy csinálj neki egy parancsikont az asztalra vagy menübe.
    FF forgatást nem javaslom. :)


  5. Igen onnan szedtem le! Köszi szépen!
    Ubuntu Jaunty-t használok, de 21 nap + két hét múlva váltok Karmic Koala-ra!
    Köszi a segítséget, mostmár működik!


  6. Ez egy nagyon hasznos leírás köszönöm, bár egy részével tisztában voltam eddig is, most mégis tanultam pár új dolgot.

    Kössz mégegyszer. :)

Vélemény, hozzászólás?

Az email címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöljük.