JavaRush /Курси /Go SELF /go build — пакети, ./..., -o

go build — пакети, ./..., -o

Go SELF
Рівень 54 , Лекція 0
Відкрита

1. Навіщо розбиратися з go build, якщо в IDE є кнопка Run

Коли ви тільки починаєте, IDE схожа на рятівне коло: натиснули зелену кнопку — і програма «якось» працює. Але щойно проєкт стає більшим за «Hello, World», вам уже потрібно свідомо керувати збіркою. Іноді треба зібрати бінарник і надіслати його другові. Іноді — перевірити, чи збираються всі пакети. Іноді — зрозуміти, чому в колеги «не збирається», а у вас — так. У цей момент go build перестає бути заклинанням і стає інструментом, яким ви справді керуєте.

У Go збірка — це не про «скомпілювати один файл», а про «зібрати пакети й залежності, а якщо серед них є main — згенерувати виконуваний файл». І це дуже логічно: у реальному проєкті код майже завжди розкладено по папках, а не живе в одному величезному main.go, який страшно відкривати без каски.

2. Пакети: як Go мислить збірку

Важливо звикнути до однієї думки: пакет у Go — це директорія з .go-файлами, які оголошують один і той самий package.

Тобто Go дивиться на проєкт приблизно так:

  • у кожній папці — один пакет;
  • пакет може імпортувати інші пакети;
  • збірка стартує з «цілі» (target), яку ви вказали: поточного пакета, конкретного пакета або набору пакетів за шаблоном.

Уявімо спрощене дерево нашого навчального проєкту todo (CLI-менеджер завдань). Воно може виглядати так:

todo/
  go.mod
  cmd/
    todo/
      main.go
  internal/
    app/
      app.go
    task/
      task.go

І ключова ідея: cmd/todo — це main-пакет (програма), а internal/app і internal/task — звичайні бібліотечні пакети (компоненти).

Щоб це було зовсім «в лоб», ось два мініфайли.

cmd/todo/main.go — це програма:

package main

import "fmt"

func main() {
	fmt.Println("todo запущено") // Повідомлення про запуск todo
}

internal/task/task.go — це бібліотечний пакет (він сам не запускається):

package task

type Task struct {
	Title string
	Done  bool
}

Збірка main-пакета дасть вам виконуваний файл. Збірка бібліотечного пакета — лише перевірить компіляцію і збереже результат у кеші (про кеш поговоримо нижче). Окремого «exe для бібліотеки» не буде — і це нормально.

3. Цілі збірки: поточний пакет, main і бінарник

Збірка без аргументів і ціль .

Коли ви запускаєте go build без аргументів, ви фактично кажете: «збери пакет у поточній директорії». Те саме можна записати явно через . — це просто прозоріший варіант.

Якщо ви перебуваєте в директорії cmd/todo, то команди еквівалентні:

go build
go build .

Обидві означають: «збери пакет, який лежить ось тут».

Тепер важлива деталь, яка часто дивує новачків: якщо поточний пакет — не main, то після go build ви можете не побачити жодного нового файла в папці. І це не збій, а нормальна поведінка: Go зібрав пакет, перевірив, що він компілюється, і використав кеш.

Це зручно: ви можете швидко перевірити, що пакет internal/task компілюється, не створюючи зайвих бінарників:

cd internal/task
go build .

У вас не з’явиться task.exe (і це нормально). Але якщо в коді є помилка — компілятор чесно покаже її.

Чому бінарник з’являється лише для main

Перш ніж говорити про ./..., закріпімо «магічний маркер» програми: package main + func main().

Якщо пакет називається main, але функції main немає, програма не вважається коректною точкою входу. Якщо функція main є, але пакет не main, — теж не вважається. Це як ключ і замок: окремо вони марні, разом — запускають застосунок.

Мініприклад: «майже програма, але ще ні».

package main

import "fmt"

// Немає func main() — немає чого запускати.
func Start() {
	fmt.Println("старт") // цей код сам по собі не запуститься
}

Якщо зібрати таке як програму, компілятор не буде в захваті: він очікує точку входу.

А ось мінімальна «правильна програма»:

package main

import "fmt"

func main() {
	fmt.Println("готово") // повідомлення про завершення
}

Висновок для практики дуже простий: якщо ви хочете отримати бінарник, збирайте main-пакет. Усе інше — це збірка компонентів і перевірка, що все компілюється.

4. ./...: усі пакети рекурсивно, а не «одна велика програма»

Запис ./... — один із найкорисніших у Go-інструментах. Він означає: усі пакети в поточній директорії та в усіх вкладених директоріях.

Тобто, якщо ви стоїте в корені модуля todo/, то:

go build ./...

означає: «пройдися деревом проєкту і спробуй зібрати кожен пакет».

Це часто використовують як швидку перевірку: «проєкт узагалі компілюється?» — особливо перед комітом або перед запуском тестів.

І ось тут найчастіша пастка очікувань: ./... не означає «збери один спільний бінарник з усього проєкту». У Go немає ідеї автоматично склеїти всі пакети в одну програму. Програма — це конкретний main-пакет (або кілька main-пакетів, якщо у вас кілька команд).

Якщо у вашому проєкті один main у cmd/todo, тоді go build ./... справді збиратиме й його. Але результатом цієї команди майже завжди є не «ось вам готовий релізний бінарник», а просто факт: «усі пакети успішно компілюються».

Щоб це вклалося в голові, корисно порівняти команди в таблиці:

Команда Що є ціллю Що ви хочете отримати Типовий результат
go build
поточний пакет «збери те, де я стою» бінарник (якщо це main), або просто перевірка й кеш
go build .
поточний пакет (явно) те саме, але читабельніше те саме
go build ./...
усі пакети рекурсивно «нехай усе компілюється» зазвичай без «нового файла», але з помилками, якщо вони є
go build ./cmd/todo
конкретний пакет «збери команду todo» бінарник todo у поточній директорії

Якщо коротко й чесно: ./... — це «перевірка здоров’я дерева пакетів», а не «виробництво одного артефакту».

5. -o: назва і місце для бінарника

Коли ви збираєте main-пакет, Go за замовчуванням кладе бінарник у поточну директорію й називає його за іменем останнього елемента шляху пакета. Наприклад, якщо ви запускаєте збірку з кореня модуля:

go build ./cmd/todo

то бінарник опиниться у поточній директорії (тобто в корені проєкту) і називатиметься todo (або todo.exe на Windows).

Це може бути нормальним варіантом, але часто хочеться акуратніше: наприклад, складати бінарники в ./bin/, щоб не захаращувати корінь проєкту. Ось тут і з’являється -o.

Приклад «збери й поклади в bin»:

go build -o bin/todo ./cmd/todo

Якщо ви працюєте у Windows, розширення зазвичай вказують явно:

go build -o bin/todo.exe ./cmd/todo

Сенс -o дуже приземлений: ви керуєте вихідним файлом. Не «десь там IDE сховала», а прямо руками: ось шлях, ось ім’я.

Ще один корисний сценарій: ви хочете збирати бінарник з назвою, зрозумілою людині, навіть якщо пакет називається інакше. Наприклад, пакет ./cmd/todo може називатися todo, а ви хочете бінарник todo-cli:

go build -o bin/todo-cli ./cmd/todo

Тоді у вас два імені: назва пакета й назва бінарника. Тому в житті зазвичай тримають їх близькими. Але коли потрібно — -o вас рятує.

Окремий нюанс — без заглиблення, але щоб не лякатися: якщо ви спробуєте використати -o для збірки одразу багатьох пакетів (наприклад, ./...), Go може вилаятися, бо «не можна вивести багато результатів в один файл». Це логічно: один файл — один бінарник.

6. Що робить go build: залежності, лінкування та build cache

Корисно хоча б на рівні схеми розуміти, що відбувається під час збірки. Go робить це доволі швидко, але всередині все одно є етапи.

Спрощено процес виглядає так:

flowchart TD
    A[Ви запускаєте go build] --> B[Go знаходить цільовий пакет]
    B --> C[Go читає imports і будує граф залежностей]
    C --> D["Компілює пакети знизу вгору"]
    D --> E{Є main-пакет?}
    E -- ні --> F[Результати в build cache, бінарника немає]
    E -- так --> G[Лінкування: збирання виконуваного файлу]
    G --> H["З’являється бінарник (якщо не задано -o — у поточній папці)"]

Слово «лінкування» звучить грізно (ніби зараз прийде лінкер і спитає, чи готові ви до дорослого життя). На практиці це просто фінальний крок: «склеїти» скомпільовані шматки в один виконуваний файл.

І ось тут є дуже важлива річ, яка робить Go приємним: build cache (кеш збірки). Він пояснює феномен:

  • перший go build — трохи довше,
  • другий go build — помітно швидше,
  • третій — узагалі «миттєво, я навіть не встиг кліпнути».

Чому? Бо якщо Go бачить, що пакет і його залежності не змінилися, він повторно використовує результати компіляції з кешу. Ви ніби отримуєте мінітурборежим, нічого не роблячи.

Це не магія, а інженерна оптимізація. Але, як і будь-яка магія, вона викликає питання, якщо ви не знаєте, що вона існує: «Чому в мене не з’явився файл?» або «Чому воно збирається так швидко, може, воно взагалі нічого не робить?». Робить. Просто розумно.

7. Мінісценарій для todo: які команди запускати

Щоб знання не залишилися теорією, прив’яжімо їх до нашого навчального застосунку todo.

Уявіть три типові стани розробника:

Перший стан — «я змінював main.go, хочу запустити програму». Логіка проста: вам потрібен бінарник, отже ви збираєте main-пакет. Якщо ви перебуваєте в cmd/todo, то вистачить:

go build

Другий стан — «я змінював код у internal/..., хочу переконатися, що проєкт повністю компілюється». Тут часто корисніше:

go build ./...

Це дасть вам упевненість, що ви не зламали якийсь пакет, який не бере участі у вашому локальному сценарії запуску, але потрібний для тестів або іншої команди.

Третій стан — «я хочу акуратний бінарник у bin/». Тоді:

go build -o bin/todo ./cmd/todo

І все: корінь проєкту чистий, артефакти в одному місці, далі хоч скрипт пишіть, хоч запускайте вручну.

8. Типові помилки

Помилка №1: очікувати, що go build ./... створить «один спільний бінарник».
Це цілком природне очікування, якщо ви прийшли зі світу, де є «зібрати проєкт цілком». У Go ./... означає «усі пакети рекурсивно», тобто перевірку, чи компілюється дерево пакетів. Бінарник створюється лише під час збірки конкретного main-пакета, а не «всього підряд».

Помилка №2: плутати пакет із файлом і мислити категорією «зіберу main.go».
Так, технічно go build уміє приймати список файлів, але це радше аварійний варіант для дрібних експериментів. Нормальний Go-проєкт збирають пакетами: go build ./cmd/todo, go build ., go build ./.... Щойно ви починаєте мислити директоріями та шляхами імпорту, багато що стає простішим і передбачуванішим.

Помилка №3: запускати go build у «не тій» папці й дивуватися відсутності бінарника.
Якщо ви випадково опинилися в internal/task і виконали go build, бінарника й не буде — це бібліотечний пакет. У такій ситуації проблема не в збірці, а в цілі: щоб отримати бінарник, збирайте main-пакет (наприклад, ./cmd/todo) або перейдіть у його директорію.

Помилка №4: використовувати -o там, де ви насправді збираєте багато пакетів.
Прапорець -o — це про керування вихідним файлом. Якщо ви збираєте один main-пакет — чудово. Але якщо ви намагаєтеся поєднати -o і ./..., ви фактично просите Go «виплюнути багато результатів в один файл», а це вже не має сенсу. У таких випадках спочатку визначтеся, який конкретно main-пакет ви хочете зібрати.

Помилка №5: думати, що «якщо вдруге швидко, значить нічого не зібралося».
Це класичний конфлікт людини з кешем. Go справді може перебудувати проєкт за частки секунди, бо повторно використовує результати компіляції пакетів. Якщо код не змінювався, швидка збірка — ознака здорової роботи, а не халтури компілятора.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