########################################################################
# Building OCaml programs.
#
# Copyright (C) 2003-2007 Jason Hickey and Mojave Group
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this file, to deal in the File without
# restriction, including without limitation the rights to use,
# copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the File, and to permit persons to whom the File
# is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the File.
#
# THE FILE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# FILE OR THE USE OR OTHER DEALINGS IN THE FILE.

open build/Common
open configure/Configure

########################################################################
# OCaml section
#
# \begin{doc}
# \section{Building OCaml code}
#
# \OMake{} provides extensive support for building OCaml code, including support for tools like
# \verb+ocamlfind+, \verb+ocamlyacc+ and \verb+menhir+. In order to use the functions
# defined in this section, you need to make sure the line
# \begin{verbatim}
# open build/OCaml
# \end{verbatim}
# is present in your \verb+OMakeroot+ file.
#
# \subsection{Autoconfiguration variables for OCaml compilation}
# These variables will get defined based on the ``autoconf-style'' tests executed when you
# run \OMake{} for the first time. You can use them to configure your project accordingly,
# and you should not redefine them.
#
# You can use the \verb+--configure+ command line option (Section~\ref{option:--configure}) to force
# re-execution of all the tests.
#
# \varlabel{OCAMLOPT_EXISTS}{OCAMLOPT\_EXISTS} True when \verb+ocamlopt+ (or \verb+ocamlopt.opt+) is
#    available on your machine.
# \varlabel{OCAMLFIND_EXISTS}{OCAMLFIND\_EXISTS} True when the ocamlfind is available on your
#    machines.
# \varlabel{OCAMLDEP_MODULES_AVAILABLE}{OCAMLDEP\_MODULES\_AVAILABLE} True when a version of
#    \verb+ocamldep+ that understands the \verb+-modules+ option is available on your machine.
# \varlabel{CMXS_SUPPORTED}{CMXS\_SUPPORTED} True if "ocamlopt -shared" is supported by the compiler.
# \varlabel{MENHIR_AVAILABLE}{MENHIR\_AVAILABLE} True when the Menhir parser-generator is available
#    on your machine.
# \var{OCAMLLIB} The location of OCaml library directory (output of \verb+ocamlc -where+). Empty when no
#    ocamlc is found.
#
# \subsection{Configuration variables for OCaml compilation}
#
# The following variables can be redefined in your project.
# \varlabel{USE_OCAMLFIND}{USE\_OCAMLFIND} Whether to use the \verb+ocamlfind+ utility (default \verb+false+)
# \var{OCAMLC} The OCaml bytecode compiler (default \verb+ocamlc.opt+ if it exists
#    and \verb+USE_OCAMLFIND+ is not set, otherwise \verb+ocamlc+).
# \var{OCAMLOPT} The OCaml native-code compiler (default \verb+ocamlopt.opt+ if it
#    exists and \verb+USE_OCAMLFIND+ is not set, otherwise \verb+ocamlopt+).
# \var{CAMLP4} The \verb+camlp4+ preprocessor (default \verb+camlp4+).
# \var{OCAMLLEX} The OCaml lexer generator (default \verb+ocamllex+).
# \var{OCAMLLEXFLAGS} The flags to pass to \verb+ocamllex+ (default \verb+-q+).
# \var{OCAMLYACC} The OCaml parser generator (default \verb+ocamlyacc+).
# \var{OCAMLYACCFLAGS} Additional options to pass to \verb+$(OCAMLYACC)+.
# \var{OCAMLDEP} The OCaml dependency analyzer (default \verb+ocamldep+).
# \varlabel{OCAMLDEP_MODULES_ENABLED}{OCAMLDEP\_MODULES\_ENABLED} Instead of using \verb+OCAMLDEP+
#    in a traditional \verb+make+-style fashion, run \verb+$(OCAMLDEP) -modules+ and then
#    postprocess the output internally to discover all the relevant generated \verb+.ml+ and
#    \verb+.mli+ files. See Section~\ref{section:ocaml-generated-files} for more information on
#    interactions between \OMake, \verb+OCAMLDEP+ and generated files. Set to
#    \verb+$(OCAMLDEP_MODULES_AVAILABLE)+ by default.
# \var{OCAMLMKTOP} The OCaml toploop compiler (default \verb+ocamlmktop+).
# \var{OCAMLLINK} The OCaml bytecode linker (default \verb+$(OCAMLC)+).
# \var{OCAMLOPTLINK} The OCaml native-code linker (default \verb+$(OCAMLOPT)+).
# \var{OCAMLINCLUDES} Search path to pass to the OCaml compilers (default \verb+.+).
#    The search path with the \verb+-I+ prefix is defined by the \verb+PREFIXED_OCAMLINCLUDES+
#    variable.
# \varlabel{OCAMLINCLUDES_FOR_OCAMLDEP_MODULES}{OCAMLINCLUDES\_FOR\_OCAMLDEP\_MODULES} Extra path for searching files
#    corresponding to dependencies returned by "ocamldep -modules". This
#    defaults to ".". There is normally no reason to change this value.
# \var{OCAMLFIND} The \verb+ocamlfind+ utility (default \verb+ocamlfind+ if
#     \verb+USE_OCAMLFIND+ is set, otherwise empty).
# \var{OCAMLFINDFLAGS} The flags to pass to \verb+ocamlfind+ (default empty, \verb+USE_OCAMLFIND+ must be set).
# \var{OCAMLPACKS} Package names to pass to \verb+ocamlfind+ (\verb+USE_OCAMLFIND+ must be set).
# \varlabel{BYTE_ENABLED}{BYTE\_ENABLED} Flag indicating whether to use the bytecode compiler (default \verb+true+, when no \verb+ocamlopt+ found, \verb+false+ otherwise).
# \varlabel{NATIVE_ENABLED}{NATIVE\_ENABLED} Flag indicating whether to use the native-code compiler (default \verb+true+, when ocamlopt is found, \verb+false+ otherwise).
#    Both \verb+BYTE_ENABLED+ and \verb+NATIVE_ENABLED+ can be set to true;
#    at least one should be set to true.
# \varlabel{CMXS_ENABLED}{CMXS\_ENABLED} Flag indicating whether libraries are
#    also created as plugins. This defaults to \verb+false+ for compatibility
#    with old omake versions. Set it to \verb+CMXS_SUPPORTED+ to enable this
#    feature when supported
# \varlabel{MENHIR_ENABLED}{MENHIR\_ENABLED} Define this as \verb+true+ if you wish to use
#    \verb+menhir+ instead of \verb+ocamlyacc+ (default \verb+false+).
# \varlabel{EXTENDED_DIGESTS}{EXTENDED\_DIGESTS} Whether to include more information into
#   rule digests and make it more sensitive to structural changes at the cost
#   of build speed (\verb+true+ or \verb+false+).
# \varlabel{OCAML_CC}{OCAML\_CC} The C compiler used internally by OCaml
# \varlabel{OCAML_CFLAGS}{OCAML\_CFLAGS} The C compiler flags used by OCaml
# \end{doc}
#
public.USE_OCAMLFIND = false

private.get_c_comp() =
    # since OCaml-4.06
    private.config = $(concat $(unhexify 0a), $(shella ocamlc -config)))
    private.configch = $(open-in-string $(config))
    scan($(configch))
    case $"c_compiler:"
        return $(nth-tl 1, $*)
    return $(string)

private.get_bytecomp_cflags() =
    # since OCaml-4.06
    private.config = $(concat $(unhexify 0a), $(shella ocamlc -config)))
    private.configch = $(open-in-string $(config))
    scan($(configch))
    case $"ocamlc_cflags:"
        return $(nth-tl 1, $*)
    return $(string)

private.get_bytecomp_cppflags() =
    # since OCaml-4.06
    private.config = $(concat $(unhexify 0a), $(shella ocamlc -config)))
    private.configch = $(open-in-string $(config))
    scan($(configch))
    case $"ocamlc_cppflags:"
        return $(nth-tl 1, $*)
    return $(string)

private.get_bytecomp_c_comp() =
    private.config = $(concat $(unhexify 0a), $(shella ocamlc -config)))
    private.configch = $(open-in-string $(config))
    scan($(configch))
    case $"bytecomp_c_compiler:"
        return $(nth-tl 1, $*)
    return $(string)

