SCEP: GetCACert - send out all CA certificates (and their chain) defined in the curre...
[openxpki.git] / tools / vergen
blob2aa7a339523d470247977f85bd0846cc9e6b3bc4
1 #!/usr/bin/env perl
3 # Written by Martin Bartosch for the OpenXPKI project 2006
4 # Copyright (c) 2006 by The OpenXPKI Project
5 # $Revision: 589 $
8 use warnings;
9 use strict;
10 use English;
12 use Data::Dumper;
14 use Pod::Usage;
15 use Getopt::Long;
16 use POSIX;
17 use File::Spec;
18 use Config::Std;
19 use Cwd;
21 # use Smart::Comments;
23 my %params;
24 my %config;
27 my $svn = 'svn';
28 my $git = 'git';
29 my $git_svn = 'git-svn';
31 my $revision_info;
32 my $mapping_of;
33 my $format_of;
34 my $saved_directory;
36 ###########################################################################
38 sub slurp {
39 my $filename = shift;
41 return unless (defined $filename && -r $filename);
43 my $result = do {
44 open my $HANDLE, '<', $filename or return;
45 if (! $HANDLE) {
46 die "Could not open file '$filename' for reading. Stopped";
48 # slurp mode
49 local $INPUT_RECORD_SEPARATOR; # long version of $/
50 <$HANDLE>;
52 return $result;
56 sub get_revision_info {
57 my $result = {};
59 # first get SVN revision information
60 my $fh;
61 if (! open $fh, "$svn info -R 2>/dev/null |") {
62 return $result;
65 SVN_INFO:
66 while (<$fh>) {
67 chomp;
68 if (m{ \A Revision: \s* (\d+) }xms) {
69 $result->{revision} = $1;
70 next SVN_INFO;
72 if (m{ \A Last\ Changed\ Rev: \s* (\d+) }xms) {
73 if (! exists $result->{'last-changed-revision'} ||
74 ($1 > $result->{'last-changed-revision'})) {
75 $result->{'last-changed-revision'} = $1;
79 close $fh;
81 # check if we are running in a git checkout
82 # getting revision info from SVN did not work, try git
83 if ( (`which $git 2>/dev/null`) && (`$git branch 2>/dev/null`) ) {
84 # try via git-svn...
85 if (! exists $result->{revision}) {
86 # try to get revision from git-svn
87 my $last_log = `$git_svn log --oneline --limit 1 2>/dev/null`;
88 my ($revision) = ($last_log =~ m{\A r(\d+)\ \|\ .*}xms);
89 if (defined $revision) {
90 $result->{revision} = $revision;
94 # ... and via plain git
95 if (! exists $result->{revision}) {
96 # revision could not be obtained by git-svn
97 # reason may be that this repository does not rebase via git-svn,
98 # but rather pulls from another repository. assume that the
99 # svn revision information is available in git full text logs.
101 open $fh, qq{ $git log --pretty=email | };
102 GITLOGLINE:
103 while (my $line = <$fh>) {
104 chomp $line;
105 # first git-svn-id line encountered wins
106 if ($line =~ m{ \A git-svn-id: .*? @(\d+) \s+ }xms) {
107 $result->{revision} = $1;
108 last GITLOGLINE;
111 close $fh;
113 # fake last-changed-revision
114 $result->{'last-changed-revision'} ||= $result->{revision};
116 # get git commit hashes
117 $result->{'git-abbreviated-commit-hash'} = `$git show-ref -s --abbrev HEAD`;
118 chomp $result->{'git-abbreviated-commit-hash'};
120 $result->{'git-commit-hash'} = `$git show-ref -s HEAD`;
121 chomp $result->{'git-commit-hash'};
123 # get git tag information
124 open $fh, qq{ $git name-rev --tags HEAD | };
125 GITLOGLINE:
126 while (my $line = <$fh>) {
127 chomp $line;
128 if ($line =~ m{ \A HEAD \s+ (?: .*?/)?(.*) }xms) {
129 if ($1 ne 'undefined') {
130 $result->{'git-tag'} ||= $1;
134 close $fh;
137 return $result;
141 sub upwards_find_file {
142 my $filename = shift;
144 return undef unless defined $filename;
146 my $dir = getcwd;
148 DIRECTORY:
149 while ($dir ne '/') {
150 if (exists $params{verbose}) {
151 print STDERR "* scanning $dir for $filename\n";
154 my $absolute_filename = File::Spec->catfile($dir, $filename);
155 if (-e $absolute_filename) {
156 if (exists $params{verbose}) {
157 print STDERR "* found $absolute_filename\n";
159 return $absolute_filename;
162 # one level up
163 my @dirs = File::Spec->splitdir($dir);
164 pop @dirs;
165 $dir = File::Spec->catdir(@dirs);
167 return;
170 sub expand_keyword {
171 my $arg = shift;
173 my $last_arg = "";
174 while (defined $arg && ($last_arg ne $arg)) {
175 $last_arg = $arg;
176 foreach my $keyword (keys %{$mapping_of->{keyword}}) {
177 if ($arg =~ m{ $keyword }xms) {
178 my $value = $mapping_of->{keyword}->{$keyword}->{entry}->{value};
179 if (! defined $value) {
180 die "Could not get value for keyword '$keyword'. Stopped";
182 $arg =~ s{ $keyword }{$value}xmsg;
185 $arg = strftime($arg, gmtime(time));
188 return $arg;
191 ###########################################################################
193 # make sure that system output is not localized
194 $ENV{LANG} = 'C';
196 GetOptions(\%params,
198 help|?
200 verbose
201 format=s
202 directory=s
203 dump
204 dumpformat=s
205 )) or pod2usage(-verbose => 0);
207 pod2usage(-exitstatus => 0, -verbose => 2) if $params{man};
208 pod2usage(-verbose => 1) if ($params{help});
210 if (defined $params{directory}) {
211 $saved_directory = getcwd;
212 if (! chdir $params{directory}) {
213 die "Could not change to directory $params{directory}. Stopped";
217 my $def_file = upwards_find_file('.VERSION_DEFINITION');
219 if (! defined $def_file) {
220 die "Could not find version definition file anywhere. Stopped";
222 read_config($def_file, %config);
224 if (exists $config{FORMAT_DEFINITIONS}) {
225 $format_of = $config{FORMAT_DEFINITIONS};
226 delete $config{FORMAT_DEFINITIONS};
229 ### %config
230 ### $format_of
232 COMPONENT:
233 foreach my $component (keys %config) {
234 ### $component...
236 # coerce keyword into arrayref
237 if (defined $config{$component}->{keyword}) {
238 if (ref $config{$component}->{keyword} eq '') {
239 $config{$component}->{keyword} = [ $config{$component}->{keyword} ];
241 } else {
242 die "No keyword defined for component $component. Stopped";
245 # built-in components
246 if ($component =~ m{ \A (?: revision |
247 last-changed-revision |
248 git-commit-hash |
249 git-abbreviated-commit-hash |
250 git-tag
251 ) \z }xms) {
252 $revision_info ||= get_revision_info();
254 if (! scalar keys %{$revision_info}) {
255 die "Could not determine revision info. (Hint: is svn installed and in path?)";
258 $mapping_of->{component}->{$component} = {
259 keyword => $config{$component}->{keyword},
260 value => $revision_info->{$component},
261 type => 'built-in',
263 next COMPONENT;
266 my $optional = (defined $config{$component}{optional}
267 && ($config{$component}{optional} =~ m{ \A (?:yes|true|1) \z }ixms));
268 ### $optional
270 if (! defined $config{$component}{filename}) {
271 die "No 'filename' defined for component '$component'. Stopped";
274 my $file = upwards_find_file($config{$component}{filename});
275 my $content = '';
277 if (defined $file) {
278 $content = slurp($file);
279 if (! defined $content) {
280 $content = '';
282 chomp $content;
283 } else {
284 if (! $optional) {
285 die "Could not find version file '$config{$component}{filename}' for component '$component' upwards from here. Stopped";
287 $content = '';
290 $mapping_of->{component}->{$component} = {
291 keyword => $config{$component}->{keyword},
292 value => $content,
293 type => 'component',
297 # remap entries
298 foreach my $component (keys %{$mapping_of->{component}}) {
299 ### $component
300 my $entry = $mapping_of->{component}->{$component};
301 foreach my $keyword (@{ $mapping_of->{component}->{$component}->{keyword} }) {
302 $mapping_of->{keyword}->{$keyword}->{component} = $component,
303 $mapping_of->{keyword}->{$keyword}->{entry} = $entry,
307 ### $mapping_of
309 if (defined $params{format}) {
310 my $format = $params{format};
312 if (exists $format_of->{$format}) {
313 $format = $format_of->{$format};
315 my $value = expand_keyword($format);
317 eval "printf('%s', qq($value));";
320 if (defined $params{'dump'}) {
321 foreach my $component (keys %{$mapping_of->{component}}) {
322 my $dumpformat = 'KEYWORD=VALUE\n';
323 if (defined $params{dumpformat}) {
324 $dumpformat = $params{dumpformat};
327 my $keyword = $mapping_of->{component}->{$component}->{keyword}->[0];
328 my $value = expand_keyword($mapping_of->{keyword}->{$keyword}->{entry}->{value});
329 my %vars = (
330 KEYWORD => $keyword,
331 VALUE => $value,
334 my @values = ();
336 MAKEFORMAT:
337 while (1) {
338 my ($item) = ($dumpformat =~ m{ (KEYWORD|VALUE) }xms);
339 last MAKEFORMAT if (! defined $item);
340 $dumpformat =~ s{ $item }{%s}xms;
341 push @values, $vars{$item};
343 ### $dumpformat
344 ### @values
346 if (! defined $values[1]) {
347 $values[1] = '';
349 ### @values
350 eval "printf(qq($dumpformat), \@values);";
354 exit 0;
356 __END__
358 =head1 NAME
360 vergen - Version management tool
362 =head1 USAGE
364 vergen [options] COMMAND
366 Options:
367 --help brief help message
368 --man full documentation
369 --format <arg> print version using format 'arg'
370 --directory <dir> pretend to be in directory 'dir'
371 --dump dump keyword assignments
372 --dumpformat <string> use <string> as dump format
373 (default: KEYWORD=VALUE\n)
374 --verbose show details about information obtained
376 =head1 DESCRIPTION
378 B<vergen> is a tool that can help to maintain consistent version numbers
379 in a project. If desired it uses Subversion and/or Git to determine
380 revision control information.
382 Starting from the current directory, vergen searches upwards for its
383 configuration file '.VERSION_DEFINITION' and reads the first file found
384 (see B<The .VERSION_DEFINITION File> for a detailed description.)
385 After the configuration file has been read, vergen again traverses the
386 directory tree upwards from the current directory and searches for all
387 file names defined in the version definition file. Once a file is found,
388 its content is read and stored as the value for the corresponding component.
390 Components can arbitrarily be combined to 'formats'. A format consists
391 of a string that may contain any number of custom and built-in component
392 names.
393 It is then possible to print the resulting version string using
394 the --format option.
396 When used within a directory hierarchy under revision control via
397 Subversion and/or Git, B<vergen> is able to obtain and use information about
398 the current checkout.
401 =head1 OPTIONS
403 =over 8
405 =item B<--format> arg
407 Print format string, using the version numbers obtained by vergen. The
408 output format is determined by 'arg' and may either be a symbolic name
409 referencing a format string in the FORMAT_DEFINITIONS section of
410 the corresponding .VERSION_DEFINITION file or a custom string specifying
411 the version number. You may use all keywords defined in .VERSION_DEFINITION
412 and all conversion specifications as defined by strftime(3).
414 =item B<--directory> dir
416 Makes it possible to simulate calling vergen in another directory.
417 This is identical to calling
419 ( cd <dir> && vergen ... )
421 =item B<--dump>
423 Dump all keyword definitions in a format that can be sources by sh.
425 =item B<--dumpformat> string
427 Use specified string as template for keyword dump. Defaults to
428 'KEYWORD=VALUE\n'.
429 Useful dump formats:
431 --dumpformat 'KEYWORD="VALUE"\n'
432 --dumpformat 'export KEYWORD="VALUE"\n'
434 =item B<--verbose>
436 Show which files are processed for determination of the version number
437 components.
439 =back
441 =head1 The .VERSION_DEFINITION File
443 The version definition file contains format and version number component
444 definitions. It is split in a number of sections that define the composition
445 of version numbers.
447 =head2 Format definition
449 The format definition section is started by the [FORMAT_DEFINITIONS] tag.
451 Example:
452 [FORMAT_DEFINITIONS]
453 simple: MAJOR.MINOR
454 advanced: MAJOR.MINOR.RELEASE
456 This configuration defines two named formats called 'simple' and 'advanced'
457 that can be used with the --format option. The value following the colon
458 is used as the output format in this case.
460 =head2 Custom component definitions
462 Section names are interpreted as a custom version component
463 definition.
464 In order to create a custom version number component you should define
465 the following keys:
467 =head3 filename
469 File name to use. vergen searches upwards from the current directory
470 until it finds the specified file.
471 The file contents will be assigned as the component value.
473 =head3 keyword
475 Symbolic name(s) describing the version component that can be used
476 to reference its value. More than one keyword can be used for
477 aliasing the component. Each of these keyword can be used to reference
478 the component value. It is possible to use the keyword in a format (--format)
479 or even in the contents of the version component files. The program resolves
480 recursive references to keywords properly.
482 Only the first keyword defined is used for --dump.
484 =head3 optional
486 If set to 'yes' or 'true', this component is not mandatory.
488 =head2 Revision control built-ins
490 When called within a directory tree that is under version control by
491 Subversion and/or Git it is possible to obtain certain information from the
492 revision control system.
494 B<vergen> support Subversion-only operation, Subversion with Git (using
495 git-svn to rebase the Subversion repository) and Git-only. Git-only mode
496 also tries to guess Subversion version numbers from the Git log (e. g.
497 when pulling from a Git repository that uses git-svn to rebase from
498 Subversion).
500 =head3 Subversion built-ins
502 The following named sections are hardcoded
503 and can be used to access Subversion information.
505 =head4 revision
507 'revision' expands to the global SVN revision number
509 =head4 last-changed-revision
511 'last-changed-revision' determines the highest "Last Changed Rev"
512 below the current directory (recursively).
515 =head3 Git built-ins
517 The following named sections are hardcoded and can be used to access Git
518 version information (if current directory is inside a Git repository).
520 =head4 git-commit-hash
522 'git-commit-hash' expands to the Git commit hash for the current HEAD.
524 =head4 git-abbreviated-commit-hash
526 'git-abbreviated-commit-hash' expands to the abbreviated commit hash.
528 =head4 git-tag
530 'git-tag' expands to the first Git tag name found for the current HEAD,
531 undefined if the current HEAD is untagged.
533 =head1 Examples
535 =head2 .VERSION_DEFINITION
537 # Format definitions
538 [FORMAT_DEFINITIONS]
539 version: MAJOR.MINOR.RELEASESUFFIX\n
540 daily_snapshot: %F-MAJOR.MINOR.RELEASESUFFIX\n
541 git-commit: MAJOR.MINOR.GIT_TAG
543 # Built-in components
544 [revision]
545 keyword: SVN_REVISION
547 [last-changed-revision]
548 keyword: SVN_LAST_CHANGED_REVISION
550 [git-commit-hash]
551 keyword: GIT_COMMIT_HASH
553 [git-abbreviated-commit-hash]
554 keyword: GIT_ABBREVIATED_COMMIT_HASH
556 [git-tag]
557 keyword: GIT_TAG
558 optional: yes
560 # Custom components
561 [major]
562 filename: .VERSION_MAJOR
563 keyword: MAJOR
565 [minor]
566 filename: .VERSION_MINOR
567 keyword: MINOR
569 [release]
570 filename: .VERSION_RELEASE
571 keyword: RELEASE
573 [suffix]
574 filename: .VERSION_SUFFIX
575 keyword: SUFFIX
576 optional: yes
579 =head2 Invocation
581 Using the above configuration file:
583 vergen --format daily_snapshot
584 vergen --format MAJOR.MINOR
585 vergen --dump