# Глава 8. Пакетная компиляция (ocamlc)

В этой главе описывается пакетный компилятор языка Objective Caml `ocamlc`, который компилирует файлы с исходными текстами на языке Caml в объектные файлы с байткодом и компонует их, создавая самостоятельные исполняемые файлы с байткодом. Затем такие файлы запускаются интерпретатором байткода `ocamlrun`.

## 8.1 Обзор компилятора

По интерфейсу командной строки `ocamlc` похож на большинство компиляторов C. Он принимает несколько типов аргументов:

- Аргументы, заканчивающиеся на `.mli` считаются исходными файлами для интерфейсов единиц компиляции. В интерфейсах указываются имена, экспортируемые единицами компиляции: там объявлены имена переменных и их типы, определены типы данных, объявлены абстрактные типы данных, и т.д. Из файла `X.mli` компилятор `ocamlc` создаст файл `X.cmi` со скомпилированным интерфейсом

- Аргументы, заканчивающиеся на `.ml` считаются исходными файлами для реализаций единиц компиляции. Реализации содержат определения для имен, экспортируемых единицей, а также выражения, вычисляемые на предмет их сторонних эффектов. Из файла `X.ml` компилятор `ocamlc` создает объект с байткодом в файле `X.cmo`.

  Если существует интерфейс `X.mli`, реализация `X.ml` сверяется с соотвествующим скомпилированным интерфейсом `X.cmi`, который уже считается существующим. Если файла `X.mli` не найдено, при компиляции `X.ml` создается также компилированный интерфейс `X.cmi`, экспортирующий все, что определено в реализации `X.ml`.

- Аргументы, заканчивающиеся на `.cmo`, считаются скомпилированными объектами с байткодом. Они компонуются c объектными файлами, полученными в результате компиляции файлов `.ml` (если такие есть) и стандартной библиотекой Objective Caml. В результате создается самостоятельная исполняемая программа. Порядок следования в командной строке аргументов `.cmo` и `.ml` важен: во время исполнения единицы компиляции инициализируются в том же порядке, так что во время компоновки ошибкой будет использовать компонент единицы до ее инициализации. Поэтому файл `X.cmo` должен стоять в командной строке компилятора прежде любых файлов `.cmo`, ссылающихся на единицу `X`.

- Аргументы, заканчивающиеся на `.cma`, считаются библиотеками объектов с байткодом. Библиотека содержит в одом файле набор файлов, компилированных в байткод (файлов `.cmo`). Она создается командой `ocamlc -a` (описание опций приведено ниже). Объетные файлы в библиотеке компонуются как обычные файлы `.cmo` (см. выше) в том же порядке, в каком строился файл `.cma`. Единственное различие состоитв том, что, если в программе нет ссылок на какой-то объектный файл в библиотеке, то он и не компонуется с программой.

- Аргументы, заканчивающиеся на `.c`, передаются комплятору С, который генерирует объектный файл `.o`. Затем этот файл компонуется с программой, если был задан флаг `-custom` (описание опций см. ниже).

- Аргументы, заканчивающиеся на `.o` или `.a` (`.obj` и `.lib` под Windows) считаются объекнтыми файлами и библиотеками С. Они передаются компоновщику С при компоновке в режиме `-custom` (см. описание опций ниже).

