View all posts
From the archive — this post is written in Czech and its content is quite outdated by now.
VÝVOJ · DEVOPS·May 2018·6 min read

VSTS a desktopové aplikace

„Vždyť VSTS je jen pro weby a musí se nasazovat do Azure!“ Nemusí — jak jde přes VSTS rozjet build a distribuce klasické desktopové aplikace.

“Vždyť to VSTS je jen pro weby…a navíc se to musí nasazovat do Azure!” říkal kamarád a pokračoval “navíc když potřebuji build někomu dát, u sebe na disku ho můžu vzít, zazipovat a poslat, to ve VSTS neudělám”.

Přemýšlel jsem, jak takovému názoru mohl přijít (ne, VSTS zatím nepoužíval — možná právě kvůli těmto předsudkům). Možná k němu došel právě proto, že skoro v každé zmínce, v téměř každém tutoriálu je (často i samotným Microsoftem) VSTS prezentováno v souvislosti právě s Azure (konkrétně s CI a deploymentem webové aplikace právě do Azure). Ano, to je nepochybně častý, užitečný a efektivní způsob využití, ale VSTS můžete stejně dobře použít nejen pro webové aplikace.

Team Services

Zkusím to stručně uvést na pravou míru a ukázat, že i pro běžnou, malou, desktopovou aplikaci lze VSTS nejen používat, ale výrazně si s jeho pomocí ušetřit práci, nervy a čas.

Jednoduše řešeno, VSTS je cloudový nástroj pro vývojáře. Už z tohoto snad plyne, že (ať už umí cokoliv) ho máte dostupný odkudkoliv. Ve stručnosti, možnosti VSTS pokrývají v zásadě 3 velké tematické oblasti:

Práce: organizace vývojových sprintů, životní cyklus ticketů,…

Kód: verzování kódu, podporován je git a TFS.

Build&Deploy: buildovací proces ať už přímo v cloudu nebo na vašem serveru prostřednictvím build agenta.

…pokryto je několik dalších oblastí, jiné lze přidat pomocí pluginů.

Kouzlo je právě v tom, že všechny tyto oblasti pokrývá jeden systém. Nemusíte řešit všechny, použijte co se vám zrovna hodí — u drobných aplikací často používám jen verzování kódu a buildování. Můžete také snadno propojit zadání práce s kódem i verzí aplikace. Snadno se pak třeba z dokumentace zadání od klienta prokliknete přímo do kódu (do commitu), který zadání implementuje. Zároveň pak můžete vidět, z kterého commitu (z jaké verze kódu) je aplikace sestavena a co který build obsahuje. Toto všechno můžete používat buď z webu, nebo třeba přímo z Visual Studia. Celé je to zdarma do 5 uživatelů, což bohatě stačí nejen na hraní, ale i pro malé týmy — osobně VSTS používám i pro projekty, na kterých pracuji sám. Je toho spousta, co by se ještě dalo k VSTS napsat, ale o tom tento článek není.

Příklad

Evidence požadavků (zadání) si asi každý dovede nějak intuitivně představit. Verzování kódu snad také. Na příkladu jedné malé desktopové aplikace se pokusím ukázat

jak ji zbuildovat ve VSTS

jaký to má přínos a proč to nedělat na lokální mašině

jak si ve VSTS připravit a zbuildovat balík

Máme teda nějaký kód ve VSTS. Jak se tam dostal? Vyvinuli jsme nějakou funkčnost (klasicky ve Visual Studiu, jak to známe), (pomineme teď jakékoliv testování), zkusili projekt zbuildovat a kód jsme pushnuli/commitnuli do repozitáře (do VSTS). Zatím z toho žádná výhoda neplyne, že?

Kód jsme ale mohli svázat se zadáním úkolu (s dokumentací). První přínos tedy je, že (za předpokladu, že budeme rozumně commitovat a nebudeme míchat nesouvisející práci – třeba do feature branch) od teď pro zadání dokážeme vidět, v kterých commitech je implementováno. A naopak, pro kód máme odkaz na zadání (dokumentaci).