.STATIC: :value: $(PATH)
    OCAMLFIND_EXISTS = $(CheckProg ocamlfind)
    OCAMLC_OPT_EXISTS = $(CheckProg ocamlc.opt)
    OCAMLC_EXISTS = $(or $(OCAMLC_OPT_EXISTS), $(CheckProg ocamlc))
    OCAMLOPT_OPT_EXISTS = $(CheckProg ocamlopt.opt)
    OCAMLOPT_EXISTS = $(or $(OCAMLOPT_OPT_EXISTS), $(CheckProg ocamlopt))
    OCAMLDEP_OPT_EXISTS = $(CheckProg ocamldep.opt)
    OCAMLLEX_OPT_EXISTS = $(CheckProg ocamllex.opt)
    ConfMsgChecking(whether ocamlc understands the "z" warnings)
    OCAML_ACCEPTS_Z_WARNING =
        if $(OCAMLC_EXISTS)
            value $(ConfMsgYesNo $(shell-success ocamlc$(if $(OCAMLC_OPT_EXISTS), .opt) -w Az))
        else
            ConfMsgResult($"FAILED - ocamlc not found")
            value false
    ConfMsgChecking(whether ocamlopt can create cmxs plugins)
    CMXS_SUPPORTED =
        if $(OCAMLOPT_EXISTS)
            ok = $(ConfMsgYesNo $(shell-success ocamlopt -shared -o .dummy.cmxs))
            rm(-f .dummy.cmxs)
            value $(ok)
        else
            ConfMsgResult($"no (ocamlopt not found)")
            value false
    #
    # Compile native or byte code?
    #
    NATIVE_ENABLED = $(OCAMLOPT_EXISTS)
    BYTE_ENABLED = $(not $(OCAMLOPT_EXISTS))
    CMXS_ENABLED = false
    #
    EXTENDED_DIGESTS = false
    #
    # Figure out the params for the C compiler
    #
    private.c_comp = $(get_c_comp)
    if $(equal X$(string $(c_comp))X, XX)
        private.bytecomp_c_comp = $(get_bytecomp_c_comp)
        OCAML_CC = $(nth-hd 1, $(bytecomp_c_comp))
        OCAML_CFLAGS = $(nth-tl 1, $(bytecomp_c_comp))
        export OCAML_CC OCAML_CFLAGS
    else
        private.bytecomp_cflags = $(get_bytecomp_cflags)
        private.bytecomp_cppflags = $(get_bytecomp_cppflags)
        OCAML_CC = $(c_comp)
        OCAML_CFLAGS = $(bytecomp_cflags) $(bytecomp_cppflags)
        export OCAML_CC OCAML_CFLAGS

public.OCAMLFIND = $`(if $(USE_OCAMLFIND), ocamlfind)
public.OCAMLFINDFLAGS =
public.LAZY_OCAMLFINDFLAGS = $`(if $(USE_OCAMLFIND), $(OCAMLFINDFLAGS))

# OCAMLC/OCAMLOPT/OCAMLDEP are now 0-ary functions instead of normal variables
# because sometimes USE_OCAMLFIND is set too late by the user.
public.OCAMLC() =
    value $(if $(OCAMLC_OPT_EXISTS), $(if $(USE_OCAMLFIND), ocamlc, ocamlc.opt), ocamlc)
public.OCAMLOPT() =
    value $(if $(OCAMLOPT_OPT_EXISTS), $(if $(USE_OCAMLFIND), ocamlopt, ocamlopt.opt), ocamlopt)
public.OCAMLDEP() =
    value $(if $(OCAMLDEP_OPT_EXISTS), $(if $(USE_OCAMLFIND), ocamldep, ocamldep.opt), ocamldep)
public.CAMLP4 = camlp4
public.OCAMLLEX = $(if $(OCAMLLEX_OPT_EXISTS), ocamllex.opt, ocamllex)
public.OCAMLLEXFLAGS = -q
public.OCAMLYACC = ocamlyacc
public.OCAMLYACCFLAGS =
public.OCAMLMKTOP = ocamlmktop
public.OCAMLLINK = $`(OCAMLC)
public.OCAMLOPTLINK = $`(OCAMLOPT)

.STATIC: :value: $(PATH)
    OCAMLLIB =
        if $(OCAMLC_EXISTS)
            ConfMsgChecking(for OCaml library location)
            value $(ConfMsgResult $(dir $"$(shell ocamlc -where)"))
        else
            value $(EMPTY)

#
# Include path
#
public.OCAMLINCLUDES[] = .
section
        vmounts = $(vmount-map)
        OCAMLINCLUDES[] += $(vmounts.keys)
        export OCAMLINCLUDES
public.OCAMLINCLUDES_FOR_OCAMLDEP_MODULES[] = .
public.PREFIXED_OCAMLINCLUDES = $`(mapprefix -I, $(OCAMLINCLUDES))

#
# Packages
#
public.OCAMLPACKS[] =
public.PREFIXED_OCAMLPACKS =\
   $`(if $(and $(USE_OCAMLFIND) $(gt $(length $(OCAMLPACKS)), 0)),\
         -package $(string $(concat \,, $(OCAMLPACKS))),\
         $(EMPTY))

#
# Various options
#
# \begin{doc}
# \subsection{OCaml command flags}
#
# The following variables specify \emph{additional} options to be passed to
# the OCaml tools.
# \var{OCAMLDEPFLAGS} Flags to pass to \verb+OCAMLDEP+.
# \var{OCAMLPPFLAGS} Flags to pass to \verb+CAMLP4+.
# \var{OCAMLCFLAGS} Flags to pass to the byte-code compiler (default \verb+-g+).
# \var{OCAMLOPTFLAGS} Flags to pass to the native-code compiler (default empty).
# \var{OCAMLFLAGS} Flags to pass to either compiler (default \verb+-warn-error A+).
# \varlabel{OCAML_BYTE_LINK_FLAGS}{OCAML\_BYTE\_LINK\_FLAGS} Flags to pass to the byte-code linker (default empty).
# \varlabel{OCAML_NATIVE_LINK_FLAGS}{OCAML\_NATIVE\_LINK\_FLAGS} Flags to pass to the native-code linker (default empty).
# \varlabel{OCAML_LINK_FLAGS}{OCAML\_LINK\_FLAGS} Flags to pass to either linker.
# \varlabel{MENHIR_FLAGS}{MENHIR\_FLAGS} Additional flags to pass to \verb+menhir+.
# \end{doc}
#
declare OCAMLDEPFLAGS
public.OCAMLPPFLAGS  =
public.OCAMLFLAGS    = 
public.OCAMLCFLAGS   = -g
public.OCAMLOPTFLAGS =
public.OCAMLCPPFLAGS =
public.OCAML_LINK_FLAGS = $`(if $(and $(USE_OCAMLFIND) $(gt $(length $(OCAMLPACKS)), 0)), -linkpkg, $(EMPTY))
public.OCAML_BYTE_LINK_FLAGS = -custom
public.OCAML_NATIVE_LINK_FLAGS =

#
# OCAML_LIBS contains libraries that are used as dependencies
# OCAML_OTHER_LIBS contains other libraries (like unix.cma)
# The lists do not include suffixes.
#
# OCAML_LINK_FLAGS contains extra linking information
#
# \begin{doc}
# \subsection{Library variables}
#
# The following variables are used during linking.
#
# \varlabel{OCAML_LIBS}{OCAML\_LIBS} Libraries to pass to the linker.  These libraries become dependencies
#    of the link step.
# \varlabel{OCAML_OTHER_LIBS}{OCAML\_OTHER\_LIBS} Additional libraries to pass to the linker.  These libraries are
#    \emph{not} included as dependencies to the link step.  Typical use is for the OCaml
#    standard libraries like \verb+unix+ or \verb+str+.
# \varlabel{OCAML_CLIBS}{OCAML\_CLIBS} C libraries to pass to the linker.
# \varlabel{OCAML_LIB_FLAGS}{OCAML\_LIB\_FLAGS} Extra flags for the library linker.
# \varlabel{ABORT_ON_DEPENDENCY_ERRORS}{ABORT\_ON\_DEPENDENCY\_ERRORS}
#   OCaml linker requires the OCaml files to be
#   listed in dependency order.  Normally, all the functions presented in this section will automatically sort
#   the list of OCaml modules passed in as the \verb+<files>+ argument. However, this variable is
#   set to \verb+true+, the order of the files passed into these function will be left as is, but \OMake{} will
#   abort with an error message if the order is illegal.
#
# \end{doc}
#
public.OCAML_LIBS =
public.OCAML_CLIBS =
public.OCAML_OTHER_LIBS =
public.OCAML_LIB_FLAGS =

########################################################################
# \begin{doc}
# \subsection{Generated OCaml Files}
# \label{section:ocaml-generated-files}
# As of OCaml version 3.09.2, the standard \verb+ocamldep+ scanner is ``broken''.  The main issue is
# that it finds only those dependencies that already exist.  If \verb+foo.ml+ contains a dependency
# on \verb+Bar+,
# \begin{verbatim}
# foo.ml:
#    open Bar
# \end{verbatim}
# then the default \verb+ocamldep+ will only find the dependency if a file \verb+bar.ml+ or
# \verb+bar.ml+ exists in the include path. It will not find (or print) the dependency if, for
# example, only \verb+bar.mly+ exists at the time \verb+ocamldep+ is run, even though \verb+bar.ml+
# and \verb+bar.mli+ can be generated from \verb+bar.mly+.
#
# \OMake{} currently provides two methods for addressing this problem --- one that requires manually
# specifying the generated files, and an experimental method for discovering such ``hidden''
# dependencies automatically. The
# \hypervarx{OCAMLDEP_MODULES_ENABLED}{OCAMLDEP\_MODULES\_ENABLED} controls which method is
# going to be used. When this variable is false, the manual specifications are expected and when it
# is true, the automated discovery will be attempted.
#
# \twofuns{OCamlGeneratedFiles}{LocalOCamlGeneratedFiles}
# \begin{verbatim}
# OCamlGeneratedFiles(files)
# LocalOCamlGeneratedFiles(files)
# \end{verbatim}
#
# When the \hypervarx{OCAMLDEP_MODULES_ENABLED}{OCAMLDEP\_MODULES\_ENABLED} variable is set
# to \verb+false+, the \verb+OCamlGeneratedFiles+ and \verb+LocalOCamlGeneratedFiles+ functions specify files
# that need to be generated before any OCaml files are scanned for dependencies. For example,
# if \verb+parser.ml+ and \verb+lexer.ml+ are both generated files, specify:
# \begin{verbatim}
# OCamlGeneratedFiles(parser.ml lexer.ml)
# \end{verbatim}
#
# The \verb+OCamlGeneratedFiles+ function is \emph{global} --- its arguments will be generated
# before any OCaml files anywhere in the project are scanned for dependencies. The
# \verb+LocalOCamlGeneratedFiles+ function follows the normal scoping rules of OMake.
#
# These functions have no effect when the
# \hypervarx{OCAMLDEP_MODULES_ENABLED}{OCAMLDEP\_MODULES\_ENABLED} is true.
#
# \subsubsection{Automatic discovery of generated files during dependency analysis}
# Having to specify the generated files manually when \OMake{} could discover them automatically is
# obviously suboptimal.  To address this, we tell \verb+ocamldep+ to \emph{only}
# find the free module names in a file and then post-process the results internally.
#
# This automated functionality is enabled when the
# \hypervarx{OCAMLDEP_MODULES_ENABLED}{OCAMLDEP\_MODULES\_ENABLED} is set to \verb+true+.
# By default, \hypervarx{OCAMLDEP_MODULES_ENABLED}{OCAMLDEP\_MODULES\_ENABLED} will be set to
# \verb+$(OCAMLDEP_MODULES_AVAILABLE)+.
#
# Note that the \verb+ocamldep+ functionality this relies upon is only included in
# the OCaml version 3.10 and higher. It's availability will be discovered automatically
# and the \hypervarx{OCAMLDEP_MODULES_AVAILABLE}{OCAMLDEP\_MODULES\_AVAILABLE}
# will be set accordingly.
#
# \end{doc}
#
.STATIC:
    ConfMsgChecking(if ocamldep understands -modules)
    OCAMLDEP_MODULES_AVAILABLE = $(ConfMsgYesNo $(shell-success ocamldep -modules))

public.OCAMLDEP_MODULES_ENABLED = $(OCAMLDEP_MODULES_AVAILABLE)

public.OCAMLDEPFLAGS = $`(if $(and $(NATIVE_ENABLED), $(not $(OCAMLDEP_MODULES_ENABLED))), -native, $(EMPTY))

