Zobrazují se příspěvky se štítkemgit. Zobrazit všechny příspěvky
Zobrazují se příspěvky se štítkemgit. Zobrazit všechny příspěvky

30. června 2018

Správa proprietárních závislostí v Golang

Se změnou zaměstnání přišly nové výzvy - prototypujeme teď nový produkt a jako primární technologie byl zvolen Golang. Myslím si, že vzhledem k povaze produktu (smečka mikro-servis) a cílové infrastruktuře (IaaS) je to dobrá volba.

Golang není zas až tak nový jazyk (je tady s námi nějakých 9 let), a ačkoliv se v něm příjemně píše, má vývoj v Golangu určitá úskalí a výzvy - to buď v případě, že vám úplně nevyhovuje, jak v Googlu vymysleli vývojový proces, anebo pokud očekáváte vlastnosti běžné na některé zralejší platformě.

Jak já, tak celý tým máme silné Java zázemí a je tedy možné, že náš přístup ke Golang infrastruktuře není úplně adekvátní. Nicméně poslední tři měsíce jsem se tématu verzování, závislostí, reprodukovatelnosti a automatizaci Go buildů intenzivně věnoval a řekl bych, že to soudruzi z U.S.A "nedotáhli úplně dokonce".

Požadavky

Pro náš projekt jsme měli, řekl bych, celkem běžné nároky:
  1. Privátní Git repository za firemní proxy. Kvůli legálním záležitostem a firemním politikám nemůžeme dát projektový kód na GitHub.
  2. Samostatná projektová repozitory. OK, chápu, že v Googlu milují monorepo, ale většina z nás v Googlu nepracuje...
  3. Reprodukovatelný build. To je samozřejmost, k tomu asi není co dodat.
  4. Spolehlivá správa závislostí a jejich verzí. V Javě, nebo na Linuxu je to no-brainer - stačí mít package manager a repozitory, ne?
  5. Plně automatizované. Zase by se řeklo samozřejmost, ale pořád a pořád potkávám lidi, kteří smolí (klidně i produkční) buildy ručně.

Problémy

Pokud budete chtít výše zmíněné požadavky naimplementovat v Golangu, narazíte na problém: out-of-the-box to nejde vůbec a pokud nebudete ochotni část nároků oželet, čeká vás world-class hackování. (Teda ne, že bych byl takovej hustej hacker... spíš jsme narazili na hluché místo.)

Esenciální potíž je, že celý Golang ekosystém je navržený jako jedno velké open-source monorepo (a.k.a. GitHub). Pokud nechcete/nemůžete mít svůj kód na GitHubu, máte problém. Protože:
  • Golang nemá jednotný/dominantní nástroj na automatizaci. Většina projektů na GitHubu builduje makem. make je fajn na kompilaci, ale dělat v něm komplexnější automatizaci je masochismus.
  • Golang nemá dořešený verzování dependencí. Člověk by řekl, že po těch letech to už bude nějak usazený, ale stačí nahlédnout do oficiální wiki stránky, nazvané lapidárně Package Management Tools a zjistit, že pro správu dependencí a verzí existuje nějakejch 20-30 nástrojů. Naštěstí se od loňského léta masivně prosazuje nástroj dep a velmi slibně to vypadá s oficiálním návrhem vgo, který by se snad měl cca na podzim dostat do Go verze 1.11.
  • Oficiální Golang nástroje neumí pracovat se závislostmi, které nejsou ve veřejných repozitářích (GitHub, Bitbucket, GitLab). Jediný podporovaný způsob je nastavit na repozitory HTML meta tag go-import. Což většinou(?) nemáte pod kontrolou.

GOPATH odbočka

Malé vysvětlení pro čtenáře, kteří nejsou Gophers (fanoušci Golangu). Proč jsou výše zmíněné problémy reálnými problémy? Všechny Golang nástroje předpokládají, že vaše zdrojáky jsou soustředeny pod tzv. GOPATH - což je fakticky lokální monorepo.

