Close

Git i zależności projektu

Zdjęcie portretowe Nicoli Paolucciego
Nicola Paolucci

Developer Advocate

Zastanówmy się nad następującymi pytaniami. Jak obsługujecie zależności projektu za pomocą git? Nasz projekt obejmuje wiele współzależnych repozytoriów. Obecnie zarządzamy nimi za pomocą svn:externals. A jak najlepiej postępować z nimi przy użyciu git? Jak podzielić bardzo duże repozytorium na mniejsze komponenty za pomocą Git? Oto kilka przykładów najczęściej zadawanych pytań, z którymi spotkaliśmy się podczas europejskiej części naszej ostatniej trasy Getting Git Right.

Ten temat wydaje się sporą bolączką dla wielu zespołów zajmujących się tworzeniem oprogramowania, które wdrażają git, dlatego w tym artykule postaram się rzucić nieco światła na to zagadnienie.

Oczywiście zależności w projekcie oraz infrastruktura kompilacji to dwa przenikające się wzajemnie obszary, a nawet wewnętrznie w Atlassian dyskusja zeszła na temat „przyszłości kompilacji”.

Tworzenie odrębnych repozytoriów, w odróżnieniu od pracy na jednym repozytorium, może utrudnić wiele rzeczy. Jest to jednak stosunkowo naturalny, a czasami wręcz obowiązkowy krok w ewolucji projektu oprogramowania z co najmniej dwóch powodów: wydłużających się czasów kompilacji oraz współdzielonych zależności między projektami.

Zastanówmy się nad następującymi pytaniami. Jak obsługujecie zależności projektu za pomocą git? Nasz projekt obejmuje wiele współzależnych repozytoriów. Obecnie zarządzamy nimi za pomocą svn:externals. A jak najlepiej postępować z nimi przy użyciu git? Jak podzielić bardzo duże repozytorium na mniejsze komponenty za pomocą Git? Oto kilka przykładów najczęściej zadawanych pytań, z którymi spotkaliśmy się podczas europejskiej części naszej ostatniej trasy Getting Git Right.

Ten temat wydaje się sporą bolączką dla wielu zespołów zajmujących się tworzeniem oprogramowania, które wdrażają git, dlatego w tym artykule postaram się rzucić nieco światła na to zagadnienie.

Oczywiście zależności w projekcie oraz infrastruktura kompilacji to dwa przenikające się wzajemnie obszary, a nawet wewnętrznie w Atlassian dyskusja zeszła na temat „przyszłości kompilacji”.

Tworzenie odrębnych repozytoriów, w odróżnieniu od pracy na jednym repozytorium, może utrudnić wiele rzeczy. Jest to jednak stosunkowo naturalny, a czasami wręcz obowiązkowy krok w ewolucji projektu oprogramowania z co najmniej dwóch powodów: wydłużających się czasów kompilacji oraz współdzielonych zależności między projektami.


Ogólny zarys: wytyczne i nieoptymalne rozwiązania


Wróćmy do pytania: jak śledzić zależności w projekcie i zarządzać nimi przy użyciu git?

W miarę możliwości wcale tego nie robić!

Żarty żartami, ale poznajmy najpierw odpowiedź ogólną, zanim zagłębimy się w szczegóły. Pamiętaj, że nie istnieje żadne panaceum — ani w git ani w żadnym innym rozwiązaniu — które w bezbolesny sposób pozwoli rozwiązać wszystkie problemy związane z zależnościami w projekcie.

W miarę jak projekt rozrasta się powyżej pewnego rozmiaru, rozsądnym rozwiązaniem jest podzielenie go na składniki logiczne, ale nie warto z tym czekać do momentu, gdy zostaniemy z jednym repozytorium zawierającym ponad 100 milionów wierszy kodu. Poniżej przedstawiam jedynie wskazówki, na podstawie których możesz opracować własne podejście.

Logo Git
materiały pokrewne

Instalacja środowiska Git

Logo Bitbucket
POZNAJ ROZWIĄZANIE

Poznaj środowisko Git z rozwiązaniem Bitbucket Cloud

Pierwszy wybór: użyć zamiast Git odpowiedniego narzędzia do obsługi kompilacji/zależności


Narzędzie do zarządzania zależnościami jest obecnie moim zalecanym sposobem radzenia sobie z narastającymi problemami i czasem kompilowania dużych projektów.

Warto rozdzielać moduły w poszczególnych repozytoriach i zarządzać ich wzajemnymi zależnościami przy użyciu narzędzia specjalnie przystosowanego do tego zadania. Istnieje takie narzędzie dla (niemal) każdego zestawu technologii. Oto przykłady:

  • Maven (lub Gradle), jeśli używasz języka Java
  • Npm w przypadku aplikacji Node.js
  • Bower, Component.io itp., jeśli używasz języka JavaScript (zaktualizowane!)
  • Pip i requirements.txt, jeśli używasz języka Python
  • RubyGems, Bundler, jeśli używasz Ruby
  • NuGet w przypadku .NET
  • Ivy (lub niestandardowe działanie CMake) w przypadku C++ (zaktualizowane!)
  • CocoaPods w przypadku aplikacji Cocoa dla systemu iOS
  • Composer lub Phing w przypadku PHP (dodane!)
  • W Go infrastruktura kompilacji/zależności jest w pewnym sensie wbudowana w język (choć pracowano już nad bardziej kompleksowym rozwiązaniem, patrz godep). Na potrzeby naszego serwera Git Bitbucket Server wykorzystujemy rozwiązania Maven i Bower. W momencie kompilowania wybrane narzędzie ściągnie właściwe wersje zależności, umożliwiając skompilowanie głównego projektu. Niektóre z tych narzędzi mają ograniczenia i przyjmują mało optymalne założenia, ale są sprawdzone i przydatne.