.PHONY: OCamlGeneratedFilesTarget

# GS: The test on $(OCAMLDEP_MODULES_ENABLED) in only partially working.
# What if we set this variable later?

public.OCamlGeneratedFiles(files) =
    if $(OCAMLDEP_MODULES_ENABLED)
        # For now, we want to allow ``backwards-compatible'' projects.
        # eprintln($"WARNING: OCamlGeneratedFiles should not be used when OCAMLDEP_MODULES_ENABLED")
        # eprintln($"         is set")
    else
        OCamlGeneratedFilesTarget: $(files)

public.LocalOCamlGeneratedFiles(files) =
    if $(OCAMLDEP_MODULES_ENABLED)
        # For now, we want to allow ``backwards-compatible'' projects.
        # eprintln($"WARNING: OCamlGeneratedFiles should not be used when OCAMLDEP_MODULES_ENABLED")
        # eprintln($"         is set")
    else
        .SCANNER: scan-ocaml-%: $(files)
        .SCANNER: %.cmi: $(files)
        .SCANNER: %.cmx %.cmo: $(files)
        export
    export

#
# The ocamldep -modules output has the following
# form, where the indented lines are the free module names in foo.ml.
#
# foo.ml:
#     Bar
#     ...
#
# From this, we generate proper dependencies by finding the files
# that can be built, using the find-targets-in-path-optional
# function.
#

#
# Print the dependencies for a ML file, based on the
# .cmi files.
#
# If OCAMLDEP_PRESERVE_TARGETS is true, then the
# ocamldep entries are taken literally (the suffix
# is not replaced with .cmo/.cmx).
#
public.OCAMLDEP_PRESERVE_TARGETS = false

public.PrintMLIDependencies(filename, cmideps) =
    if $(cmideps)
        private.base = $(string-escaped $(removesuffix $(filename)))
        println($"""$(base).cmi: $(string-escaped $(cmideps))""")

public.PrintMLDependencies(filename, cmideps, cmxdeps) =
    protected.base = $(string-escaped $(removesuffix $(filename)))
    protected.esc = $' \' # '
    protected.text =
    if $(cmideps)
        cmideps = $(string-escaped $(cmideps))
        text = $"""
$(base).cmo: $(cmideps)
$(base).cmx $(base)$(EXT_OBJ):$(esc)
    $(cmideps)"""
        export text
    if $(cmxdeps)
        if $(not $(text))
            text = $"""$(base).cmx $(base)$(EXT_OBJ):"""
            export
        text += $"""$(esc)
    $(string-escaped $(cmxdeps))"""
        export text
    # eprintln($(text))
    println($(text))

public.PrintFileDependencies(filename, cmideps) =
    if $(cmideps)
        private.text = $"""$(string-escaped $(filename)): $(string-escaped $(cmideps))"""
        # eprintln($(text))
        println($(text))

#
# Given a set of literal dependencies, compute
# the actual dependencies by finding the filenames
# associated with each module.
#
public.PrintDependencies(filename, modules) =
    private.includes[] =
        $(OCAMLINCLUDES_FOR_OCAMLDEP_MODULES)
        $(OCAMLINCLUDES)
    if $(filename)
        #
        # Find the .cmi files that can be built
        #
        private.cmideps = $(find-ocaml-targets-in-path-optional $(includes), $(addsuffix .cmi, $(modules)))

        # Now produce the dependencies
        if $(OCAMLDEP_PRESERVE_TARGETS)
            PrintFileDependencies($(filename), $(cmideps))
        else
            switch($(suffix $(filename)))
            case .ml
                protected.cmxdeps[] =
                if $(NATIVE_ENABLED)
                    cmxdeps = $(find-ocaml-targets-in-path-optional $(includes), $(addsuffix .cmx, $(modules)))
                    export
                PrintMLDependencies($(filename), $(cmideps), $(cmxdeps))
            case .mli
                PrintMLIDependencies($(filename), $(cmideps))
            default
                eprintln($"ocaml scanner: illegal filename $(filename)")
                exit(1)


#
# Post-process the output of ocamldep.
# Use awk to process the input, find the targets that
# exist, and then print the dependencies.
#
public.OCamlScannerPostproc(input) =
    #
    # Read the module names from the standard input
    #
    protected.filename =
    protected.modules[] =
    protected.vmounts = $(vmount-map)
    protected.vmount_source_dirs = $(vmounts.keys)
    protected.compare_by_length_descending(a, b) =
        delta_length = $(sub $(b.length), $(a.length))
        value $(if $(eq $(delta_length), $(int 0)), $(compare $a, $b), $(delta_length))
    protected.sorted_vmount_source_dirs = $(vmount_source_dirs.sort $(compare_by_length_descending))
    awk(b, $(input))
    case $'^\(.*\):[[:space:]]*\(.*\)$'
        PrintDependencies($(filename), $(modules))
        filename = $1
        sorted_vmount_source_dirs.foreach(src) =>
            filename = $(subst $(src), $(vmounts.find $(src)), $(filename))
            export
        modules[] = $(split $' ', $2)
        export
    case $'^	\(.*\)'
        # Add the dependency
        modules[] += $1
        export
    default
        eprintln(Unrecognized ocamldep output: $0)
    PrintDependencies($(filename), $(modules))

Shell. +=
    ocamldep-postproc(argv) =
        if $(defined ocamldep-postproc)
            ocamldep-postproc($(OCAMLINCLUDES_FOR_OCAMLDEP_MODULES) $(OCAMLINCLUDES), $(NATIVE_ENABLED))
        else
            OCamlScannerPostproc($(stdin))

Shell. +=
    ocamldep-postproc-preserve(argv) =
        OCAMLDEP_PRESERVE_TARGETS = true
        OCamlScannerPostproc($(stdin))

public.OCamlScanner(src_file) =
    if $(OCAMLDEP_MODULES_ENABLED)
        value $(OCAMLFIND) $(OCAMLDEP) $(LAZY_OCAMLFINDFLAGS) $(PREFIXED_OCAMLPACKS) $(OCAMLDEPFLAGS) -modules $(src_file) | ocamldep-postproc
    else
        value $(OCAMLFIND) $(OCAMLDEP) $(LAZY_OCAMLFINDFLAGS) $(PREFIXED_OCAMLPACKS) $(OCAMLDEPFLAGS) $(PREFIXED_OCAMLINCLUDES) $(src_file)

