środa, 26 lutego 2014

Ant - Selektory plików dla fileset

Na wstępie powiem tak - to nie jest tutorial. Znacznie lepsze określenie do tego co jest niżej to rodzaj pamiętnika. Wiele rzeczy można zrobić na wiele sposobów. Niektóre są dobrze opracowane i łatwo je znaleźć. Niektóre nie wydają się zbyt popularne i drążenie zaczyna się od suchej dokumentacji. To co próbuje zrobić niżej można zrobić na wiele innych sposobów (pewnie to prawda). Ja akurat się uwziąłem aby zrobić to przy pomocy ANT-a. Chodzi o wybieranie elementów do fileset-a. fileset jest przekazywany jako argument do innych task-ów i dlatego jest popularny.

W przypadku prostych definicji nie trzeba sobie zawracać głowy, wystarczy tylko wskazać katalog i się robi. To jest dobre. Co zrobić gdy trzeba w jakiś złożony sposób spreparować listę do skopiowania?
Zamiast zmagać się z jakimś kosmicznym wyrażeniem regularnym pisanym w xml-u można spróbować dodać do fileset selektor który będzie wybierał pliki przy pomocy kodu. Wydaje się że to będzie prostsze. Czy to prawda? Z tego co piszą można to zrobić korzystając z języków skryptowych. Można też napisać klasę która to realizuje wykorzystując Custom Selectors.

Może najpierw spróbuje sprawdzić selektory skryptowe. Wybrałem jako język JavaScript.
Przykład selektora który wybiera każdy plik
<scriptselector language="javascript">
    self.setSelected(true);
</scriptselector>
Przykład selektora który ma nazwę o długości parzystej
<scriptselector language="javascript">
    self.setSelected((filename.length%2)==0);
</scriptselector>
Jak to wykorzystać w taskach które akceptują fileset? Poniżej przykład kopiowania z takim selektorem
<copy todir="dir-to">
    <fileset dir="..">
        <scriptselector language="javascript">
            self.setSelected((filename.length%2)==0);
        </scriptselector>
    </fileset>
</copy>
Po wykonaniu widać że kopiowana jest cała hierarchia. Wcale nie jest to takie proste. Na pierwszy rzut oka nie wiadomo dlaczego i skąd bierze się self i co to w ogóle jest. No na szczęście w tym temacie wyjaśnia się sprawa po lekturze manual-a Script Selector (patrz Selektory w ANT i radzę dokładnie przeczytać pisze co i jak).

Mam już jakieś narzędzie. Postawiłem więc sobie takie zadanie. Chcę wybrać tylko katalogi (nie pliki), które są w katalogu poniżej i zaczynają się od liczby.
Moja struktura może to wyglądać tak:
[all]
    build.xml
    test.xml
[00]
    [properties]
    ...
    build.xml
[00_auth]
    [properties]
    ...
    build.xml
[02]
    [properties]
    ...
    build.xml
...
[17]
    [properties]
    ...
    build.xml
[17_auth]
    [properties]
    ...
    build.xml
Jest to typowe zadanie gdy buduje wersję dla różnych oddziałów. W każdym katalogu który wybiorę jest skrypt, który buduje aplikację. Nie jest ważne jak to się buduje. Ważne jest to że chce mieć jakiś customizowalny sposób na wybieranie tych katalogów. Jakby się zastanowić to wcale może nie trzeba korzystać z selektora skryptowego! Może skorzystać z Filename Selector oraz Depth Selector. Mój testowy skrypt Ant-a to test.xml. Przyjąłem założenie, że w każdym katalogu jest plik o nazwie build.xml. Jako częściowa próba zrealizowania tego może to wyglądać tak:
<copy todir="dir-to" verbose="true">
    <fileset dir="..">
        <and>
            <depth max="1" />
            <filename regex="^\d+" />
            <filename regex="build.xml" />
        </and>
    </fileset>
</copy>
Wydaje się to działać. Ale czy to koniec? Normalne copy kopiuje wszystko to kopiuje tylko build.xml. Działa ale tylko dlatego że jest jeden build.xml. Co by było gdyby  build.xml był w kolejnym podkatalogu [00]/xx/build.xml? Skopiuje się! Nie okazuje się że właśnie depth przed tym zabezpieczył!

Selectory filename

W selektorze przekazywana jest nazwa pliku. W dokumentacji bezpośrednio nie ma informacji że to jest pełna ścieżka od miejsca podanego jako atrybut dir dla fileset. To dość ważne. Pisząc warunek który wybiera po nazwie chcemy np zdefiniować tak aby brało 17/ ale już nie 17_auth/. W zależności od tego czy ant jest odpalony na Windows czy Linux warunek jest interpretowany inaczej i wartość filename przekazana do badania warunkiem może mieć ścieżkę z separatorem / lub \.
Stosując wyrażenie regularne można to opędzić definiując np. wyważenie
<filename regex="^17[\\/]" />
Taki patent działa na obu systemach.

Eclipse