VSTS nabízí i nástroje pro CI (průběžnou integraci). Po každém commitu můžeme VSTS nechat náš projekt zbuildovat.

Co to znamená? Projekt je zbuildován úplně stejně jako ve Visual Studiu (pokud chcete, nemusí to být úplně stejně, viz dále). Z takového buildu dostaneme report o tom, zda se build zdařil, popř. co se nezdařilo (někdy z toho je na první pohled poznat, proč se build nezdařil).

Co z toho? Když to jde zbuildovat ve Visual Studiu, půjde to i v cloudu, no ne? Ne tak úplně. Jako vývojáři dřív nebo později narazíte na situaci, kdy se budete dohadovat s kolegou a začnou padat argumenty jako “u mě to funguje”. Pokud to nefunguje na “neutrálním bojišti” (což je v tomto případě VSTS), pak to nefunguje vůbec. Příčinou jsou typicky nějaké lokální závislosti — třeba když kód závisí na nějaké entitě, kterou na svém vývojářském stroji máte, ale do cloudu jste ji nedali (v nejjednodušším případě třeba lokální obrázky/knihovny). Pomocí buildu v cloudu se hned dozvíte, že je něco špatně a máte šanci to spravit dřív, než chyba omezí kolegy (doslova dřív, než jim znemožní další práci).

Příklad reportu úspěšného buildu.
Příklad reportu úspěšného buildu.

Samotný úspěšný build samozřejmě nijak nezaručuje, že kód je logicky správně (že dělá to, co dělat má…a že nerozbíjí něco, co rozbíjet nemá). Jako jeden z kroků buildovacího procesu ve VSTS tedy obvykle následuje spuštění automatických testů (píšete testy, že?). Pokud test(y) neprojdou, build není úspěšný a opět se o tom dozvíte.

Součástí buildovacího procesu samozřejmě mohou být další kroky, které během vývoje nemají smysl, ale během přípravy balíčku už ano. Můžete třeba přidat atributy.

Nastavení atributů v build procesu.
Nastavení atributů v build procesu.
Atributy výsledného EXE souboru.
Atributy výsledného EXE souboru.

Abych ukázal, že ve VSTS je možné snadno připravit balíček, podíváme se na následující scénář. Řekněme, že naše aplikace využívá pár knihoven, které jsou součástí buildu ve formě DLL knihoven. Pro ilustraci řekněme, že nechceme klientovi (testerovi, managerovi,…) dávat adresář s desítkami souborů, ale rádi bychom mu dali jeden EXE soubor. (Pzn. pro jednoduchost tady nebudeme řešit vytvoření instalátoru, zkrátka jen chceme všechno splácat do jednoho souboru, který pak někomu můžeme předat).

Jednou z cest jak toho dosáhnout ve Visual Studiu jsou post build akce. To není nic jiného, než kus kódu, který se provede po úspěšném buildu. V našem konkrétním příkladu tedy potřebujeme kus kódu, který z adresáře vezme spustitelný soubor (EXE) a potřebné knihovny (DLL) a vše spojí do jednoho. K tomu existuje šikovná utilitka ILMerge (je potřeba ji stáhnout a přidat do projektu), kterou stačí zkonfigurovat a předat jí seznam souborů, které má spojit.

Tento kus kódu je potřeba někde zavolat. To se děje ve vlastnostech projektu (Properties) v záložce Build Events. Kód by mohl být přímo zde, ale pro lepší přehlednost doporučuji do projektu přidat nový soubor (pojmenovaný třeba post_build a ten z Build Events zavolat). Rovnou můžete zvážit, že tyto akce nebudete volat vždy (během vývoje žádné balíky nepotřebujete, tak proč na ně čekat), ale post build akce volat třeba na základě konfigurace (během vývoje používáte Debug konfiguraci, při buildu balíčku Release konfiguraci). Pak by post build akce mohla vypadat takto.