Protože v Golangu se všechno linkuje a kompiluje staticky, je potřeba mít všechny zdrojové kódy na jednom místě, neexistují binární závislosti. Pro stažení externích závislostí (v Go nazývaných package import) slouží nástroj go get, který de facto naklonuje Git repository dané závislosti do adresářové struktury pod GOPATH.

Pokud bychom pominuli verzování a reprodukovatelnost a soustředili se jenom na kompilaci, máme tady dva základní konflikty:
  • go get neumí klonovat z non-public repozitářů.
  • Pokud nemáte váš projekt pod GOPATH, budete muset s GOPATH nějak manipulovat, aby Go nástroje vůbec fungovaly.


Řešení

Protože jsem husto-krutej drsňák, před kterým bledne jak Phil Marlowe, tak Dirty Harry, tak jsem se nezaleknul, zatnul zuby a všechny problémy vyřešil. Svým způsobem...

Pravdou je, že řešení, ke kterému jsme prozatím doiterovali, byl průzkum bojem - tj. paralelně řešit business požadavky (proč vlastně ten prototyp děláme) a zároveň vyřešit automatizaci buildů (nebo minimálně aspoň kompilaci Golang projektů).

Když začnu s výše popsanými požadavky od konce - pro automatizaci jsme zvolili Gradle. Původně jsme počítali s nějakým polyglot řešením (něco v Javě, něco v Golang), ale nakonec jsme skončili s čistě Golang moduly.

Ironií je, že v celém řešení víceméně žádná Java není (výjimkou jsou Gradle pluginy). Každopádně, Gradle se výborně osvědčil - buildujeme v něm Go, RPM balíčky, Docker image, stahujeme a re-packagujeme binárky z internetu, pouštíme integrační testy, deployujeme do repozitářů a do cloudu atd.

To všechno je hezké, jak ale v Gradlu skompilovat Golang zdrojáky? Samozřejmě, vezmu nějaký hotový plugin. Bohužel, výše naznačené problémy s verzováním Golang závislostí způsobily, že nám žádný plugin úplně nevyhovoval.

A tak jsem si napsal svůj plugin. Řekl jsem si, že základní nástroje v Go fungují dobře a tak je jenom zorchestruju. Takže můj plugin není nic jiného než: wrapper + orchestrátor + lifecycle. Plugin řeší následující věci:
  1. Manipulace s GOPATH, aby fungovaly nástroje, které očekávají zdrojáky v... GOPATH.
  2. Stažení externích závislostí z public repozitářů (pomocí nástroje dep).
  3. Stažení externích závislostí z non-public repozitářů (pomocí Git klonování).
  4. Spuštění testů.
  5. Kompilace binárky.

O správu verzí public závislostí se stará nástroj dep. O správu verzí non-public závislostí se stará plugin (task proprietaryVendors). V případě, že non-public závislosti mají tranzitivní závislosti na public závislostech, je to kombinace obojího. Technický popis je trochu komplexnější, takže zájemce odkazuji do dokumentace: How to handle proprietary vendors; nicméně použití by mělo být (v rámci možností) jednoduché.

Sekvenční diagram build life-cyclu godep pluginu


Problem solved?

Takže... problém vyřešen? Nový Gradle plugin splňuje všechny v úvodu uvedené požadavky a za ty tři měsíce má za sebou stovky úspěšných buildů (build se v GitLab Pipelině pouští pro každý git push). Tudíž zatím ano, funguje to k naší spokojenosti.

Otázkou ale je, jak dlouho to vydrží - verzování závislostí je žhavé téma jak Golang komunity, tak core vývojářů Go. Všichni s očekáváním vzhlížejí ke zmíněnému vgo a tak za půl roku možná bude všechno jinak a nějaký Gradle plugin už nebude potřeba.

