View source with raw comments or as raw
    1/*  Part of SWI-Prolog
    2
    3    Author:        Jan Wielemaker
    4    E-mail:        jan@swi-prolog.org
    5    WWW:           http://www.swi-prolog.org
    6    Copyright (c)  2021, SWI-Prolog Solutions b.v.
    7    All rights reserved.
    8
    9    Redistribution and use in source and binary forms, with or without
   10    modification, are permitted provided that the following conditions
   11    are met:
   12
   13    1. Redistributions of source code must retain the above copyright
   14       notice, this list of conditions and the following disclaimer.
   15
   16    2. Redistributions in binary form must reproduce the above copyright
   17       notice, this list of conditions and the following disclaimer in
   18       the documentation and/or other materials provided with the
   19       distribution.
   20
   21    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   22    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   23    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   24    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   25    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   26    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   27    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   28    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   29    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   30    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   31    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   32    POSSIBILITY OF SUCH DAMAGE.
   33*/
   34
   35:- module(build_tools,
   36          [ build_steps/3,              % +Steps, +SrcDir, +Options
   37            prolog_install_prefix/1,    % -Prefix
   38            run_process/3,              % +Executable, +Argv, +Options
   39            has_program/3,              % +Spec, -Path, +Env
   40            path_sep/1,                 % -Separator
   41            ensure_build_dir/3          % +Dir, +State0, -State
   42          ]).   43:- autoload(library(lists), [selectchk/3, member/2, append/3, last/2]).   44:- autoload(library(option), [option/2, option/3, dict_options/2]).   45:- autoload(library(pairs), [pairs_values/2]).   46:- autoload(library(process), [process_create/3, process_wait/2]).   47:- autoload(library(readutil), [read_stream_to_codes/3]).   48:- autoload(library(dcg/basics), [string/3]).   49:- autoload(library(apply), [foldl/4, maplist/2]).   50:- autoload(library(filesex), [directory_file_path/3, make_directory_path/1]).   51:- autoload(library(prolog_config), [apple_bundle_libdir/1]).   52:- autoload(library(solution_sequences), [distinct/2]).   53
   54% The plugins.  Load them in the order of preference.
   55:- use_module(conan).   56:- use_module(cmake).   57:- use_module(make).   58
   59:- multifile
   60    prolog:build_file/2,                % ?File, ?Toolchain
   61    prolog:build_step/4,                % ?Step, ?Tool, ?SrcDir, ?BuildDir
   62    prolog:build_environment/2,         % ?Name, ?Value
   63    prolog_pack:environment/2.          % ?Name, ?Value (backward compatibility)

Utilities for building foreign resources

This module implements the build system that is used by pack_install/1 and pack_rebuild/1. The build system is a plugin based system where each plugin knows about a specific build toolchain. The plugins recognise whether they are applicable based on the existence of files that are unique to the toolchain. Currently it supports

 build_steps(+Steps:list, SrcDir:atom, +Options) is det
Run the desired build steps. Normally, Steps is the list below, optionally prefixed with distclean or clean. [test] may be omited if --no-test is effective.
[[dependencies], [configure], build, [test], install]

Each step finds an applicable toolchain based on known unique files and calls the matching plugin to perform the step. A step may fail, which causes the system to try an alternative. A step that wants to abort the build process must throw an exception.

To be done
- If no tool is willing to execute some step, the step is skipped. This is ok for some steps such as dependencies or test. Possibly we should force the install step to succeed?
   96build_steps(Steps, SrcDir, Options) :-
   97    dict_options(Dict0, Options),
   98    setup_path,
   99    build_environment(BuildEnv, Options),
  100    State0 = Dict0.put(#{ env: BuildEnv,
  101                          src_dir: SrcDir
  102                        }),
  103    foldl(build_step, Steps, State0, _State).
  104
  105build_step(Spec, State0, State) :-
  106    step_name(Spec, Step),
  107    prolog:build_file(File, Tool),
  108    directory_file_path(State0.src_dir, File, Path),
  109    exists_file(Path),
  110    prolog:build_step(Step, Tool, State0, State),
  111    post_step(Step, Tool, State),
  112    !.
  113build_step([_], State, State) :-
  114    !.
  115build_step(Step, State, State) :-
  116    print_message(warning, build(step_failed(Step))).
  117
  118step_name([Step], Step) :-              % options
  119    !.
  120step_name(Step, Step).
  121
  122post_step(configure, _, State) :-
  123    !,
  124    save_build_environment(State.bin_dir, State.env).
  125post_step(_, _, _).
 ensure_build_dir(+Dir, +State0, -State) is det