########################################################################
# Generic build rules.
#
# The order of the %.cmi rules is important.
# The most recent definition is used first, if it applies.
#    1. The .cmi is generated from the .mli, if it exists
#    2. Otherwise it is generated from the .ml
#
# In case 2, make sure to use the same command text that is used for
# generating the .cmo or .cmx file.  This will prevent the compiler
# from being called twice: once to generate the .cmi file, and again
# for the .cmo or .cmx file.
#
CompilerNotEnabledBuildError. =
        extends $(UnbuildableException)
        class CompilerNotEnabledBuildError

        new(a_target) =
                this.target = $(a_target)
                this.message = $"""Compiler to build "$(a_target)" is either not enabled or not available."""

NativeCompilerNotEnabledBuildError. =
        extends $(CompilerNotEnabledBuildError)
        class NativeCompilerNotEnabledBuildError

        new(a_target) =
                this = $(CompilerNotEnabledBuildError::new $(a_target))
                lead_in_sentence = $"""You are trying to build OCaml native-code file "$(a_target)"."""
                if $(OCAMLOPT_EXISTS)
                        this.message = $"""$(lead_in_sentence)
However, the NATIVE_ENABLED flag is not set.
Include the following definition in the relevant OMakefile:
        NATIVE_ENABLED = true"""
                        return $(this)
                else
                        this.message = $"""$(lead_in_sentence)
However, no OCaml native-code compiler (ocamlopt or ocamlopt.opt)
was found during configuration."""
                        return $(this)

ByteCompilerNotEnabledBuildError. =
        extends $(CompilerNotEnabledBuildError)
        class ByteCompilerNotEnabledBuildError

        new(a_target) =
                this = $(CompilerNotEnabledBuildError::new $(a_target))
                lead_in_sentence = $"""You are trying to build OCaml byte-code file "$(a_target)"."""
                if $(OCAMLC_EXISTS)
                        this.message = $"""$(lead_in_sentence)
However, the BYTE_ENABLED flag is not set.
Include the following definition in the relevant OMakefile:
        BYTE_ENABLED = true"""
                        return $(this)
                else
                        this.message = $"""$(lead_in_sentence)
However, no OCaml byte-code compiler (ocamlc or ocamlc.opt)
was found during configuration."""
                        return $(this)

public.OCamlC() =
    value $(OCAMLFIND) $(OCAMLC) $(LAZY_OCAMLFINDFLAGS) $(PREFIXED_OCAMLPACKS) $(OCAMLFLAGS)\
              $(OCAMLCFLAGS) $(OCAMLPPFLAGS) $(PREFIXED_OCAMLINCLUDES)

public.OCamlOpt() =
    value $(OCAMLFIND) $(OCAMLOPT) $(LAZY_OCAMLFINDFLAGS) $(PREFIXED_OCAMLPACKS) $(OCAMLFLAGS)\
              $(OCAMLOPTFLAGS) $(OCAMLPPFLAGS) $(PREFIXED_OCAMLINCLUDES)

%.cmx: %.ml
    section rule
        if $(not $(NATIVE_ENABLED))
            raise $(NativeCompilerNotEnabledBuildError.new $(file %.cmx))
        elseif $(target-exists %.mli)
            %.cmx %$(EXT_OBJ): %.ml %.cmi :scanner: scan-ocaml-%.ml
                $(OCamlOpt) -o $@ -c $<
        elseif $(BYTE_ENABLED)
            %.cmx %.cmi %$(EXT_OBJ) %.cmo: %.ml :scanner: scan-ocaml-%.ml
                $(OCamlC) -o $(replacesuffixes .cmx, .cmo, $@) -c $<
                $(OCamlOpt) -o $@ -c $<
        else
            %.cmx %.cmi %$(EXT_OBJ): %.ml :scanner: scan-ocaml-%.ml
                $(OCamlOpt) -o $@ -c $<

%$(EXT_OBJ): %.ml
    section rule
        if $(not $(NATIVE_ENABLED))
            raise $(NativeCompilerNotEnabledBuildError.new $(file %$(EXT_OBJ)))
        elseif $(target-exists %.mli)
            %$(EXT_OBJ) %.cmx: %.ml %.cmi :scanner: scan-ocaml-%.ml
                $(OCamlOpt) -o $(replacesuffixes $(EXT_OBJ), .cmx, $@) -c $<
        elseif $(BYTE_ENABLED)
            %$(EXT_OBJ) %.cmi %.cmx %.cmo: %.ml :scanner: scan-ocaml-%.ml
                $(OCamlC) -o $(replacesuffixes $(EXT_OBJ), .cmo, $@) -c $<
                $(OCamlOpt) -o $(replacesuffixes $(EXT_OBJ), .cmx, $@) -c $<
        else
            %$(EXT_OBJ) %.cmi %.cmx: %.ml :scanner: scan-ocaml-%.ml
                $(OCamlOpt) -o $(replacesuffixes $(EXT_OBJ), .cmx, $@) -c $<

# This is an experimental rule
%.i.mli: %.ml
    section rule
        %.i.mli: %.ml %.cmi :scanner: scan-ocaml-%.ml
                $(OCamlC) -i -c %.ml > $@
%.cmo: %.ml
    section rule
        if $(not $(BYTE_ENABLED))
            raise $(ByteCompilerNotEnabledBuildError.new $(file %.cmo))
        elseif $(target-exists %.mli)
            %.cmo: %.ml %.cmi :scanner: scan-ocaml-%.ml
                $(OCamlC) -o $@ -c $<
        elseif $(NATIVE_ENABLED)
            %.cmo %.cmi %.cmx %$(EXT_OBJ): %.ml :scanner: scan-ocaml-%.ml
                $(OCamlC) -o $@ -c $<
                $(OCamlOpt) -o $(replacesuffixes .cmo, .cmx, $@) -c $<
        else
            %.cmo %.cmi: %.ml :scanner: scan-ocaml-%.ml
                $(OCamlC) -o $@ -c $<

%.cmi: %.ml
    section rule
        if $(BYTE_ENABLED)
            if $(NATIVE_ENABLED)
                %.cmi %.cmo %.cmx %$(EXT_OBJ): %.ml :scanner: scan-ocaml-%.ml
                    $(OCamlC) -o $(replacesuffixes .cmi, .cmo, $@) -c $<
                    $(OCamlOpt) -o $(replacesuffixes .cmi, .cmx, $@) -c $<
            else
                %.cmi %.cmo: %.ml :scanner: scan-ocaml-%.ml
                    $(OCamlC) -o $(replacesuffixes .cmi, .cmo, $@) -c $<
        else
            %.cmi %.cmx %$(EXT_OBJ): %.ml :scanner: scan-ocaml-%.ml
                $(OCamlOpt) -o $(replacesuffixes .cmi, .cmx, $@) -c $<

%.cmi: %.mli :scanner: scan-ocaml-%.mli
    $(OCamlC) -o $@ -c $<

# \begin{doc}
# \fun{DeclareMLIOnly}
#
# Sometimes, MLI files only contain type and exception definitions. In fact,
# the MLI file could also be parsed as ML file. For convenience, it is possible
# to declare modules as MLI-only. In this case, an ML file needs not to be
# written. Do this as follows:
#
# \verb+DeclareMLIOnly(<files>)+
#
# where the \verb+<files>+ are without suffixes.
#
# Note that this really only works if the MLI file can be parsed as ML file.
# Also, it is possible this results in an object to be linked in, so don't
# forget to link the modules into the library or executable.
# \end{doc}

DeclareMLIOnly(modules) =
    foreach(mod => ..., $(modules))
        if $(BYTE_ENABLED)
            if $(NATIVE_ENABLED)
               $(mod).cmi $(mod).cmo $(mod).cmx $(mod)$(EXT_OBJ): $(mod).mli
                   $(OCamlC) -o $@ -c $(mod).mli
                   $(OCamlC) -o $(replacesuffixes .cmi, .cmo, $@) -c -impl $(mod).mli
                   $(OCamlOpt) -o $(replacesuffixes .cmi, .cmx, $@) -c -impl $(mod).mli
            else
               $(mod).cmi $(mod).cmo: $(mod).mli
                   $(OCamlC) -o $@ -c $(mod).mli
                   $(OCamlC) -o $(replacesuffixes .cmi, .cmo, $@) -c -impl $(mod).mli
        else
           $(mod).cmi $(mod).cmx $(mod)$(EXT_OBJ): $(mod).mli
               $(OCamlC) -o $@ -c $(mod).mli
               $(OCamlOpt) -o $(replacesuffixes .cmi, .cmx, $@) -c -impl $(mod).mli


########################################################################
# Parser generators
#

