1#**************************************************************
2#
3#  Licensed to the Apache Software Foundation (ASF) under one
4#  or more contributor license agreements.  See the NOTICE file
5#  distributed with this work for additional information
6#  regarding copyright ownership.  The ASF licenses this file
7#  to you under the Apache License, Version 2.0 (the
8#  "License"); you may not use this file except in compliance
9#  with the License.  You may obtain a copy of the License at
10#
11#    http://www.apache.org/licenses/LICENSE-2.0
12#
13#  Unless required by applicable law or agreed to in writing,
14#  software distributed under the License is distributed on an
15#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16#  KIND, either express or implied.  See the License for the
17#  specific language governing permissions and limitations
18#  under the License.
19#
20#**************************************************************
21
22
23
24#*************************************************************************
25#
26# SourceConfig - Perl extension for parsing general info databases
27#
28# usage: see below
29#
30#*************************************************************************
31
32package SourceConfig;
33
34use strict;
35
36use constant SOURCE_CONFIG_FILE_NAME => 'source_config';
37use constant SOURCE_CONFIG_VERSION => 3;
38
39use Carp;
40use Cwd;
41use RepositoryHelper;
42use File::Basename;
43use File::Temp qw(tmpnam);
44
45my $debug = 0;
46
47#####  profiling #####
48
49##### ctor #####
50
51sub new {
52    my $proto = shift;
53    my $class = ref($proto) || $proto;
54    my $source_root = shift;
55    my @additional_repositories = @_;
56
57    my $self = {};
58    $self->{USER_SOURCE_ROOT} = undef;
59    $self->{SOURCE_CONFIG_FILE} = undef;
60    if (defined $source_root) {
61        $source_root = Cwd::realpath($source_root);
62        $source_root =~ s/\\|\/$//;
63        if (-f $source_root) {
64            # We have path to source_config
65            if (File::Basename::basename($source_root) eq 'source_config') {
66                # We have path to source_config
67                $self->{SOURCE_CONFIG_FILE} = $source_root;
68                $source_root = File::Basename::dirname($source_root);
69            } else {
70                croak("$source_root is not a source_config file");
71            };
72        } else {
73            $self->{USER_SOURCE_ROOT} = $source_root;
74            $source_root .= '/..';
75        }
76    } else {
77        $source_root = $ENV{SOURCE_ROOT_DIR};
78    };
79    $source_root = Cwd::realpath($source_root);
80    $self->{SOURCE_ROOT} = $source_root;
81    $self->{DEBUG} = 0;
82    $self->{VERBOSE} = 0;
83    $self->{REPOSITORIES} = {};
84    $self->{ACTIVATED_REPOSITORIES} = {};
85    $self->{MODULE_PATHS} = {};
86    $self->{MODULE_BUILD_LIST_PATHS} = {};
87    $self->{ACTIVATED_MODULES} = {};
88    $self->{MODULE_REPOSITORY} = {};
89    $self->{REAL_MODULES} = {};
90    $self->{NEW_MODULES} = [];
91    $self->{REMOVE_MODULES} = {};
92    $self->{REMOVE_REPOSITORIES} = {};
93    $self->{NEW_REPOSITORIES} = [];
94    $self->{WARNINGS} = [];
95    $self->{REPORT_MESSAGES} = [];
96    $self->{CONFIG_FILE_CONTENT} = [];
97    if (defined $self->{USER_SOURCE_ROOT}) {
98        ${$self->{REPOSITORIES}}{File::Basename::basename($self->{USER_SOURCE_ROOT})} = $self->{USER_SOURCE_ROOT};
99    };
100    $self->{SOURCE_CONFIG_FILE} = get_config_file($self->{SOURCE_ROOT}) if (!defined $self->{SOURCE_CONFIG_FILE});
101    $self->{SOURCE_CONFIG_DEFAULT} = $self->{SOURCE_ROOT} .'/'.SOURCE_CONFIG_FILE_NAME;
102    if (defined $self->{USER_SOURCE_ROOT}) {
103        ${$self->{REPOSITORIES}}{File::Basename::basename($self->{USER_SOURCE_ROOT})} = $self->{USER_SOURCE_ROOT};
104    };
105    foreach my $additional_repository (@additional_repositories)
106    {
107        ${$self->{REPOSITORIES}}{File::Basename::basename($additional_repository)} = $additional_repository;
108    }
109
110    read_config_file($self);
111   	get_module_paths($self);
112    bless($self, $class);
113    return $self;
114}
115
116##### methods #####
117
118sub get_version {
119    return SOURCE_CONFIG_VERSION;
120};
121
122sub get_repositories
123{
124    my $self        = shift;
125    return sort keys %{$self->{REPOSITORIES}};
126}
127
128sub add_repository
129{
130    my $self        = shift;
131    my $new_rep_path = shift;
132    $new_rep_path = Cwd::realpath($new_rep_path);
133    my $new_rep_name = File::Basename::basename($new_rep_path);
134    if (defined ${$self->{REPOSITORIES}}{$new_rep_name}) {
135        croak("Repository $new_rep_name is already defined!!");
136    };
137    ${$self->{REPOSITORIES}}{$new_rep_name} = $new_rep_path;
138    $self -> get_repository_module_paths($new_rep_name);
139}
140
141sub get_config_file_default_path {
142    my $self        = shift;
143    return $self->{SOURCE_CONFIG_DEFAULT};
144}
145
146sub get_config_file_path {
147    my $self = shift;
148    return $self->{SOURCE_CONFIG_FILE};
149}
150
151sub get_module_repository {
152    my $self = shift;
153    my $module = shift;
154    if (defined ${$self->{MODULE_REPOSITORY}}{$module}) {
155        return ${$self->{MODULE_REPOSITORY}}{$module};
156    } else {
157        Carp::cluck("No such module $module in active repositories!!\n");
158        return undef;
159    };
160}
161
162sub get_module_path {
163    my $self = shift;
164    my $module = shift;
165    if (defined ${$self->{MODULE_PATHS}}{$module}) {
166        return ${$self->{MODULE_PATHS}}{$module};
167    } else {
168        Carp::cluck("No path for module $module in active repositories!!\n") if ($debug);
169        return undef;
170    };
171}
172
173sub get_module_build_list {
174    my $self = shift;
175    my $module = shift;
176    if (defined ${$self->{MODULE_BUILD_LIST_PATHS}}{$module}) {
177        return ${$self->{MODULE_BUILD_LIST_PATHS}}{$module};
178    } else {
179        my @possible_build_lists = ('build.lst', 'build.xlist'); # build lists names
180        foreach (@possible_build_lists) {
181            my $possible_path = ${$self->{MODULE_PATHS}}{$module} . "/prj/$_";
182            if (-e $possible_path) {
183                ${$self->{MODULE_BUILD_LIST_PATHS}}{$module} = $possible_path;
184                return $possible_path;
185            };
186        };
187        Carp::cluck("No build list in module $module found!!\n") if ($self->{DEBUG});
188        return undef;
189    };
190}
191
192sub get_all_modules
193{
194    my $self = shift;
195    my $module = shift;
196    return sort keys %{$self->{MODULE_PATHS}};
197};
198
199sub get_active_modules
200{
201    my $self        = shift;
202    if (scalar keys %{$self->{ACTIVATED_MODULES}}) {
203        return sort keys %{$self->{ACTIVATED_MODULES}};
204	}
205   	return sort keys %{$self->{REAL_MODULES}};
206}
207
208sub is_active
209{
210    my $self        = shift;
211    my $module		= shift;
212    if (scalar keys %{$self->{ACTIVATED_MODULES}}) {
213        return exists ($self->{ACTIVATED_MODULES}{$module});
214	}
215    return exists ($self->{REAL_MODULES}{$module});
216}
217
218##### private methods #####
219
220sub get_repository_module_paths {
221    my $self        = shift;
222    my $repository        = shift;
223    my $repository_path = ${$self->{REPOSITORIES}}{$repository};
224    if (opendir DIRHANDLE, $repository_path) {
225        foreach my $module (readdir(DIRHANDLE)) {
226            next if (($module =~ /^\.+/) || (!-d "$repository_path/$module"));
227            my $module_entry = $module;
228            if (($module !~ s/\.lnk$//) && ($module !~ s/\.link$//)) {
229                $self->{REAL_MODULES}{$module}++;
230            }
231            my $possible_path = "$repository_path/$module_entry";
232            if (-d $possible_path) {
233                if (defined ${$self->{MODULE_PATHS}}{$module}) {
234                    close DIRHANDLE;
235                    croak("Ambiguous paths for module $module: $possible_path and " . ${$self->{MODULE_PATHS}}{$module});
236                };
237                ${$self->{MODULE_PATHS}}{$module} = $possible_path;
238                ${$self->{MODULE_REPOSITORY}}{$module} = $repository;
239            }
240        };
241        close DIRHANDLE;
242    } else {
243        croak("Cannot read $repository_path repository content");
244    };
245};
246
247sub get_module_paths {
248    my $self        = shift;
249    foreach my $repository (keys %{$self->{REPOSITORIES}}) {
250        get_repository_module_paths($self, $repository);
251    };
252    my @false_actives = ();
253    foreach (keys %{$self->{ACTIVATED_MODULES}}) {
254        push(@false_actives, $_) if (!defined  ${$self->{MODULE_PATHS}}{$_});
255    };
256    croak("Error!! Activated module(s): @false_actives\nnot found in the active repositories!! Please check your " . $self->{SOURCE_CONFIG_FILE} . "\n") if (scalar @false_actives);
257    croak("No modules found!") if (!scalar keys %{$self->{MODULE_PATHS}});
258};
259
260sub get_config_file {
261    my $source_root = shift;
262    my $possible_path = $source_root . '/' . SOURCE_CONFIG_FILE_NAME;
263    return $possible_path if (-f $possible_path);
264    return '';
265};
266
267#
268# Fallback - fallback repository is based on RepositoryHelper educated guess
269#
270sub get_fallback_repository {
271    my $self = shift;
272    my $repository_root = RepositoryHelper->new()->get_repository_root();
273    ${$self->{REPOSITORIES}}{File::Basename::basename($repository_root)} = $repository_root;
274};
275
276sub read_config_file {
277    my $self = shift;
278    if (!$self->{SOURCE_CONFIG_FILE}) {
279        if (!defined $self->{USER_SOURCE_ROOT}) {
280            get_fallback_repository($self);
281        };
282        return;
283    };
284    my $repository_section = 0;
285    my $module_section = 0;
286    my $line = 0;
287    my @file_content = ();
288
289    if (open(SOURCE_CONFIG_FILE, $self->{SOURCE_CONFIG_FILE})) {
290        foreach (<SOURCE_CONFIG_FILE>) {
291            push (@{$self->{CONFIG_FILE_CONTENT}}, $_);
292            $line++;
293            chomp;
294            next if (!/^\S+/);
295            next if (/^\s*#+/);
296            s/\r\n//;
297            if (/^\[repositories\]\s*(\s+#)*/) {
298                $module_section = 0;
299                $repository_section = 1;
300                next;
301            };
302            if (/^\[modules\]\s*(\s+#)*/) {
303                $module_section = 1;
304                $repository_section = 0;
305                next;
306            };
307            next if (!$repository_section && !$module_section);
308            if (/\s*(\S+)=active\s*(\s+#)*/) {
309                if ($repository_section) {
310                    my $repository_source_path = $self->{SOURCE_ROOT} . "/$1";
311                    if (defined $ENV{UPDMINOREXT}) {
312                        $repository_source_path .= $ENV{UPDMINOREXT};
313                        if (defined ${$self->{REPOSITORIES}}{$1.$ENV{UPDMINOREXT}}) {
314                            delete ${$self->{REPOSITORIES}}{$1.$ENV{UPDMINOREXT}};
315                        };
316                    };
317                    ${$self->{REPOSITORIES}}{$1} = $repository_source_path;
318                    ${$self->{ACTIVATED_REPOSITORIES}}{$1}++;
319                    next;
320                }
321                if ($module_section) {
322                    ${$self->{ACTIVATED_MODULES}}{$1}++;
323                    next;
324                };
325            };
326            croak("Line $line in " . $self->{SOURCE_CONFIG_FILE} . ' violates format. Please make your checks!');
327        };
328        close SOURCE_CONFIG_FILE;
329        if (!scalar keys %{$self->{REPOSITORIES}}) {
330            get_fallback_repository($self);
331        };
332    } else {
333        croak('Cannot open ' . $self->{SOURCE_CONFIG_FILE} . ' for reading');
334    };
335};
336
337sub remove_all_activated_repositories {
338    my $self = shift;
339    $self->remove_activated_repositories([keys %{$self->{ACTIVATED_REPOSITORIES}}]);
340};
341
342sub remove_activated_repositories {
343    my $self = shift;
344    my $new_repositories_ref = shift;
345    push(@{$self->{WARNINGS}}, "\nWARNING: Empty repository list passed for removing from source_config\n") if (!scalar @$new_repositories_ref);
346    $self->{VERBOSE} = shift;
347    $self->{REMOVE_REPOSITORIES} = {};
348    foreach (@$new_repositories_ref) {
349        if (!defined ${$self->{ACTIVATED_REPOSITORIES}}{$_}) {
350            push (@{$self->{WARNINGS}}, "\nWARNING: repository $_ is not activated in ". $self->get_config_file_default_path()."\n");
351        } else {
352            ${$self->{REMOVE_REPOSITORIES}}{$_}++;
353            delete ${$self->{ACTIVATED_REPOSITORIES}}{$_};
354        };
355    };
356    generate_config_file($self);
357};
358
359sub remove_all_activated_modules {
360    my $self = shift;
361    $self->remove_activated_modules([keys %{$self->{ACTIVATED_MODULES}}]);
362};
363
364sub remove_activated_modules {
365    my $self = shift;
366    my $new_modules_ref = shift;
367    push(@{$self->{WARNINGS}}, "\nWARNING: Empty module list passed for removing from source_config\n") if (!scalar @$new_modules_ref);
368    $self->{VERBOSE} = shift;
369    $self->{REMOVE_MODULES} = {};
370    foreach (@$new_modules_ref) {
371        if (!defined ${$self->{ACTIVATED_MODULES}}{$_}) {
372            push (@{$self->{WARNINGS}}, "\nWARNING: module $_ is not activated in ". $self->get_config_file_default_path()."\n");
373        } else {
374            ${$self->{REMOVE_MODULES}}{$_}++;
375            delete ${$self->{ACTIVATED_MODULES}}{$_};
376        };
377    };
378    generate_config_file($self);
379};
380
381sub add_active_repositories {
382    my $self = shift;
383    $self->{NEW_REPOSITORIES} = shift;
384    croak('Empty repository list passed for addition to source_config') if (!scalar @{$self->{NEW_REPOSITORIES}});
385    $self->{VERBOSE} = shift;
386    foreach (@{$self->{NEW_REPOSITORIES}}) {
387        $self->add_repository($_);
388    };
389    generate_config_file($self);
390};
391
392sub add_active_modules {
393    my $self = shift;
394    my $module_list_ref = shift;
395    my $ignored_modules_string = '';
396    my @real_modules = ();
397    foreach my $module (sort @$module_list_ref) {
398        if ($self->get_module_path($module)) {
399            push(@real_modules, $module);
400        } else {
401            $ignored_modules_string .= " $module";
402        };
403    };
404    push (@{$self->{WARNINGS}}, "\nWARNING: following modules are not found in active repositories, and have not been added to the " . $self->get_config_file_default_path() . ":$ignored_modules_string\n") if ($ignored_modules_string);
405    $self->{NEW_MODULES} = \@real_modules;
406    croak('Empty module list passed for addition to source_config') if (!scalar @{$self->{NEW_MODULES}});
407    $self->{VERBOSE} = shift;
408    generate_config_file($self);
409};
410
411sub add_content {
412    my $self = shift;
413    my $content = shift;
414    my $entries_to_add = shift;
415    return if (!scalar @$entries_to_add);
416    my $message;
417    my $message_part1;
418    my $warning_message;
419    my $activated_entries;
420
421    if ($entries_to_add == $self->{NEW_MODULES}) {
422        $self->{NEW_MODULES} = [];
423        $message_part1 = "Module(s):\n";
424        $activated_entries = $self->{ACTIVATED_MODULES};
425    } elsif ($entries_to_add == $self->{NEW_REPOSITORIES}) {
426        $self->{NEW_REPOSITORIES} = [];
427        $message_part1 = "Repositories:\n";
428        $activated_entries = $self->{ACTIVATED_REPOSITORIES};
429    };
430    foreach my $entry (@$entries_to_add) {
431        if (defined $$activated_entries{$entry}) {
432            $warning_message .= "$entry "
433        } else {
434            push(@$content, "$entry=active\n");
435            ${$activated_entries}{$entry}++;
436            $message .= "$entry "
437        };
438    };
439
440    push(@{$self->{REPORT_MESSAGES}}, "\n$message_part1 $message\nhave been added to the ". $self->get_config_file_default_path()."\n") if ($message);
441    push (@{$self->{WARNINGS}}, "\nWARNING: $message_part1 $warning_message\nare already added to the ". $self->get_config_file_default_path()."\n") if ($warning_message);
442};
443
444sub generate_config_file {
445    my $self = shift;
446    my @config_content_new = ();
447    my ($module_section, $repository_section);
448    my %removed_modules = ();
449    my %removed_repositories = ();
450    foreach (@{$self->{CONFIG_FILE_CONTENT}}) {
451        if (/^\[repositories\]\s*(\s+#)*/) {
452            if ($module_section) {
453                $self->add_content(\@config_content_new, $self->{NEW_MODULES});
454            };
455            $module_section = 0;
456            $repository_section = 1;
457        };
458        if (/^\[modules\]\s*(\s+#)*/) {
459            if ($repository_section) {
460                $self->add_content(\@config_content_new, $self->{NEW_REPOSITORIES});
461            };
462            $module_section = 1;
463            $repository_section = 0;
464        };
465        if ($module_section && /\s*(\S+)=active\s*(\s+#)*/) {
466            if (defined ${$self->{REMOVE_MODULES}}{$1}) {
467                $removed_modules{$1}++;
468                next;
469            };
470        }
471        if ($repository_section && /\s*(\S+)=active\s*(\s+#)*/) {
472            if (defined ${$self->{REMOVE_REPOSITORIES}}{$1}) {
473                $removed_repositories{$1}++;
474                next;
475            };
476        }
477        push(@config_content_new, $_);
478    };
479    if (scalar @{$self->{NEW_MODULES}}) {
480        push(@config_content_new, "[modules]\n") if (!$module_section);
481        $self->add_content(\@config_content_new, $self->{NEW_MODULES});
482    };
483    if (scalar @{$self->{NEW_REPOSITORIES}}) {
484        push(@config_content_new, "[repositories]\n") if (!$repository_section);
485        $self->add_content(\@config_content_new, $self->{NEW_REPOSITORIES});
486    };
487    if (scalar keys %removed_modules) {
488        my @deleted_modules = keys %removed_modules;
489        push(@{$self->{REPORT_MESSAGES}}, "\nModules: @deleted_modules\nhave been removed from the ". $self->get_config_file_default_path()."\n");
490
491    };
492    if (scalar keys %removed_repositories) {
493        my @deleted_repositories = keys %removed_repositories;
494        push(@{$self->{REPORT_MESSAGES}}, "\nRepositories: @deleted_repositories\nhave been removed from the ". $self->get_config_file_default_path()."\n");
495
496    };
497
498    # Writing file, printing warnings and reports
499
500    #check if we need to write a new file
501    my $write_needed = 0;
502    if ((scalar @{$self->{CONFIG_FILE_CONTENT}}) != (scalar @config_content_new)) {
503        $write_needed++;
504    } else {
505        foreach my $i (0 .. $#{$self->{CONFIG_FILE_CONTENT}}) {
506            if (${$self->{CONFIG_FILE_CONTENT}}[$i] ne $config_content_new[$i]) {
507                $write_needed++;
508                last;
509            };
510        };
511    };
512    if ($write_needed) {
513        my $temp_config_file = File::Temp::tmpnam($ENV{TMP});
514        die("Cannot open $temp_config_file") if (!open(NEW_CONFIG, ">$temp_config_file"));
515        print NEW_CONFIG $_ foreach (@config_content_new);
516        close NEW_CONFIG;
517        rename($temp_config_file, $self->get_config_file_default_path()) or  system("mv", $temp_config_file, $self->get_config_file_default_path());
518        if (-e $temp_config_file) {
519            system("rm -rf $temp_config_file") if (!unlink $temp_config_file);
520        };
521        $self->{CONFIG_FILE_CONTENT} = \@config_content_new;
522    };
523    if ($self->{VERBOSE}) {
524        print $_ foreach (@{$self->{WARNINGS}});
525        $self->{VERBOSE} = 0;
526    };
527    $self->{WARNINGS} = [];
528    print $_ foreach (@{$self->{REPORT_MESSAGES}});
529    $self->{REPORT_MESSAGES} = [];
530};
531
532##### finish #####
533
5341; # needed by use or require
535
536__END__
537
538=head1 NAME
539
540SourceConfig - Perl extension for parsing general info databases
541
542=head1 SYNOPSIS
543
544    # example that will read source_config file and return the active repositories
545
546    use SourceConfig;
547
548    # Create a new instance of the parser:
549    $a = SourceConfig->new();
550
551    # Get repositories for the actual workspace:
552    $a->get_repositories();
553
554    # Add a repository new_repository for the actual workspace (via full path):
555    $a->add_repository(/DEV300/new_repository);
556
557=head1 DESCRIPTION
558
559SourceConfig is a perl extension to load and parse General Info Databses.
560It uses a simple object oriented interface to retrieve the information stored
561in the database.
562
563Methods:
564
565SourceConfig::new()
566
567Creates a new instance of SourceConfig. Can be initialized by: path to the default repository, path to the source_config, default - empty, the source_config will be taken from the environment
568
569
570SourceConfig::get_version()
571
572Returns version number of the module. Can't fail.
573
574
575SourceConfig::get_repositories()
576
577Returns sorted list of active repositories for the actual workspace
578
579
580SourceConfig::add_repository(REPOSITORY_PATH)
581
582Adds a repository to the list of active repositories
583
584
585SourceConfig::get_active_modules()
586
587Returns a sorted list of active modules
588
589SourceConfig::get_all_modules()
590
591Returns sorted list of all modules in active repositories.
592
593SourceConfig::get_module_path($module)
594
595Returns absolute module path
596
597SourceConfig::get_module_build_list($module)
598
599Returns absolute module build list path
600
601SourceConfig::get_module_repository($module)
602
603Returns the module's repository
604
605SourceConfig::get_config_file_path()
606
607Returns absolute module to the source configuration file
608
609SourceConfig::get_config_file_default_path()
610
611Returns default path for source configuration file
612
613SourceConfig::is_active()
614
615Returns 1 (TRUE) if a module is active
616Returns 0 (FALSE) if a module is not active
617
618SourceConfig::add_active_modules($module_array_ref)
619
620Adds modules from the @$module_array_ref as active to the source_config file
621
622SourceConfig::add_active_repositories($repository_array_ref)
623
624Adds repositories from the @$repository_array_ref as active to the source_config file
625
626SourceConfig::remove_activated_modules($module_array_ref)
627
628Removes modules from the @$module_array_ref from the source_config file
629
630SourceConfig::remove_all_activated_modules()
631
632Removes all activated modules from the source_config file
633
634SourceConfig::remove_activated_repositories($repository_array_ref)
635
636Removes repositories from the @$repository_array_ref from the source_config file
637
638SourceConfig::remove_all_activated_repositories()
639
640Removes all activated repositories from the source_config file
641
642
643=head2 EXPORT
644
645SourceConfig::new()
646SourceConfig::get_version()
647SourceConfig::get_repositories()
648SourceConfig::add_repository()
649SourceConfig::get_active_modules()
650SourceConfig::get_all_modules()
651SourceConfig::get_module_path($module)
652SourceConfig::get_module_build_list($module)
653SourceConfig::get_module_repository($module)
654SourceConfig::get_config_file_path()
655SourceConfig::get_config_file_default_path()
656SourceConfig::is_active($module)
657SourceConfig::add_active_modules($module_array_ref)
658SourceConfig::add_active_repositories($repository_array_ref)
659SourceConfig::remove_activated_modules($module_array_ref)
660SourceConfig::remove_all_activated_modules()
661SourceConfig::remove_activated_repositories($repository_array_ref)
662SourceConfig::remove_all_activated_repositories()
663
664=head1 AUTHOR
665
666Vladimir Glazunov, vg@openoffice.org
667
668=head1 SEE ALSO
669
670perl(1).
671
672=cut
673