Create the build directory. Dir is normally either '.' to build in the source directory or build to create a build subdir.
  133ensure_build_dir(_, State0, State) :-
  134    _ = State0.get(bin_dir),
  135    !,
  136    State = State0.
  137ensure_build_dir(., State0, State) :-
  138    !,
  139    State = State0.put(bin_dir, State0.src_dir).
  140ensure_build_dir(Dir, State0, State) :-
  141    directory_file_path(State0.src_dir, Dir, BinDir),
  142    make_directory_path(BinDir),
  143    !,
  144    State = State0.put(bin_dir, BinDir).
  145
  146
  147		 /*******************************
  148		 *          ENVIRONMENT		*
  149		 *******************************/
 build_environment(-Env, +Options) is det
Assemble a clean build environment for creating extensions to SWI-Prolog. Env is a list of Var=Value pairs. The variable names depend on the pack_version(Version) term from pack.pl. When absent or 1, the old names are used. These names are confusing and conflict with some build environments. Using 2 (or later), the new names are used. The list below first names the new name and than between parenthesis, the new name. Provided variables are:
PATH
contains the environment path with the directory holding the currently running SWI-Prolog instance prepended in front of it. As a result, swipl is always present and runs the same SWI-Prolog instance as the current Prolog process.
SWIPL
contains the absolute file name of the running executable.
SWIPL_PACK_VERSION
Version of the pack system (1 or 2)
SWIPL_VERSION (SWIPLVERSION)
contains the numeric SWI-Prolog version defined as Major*10000+Minor*100+Patch.
SWIPL_HOME_DIR (SWIHOME)
contains the directory holding the SWI-Prolog home.
SWIPL_ARCH (SWIARCH)
contains the machine architecture identifier.
SWIPL_MODULE_DIR (PACKSODIR)
constains the destination directory for shared objects/DLLs relative to a Prolog pack, i.e., lib/$SWIARCH.
SWIPL_MODULE_LIB (SWISOLIB)
The SWI-Prolog library or an empty string when it is not required to link modules against this library (e.g., ELF systems)
SWIPL_LIB (SWILIB)
The SWI-Prolog library we need to link to for programs that embed SWI-Prolog (normally -lswipl).
SWIPL_INCLUDE_DIRS
CMake style variable that contains the directory holding SWI-Prolog.h, SWI-Stream.h and SWI-cpp.h.
SWIPL_LIBRARIES_DIR
CMake style variable that contains the directory holding libswipl
SWIPL_CC (CC)
Prefered C compiler
SWIPL_LD (LD)
Prefered linker
SWIPL_CFLAGS (CLFLAGS)
C-Flags for building extensions. Always contains -ISWIPL-INCLUDE-DIR.
SWIPL_MODULE_LDFLAGS (LDSOFLAGS)
Link flags for linking modules.
SWIPL_MODULE_EXT (SOEXT)
File name extension for modules (e.g., so or dll)
SWIPL_PREFIX (PREFIX)
Install prefix for global binaries, libraries and include files.

In addition, several environment variables are passes verbatim: TMP, TEMP, USER, HOME, LANG, CC, CXX, LD, CFLAGS, CXXFLAGS and LDFLAGS are passed verbatim unless redefined for version 1 packs as described above.

  210build_environment(Env, Options) :-
  211    findall(Name=Value,
  212            distinct(Name, user_environment(Name, Value)),
  213            UserEnv),
  214    findall(Name=Value,
  215            ( def_environment(Name, Value, Options),
  216              \+ memberchk(Name=_, UserEnv)
  217            ),
  218            DefEnv),
  219    append(UserEnv, DefEnv, Env).
  220
  221user_environment(Name, Value) :-
  222    prolog:build_environment(Name, Value).
  223user_environment(Name, Value) :-
  224    prolog_pack:environment(Name, Value).
 prolog:build_environment(-Name, -Value) is nondet