#
# You can choose to use ocamlyacc or menhir for a parser
# generator.  The default is ocamlyacc.  Define the
# MENHIR_ENABLED as true if you would rather use menhir.
#
# Variables:
#    MENHIR : the name of the menhir executable
#    MENHIR_FLAGS : any additional options to pass to Menhir
#    MENHIR_AVAILABLE : the menhir executable is installed
#    MENHIR_RAW_DEPEND : menhir supports the --raw-depend option
#
public.MENHIR = menhir
public.MENHIR_FLAGS =
public.MENHIR_ENABLED = false
.STATIC:
    MENHIR_AVAILABLE = $(CheckProg $(MENHIR))
    MENHIR_RAW_DEPEND = false
    if $(MENHIR_AVAILABLE)
        ConfMsgChecking(if $(MENHIR) supports the --raw-depend option)
        MENHIR_RAW_DEPEND = $(ConfMsgYesNo $(shell-success $(MENHIR) -help | grep $'^ *--raw-depend'))
        export

# Menhir is being requested.  Check that it is installed.
public.MenhirCheck() =
    if $(not $(MENHIR_AVAILABLE))
       eprintln($"""!!! You are asking to use Menhir, but it is not installed.""")
       eprintln($"""!!! See the Menhir home page for instructions on downloading.""")
       eprintln($"""!!!    http://cristal.inria.fr/~fpottier/menhir/""")
       exit(1)

# Compute the correct ocamlc and ocamldep options for Menhir.
public.MenhirOCamlcCommand() =
    MenhirCheck()
    private.ocamlc[] =\
       $(OCAMLFIND) $(OCAMLC) $(LAZY_OCAMLFINDFLAGS) $(PREFIXED_OCAMLPACKS) $(OCAMLFLAGS)\
           $(OCAMLCFLAGS) $(OCAMLPPFLAGS) $(PREFIXED_OCAMLINCLUDES)
    value --ocamlc $(quote-argv $(ocamlc))

public.MenhirOCamldepCommand(raw) =
    ocamldep =
       if $(raw)
           value $(OCAMLDEP) -modules $(OCAMLDEPFLAGS)
       else
           value $(OCAMLDEP) $(PREFIXED_OCAMLINCLUDES) $(OCAMLDEPFLAGS)
    value --ocamldep $(quote-argv $(ocamldep))

# Compute the Mendir dependency scanner command
public.MenhirScannerCommand(base, src) =
    if $(MENHIR_ENABLED)
        if $(and $(MENHIR_RAW_DEPEND), $(OCAMLDEP_MODULES_ENABLED))
            value $(MENHIR) $(MENHIR_FLAGS) $(MenhirOCamldepCommand true) $(base) --raw-depend $(src) | ocamldep-postproc-preserve
        else
            value $(MENHIR) $(MENHIR_FLAGS) $(MenhirOCamldepCommand false) $(base) --depend $(src)

# Use Menhir if MENHIR_ENABLED, ocamlyacc otherwise
public.OCamlYaccCommand(src) =
    if $(MENHIR_ENABLED)
        value $(MENHIR) $(MENHIR_FLAGS) $(MenhirOCamlcCommand) $(src)
    else
        value $(OCAMLYACC) $(OCAMLYACCFLAGS) $(src)

.SCANNER: scan-ocamlyacc-%.mly: %.mly :value: $(MENHIR_ENABLED) $(OCamlScannerTargets $&)
    $(MenhirScannerCommand $(EMPTY), $<)

%.ml %.mli: %.mly :scanner: scan-ocamlyacc-%.mly
    $(OCamlYaccCommand $<)

#
# \begin{doc}
# \subsection{Using the Menhir parser generator}
#
# Menhir is a parser generator that is mostly compatible with
# \verb+ocamlyacc+, but with many improvements.  A few of these
# are listed here (excerpted from the Menhir home page
# \url{http://cristal.inria.fr/~fpottier/menhir/}).
#
# \begin{itemize}
# \item Menhir's explanations are believed to be understandable by mere humans.
# \item Menhir allows grammar specifications to be split over multiple files.
#     It also allows several grammars to share a single set of tokens.
# \item Menhir is able to produce parsers that are parameterized by Objective Caml modules.
# \item [Added by jyh] With the \verb+--infer+ option, Menhir can typecheck the semantic actions
#    in your grammar at \emph{generation} time.
# \end{itemize}
#
# What do you need to do to use Menhir instead of \verb+ocamlyacc+?
# \begin{enumerate}
# \item Place the following definition before the relevant section of your project
#    (or at the top of your project \verb+OMakefile+ if you want to use Menhir everywhere).
#
# \begin{verbatim}
#    MENHIR_ENABLED = true
# \end{verbatim}
#
# \item Optionally, add any desired Menhir options to the \verb+MENHIR_FLAGS+ variable.
#
# \begin{verbatim}
#    MENHIR_FLAGS += --infer
# \end{verbatim}
# \end{enumerate}
#
# With this setup, any file with a \verb+.mly+ suffix will be compiled with Menhir.
#
# If your grammar is split across several files, you need to specify it explicitly,
# using the \verb+MenhirMulti+ function.
#
# \begin{verbatim}
#     MenhirMulti(target, sources)
#         target : filename, without suffix
#         sources : the files that define the grammar, without suffixes
# \end{verbatim}
#
# For example, if you want to generate the parser files \verb+parse.ml+ and \verb+parse.mli+,
# from the grammar specified in files \verb+a.mly+ and \verb+b.mly+, you would use
# the following.
#
# \begin{verbatim}
#     MenhirMulti(parse, a b)
# \end{verbatim}
# \end{doc}
#
public.MenhirMulti(target, sources) =
    MenhirCheck()
    sources = $(addsuffix .mly, $(sources))

    # Menhir needs all the files for dependency analysis
    .SCANNER: scan-menhir-$(target).multi: $(sources) :value: $(OCamlScannerTargets $&)
        $(MenhirScannerCommand --base $(target), $+)

    # Set up the actual rule
    $(target).ml $(target).mli: $(sources) :scanner: scan-menhir-$(target).multi
        $(MENHIR) $(MENHIR_FLAGS) $(MenhirOCamlcCommand) --base $(target) $+

########################################################################
# Other common generated files
#
%.ml %.mli: %.mlz
    ln-or-cp $< $*.ml
    ln-or-cp $< $*.mli

%.ml: %.mll
    $(OCAMLLEX) $(OCAMLLEXFLAGS) $<

%.ml: %.mlp %.h
    @rm -f $@
    @echo "(* CAUTION: this is a generated file.  If you edit it, all changes will be lost! *)" > $@
    $(CPP) $(OCAMLCPPFLAGS) -imacros $*.h $*.mlp >> $@
    @chmod 444 $@

#
# Generic scanners
#
OCamlScannerTargetsExtended(files) =
    files[] = $(basename $(files))
    files[] = $(if $(NATIVE_ENABLED), $(files), $(filter-out %.cmx, $(files)))
    files[] = $(if $(BYTE_ENABLED), $(files), $(filter-out %.cmo, $(files)))
    value $(find-targets-in-path-optional $(OCAMLINCLUDES), $(files)) $(NATIVE_ENABLED) $(BYTE_ENABLED)

OCamlScannerTargetsSimplified(dummy) =
    value $(NATIVE_ENABLED) $(BYTE_ENABLED)

if $(EXTENDED_DIGESTS)
    OCamlScannerTargets = $(OCamlScannerTargetsExtended)
    export
else
    OCamlScannerTargets = $(OCamlScannerTargetsSimplified)
    export

.SCANNER: scan-ocaml-%.mli: %.mli /.PHONY/OCamlGeneratedFilesTarget :value: $(OCamlScannerTargets $&)
    $(OCamlScanner $<)

.SCANNER: scan-ocaml-%.ml: %.ml /.PHONY/OCamlGeneratedFilesTarget :exists: %.mli :value: $(OCamlScannerTargets $&)
    $(OCamlScanner $<)

#
# Default .SCANNER rules for backwards-compatibility.
#
.SCANNER: %.cmi: %.mli /.PHONY/OCamlGeneratedFilesTarget :value: $(OCamlScannerTargets $&)
    $(OCamlScanner $<)

.SCANNER: %.cmx %.cmo %$(EXT_OBJ): %.ml /.PHONY/OCamlGeneratedFilesTarget :exists: %.mli :value: $(OCamlScannerTargets $&)
    $(OCamlScanner $<)

#
# Define a link order for OCaml files.
# If a file depends on a %.cmi, it also depends on %.cmo
#
.ORDER: .OCAMLLINK

.OCAMLLINK: %.cmi: %.cmo
.OCAMLLINK: %.cmx: %.cmo

public.ABORT_ON_DEPENDENCY_ERRORS = false

OCamlLinkSort(nodes) =
   if $(ABORT_ON_DEPENDENCY_ERRORS)
      value $(file-check-sort .OCAMLLINK, $(nodes))
   else
      value $(file-sort .OCAMLLINK, $(nodes))