Ale nijak se tím netrápím - pár takových Gradle pluginů už jsem napsal: v daný moment vyřešily mou situaci, avšak časem přestaly být potřeba. A to se mi líbí - nástroje, které jsou dostatečně flexibilní a zároveň mají hezký a moderní design. A dobře se v nich dělá. A to jak Golang, tak Gradle splňují.

Externí odkazy


Mind Map


13. listopadu 2017

vimdiff, nástroj drsňáků


Musím se vám k něčemu přiznat... Už patnáct let je Vim můj nejoblíbenější textový editor. A občas, čas od času, i hlavní nástroj na programování.

Umím si poeditovat vimrc, který po léta udržuju a vylepšuju. Dokonce jsem se i naučil trochu Vim script/VimL a napsal dva zanedbatelné a nedotažené pluginy (pro Gradle a WSDL).

Ale vždycky jsem se jako čert kříži vyhýbal jedné věci - používání vimdiff. Nicméně na každého jednou dojde. Z určitých (pro článek nepodstatných) důvodů jsem si nemohl pro nové vývojové prostředí nastavit P4Merge a tak jsem vstoupil do zapovězené komnaty.

Disclaimer: Tenhle článek píšu jako shrnutí toho, jak jsem práci s vimdiff pochopil. Pokud máte víc zkušeností, budu rád, když se podělíte v komentářích.

2-way merge

Nejjednodušší způsob, jak používat vimdiff - pokud pomineme, že umí dělat i "plain old" diff - je 2-way merge: máme vedle sebe dvě okna se zvýrazněnými rozdíly a chceme mezi nimi tyto změny propagovat.

vimdiff, 2-way merge

Stav na předešlém obrázku, který je výchozí pro merge, se dá dosáhnout několika způsoby:
  • Příkazem: vimdiff myFile.txt theirFile.txt
  • Příkazem: vim -d myFile.txt theirFile.txt
  • Kombinací příkazů:
    • vim myFile.txt
    • :diffsplit theirFile.txt
  • Kombinací příkazů
    • vim -O myFile.txt theirFile.txt (vsplit obou souborů)
    • :diffthis (zapne diff na aktuálním bufferu)
    • Ctrl-W Ctrl-W (skok do druhého bufferu)
    • :diffthis(zapne diff v druhém bufferu)

Základní příkazy

Tak, diff máme zobrazený, co s ním? První věc - je potřeba se v diffu umět pohybovat. Kromě toho, že můžete použít jakýkoli skok, který znáte z běžného Vimu, jsou tu dva příkazy, které umožňují skákat po jednotlivých rozdílech:
  • ]c skočí na následující diff
  • [c skočí na předcházející diff

Za druhé - chceme propagovat změny z/do aktuálního bufferu: skočíme na diff, který chceme upravit a:
  • do, nebo :diffget natáhne změny z "druhého" bufferu do toho aktuálního.
  • dp, nebo :diffput propaguje změny z aktuálního bufferu do "toho druhého".

Za třetí - změny uložíme. Kromě příkazů na standardní ukládání (:w, ZZ atd.) se může hodit:
  • :only zavře všechny ostatní buffery kromě toho aktuálního
  • :qall zavře všechny otevřené buffery
  • :only | wq zavře ostatní buffery + uloží stávající + ukončí Vim. Cool!

Eventuálně začtvrté - pokud věci nejdou hladce, může se šiknout:
  • :diffupdate znovu proskenuje a překreslí rozdíly (u komplikovanějších mergů nemusí Vim správně pochopit danou změnu)
  • :set wrap nastavení zalamování řádků (hodí se při velmi dlouhých řádcích, typicky některá XML)
  • zo/zc otevře/zavře skryté (folded) řádky

3-way merge

Nemusím vám říkat, že 2-way merge je pro školáky - profíci makaj v Gitu, či v Mercurialu a tam je dvoucestný merge nedostačující. Ke slovu přichází 3-way merge. Co to je?

Schéma 3-way merge

3-way merge není nic složitého. V podstatě jde o to, že máme dvě verze, které mají společného předka. V mergovacím nástroji pak vidíme všechny tři verze vedle sebe a většinou máme k dispozici ještě čtvrté okno s aktuálním výsledkem merge.

3-way merge v aplikaci


Nastavení Gitu

Nastavení spolupráce Gitu a vimdiff je jednoduché - stačí spustit z příkazové řádky následující sadu příkazů:
$ git config --global merge.tool vimdiff
$ git config --global merge.conflictstyle diff3
$ git config --global mergetool.prompt false
$ git config --global mergetool.keepBackup false

Pokud se podíváte do ~/.gitconfig, měli byste tam vidět:


Nastavení Mercurialu

Nastavení Mercurialu je podobně jednoduché. Otevřeme soubor ~/.hgrc příkazem
$ hg config --edit
a vložíme následující řádky


Sekce [extensions] a [extdiff] nejsou pro merge nutné, ale hodí se, pokud chceme vimdiff používat jako dodatečný externí diff nástroj. Sekundární diff spustíme příkazem hg vimdiff.

Základní příkazy

Základní příkazy jsou stejné jako v sekci 2-way merge, s výjimkou příkazů dp/do (:diffput/:diffget) - pokud bychom je nyní použili, vimdiff nám zahlásí chybu:
More than two buffers in diff mode, don't know which one to use
To je v pořádku: u 3-way merge se ve vimdiff otevřou 4 buffery, všechny v diff módu. Takže do té doby, než se vimdiff naučí komunikovat telepaticky, je potřeba mu říct, ze kterého bufferu chceme danou změnu natáhnout.

vimdiff, 3-way merge v Gitu

Klasické merge flow vypadá následovně:
  1. Začínáme v dolním "výsledkovém" bufferu.
  2. ]c (skočit na následující diff, který chceme mergovat)
  3. :diffget <identifikace-bufferu> získáme změnu z daného bufferu (viz dále)
  4. Opakujeme 2-3.
  5. :only | wq uložíme merge.

Obecně, identifikátor bufferu získáme příkazem :ls. To je ale dost nepraktické a nepřehledné. Další možnost je identifikovat buffer částečným názvem souboru. Tady přichází na pomoc jak Git, tak Mercurial, který přidávají k názvům souborů příhodný suffix.

Merge v Gitu

Git přidává do názvů mergovaných souborů následující suffixy, v pořadí zleva doprava: LOCAL (vlevo), BASE (uprostřed), REMOTE (vpravo). Pro natažení změny z (levého) bufferu LOCAL můžeme použít příkaz :diffg LO.

Výpis bufferů pro Git:
:ls
  1 #a   "./myFile_LOCAL_7424.txt"      line 1
  2  a   "./myFile_BASE_7424.txt"       line 0
  3  a   "./myFile_REMOTE_7424.txt"     line 0
  4 %a   "myFile.txt"                   line 12

Merge v Mercurialu


vimdiff, 3-way merge v Mercurialu

Mercurial přidává do názvů mergovaných souborů následující suffixy, v pořadí zleva doprava: orig (vlevo), base (uprostřed), other (vpravo). Pro natažení změny z (levého) bufferu orig můžeme použít příkaz :diffg orig.

Výpis bufferů pro Mercurial:
:ls
  1 %a   "myFile.txt"                   line 2
  2  a-  "myFile.txt.orig"              line 0
  3  a-  "/tmp/myFile.txt~base.iZwwuA"  line 0
  4  a-  "/tmp/myFile.txt~other.km9Itr" line 0

Co mi (zatím) schází?

Musím říct, že potom, co jsem si vimdiff osahal, pochopil jeho logiku a naučil se jeho příkazy, jsem si ho docela oblíbil.

Jediná výtka zatím jde za jeho neschopností skákat přímo po konfliktech - Git i Mercurial dělají výborně automatické merge a ty jsou samozřejmě vidět ve vimdiffu taky, jako pouhá změna bez konfliktu. Mít nějaký příkaz, který rozlišuje pouhý diff a konflikt, by bylo fajn.

Související články