Hook to define the environment for building packs. This Multifile hook extends the process environment for building foreign extensions. A value provided by this hook overrules defaults provided by def_environment/3. In addition to changing the environment, this may be used to pass additional values to the environment, as in:
prolog:build_environment('USER', User) :-
    getenv('USER', User).
Arguments:
Name- is an atom denoting a valid variable name
Value- is either an atom or number representing the value of the variable.
 def_environment(-Name, -Value, +Options) is nondet
True if Name=Value must appear in the environment for building foreign extensions.
  250def_environment('PATH', Value, _) :-
  251    getenv('PATH', PATH),
  252    current_prolog_flag(executable, Exe),
  253    file_directory_name(Exe, ExeDir),
  254    prolog_to_os_filename(ExeDir, OsExeDir),
  255    path_sep(Sep),
  256    atomic_list_concat([OsExeDir, Sep, PATH], Value).
  257def_environment('SWIPL', Value, _) :-
  258    current_prolog_flag(executable, Value).
  259def_environment('SWIPL_PACK_VERSION', Value, Options) :-
  260    option(pack_version(Value), Options, 1).
  261def_environment(VAR, Value, Options) :-
  262    env_name(version, VAR, Options),
  263    current_prolog_flag(version, Value).
  264def_environment(VAR, Value, Options) :-
  265    env_name(home, VAR, Options),
  266    current_prolog_flag(home, Value).
  267def_environment(VAR, Value, Options) :-
  268    env_name(arch, VAR, Options),
  269    current_prolog_flag(arch, Value).
  270def_environment(VAR, Value, Options) :-
  271    env_name(module_dir, VAR, Options),
  272    current_prolog_flag(arch, Arch),
  273    atom_concat('lib/', Arch, Value).
  274def_environment(VAR, Value, Options) :-
  275    env_name(module_lib, VAR, Options),
  276    current_prolog_flag(c_libplso, Value).
  277def_environment(VAR, '-lswipl', Options) :-
  278    env_name(lib, VAR, Options).
  279def_environment(VAR, Value, Options) :-
  280    env_name(cc, VAR, Options),
  281    (   getenv('CC', Value)
  282    ->  true
  283    ;   default_c_compiler(Value)
  284    ->  true
  285    ;   current_prolog_flag(c_cc, Value)
  286    ).
  287def_environment(VAR, Value, Options) :-
  288    env_name(ld, VAR, Options),
  289    (   getenv('LD', Value)
  290    ->  true
  291    ;   current_prolog_flag(c_cc, Value)
  292    ).
  293def_environment('SWIPL_INCLUDE_DIRS', Value, _) :- % CMake style environment
  294    current_prolog_flag(home, Home),
  295    atom_concat(Home, '/include', Value).
  296def_environment('SWIPL_LIBRARIES_DIR', Value, _) :-
  297    swipl_libraries_dir(Value).
  298def_environment(VAR, Value, Options) :-
  299    env_name(cflags, VAR, Options),
  300    (   getenv('CFLAGS', SystemFlags)
  301    ->  Extra = [' ', SystemFlags]
  302    ;   Extra = []
  303    ),
  304    current_prolog_flag(c_cflags, Value0),
  305    current_prolog_flag(home, Home),
  306    atomic_list_concat([Value0, ' -I"', Home, '/include"' | Extra], Value).
  307def_environment(VAR, Value, Options) :-
  308    env_name(module_ldflags, VAR, Options),
  309    (   getenv('LDFLAGS', SystemFlags)
  310    ->  Extra = [SystemFlags|System]
  311    ;   Extra = System
  312    ),
  313    (   current_prolog_flag(windows, true)
  314    ->  current_prolog_flag(home, Home),
  315        atomic_list_concat(['-L"', Home, '/bin"'], SystemLib),
  316        System = [SystemLib]
  317    ;   apple_bundle_libdir(LibDir)
  318    ->  atomic_list_concat(['-L"', LibDir, '"'], SystemLib),
  319        System = [SystemLib]
  320    ;   current_prolog_flag(c_libplso, '')
  321    ->  System = []                 % ELF systems do not need this
  322    ;   prolog_library_dir(SystemLibDir),
  323        atomic_list_concat(['-L"',SystemLibDir,'"'], SystemLib),
  324        System = [SystemLib]
  325    ),
  326    current_prolog_flag(c_ldflags, LDFlags),
  327    atomic_list_concat([LDFlags, '-shared' | Extra], ' ', Value).
  328def_environment(VAR, Value, Options) :-
  329    env_name(module_ext, VAR, Options),
  330    current_prolog_flag(shared_object_extension, Value).
  331def_environment('PREFIX', Value, _) :-
  332    prolog_install_prefix(Value).
  333def_environment(Pass, Value, _) :-
  334    pass_env(Pass),
  335    getenv(Pass, Value).
  336
  337swipl_libraries_dir(Dir) :-
  338    current_prolog_flag(windows, true),
  339    !,
  340    current_prolog_flag(home, Home),
  341    atom_concat(Home, '/bin', Dir).
  342swipl_libraries_dir(Dir) :-
  343    apple_bundle_libdir(Dir),
  344    !.
  345swipl_libraries_dir(Dir) :-
  346    prolog_library_dir(Dir).
  347
  348pass_env('TMP').
  349pass_env('TEMP').
  350pass_env('USER').
  351pass_env('HOME').
  352pass_env('LANG').
  353pass_env('CC').
  354pass_env('CXX').
  355pass_env('LD').
  356pass_env('CFLAGS').
  357pass_env('CXXFLAGS').
  358pass_env('LDFLAGS').
  359
  360env_name(Id, Name, Options) :-
  361    option(pack_version(V), Options, 1),
  362    must_be(oneof([1,2]), V),
  363    env_name_v(Id, V, Name).
  364
  365env_name_v(version,        1, 'SWIPLVERSION').
  366env_name_v(version,        2, 'SWIPL_VERSION').
  367env_name_v(home,           1, 'SWIHOME').
  368env_name_v(home,           2, 'SWIPL_HOME_DIR').
  369env_name_v(module_dir,     1, 'PACKSODIR').
  370env_name_v(module_dir,     2, 'SWIPL_MODULE_DIR').
  371env_name_v(module_lib,     1, 'SWISOLIB').
  372env_name_v(module_lib,     2, 'SWIPL_MODULE_LIB').
  373env_name_v(lib,            1, 'SWILIB').
  374env_name_v(lib,            2, 'SWIPL_LIB').
  375env_name_v(arch,           1, 'SWIARCH').
  376env_name_v(arch,           2, 'SWIPL_ARCH').
  377env_name_v(cc,             1, 'CC').
  378env_name_v(cc,             2, 'SWIPL_CC').
  379env_name_v(ld,             1, 'LD').
  380env_name_v(ld,             2, 'SWIPL_LD').
  381env_name_v(cflags,         1, 'CFLAGS').
  382env_name_v(cflags,         2, 'SWIPL_CFLAGS').
  383env_name_v(module_ldflags, 1, 'LDSOFLAGS').
  384env_name_v(module_ldflags, 2, 'SWIPL_MODULE_LDFLAGS').
  385env_name_v(module_ext,     1, 'SOEXT').
  386env_name_v(module_ext,     2, 'SWIPL_MODULE_EXT').
  387env_name_v(prefix,         1, 'PREFIX').
  388env_name_v(prefix,         2, 'SWIPL_PREFIX').
 prolog_library_dir(-Dir) is det
