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
RevLine 
[405]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#
[2488]7# Copyright B. Cornec 2007-today
[1528]8# Eric Anderson's changes are (c) Copyright 2012 Hewlett Packard
9# Provided under the GPL v2
10#
[405]11# $Id$
12#
13
14package ProjectBuilder::Conf;
15
16use strict;
[2362]17use Carp qw/cluck confess/;
[405]18use Data::Dumper;
19use ProjectBuilder::Base;
[1148]20use ProjectBuilder::Version;
[405]21
22# Inherit from the "Exporter" module which handles exporting functions.
23
[2279]24use vars qw($VERSION $REVISION $PBCONFVER @ISA @EXPORT);
[405]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);
[2426]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);
[2254]32($VERSION,$REVISION,$PBCONFVER) = pb_version_init();
[405]33
[898]34# Global hash of conf files
35# Key is the conf file name
36# Value is its rank
37my %pbconffiles;
[409]38
[1495]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
[898]43# We consider that values can not change during the life of pb
[1495]44my $h = ();
[898]45
[405]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 #
[2252]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");
[405]65
66=head1 USAGE
67
[2152]68The configuration files are loaded in a specific order from most generic to the most specific
69to allow for overwrite to work:
70
[2250]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:
[2241]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
[2152]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
[2250]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
[2257]93 ---
[2250]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
[405]134=over 4
135
[505]136=item B<pb_conf_init>
137
[898]138This function setup the environment PBPROJ for project-builder function usage from other projects.
[505]139The first parameter is the project name.
[898]140It sets up environment variables (PBPROJ)
[505]141
142=cut
143
144sub pb_conf_init {
145
[1907]146my $proj=shift;
[505]147
[1495]148pb_log(1,"Entering pb_conf_init\n");
[1584]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
[505]161if (defined $proj) {
162 $ENV{'PBPROJ'} = $proj;
163} else {
164 $ENV{'PBPROJ'} = "default";
165}
[1495]166pb_log(1,"PBPROJ = $ENV{'PBPROJ'}\n");
[505]167}
168
169
[1495]170=item B<pb_conf_cache>
[505]171
[2250]172This function caches the configuration file content passed as first parameter into the hash passed in second parameter
[1495]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
[2249]183my $ldfunc;
184
[2077]185# Read the content of the config file and cache it in the %h hash then available for queries
[2279]186if ($PBCONFVER < 1) {
[2434]187 open(CONF,$cf) || (cluck "Unable to open $cf" && return($lh));
[2176]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");
[2253]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();
[2256]206 warn "unexpected line '$_' in $cf";
[2176]207 }
[1495]208 }
[2176]209 close(CONF);
210} else {
[2249]211 eval {
[2491]212 require YAML::XS;
213 YAML::XS->import();
[2249]214 };
215 if ($@) {
216 eval {
[2491]217 require YAML;
218 YAML->import();
[2249]219 };
220 if ($@) {
[2484]221 eval {
222 # No YAML found using a more std but less complete one. Old perl only
[2491]223 require Module::Build::YAML;
224 Module::Build::YAML->import();
[2484]225 };
226 if ($@) {
227 eval {
[2491]228 # No YAML found using a more std but less complete one. Old perl only
229 require YAML::Tiny;
230 YAML::Tiny->import();
[2484]231 };
232 if ($@) {
[2491]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 }
[2484]245 } else {
[2491]246 $ldfunc = \&YAML::Tiny::LoadFile;
[2484]247 }
248 } else {
[2491]249 $ldfunc = \&Module::Build::YAML::LoadFile;
[2484]250 }
[2249]251 } else {
[2491]252 $ldfunc = \&YAML::LoadFile;
[2494]253 }
[2249]254 } else {
[2491]255 $ldfunc = \&YAML::XS::LoadFile;
[2249]256 }
257
[2491]258 # Have we already handled that conf file ?
[2494]259 next if ((defined $lh) && (defined $lh->{'__cf'}) && (defined $lh->{'__cf'}->{$cf}) && ($lh->{'__cf'}->{$cf} eq 1));
[2491]260
[2261]261 pb_log(1,"Loading YAML conf file $cf\n");
[2402]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 }
[2263]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 }
[2491]278 # Remember we've seen that conf file
279 $lh->{'__cf'}->{$cf} = 1;
[1495]280}
281return($lh);
282}
283
[409]284=item B<pb_conf_add>
285
[1495]286This function adds the configuration file to the list last, and cache their content in the %h hash
[409]287
288=cut
289
290sub pb_conf_add {
291
[1495]292my $lh;
[898]293
294foreach my $cf (@_) {
[1495]295 if (! -r $cf) {
296 pb_log(0,"WARNING: pb_conf_add can not read $cf\n");
297 next;
298 }
[898]299 # Skip already used conf files
[1495]300 return($lh) if (defined $pbconffiles{$cf});
301
[2491]302 pb_log(2,"DEBUG: pb_conf_add with $cf\n");
[2154]303 # The new conf file overload values already managed
[898]304 my $num = keys %pbconffiles;
[1495]305 pb_log(2,"DEBUG: pb_conf_cache of $cf at position $num\n");
[898]306 $pbconffiles{$cf} = $num;
[1495]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)
[409]313}
[898]314}
[409]315
[1495]316
[405]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
[1495]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
[405]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;
[1495]348my $lh;
[405]349
[1495]350$lh = pb_conf_cache($conffile,$lh);
351
352foreach my $param (@param) {
353 push @ptr,$lh->{$param};
[405]354}
[1495]355return(@ptr);
356}
[405]357
[1904]358=item B<pb_conf_write>
[1495]359
[2278]360This function writes in the file passed as first parameter the hash of values passed as second parameter
[1495]361
[1904]362=cut
363
364sub pb_conf_write {
365
366my $conffile = shift;
[1905]367my $h = shift;
[2249]368my $dpfunc;
[1904]369
[1905]370confess "No configuration file defined to write into !" if (not defined $conffile);
371confess "No hash defined to read from !" if (not defined $h);
[2434]372open(CONF,"> $conffile") || (cluck "Unable to write into $conffile" && return);
[1904]373
[2279]374if ($PBCONFVER < 1) {
[2176]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 }
[1904]381 }
[2176]382} else {
383 # This is the new YAML format
[2249]384 eval {
[2494]385 require YAML::XS;
386 YAML::XS->import();
[2249]387 };
388 if ($@) {
389 eval {
[2494]390 require YAML;
391 YAML->import();
[2249]392 };
393 if ($@) {
[2494]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 }
[2249]424 } else {
[2494]425 $dpfunc = \&YAML::Dump;
[2249]426 }
427 } else {
[2494]428 $dpfunc = \&YAML::XS::Dump;
[2249]429 }
430
[2261]431 pb_log(1,"Writing YAML conf file $conffile\n");
[2494]432 delete $h->{'__cf'};
[2249]433 print CONF $dpfunc->($h);
[1904]434}
435close(CONF);
436}
437
438
439
[1495]440=item B<pb_conf_get_in_hash_if>
441
[1594]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.
[1495]444
445=cut
446
447sub pb_conf_get_in_hash_if {
448
449my $lh = shift || return(());
450my @params = @_;
451my @ptr = ();
452
[2488]453pb_log(3,"DEBUG: pb_conf_get_in_hash_if on params ".join(' ',@params)."\n");
[1495]454foreach my $k (@params) {
455 push @ptr,$lh->{$k};
[405]456}
[1495]457
[2488]458pb_log(3,"DEBUG: pb_conf_get_in_hash_if returns\n".Dumper(@ptr));
[405]459return(@ptr);
460}
461
[1495]462
463
[409]464=item B<pb_conf_get_if>
[405]465
[1495]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.
[405]467
[409]468=cut
469
470sub pb_conf_get_if {
471
[2154]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'};
[405]480}
[409]481
[2154]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
[1495]490=item B<pb_conf_add_last_in_hash>
[405]491
[1495]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)
[405]493
[1495]494It is used internally by pb_conf_add and is not exported.
[409]495
496=cut
497
[1495]498sub pb_conf_add_last_in_hash {
[409]499
[1907]500my $ptr = shift;
[409]501
[1495]502return if (not defined $ptr);
503# TODO: test $ptr is a hash pointer
[405]504
[1509]505# When called without correct initialization, try to work anyway with default as project
506pb_conf_init("default") if (not defined $ENV{'PBPROJ'});
507
[1495]508my @params = (sort keys %$ptr);
[405]509
[1495]510# Everything is returned via @h
511# @h contains the values overloading what @ptr may contain.
[2154]512my @h = pb_conf_get_in_hash_if($h,@params);
[1495]513my @ptr = pb_conf_get_in_hash_if($ptr,@params);
[409]514
[405]515my $p1;
516my $p2;
517
[2488]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");
[405]521
[1495]522foreach my $i (0..$#params) {
523 $p1 = $h[$i];
524 $p2 = $ptr[$i];
[2154]525 # Always try to take the param from h in priority
[1495]526 # in order to mask what could be defined already in ptr
[405]527 if (not defined $p2) {
[415]528 # exit if no p1 either
[1509]529 next if (not defined $p1);
[405]530 } else {
[409]531 # Ref found in p2
[405]532 if (not defined $p1) {
[409]533 # No ref in p1 so use p2's value
[405]534 $p1 = $p2;
535 } else {
536 # Both are defined - handling the overloading
[2154]537 # Now copy back into p1 all p2 content
538 # as p1 content always has priority over p2
[405]539 if (not defined $p1->{$ENV{'PBPROJ'}}) {
540 if (defined $p2->{$ENV{'PBPROJ'}}) {
[1594]541 $p1->{$ENV{'PBPROJ'}} = $p2->{$ENV{'PBPROJ'}};
[405]542 }
543 }
544 # Now copy back into p1 all p2 content which doesn't exist in p1
[2154]545 # # p1 content always has priority over p2
[405]546 foreach my $k (keys %$p2) {
547 $p1->{$k} = $p2->{$k} if (not defined $p1->{$k});
548 }
549 }
550 }
[1495]551 $h->{$params[$i]} = $p1;
[405]552}
[1495]553pb_log(2,"DEBUG: pb_conf_add_last_in_hash output: ".Dumper($h)."\n");
[405]554}
555
[409]556=item B<pb_conf_get>
[405]557
[409]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);
[932]566my $proj = undef;
[409]567
[932]568if (not defined $ENV{'PBPROJ'}) {
569 $proj = "unknown";
570} else {
571 $proj = $ENV{'PBPROJ'};
572}
[409]573
[1538]574confess "No params found for $proj" if (not @return);
[932]575
[409]576foreach my $i (0..$#param) {
[1507]577 confess "No $param[$i] defined for $proj" if (not defined $return[$i]);
[409]578}
579return(@return);
580}
581
[1495]582
[1694]583=item B<pb_conf_get_all>
584
[2077]585This function returns an array with all configuration parameters
[1694]586
587=cut
588
589sub pb_conf_get_all {
590
591return(sort keys %$h);
592}
593
[2077]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
[2253]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
[2434]617open(ORIG,$orig) || (cluck "Unable to open $orig" && return);
[2253]618confess "Will not erase existing $dest while transforming $orig" if (-f $dest);
[2434]619open(DEST,"> $dest") || (cluck "Unable to write into $dest" && close(ORIG) && return);
[2257]620print DEST "---\n";
[2253]621my $pbconfverbkp = $PBCONFVER;
622# We force migration from v0 to v1
623$PBCONFVER = 0;
624my $lh0;
[2257]625my $lh1;
[2253]626$lh0 = pb_conf_cache($orig,$lh0);
[2263]627pb_log(2,"lh0:\n".Dumper($lh0)."\n");
[2253]628$PBCONFVER = $pbconfverbkp;
629
[2264]630pb_log(0,"Converting v0 conf file $orig to v1 conf file $dest\n");
[2402]631# We can't just write the YAML if we want to keep comments !
[2253]632while (<ORIG>) {
633 if ($_ =~ /^#/) {
634 # Keep comments
635 print DEST $_;
[2257]636 } elsif ($_ =~ /^\s*$/) {
637 # Replace empty lines by comments
638 print DEST "#\n";;
[2253]639 } else {
640 if (/^\s*([A-z0-9-_]+)\s+(.+)$/) {
641 # Handle parameters
[2257]642 my ($param,$void) = ($1, $2);
643 if (not defined $lh1->{$param}) {
644 pb_log(2,"Converting parameter $param\n");
[2300]645 my $param2 = $param;
646 # param pburl in v0 is now pbprojurl in v1
647 $param2 =~ s/pburl/pbprojurl/;
648 print DEST "$param2:\n";
[2257]649 foreach my $k (keys %{$lh0->{$param}}) {
650 pb_log(2,"Handling key $k\n");
[2264]651 if ($lh0->{$param}->{$k} =~ /^\s*$/) {
652 print DEST " $k: !!str \"\"\n";
653 } else {
654 print DEST " $k: $lh0->{$param}->{$k}\n";
655 }
[2257]656 }
657 $lh1->{$param} = 1;
658 }
[2253]659 } else {
[2257]660 pb_log(0,"Unable to convert line $_\n");
[2253]661 }
662 }
663}
664close(ORIG);
665close(DEST);
666return();
667}
668
[405]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.