34
35:- module(build_tools,
36 [ build_steps/3, 37 prolog_install_prefix/1, 38 run_process/3, 39 has_program/3, 40 ensure_build_dir/3 41 ]). 42:- autoload(library(lists), [selectchk/3, member/2, append/3, last/2]). 43:- autoload(library(option), [option/2, option/3, dict_options/2]). 44:- autoload(library(pairs), [pairs_values/2]). 45:- autoload(library(process), [process_create/3, process_wait/2]). 46:- autoload(library(readutil), [read_stream_to_codes/3]). 47:- autoload(library(dcg/basics), [string/3]). 48:- autoload(library(apply), [foldl/4, maplist/2]). 49:- autoload(library(filesex), [directory_file_path/3, make_directory_path/1]). 50:- autoload(library(prolog_config), [prolog_config/2]). 51:- autoload(library(solution_sequences), [distinct/2]). 52
54:- use_module(conan). 55:- use_module(cmake). 56:- use_module(make). 57
58:- multifile
59 prolog:build_file/2, 60 prolog:build_step/4, 61 prolog:build_environment/2, 62 prolog_pack:environment/2. 63
77
112
113build_steps(Steps, SrcDir, Options) :-
114 dict_options(Dict0, Options),
115 setup_path,
116 build_environment(BuildEnv, Options),
117 State0 = Dict0.put(#{ env: BuildEnv,
118 src_dir: SrcDir
119 }),
120
121 foldl(build_step, Steps, State0, _State).
122
123build_step(Spec, State0, State) :-
124 build_step_(Spec, State0, State),
125 post_step(Spec, State).
126
127build_step_(Spec, State0, State) :-
128 step_name(Spec, Step),
129 prolog:build_file(File, Tool),
130 directory_file_path(State0.src_dir, File, Path),
131 exists_file(Path),
132 prolog:build_step(Step, Tool, State0, State),
133 !.
134build_step_([_], State, State) :-
135 !.
136build_step_({Step}, State, State) :-
137 !,
138 print_message(error, build(step_failed(Step))),
139 throw(error(build(step_failed(Step)))).
140build_step_(Step, State, State) :-
141 print_message(warning, build(step_failed(Step))).
142
143step_name([Step], Name) => Name = Step.
144step_name({Step}, Name) => Name = Step.
145step_name(Step, Name) => Name = Step.
146
150
151post_step(Step, State) :-
152 step_name(Step, configure),
153 !,
154 save_build_environment(State).
155post_step(_, _).
156
157
162
163ensure_build_dir(_, State0, State) :-
164 _ = State0.get(bin_dir),
165 !,
166 State = State0.
167ensure_build_dir(., State0, State) :-
168 !,
169 State = State0.put(bin_dir, State0.src_dir).
170ensure_build_dir(Dir, State0, State) :-
171 directory_file_path(State0.src_dir, Dir, BinDir),
172 make_directory_path(BinDir),
173 !,
174 State = State0.put(bin_dir, BinDir).
175
176
177 180
237
238build_environment(Env, Options) :-
239 findall(Name=Value,
240 distinct(Name, user_environment(Name, Value)),
241 UserEnv),
242 findall(Name=Value,
243 ( def_environment(Name, Value, Options),
244 \+ memberchk(Name=_, UserEnv)
245 ),
246 DefEnv),
247 append(UserEnv, DefEnv, Env).
248
249user_environment(Name, Value) :-
250 prolog:build_environment(Name, Value).
251user_environment(Name, Value) :-
252 prolog_pack:environment(Name, Value).
253
271
272
277
278def_environment('PATH', Value, _) :-
279 getenv('PATH', PATH),
280 current_prolog_flag(executable, Exe),
281 file_directory_name(Exe, ExeDir),
282 prolog_to_os_filename(ExeDir, OsExeDir),
283 current_prolog_flag(path_sep, Sep),
284 atomic_list_concat([OsExeDir, Sep, PATH], Value).
285def_environment('SWIPL', Value, _) :-
286 current_prolog_flag(executable, Value).
287def_environment('SWIPL_PACK_VERSION', Value, Options) :-
288 option(pack_version(Value), Options, 1).
289def_environment('SWIPL_PACK_PATH', Value, _Options) :-
290 prolog_config(pack_path, Value).
291def_environment(VAR, Value, Options) :-
292 env_name(version, VAR, Options),
293 current_prolog_flag(version, Value).
294def_environment(VAR, Value, Options) :-
295 env_name(home, VAR, Options),
296 current_prolog_flag(home, Value).
297def_environment(VAR, Value, Options) :-
298 env_name(arch, VAR, Options),
299 current_prolog_flag(arch, Value).
300def_environment(VAR, Value, Options) :-
301 env_name(module_dir, VAR, Options),
302 current_prolog_flag(arch, Arch),
303 atom_concat('lib/', Arch, Value).
304def_environment(VAR, Value, Options) :-
305 env_name(module_lib, VAR, Options),
306 current_prolog_flag(c_libplso, Value).
307def_environment(VAR, '-lswipl', Options) :-
308 env_name(lib, VAR, Options).
309def_environment(VAR, Value, Options) :-
310 env_name(cc, VAR, Options),
311 default_c_compiler(Value).
312def_environment(VAR, Value, Options) :-
313 env_name(cxx, VAR, Options),
314 default_cxx_compiler(Value).
315def_environment(VAR, Value, Options) :-
316 env_name(ld, VAR, Options),
317 ( getenv('LD', Value)
318 -> true
319 ; default_c_compiler(Value)
320 ).
321def_environment('SWIPL_INCLUDE_DIRS', Value, _) :- 322 current_prolog_flag(home, Home),
323 atom_concat(Home, '/include', Value).
324def_environment('SWIPL_LIBRARIES_DIR', Value, _) :-
325 swipl_libraries_dir(Value).
326def_environment(VAR, Value, Options) :-
327 env_name(cflags, VAR, Options),
328 ( getenv('CFLAGS', SystemFlags)
329 -> Extra = [' ', SystemFlags]
330 ; Extra = []
331 ),
332 current_prolog_flag(c_cflags, Value0),
333 current_prolog_flag(home, Home),
334 atomic_list_concat([Value0, ' -I"', Home, '/include"' | Extra], Value).
335def_environment(VAR, Value, Options) :-
336 env_name(module_ldflags, VAR, Options),
337 ( getenv('LDFLAGS', SystemFlags)
338 -> Extra = [SystemFlags|System]
339 ; Extra = System
340 ),
341 ( current_prolog_flag(windows, true)
342 -> prolog_library_dir(LibDir),
343 atomic_list_concat(['-L"', LibDir, '"'], SystemLib),
344 System = [SystemLib]
345 ; prolog_config(apple_bundle_libdir, LibDir)
346 -> atomic_list_concat(['-L"', LibDir, '"'], SystemLib),
347 System = [SystemLib]
348 ; current_prolog_flag(c_libplso, '')
349 -> System = [] 350 ; prolog_library_dir(SystemLibDir),
351 atomic_list_concat(['-L"',SystemLibDir,'"'], SystemLib),
352 System = [SystemLib]
353 ),
354 current_prolog_flag(c_ldflags, LDFlags),
355 atomic_list_concat([LDFlags, '-shared' | Extra], ' ', Value).
356def_environment(VAR, Value, Options) :-
357 env_name(module_ext, VAR, Options),
358 current_prolog_flag(shared_object_extension, Value).
359def_environment(VAR, Value, Options) :-
360 env_name(prefix, VAR, Options),
361 prolog_install_prefix(Value).
362
363swipl_libraries_dir(Dir) :-
364 current_prolog_flag(windows, true),
365 !,
366 current_prolog_flag(home, Home),
367 atom_concat(Home, '/bin', Dir).
368swipl_libraries_dir(Dir) :-
369 prolog_config(apple_bundle_libdir, Dir),
370 !.
371swipl_libraries_dir(Dir) :-
372 prolog_library_dir(Dir).
373
374env_name(Id, Name, Options) :-
375 option(pack_version(V), Options, 1),
376 must_be(oneof([1,2]), V),
377 env_name_v(Id, V, Name).
378
379env_name_v(version, 1, 'SWIPLVERSION').
380env_name_v(version, 2, 'SWIPL_VERSION').
381env_name_v(home, 1, 'SWIHOME').
382env_name_v(home, 2, 'SWIPL_HOME_DIR').
383env_name_v(module_dir, 1, 'PACKSODIR').
384env_name_v(module_dir, 2, 'SWIPL_MODULE_DIR').
385env_name_v(module_lib, 1, 'SWISOLIB').
386env_name_v(module_lib, 2, 'SWIPL_MODULE_LIB').
387env_name_v(lib, 1, 'SWILIB').
388env_name_v(lib, 2, 'SWIPL_LIB').
389env_name_v(arch, 1, 'SWIARCH').
390env_name_v(arch, 2, 'SWIPL_ARCH').
391env_name_v(cc, 1, 'CC').
392env_name_v(cc, 2, 'SWIPL_CC').
393env_name_v(cxx, 1, 'CXX').
394env_name_v(cxx, 2, 'SWIPL_CXX').
395env_name_v(ld, 1, 'LD').
396env_name_v(ld, 2, 'SWIPL_LD').
397env_name_v(cflags, 1, 'CFLAGS').
398env_name_v(cflags, 2, 'SWIPL_CFLAGS').
399env_name_v(module_ldflags, 1, 'LDSOFLAGS').
400env_name_v(module_ldflags, 2, 'SWIPL_MODULE_LDFLAGS').
401env_name_v(module_ext, 1, 'SOEXT').
402env_name_v(module_ext, 2, 'SWIPL_MODULE_EXT').
403env_name_v(prefix, 1, 'PREFIX').
404env_name_v(prefix, 2, 'SWIPL_PREFIX').
405
409
410:- multifile
411 prolog:runtime_config/2. 412
413prolog_library_dir(Dir) :-
414 prolog:runtime_config(c_libdir, Dir),
415 !.
416prolog_library_dir(Dir) :-
417 current_prolog_flag(windows, true),
418 \+ current_prolog_flag(msys2, true),
419 current_prolog_flag(home, Home),
420 !,
421 atomic_list_concat([Home, bin], /, Dir).
422prolog_library_dir(Dir) :-
423 current_prolog_flag(home, Home),
424 ( current_prolog_flag(c_libdir, Rel)
425 -> atomic_list_concat([Home, Rel], /, Dir)
426 ; current_prolog_flag(arch, Arch)
427 -> atomic_list_concat([Home, lib, Arch], /, Dir)
428 ).
429
436
437default_c_compiler(CC) :-
438 getenv('CC', CC),
439 !.
440default_c_compiler(CC) :-
441 preferred_c_compiler(CC0),
442 has_program(CC0, CC),
443 !.
444
445default_cxx_compiler(CXX) :-
446 getenv('CXX', CXX),
447 !.
448default_cxx_compiler(CXX) :-
449 preferred_cxx_compiler(CXX0),
450 has_program(CXX0, CXX),
451 !.
452
453preferred_c_compiler(CC) :-
454 current_prolog_flag(c_cc, CC).
455preferred_c_compiler(gcc).
456preferred_c_compiler(clang).
457preferred_c_compiler(cc).
458
459preferred_cxx_compiler(CXX) :-
460 current_prolog_flag(c_cxx, CXX).
461preferred_cxx_compiler('g++').
462preferred_cxx_compiler('clang++').
463preferred_cxx_compiler('c++').
464
465
472
473:- det(save_build_environment/1). 474save_build_environment(State) :-
475 Env = State.get(env),
476 !,
477 ( BuildDir = State.get(bin_dir)
478 -> true
479 ; BuildDir = State.get(src_dir)
480 ),
481 directory_file_path(BuildDir, 'buildenv.sh', EnvFile),
482 setup_call_cleanup(
483 open(EnvFile, write, Out),
484 write_env_script(Out, Env),
485 close(Out)).
486save_build_environment(_).
487
488write_env_script(Out, Env) :-
489 format(Out,
490 '# This file contains the environment that can be used to\n\c
491 # build the foreign pack outside Prolog. This file must\n\c
492 # be loaded into a bourne-compatible shell using\n\c
493 #\n\c
494 # $ source buildenv.sh\n\n',
495 []),
496 forall(member(Var=Value, Env),
497 format(Out, '~w=\'~w\'\n', [Var, Value])),
498 format(Out, '\nexport ', []),
499 forall(member(Var=_, Env),
500 format(Out, ' ~w', [Var])),
501 format(Out, '\n', []).
502
514
515prolog_install_prefix(Prefix) :-
516 current_prolog_flag(pack_prefix, Prefix),
517 access_file(Prefix, write),
518 !.
519prolog_install_prefix(Prefix) :-
520 current_prolog_flag(os_argv, [Name|_]),
521 has_program(path(Name), EXE),
522 file_directory_name(EXE, Bin),
523 file_directory_name(Bin, Prefix0),
524 ( local_prefix(Prefix0, Prefix1)
525 -> Prefix = Prefix1
526 ; Prefix = Prefix0
527 ),
528 access_file(Prefix, write),
529 !.
530prolog_install_prefix(Prefix) :-
531 expand_file_name(~, [UserHome]),
532 directory_file_path(UserHome, bin, BinDir),
533 exists_directory(BinDir),
534 access_file(BinDir, write),
535 !,
536 Prefix = UserHome.
537
538local_prefix('/usr', '/usr/local').
539
540
541 544
563
564run_process(path(Exe), Argv, Options) :-
565 option(env(BuildEnv), Options),
566 !,
567 setup_call_cleanup(
568 b_setval('$build_tool_env', BuildEnv),
569 run_process(pack_build_path(Exe), Argv, Options),
570 nb_delete('$build_tool_env')).
571run_process(Executable, Argv, Options) :-
572 \+ option(output(_), Options),
573 \+ option(error(_), Options),
574 current_prolog_flag(unix, true),
575 current_prolog_flag(threads, true),
576 !,
577 process_create_options(Options, Extra),
578 process_create(Executable, Argv,
579 [ stdout(pipe(Out)),
580 stderr(pipe(Error)),
581 process(PID)
582 | Extra
583 ]),
584 thread_create(relay_output([output-Out, error-Error]), Id, []),
585 process_wait(PID, Status),
586 thread_join(Id, _),
587 ( Status == exit(0)
588 -> true
589 ; throw(error(process_error(process(Executable, Argv), Status), _))
590 ).
591run_process(Executable, Argv, Options) :-
592 process_create_options(Options, Extra),
593 setup_call_cleanup(
594 process_create(Executable, Argv,
595 [ stdout(pipe(Out)),
596 stderr(pipe(Error)),
597 process(PID)
598 | Extra
599 ]),
600 ( read_stream_to_codes(Out, OutCodes, []),
601 read_stream_to_codes(Error, ErrorCodes, []),
602 process_wait(PID, Status)
603 ),
604 ( close(Out),
605 close(Error)
606 )),
607 print_error(ErrorCodes, Options),
608 print_output(OutCodes, Options),
609 ( Status == exit(0)
610 -> true
611 ; throw(error(process_error(process(Executable, Argv), Status), _))
612 ).
613
614process_create_options(Options, Extra) :-
615 option(directory(Dir), Options, .),
616 ( option(env(Env), Options)
617 -> Extra = [cwd(Dir), environment(Env)]
618 ; Extra = [cwd(Dir)]
619 ).
620
621relay_output([]) :- !.
622relay_output(Output) :-
623 pairs_values(Output, Streams),
624 wait_for_input(Streams, Ready, infinite),
625 relay(Ready, Output, NewOutputs),
626 relay_output(NewOutputs).
627
628relay([], Outputs, Outputs).
629relay([H|T], Outputs0, Outputs) :-
630 selectchk(Type-H, Outputs0, Outputs1),
631 ( at_end_of_stream(H)
632 -> close(H),
633 relay(T, Outputs1, Outputs)
634 ; read_pending_codes(H, Codes, []),
635 relay(Type, Codes),
636 relay(T, Outputs0, Outputs)
637 ).
638
639relay(error, Codes) :-
640 set_prolog_flag(message_context, []),
641 print_error(Codes, []).
642relay(output, Codes) :-
643 print_output(Codes, []).
644
645print_output(OutCodes, Options) :-
646 option(output(Codes), Options),
647 !,
648 Codes = OutCodes.
649print_output(OutCodes, _) :-
650 print_message(informational, build(process_output(OutCodes))).
651
652print_error(OutCodes, Options) :-
653 option(error(Codes), Options),
654 !,
655 Codes = OutCodes.
656print_error(OutCodes, _) :-
657 phrase(classify_message(Level), OutCodes, _),
658 print_message(Level, build(process_output(OutCodes))).
659
660classify_message(error) -->
661 string(_), "fatal:",
662 !.
663classify_message(error) -->
664 string(_), "error:",
665 !.
666classify_message(warning) -->
667 string(_), "warning:",
668 !.
669classify_message(informational) -->
670 [].
671
672
673:- multifile user:file_search_path/2. 674user:file_search_path(pack_build_path, Dir) :-
675 nb_current('$build_tool_env', Env),
676 memberchk('PATH'=Path, Env),
677 current_prolog_flag(path_sep, Sep),
678 atomic_list_concat(Dirs, Sep, Path),
679 member(Dir, Dirs),
680 Dir \== ''.
681
691
692has_program(Prog) :-
693 has_program(Prog, _).
694has_program(Program, Path) :-
695 has_program(Program, Path, []).
696
697has_program(path(Program), Path, Env), memberchk('PATH'=_, Env) =>
698 setup_call_cleanup(
699 b_setval('$build_tool_env', Env),
700 has_program(pack_build_path(Program), Path, []),
701 nb_delete('$build_tool_env')).
702has_program(Name, Path, Env), plain_program_name(Name) =>
703 has_program(path(Name), Path, Env).
704has_program(Program, Path, _Env) =>
705 exe_options(ExeOptions),
706 absolute_file_name(Program, Path,
707 [ file_errors(fail)
708 | ExeOptions
709 ]).
710
711plain_program_name(Name) :-
712 atom(Name),
713 \+ sub_atom(Name, _, _, _, '/').
714
715exe_options(Options) :-
716 current_prolog_flag(windows, true),
717 !,
718 Options = [ extensions(['',exe,com]), access(read) ].
719exe_options(Options) :-
720 Options = [ access(execute) ].
721
722
723 726
727setup_path :-
728 current_prolog_flag(windows, true),
729 \+ current_prolog_flag(msys2, true),
730 !,
731 setup_path([make, gcc]).
732setup_path.
733
739
740setup_path(Programs) :-
741 maplist(has_program, Programs).
742setup_path(_) :-
743 current_prolog_flag(windows, true),
744 !,
745 ( mingw_extend_path
746 -> true
747 ; print_message(error, build(no_mingw))
748 ).
749setup_path(_).
750
755
756mingw_extend_path :-
757 absolute_file_name(path('gcc.exe'), _,
758 [ access(exist),
759 file_errors(fail)
760 ]),
761 !.
762mingw_extend_path :-
763 mingw_root(MinGW),
764 directory_file_path(MinGW, bin, MinGWBinDir),
765 atom_concat(MinGW, '/msys/*/bin', Pattern),
766 expand_file_name(Pattern, MsysDirs),
767 last(MsysDirs, MSysBinDir),
768 prolog_to_os_filename(MinGWBinDir, WinDirMinGW),
769 prolog_to_os_filename(MSysBinDir, WinDirMSYS),
770 getenv('PATH', Path0),
771 atomic_list_concat([WinDirMSYS, WinDirMinGW, Path0], ';', Path),
772 setenv('PATH', Path),
773 print_message(informational,
774 build(mingw_extend_path(WinDirMSYS, WinDirMinGW))).
775
776mingw_root(MinGwRoot) :-
777 current_prolog_flag(executable, Exe),
778 sub_atom(Exe, 1, _, _, :),
779 sub_atom(Exe, 0, 1, _, PlDrive),
780 Drives = [PlDrive,c,d],
781 member(Drive, Drives),
782 format(atom(MinGwRoot), '~a:/MinGW', [Drive]),
783 exists_directory(MinGwRoot),
784 !.
785
786 789
790:- multifile prolog:message//1. 791
792prolog:message(build(Msg)) -->
793 message(Msg).
794
795message(no_mingw) -->
796 [ 'Cannot find MinGW and/or MSYS.'-[] ].
797message(process_output(Codes)) -->
798 process_output(Codes).
799message(step_failed(Step)) -->
800 [ 'No build plugin could execute build step ~p'-[Step] ].
801message(mingw_extend_path(WinDirMSYS, WinDirMinGW)) -->
802 [ 'Extended %PATH% with ~p and ~p'-[WinDirMSYS, WinDirMinGW] ].
803
808
809process_output([]) -->
810 !.
811process_output(Codes) -->
812 { string_codes(String, Codes),
813 split_string(String, "\n", "\r", Lines)
814 },
815 [ at_same_line ],
816 process_lines(Lines).
817
818process_lines([H|T]) -->
819 [ '~s'-[H] ],
820 ( {T==[""]}
821 -> [nl]
822 ; {T==[]}
823 -> [flush]
824 ; [nl], process_lines(T)
825 )