Глава 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
.