True when Dir is the directory holding libswipl.$SOEXT
  394:- multifile
  395    prolog:runtime_config/2.  396
  397prolog_library_dir(Dir) :-
  398    prolog:runtime_config(c_libdir, Dir),
  399    !.
  400prolog_library_dir(Dir) :-
  401    current_prolog_flag(home, Home),
  402    (   current_prolog_flag(c_libdir, Rel)
  403    ->  atomic_list_concat([Home, Rel], /, Dir)
  404    ;   current_prolog_flag(arch, Arch)
  405    ->  atomic_list_concat([Home, lib, Arch], /, Dir)
  406    ).
 default_c_compiler(-CC) is semidet
Try to find a suitable C compiler for compiling packages with foreign code.
To be done
- Needs proper defaults for Windows. Find MinGW? Find MSVC?
  415default_c_compiler(CC) :-
  416    preferred_c_compiler(CC),
  417    has_program(path(CC), _),
  418    !.
  419
  420preferred_c_compiler(gcc).
  421preferred_c_compiler(clang).
  422preferred_c_compiler(cc).
 save_build_environment(+BuildDir, +Env) is det
Create a shell-script buildenv.sh that contains the build environment. This may be sourced in the build directory to run the build steps outside Prolog. It may also be useful for debugging purposes.
  431save_build_environment(BuildDir, Env) :-
  432    directory_file_path(BuildDir, 'buildenv.sh', EnvFile),
  433    setup_call_cleanup(
  434        open(EnvFile, write, Out),
  435        write_env_script(Out, Env),
  436        close(Out)).
  437
  438write_env_script(Out, Env) :-
  439    format(Out,
  440           '# This file contains the environment that can be used to\n\c
  441            # build the foreign pack outside Prolog.  This file must\n\c
  442            # be loaded into a bourne-compatible shell using\n\c
  443            #\n\c
  444            #   $ source buildenv.sh\n\n',
  445           []),
  446    forall(member(Var=Value, Env),
  447           format(Out, '~w=\'~w\'\n', [Var, Value])),
  448    format(Out, '\nexport ', []),
  449    forall(member(Var=_, Env),
  450           format(Out, ' ~w', [Var])),
  451    format(Out, '\n', []).
 prolog_install_prefix(-Prefix) is semidet