- Аргументы, заканчивающиеся на `.so` (`.dll` под Windows, считаются совместно используемыми библиотеками С. Во время компоновки в них ищутся внешние функции С, на которые есть ссылки в коде Caml, и имена этих библиотек записываются в байткод исполнямых файлов. Система времени исполнения `ocamlrun` загружает необходимые библиотеки динамически при запуске программы.

На стадии компоновки генерируется файл, содержащий скомпилированный байткод, пригодный к исполнению интерпретатором байкода Objective Caml - программой `ocamlrun`. Для скомпилированного файла `caml.out` команда

    ocamlrun caml.out arg1 arg2 ... argn

выполнит скомпилированный код, содержащийся в файле `caml.out`, передав ему как аргументы символьные строки от `arg1` до `argn`. (Дополнительно см. главу 10.)

В большинстве систем файл, созданный на стадии компоновки можно запускать непосредственно:

    ./caml.out arg1 arg2 ... argn

Бит исполнения обычно уже установлен, поэтому интерпретатор байткода запустится автоматически.

## 8.2 Опции

`ocamlc` разпознает следующие опции командной строки:

-a

  Создает библиотеку (файл `.cma`) из объектных файлов `.cmo`, заданных в командной строке. Имя библиотеки может быть задано с помощью опции `-o`. Имя по умолчанию - `library.cma`

  Если в командной строке присутствуют опции `-custom`, `-cclib` или `-ccopt`, то они сохраняются в библиотеке и затем автоматически добавляются при компоновке с этой библиотекой к командной строке в исходном порядке, если только не было задано опции `-noautolink`.

-c

  Только компиляция. Фаза компоновки опускается. Файлы с исходным текстом преобразуются в компилированные файлы, но исполняемый файл не создается. Эта опция полезна для раздельной компиляции модулей.

-cc ccomp

  Использовать `ccomp` в качестве компоновщика и компилятора С для компиляции файлов с исходными текстами `.c` при вызове `ocamlc -custom`.

-cclib -llibname

  Передать компоновщику С опцию `-llibname` при компиляции в "заказном" режиме (см. описание опции `-custom`). В результате данная библиотека С будет скомпонована с программой.

-ccopt option

  Передать данную опцию компилятору и компоновщику С при сборке в "заказном" режиме (см. описание опции `-custom`). Например, при задании `-ccopt -Ldir` компоновщик С будет искать библиотеки С в каталоге `dir`.

-custom

  Сборка в "заказном" режиме. В режиме по умолчанию компоновщик создает байткод, предназначенный для исполнения разделямой системой `camlrun`. В заказном режиме результирующий файл содержит как байткод, так и систему времени выполнения. Он становится больше, но может выполняться сам, даже если команда `ocamlrun` не установлена. Кроме того, заказной режим позволяет статически компоновать код Caml c пользовательскими функциями на С, как описано в гл. 18. Под Unix не используйте команду `strip` для файлов, созданных `ocamlc -custom`. Она удаляет байкод из исполняемого файла.

-dllib -llibname

  Разделяемая библиотека С `libname.so` (`libname.dll` под Windows) будет загружена динамически системой времени исполнения `ocamlrun` при старте программы.

-dllpath dir

  Добавляет каталог `dir` к пути поиска времени исполнения для разделяемых библиотек С. При компоновке разделяемые библиотеки ищутся в стандартном пути поиска (он соответсвует опции `-I`). Опция `-dllpath` просто сохраняет `dir` в исполняемом файле, где `ocamlrun` может найти ее и использовать, как описано в разделе 10.3.

-g

  Добавляет отладочную информацию во время компиляции и компоновки. Эта опция необходима для того, чтобы отлажиать программу с помощью `ocamldebug` (см. гл. 16).

-i

  Компилятор выводит все определенные имена (а также автоматически распознанные типы или их определения) при компиляции реализации (файл `.ml`. Это бывает полезно для проверки типов, распознанных компилятором. Кроме того, поскольку вывод соответствует синтаксису интерфейсов, он может помочь явно написать интерфейс (файл `.mli`) для файла: достаточно перенаправить вывод компилятора в файл `.mli` и убрать из результата декларации имен, экспорт которых не предполагается.

-I directory

  Добавляет `directory` к списку каталогов, в которых ищутся компилированные файлы интерфейсов (`.mli`), компилированные объектные файлы (`.cml`), библиотеки (`.cma`) и библиотеки С, заданнын в опциях `-cclib -lxxx`. По умолчанию в первую очередь файлы ищутся в текущем каталоге, затем - в каталоге стандартной библиотеки. Каталоги, заданные опцией `-I` учитываются после текущего, в том же порядке, как они заданы в командной строке, но перед стандартной библиотекой.

  Если каталог начиначается со знака `+`, то он считается заданным относительно каталога стандартной библиотеки. Например, `-I +labltk` добавляет к списку поиска подкаталог `labltk` стандартной библиотеки.

-impl filename

  Компилировать указанный файл как реализацию, даже если его расширение отличается от `.ml`.

-intf filename

  Компилировать указанный файл как интерфейс, даже если его расширение отличается от `.mli`.

-linkall

  Принудительно компоновать все модули в библиотеках. При отсутствии этой опции компонуются только модули, на которые есть ссылки. При построении библиотеки (флаг `-a`) установка флага `-linkall` приводит к тому, что в последующем при компиляции программ, использующих эту библиотеку, все модули библиотеки будут компоноваться заново.

-make-runtime

  Создает пользовательскую систему времени выполнения (в файле, заданном опцией `-o`), внедряя объектные файлы С и библитеки, указанные в командной строке. Полученная система может быть использована позднее для запуская исполняемого байткода, скомпилированного командой `ocamlc -use-runtime runtime-name`. Более подробная информация содержится в разделе 18.1.6.

-noassert

  Отключает проверку утверждений, и утверждения не компилируются. При компоновке ранее скомпилированных файлов этот флаг не работает.

-noautolink

  При компоновке библиотек `.cma` опции `-custom`, `-cclib` и `-ccopt`, вероятно хранящиеся в библиотеках (если они были указаны при построении библиотек), игнорируются. Этот флаг полезен, если библиотека содержит неправильные спецификации библиотек или опций С. В этом случае надо указать в командной строке правильные библиотеки и опции С.

-nolabels

  Игнорировать неопциональные метки в типах. В этом случае метки не могут использоваться в приложениях, и порядок аргументов становится строгим.

-o exec-file

  Имя файла, создаваемого компоновщиком. По умолчанию по традициям Unix создается файл `a.out`. Если используется опция `-a`, укажите имя библиотеки. Если используется опция `-output-obj`, укажите имя объектного файла.

-output-obj

  Вместо исполняемого байткода компоновщик создает объектный файл С. Это позволяет при необходимости оформить код Caml как библиотеку С, которую можно вызывать из любой программы С. Более подробно см. раздел 18.7.5. По умолчанию создается файл `camlprog.o`, но имя может быть задано с помощью опции `-o`.

-pack

  Создает объектный файл с байткодом (`.cmo`) и связанный с с ним скомпилированный интерфейс (`.cmi`), которые включают все объектные файлы, перечисленные в командной строке. Эти файлы становятся субмодулями результирующего файла `.cmo`. Имя последнего задается опцией `-o`. Например,

      ocamlc -pack -o p.cmo a.cmo b.cmo c.cmo

  создает компилированные файлы `p.cmo` и `p.cmi`, описывающие единицу компиляции, включающую субмодули A, B и C, соотвествующие содержанию объектных файлов `a.cmo`, `b.cmo` и `c.cmo`. В дальнейшем на них можно ссылаться как `P.A`, `P.B` и `P.C`.

-pp command

  Компилятр вызывает заданную команду `command` как препроцессор для каждого исходного файла. Вывод команды перенаправляется в промежуточный файл, который и компилируется. Если компиляция проходит без ошибок, по ее завершении промежуточный файл удаляется. Имя файла конструируется на основе имени исходного файла и получает расширение `.ppi` для интерфейса (`.mli`) или `.ppo` для реализации (`.ml`).

-principal

  Во время проверки типов компилятор проверяет также информацию о путях, следя чтобы все типы выводились приниципально. Этот флаг обязателен при использовании аргументов с метками и/или полиморфных методов - только так можно быть уверенным, что будущие версии компилятора смогут правильно определить типы даже при изменении внутреннего алгоритма. Программы, допустимые в режиме `-principal`, допустимы и в режиме по умолчанию с эквивалентными типами, однако бинарные сигнатуры в этом случае другие, поэтому проверка типов работает медленнее. Тем не менее, компиляция с этим флагом может быть полезна, если исходный код еще не опубликован.

-rectypes

  Разрешает во время проверки типа произвольные рекурсивные типы. По умолчанию поддерживаются только рекурсивные типы с рекурсией по типу объекта.

-thread

  Компилирует или компонует многопоточные программы с использованием библиотеки `thread`, описанной в главе 24. На самом деле, эта опция просто подключает особую версию стандартной библиотеки, безопасную по потокам.

-unsafe

  Отключает проверку границ на массивах и обращении к строкам (конструкции `v.(i)` и `s.[i]`). Программы, собранные с этой опцией несколько быстрее, но не являются безопасными: при обращении к элементу за пределами массива или границы строки может произойти все, что угодно.

-use-runtime runtime-name

  Создает байткод, исполняемый в пользовательской системе времени исполнения, построенной ранее командой `ocamlc -make-runtime runtime-name`. Дополнительная информация содержится в разделе 18.1.6.

-v

  Выводит номер версии компилятора и путь к стандартной библиотеке, после чего прекращает работу.

-verbose

  Выводит все внешние команды перед их выполнением. В частности это касается компилятора и компоновщика C в режиме `-custom`. Эта опция полезна при отладке проблем с библиотеками C.

-version

  Выводит номер версии компилятора в краткой форме (например `3.06`) и прекращает работу.

-w warning-list

  Включает или выключает предупреждения согласно аргументу `warning-list`. Аргумент является строкой из одной или нескольких букв следующего значения:

  A/a

    включить/выключить все предупреждения.

  C/c

    включить/выключить предупреждения о подозрительных комментариях.

  D/d

    Включить/выключить предупреждения об использовании устаревших функций.

  F/f

    Включить/выключить предупреждения о неполном применении функций (например, `f x; expr` при том, что вызов `f x` имеет тип функции).

  L/l

    Включить/выключить предупреждения о пропуске меток в вызовах.

  M/m

    Включить/выключить предупреждения о переопределенных методах.

  P/p

    Включить/выключить предупреждения о неполных совпадениях (пропущенные ветки в поиске по образцу).

  S/s

    Включить/выключить предупреждения о предложениях с типом, отличным от `unit` (например, `expr1; expr2`, когда `expr1` не имеет тип `unit`.

  U/u

    Включить/выключить предупреждения о неиспользуемых (избыточных) совпадениях с образцом.

  V/v

    Включить/выключить предупреждения о скрытых переменных экземпляра.

  X/x

    Включить/выключить все остальные предупреждения.

  По умолчанию используется установка `-w Al` (все предупреждения, кроме меток).

-warn-error warning-list

  Все предупреждения, включенные в аргумент `warning-list` считаются ошибками. Компилятор останавливается на ошибке при появлении одного из предупреждений, вместо того, чтобы продолжать выполнение. `warning-list` является строкой из одной или нескольких букв того же значения, что и в опции `w`: прописная буква превращает предупреждение в ошибку, строчная оставляет ее предупреждением. Значение по умолчанию - `-warn-error a` (все предупреждения не считаются ошибками).

-where

  Выводит путь к стандартной библиотеке и прекращает работу.

## 8.3 Модули и файловая система

Этот раздел должен объяснить связь между именами модулей, соответствующих единицам компиляции, и именами файлов, содержащих соответствующие компилированные реализации и интерфейсы.

Компилятор всегда выводит имя модуля из имени исходного файла без расширения (`.ml` или `.mli`). Таким образом, он удаляет информацию о пути (если она есть) и расширение и преобразует первую букву в верхний регистр (поскольку имена модулей должны быть капитализированы). Например, файл `mylib/misc.ml` содержит реализацию модуля `Misc`. Другие единицы компиляции могут ссылаться на компоненты, определенные в `mylib/misc.ml` по имени типа `Misc.name` или использовать выражение `open Misc`, а затем - неполное имя `name`.

Создаваемые компилятором файлы `.cmi` и `.cmo` имеют те же имена, что и файлы с исходным текстом, поскольку компилятор сохраняют для них имя модуля, который они описывают (для файлов `.cmi`) или реализуют (для файлов `.cmo`).

Встречая ссылку на идентификатор модуля `Mod`, компилятор просматривает путь поиска, ища файл `mod.cmi` (обратите внимание, что первая буква строчная), и загружает компилированный интерфейс, содержащийся в этом файле. Поэтому крайне не рекомендуется переименовывать файлы `.cmi`: их имена всегода должны соотвествовать имени реализованной в них единицы компиляции. В противном случае компилятор сообщит об ошибке.

Что же касается файлов с байткодом (`.cmo`), то их можно переименовывать сколько угодно. Дело в том, что компоновщик не ищет файл `.cmo`, содержащий реализацию модуля: считатется, что пользователь сам должен предоставить ему список файлов `.cmo`.

## 8.4 Распространенные ошибки

В этом разделе описываются наиболее распространенные сообщения об ошибках.

Cannot find file filename

  Указанный файл не обнаружен в текущем каталоге или каталогах, входящих в пусть поиска. `filename` является либо компилированным интерфейсом (`.cmi`), либо байткодом (`.cmo`). Если `filename` представлен в формате `mod.cmi`, то это означает, что компилированный файл ссылается на модуль `mod`, который еще не скомпилирован. Чтобы исправить эту ошибку, достаточно скомпилировать предварительно файл `mod.mli` или `mod.ml`.

  Если `filename` имеет формат `mod.cmo`, то это означает, что предпринята попытка скомнововать еще не созданный файл с байткодом. В данном случае надо предварительно скомпилировать файл `mod.ml`.

  Если исходные тексты программы размещены в нескольких каталогах, эта ошибка может означать, что каталоги не включены в путь поиска, и надо исправить опции `-I` командной строки.

Corrupted compiled interface filename

  Эта ошибка возникает, когда компилятор пытается прочесть скомпилированный файл интерфейса (`.cmi`) с неверной структурой. Она означает, что произошла ошибка при записи файла - либо диск был заполнен, либо процесс компиляции прерван во время записи файла и т.д. Кроме того, ошибка может возникать, если файл `.cmi` был модифицирован после его создания. Для исправления следует удалить файл `.cmi` и построить его заново.

This expression has type t1, but is used with type t2

  Это одна из наиболее распространенных ошибок. Тип `t1` выводится для выражения (в сообщении об ошибке показывается эта часть программы) из самого выражения. Тип `t2` ожидается, исходя из контекста выражения. Он выводится из того, как значение выражения используется в программе далее. Если `t1` и `t2` несовместимы, возникает ошибка.

  Иногды бывает сложно понять, почему типы несовместимы. Например, компилятор может сообщить, что "выражение типа `foo` не может использоваться с типом `foo`", хотя два типа `foo` явно должны быть совместимы. Но это не всегда так. Два конструктора типов могут иметь общее имя, но на самом деле представлять разные типы. Такое случается при переопределении конструктора. Например:

      type foo = A | B
      let f = function A -> 0 | B -> 1
      type foo = C | D
      f C

  При компиляции такого кода появится сообщение об ошибке "Выражение `c` типа `foo` не может использоваться с типом `foo`".

The type of this expression, t, contains type variables that cannot be generalized

  Переменные типа (`'a, 'b, ...`) в типе `t` могут быть в двух состояниях: обобщенные (в этом случае тип `t` пригоден для всех возможных инстанциаций переменных) и не обобщенные (в этом случае тип `t` пригоден только для одной инстанциации переменных). В связывании `let name = expr` система проверки типов как правило обобщает все переменные типа, возможные в типе `expr`. Однако, в совокупности с полиморфическими изменяемыми структурами данных это приводит к уязвимости программ (исполнение правильной программ заканчивается ошибками). Поэтому обобщение производится для связываний `let` только в том случае, если выражение `expr` принадлежит к классу "синтаксических значений", включающему константы, идентификаторы, функции, кортежи синтаксических значений и т.д. Во всех остальных случаях (например, `expr` является приложением функции) может создаваться полиморфная изменяемая структура, и обобщение выключается.

  Внутри данной структуры или единицы компиляции (содержимое файла `.ml` или интерактивной сессии) необобщенные переменные типа не несут никаких проблем, но в сигнатурах или компилированных интерфейсах их быть не должно, поскольку позднее они могут быть использованы неправильно. Поэтому компилятор сообщает об ошибке, когда структура или единица компиляции определяет значение `name`, тип которой содержит необобщенные переменные типа. Есть два способа исправить эту ошибку:

  - Можно добавить в файл `.mli` ограничение типа. В результате тип станет мономорфным (без переменных типа). Например, вместо

        let sort_int_list = Sort.list (<)
        (* выводится тип 'a list -> 'a list, причем 'a не обобщается *)

    пишите

        let sort_int_list = (Sort.list (<) : int list -> int list);;

  - Если необходим именно полиморфный тип, определяющее его выражение можно преобразовать в функцию, добавив параметр. Например, вместо

        let map_length = List.map Array.length
        (* выводится тип 'a list -> int list, причем 'a не обобщается *)

    пишите

        let map_length lv = List.map Array.length lv

Reference to undefined global mod

  Эта ошибка появляется при ссылке на неполный или неправильно упорядоченный набор файлов. Возможно, в командной строке не указана реализация для единицы компиляции `mod`(как правило, файл `mod.cmo` или содержащая его библиотека). Для исправления достаточно указать в командной строке недостающий файл `.ml` или `.cmo`. Кроме того, реализация модуля `mod` может находиться в командной строке после модуля, ссылающегося на нее, а должно быть наоборот. В этом случае надо изменить порядок модулей.

  Разумеется, при взаимно рекурсивных ссылках в модулях (например, функция `Mod1.f` вызывает функцию `Mod2.g`, а та вызывает `Mod1.f`) эта ошибка будет проявляться всегда. В этом случае исходные файлы не будут скомпилированы, как не переставляй их в командной строке. Возможные решения:

  - Поместить `f` и `g` в один модуль.

  - Параметризовать одну функцию другой. То есть вместо:

        mod1.ml:    let f x = ... Mod2.g ...
        mod2.ml:    let g y = ... Mod1.f ...

    писать:

        mod1.ml:    let f g x = ... g ...
        mod2.ml:    let rec g y = ... Mod1.f g ...

    и передавать компоновщику сначала `mod1.cmo`, затем `mod2.cmo`.

  - Одну из двух функций можно поместить в ссылку, например так

        mod1.ml:    let forward_g =
                        ref((fun x -> failwith "forward_g") : <type>)
                    let f x = ... !forward_g ...
        mod2.ml:    let g y = ... Mod1.f ...
                    let _ = Mod1.forward_g := g

    Впрочем, если `g` полиморфна, это работать не будет.

The external function f is not available

  Эта ошибка появляется при попытке компиляции кола, вызывающего с внешнюю функцию на языке С. В гл. 18 объясняется, что такой код должен компоноваться с библиотекой С, содержащей функцию `f`. Если библиотека не разделяемая (DLL), компоновка должна проходить в "заказном режиме" Решение: указать необходимую библиотеку С и, возможно, ключ `-custom`.