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