#
# Generic rule to build an ML library
#
# \begin{doc}
# \subsection{Building OCaml programs and Libraries}
# \fun{OCamlLibrary}
#
# The \verb+OCamlLibrary+ function builds an OCaml~library.
#
# \verb+OCamlLibrary(<libname>, <files>)+
#
# The \verb+<libname>+ and \verb+<files>+ are listed \emph{without} suffixes.
#
# This function returns the list of all the targets that it defines the rules
# for (including the \verb+$(name)$(EXT_LIB)+ file when \verb+NATIVE_ENABLED+ is set).
#
# The following code builds the \verb+libfoo.cmxa+ library from the files \verb+foo.cmx+
# and \verb+bar.cmx+ (if \verb+NATIVE_ENABLED+ is set), and \verb+libfoo.cma+ from
# \verb+foo.cmo+ and \verb+bar.cmo+ (if \verb+BYTE_ENABLED+ is set).
#
# \begin{verbatim}
# OCamlLibrary(libfoo, foo bar)
# \end{verbatim}
#
# If the variable \verb+CMXS_ENABLED+ is set, additionally the cmxs~plugin
# is created.  Note that \verb+CMXS_SUPPORTED+ returns whether the compiler
# installation supports plugins, so you can simply set
#
# \begin{verbatim}
# CMXS_ENABLED = CMXS_SUPPORTED
# \end{verbatim}
#
# before calling \verb+OCamlLibrary+.  For compatibility with older omake
# versions, \verb+CMXS_ENABLED+ defaults to \verb+false+.
# \end{doc}
#
public.OCamlLibrary(library_name, ocaml_module_names) =
        # XXX: JYH: these variables should be marked private in 0.9.9
        protected.LIBRARY_NAME = $(file $(library_name))

        protected.OFILES = $(addsuffix $(EXT_OBJ), $(ocaml_module_names))
        protected.CMOFILES = $(addsuffix .cmo, $(ocaml_module_names))
        protected.CMXFILES = $(addsuffix .cmx, $(ocaml_module_names))

        protected.CLIB = $(file $(LIBRARY_NAME)$(EXT_LIB))
        protected.BYTELIB = $(file $(LIBRARY_NAME).cma)
        protected.NATIVELIB = $(file $(LIBRARY_NAME).cmxa)
        protected.SHAREDLIB = $(file $(LIBRARY_NAME).cmxs)

        #
        # Link commands
        #
        $(BYTELIB): $(CMOFILES)
                $(OCAMLFIND) $(OCAMLLINK) $(LAZY_OCAMLFINDFLAGS) $(PREFIXED_OCAMLPACKS)  \
                                          $(OCAMLFLAGS) $(OCAMLCFLAGS)  \
                                          $(OCAML_LIB_FLAGS) -a  \
                                          -o $@  \
                                          $(OCamlLinkSort $(CMOFILES))

        $(NATIVELIB) $(CLIB): $(CMXFILES) $(OFILES)
                $(OCAMLFIND) $(OCAMLOPTLINK) $(LAZY_OCAMLFINDFLAGS) $(PREFIXED_OCAMLPACKS)  \
                                             $(OCAMLFLAGS) $(OCAMLOPTFLAGS)  \
                                             $(OCAML_LIB_FLAGS) -a  \
                                             -o $(NATIVELIB)  \
                                             $(OCamlLinkSort $(CMXFILES))

        $(SHAREDLIB): $(NATIVELIB) $(CLIB)
                $(OCAMLFIND) $(OCAMLOPTLINK) -shared -cclib -L. -o $@ $(NATIVELIB)

        return $(array $(if $(NATIVE_ENABLED), $(NATIVELIB)),  \
                       $(if $(NATIVE_ENABLED), $(CLIB)),  \
                       $(if $(BYTE_ENABLED), $(BYTELIB)),  \
                       $(if $(CMXS_ENABLED), $(SHAREDLIB)))

#
# Generic rule to build an ML library that also depends on foreign (e.g. C) objects
#
# \begin{doc}
# \fun{OCamlMixedLibrary}
#
# The \verb+OCamlMixedLibrary+ function builds an OCaml~library from
# ML~files and foreign objects.
#
# \verb+OCamlMixedLibrary(<libname>, <ml-files>, <foreign-files>)+
#
# It is particularly useful if one or more ml-files contain
# \verb+external+~definitions that are satisifed by the foreign-files.
# It works similarly to \verb+OCamlLibrary+, but also
# \begin{enumerate}
#   \item adds dependencies of \verb+<foreign-files>+ to
#         \verb+<libname>+ and
#   \item appends all objects defined by \verb+<foreign-files>+ to
#         \verb+<libname>+.
# \end{enumerate}
#
# The \verb+<libname>+, \verb+<files>+, and \verb+<foreign-files>+ are
# listed \emph{without} suffixes.
# \end{doc}
#
public.OCamlMixedLibrary(library_name, ocaml_module_names, foreign_module_names) =
        protected.LIBRARY_NAME = $(file $(library_name))

        protected.OFILES = $(addsuffix $(EXT_OBJ), $(ocaml_module_names))
        protected.FOREIGN_OFILES = $(addsuffix $(EXT_OBJ), $(foreign_module_names))
        protected.CMOFILES = $(addsuffix .cmo, $(ocaml_module_names))
        protected.CMXFILES = $(addsuffix .cmx, $(ocaml_module_names))

        protected.CLIB = $(file $(LIBRARY_NAME)$(EXT_LIB))
        protected.BYTELIB = $(file $(LIBRARY_NAME).cma)
        protected.NATIVELIB = $(file $(LIBRARY_NAME).cmxa)
        protected.SHAREDLIB = $(file $(LIBRARY_NAME).cmxs)

        #
        # Link commands
        #
        $(BYTELIB): $(CMOFILES) $(FOREIGN_OFILES)
                $(OCAMLFIND) $(OCAMLLINK) $(LAZY_OCAMLFINDFLAGS) $(PREFIXED_OCAMLPACKS)  \
                                          $(OCAMLFLAGS) $(OCAMLCFLAGS)  \
                                          $(OCAML_LIB_FLAGS) -a  \
                                          -o $@  \
                                          $(OCamlLinkSort $(CMOFILES)) $(FOREIGN_OFILES)

        $(NATIVELIB) $(CLIB): $(CMXFILES) $(OFILES) $(FOREIGN_OFILES)
                $(OCAMLFIND) $(OCAMLOPTLINK) $(LAZY_OCAMLFINDFLAGS) $(PREFIXED_OCAMLPACKS)  \
                                             $(OCAMLFLAGS) $(OCAMLOPTFLAGS)  \
                                             $(OCAML_LIB_FLAGS) -a  \
                                             -o $(NATIVELIB)  \
                                             $(OCamlLinkSort $(CMXFILES)) $(FOREIGN_OFILES)

        $(SHAREDLIB): $(NATIVELIB) $(CLIB)
                $(OCAMLFIND) $(OCAMLOPTLINK) -shared -cclib -L. -o $@ $(NATIVELIB)

        return $(array $(if $(NATIVE_ENABLED), $(NATIVELIB)),  \
                       $(if $(NATIVE_ENABLED), $(CLIB)),  \
                       $(if $(BYTE_ENABLED), $(BYTELIB)),  \
                       $(if $(CMXS_ENABLED), $(SHAREDLIB)))

