1. Основные понятия SwiftPM
Когда вы только начинаете, кажется, что разработка — это просто: написал код, нажал «Run», программа побежала. И это правда… ровно до момента, пока проект не становится чуть больше одного файла, а вы не хотите: хранить код аккуратно, запускать тесты, собирать проект одинаково на вашем ноутбуке и на компьютере друга, а ещё не проклинать настройки IDE каждый раз после обновления.
Тут и появляется SwiftPM (Swift Package Manager). Это не «ещё одна страшная штука», а просто стандартный способ Swift-проекту договориться с компьютером, как он должен собираться и запускаться. Причём не «как получится», а по описанию, которое лежит рядом с кодом.
Небольшой факт для уверенности: Swift обычно поставляется вместе со SwiftPM как с частью набора инструментов (toolchain). То есть это не редкий плагин из тёмного угла интернета, а базовый инструмент экосистемы.
SwiftPM: сборка и зависимости
Если кратко, SwiftPM — это штука, которая умеет две большие вещи: собирать ваш проект и подключать зависимости (библиотеки). И делает это не «по настроению», а на основе манифеста.
Важно понять одну мысль: SwiftPM — это не IDE. Он не рисует кнопки, не раскрашивает код и не спорит с вами за отступы. Он работает как строгий, но справедливый повар: ему дают рецепт — он готовит. Не дали рецепт — он стоит и молча смотрит, как вы страдаете.
Вот упрощённая схема, что вообще происходит:
flowchart TD
A["Вы (разработчик)<br/>пишете код"] --> B["SwiftPM читает Package.swift<br/>и понимает, что собирать"]
B --> C["Компиляция исходников<br/>в нужном порядке"]
C --> D["Результат: исполняемый файл<br/>или библиотека"]
И ключевой элемент этой схемы — Package.swift.
Пакет: проект, который «умеет объяснить себя»
Слово package сначала сбивает с толку: «пакет… это как посылка?». На практике пакет — это просто директория проекта, в корне которой лежит файл-описание сборки. Этот файл и есть Package.swift.
Можно думать так: папка без Package.swift — это просто папка с .swift файлами. Папка с Package.swift — это проект, который умеет сказать SwiftPM: «вот что у меня внутри, вот как меня собирать».
Swift.org в самом простом гайде для CLI прямо называет Package.swift манифестом и показывает, что при создании проекта SwiftPM генерирует структуру, где рядом с Package.swift появляется Sources/ (а позже обычно и Tests/). Мы подробно разберём структуру папок в следующей лекции дня, а сегодня нам важно понять роль манифеста.
Два мира: сборка и приложение
Когда вы запускаете команды SwiftPM (мы детально сделаем это в следующих лекциях дня), первым делом SwiftPM ищет Package.swift и читает его. Важно понимать: ваш проект начинается не с main.swift, а с того, что сборщик понимает структуру.
Удобно представить это как два разных мира:
| Мир | Вопрос | Главный файл |
|---|---|---|
| Мир сборки | «Что собирать и как?» | |
| Мир приложения | «Что делать при запуске?» | |
Swift.org формулирует эту идею очень прямо: Package.swift хранит метаданные и зависимости проекта, а исходники приложения живут в Sources/... (и entry point — там же). Сегодня мы не углубляемся в точку входа и структуру папок, но важно увидеть: манифест — это «про сборку», исходники — это «про поведение программы».
3. Файл Package.swift: что внутри и как его читать
Package.swift — про сборку, а не про логику приложения
С виду Package.swift выглядит как обычный Swift-код. Там действительно пишется Swift, там можно заводить let, можно собирать строки, можно даже делать маленькие вычисления. И вот здесь новичок попадает в ловушку: «О, значит я могу написать в Package.swift логику приложения!»
Нет. Технически — вы можете написать что угодно, но смысл файла другой: Package.swift — это декларация того, как собирать проект. Это не место для бизнес-логики, не место для print("Привет"), не место для парсинга команд и хранения данных. Он должен быть коротким, скучным и предсказуемым. Как инструкция к микроволновке: интересно — ноль, зато работает каждый раз.
Внутри Package.swift мы обычно описываем:
- имя пакета;
- какие цели (targets) в нём есть (например, исполняемая программа);
- (иногда) зависимости от внешних пакетов.
Чтобы описывать всё это, в манифесте импортируется специальный модуль PackageDescription. Он даёт типы вроде Package, через которые мы «собираем описание проекта».
// swift-tools-version: 6.2: версия правил, по которым читают манифест
Когда вы видите вверху манифеста строку вида:
// swift-tools-version: 6.2
она кажется чем-то декоративным, как лавровый лист в супе: вроде лежит, но зачем — непонятно. На самом деле это очень практичная штука: она говорит SwiftPM, какая минимальная версия инструментов нужна, чтобы правильно понять ваш Package.swift.
Можно воспринимать это как «версия языка манифеста». Манифест со временем менялся: добавлялись новые возможности, менялись правила, появлялись новые API в PackageDescription. Поэтому SwiftPM должен понимать: «по каким правилам читать этот файл».
В рамках курса мы фиксируем Swift 6.2, поэтому в примерах будет // swift-tools-version: 6.2. Старайтесь не менять это без причины: это часть контракта между проектом и инструментами сборки.
import PackageDescription: зачем он нужен
До этого вы уже видели import Foundation и привыкли: «импорт — чтобы получить доступ к API». В Package.swift тот же принцип, но контекст другой: мы импортируем модуль PackageDescription, потому что именно он предоставляет типы, которыми описывается пакет.
То есть это не «подключили библиотеку для строк/дат», а «подключили словарь терминов для сборки».
Минимальный, абсолютно нормальный каркас манифеста выглядит так:
// swift-tools-version: 6.2
import PackageDescription
let package = Package(
name: "LibraryCLI"
)
Здесь важно не «что он делает», а «что он означает»: мы создали объект Package, который описывает пакет с именем LibraryCLI.
4. Цели и нейминг: как не запутаться в именах
Фиксируем имя LibraryCLI: меньше ошибок, больше предсказуемости
На этом дне курса мы сознательно «упрощаем вселенную» и договариваемся: одно имя — везде. Это звучит скучно, но это прямо спасает новичков от вечного вопроса: «почему swift run не запускает то, что я думаю?».
Мы фиксируем:
| Сущность | Что это | Имя в курсе |
|---|---|---|
| package name | имя пакета в |
|
| executable target name | имя исполняемой цели (программы) | |
| бинарник/CLI-команда | имя запускаемой команды | |
Почему это полезно? Потому что без этой дисциплины легко получить ситуацию: пакет называется Library, цель называется CLI, а запускать надо swift run BooksApp, и вы сидите как человек, который открыл три одинаковые двери и не помнит, где его квартира.
Исполняемая цель: .executableTarget(...)
Когда пакет должен собираться в программу, SwiftPM должен понимать, что это именно исполняемая цель, а не библиотека. Современный SwiftPM позволяет объявлять это явно через .executableTarget(...) (и это появилось как отдельная явная возможность, чтобы цель не приходилось «угадывать по имени файла main.swift» и чтобы лучше работало с @main).
Минимальный манифест с исполняемой целью выглядит так:
// swift-tools-version: 6.2
import PackageDescription
let package = Package(
name: "LibraryCLI",
targets: [
.executableTarget(name: "LibraryCLI")
]
)
Обратите внимание на стиль: мы не «настраиваем миллион параметров», а задаём только самое важное. Чем меньше магии — тем меньше сюрпризов.
«Можно я сделаю переменную?» — можно, но аккуратно
Иногда хочется избежать дублирования строк. Например, имя LibraryCLI встречается и в name, и в .executableTarget. И тут появляется нормальная идея: «а сделаю let name = ...».
Это окей, потому что это не усложняет манифест и не превращает его в мини-программу. Главное правило — чтобы по Package.swift можно было пробежать глазами и понять проект.
// swift-tools-version: 6.2
import PackageDescription
let name = "LibraryCLI"
let package = Package(
name: name,
targets: [
.executableTarget(name: name)
]
)
Если вы когда-нибудь ловили баг «в одном месте переименовал, в другом забыл» — вы уже понимаете, почему такая константа делает жизнь спокойнее.
5. Мини-пример: скелет LibraryCLI
Соберём мысль в одну картинку. Представьте, что у нас в проекте есть два ключевых элемента: манифест и исходники. Сейчас нам достаточно даже не реальных файлов, а мысленной карты (в следующей лекции дня мы сделаем это «по-настоящему»).
import Foundation
let layout = """
Package.swift
Sources/
LibraryCLI/
main.swift
"""
print(layout)
// Package.swift
// Sources/
// LibraryCLI/
// main.swift
И вот что важно: SwiftPM не угадывает ваш проект по вдохновению. Он смотрит на Package.swift и говорит: «Ага, package LibraryCLI, есть исполняемая цель LibraryCLI — значит буду искать исходники цели там, где принято».
6. Типичные ошибки со SwiftPM и Package.swift
Ошибка №1: писать логику приложения в Package.swift.
Обычно это происходит из лучших побуждений: «раз это Swift — значит тут можно делать всё». Но результат печальный: манифест превращается в сложную программу, которую трудно читать, трудно менять и легко сломать при обновлении инструментов. Правильная привычка — держать Package.swift коротким, описательным и скучным: имя, цели, (иногда) зависимости.
Ошибка №2: путать «проект», «пакет», «таргет» и «бинарник».
Новичок часто думает, что это всё одно и то же. На самом деле это четыре близких, но разных сущности. Чтобы не утонуть в терминах, мы на этом дне фиксируем единое имя LibraryCLI для ключевых сущностей. Это не «единственно правильный стиль в индустрии», это учебная страховка от путаницы.
Ошибка №3: менять // swift-tools-version: 6.2 «наугад».
Иногда кажется: «поставлю версию повыше, вдруг станет лучше». Но // swift-tools-version — это не ускоритель, а контракт совместимости. Если поставить версию, которую ваши инструменты не поддерживают, SwiftPM может вообще перестать читать манифест. В курсе мы используем Swift 6.2 — значит и в манифесте пишем 6.2, не устраивая лотерею.
Ошибка №4: забыть import PackageDescription и удивляться ошибкам.
Это типичная история: вы пишете Package(...), а компилятор манифеста говорит «не знаю такой тип». Потому что Package — не встроенное слово Swift, а тип из PackageDescription. Лечится одной строкой импорта — и привычкой помнить, что в манифесте мы используем специальный API.
Ошибка №5: устроить «зоопарк имён» и потом не понимать, что запускать.
Если пакет называется одним именем, исполняемая цель — другим, а бинарник в итоге — третьим, то команда swift run ... превращается в угадайку. Особенно обидно, когда всё собрано правильно, но вы запускаете «не то». Поэтому дисциплина нейминга — это не занудство, а способ быстрее учиться, пока мозг занят более важными вещами, чем поиск опечатки в имени.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