Return the directory that can be passed into configure or cmake to install executables and other related resources in a similar location as SWI-Prolog itself. Tries these rules:
  1. If the Prolog flag pack_prefix at a writable directory, use this.
  2. If the current executable can be found on $PATH and the parent of the directory of the executable is writable, use this.
  3. If the user has a writable ~/bin directory, use ~.
  465prolog_install_prefix(Prefix) :-
  466    current_prolog_flag(pack_prefix, Prefix),
  467    access_file(Prefix, write),
  468    !.
  469prolog_install_prefix(Prefix) :-
  470    current_prolog_flag(os_argv, [Name|_]),
  471    has_program(path(Name), EXE),
  472    file_directory_name(EXE, Bin),
  473    file_directory_name(Bin, Prefix0),
  474    (   local_prefix(Prefix0, Prefix1)
  475    ->  Prefix = Prefix1
  476    ;   Prefix = Prefix0
  477    ),
  478    access_file(Prefix, write),
  479    !.
  480prolog_install_prefix(Prefix) :-
  481    expand_file_name(~, [UserHome]),
  482    directory_file_path(UserHome, bin, BinDir),
  483    exists_directory(BinDir),
  484    access_file(BinDir, write),
  485    !,
  486    Prefix = UserHome.
  487
  488local_prefix('/usr', '/usr/local').
  489
  490
  491                 /*******************************
  492                 *          RUN PROCESSES       *
  493                 *******************************/
 run_process(+Executable, +Argv, +Options) is det
Run Executable. Defined options:
directory(+Dir)
Execute in the given directory
output(-Out)
Unify Out with a list of codes representing stdout of the command. Otherwise the output is handed to print_message/2 with level informational.
error(-Error)
As output(Out), but messages are printed at level error.
env(+Environment)
Environment passed to the new process.