#
# Generic rule to build an ML package
#
# \begin{doc}
# \fun{OCamlPackage}
#
# The \verb+OCamlPackage+ function builds an OCaml package.
#
# \verb+OCamlPackage(<name>, <files>)+
#
# The \verb+<name>+ and \verb+<files>+ are listed \emph{without} suffixes.
# The \verb+<files>+ must have been compiled with the \verb+-for-pack <ident>+
# flag to the OCaml compiler.
#
# This function returns the list of all the targets that it defines the rules
# for (including the \verb+$(name)$(EXT_LIB)+ file when \verb+NATIVE_ENABLED+ is set).
#
# The following code builds the \verb+libfoo.cmx+ package from the files \verb+package.cmx+
# and \verb+bar.cmx+ (if \verb+NATIVE_ENABLED+ is set), and \verb+package.cmo+ from
# \verb+foo.cmo+ and \verb+bar.cmo+ (if \verb+BYTE_ENABLED+ is set).
#
# \begin{verbatim}
# OCamlPackage(package, foo bar)
# \end{verbatim}
# \end{doc}
#
public.OCamlPackage(name, files) =
   # XXX: JYH: these variables should be marked private in 0.9.9
   protected.OFILES   = $(addsuffix $(EXT_OBJ), $(files))
   protected.CMOFILES = $(addsuffix .cmo, $(files))
   protected.CMXFILES = $(addsuffix .cmx, $(files))

   protected.OBJ       = $(file $(name)$(EXT_OBJ))
   protected.CMO       = $(file $(name).cmo)
   protected.CMX       = $(file $(name).cmx)
   protected.CMI       = $(file $(name).cmi)
   protected.MLI       = $(file $(name).mli)

   protected.BYTE_TARGETS   = $(CMO)
   protected.NATIVE_TARGETS = $(CMX) $(OBJ)

   protected.BYTE_DEPS   = $(CMOFILES)
   protected.NATIVE_DEPS = $(CMXFILES) $(OFILES)

   # note that always returning $(CMI) is slightly incorrect, but we cannot
   # do it better
   protected.TARGETS = $(CMI)
   if $(NATIVE_ENABLED)
       TARGETS += $(NATIVE_TARGETS)
       export

   if $(BYTE_ENABLED)
       TARGETS += $(BYTE_TARGETS)
       export

   BYTE_CMD = $(OCAMLFIND) $(OCAMLC) $(LAZY_OCAMLFINDFLAGS) $(PREFIXED_OCAMLPACKS) $(OCAMLFLAGS) \
              $(OCAMLCFLAGS) $(OCAML_LIB_FLAGS) -pack -o $(CMO) $`(OCamlLinkSort $(CMOFILES))

   NATIVE_CMD = $(OCAMLFIND) $(OCAMLOPTLINK) $(LAZY_OCAMLFINDFLAGS) $(PREFIXED_OCAMLPACKS) $(OCAMLFLAGS) \
                $(OCAMLOPTFLAGS) $(OCAML_LIB_FLAGS) -pack -o $(CMX) $`(OCamlLinkSort $(CMXFILES))

   #
   # Link commands
   #
   # NB. we use here section rules because we want to evaluate target-exists
   # first when the build has started, and not NOW. The target could be
   # defined later.

   if $(and $(BYTE_ENABLED), $(NATIVE_ENABLED))
      $(BYTE_TARGETS):
          section rule
             if $(target-exists $(MLI))
                 $(BYTE_TARGETS): $(BYTE_DEPS) $(CMI)
                    $(BYTE_CMD)
             else
                 $(BYTE_TARGETS) $(CMI): $(BYTE_DEPS)
                    $(BYTE_CMD)
      $(NATIVE_TARGETS):
          section rule
             if $(target-exists $(MLI))
                 $(NATIVE_TARGETS): $(NATIVE_DEPS) $(CMI)
                    $(NATIVE_CMD)
             else
                 $(NATIVE_TARGETS): $(NATIVE_DEPS) $(CMI)
                    $(NATIVE_CMD) -intf-suffix .cmi
      $(CMI):
          section rule
             if $(target-exists $(MLI))
                 $(CMI): $(MLI)
             else
                 $(BYTE_TARGETS) $(CMI): $(BYTE_DEPS)
                    $(BYTE_CMD)
   elseif $(BYTE_ENABLED)
      $(BYTE_TARGETS):
          section rule
             if $(target-exists $(MLI))
                 $(BYTE_TARGETS): $(BYTE_DEPS) $(CMI)
                    $(BYTE_CMD)
             else
                 $(BYTE_TARGETS) $(CMI): $(BYTE_DEPS)
                    $(BYTE_CMD)
      $(CMI):
          section rule
             if $(target-exists $(MLI))
                 $(CMI): $(MLI)
             else
                 $(BYTE_TARGETS) $(CMI): $(BYTE_DEPS)
                    $(BYTE_CMD)
   elseif $(NATIVE_ENABLED)
      $(NATIVE_TARGETS):
          section rule
             if $(target-exists $(MLI))
                 $(NATIVE_TARGETS): $(NATIVE_DEPS) $(CMI)
                    $(NATIVE_CMD)
             else
                 $(NATIVE_TARGETS) $(CMI): $(NATIVE_DEPS)
                    $(NATIVE_CMD)
      $(CMI):
          section rule
             if $(target-exists $(MLI))
                 $(CMI): $(MLI)
             else
                 $(NATIVE_TARGETS) $(CMI): $(NATIVE_DEPS)
                    $(NATIVE_CMD)

   return $(TARGETS)

#
# If the interfaces are to be installed,
# define this variable to be true.
#
public.INSTALL_INTERFACES = false

#
# Install the library
#
# \begin{doc}
# \fun{OCamlLibraryCopy}
#
# The \verb+OCamlLibraryCopy+ function copies a library to an install location.
#
# \verb+OCamlLibraryCopy(<tag>, <libdir>, <libname>, <interface-files>)+
#
# The \verb+<interface-files>+ specify additional interface files
# to be copied if the \verb+INSTALL_INTERFACES+ variable is true.
# \end{doc}
#
public.OCamlLibraryCopy(tag, lib, name, ifiles) =
   #
   # Copy interfaces
   #
   if $(INSTALL_INTERFACES)
      private.MLIFILES  = $(filter-targets $(addsuffix .mli, $(ifiles)))
      private.CMIFILES  = $(addsuffix .cmi, $(ifiles))

      foreach(src => ..., $(MLIFILES) $(CMIFILES))
          $(lib)/$(basename $(src)): $(src) $(lib) :scanner: $(NOSCANNER)
              ln-or-cp $< $@

      # Add to the install tag
      $(tag): $(file $(addprefix $(lib)/, $(basename $(MLIFILES) $(CMIFILES))))

   #
   # Also install libraries
   #
   private.CLIB      = $(file $(name)$(EXT_LIB))
   private.BYTELIB   = $(file $(name).cma)
   private.NATIVELIB = $(file $(name).cmxa)

   private.LIBCLIB   = $(file $(lib)/$(name)$(EXT_LIB))
   private.LIBBYTE   = $(file $(lib)/$(name).cma)
   private.LIBNATIVE = $(file $(lib)/$(name).cmxa)

   #
   # Link libraries into lib directory
   #
   $(LIBBYTE): $(BYTELIB)
        ln-or-cp $< $@

   $(LIBNATIVE): $(NATIVELIB)
        ln-or-cp $< $@

   $(LIBCLIB): $(CLIB)
        ln-or-cp $< $@

   #
   # Add dependencies to the target tag
   #
   public.FILES[] =

   if $(BYTE_ENABLED)
      FILES[] += $(LIBBYTE)
      export

   if $(NATIVE_ENABLED)
      FILES[] +=
         $(LIBNATIVE)
         $(LIBCLIB)
      export

   $(tag): $(FILES)

   return $(FILES)

#
# We often use them together
#
# \begin{doc}
# \fun{OCamlLibraryInstall}
#
# The \verb+OCamlLibraryInstall+ function builds a library
# and copies it to an install location in one step.
#
# \verb+OCamlLibraryInstall(<tag>, <libdir>, <libname>, <files>)+
# \end{doc}
#
public.OCamlLibraryInstall(tag, lib, name, files) =
    OCamlLibrary($(name), $(files))
    return $(OCamlLibraryCopy $(tag), $(lib), $(name), $(files))

#
# Generic rule to build an OCaml program
#    name: the name of the target, without a suffix
#    files: names of the object files, without suffixes
#
# Other variables:
#    OCAML_LIBS: OCaml libraries target depends on, without suffix
#    OCAML_CLIBS: C libraries we depend on, without suffix
#    OCAML_OTHER_LIBS: OCaml libraries, without dependencies, without suffix
#    OCAML_BYTE_LINK_FLAGS: additional flags for byte compiler
#    OCAML_NATIVE_LINK_FLAGS: additional flags for native-code compiler
#    OCAML_LINK_FLAGS: general additional options (usually the -cclib options)
#
# \begin{doc}
# \fun{OCamlProgram}
#
# The \verb+OCamlProgram+~function builds an OCaml~program.  It
# returns the array with all the targets for which it has defined the
# rules (\verb+$(name)$(EXE)+ and \verb+$(name).run+ and/or
# \verb+$(name).opt+, depending on the \verb+NATIVE_ENABLED+ and
# \verb+BYTE_ENABLED+ variables).
#
# \verb+OCamlProgram(<name>, <files>)+
#
# Additional variables used:
# \begin{description}
# \item[\hypervarxn{OCAML_LIBS}{OCAML\_LIBS}] Additional libraries
#    passed to the linker, without suffix.  These files
#    become dependencies of the target program.
# \item[\hypervarxn{OCAML_OTHER_LIBS}{OCAML\_OTHER\_LIBS}] Additional
#    libraries passed to the linker, without suffix.  These
#    files do \emph{not} become dependencies of the target program.
# \item[\hypervarxn{OCAML_CLIBS}{OCAML\_CLIBS}] C~libraries to pass to
#    the linker.
# \item[\hypervarxn{OCAML_BYTE_LINK_FLAGS}{OCAML\_BYTE\_LINK\_FLAGS}] Flags
#    to pass to the bytecode~linker.
# \item[\hypervarxn{OCAML_NATIVE_LINK_FLAGS}{OCAML\_NATIVE\_LINK\_FLAGS}] Flags
#    to pass to the native-code~linker.
# \item[\hypervarxn{OCAML_LINK_FLAGS}{OCAML\_LINK\_FLAGS}] Flags to
#    pass to both linkers.
# \end{description}
# \end{doc}
#
public.OCamlProgram(program_name, ocaml_module_names) =
        # XXX: JYH: these variables should be marked private in 0.9.9
        protected.PROGRAM_NAME = $(file $(program_name))

        protected.CMOFILES = $(addsuffix .cmo, $(ocaml_module_names))
        protected.CMXFILES = $(addsuffix .cmx, $(ocaml_module_names))
        protected.OFILES = $(addsuffix $(EXT_OBJ), $(ocaml_module_names))

        protected.CMAFILES = $(addsuffix .cma,  $(OCAML_LIBS))
        protected.CMXAFILES = $(addsuffix .cmxa, $(OCAML_LIBS))
        protected.ARFILES = $(addsuffix $(EXT_LIB), $(OCAML_LIBS))
        protected.CMA_OTHER_FILES = $(addsuffix .cma, $(OCAML_OTHER_LIBS))
        protected.CMXA_OTHER_FILES = $(addsuffix .cmxa, $(OCAML_OTHER_LIBS))

        protected.CLIBS = $(addsuffix $(EXT_LIB), $(OCAML_CLIBS))

        protected.PROGRAM = $(file $(PROGRAM_NAME)$(EXE))
        protected.BYTEPROGRAM = $(file $(PROGRAM_NAME).run)
        protected.OPTPROGRAM = $(file $(PROGRAM_NAME).opt)

        #
        # Rules to build byte-code and native targets
        #
        $(BYTEPROGRAM): $(CMAFILES) $(CMOFILES) $(CLIBS)
                $(OCAMLFIND) $(OCAMLLINK) $(LAZY_OCAMLFINDFLAGS) $(PREFIXED_OCAMLPACKS)  \
                                          $(OCAMLFLAGS) $(OCAMLCFLAGS)  \
                                          $(PREFIXED_OCAMLINCLUDES) $(OCAML_BYTE_LINK_FLAGS) \
                                          -o $@  \
                                          $(CMA_OTHER_FILES) $(CMAFILES)  \
                                          $(OCamlLinkSort $(CMOFILES)) \
                                          $(CLIBS) $(OCAML_LINK_FLAGS)

        $(OPTPROGRAM): $(CMXAFILES) $(ARFILES) $(CMXFILES) $(OFILES) $(CLIBS)
                $(OCAMLFIND) $(OCAMLOPTLINK) $(LAZY_OCAMLFINDFLAGS) $(PREFIXED_OCAMLPACKS)  \
                                             $(OCAMLFLAGS) $(OCAMLOPTFLAGS)  \
                                             $(PREFIXED_OCAMLINCLUDES) $(OCAML_NATIVE_LINK_FLAGS)  \
                                             -o $@  \
                                             $(CMXA_OTHER_FILES) $(CMXAFILES)  \
                                             $(OCamlLinkSort $(CMXFILES))  \
                                             $(CLIBS) $(OCAML_LINK_FLAGS)

        #
        # Link the actual executables.
        # Always prefer native executables.
        #
        if $(NATIVE_ENABLED)
             $(PROGRAM): $(OPTPROGRAM)
                 ln-or-cp $< $@
        else
             $(PROGRAM): $(BYTEPROGRAM)
                 ln-or-cp $< $@

        return $(array $(PROGRAM),  \
                       $(if $(NATIVE_ENABLED), $(OPTPROGRAM)),  \
                       $(if $(BYTE_ENABLED), $(BYTEPROGRAM)))

