source: ProjectBuilder/devel/pb-modules/lib/ProjectBuilder/Conf.pm@ 2494

Last change on this file since 2494 was 2494, checked in by Bruno Cornec, 4 years ago

pb_conf_write also needs multiple YAML implementation support

File size: 16.8 KB
Line 
1#!/usr/bin/perl -w
2#
3# ProjectBuilder Conf module
4# Conf files subroutines brought by the the Project-Builder project
5# which can be easily used by wahtever perl project
6#
7# Copyright B. Cornec 2007-today
8# Eric Anderson's changes are (c) Copyright 2012 Hewlett Packard
9# Provided under the GPL v2
10#
11# $Id$
12#
13
14package ProjectBuilder::Conf;
15
16use strict;
17use Carp qw/cluck confess/;
18use Data::Dumper;
19use ProjectBuilder::Base;
20use ProjectBuilder::Version;
21
22# Inherit from the "Exporter" module which handles exporting functions.
23
24use vars qw($VERSION $REVISION $PBCONFVER @ISA @EXPORT);
25use Exporter;
26
27# Export, by default, all the functions into the namespace of
28# any code which uses this module.
29
30our @ISA = qw(Exporter);
31our @EXPORT = qw(pb_conf_init pb_conf_add pb_conf_read pb_conf_read_if pb_conf_write pb_conf_get pb_conf_get_if pb_conf_get_all pb_conf_get_hash pb_conf_cache pb_conf_update_v0 pb_conf_get_in_hash_if);
32($VERSION,$REVISION,$PBCONFVER) = pb_version_init();
33
34# Global hash of conf files
35# Key is the conf file name
36# Value is its rank
37my %pbconffiles;
38
39# Global hash of conf file content
40# Key is the config keyword
41# Value is a hash whose key depends on the nature of the config keyword as documented
42# and value is the confguration value
43# We consider that values can not change during the life of pb
44my $h = ();
45
46=pod
47
48=head1 NAME
49
50ProjectBuilder::Conf, part of the project-builder.org - module dealing with configuration files
51
52=head1 DESCRIPTION
53
54This modules provides functions dealing with configuration files.
55
56=head1 SYNOPSIS
57
58 use ProjectBuilder::Conf;
59
60 #
61 # Read hash codes of values from a configuration file and return table of pointers
62 #
63 my ($k1, $k2) = pb_conf_read_if("$ENV{'HOME'}/.pbrc.yml","key1","key2");
64 my ($k) = pb_conf_read("$ENV{'HOME'}/.pbrc.yml","key");
65
66=head1 USAGE
67
68The configuration files are loaded in a specific order from most generic to the most specific
69to allow for overwrite to work:
70
71For recent versions of pb (>= 0.15):
721. /usr/share/pb/pb.yml - the read-only system conf file provided by install
732. /etc/pb/pb.yml - the same global conf file given to the sysadmin in order to make system wide modifications
743. /path/to/project.yml - Configuration file for the project we're building for
754. /vm|vepath/to/.pbrc.yml - configuration file for VM, VE or RM specific parameters. Cumulative should be orthogonal
765. $HOME/.pbrc.yml - user's configuration file
77
78For versions of pb up to 0.14:
791. /usr/share/pb/pb.conf - the read-only system conf file provided by install
802. /etc/pb/pb.conf - the same global conf file given to the sysadmin in order to make system wide modifications
813. /path/to/project.pb - Configuration file for the project we're building for
824. /(vm|ve|rm)path/to/.pbrc - configuration file for VM, VE or RM specific parameters. Cumulative should be orthogonal
835. $HOME/.pbrc - user's configuration file
84
85The format of the configuration file is as follows:
86
87For recent versions of pb (>= 0.15):
88YAML format is now used - The version of the configuration files is
89
90Supposing the file is called "$ENV{'HOME'}/.pbrc.yml", containing the following:
91
92 $ cat $HOME/.pbrc.yml
93 ---
94 pbver:
95 - pb: 3
96 - default: 1
97 pblist:
98 - pb: 12,25
99
100calling it like this:
101
102 my ($k1, $k2) = pb_conf_read_if("$ENV{'HOME'}/.pbrc.yml","pbver","pblist");
103
104will allow to get the mapping:
105
106 $k1->{'pb'} contains 3
107 $k1->{'default'} contains 1
108 $k2->{'pb'} contains 12,25
109
110For versions of pb up to 0.14:
111An own format was used - The version of the configuration files is 0
112
113key tag = value1,value2,...
114
115Supposing the file is called "$ENV{'HOME'}/.pbrc", containing the following:
116
117 $ cat $HOME/.pbrc
118 pbver pb = 3
119 pbver default = 1
120 pblist pb = 12,25
121
122calling it like this:
123
124 my ($k1, $k2) = pb_conf_read_if("$ENV{'HOME'}/.pbrc","pbver","pblist");
125
126will allow to get the mapping:
127
128 $k1->{'pb'} contains 3
129 $k1->{'default'} contains 1
130 $k2->{'pb'} contains 12,25
131
132Valid chars for keys and tags are letters, numbers, '-' and '_'.
133
134=over 4
135
136=item B<pb_conf_init>
137
138This function setup the environment PBPROJ for project-builder function usage from other projects.
139The first parameter is the project name.
140It sets up environment variables (PBPROJ)
141
142=cut
143
144sub pb_conf_init {
145
146my $proj=shift;
147
148pb_log(1,"Entering pb_conf_init\n");
149#
150# Check project name
151# Could be with env var PBPROJ
152# or option -p
153# if not defined take the first in conf file
154#
155if ((defined $ENV{'PBPROJ'}) &&
156 (not defined $proj)) {
157 pb_log(2,"PBPROJ env var setup ($ENV{'PBPROJ'}) so using it\n");
158 $proj = $ENV{'PBPROJ'};
159}
160
161if (defined $proj) {
162 $ENV{'PBPROJ'} = $proj;
163} else {
164 $ENV{'PBPROJ'} = "default";
165}
166pb_log(1,"PBPROJ = $ENV{'PBPROJ'}\n");
167}
168
169
170=item B<pb_conf_cache>
171
172This function caches the configuration file content passed as first parameter into the hash passed in second parameter
173It returns the modified hash
174Can be used in correlation with the %h hash to store permanently values or not if temporarily.
175
176=cut
177
178sub pb_conf_cache {
179
180my $cf = shift;
181my $lh = shift;
182
183my $ldfunc;
184
185# Read the content of the config file and cache it in the %h hash then available for queries
186if ($PBCONFVER < 1) {
187 open(CONF,$cf) || (cluck "Unable to open $cf" && return($lh));
188 # This is the original conf file format for versions up to 0.14
189 while(<CONF>) {
190 next if (/^#/);
191 if (/^\s*([A-z0-9-_.]+)\s+([[A-z0-9-_.\?\[\]\*\+\\]+)\s*=\s*(.*)$/) {
192 pb_log(3,"DEBUG: 1:$1 2:$2 3:$3\n");
193 my ($what, $var, $value) = ($1, $2, $3);
194 # Add support for multi-lines
195 while ($value =~ s/\\\s*$//o) {
196 $_ = <CONF>;
197 die "Still processing continuations for $what $var at EOF" if (not defined $_);
198 s/[\r\n]//go;
199 $value .= "\n$_";
200 }
201 $lh->{$what}->{$var}=$value;
202 } elsif ((/^\s*#/o) || (/^\s*$/o)) {
203 # ignore
204 } else {
205 chomp();
206 warn "unexpected line '$_' in $cf";
207 }
208 }
209 close(CONF);
210} else {
211 eval {
212 require YAML::XS;
213 YAML::XS->import();
214 };
215 if ($@) {
216 eval {
217 require YAML;
218 YAML->import();
219 };
220 if ($@) {
221 eval {
222 # No YAML found using a more std but less complete one. Old perl only
223 require Module::Build::YAML;
224 Module::Build::YAML->import();
225 };
226 if ($@) {
227 eval {
228 # No YAML found using a more std but less complete one. Old perl only
229 require YAML::Tiny;
230 YAML::Tiny->import();
231 };
232 if ($@) {
233 eval {
234 # No YAML found using an embedded pne
235 require ProjectBuilder::YAML;
236 ProjectBuilder::YAML->import();
237 };
238 if ($@) {
239 # Here we should be in the setup phase, with an embedded YAML code
240 $ldfunc = \&LoadFile;
241 #die "Unable to handle YAML configuration files without a YAML.pm module\n";
242 } else {
243 $ldfunc = \&ProjectBuilder::YAML::LoadFile;
244 }
245 } else {
246 $ldfunc = \&YAML::Tiny::LoadFile;
247 }
248 } else {
249 $ldfunc = \&Module::Build::YAML::LoadFile;
250 }
251 } else {
252 $ldfunc = \&YAML::LoadFile;
253 }
254 } else {
255 $ldfunc = \&YAML::XS::LoadFile;
256 }
257
258 # Have we already handled that conf file ?
259 next if ((defined $lh) && (defined $lh->{'__cf'}) && (defined $lh->{'__cf'}->{$cf}) && ($lh->{'__cf'}->{$cf} eq 1));
260
261 pb_log(1,"Loading YAML conf file $cf\n");
262 my $lh0;
263 eval { $lh0 = $ldfunc->($cf); };
264 if ($@) {
265 # Repeat to get the YAML error line
266 $lh0 = $ldfunc->($cf);
267 die "Unable to analyze YAML conf file $cf\n";
268 }
269 foreach my $k (keys %$lh0) {
270 if (defined $lh->{$k}) {
271 foreach my $k2 (keys %{$lh0->{$k}}) {
272 $lh->{$k}->{$k2} = $lh0->{$k}->{$k2};
273 }
274 } else {
275 $lh->{$k} = $lh0->{$k};
276 }
277 }
278 # Remember we've seen that conf file
279 $lh->{'__cf'}->{$cf} = 1;
280}
281return($lh);
282}
283
284=item B<pb_conf_add>
285
286This function adds the configuration file to the list last, and cache their content in the %h hash
287
288=cut
289
290sub pb_conf_add {
291
292my $lh;
293
294foreach my $cf (@_) {
295 if (! -r $cf) {
296 pb_log(0,"WARNING: pb_conf_add can not read $cf\n");
297 next;
298 }
299 # Skip already used conf files
300 return($lh) if (defined $pbconffiles{$cf});
301
302 pb_log(2,"DEBUG: pb_conf_add with $cf\n");
303 # The new conf file overload values already managed
304 my $num = keys %pbconffiles;
305 pb_log(2,"DEBUG: pb_conf_cache of $cf at position $num\n");
306 $pbconffiles{$cf} = $num;
307
308 # Read the content of the config file
309 $lh = pb_conf_cache($cf,$lh);
310 # and cache it in the %h hash for further queries but after the previous
311 # as we load conf files in reverse order (most precise first)
312 pb_conf_add_last_in_hash($lh)
313}
314}
315
316
317=item B<pb_conf_read_if>
318
319This function returns a table of pointers on hashes
320corresponding to the keys in a configuration file passed in parameter.
321If that file doesn't exist, it returns undef.
322
323The file read is forgotten after its usage. If you want permanent caching of the data, use pb_conf_add then pb_conf_get
324
325=cut
326
327sub pb_conf_read_if {
328
329my $conffile = shift;
330my @param = @_;
331
332open(CONF,$conffile) || return((undef));
333close(CONF);
334return(pb_conf_read($conffile,@param));
335}
336
337=item B<pb_conf_read>
338
339This function is similar to B<pb_conf_read_if> except that it dies when the file in parameter doesn't exist.
340
341=cut
342
343sub pb_conf_read {
344
345my $conffile = shift;
346my @param = @_;
347my @ptr;
348my $lh;
349
350$lh = pb_conf_cache($conffile,$lh);
351
352foreach my $param (@param) {
353 push @ptr,$lh->{$param};
354}
355return(@ptr);
356}
357
358=item B<pb_conf_write>
359
360This function writes in the file passed as first parameter the hash of values passed as second parameter
361
362=cut
363
364sub pb_conf_write {
365
366my $conffile = shift;
367my $h = shift;
368my $dpfunc;
369
370confess "No configuration file defined to write into !" if (not defined $conffile);
371confess "No hash defined to read from !" if (not defined $h);
372open(CONF,"> $conffile") || (cluck "Unable to write into $conffile" && return);
373
374if ($PBCONFVER < 1) {
375 # This is the original conf file format for versions up to 0.14
376 foreach my $p (sort keys %$h) {
377 my $j = $h->{$p};
378 foreach my $k (sort keys %$j) {
379 print CONF "$p $k = $j->{$k}\n";
380 }
381 }
382} else {
383 # This is the new YAML format
384 eval {
385 require YAML::XS;
386 YAML::XS->import();
387 };
388 if ($@) {
389 eval {
390 require YAML;
391 YAML->import();
392 };
393 if ($@) {
394 eval {
395 # No YAML found using a more std but less complete one. Old perl only
396 require Module::Build::YAML;
397 Module::Build::YAML->import();
398 };
399 if ($@) {
400 eval {
401 # No YAML found using a more std but less complete one. Old perl only
402 require YAML::Tiny;
403 YAML::Tiny->import();
404 };
405 if ($@) {
406 eval {
407 # No YAML found using an embedded pne
408 require ProjectBuilder::YAML;
409 ProjectBuilder::YAML->import();
410 };
411 if ($@) {
412 # Here we should be in the setup phase, with an embedded YAML code
413 $dpfunc = \&LoadFile;
414 #die "Unable to handle YAML configuration files without a YAML.pm module\n";
415 } else {
416 $dpfunc = \&ProjectBuilder::YAML::Dump;
417 }
418 } else {
419 $dpfunc = \&YAML::Tiny::Dump;
420 }
421 } else {
422 $dpfunc = \&Module::Build::YAML::Dump;
423 }
424 } else {
425 $dpfunc = \&YAML::Dump;
426 }
427 } else {
428 $dpfunc = \&YAML::XS::Dump;
429 }
430
431 pb_log(1,"Writing YAML conf file $conffile\n");
432 delete $h->{'__cf'};
433 print CONF $dpfunc->($h);
434}
435close(CONF);
436}
437
438
439
440=item B<pb_conf_get_in_hash_if>
441
442This function returns a table, corresponding to a set of values queried in the hash passed in parameter or undef if it doesn't exist.
443It takes a table of keys as an input parameter.
444
445=cut
446
447sub pb_conf_get_in_hash_if {
448
449my $lh = shift || return(());
450my @params = @_;
451my @ptr = ();
452
453pb_log(3,"DEBUG: pb_conf_get_in_hash_if on params ".join(' ',@params)."\n");
454foreach my $k (@params) {
455 push @ptr,$lh->{$k};
456}
457
458pb_log(3,"DEBUG: pb_conf_get_in_hash_if returns\n".Dumper(@ptr));
459return(@ptr);
460}
461
462
463
464=item B<pb_conf_get_if>
465
466This function returns a table, corresponding to a set of values queried in the %h hash or undef if it doen't exist. It takes a table of keys as an input parameter.
467
468=cut
469
470sub pb_conf_get_if {
471
472my @param = @_;
473my @return = pb_conf_get_in_hash_if($h,@_);
474my $proj = undef;
475
476if (not defined $ENV{'PBPROJ'}) {
477 $proj = "unknown";
478} else {
479 $proj = $ENV{'PBPROJ'};
480}
481
482foreach my $i (0..$#param) {
483 if (not defined $return[$i]->{$proj}) {
484 $return[$i]->{$proj} = $return[$i]->{'default'} if (defined $return[$i]->{'default'});
485 }
486}
487return(@return);
488}
489
490=item B<pb_conf_add_last_in_hash>
491
492This function merges the values passed in the hash parameter into the %h hash, but only if itdoesn't already contain a value, or if the value is more precise (real value instead of default)
493
494It is used internally by pb_conf_add and is not exported.
495
496=cut
497
498sub pb_conf_add_last_in_hash {
499
500my $ptr = shift;
501
502return if (not defined $ptr);
503# TODO: test $ptr is a hash pointer
504
505# When called without correct initialization, try to work anyway with default as project
506pb_conf_init("default") if (not defined $ENV{'PBPROJ'});
507
508my @params = (sort keys %$ptr);
509
510# Everything is returned via @h
511# @h contains the values overloading what @ptr may contain.
512my @h = pb_conf_get_in_hash_if($h,@params);
513my @ptr = pb_conf_get_in_hash_if($ptr,@params);
514
515my $p1;
516my $p2;
517
518pb_log(3,"DEBUG: pb_conf_add_last_in_hash params: ".Dumper(@params)."\n");
519pb_log(3,"DEBUG: pb_conf_add_last_in_hash current hash: ".Dumper(@h)."\n");
520pb_log(3,"DEBUG: pb_conf_add_last_in_hash new inputs: ".Dumper(@ptr)."\n");
521
522foreach my $i (0..$#params) {
523 $p1 = $h[$i];
524 $p2 = $ptr[$i];
525 # Always try to take the param from h in priority
526 # in order to mask what could be defined already in ptr
527 if (not defined $p2) {
528 # exit if no p1 either
529 next if (not defined $p1);
530 } else {
531 # Ref found in p2
532 if (not defined $p1) {
533 # No ref in p1 so use p2's value
534 $p1 = $p2;
535 } else {
536 # Both are defined - handling the overloading
537 # Now copy back into p1 all p2 content
538 # as p1 content always has priority over p2
539 if (not defined $p1->{$ENV{'PBPROJ'}}) {
540 if (defined $p2->{$ENV{'PBPROJ'}}) {
541 $p1->{$ENV{'PBPROJ'}} = $p2->{$ENV{'PBPROJ'}};
542 }
543 }
544 # Now copy back into p1 all p2 content which doesn't exist in p1
545 # # p1 content always has priority over p2
546 foreach my $k (keys %$p2) {
547 $p1->{$k} = $p2->{$k} if (not defined $p1->{$k});
548 }
549 }
550 }
551 $h->{$params[$i]} = $p1;
552}
553pb_log(2,"DEBUG: pb_conf_add_last_in_hash output: ".Dumper($h)."\n");
554}
555
556=item B<pb_conf_get>
557
558This function is the same B<pb_conf_get_if>, except that it tests each returned value as they need to exist in that case.
559
560=cut
561
562sub pb_conf_get {
563
564my @param = @_;
565my @return = pb_conf_get_if(@param);
566my $proj = undef;
567
568if (not defined $ENV{'PBPROJ'}) {
569 $proj = "unknown";
570} else {
571 $proj = $ENV{'PBPROJ'};
572}
573
574confess "No params found for $proj" if (not @return);
575
576foreach my $i (0..$#param) {
577 confess "No $param[$i] defined for $proj" if (not defined $return[$i]);
578}
579return(@return);
580}
581
582
583=item B<pb_conf_get_all>
584
585This function returns an array with all configuration parameters
586
587=cut
588
589sub pb_conf_get_all {
590
591return(sort keys %$h);
592}
593
594
595=item B<pb_conf_get_hash>
596
597This function returns a pointer to the hash with all configuration parameters
598
599=cut
600
601sub pb_conf_get_hash {
602
603return($h);
604}
605
606=item B<pb_conf_update_v0>
607
608This function transform the old configuration v0 file as first param into a new v1 one as second param
609
610=cut
611
612sub pb_conf_update_v0 {
613
614my $orig = shift;
615my $dest = shift;
616
617open(ORIG,$orig) || (cluck "Unable to open $orig" && return);
618confess "Will not erase existing $dest while transforming $orig" if (-f $dest);
619open(DEST,"> $dest") || (cluck "Unable to write into $dest" && close(ORIG) && return);
620print DEST "---\n";
621my $pbconfverbkp = $PBCONFVER;
622# We force migration from v0 to v1
623$PBCONFVER = 0;
624my $lh0;
625my $lh1;
626$lh0 = pb_conf_cache($orig,$lh0);
627pb_log(2,"lh0:\n".Dumper($lh0)."\n");
628$PBCONFVER = $pbconfverbkp;
629
630pb_log(0,"Converting v0 conf file $orig to v1 conf file $dest\n");
631# We can't just write the YAML if we want to keep comments !
632while (<ORIG>) {
633 if ($_ =~ /^#/) {
634 # Keep comments
635 print DEST $_;
636 } elsif ($_ =~ /^\s*$/) {
637 # Replace empty lines by comments
638 print DEST "#\n";;
639 } else {
640 if (/^\s*([A-z0-9-_]+)\s+(.+)$/) {
641 # Handle parameters
642 my ($param,$void) = ($1, $2);
643 if (not defined $lh1->{$param}) {
644 pb_log(2,"Converting parameter $param\n");
645 my $param2 = $param;
646 # param pburl in v0 is now pbprojurl in v1
647 $param2 =~ s/pburl/pbprojurl/;
648 print DEST "$param2:\n";
649 foreach my $k (keys %{$lh0->{$param}}) {
650 pb_log(2,"Handling key $k\n");
651 if ($lh0->{$param}->{$k} =~ /^\s*$/) {
652 print DEST " $k: !!str \"\"\n";
653 } else {
654 print DEST " $k: $lh0->{$param}->{$k}\n";
655 }
656 }
657 $lh1->{$param} = 1;
658 }
659 } else {
660 pb_log(0,"Unable to convert line $_\n");
661 }
662 }
663}
664close(ORIG);
665close(DEST);
666return();
667}
668
669=back
670
671=head1 WEB SITES
672
673The main Web site of the project is available at L<http://www.project-builder.org/>. Bug reports should be filled using the trac instance of the project at L<http://trac.project-builder.org/>.
674
675=head1 USER MAILING LIST
676
677None exists for the moment.
678
679=head1 AUTHORS
680
681The Project-Builder.org team L<http://trac.project-builder.org/> lead by Bruno Cornec L<mailto:bruno@project-builder.org>.
682
683=head1 COPYRIGHT
684
685Project-Builder.org is distributed under the GPL v2.0 license
686described in the file C<COPYING> included with the distribution.
687
688=cut
689
690
6911;
Note: See TracBrowser for help on using the repository browser.