If Executable is path(Program) and we have an environment we make sure to use the PATH from this environment for searching Program.

  514run_process(path(Exe), Argv, Options) :-
  515    option(env(BuildEnv), Options),
  516    !,
  517    setup_call_cleanup(
  518        b_setval('$build_tool_env', BuildEnv),
  519        run_process(pack_build_path(Exe), Argv, Options),
  520        nb_delete('$build_tool_env')).
  521run_process(Executable, Argv, Options) :-
  522    \+ option(output(_), Options),
  523    \+ option(error(_), Options),
  524    current_prolog_flag(unix, true),
  525    current_prolog_flag(threads, true),
  526    !,
  527    process_create_options(Options, Extra),
  528    process_create(Executable, Argv,
  529                   [ stdout(pipe(Out)),
  530                     stderr(pipe(Error)),
  531                     process(PID)
  532                   | Extra
  533                   ]),
  534    thread_create(relay_output([output-Out, error-Error]), Id, []),
  535    process_wait(PID, Status),
  536    thread_join(Id, _),
  537    (   Status == exit(0)
  538    ->  true
  539    ;   throw(error(process_error(process(Executable, Argv), Status), _))
  540    ).
  541run_process(Executable, Argv, Options) :-
  542    process_create_options(Options, Extra),
  543    setup_call_cleanup(
  544        process_create(Executable, Argv,
  545                       [ stdout(pipe(Out)),
  546                         stderr(pipe(Error)),
  547                         process(PID)
  548                       | Extra
  549                       ]),
  550        (   read_stream_to_codes(Out, OutCodes, []),
  551            read_stream_to_codes(Error, ErrorCodes, []),
  552            process_wait(PID, Status)
  553        ),
  554        (   close(Out),
  555            close(Error)
  556        )),
  557    print_error(ErrorCodes, Options),
  558    print_output(OutCodes, Options),
  559    (   Status == exit(0)
  560    ->  true
  561    ;   throw(error(process_error(process(Executable, Argv), Status), _))
  562    ).
  563
  564process_create_options(Options, Extra) :-
  565    option(directory(Dir), Options, .),
  566    (   option(env(Env), Options)
  567    ->  Extra = [cwd(Dir), env(Env)]
  568    ;   Extra = [cwd(Dir)]
  569    ).
  570
  571relay_output([]) :- !.
  572relay_output(Output) :-
  573    pairs_values(Output, Streams),
  574    wait_for_input(Streams, Ready, infinite),
  575    relay(Ready, Output, NewOutputs),
  576    relay_output(NewOutputs).
  577
  578relay([], Outputs, Outputs).
  579relay([H|T], Outputs0, Outputs) :-
  580    selectchk(Type-H, Outputs0, Outputs1),
  581    (   at_end_of_stream(H)
  582    ->  close(H),
  583        relay(T, Outputs1, Outputs)
  584    ;   read_pending_codes(H, Codes, []),
  585        relay(Type, Codes),
  586        relay(T, Outputs0, Outputs)
  587    ).
  588
  589relay(error,  Codes) :-
  590    set_prolog_flag(message_context, []),
  591    print_error(Codes, []).
  592relay(output, Codes) :-
  593    print_output(Codes, []).
  594
  595print_output(OutCodes, Options) :-
  596    option(output(Codes), Options),
  597    !,
  598    Codes = OutCodes.
  599print_output(OutCodes, _) :-
  600    print_message(informational, build(process_output(OutCodes))).
  601
  602print_error(OutCodes, Options) :-
  603    option(error(Codes), Options),
  604    !,
  605    Codes = OutCodes.
  606print_error(OutCodes, _) :-
  607    phrase(classify_message(Level), OutCodes, _),
  608    print_message(Level, build(process_output(OutCodes))).
  609
  610classify_message(error) -->
  611    string(_), "fatal:",
  612    !.
  613classify_message(error) -->
  614    string(_), "error:",
  615    !.
  616classify_message(warning) -->
  617    string(_), "warning:",
  618    !.
  619classify_message(informational) -->
  620    [].
  621
  622
  623:- multifile user:file_search_path/2.  624user:file_search_path(pack_build_path, Dir) :-
  625    nb_current('$build_tool_env', Env),
  626    memberchk('PATH'=Path, Env),
  627    path_sep(Sep),
  628    atomic_list_concat(Dirs, Sep, Path),
  629    member(Dir, Dirs),
  630    Dir \== ''.
 has_program(+Spec) is semidet
 has_program(+Spec, -Path) is semidet
 has_program(+Spec, -Path, +Env:list) is semidet
