1% * -*- Mode: Prolog -*- */
    2
    3:- use_module(library(biomake/biomake)).    4:- use_module(library(biomake/utils)).    5:- use_module(library(biomake/embed)).    6
    7% ----------------------------------------
    8% MAIN PROGRAM
    9% ----------------------------------------
   10
   11% Declare all debug topics defined in this module
   12:- nodebug(verbose).   13:- nodebug(build).   14:- nodebug(md5).   15
   16
   17main :-
   18        current_prolog_flag(argv, Arguments),
   19        (append(_SytemArgs, [--|Args], Arguments) ; =(Arguments,Args)),
   20        !,
   21        parse_args(Args,TmpOpts),
   22	get_cmd_args(TmpOpts,Opts),
   23 	add_assignments(Opts),
   24	bind_special_variables(Opts),
   25	eval_makefile_syntax_args(Opts2,Opts),
   26	eval_makespec_syntax_args(Opts3,Opts2),
   27	consult_makefile(Opts4,Opts3),
   28	AllOpts = Opts4,
   29        forall(member(goal(G),AllOpts),
   30               G),
   31        forall(member(flush_queue(T),AllOpts),
   32	       flush_queue_recursive(T,AllOpts)),
   33	(build_toplevel(AllOpts)
   34	 -> halt_success
   35	 ;  halt_error).
   36
   37build_toplevel(Opts) :-
   38	member(toplevel(_),Opts),
   39	!,
   40	start_queue(Opts),
   41	forall(member(toplevel(T),Opts),
   42               build(T,Opts)),
   43        finish_queue(Opts).
   44
   45build_toplevel(Opts) :-
   46	nonbuild_task_specified(Opts),
   47	!.
   48
   49build_toplevel(Opts) :-
   50	start_queue(Opts),
   51	build_default(Opts),
   52        finish_queue(Opts).
   53
   54nonbuild_task_specified(Opts) :- member(translate_gnu_makefile(_),Opts).
   55nonbuild_task_specified(Opts) :- member(goal(_),Opts).
   56nonbuild_task_specified(Opts) :- member(flush_queue(_),Opts).
   57
   58add_assignments(Opts) :-
   59        forall(member(assignment(Var,Val),Opts),
   60	       add_cmdline_assignment((Var = Val))).
   61
   62eval_makefile_syntax_args(OptsOut,OptsIn) :-
   63    bagof(Eval,member(eval_makefile_syntax(Eval),OptsIn),Evals),
   64    !,
   65    ensure_loaded(library(biomake/gnumake_parser)),
   66    eval_makefile_syntax_args(Evals,OptsOut,OptsIn).
   67eval_makefile_syntax_args(Opts,Opts).
   68eval_makefile_syntax_args([Eval|Evals],OptsOut,OptsIn) :-
   69    eval_gnu_makefile(Eval,_,Opts,OptsIn),
   70    eval_makefile_syntax_args(Evals,OptsOut,Opts).
   71eval_makefile_syntax_args([],Opts,Opts).
   72
   73eval_makespec_syntax_args(OptsOut,OptsIn) :-
   74    bagof(Eval,member(eval_makespec_syntax(Eval),OptsIn),Evals),
   75    !,
   76    eval_makespec_syntax_args(Evals,OptsOut,OptsIn).
   77eval_makespec_syntax_args(Opts,Opts).
   78eval_makespec_syntax_args([Eval|Evals],OptsOut,OptsIn) :-
   79    eval_atom_as_makeprog_term(Eval,Opts,OptsIn),
   80    eval_makespec_syntax_args(Evals,OptsOut,Opts).
   81eval_makespec_syntax_args([],Opts,Opts).
   82
   83consult_makefile(AllOpts,Opts) :-
   84	DefaultMakeprogs = ['Makeprog','makeprog','Makespec.pro','makespec.pro'],
   85	DefaultGnuMakefiles = ['Makefile','makefile'],
   86	(member(makeprog(BF),Opts)
   87	 -> consult_makeprog(BF,AllOpts,Opts);
   88	 (member(gnu_makefile(F),Opts)
   89	  -> consult_gnu_makefile(F,AllOpts,Opts);
   90	  (find_file(DefaultMakeprog,DefaultMakeprogs)
   91	   -> consult_makeprog(DefaultMakeprog,AllOpts,Opts);
   92	   (find_file(DefaultGnuMakefile,DefaultGnuMakefiles)
   93	    -> consult_gnu_makefile(DefaultGnuMakefile,AllOpts,Opts)
   94	    ; (format("No Makefile found~n"),
   95	       halt_error))))).
   96
   97find_file(File,List) :-
   98    member(File,List),
   99    exists_file(File),
  100    !.
  101
  102% ----------------------------------------
  103% OPTION PROCESSING
  104% ----------------------------------------
  105
  106parse_args([],[]).
  107parse_args([Alias|Rest],Opt) :-
  108	arg_alias(Arg,Alias),
  109	!,
  110	parse_args([Arg|Rest],Opt).
  111parse_args([ArgEqualsVal|Rest],Opts) :-
  112        string_chars(ArgEqualsVal,C),
  113        phrase(arg_equals_val(Arg,Val),C),
  114	!,
  115	parse_args([Arg,Val|Rest],Opts).
  116parse_args([MultiArgs|Args],Opts) :-
  117        string_codes(MultiArgs,C),
  118        phrase(multi_args(MultiOpts),C),
  119	!,
  120        append(MultiOpts,RestOpts,Opts),
  121        parse_args(Args,RestOpts).
  122parse_args(Args,Opts) :-
  123        parse_arg(Args,RestArgs,Opt),
  124        !,
  125	(Opt = [_|_] -> ArgOpts = Opt; ArgOpts = [Opt]),
  126	append(ArgOpts,RestOpts,Opts),
  127        parse_args(RestArgs,RestOpts).
  128parse_args([A|Args],[toplevel(A)|Opts]) :-
  129        parse_args(Args,Opts).
  130
  131arg_equals_val(Arg,Val) --> arg_chars(Arg), ['='], !, val_chars(Val).
  132arg_chars(A) --> ['-','-'], char_list(Ac,"="), {atom_chars(A,['-','-'|Ac])}.
  133val_chars(V) --> atom_from_chars(V,"").
  134
  135get_cmd_args(FlatOpts,Opts) :-
  136	(bagof(Arg,arg_from_opts(Arg,FlatOpts),LumpyCore);
  137	    LumpyCore=[]),
  138        flatten(LumpyCore,Core),
  139        concat_string_list_spaced(Core,CoreStr),
  140	biomake_prog(Cmd),
  141	absolute_file_name(Cmd,CmdPath),
  142	working_directory(CWD,CWD),
  143	default_qsub_biomake_args(DQ),
  144	CmdOpts = [biomake_prog(CmdPath),
  145		   biomake_args(CoreStr),
  146		   biomake_cwd(CWD),
  147		   qsub_biomake_args(DQ)],
  148	append(FlatOpts,CmdOpts,Opts).
  149
  150arg_from_opts(Arg,Opts) :-
  151	member(Opt,Opts),
  152	recover_arg(Arg,Opt).
  153
  154multi_args(Opts) --> "-", multi_arg(Opts).
  155multi_arg([Opt|Rest]) --> [C], {char_code('-',H),C\=H,atom_codes(Arg,[H,C])}, !, {parse_arg([Arg],[],Opt)}, !, multi_arg(Rest).
  156multi_arg([]) --> !.
  157
  158:- discontiguous parse_arg/3.   % describes how to parse a cmdline arg into an option
  159:- discontiguous recover_arg/2. % describes how to recover the cmdline arg from the option (finding canonical paths, etc)
  160:- discontiguous simple_arg/2.  % combines parse_arg & recover_arg for simple options with no parameters
  161:- discontiguous arg_alias/2.   % specifies an alias for a cmdline arg
  162:- discontiguous arg_info/3.    % specifies the help text for a cmdline arg
  163
  164parse_arg([Arg|L],L,Opt) :- simple_arg(Arg,Opt).
  165recover_arg(Arg,Opt) :- simple_arg(Arg,Opt).
  166
  167% ----------------------------------------
  168% COMMON OPERATIONS
  169% ----------------------------------------
  170
  171parse_arg(['-h'|L],L,null) :- show_help, !.
  172arg_alias('-h','--help').
  173arg_info('-h','','Show help').
  174
  175show_help :-
  176        writeln('biomake [OPTION...] target1 target2...'),
  177        nl,
  178        writeln('Options:'),
  179	forall(arg_info(X,Args,Info),
  180	       ((bagof(Alias,arg_alias(X,Alias),AliasList); AliasList = []),
  181	        atomic_list_concat([X|AliasList],",",AliasStr),
  182	        format("~w ~w~n    ~w~n",[AliasStr,Args,Info]))),
  183        nl,
  184        writeln('For more info see http://github.com/evoldoers/biomake'),
  185        nl,
  186        halt_success.
  187
  188parse_arg(['-v'|L],L,null) :- show_version, !.
  189arg_alias('-v','--version').
  190arg_info('-v','','Show version').
  191
  192show_version :-
  193        writeln('Biomake v0.1.5'),
  194        writeln('Copyright (C) 2016 Evolutionary Software Foundation, Inc.'),
  195        writeln('Authors: Chris Mungall, Ian Holmes.'),
  196        writeln('This is free software; see the source for copying conditions.'),
  197        writeln('There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A'),
  198        writeln('PARTICULAR PURPOSE.'),
  199        halt_success.
  200
  201simple_arg('-n',dry_run(true)).
  202arg_alias('-n','--dry-run').
  203arg_alias('-n','--recon').
  204arg_alias('-n','--just-print').
  205arg_info('-n','','Print the commands that would be executed, but do not execute them').
  206
  207simple_arg('-B',always_make(true)).
  208arg_alias('-B','--always-make').
  209arg_info('-B','','Always build fresh target even if dependency is up to date').
  210
  211parse_arg(['-f',F|L],L,gnu_makefile(F)).
  212arg_alias('-f','--file').
  213arg_alias('-f','--makefile').
  214recover_arg(['-f',Fabs],gnu_makefile(F)) :- absolute_file_name(F,Fabs).
  215arg_info('-f','GNUMAKEFILE','Use a GNU Makefile as the build specification [default: Makefile]').
  216
  217parse_arg(['-p',F|L],L,makeprog(F)).
  218arg_alias('-p','--prog').
  219arg_alias('-p','--makeprog').
  220recover_arg(['-p',Fabs],makeprog(F)) :- absolute_file_name(F,Fabs).
  221arg_info('-p','MAKEPROG','Use MAKEPROG as the (Prolog) build specification [default: Makeprog]').
  222
  223parse_arg(['-m',Text|L],L,eval_makefile_syntax(Text)).
  224recover_arg(['-m',Text],eval_makefile_syntax(Text)).
  225arg_alias('-m','--eval').
  226arg_alias('-m','--makefile-syntax').
  227arg_info('-m','STRING','Evaluate STRING as GNU Makefile syntax').
  228
  229parse_arg(['-P',Text|L],L,eval_makespec_syntax(Text)).
  230recover_arg(['-P',Text],eval_makespec_syntax(Text)).
  231arg_alias('-P','--eval-prolog').
  232arg_alias('-P','--makeprog-syntax').
  233arg_info('-P','STRING','Evaluate STRING as Prolog Makeprog syntax').
  234
  235parse_arg(['-I',D|L],L,include_dir(D)).
  236arg_alias('-I','--include-dir').
  237recover_arg(['-I',Dabs],include_dir(D)) :- absolute_file_name(D,Dabs).
  238arg_info('-I','DIR','Specify search directory for included Makefiles').
  239
  240parse_arg(['--target',T|L],L,toplevel(T)).
  241arg_info('--target','TARGET','Force biomake to recognize a target even if it looks like an option').
  242
  243parse_arg(['-T',F|L],L,translate_gnu_makefile(F)).
  244arg_alias('-T','--translate').
  245arg_alias('-T','--save-prolog').
  246arg_info('-T','FILE','Translate GNU Makefile to Prolog Makeprog syntax').
  247
  248parse_arg(['-W',F|L],L,what_if(Fs)) :- atom_string(F,Fs).
  249arg_alias('-W','--what-if').
  250arg_alias('-W','--new-file').
  251arg_alias('-W','--assume-new').
  252recover_arg(['-W',F],what_if(F)).
  253arg_info('-W','TARGET','Pretend that TARGET has been modified').
  254
  255parse_arg(['-o',F|L],L,old_file(Fs)) :- atom_string(F,Fs).
  256arg_alias('-o','--old-file').
  257arg_alias('-o','--assume-old').
  258recover_arg(['-o',F],old_file(F)).
  259arg_info('-o','TARGET','Do not remake TARGET, or remake anything on account of it').
  260
  261simple_arg('-k',keep_going_on_error(true)).
  262arg_alias('-k','--keep-going').
  263arg_info('-k','','Keep going after error').
  264
  265simple_arg('-S',keep_going_on_error(false)).
  266arg_alias('-S','--no-keep-going').
  267arg_alias('-S','--stop').
  268arg_info('-S','','Stop after error').
  269
  270simple_arg('-t',touch_only(true)).
  271arg_alias('-t','--touch').
  272arg_info('-t','','Touch files (& update MD5 hashes, if appropriate) instead of running recipes').
  273
  274parse_arg(['-N'|L],L,no_deps(true)).
  275arg_alias('-N','--no-dependencies').
  276arg_info('-N','','Do not test or rebuild dependencies').
  277
  278parse_arg(['-D',Var,Val|L],L,assignment(Var,Val)).
  279arg_alias('-D','--define').
  280parse_arg([VarEqualsVal|L],L,assignment(Var,Val)) :-
  281    string_codes(VarEqualsVal,C),
  282    phrase(makefile_assign(Var,Val),C).
  283recover_arg(VarEqualsVal,assignment(Var,Val)) :-
  284    format(string(VarEqualsVal),"--define ~w ~q",[Var,Val]).
  285arg_info('-D','Var Val','Assign Makefile variables from command line').
  286arg_info('Var=Val','','Alternative syntax for \'-D Var Val\'').
  287
  288makefile_assign(Var,Val) --> makefile_var(Var), "=", makefile_val(Val).
  289makefile_var(A) --> makefile_var_atom_from_codes(A).
  290makefile_val(S) --> "\"", string_from_codes(S,"\""), "\"".
  291makefile_val(S) --> string_from_codes(S," ").
  292
  293% ----------------------------------------
  294% ESOTERIC FEATURES
  295% ----------------------------------------
  296
  297parse_arg(['-l',F|L],L,
  298          goal( (collect_stored_targets(F,[]),
  299                 show_stored_targets
  300                ) )) :-
  301        ensure_loaded(library(biomake/scan)),
  302        !.
  303arg_info('-l','DIRECTORY','Iterates through directory writing metadata on each file found').
  304
  305simple_arg('-s',silent(true)).
  306arg_alias('-s','--quiet').
  307arg_alias('-s','--silent').
  308arg_info('-s','','Silent operation; do not print recipes as they are executed').
  309
  310simple_arg('--one-shell',oneshell(true)).
  311arg_info('--one-shell','','Run recipes in single shell (loosely equivalent to GNU Make\'s .ONESHELL)').
  312
  313% ----------------------------------------
  314% MD5 CHECKSUMS
  315% ----------------------------------------
  316
  317parse_arg(['-H'|L],L,md5(true)) :- ensure_loaded(library(biomake/md5hash)), !.
  318arg_alias('-H','--md5-hash').
  319arg_alias('-H','--md5-checksum').
  320recover_arg(['-H'],md5(true)).
  321arg_info('-H','','Use MD5 hashes instead of timestamps').
  322
  323simple_arg('-C',no_md5_cache(true)).
  324arg_alias('-C','--no-md5-cache').
  325arg_info('-C','','Recompute MD5 checksums whenever biomake is restarted').
  326
  327simple_arg('-M',ignore_md5_timestamp(true)).
  328arg_alias('-M','--no-md5-timestamp').
  329arg_info('-M','','Do not recompute MD5 checksums when timestamps appear stale').
  330
  331% ----------------------------------------
  332% QUEUES
  333% ----------------------------------------
  334
  335parse_arg(['-Q',Qs|L],L,queue(Q)) :-
  336        ensure_loaded(library(biomake/queue)),
  337	atom_string(Q,Qs),
  338	queue_engine(Q),
  339	!.
  340parse_arg(['-Q',Qs|L],L,null) :- format("Warning: unknown queue '~w'~n",Qs), !.
  341arg_alias('-Q','--queue-engine').
  342arg_info('-Q','ENGINE','Queue recipes using ENGINE (supported: poolq,sge,pbs,slurm,test)').
  343
  344parse_arg(['-j',Jobs|L],L,poolq_threads(NJobs)) :- atom_number(Jobs,NJobs).
  345arg_alias('-j','--jobs').
  346arg_info('-j','JOBS','Number of job threads (poolq engine)').
  347
  348parse_arg(['--qsub-exec',X|L],L,qsub_exec(X)).
  349arg_alias('--qsub-exec','--sbatch-exec').
  350arg_info('--qsub-exec','PATH','Path to qsub (sge,pbs) or sbatch (slurm)').
  351
  352parse_arg(['--qdel-exec',X|L],L,qsub_exec(X)).
  353arg_alias('--qdel-exec','--scancel-exec').
  354arg_info('--qdel-exec','PATH','Path to qdel (sge,pbs) or scancel (slurm)').
  355
  356parse_arg(['--queue-args',X|L],L,queue_args(X)).
  357arg_info('--queue-args','\'ARGS\'','Queue-specifying arguments for qsub/qdel (sge,pbs) or sbatch/scancel (slurm)').
  358
  359parse_arg(['--qsub-args',X|L],L,qsub_args(X)).
  360arg_alias('--qsub-args','--sbatch-args').
  361arg_info('--qsub-args','\'ARGS\'','Additional arguments for qsub (sge,pbs) or sbatch (slurm)').
  362
  363parse_arg(['--qsub-use-biomake'|L],L,qsub_use_biomake(true)).
  364arg_alias('--qsub-use-biomake','--sbatch-use-biomake').
  365arg_info('--qsub-use-biomake','','Force qsub/sbatch to always call biomake recursively').
  366
  367parse_arg(['--qsub-biomake-args',X|L],L,qsub_args(X)).
  368parse_arg(['--qsub-biomake-args',X|L],L,qsub_args(X)).
  369arg_alias('--qsub-biomake-args','--sbatch-biomake-args').
  370default_qsub_biomake_args('-N').
  371arg_info('--qsub-biomake-args','\'ARGS\'',S) :-
  372    default_qsub_biomake_args(Default),
  373    format(atom(S),'Arguments passed recursively to biomake by qsub/sbatch (default: ~q)',[Default]).
  374
  375parse_arg(['--qsub-header',X|L],L,qsub_header(X)).
  376arg_alias('--qsub-header','--sbatch-header').
  377arg_info('--qsub-header','\'HEADER\'','Header for qsub (sge,pbs) or sbatch (slurm)').
  378
  379parse_arg(['--qsub-header-file',X|L],L,qsub_header_file(X)).
  380arg_alias('--qsub-header-file','--sbatch-header-file').
  381arg_info('--qsub-header-file','\'FILENAME\'','Header file for qsub (sge,pbs) or sbatch (slurm)').
  382
  383parse_arg(['--qdel-args',X|L],L,qdel_args(X)).
  384arg_alias('--qdel-args','--scancel-args').
  385arg_info('--qdel-args','\'ARGS\'','Additional arguments for qdel (sge,pbs) or scancel (slurm)').
  386
  387parse_arg(['--flush',X|L],L,flush_queue(X)).
  388arg_alias('--flush','--qsub-flush').
  389arg_info('--flush','<target or directory>','Erase all jobs for given target/dir').
  390
  391% ----------------------------------------
  392% DEBUGGING
  393% ----------------------------------------
  394
  395parse_arg(['-d'|L],L,null) :- debug(verbose), debug(build), set_prolog_flag(verbose,normal).
  396arg_info('-d','','[developers] Print debugging messages. Equivalent to \'--debug verbose\'').
  397
  398parse_arg(['--debug',D|L],L,null) :- debug(D), set_prolog_flag(verbose,normal).
  399arg_info('--debug','MSG','[developers] Richer debugging messages. MSG can be verbose, bindrule, build, pattern, makefile, makeprog, md5...').
  400
  401parse_arg(['--trace',Pred|L],L,null) :- trace(Pred), !.
  402arg_info('--trace','PREDICATE','[developers] Print debugging trace for given predicate').
  403
  404parse_arg(['--no-backtrace'|L],L,null) :- disable_backtrace, !.
  405arg_info('--no-backtrace','','[developers] Do not print a backtrace on error')