Merge pull request #228 from DOCGroup/jwillemsen-patch-1
[MPC.git] / modules / Options.pm
blob67bef1a561cb7eca6a2e1d157fdac6d1426bbcbf
1 package Options;
3 # ************************************************************
4 # Description : Process mpc command line options
5 # Author : Chad Elliott
6 # Create Date : 3/20/2003
7 # ************************************************************
9 # ************************************************************
10 # Pragmas
11 # ************************************************************
13 use strict;
15 use DirectoryManager;
16 use StringProcessor;
17 use ProjectCreator;
19 # ************************************************************
20 # Subroutine Section
21 # ************************************************************
23 sub printUsage {
24 my $self = shift;
25 my $msg = shift;
26 my $base = shift;
27 my $version = shift;
28 my @types = @_;
30 print STDERR "ERROR: $msg\n" if (defined $msg);
32 my $spaces = (' ' x (length($base) + 8));
33 print STDERR "$base v$version\n" .
34 "Usage: $base [-global <file>] [-include <directory>] [-recurse]\n" .
35 $spaces . "[-ti <dll | lib | dll_exe | lib_exe>:<file>] [-hierarchy]\n" .
36 $spaces . "[-template <file>] [-relative NAME=VAL] [-base <project>]\n" .
37 $spaces . "[-noreldefs] [-notoplevel] [-static] [-genins] [-use_env]\n" .
38 $spaces . "[-value_template <NAME+=VAL | NAME=VAL | NAME-=VAL>]\n" .
39 $spaces . "[-value_project <NAME+=VAL | NAME=VAL | NAME-=VAL>]\n" .
40 $spaces . "[-make_coexistence] [-feature_file <file name>] [-gendot]\n" .
41 $spaces . "[-expand_vars] [-features <feature definitions>]\n" .
42 $spaces . "[-exclude <directories>] [-name_modifier <pattern>]\n" .
43 $spaces . "[-apply_project] [-version] [-into <directory>]\n" .
44 $spaces . "[-gfeature_file <file name>] [-nocomments]\n" .
45 $spaces . "[-relative_file <file name>] [-for_eclipse]\n" .
46 $spaces . "[-workers <#>] [-workers_dir <dir> | -workers_port <#>]\n" .
47 $spaces . "[-language <";
49 my $olen = length($spaces) + 12;
50 my $len = $olen;
51 my $mlen = 77;
52 my @keys = sort(Creator::validLanguages());
53 for(my $i = 0; $i <= $#keys; $i++) {
54 my $klen = length($keys[$i]);
55 $len += $klen;
56 if ($len > $mlen) {
57 print STDERR "\n$spaces ";
58 $len = $olen + $klen;
60 print STDERR $keys[$i];
61 if ($i != $#keys) {
62 print STDERR ' | ';
63 $len += 3;
66 print STDERR ">]\n",
67 $spaces, "[-type <";
69 $olen = length($spaces) + 8;
70 $len = $olen;
72 ## Sort the project types, but keep those that are the same with different
73 ## version numbers in the right order (i.e., vc8, vc9, vc10, vc11). The vc71
74 ## type is a special case and needs to stay between vc7 and vc8.
75 @keys = sort { if ($a ne 'vc71' && $b ne 'vc71' && $a =~ /^([^\d]+)(\d+)$/) {
76 my($a1, $a2) = ($1, $2);
77 if ($b =~ /^([^\d]+)(\d+)$/ && $a1 eq $1) {
78 return $a2 <=> $2;
81 return $a cmp $b;
82 } @types;
83 for(my $i = 0; $i <= $#keys; $i++) {
84 my $klen = length($keys[$i]);
85 $len += $klen;
86 if ($len > $mlen) {
87 print STDERR "\n$spaces ";
88 $len = $olen + $klen;
90 elsif ($i) {
91 print STDERR ' ';
93 print STDERR $keys[$i];
94 if ($i != $#keys) {
95 print STDERR ' |';
96 $len += 3;
99 print STDERR ">]\n" .
100 $spaces . "[files]\n\n";
102 print STDERR
103 " -base Add <project> as a base project to each generated\n",
104 " project file. Do not provide a file extension, the\n",
105 " .mpb extension will be tried first; if that fails the\n",
106 " .mpc extension will be tried.\n",
107 " -exclude Use this option to exclude directories or files when\n",
108 " searching for input files.\n",
109 " -expand_vars Perform direct expansion, instead of performing relative\n",
110 " replacement with either -use_env or -relative options.\n",
111 " -feature_file Specifies the feature file to read before processing.\n",
112 " The default feature file is default.features under the\n",
113 " config directory.\n",
114 " -features Specifies the feature list to set before processing.\n",
115 " -for_eclipse Generate files for use with eclipse. This is only\n",
116 " useful for make based project types.\n",
117 " -gendot Generate .dot files for use with Graphviz.\n",
118 " -genins Generate .ins files for use with prj_install.pl.\n",
119 " -gfeature_file Specifies the global feature file. The\n",
120 " default value is global.features under the\n",
121 " config directory.\n",
122 " -global Specifies the global input file. Values stored\n",
123 " within this file are applied to all projects.\n",
124 " -hierarchy Generate a workspace in a hierarchical fashion.\n",
125 " -include Specifies a directory to search when looking for base\n",
126 " projects, template input files and templates. This\n",
127 " option can be used multiple times to add directories.\n",
128 " -into Place all output files in a mirrored directory\n",
129 " structure starting at <directory>. This should be a\n",
130 " full path. If any project within the workspace is\n",
131 " referenced via a full path, use of this option is\n",
132 " likely to cause problems.\n",
133 " -language Specify the language preference; possible values are\n",
134 " [", join(', ', sort(Creator::validLanguages())), "]. The default is\n",
135 " ", Creator::defaultLanguage(), ".\n",
136 " -make_coexistence If multiple 'make' based project types are\n",
137 " generated, they will be named such that they can coexist.\n",
138 " -name_modifier Modify output names. The pattern passed to this\n",
139 " parameter will have the '*' portion replaced with the\n",
140 " actual output name. Ex. *_Static\n",
141 " -apply_project When used in conjunction with -name_modifier, it applies\n",
142 " the name modifier to the project name also.\n",
143 " -nocomments Do not place comments in the generated files.\n",
144 " -noreldefs Do not try to generate default relative definitions.\n",
145 " -notoplevel Do not generate the top level target file. Files\n",
146 " are still processed, but no top level file is created.\n",
147 " -recurse Recurse from the current directory and generate from\n",
148 " all found input files.\n",
149 " -relative Any \$() variable in an mpc file that is matched to NAME\n",
150 " is replaced by VAL only if VAL can be made into a\n",
151 " relative path based on the current working directory.\n",
152 " This option can be used multiple times to add multiple\n",
153 " variables.\n",
154 " -relative_file Specifies the relative file to read before processing.\n",
155 " The default relative file is default.rel under the\n",
156 " config directory.\n",
157 " -static Specifies that only static projects will be generated.\n",
158 " By default, only dynamic projects are generated.\n",
159 " -template Specifies the template name (with no extension).\n",
160 " -workers Specifies number of child processes to use to generate\n",
161 " projects.\n",
162 " -workers_dir The directory for storing temporary output files\n",
163 " from the child processes. The default is '/tmp/mpc'\n",
164 " If neither -workers_dir nor -workers_port is used,\n",
165 " -workers_dir is assumed.\n",
166 " -workers_port The port number for the parent listener. If neither\n",
167 " -workers_dir nor -workers_port is used, -workers_dir\n",
168 " is assumed.\n",
169 " -ti Specifies the template input file (with no extension)\n",
170 " for the specific type (ex. -ti dll_exe:vc8exe).\n",
171 " -type Specifies the type of project file to generate. This\n",
172 " option can be used multiple times to generate multiple\n",
173 " types. There is no longer a default.\n",
174 " -use_env Use environment variables for all uses of \$() instead\n",
175 " of the relative replacement values.\n",
176 " -value_project This option allows modification of a project variable\n",
177 " assignment. Use += to add VAL to the NAME's value.\n",
178 " Use -= to subtract and = to override the value.\n",
179 " This can be used to introduce new name value pairs to\n",
180 " a project. However, it must be a valid project\n",
181 " assignment.\n",
182 " -value_template This option allows modification of a template input\n",
183 " name value pair. Use += to add VAL to the NAME's\n",
184 " value. Use -= to subtract and = to override the value.\n",
185 " -version Print the MPC version and exit.\n";
189 sub optionError {
190 #my $self = shift;
191 #my $str = shift;
195 sub path_cleanup {
196 ## Clean up the path as much as possible. For some reason,
197 ## File::Spec->canonpath() on Windows doesn't remove trailing
198 ## /. from the path. (Current versions have fixed this, but
199 ## we'll leave the work-around in case users have an old Perl.)
200 my $p = File::Spec->canonpath($_[0]);
201 $p =~ s/\\/\//g;
202 $p =~ s!/\.$!!;
203 return $p;
207 sub completion_command {
208 my($self, $name, $types) = @_;
209 my $str = "complete $name " .
210 "'c/-/(gendot genins global include type template relative " .
211 "ti static noreldefs notoplevel feature_file use_env " .
212 "value_template value_project make_coexistence language " .
213 "hierarchy exclude name_modifier apply_project version " .
214 "expand_vars gfeature_file nocomments for_eclipse relative_file)/' " .
215 "'c/dll:/f/' 'c/dll_exe:/f/' 'c/lib_exe:/f/' 'c/lib:/f/' " .
216 "'n/-ti/(dll lib dll_exe lib_exe)/:' ";
218 $str .= "'n/-language/(";
219 my @keys = sort(Creator::validLanguages());
220 for(my $i = 0; $i <= $#keys; $i++) {
221 $str .= $keys[$i];
222 $str .= " " if ($i != $#keys);
224 $str .= ")/' 'n/-type/(";
226 @keys = sort keys %$types;
227 for(my $i = 0; $i <= $#keys; $i++) {
228 $str .= $keys[$i];
229 $str .= " " if ($i != $#keys);
231 $str .= ")/'";
232 return $str;
236 sub options {
237 my $self = shift;
238 my $name = shift;
239 my $types = shift;
240 my $defaults = shift;
241 my @args = @_;
242 my @include;
243 my @input;
244 my @creators;
245 my @baseprojs;
246 my %ti;
247 my %relative;
248 my %addtemp;
249 my %addproj;
250 my @exclude;
251 my $global;
252 my $template;
253 my $feature_f;
254 my $gfeature_f;
255 my $relative_f;
256 my @features;
257 my $nmodifier;
258 my $into;
259 my $hierarchy = 0;
260 my $language = ($defaults ? Creator::defaultLanguage() : undef);
261 my $dynamic = ($defaults ? 1 : undef);
262 my $comments = ($defaults ? 1 : undef);
263 my $reldefs = ($defaults ? 1 : undef);
264 my $toplevel = ($defaults ? 1 : undef);
265 my $use_env = ($defaults ? 0 : undef);
266 my $expandvars = ($defaults ? 0 : undef);
267 my $static = ($defaults ? 0 : undef);
268 my $recurse = ($defaults ? 0 : undef);
269 my $makeco = ($defaults ? 0 : undef);
270 my $applypj = ($defaults ? 0 : undef);
271 my $genins = ($defaults ? 0 : undef);
272 my $gendot = ($defaults ? 0 : undef);
273 my $foreclipse = ($defaults ? 0 : undef);
274 my $workers = ($defaults ? 0 : undef);
275 my $workers_dir ;
276 my $workers_port;
278 ## Process the command line arguments
279 for(my $i = 0; $i <= $#args; $i++) {
280 my $arg = $args[$i];
281 $arg =~ s/^--/-/;
283 if ($arg eq '-apply_project') {
284 $applypj = 1;
286 elsif ($arg eq '-complete') {
287 print $self->completion_command($name, $types) . "\n";
288 return undef;
290 elsif ($arg eq '-base') {
291 $i++;
292 if (!defined $args[$i]) {
293 $self->optionError('-base requires an argument');
295 else {
296 push(@baseprojs, $args[$i]);
299 elsif ($arg eq '-type') {
300 $i++;
301 if (!defined $args[$i]) {
302 $self->optionError('-type requires an argument');
304 else {
305 my $type = lc($args[$i]);
306 if (defined $types->{$type}) {
307 my $call = $types->{$type};
308 my $found = 0;
309 foreach my $creator (@creators) {
310 if ($creator eq $call) {
311 $found = 1;
312 last;
315 push(@creators, $call) if (!$found);
317 else {
318 $self->optionError("Invalid type: $args[$i]");
322 elsif ($arg eq '-exclude') {
323 $i++;
324 if (defined $args[$i]) {
325 foreach my $exclude (split(',', $args[$i])) {
326 push(@exclude, DirectoryManager::mpc_glob(undef, $exclude));
329 else {
330 $self->optionError('-exclude requires a ' .
331 'comma separated list argument');
334 elsif ($arg eq '-expand_vars') {
335 $expandvars = 1;
337 elsif ($arg eq '-feature_file') {
338 $i++;
339 $feature_f = $args[$i];
340 if (!defined $feature_f) {
341 $self->optionError('-feature_file requires a file name argument');
344 elsif ($arg eq '-features') {
345 $i++;
346 if (defined $args[$i]) {
347 push(@features, split(',', $args[$i]));
349 else {
350 $self->optionError('-features requires a comma separated list argument');
353 elsif ($arg eq '-for_eclipse') {
354 $foreclipse = 1;
356 elsif ($arg eq '-gfeature_file') {
357 $i++;
358 $gfeature_f = $args[$i];
359 if (!defined $gfeature_f) {
360 $self->optionError('-gfeature_file ' .
361 'requires a file name argument');
364 elsif ($arg eq '-relative_file') {
365 $i++;
366 $relative_f = $args[$i];
367 if (!defined $relative_f) {
368 $self->optionError('-relative_file ' .
369 'requires a file name argument');
372 elsif ($arg eq '-gendot') {
373 $gendot = 1;
375 elsif ($arg eq '-genins') {
376 $genins = 1;
378 elsif ($arg eq '-global') {
379 $i++;
380 $global = $args[$i];
381 if (!defined $global) {
382 $self->optionError('-global requires a file name argument');
385 elsif ($arg eq '-help') {
386 $self->optionError();
388 elsif ($arg eq '-hierarchy') {
389 $hierarchy = 1;
391 elsif ($arg eq '-include') {
392 $i++;
393 my $include = $args[$i];
394 if (!defined $include) {
395 $self->optionError('-include requires a directory argument');
397 else {
398 ## If the specified include path is relative, expand it based on
399 ## the current working directory.
400 if ($include !~ /^[\/\\]/ &&
401 $include !~ /^[A-Za-z]:[\/\\]?/) {
402 $include = DirectoryManager::getcwd() . '/' . $include;
405 push(@include, $include);
408 elsif ($arg eq '-into') {
409 $i++;
410 $into = $args[$i];
411 if (!defined $into) {
412 $self->optionError('-into requires a directory argument');
415 elsif ($arg eq '-language') {
416 $i++;
417 $language = $args[$i];
418 if (!defined $language) {
419 $self->optionError('-language requires a language argument');
421 elsif (!Creator::isValidLanguage($language)) {
422 $self->optionError("$language is not a valid language");
425 elsif ($arg eq '-make_coexistence') {
426 $makeco = 1;
428 elsif ($arg eq '-name_modifier') {
429 $i++;
430 my $nmod = $args[$i];
431 if (!defined $nmod) {
432 $self->optionError('-name_modifier requires a modifier argument');
434 else {
435 $nmodifier = $nmod;
438 elsif ($arg eq '-nocomments') {
439 $comments = 0;
441 elsif ($arg eq '-noreldefs') {
442 $reldefs = 0;
444 elsif ($arg eq '-notoplevel') {
445 $toplevel = 0;
447 elsif ($arg eq '-recurse') {
448 $recurse = 1;
450 elsif ($arg eq '-template') {
451 $i++;
452 $template = $args[$i];
453 if (!defined $template) {
454 $self->optionError('-template requires a file name argument');
457 elsif ($arg eq '-relative') {
458 $i++;
459 my $rel = $args[$i];
460 if (!defined $rel) {
461 $self->optionError('-relative requires a variable assignment argument');
463 else {
464 if ($rel =~ /(\w+)\s*=\s*(.*)/) {
465 my $name = $1;
466 my $val = $2;
467 $val =~ s/^\s+//;
468 $val =~ s/\s+$//;
470 ## If the specified path is relative, expand it based on
471 ## the current working directory.
472 if ($val !~ /^[\/\\]/ &&
473 $val !~ /^[A-Za-z]:[\/\\]?/) {
474 my $orig = $val;
475 $val = DirectoryManager::getcwd() . '/' . $val;
477 ## Inform the user that we're changing the value that they
478 ## provided.
479 $self->warning("-relative value $orig has been changed to\n$val");
482 $relative{$name} = path_cleanup($val);
484 else {
485 $self->optionError('Invalid argument to -relative');
489 elsif ($arg eq '-workers') {
490 $i++;
491 $workers = $args[$i];
493 if (!defined $workers) {
494 $self->optionError('-workers requires an argument');
497 elsif ($arg eq '-workers_dir') {
498 $i++;
499 $workers_dir = $args[$i];
501 if (!defined $workers_dir) {
502 $self->optionError('-workers_dir requires an argument');
505 if (! -d $workers_dir) {
506 $self->diagnostic("Creating temp directory $workers_dir");
507 unless (mkdir $workers_dir) {
508 $self->optionError("Unable to create temp directory $workers_dir");
512 elsif ($arg eq '-workers_port') {
513 $i++;
514 $workers_port = $args[$i];
516 if (!defined $workers_port) {
517 $self->optionError('-workers_port requires an argument');
520 if ($workers_port < 0 || $workers_port > 65535) {
521 $self->optionError('valid -workers_port range is between 0 and 65535');
524 elsif ($arg eq '-ti') {
525 $i++;
526 my $tmpi = $args[$i];
527 if (!defined $tmpi) {
528 $self->optionError('-ti requires a template input argument');
530 else {
531 if ($tmpi =~ /((dll|lib|dll_exe|lib_exe):)?(.*)/) {
532 my $key = $2;
533 my $name = $3;
534 if (defined $key) {
535 $ti{$key} = $name;
537 else {
538 foreach my $type ('dll', 'lib', 'dll_exe', 'lib_exe') {
539 $ti{$type} = $name;
543 else {
544 $self->optionError("Invalid -ti argument: $tmpi");
548 elsif ($arg eq '-use_env') {
549 $use_env = 1;
551 elsif ($arg eq '-value_template') {
552 $i++;
553 my $value = $args[$i];
554 if (!defined $value) {
555 $self->optionError('-value_template requires a variable assignment argument');
557 else {
558 my @values;
559 my $pc = new ProjectCreator();
560 if ($pc->parse_assignment($value, \@values)) {
561 $addtemp{$values[1]} = [] if (!defined $addtemp{$values[1]});
562 ## The extra parameter (3rd) indicates that this value was
563 ## specified on the command line. This "extra parameter" is
564 ## used in ProjectCreator::update_template_variable().
565 push(@{$addtemp{$values[1]}}, [$values[0], $values[2], 1]);
567 my $keywords = ProjectCreator::getKeywords();
568 if (defined $$keywords{$values[1]}) {
569 $self->warning($values[1] . ' is a project keyword; you ' .
570 'should use -value_project instead.');
573 else {
574 $self->optionError('Invalid argument to -value_template');
578 elsif ($arg eq '-value_project') {
579 $i++;
580 my $value = $args[$i];
581 if (!defined $value) {
582 $self->optionError('-value_project requires a variable assignment argument');
584 else {
585 my @values;
586 my $pc = new ProjectCreator();
587 if ($pc->parse_assignment($value, \@values)) {
588 $addproj{$values[1]} = [] if (!defined $addproj{$values[1]});
589 push(@{$addproj{$values[1]}}, [$values[0], $values[2]]);
591 else {
592 $self->optionError('Invalid argument to -value_project');
596 elsif ($arg eq '-version') {
597 print 'MPC v', Version::get(), "\n";
598 return undef;
600 elsif ($arg eq '-static') {
601 $static = 1;
602 $dynamic = 0;
604 elsif ($arg =~ /^-/) {
605 $self->optionError("Unknown option: $arg");
607 else {
608 push(@input, path_cleanup($arg));
612 ## The following can be time consuming, so we'll only do it if we know
613 ## we're debugging.
614 $self->dump_base_projects(\@include) if ($self->get_debug_level());
616 return {'global' => $global,
617 'feature_file' => $feature_f,
618 'gfeature_file' => $gfeature_f,
619 'relative_file' => $relative_f,
620 'features' => \@features,
621 'for_eclipse' => $foreclipse,
622 'include' => \@include,
623 'input' => \@input,
624 'comments' => $comments,
625 'creators' => \@creators,
626 'baseprojs' => \@baseprojs,
627 'template' => $template,
628 'ti' => \%ti,
629 'dynamic' => $dynamic,
630 'static' => $static,
631 'relative' => \%relative,
632 'reldefs' => $reldefs,
633 'workers' => $workers,
634 'workers_dir' => $workers_dir,
635 'workers_port' => $workers_port,
636 'toplevel' => $toplevel,
637 'recurse' => $recurse,
638 'addtemp' => \%addtemp,
639 'addproj' => \%addproj,
640 'make_coexistence' => $makeco,
641 'hierarchy' => $hierarchy,
642 'exclude' => \@exclude,
643 'name_modifier' => $nmodifier,
644 'apply_project' => $applypj,
645 'gendot' => $gendot,
646 'genins' => $genins,
647 'into' => $into,
648 'language' => $language,
649 'use_env' => $use_env,
650 'expand_vars' => $expandvars,
655 sub is_set {
656 my($self, $key, $options) = @_;
658 if (defined $options->{$key}) {
659 if (UNIVERSAL::isa($options->{$key}, 'ARRAY')) {
660 return 'ARRAY' if (defined $options->{$key}->[0]);
662 elsif (UNIVERSAL::isa($options->{$key}, 'HASH')) {
663 my @keys = keys %{$options->{$key}};
664 return 'HASH' if (defined $keys[0]);
666 else {
667 return 'SCALAR';
671 return undef;
674 sub dump_base_projects {
675 my($self, $includes) = @_;
676 my $dp = new FileHandle();
678 ## Go through each include directory and print all of the possible base
679 ## projects. Both .mpb and .mpc files can be base projects.
680 foreach my $dir (@$includes) {
681 if (opendir($dp, $dir)) {
682 $self->debug("Base projects from $dir: " .
683 join(' ', grep(/\.mp[bc]$/, readdir($dp))));
684 closedir($dp);