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
235
236build_environment(Env, Options) :-
237 findall(Name=Value,
238 distinct(Name, user_environment(Name, Value)),
239 UserEnv),
240 findall(Name=Value,
241 ( def_environment(Name, Value, Options),
242 \+ memberchk(Name=_, UserEnv)
243 ),
244 DefEnv),
245 append(UserEnv, DefEnv, Env).
246
247user_environment(Name, Value) :-
248 prolog:build_environment(Name, Value).
249user_environment(Name, Value) :-
250 prolog_pack:environment(Name, Value).
251
269
270
275
276def_environment('PATH', Value, _) :-
277 getenv('PATH', PATH),
278 current_prolog_flag(executable, Exe),
279 file_directory_name(Exe, ExeDir),
280 prolog_to_os_filename(ExeDir, OsExeDir),
281 current_prolog_flag(path_sep, Sep),
282 atomic_list_concat([OsExeDir, Sep, PATH], Value).
283def_environment('SWIPL', Value, _) :-
284 current_prolog_flag(executable, Value).
285def_environment('SWIPL_PACK_VERSION', Value, Options) :-
286 option(pack_version(Value), Options, 1).
287def_environment('SWIPL_PACK_PATH', Value, _Options) :-
288 prolog_config(pack_path, Value).
289def_environment(VAR, Value, Options) :-
290 env_name(version, VAR, Options),
291 current_prolog_flag(version, Value).
292def_environment(VAR, Value, Options) :-
293 env_name(home, VAR, Options),
294 current_prolog_flag(home, Value).
295def_environment(VAR, Value, Options) :-
296 env_name(arch, VAR, Options),
297 current_prolog_flag(arch, Value).
298def_environment(VAR, Value, Options) :-
299 env_name(module_dir, VAR, Options),
300 current_prolog_flag(arch, Arch),
301 atom_concat('lib/', Arch, Value).
302def_environment(VAR, Value, Options) :-
303 env_name(module_lib, VAR, Options),
304 current_prolog_flag(c_libplso, Value).
305def_environment(VAR, '-lswipl', Options) :-
306 env_name(lib, VAR, Options).
307def_environment(VAR, Value, Options) :-
308 env_name(cc, VAR, Options),
309 default_c_compiler(Value).
310def_environment(VAR, Value, Options) :-
311 env_name(ld, VAR, Options),
312 ( getenv('LD', Value)
313 -> true
314 ; default_c_compiler(Value)
315 ).
316def_environment('SWIPL_INCLUDE_DIRS', Value, _) :- 317 current_prolog_flag(home, Home),
318 atom_concat(Home, '/include', Value).
319def_environment('SWIPL_LIBRARIES_DIR', Value, _) :-
320 swipl_libraries_dir(Value).
321def_environment(VAR, Value, Options) :-
322 env_name(cflags, VAR, Options),
323 ( getenv('CFLAGS', SystemFlags)
324 -> Extra = [' ', SystemFlags]
325 ; Extra = []
326 ),
327 current_prolog_flag(c_cflags, Value0),
328 current_prolog_flag(home, Home),
329 atomic_list_concat([Value0, ' -I"', Home, '/include"' | Extra], Value).
330def_environment(VAR, Value, Options) :-
331 env_name(module_ldflags, VAR, Options),
332 ( getenv('LDFLAGS', SystemFlags)
333 -> Extra = [SystemFlags|System]
334 ; Extra = System
335 ),
336 ( current_prolog_flag(windows, true)
337 -> prolog_library_dir(LibDir),
338 atomic_list_concat(['-L"', LibDir, '"'], SystemLib),
339 System = [SystemLib]
340 ; prolog_config(apple_bundle_libdir, LibDir)
341 -> atomic_list_concat(['-L"', LibDir, '"'], SystemLib),
342 System = [SystemLib]
343 ; current_prolog_flag(c_libplso, '')
344 -> System = [] 345 ; prolog_library_dir(SystemLibDir),
346 atomic_list_concat(['-L"',SystemLibDir,'"'], SystemLib),
347 System = [SystemLib]
348 ),
349 current_prolog_flag(c_ldflags, LDFlags),
350 atomic_list_concat([LDFlags, '-shared' | Extra], ' ', Value).
351def_environment(VAR, Value, Options) :-
352 env_name(module_ext, VAR, Options),
353 current_prolog_flag(shared_object_extension, Value).
354def_environment(VAR, Value, Options) :-
355 env_name(prefix, VAR, Options),
356 prolog_install_prefix(Value).
357
358swipl_libraries_dir(Dir) :-
359 current_prolog_flag(windows, true),
360 !,
361 current_prolog_flag(home, Home),
362 atom_concat(Home, '/bin', Dir).
363swipl_libraries_dir(Dir) :-
364 prolog_config(apple_bundle_libdir, Dir),
365 !.
366swipl_libraries_dir(Dir) :-
367 prolog_library_dir(Dir).
368
369env_name(Id, Name, Options) :-
370 option(pack_version(V), Options, 1),
371 must_be(oneof([1,2]), V),
372 env_name_v(Id, V, Name).
373
374env_name_v(version, 1, 'SWIPLVERSION').
375env_name_v(version, 2, 'SWIPL_VERSION').
376env_name_v(home, 1, 'SWIHOME').
377env_name_v(home, 2, 'SWIPL_HOME_DIR').
378env_name_v(module_dir, 1, 'PACKSODIR').
379env_name_v(module_dir, 2, 'SWIPL_MODULE_DIR').
380env_name_v(module_lib, 1, 'SWISOLIB').
381env_name_v(module_lib, 2, 'SWIPL_MODULE_LIB').
382env_name_v(lib, 1, 'SWILIB').
383env_name_v(lib, 2, 'SWIPL_LIB').
384env_name_v(arch, 1, 'SWIARCH').
385env_name_v(arch, 2, 'SWIPL_ARCH').
386env_name_v(cc, 1, 'CC').
387env_name_v(cc, 2, 'SWIPL_CC').
388env_name_v(ld, 1, 'LD').
389env_name_v(ld, 2, 'SWIPL_LD').
390env_name_v(cflags, 1, 'CFLAGS').
391env_name_v(cflags, 2, 'SWIPL_CFLAGS').
392env_name_v(module_ldflags, 1, 'LDSOFLAGS').
393env_name_v(module_ldflags, 2, 'SWIPL_MODULE_LDFLAGS').
394env_name_v(module_ext, 1, 'SOEXT').
395env_name_v(module_ext, 2, 'SWIPL_MODULE_EXT').
396env_name_v(prefix, 1, 'PREFIX').
397env_name_v(prefix, 2, 'SWIPL_PREFIX').
398
402
403:- multifile
404 prolog:runtime_config/2. 405
406prolog_library_dir(Dir) :-
407 prolog:runtime_config(c_libdir, Dir),
408 !.
409prolog_library_dir(Dir) :-
410 current_prolog_flag(windows, true),
411 \+ current_prolog_flag(msys2, true),
412 current_prolog_flag(home, Home),
413 !,
414 atomic_list_concat([Home, bin], /, Dir).
415prolog_library_dir(Dir) :-
416 current_prolog_flag(home, Home),
417 ( current_prolog_flag(c_libdir, Rel)
418 -> atomic_list_concat([Home, Rel], /, Dir)
419 ; current_prolog_flag(arch, Arch)
420 -> atomic_list_concat([Home, lib, Arch], /, Dir)
421 ).
422
429
430default_c_compiler(CC) :-
431 getenv('CC', CC),
432 !.
433default_c_compiler(CC) :-
434 preferred_c_compiler(CC),
435 has_program(path(CC), _),
436 !.
437
438preferred_c_compiler(CC) :-
439 current_prolog_flag(c_cc, CC).
440preferred_c_compiler(gcc).
441preferred_c_compiler(clang).
442preferred_c_compiler(cc).
443
450
451:- det(save_build_environment/1). 452save_build_environment(State) :-
453 Env = State.get(env),
454 !,
455 ( BuildDir = State.get(bin_dir)
456 -> true
457 ; BuildDir = State.get(src_dir)
458 ),
459 directory_file_path(BuildDir, 'buildenv.sh', EnvFile),
460 setup_call_cleanup(
461 open(EnvFile, write, Out),
462 write_env_script(Out, Env),
463 close(Out)).
464save_build_environment(_).
465
466write_env_script(Out, Env) :-
467 format(Out,
468 '# This file contains the environment that can be used to\n\c
469 # build the foreign pack outside Prolog. This file must\n\c
470 # be loaded into a bourne-compatible shell using\n\c
471 #\n\c
472 # $ source buildenv.sh\n\n',
473 []),
474 forall(member(Var=Value, Env),
475 format(Out, '~w=\'~w\'\n', [Var, Value])),
476 format(Out, '\nexport ', []),
477 forall(member(Var=_, Env),
478 format(Out, ' ~w', [Var])),
479 format(Out, '\n', []).
480
492
493prolog_install_prefix(Prefix) :-
494 current_prolog_flag(pack_prefix, Prefix),
495 access_file(Prefix, write),
496 !.
497prolog_install_prefix(Prefix) :-
498 current_prolog_flag(os_argv, [Name|_]),
499 has_program(path(Name), EXE),
500 file_directory_name(EXE, Bin),
501 file_directory_name(Bin, Prefix0),
502 ( local_prefix(Prefix0, Prefix1)
503 -> Prefix = Prefix1
504 ; Prefix = Prefix0
505 ),
506 access_file(Prefix, write),
507 !.
508prolog_install_prefix(Prefix) :-
509 expand_file_name(~, [UserHome]),
510 directory_file_path(UserHome, bin, BinDir),
511 exists_directory(BinDir),
512 access_file(BinDir, write),
513 !,
514 Prefix = UserHome.
515
516local_prefix('/usr', '/usr/local').
517
518
519 522
541
542run_process(path(Exe), Argv, Options) :-
543 option(env(BuildEnv), Options),
544 !,
545 setup_call_cleanup(
546 b_setval('$build_tool_env', BuildEnv),
547 run_process(pack_build_path(Exe), Argv, Options),
548 nb_delete('$build_tool_env')).
549run_process(Executable, Argv, Options) :-
550 \+ option(output(_), Options),
551 \+ option(error(_), Options),
552 current_prolog_flag(unix, true),
553 current_prolog_flag(threads, true),
554 !,
555 process_create_options(Options, Extra),
556 process_create(Executable, Argv,
557 [ stdout(pipe(Out)),
558 stderr(pipe(Error)),
559 process(PID)
560 | Extra
561 ]),
562 thread_create(relay_output([output-Out, error-Error]), Id, []),
563 process_wait(PID, Status),
564 thread_join(Id, _),
565 ( Status == exit(0)
566 -> true
567 ; throw(error(process_error(process(Executable, Argv), Status), _))
568 ).
569run_process(Executable, Argv, Options) :-
570 process_create_options(Options, Extra),
571 setup_call_cleanup(
572 process_create(Executable, Argv,
573 [ stdout(pipe(Out)),
574 stderr(pipe(Error)),
575 process(PID)
576 | Extra
577 ]),
578 ( read_stream_to_codes(Out, OutCodes, []),
579 read_stream_to_codes(Error, ErrorCodes, []),
580 process_wait(PID, Status)
581 ),
582 ( close(Out),
583 close(Error)
584 )),
585 print_error(ErrorCodes, Options),
586 print_output(OutCodes, Options),
587 ( Status == exit(0)
588 -> true
589 ; throw(error(process_error(process(Executable, Argv), Status), _))
590 ).
591
592process_create_options(Options, Extra) :-
593 option(directory(Dir), Options, .),
594 ( option(env(Env), Options)
595 -> Extra = [cwd(Dir), environment(Env)]
596 ; Extra = [cwd(Dir)]
597 ).
598
599relay_output([]) :- !.
600relay_output(Output) :-
601 pairs_values(Output, Streams),
602 wait_for_input(Streams, Ready, infinite),
603 relay(Ready, Output, NewOutputs),
604 relay_output(NewOutputs).
605
606relay([], Outputs, Outputs).
607relay([H|T], Outputs0, Outputs) :-
608 selectchk(Type-H, Outputs0, Outputs1),
609 ( at_end_of_stream(H)
610 -> close(H),
611 relay(T, Outputs1, Outputs)
612 ; read_pending_codes(H, Codes, []),
613 relay(Type, Codes),
614 relay(T, Outputs0, Outputs)
615 ).
616
617relay(error, Codes) :-
618 set_prolog_flag(message_context, []),
619 print_error(Codes, []).
620relay(output, Codes) :-
621 print_output(Codes, []).
622
623print_output(OutCodes, Options) :-
624 option(output(Codes), Options),
625 !,
626 Codes = OutCodes.
627print_output(OutCodes, _) :-
628 print_message(informational, build(process_output(OutCodes))).
629
630print_error(OutCodes, Options) :-
631 option(error(Codes), Options),
632 !,
633 Codes = OutCodes.
634print_error(OutCodes, _) :-
635 phrase(classify_message(Level), OutCodes, _),
636 print_message(Level, build(process_output(OutCodes))).
637
638classify_message(error) -->
639 string(_), "fatal:",
640 !.
641classify_message(error) -->
642 string(_), "error:",
643 !.
644classify_message(warning) -->
645 string(_), "warning:",
646 !.
647classify_message(informational) -->
648 [].
649
650
651:- multifile user:file_search_path/2. 652user:file_search_path(pack_build_path, Dir) :-
653 nb_current('$build_tool_env', Env),
654 memberchk('PATH'=Path, Env),
655 current_prolog_flag(path_sep, Sep),
656 atomic_list_concat(Dirs, Sep, Path),
657 member(Dir, Dirs),
658 Dir \== ''.
659
669
670has_program(Prog) :-
671 has_program(Prog, _).
672has_program(Program, Path) :-
673 has_program(Program, Path, []).
674
675has_program(path(Program), Path, Env), memberchk('PATH'=_, Env) =>
676 setup_call_cleanup(
677 b_setval('$build_tool_env', Env),
678 has_program(pack_build_path(Program), Path, []),
679 nb_delete('$build_tool_env')).
680has_program(Name, Path, Env), plain_program_name(Name) =>
681 has_program(path(Name), Path, Env).
682has_program(Program, Path, _Env) =>
683 exe_options(ExeOptions),
684 absolute_file_name(Program, Path,
685 [ file_errors(fail)
686 | ExeOptions
687 ]).
688
689plain_program_name(Name) :-
690 atom(Name),
691 \+ sub_atom(Name, _, _, _, '/').
692
693exe_options(Options) :-
694 current_prolog_flag(windows, true),
695 !,
696 Options = [ extensions(['',exe,com]), access(read) ].
697exe_options(Options) :-
698 Options = [ access(execute) ].
699
700
701 704
705setup_path :-
706 current_prolog_flag(windows, true),
707 \+ current_prolog_flag(msys2, true),
708 !,
709 setup_path([make, gcc]).
710setup_path.
711
717
718setup_path(Programs) :-
719 maplist(has_program, Programs).
720setup_path(_) :-
721 current_prolog_flag(windows, true),
722 !,
723 ( mingw_extend_path
724 -> true
725 ; print_message(error, build(no_mingw))
726 ).
727setup_path(_).
728
733
734mingw_extend_path :-
735 absolute_file_name(path('gcc.exe'), _,
736 [ access(exist),
737 file_errors(fail)
738 ]),
739 !.
740mingw_extend_path :-
741 mingw_root(MinGW),
742 directory_file_path(MinGW, bin, MinGWBinDir),
743 atom_concat(MinGW, '/msys/*/bin', Pattern),
744 expand_file_name(Pattern, MsysDirs),
745 last(MsysDirs, MSysBinDir),
746 prolog_to_os_filename(MinGWBinDir, WinDirMinGW),
747 prolog_to_os_filename(MSysBinDir, WinDirMSYS),
748 getenv('PATH', Path0),
749 atomic_list_concat([WinDirMSYS, WinDirMinGW, Path0], ';', Path),
750 setenv('PATH', Path),
751 print_message(informational,
752 build(mingw_extend_path(WinDirMSYS, WinDirMinGW))).
753
754mingw_root(MinGwRoot) :-
755 current_prolog_flag(executable, Exe),
756 sub_atom(Exe, 1, _, _, :),
757 sub_atom(Exe, 0, 1, _, PlDrive),
758 Drives = [PlDrive,c,d],
759 member(Drive, Drives),
760 format(atom(MinGwRoot), '~a:/MinGW', [Drive]),
761 exists_directory(MinGwRoot),
762 !.
763
764 767
768:- multifile prolog:message//1. 769
770prolog:message(build(Msg)) -->
771 message(Msg).
772
773message(no_mingw) -->
774 [ 'Cannot find MinGW and/or MSYS.'-[] ].
775message(process_output(Codes)) -->
776 process_output(Codes).
777message(step_failed(Step)) -->
778 [ 'No build plugin could execute build step ~p'-[Step] ].
779message(mingw_extend_path(WinDirMSYS, WinDirMinGW)) -->
780 [ 'Extended %PATH% with ~p and ~p'-[WinDirMSYS, WinDirMinGW] ].
781
786
787process_output([]) -->
788 !.
789process_output(Codes) -->
790 { string_codes(String, Codes),
791 split_string(String, "\n", "\r", Lines)
792 },
793 [ at_same_line ],
794 process_lines(Lines).
795
796process_lines([H|T]) -->
797 [ '~s'-[H] ],
798 ( {T==[""]}
799 -> [nl]
800 ; {T==[]}
801 -> [flush]
802 ; [nl], process_lines(T)
803 )