Bolączki związane z dzieleniem projektu

W uproszczeniu, na początku projektu wszystko jest spakowane w jednej kompilacji. Jednak w miarę rozrastania się projektu kompilacja może się stawać powolna. Wówczas konieczne jest zastosowanie „buforowania” i na tym etapie wkracza zarządzanie zależnościami. Przy okazji oznacza to, że moduły podrzędne (patrz niżej) doskonale sprawdzają się na przykład w językach dynamicznych. Zasadniczo uważam, że na pewnym etapie dla większości ludzi czasy kompilacji są zmartwieniem, dlatego należy korzystać z narzędzia do zarządzania zależnościami.

Dzielenie komponentów na odrębne repozytoria wiąże się z pewnymi trudnościami. Wymieńmy je w przypadkowej kolejności:

  • Dokonanie zmiany w komponencie wymaga wydania.
  • Wymaga czasu i może się nie powieść z wielu błahych powodów.
  • Wydaje się głupie w przypadku drobnych zmian.
  • Wymaga ręcznego konfigurowania nowych kompilacji dla każdego komponentu.
  • Utrudnia wykrywalność repozytoriów.
  • Refaktoryzacja, gdy nie cały kod źródłowy jest dostępny w jednym repozytorium.
  • W niektórych konfiguracjach (takich jak nasza) zaktualizowanie interfejsów API wymaga wydania kamienia milowego produktu, następnie wtyczki, a potem znów produktu. Prawdopodobnie pominęliśmy kilka rzeczy, ale masz chociaż ogólne pojęcie. Jeszcze daleko nam do idealnego rozwiązania.

Drugi wybór: użycie polecenia git submodule


Jeśli nie możesz lub nie chcesz używać narzędzia do obsługi zależności, git ma funkcję umożliwiającą obsługiwanie modułów podrzędnych (submodule). Moduły podrzędne bywają wygodne, zwłaszcza w przypadku języków dynamicznych. Niekoniecznie jednak pozwolą wyeliminować problem powolnych kompilacji. Pisałem już o pewnych wytycznych i poradach na ich temat, a także przedstawiłem przegląd alternatyw. W Internecie można też znaleźć argumenty świadczące przeciwko nim.

Zgodność 1:1 między svn:external i git

ALE! Jeśli potrzebujesz zgodności 1:1 między svn:externals a git, użyj modułów podrzędnych, upewniając się, żeśledzą one jedynie gałęzie wydań, a nie losowe commity.

Trzeci wybór: używanie innych narzędzi do obsługi kompilacji i zależności między zestawami technologii


Nie zawsze mamy do czynienia z projektem całkowicie jednorodnym, który można kompilować i asemblować przy użyciu jednego narzędzia. Przykładowo niektóre projekty dla urządzeń mobilnych wymagają obsługi zależności między obiektami Java i C++ albo stosowania autorskich narzędzi do generowania zasobów. W przypadku tych bardziej złożonych sytuacji można rozbudować git o dodatkową warstwę na wyższym poziomie. Doskonałym przykładem takiej sytuacji jest repozytorium systemu Android.

Inne narzędzia do obsługi kompilacji, którym warto się przyjrzeć:

Wnioski i dodatkowe materiały


Dodatkowe materiały na temat infrastruktury kompilacji (i Maven) zostały w przemyślany sposób zebrane przez Charlesa O'Farrella. Bardzo inspirujące opracowania:

Na zakończenie posłużę się fantastycznym cytatem z ostatniego spośród wspomnianych powyżej artykułów. Choć odnosi się on do narzędzia Maven, równie dobrze można zastosować go do innych narzędzi do obsługi kompilacji i zależności:

„Jedynym zadaniem pamięci podręcznej jest przyspieszanie działania. Możesz ją całkowicie wyeliminować, a otaczający ją system będzie działał tak samo, tylko wolniej. Pamięć podręczna nie ma też żadnych skutków ubocznych. Bez względu na to, co robiono z nią w przeszłości, konkretne zapytanie skierowane do pamięci podręcznej ponowione w przyszłości zwróci taką samą wartość.

Środowisko Maven zdecydowanie różni się od tego, co opisuję. Repozytoria Maven są używane jak pamięć podręczna, ale nie mają właściwości pamięci podręcznych. Gdy tworzysz zapytanie do repozytorium Maven, bardzo istotne są czynności, które wykonano w przeszłości. Zapytanie zwraca najnowszy wynik przekazany do repozytorium. Jeśli wykonasz zapytanie o element, zanim umieścisz go w repozytorium, może nawet wystąpić błąd”.

Nicola Paolucci

Nicola is an all-round hacker who loves exploring and teaching bleeding edge technologies. He writes and talks about Git, development workflows, code collaboration and more recently about Docker. Prior to his current role as Developer Instigator at Atlassian he led software teams, built crowd sourcing applications for geo-spacial data, worked on huge e-commerce deployments. Little known facts about Nicola: he gesticulates a lot while speaking (being Italian), lives in Amsterdam and rides a Ducati.


Udostępnij ten artykuł

Zalecane lektury

Dodaj te zasoby do zakładek, aby dowiedzieć się więcej na temat rodzajów zespołów DevOps lub otrzymywać aktualności na temat metodyki DevOps w Atlassian.

Ludzie współpracujący przy ścianie pełnej narzędzi

Blog Bitbucket

Ilustracja DevOps

Ścieżka szkoleniowa DevOps

Demonstracje funkcji z ekspertami Atlassian

Zobacz, jak Bitbucket Cloud współpracuje z Atlassian Open DevOps

Zapisz się do newslettera DevOps

Thank you for signing up