True when the OS has the program Spec at the absolute file location Path. Normally called as e.g. has_program(path(cmake), CMakeExe). The second allows passing in an environment as Name=Value pairs. If this contains a value for PATH, this is used rather than the current path variable.
  642has_program(Prog) :-
  643    has_program(Prog, _).
  644has_program(Program, Path) :-
  645    has_program(Program, Path, []).
  646
  647has_program(path(Program), Path, Env), memberchk('PATH'=_, Env) =>
  648    setup_call_cleanup(
  649        b_setval('$build_tool_env', Env),
  650        has_program(pack_build_path(Program), Path, []),
  651        nb_delete('$build_tool_env')).
  652has_program(Name, Path, Env), plain_program_name(Name) =>
  653    has_program(path(Name), Path, Env).
  654has_program(Program, Path, _Env) =>
  655    exe_options(ExeOptions),
  656    absolute_file_name(Program, Path,
  657                       [ file_errors(fail)
  658                       | ExeOptions
  659                       ]).
  660
  661plain_program_name(Name) :-
  662    atom(Name),
  663    \+ sub_atom(Name, _, _, _, '/').
  664
  665exe_options(Options) :-
  666    current_prolog_flag(windows, true),
  667    !,
  668    Options = [ extensions(['',exe,com]), access(read) ].
  669exe_options(Options) :-
  670    Options = [ access(execute) ].
 path_sep(-Sep) is det
Path separator for the OS. ; for Windows, : for POSIX.
  676path_sep(Sep) :-
  677    (   current_prolog_flag(windows, true)
  678    ->  Sep = ';'
  679    ;   Sep = ':'
  680    ).
  681
  682
  683		 /*******************************
  684		 *             OS PATHS		*
  685		 *******************************/
  686
  687setup_path :-
  688    current_prolog_flag(windows, true),
  689    !,
  690    setup_path([make, gcc]).
  691setup_path.
 setup_path(+Programs) is det
Deals with specific platforms to add specific directories to $PATH such that we can find the tools. Currently deals with MinGW on Windows to provide make and gcc.
  699setup_path(Programs) :-
  700    maplist(has_program, Programs).
  701setup_path(_) :-
  702    current_prolog_flag(windows, true),
  703    !,
  704    (   mingw_extend_path
  705    ->  true
  706    ;   print_message(error, build(no_mingw))
  707    ).
  708setup_path(_).
  709
  710mingw_extend_path :-
  711    mingw_root(MinGW),
  712    directory_file_path(MinGW, bin, MinGWBinDir),
  713    atom_concat(MinGW, '/msys/*/bin', Pattern),
  714    expand_file_name(Pattern, MsysDirs),
  715    last(MsysDirs, MSysBinDir),
  716    prolog_to_os_filename(MinGWBinDir, WinDirMinGW),
  717    prolog_to_os_filename(MSysBinDir, WinDirMSYS),
  718    getenv('PATH', Path0),
  719    atomic_list_concat([WinDirMSYS, WinDirMinGW, Path0], ';', Path),
  720    setenv('PATH', Path).
  721
  722mingw_root(MinGwRoot) :-
  723    current_prolog_flag(executable, Exe),
  724    sub_atom(Exe, 1, _, _, :),
  725    sub_atom(Exe, 0, 1, _, PlDrive),
  726    Drives = [PlDrive,c,d],
  727    member(Drive, Drives),
  728    format(atom(MinGwRoot), '~a:/MinGW', [Drive]),
  729    exists_directory(MinGwRoot),
  730    !.
  731
  732                 /*******************************
  733                 *            MESSAGES          *
  734                 *******************************/
  735
  736:- multifile prolog:message//1.  737
  738prolog:message(build(Msg)) -->
  739    message(Msg).
  740
  741message(no_mingw) -->
  742    [ 'Cannot find MinGW and/or MSYS.'-[] ].
  743message(process_output(Codes)) -->
  744    { split_lines(Codes, Lines) },
  745    process_lines(Lines).
  746message(step_failed(Step)) -->
  747    [ 'No build plugin could execute build step ~p'-[Step] ].
  748
  749split_lines([], []) :- !.
  750split_lines(All, [Line1|More]) :-
  751    append(Line1, [0'\n|Rest], All),
  752    !,
  753    split_lines(Rest, More).
  754split_lines(Line, [Line]).
  755
  756process_lines([]) --> [].
  757process_lines([H|T]) -->
  758    [ '~s'-[H] ],
  759    (   {T==[]}
  760    ->  []
  761    ;   [nl], process_lines(T)
  762    )