if $(ConfigurationName) == Release 
(
  post_build.bat 
    "$(ProjectDir)" "$(SolutionDir)..\" "$(ConfigurationName)"
)

A post_build skript mohli vypadat třeba takto.

echo Building package
echo %~1
echo %~2
echo %~3
echo Clean final destination
del /f /s /q __PACKAGE__ 1>nul
rmdir /s /q __PACKAGE__
mkdir __PACKAGE__
echo Clean temp destination
del /f /s /q __TEMP_PACKAGE__ 1>nul
rmdir /s /q __TEMP_PACKAGE__
mkdir __TEMP_PACKAGE__
echo Add dependencies
move x64 __TEMP_PACKAGE__/x64
move x86 __TEMP_PACKAGE__/x86
move THIRDPARTY __TEMP_PACKAGE__/THIRDPARTY
echo Merge DLLs
ILMerge.exe /target:winexe /out:dropdetector_merge.exe /targetplatform:v4 /closed DropDetector.exe StructureMap.dll log4net.dll OpenTK.dll OpenTK.GLControl.dll OpenTK.Compatibility.dll ZedGraph.dll Emgu.CV.UI.dll Emgu.CV.World.dll Emgu.CV.UI.GL.dll
move dropdetector_merge.exe __TEMP_PACKAGE__/dropdetector_merge.exe 2>nul 1>nul
move DropDetector.exe.config __TEMP_PACKAGE__/dropdetector_merge.exe.config 2>nul 1>nul

Výsledkem je tedy adresář obsahující v tomto případě soubor dropdetector_merge.exe (nezapomeňte přesunout také příslušný config).

Když to funguje ve Visual Studiu, proč by se s tím nepopralo VSTS (neudělali jsme nic víc než drobnou úpravu definice buildu)? Co se stane, když zapojení post build akcí comitnete do VSTS? Pokud máte vhodně nastavený CI build, pak stejný balíček dostanete i zde (včetně benefitu buildu v cloudu, spuštění automatických testů,…). Balíček si pak můžete stáhnout z artefaktů.

VSTS a desktopové aplikace — obrázek 4
Vlastnosti výsledného EXE souboru.
Vlastnosti výsledného EXE souboru.

Můžete to dále vyšperkovat třeba tak, že výsledný balíček zazipujete. Dalším z kroků build procesu ve VSTS může být třeba upload na FTP, nebo cokoliv dalšího vás napadne…

echo Create package
"%~1\THIRDPARTY\7Zip\7z.exe" a dropdetector_package.zip "%~1\bin\%~3\__TEMP_PACKAGE__\*" 2>nul 1>nul
echo Add package
move dropdetector_package.zip __PACKAGE__/dropdetector_package.zip

Jak to máte vy? Používáte VSTS nebo dáváte přednost jiným nástrojům?

Jiří Koutný
Engineering Manager · Lecturer · Pilot
LinkedIn ↗
Jiří Koutný {{ navTagline }}
{{ t.hero.kicker }}

{{ t.hero.titleA }} {{ t.hero.titleB }}

{{ t.hero.lead }}

{{ labels.posts }}

{{ t.posts.title }}

{{ t.posts.all }} →
{{ post.category }}
{{ post.date }} · {{ post.readLabel }}

{{ post.title }}

{{ post.excerpt }}

{{ labels.contact }}

{{ t.contact.title }}

{{ t.contact.lead }}

{{ d.label }}
{{ d.value }}
{{ t.about.label }}

{{ t.about.title }}

{{ t.about.lead }}

{{ t.about.achievementsLabel }}

{{ ac.stat }}
{{ ac.label }}
{{ ac.text }}
{{ aboutBaseLabel }}
{{ aboutBaseValue }}

{{ para }}

{{ t.about.expLabel }}

{{ job.tenure }}
{{ job.place }}

{{ job.org }}

{{ r.title }} {{ r.period }}

{{ pt }}

{{ t.about.teachingLabel }}

{{ job.tenure }}
{{ job.place }}

{{ job.org }}

{{ r.title }} {{ r.period }}

{{ pt }}

{{ t.about.earlierLabel }}

{{ t.about.earlierText }}

{{ t.about.eduLabel }}

{{ ed.period }}
{{ ed.school }}
{{ ed.degree }}

{{ ed.note }}

{{ t.about.staysLabel }}

{{ t.about.staysIntro }}

{{ grp.year }}
{{ s.period }}
{{ s.inst }} · {{ s.place }}
{{ s.what }}
{{ t.aviation.label }}

{{ t.aviation.title }}

{{ t.aviation.lead }}

{{ s.label }}
{{ s.value }}
{{ a.title }}
{{ a.body }}
{{ t.aviation.project.tag }}

{{ t.aviation.project.name }}

{{ t.aviation.project.tagline }}

{{ t.aviation.project.body }}

{{ t.aviation.project.cta }} ↗
{{ p }}
{{ t.aviation.flyWithMe.kicker }}

{{ t.aviation.flyWithMe.title }}

{{ t.aviation.flyWithMe.body }}

{{ t.aviation.flyWithMe.cta }} →
{{ t.grammars.kicker }}

{{ t.grammars.title }}

{{ t.grammars.lead }}

{{ t.grammars.thesisLinkLabel }} ↗
B a b d e S A D c
A derivation tree. Its leaves, left to right, spell the generated word. Reading the highlighted root-to-leaf path top-down gives word(p) = S·A·D·c. These grammars require such a path — or a cut (a frontier of nodes, below) — to belong to a separate control language, while every production stays context-free.
{{ t.grammars.problemLabel }}

{{ para }}

{{ t.grammars.bgLabel }}

{{ t.grammars.bgIntro }}

{{ m.name }}

{{ m.text }}

{{ t.grammars.limitsLabel }}

{{ lim }}

{{ t.grammars.contribLabel }}

{{ t.grammars.contribIntro }}

Cuts
A frontier of nodes; their labels spell word(c).
Paths
One root-to-leaf path; its labels spell word(p).
Multiple paths
Several paths, each spelling a controlled word.
{{ c.n }}

{{ c.name }}

{{ c.what }}

{{ t.grammars.resultWord }}

{{ c.result }}

{{ t.grammars.acrossLabel }}

{{ a }}

{{ t.grammars.receptionLabel }}

“{{ r.quote }}”

{{ r.who }} · {{ r.role }}
{{ t.grammars.defenseLabel }}

{{ t.grammars.defenseText }}

{{ t.grammars.defenseResult }}

{{ t.grammars.pubsLabel }}
  1. {{ p.title }} {{ p.venue }}
{{ t.grammars.recognitionLabel }}

{{ r }}

{{ t.grammars.futureLabel }}

{{ f }}

{{ t.grammars.citationLabel }}

{{ t.grammars.citation }}

{{ t.grammars.bibtex }}
{{ t.grammars.thesisLinkLabel }} ↗

{{ t.grammars.oneLine }}

{{ labels.posts }}

{{ t.posts.title }}

{{ leadPost.category }}·{{ leadPost.date }}·{{ leadPost.readLabel }}

{{ leadPost.title }}

{{ leadPost.excerpt }}

{{ t.posts.readMore }} →
{{ post.category }}
{{ post.date }} · {{ post.readLabel }}

{{ post.title }}

{{ post.excerpt }}

{{ t.posts.archiveLabel }}

{{ t.posts.archiveTitle }}

{{ t.posts.archiveDisclaimer }}

← {{ t.posts.all }}
{{ t.posts.archiveNote }}
{{ currentPost.category }}·{{ currentPost.date }}·{{ currentPost.readLabel }}

{{ currentPost.title }}

404

{{ notFound.title }}

{{ notFound.body }}