В этом разделе описано, какие файлы GHC ожидает найти, какие файлы он создаёт, где они хранятся и какие параметры влияют на это поведение.
Правила именования путей различаются в разных системах. В частности, в Unix системах в качестве разделителя каталогов используется «/», а в Windows системах — «\». В дальнейших разделах мы будем последовательно использовать «/» в качестве разделителя каталогов; замените его на соответствующий символ в вашей системе.
5.8.1. Исходные файлы Haskell
Каждый исходный модуль Haskell следует помещать в отдельный файл.
Как правило, файл следует называть по имени модуля, заменяя точки в имени модуля разделителями каталогов. Например, в системе Unix модуль A.B.C следует поместить в файл A/B/C.hs относительно некоторого базового каталога. Если модуль не будет импортироваться другим модулем (например, Main), для него можно использовать любое имя файла.
GHC предполагает, что исходные файлы содержат только символы ASCII или UTF-8; другие кодировки не распознаются. Однако некорректные последовательности UTF-8 игнорируются в комментариях, поэтому можно использовать другие кодировки, например Latin-1, если исходный код вне комментариев содержит только символы ASCII.
5.8.2. Выходные файлы
При компиляции исходного файла GHC обычно создаёт два файла: объектный файл и файл интерфейса.
Объектный файл, имя которого обычно заканчивается суффиксом .o, содержит скомпилированный код модуля.
Файл интерфейса, имя которого обычно заканчивается суффиксом .hi, содержит информацию, необходимую GHC для компиляции других модулей, зависящих от этого модуля. В нём содержатся, например, типы экспортируемых функций, определения типов данных и так далее. Он хранится в двоичном формате, поэтому не пытайтесь его читать; вместо этого используйте параметр --show-iface ⟨file⟩ (см. раздел Другие параметры, связанные с файлами интерфейсов).
Объектный файл и файл интерфейса следует рассматривать как пару, поскольку файл интерфейса — это, в некотором смысле, описание содержимого объектного файла, предназначенное для чтения компилятором. Если файл интерфейса и объектный файл по какой-либо причине рассинхронизируются, компилятор может сделать неверные предположения об объектном файле; почти наверняка это приведёт к проблемам. Поэтому мы рекомендуем хранить объектные файлы и файлы интерфейсов в одном месте (по умолчанию GHC так и делает, однако, как мы вскоре объясним, значения по умолчанию можно переопределить).
У каждого модуля есть имя модуля, определённое в его исходном коде (module A.B.C where ...).
Имя объектного файла, создаваемого GHC, определяется по следующим правилам, где ⟨osuf⟩ — суффикс объектного файла (его можно изменить с помощью параметра -osuf).
- Если параметр
-odirне задан (значение по умолчанию), имя объектного файла определяется по имени исходного файла (имя модуля не учитывается), а его суффикс заменяется на ⟨osuf⟩. - Если задан параметр
-odir ⟨dir⟩, имя объектного файла имеет вид ⟨dir⟩/⟨mod⟩.⟨osuf⟩, где ⟨mod⟩ — имя модуля, в котором точки заменены косыми чертами. Если необходимая структура каталогов внутри ⟨dir⟩ ещё не существует, GHC создаст её без уведомления.
Имя файла интерфейса определяется по тем же правилам, за исключением того, что вместо ⟨osuf⟩ используется суффикс ⟨hisuf⟩ (по умолчанию .hi), а вместо параметров -odir ⟨dir⟩ и -osuf ⟨suffix⟩ используются соответствующие параметры -hidir ⟨dir⟩ и -hisuf ⟨suffix⟩.
Например, если GHC компилирует модуль A.B.C из файла src/A/B/C.hs без флагов -odir и -hidir, файл интерфейса будет помещён в src/A/B/C.hi, а объектный файл — в src/A/B/C.o.
Для любого импортируемого модуля имя модуля в инструкции импорта должно в точности совпадать с именем модуля в файле интерфейса (или исходном файле), найденном согласно стратегии, описанной в разделе Путь поиска. Это означает, что для большинства модулей имя исходного файла должно совпадать с именем модуля.
Однако вполне допустимо поместить модуль Main в файл с именем foo.hs, но это работает только потому, что GHC никогда не требуется искать интерфейс модуля Main (поскольку он никогда не импортируется). Поэтому в одном каталоге можно иметь несколько модулей Main в отдельных исходных файлах, и GHC не будет путаться.
В режиме пакетной компиляции имя объектного файла также можно переопределить с помощью параметра -o ⟨file⟩, а имя файла интерфейса можно задать напрямую с помощью параметра -ohi ⟨file⟩.
5.8.3. Путь поиска
В программе модуль Foo импортируется с помощью инструкции import Foo. В режиме --make или в GHCi GHC найдёт исходный файл для Foo и сначала скомпилирует его. Без --make GHC будет искать файл интерфейса для Foo, который должен быть создан при предыдущей компиляции Foo.
Поиск исходных файлов выполняется следующим образом: GHC хранит список каталогов, называемый путём поиска. Для каждого из этих каталогов он пытается добавить к нему ⟨basename⟩.⟨extension⟩ и проверяет, существует ли полученный файл. Значение ⟨basename⟩ — это имя модуля, в котором точки заменены разделителем каталогов («/» или «\\"» — в зависимости от системы), а ⟨extension⟩ — расширение исходного файла (hs, lhs), если используется режим --make или GHCi.
При поиске файлов интерфейсов в режиме -c поиск выполняется в каталоге -hidir, если он задан. В противном случае для поиска файла интерфейса используется та же стратегия, что и для исходных файлов.
Например, пусть путь поиска содержит каталоги d1, d2 и d3, а мы ищем исходный файл модуля A.B.C в режиме --make. GHC будет искать его в d1/A/B/C.hs, d1/A/B/C.lhs, d2/A/B/C.hs и так далее.
По умолчанию путь поиска содержит один каталог: «.» (то есть текущий каталог). Для добавления каталогов в путь поиска или изменения его содержимого можно использовать следующие параметры:
-
-i⟨dir⟩[:⟨dir⟩]* -
Этот флаг добавляет в путь поиска список каталогов
dirs, разделённых двоеточиями.
-
-i -
сбрасывает путь поиска, оставляя его пустым.
Это ещё не всё: GHC также ищет модули в предварительно скомпилированных библиотеках, называемых пакетами. Подробности см. в разделе о пакетах (Пакеты).
5.8.4. Перенаправление выходных файлов компиляции
-
-o ⟨file⟩ -
Скомпилированный результат GHC обычно записывается в файл
.hc,.oи т. д. в зависимости от последней стадии компиляции. Параметр-o fileперенаправляет вывод этой последней стадии в ⟨file⟩.Примечание
Эта «возможность» может показаться нелогичной:
ghc -C -o foo.o foo.hsпоместит промежуточный код C в файлfoo.o, несмотря на его имя!Чаще всего этот параметр используется при создании исполняемого файла, чтобы задать его имя. Например:
ghc -o prog --make Main
скомпилирует программу, начиная с модуля
Main, и поместит исполняемый файл вprog.Примечание: в Windows, если результатом является исполняемый файл, к указанному имени добавляется расширение «
.exe», если у него ещё нет расширения. Таким образом,ghc -o foo Main.hs
скомпилирует и скомпонует модуль
Main.hsи поместит полученный исполняемый файл вfoo.exe(а не вfoo).Если вы используете
ghc --make, но не используете-o, имя исполняемого файла, выбранное GHC, будет основано на имени файла, содержащего модульMain. Обратите внимание, что в GHC модульMainне обязательно должен находиться в файлеMain.hs. Поэтому обе командыghc --make Prog
и
ghc --make Prog.hs
создадут файл
Prog(илиProg.exeв Windows).
-
-dyno ⟨file⟩ -
При использовании
-dynamic-tooпараметр-dyno⟨suffix⟩ является аналогом-o. Он перенаправляет динамический вывод в ⟨file⟩.
-
-odir ⟨dir⟩ -
Перенаправляет объектные файлы в каталог ⟨dir⟩. Например:
$ ghc -c parse/Foo.hs parse/Bar.hs gurgle/Bumble.hs -odir `uname -m`
Объектные файлы
Foo.o,Bar.oиBumble.oбудут помещены в подкаталог, названный в соответствии с архитектурой выполняющей машины (x86,mipsи т. д.).Обратите внимание, что параметр
-odirне влияет на каталог, в который помещаются файлы интерфейсов; для этого используйте параметр-hidir. В приведённом выше примере они по-прежнему будут помещены вparse/Foo.hi,parse/Bar.hiиgurgle/Bumble.hi.Также обратите внимание, что при инкрементальной компиляции GHC ищет в этом каталоге объектные файлы из предыдущих сборок.
-
-ohi ⟨file⟩ -
Параметр
-ohi bar2/Wurble.ifaceпозволяет перенаправить вывод файла интерфейса в другой файлbar2/Wurble.iface(не рекомендуется).Предупреждение
Если перенаправить файл интерфейса туда, где GHC не сможет его найти, проверка необходимости перекомпиляции может работать некорректно (как минимум, перекомпиляции избежать не удастся). По возможности рекомендуем вместо этого использовать комбинацию параметров
-hidirи-hisuf.Чтобы вообще не создавать файл интерфейса, можно перенаправить его в никуда с помощью этого параметра, например:
-ohi /dev/null.
-
-dynohi ⟨file⟩ -
При использовании
-dynamic-tooпараметр-dynohi⟨file⟩ является аналогом-ohi. Он перенаправляет вывод динамического файла интерфейса в ⟨file⟩.
-
-hidir ⟨dir⟩ -
Перенаправляет все создаваемые файлы интерфейсов в каталог ⟨dir⟩ вместо каталога по умолчанию.
Также обратите внимание, что при инкрементальной компиляции (с помощью
ghc --makeилиghc -c) GHC ищет файлы интерфейсов именно в этом каталоге.
-
-hiedir ⟨dir⟩ -
Перенаправляет все создаваемые расширенные файлы интерфейсов в каталог ⟨dir⟩ вместо каталога по умолчанию.
Также обратите внимание, что при инкрементальной компиляции (с помощью
ghc --makeилиghc -c) GHC ищет расширенные файлы интерфейсов именно в этом каталоге.
-
-stubdir ⟨dir⟩ -
Перенаправляет все создаваемые файлы-заглушки FFI в каталог ⟨dir⟩. Файлы-заглушки создаются, если исходный код Haskell содержит объявление
foreign exportилиforeign import "&wrapper"(см. раздел Использование foreign export и foreign import ccall "wrapper" с GHC). Параметр-stubdirработает с иерархическими модулями точно так же, как-odirи-hidir.
-
-dumpdir ⟨dir⟩ -
Перенаправляет все файлы дампа в каталог ⟨dir⟩. Файлы дампа создаются, если
-ddump-to-fileиспользуется с другими флагами-ddump-*.
-
-outputdir ⟨dir⟩ -
Параметр
-outputdirявляется сокращённой записью для комбинации параметров-odir ⟨dir⟩,-hidir ⟨dir⟩,-hiedir ⟨dir⟩,-stubdir ⟨dir⟩и-dumpdir ⟨dir⟩.
-
-osuf ⟨suffix⟩ -
Параметр
-osuf⟨suffix⟩ изменяет суффикс.oобъектных файлов на указанный вами. Мы используем его при компиляции библиотек, чтобы объектные файлы профилируемых версий библиотек не перезаписывали обычные.
-
-dynosuf ⟨suffix⟩ -
При использовании
-dynamic-tooпараметр-dynosuf⟨suffix⟩ является аналогом-osuf. Он изменяет суффикс.dyn_oдинамических объектных файлов.
-
-hisuf ⟨suffix⟩ -
Аналогично, параметр
-hisuf⟨suffix⟩ изменяет суффикс.hiфайлов интерфейсов, не относящихся к системным (см. раздел Другие параметры, связанные с файлами интерфейсов).Сочетание
-hisuf/-osufособенно полезно, если нужно скомпилировать программу в одном каталоге как с профилированием, так и без него. Можно выполнить:ghc ...
чтобы получить обычную версию, и
ghc ... -osuf prof.o -hisuf prof.hi -prof -fprof-auto
чтобы получить профилируемую версию.
-
-dynhisuf ⟨suffix⟩ -
При использовании
-dynamic-tooпараметр-dynhisuf⟨suffix⟩ является аналогом-hisuf. Он изменяет суффикс.dyn_hiдинамических файлов интерфейсов.
-
-hiesuf ⟨suffix⟩ -
Параметр
-hiesuf⟨suffix⟩ изменяет суффикс.hieрасширенных файлов интерфейсов на указанный вами.
-
-hcsuf ⟨suffix⟩ -
Наконец, параметр
-hcsuf⟨suffix⟩ изменяет суффикс.hcпромежуточных файлов C, создаваемых компилятором.
5.8.5. Сохранение промежуточных файлов
Следующие параметры позволяют сохранять (или не сохранять) некоторые промежуточные файлы, которые GHC обычно удаляет после компиляции:
-
-keep-hc-file -
-keep-hc-files -
Сохраняет промежуточные файлы
.hcпри компиляции из.hsв.oчерез C (примечание: файлы.hcсоздаются только компиляторами без регистрации).
-
-keep-hi-files -
Сохраняет промежуточные файлы
.hi. Это поведение установлено по умолчанию. Используйте-no-keep-hi-files, если файлы.hiвам не нужны.
-
-keep-hscpp-file -
-keep-hscpp-files -
Сохраняет результат этапа предварительной обработки
CPPв виде файлов.hscpp. Файл.hscppсоздаётся только при компиляции модуля, использующего препроцессор C.
-
-keep-llvm-file -
-keep-llvm-files -
- Подразумевает:
Сохраняет промежуточные файлы
.llпри компиляции из.hsв.oчерез LLVM (примечание: файлы.llне создаются при использовании собственного генератора машинного кода; для их создания может потребоваться параметр-fllvm).
-
-keep-o-files -
Сохраняет промежуточные файлы
.o. Это поведение установлено по умолчанию. Используйте-no-keep-o-files, если файлы.oвам не нужны.
-
-keep-s-file -
-keep-s-files -
Сохраняет промежуточные файлы
.s.
-
-keep-tmp-files -
Указывает драйверу GHC не удалять временные файлы, которые обычно хранятся в
/tmp(или, возможно, в другом месте; см. раздел Перенаправление временных файлов). Запуск GHC с параметром-vпокажет, какие временные файлы были созданы в процессе работы.
5.8.6. Перенаправление временных файлов
-
-tmpdir ⟨dir⟩ -
Если возникают проблемы из-за нехватки места в
/tmp(или другом каталоге, куда ваша система помещает временные файлы), можно использовать параметр-tmpdir ⟨dir⟩, чтобы указать другой каталог. Например,-tmpdir .задаёт текущий рабочий каталог для временных файлов.В качестве альтернативы используйте переменную окружения
TMPDIR. Укажите в ней имя каталога, куда следует помещать временные файлы. GCC и другие программы также учитывают переменнуюTMPDIR.
5.8.9. Проверка необходимости перекомпиляции
-
-fforce-recomp -
Отключает проверку необходимости перекомпиляции (по умолчанию она включена). Обычно такая проверка позволяет досрочно завершить компиляцию и оставить существующий файл
.oна месте, если удаётся определить, что модуль не нужно перекомпилировать.
-
-fignore-optim-changes
-
-fignore-hpc-changes
Раньше GHC сравнивал только что созданный файл .hi с предыдущей версией; если они совпадали, он оставлял старый файл без изменений и не менял дату его модификации. В результате модули, импортирующие модуль с неизменившимся выходным файлом .hi, не перекомпилировались.
Теперь этот подход не работает. Предположим, что модуль C импортирует модуль B, а B импортирует модуль A. Поэтому изменения в модуле A могут потребовать перекомпиляции модуля C, а значит, при изменении A.hi нужно проверять, следует ли перекомпилировать C. Однако в списке зависимостей C указан только B.hi, но не A.hi, а некоторые изменения в A (например, изменение определения функции, используемой при встраивании функции, экспортируемой B) теоретически могут не изменить B.hi ни на йоту. Поэтому теперь…
GHC вычисляет контрольную сумму (фактически хеш MD5) для каждого файла интерфейса и каждого объявления в нём. Кроме того, в каждом файле интерфейса хранится список контрольных сумм всего, что использовалось при последней компиляции файла. Если хеш MD5 исходного файла, сохранённый в файле .hi, не изменился, дата модификации файла .o не меньше даты модификации файла .hi и проверка перекомпиляции включена, GHC действует предусмотрительно. Он сравнивает контрольные суммы необходимых сейчас данных с контрольными суммами данных, необходимых при предыдущей компиляции (полученными из файла интерфейса компилируемого модуля); если они все совпадают, компиляция досрочно останавливается с сообщением «Компиляция НЕ ТРЕБУЕТСЯ». Какое прекрасное зрелище!
О том, как всё это работает, можно прочитать в описании внутреннего устройства GHC.
5.8.9.1. Перекомпиляция для Template Haskell и плагинов
При использовании Template Haskell или плагинов проверка необходимости перекомпиляции становится несколько сложнее. Обе эти возможности выполняют код во время компиляции, поэтому изменение любого из фрагментов выполняемого кода требует перекомпиляции модуля. Рассмотрим сплайс верхнего уровня:
main = $(foo bar [| () |])
При компиляции модуля будет вычислено foo bar [| () |], а полученный код будет включён в программу. Зависимости выражения вычисляются и сохраняются во время компиляции модуля. При записи файла интерфейса создаются дополнительные зависимости от объектных файлов, от которых зависит выражение. Например, если foo находится в модуле A, а bar — в модуле B, модуль будет зависеть от A.o и B.o; при изменении любого из них модуль будет перекомпилирован.