Uruchomienie takiego skryptu w Eclipse wymaga ustawienia ANT_HOME w Eclipse na właściwą wersję ANT. Wymagana też jest wskazanie odpowiedniej wersji JAVA. W moim przypadku korzystałem ze starej wersji Eclipse dlatego wskazałem moją własną - zewnętrzną wersję ANT (Window / Preferences / Ant / Runtime / Ant Home). Uruchomnienie przy pomocy Eclipse wymagało wskazania JAVA 1.6 (prawoklik - menu - Debug As / Ant Build / JRE / minumum 1.6)
W przypadku JAVA 1.5 dostałem komunikat że nie można załadować BSF
Nie udało mi się na razie debugować wyrażenia w JavaScript. Debuguje się jedynie skrypt ANT-a.
W sumie mało użyteczne odpalenie tego z Eclipse!

Selektory w ANT http://ant.apache.org/manual/Types/selectors.html
Opis języka skryptowego http://ant.apache.org/manual/Tasks/script.html

poniedziałek, 24 lutego 2014

Windows - batch

Manager urządzeń

Można odpalić z cmd przez komenę devmgmt.msc

Pobranie do zmiennej bieżącego katalogu

Przypadkowo zdarzyło mi się że musiałem napisać prosty plik bat. Nie przepadam za skryptami w batch-ach... ale gdy trzeba trudno.
Chciałem sprawdzić bieżący katalog.
Postąpiłem w sposób analogiczny jak dla Linux.
Pewnie jest to jakaś zmienna systemowa.
Okazało się że to nie tak samo...

W przypadku Linux można to sprawdzić przeglądając zawartość zmiennych komendą set
Wartość mamy w PWD
set |less
set |grep PWD=
Chciałem to sprawdzić dla Windows. I co? Okazuje się że szukam a tu nie ma!
set |more
set >c:\test
Można sobie taką zmienną spreparować. Komenda cd zwraca tę wartość.
set PWD="%cd%"
echo %PWD%

... i gotowe

Źródło http://stackoverflow.com/questions/6789491/batch-script-to-set-a-variable-with-the-current-path-location

Szukanie pliku który się uruchomił

I znów pracuję w Windows.
Odpalam konsole cmd. Wydaje polecenie np: java i wykonuje. 
Działa... ale czemu?
Może na początku zaznaczę że polecenia wydaje bez podania pełnej ścieżki. Wygląda tak jak komenda systemu. Niektóre polecenia się wywołują - ot tak - bez podania pełnej ścieżki.
Gdyby się zastanowić, to tak do końca nie wiem co się uruchomiło.
Sprawdzając managera zadań, pewnie zobaczę pełną ścieżkę, ale już po uruchomieniu.
A jak zobaczyć co system wybierze zanim to wykonam?

Wracając do teori w systemie Windows programy są odnajdywane na podstawie ścieżki PATH. Skąd tak na prawdę ten program jest uruchamiany? Jak znasz scieżkę PATH możesz spróbować to prześledzić, ale czy to proste i komu by się to chciało robić.

Okazuje się że można zobaczyć co właściwie system znajduje gdy się poda polecenie - służy do tego where.
where java
 C:\Windows\System32\java.exe
 D:\java\jdk\jdk1.6\bin\java.exe
Teraz już wiem.... wykona C:\Windows\System32\java.exe bo jest pierwsze na liście

W przypadku Linux jest whereis. Wydaje się że where z Windows to właśnie jego odpowiednik.

Rozszerzone polecenia cmd

Chciałem wykonać prosty skrypt do odpalania programu java. Jestem w katalogu X, wywołuje skrypt program.bat z podaniem bezwzględnej ścieżki katalogu Y. W ścieżce Y znajdują się wymagane jary. Trzeba je dodać do wywołania jar-a. Okazuje się że zrobiłem sobie zagadkę.
c:\X> c:\Y\program.bat
Kod program.bat jest prosty:
java -cp .\lib1.jar Class1
Program przy takim wywołaniu się wykłada. Wina to podanie bezwzględnej ścieżki. Ścieżka reprezentowana przez . to c:\X a tu nie ma lib1.jar.

Jak zatem to zrobić aby działało również dla takiego przypadku?
Nie chce przecież w skrypcie mieć zapisanych ścieżek na sztywno!
Czytam więc reference dla cmd. Zabrałem się do tego po lekturze linka poniżej. Należy zwrócić uwagę że I to numer od 0 - n
%~fI        - expands %I to a fully qualified path name
%~dI        - expands %I to a drive letter only
%~pI        - expands %I to a path only
Co ciekawe podobny rezultat dostałem gdy sprawdziłem zawartość %0 podobnie jak dla %~f0 

Idąc tym tropem spróbowałem ustawić zmienną. Wydaje się działać:
set BATH_PATH=%~d0%~p0
Po tych zmianach kod program.bat wygląda tak
set BATH_PATH=%~d0%~p0
java -cp %BATH_PATH%\lib1.jar Class1
Teraz działa tak jak chciałem!