#
# Generic rule to build an OCaml program that also depends on foreign (e.g. C) objects
#
# \begin{doc}
# \fun{OCamlMixedProgram}
#
# The \verb+OCamlMixedProgram+ function builds an OCaml~program from
# ML files and foreign objects.
#
# \verb+OCamlMixedProgram(<programname>, <ml-files>, <foreign-files>)+
#
# It is particularly useful if one or more ml-files contain
# \verb+external+~definitions that are satisifed by the foreign-files.
# It works similarly to \verb+OCamlProgram+, but also
# \begin{enumerate}
#   \item adds dependencies of \verb+<foreign-files>+ to
#         \verb+<programname>+ and
#   \item appends all objects defined by \verb+<foreign-files>+ to
#         \verb+<programname>+.
# \end{enumerate}
#
# The \verb+<programname>+, \verb+<files>+, and \verb+<foreign-files>+ are
# listed \emph{without} suffixes.
# \end{doc}
#
public.OCamlMixedProgram(program_name, ocaml_module_names, foreign_module_names) =
        # XXX: JYH: these variables should be marked private in 0.9.9
        protected.PROGRAM_NAME = $(file $(program_name))

        protected.CMOFILES = $(addsuffix .cmo, $(ocaml_module_names))
        protected.CMXFILES = $(addsuffix .cmx, $(ocaml_module_names))
        protected.OFILES = $(addsuffix $(EXT_OBJ), $(ocaml_module_names))
        protected.FOREIGN_OFILES = $(addsuffix $(EXT_OBJ), $(foreign_module_names))

        protected.CMAFILES = $(addsuffix .cma,  $(OCAML_LIBS))
        protected.CMXAFILES = $(addsuffix .cmxa, $(OCAML_LIBS))
        protected.ARFILES = $(addsuffix $(EXT_LIB), $(OCAML_LIBS))
        protected.CMA_OTHER_FILES = $(addsuffix .cma, $(OCAML_OTHER_LIBS))
        protected.CMXA_OTHER_FILES = $(addsuffix .cmxa, $(OCAML_OTHER_LIBS))

        protected.CLIBS = $(addsuffix $(EXT_LIB), $(OCAML_CLIBS))

        protected.PROGRAM = $(file $(PROGRAM_NAME)$(EXE))
        protected.BYTEPROGRAM = $(file $(PROGRAM_NAME).run)
        protected.OPTPROGRAM = $(file $(PROGRAM_NAME).opt)

        #
        # Rules to build byte-code and native targets
        #
        $(BYTEPROGRAM): $(CMAFILES) $(CMOFILES) $(CLIBS) $(FOREIGN_OFILES)
                $(OCAMLFIND) $(OCAMLLINK) $(LAZY_OCAMLFINDFLAGS) $(PREFIXED_OCAMLPACKS)  \
                                          $(OCAMLFLAGS) $(OCAMLCFLAGS)  \
                                          $(PREFIXED_OCAMLINCLUDES) $(OCAML_BYTE_LINK_FLAGS) \
                                          -o $@  \
                                          $(CMA_OTHER_FILES) $(CMAFILES)  \
                                          $(OCamlLinkSort $(CMOFILES)) $(FOREIGN_OFILES)  \
                                          $(CLIBS) $(OCAML_LINK_FLAGS)

        $(OPTPROGRAM): $(CMXAFILES) $(ARFILES) $(CMXFILES) $(OFILES) $(CLIBS) $(FOREIGN_OFILES)
                $(OCAMLFIND) $(OCAMLOPTLINK) $(LAZY_OCAMLFINDFLAGS) $(PREFIXED_OCAMLPACKS)  \
                                             $(OCAMLFLAGS) $(OCAMLOPTFLAGS)  \
                                             $(PREFIXED_OCAMLINCLUDES) $(OCAML_NATIVE_LINK_FLAGS)  \
                                             -o $@  \
                                             $(CMXA_OTHER_FILES) $(CMXAFILES)  \
                                             $(OCamlLinkSort $(CMXFILES)) $(FOREIGN_OFILES)  \
                                             $(CLIBS) $(OCAML_LINK_FLAGS)

        #
        # Link the actual executables.
        # Always prefer native executables.
        #
        if $(NATIVE_ENABLED)
             $(PROGRAM): $(OPTPROGRAM)
                 ln-or-cp $< $@
        else
             $(PROGRAM): $(BYTEPROGRAM)
                 ln-or-cp $< $@

        return $(array $(PROGRAM),  \
                       $(if $(NATIVE_ENABLED), $(OPTPROGRAM)),  \
                       $(if $(BYTE_ENABLED), $(BYTEPROGRAM)))

#
# Copy to $(BIN) directory
#
# \begin{doc}
# \fun{OCamlProgramCopy}
#
# The \verb+OCamlProgramCopy+ function copies an OCaml program to an install location.
#
# \verb+OCamlProgramCopy(<tag>, <bindir>, <name>)+
#
# Additional variables used:
# \begin{description}
# \item[NATIVE\_ENABLED]  If the \hypervarx{NATIVE_ENABLED}{NATIVE\_ENABLED} is set, the native-code executable
#    is copied; otherwise the byte-code executable is copied.
# \end{description}
# \end{doc}
#
public.OCamlProgramCopy(tag, bin, name) =
   private.name = $(file $(name))

   private.BYTEPROG = $(file $(name).run)
   private.OPTPROG  = $(file $(name).opt)

   private.SRCNAME  = $(if $(NATIVE_ENABLED), $(OPTPROG), $(BYTEPROG))
   private.BINNAME  = $(file $(bin)/$(basename $(name))$(EXE))

   #
   # Link the actual executables.
   # Always prefer native executables.
   #
   $(BINNAME): $(SRCNAME) $(bin)
      ln-or-cp $< $@

   # Add to phony tag.
   $(tag): $(BINNAME)

   return $(BINNAME)

#
# We often use them together
#
# \begin{doc}
# \fun{OCamlProgramInstall}
#
# The \verb+OCamlProgramInstall+ function builds a programs and copies it to
# an install location in one step.
#
# \verb+OCamlProgramInstall(<tag>, <bindir>, <name>, <files>)+
# \end{doc}
#
public.OCamlProgramInstall(tag, bin, name, files) =
    OCamlProgram($(name), $(files))
    return $(OCamlProgramCopy $(tag), $(bin), $(name))

# vim:tw=100:fo=tcq:
