Merge Ralph's config-from-snippets branch
This commit is contained in:
@@ -2,6 +2,9 @@
|
||||
|
||||
vpath %@ $(USR_VPATH) $(ALL_SRC_DIRS)
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# Variable expansion
|
||||
|
||||
# Default settings
|
||||
EXPAND_TOOL ?= $(PERL) $(TOOLS)/expandVars.pl
|
||||
|
||||
@@ -24,3 +27,39 @@ expand_clean:
|
||||
@$(RM) $(EXPANDED)
|
||||
|
||||
.PHONY : expand_clean
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# Assemblies (files assembled from snippets)
|
||||
|
||||
ASSEMBLE_TOOL ?= $(PERL) $(TOOLS)/assembleSnippets.pl
|
||||
|
||||
define COMMON_ASSEMBLY_template
|
||||
$1_SNIPPETS += $$(foreach dir, .. $$(SRC_DIRS), \
|
||||
$$(wildcard $$(dir)/$$($1_PATTERN)))
|
||||
$(COMMON_DIR)/$1: $$($1_SNIPPETS)
|
||||
$(ECHO) "Assembling common file $$@ from snippets"
|
||||
@$(RM) $1
|
||||
$(ASSEMBLE_TOOL) -o $1 $$^
|
||||
@$(MV) $1 $$@
|
||||
endef
|
||||
$(foreach asy, $(COMMON_ASSEMBLIES), \
|
||||
$(eval $(call COMMON_ASSEMBLY_template,$(strip $(asy)))))
|
||||
|
||||
define ASSEMBLY_template
|
||||
$1_SNIPPETS += $$(foreach dir, .. $$(SRC_DIRS), \
|
||||
$$(wildcard $$(dir)/$$($1_PATTERN)))
|
||||
$1: $$($1_SNIPPETS)
|
||||
$(ECHO) "Assembling file $$@ from snippets"
|
||||
@$(RM) $$@
|
||||
$(ASSEMBLE_TOOL) -o $$@ $$^
|
||||
endef
|
||||
$(foreach asy, $(ASSEMBLIES), \
|
||||
$(eval $(call ASSEMBLY_template,$(strip $(asy)))))
|
||||
|
||||
define ASSEMBLY_DEP_template
|
||||
$1$(DEP):
|
||||
@echo $1: > $$@
|
||||
endef
|
||||
$(foreach asy, $(sort $(COMMON_ASSEMBLIES) $(ASSEMBLIES)), \
|
||||
$(eval $(call ASSEMBLY_DEP_template,$(strip $(asy)))))
|
||||
|
||||
|
||||
@@ -14,6 +14,17 @@
|
||||
<h2 align="center">Changes between 3.15.2 and 3.15.3</h2>
|
||||
<!-- Insert new items immediately below here ... -->
|
||||
|
||||
<h3>Assembling files from numbered snippets</h3>
|
||||
|
||||
<p>A tool has been added that assembles file snippets specified on the
|
||||
command line into a single output file, with sorting and replacing/adding of
|
||||
snippets done based on their file names. The build system integration requires
|
||||
the output file to be specified setting COMMON_ASSEMBLIES (arch independent)
|
||||
or ASSEMBLIES (created by arch), then defining the snippets for each assembly
|
||||
setting *_SNIPPETS (explicitly) or *_PATTERN (searched relative to all source
|
||||
directories).
|
||||
</p>
|
||||
|
||||
<h3>Clean up after GNU readline()</h3>
|
||||
|
||||
<p>If EPICS Base is built with readline support, any IOC that calls epicsExit()
|
||||
|
||||
@@ -32,6 +32,7 @@ PERL_MODULES += DBD/Recordtype.pm
|
||||
PERL_MODULES += DBD/Registrar.pm
|
||||
PERL_MODULES += DBD/Variable.pm
|
||||
|
||||
PERL_SCRIPTS += assembleSnippets.pl
|
||||
PERL_SCRIPTS += convertRelease.pl
|
||||
PERL_SCRIPTS += cvsclean.pl
|
||||
PERL_SCRIPTS += dos2unix.pl
|
||||
|
||||
151
src/tools/assembleSnippets.pl
Normal file
151
src/tools/assembleSnippets.pl
Normal file
@@ -0,0 +1,151 @@
|
||||
#!/usr/bin/env perl
|
||||
#*************************************************************************
|
||||
# Copyright (c) 2015 ITER Organization.
|
||||
# EPICS BASE is distributed subject to a Software License Agreement found
|
||||
# in file LICENSE that is included with this distribution.
|
||||
#*************************************************************************
|
||||
|
||||
# $Id$
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Getopt::Std;
|
||||
use Sys::Hostname;
|
||||
use File::Basename;
|
||||
use Data::Dumper;
|
||||
|
||||
our ($opt_o, $opt_d, $opt_m, $opt_i, $opt_M);
|
||||
|
||||
$Getopt::Std::OUTPUT_HELP_VERSION = 1;
|
||||
&HELP_MESSAGE if !getopts('M:i:m:o:d') || @ARGV == 0;
|
||||
|
||||
my $out;
|
||||
my $dep;
|
||||
my %snippets;
|
||||
my $ipattern;
|
||||
|
||||
my $datetime = localtime();
|
||||
my $user = $ENV{LOGNAME} || $ENV{USER} || $ENV{USERNAME};
|
||||
my $host = hostname;
|
||||
my %replacements = (
|
||||
_DATETIME_ => $datetime,
|
||||
_USER_ => $user,
|
||||
_HOST_ => $host,
|
||||
);
|
||||
|
||||
if ($opt_o) {
|
||||
open $out, '>', $opt_o or
|
||||
die "Can't create $opt_o: $!\n";
|
||||
print STDERR "opened file $opt_o for output\n" if $opt_d;
|
||||
$replacements{_OUTPUTFILE_} = $opt_o;
|
||||
} else {
|
||||
open $out, '>&', STDOUT;
|
||||
print STDERR "using STDOUT for output\n" if $opt_d;
|
||||
$replacements{_OUTPUTFILE_} = 'STDERR';
|
||||
}
|
||||
|
||||
if ($opt_m) {
|
||||
foreach my $r (split /,/, $opt_m) {
|
||||
(my $k, my $v) = split /=/, $r;
|
||||
$replacements{$k} = $v;
|
||||
}
|
||||
}
|
||||
|
||||
if ($opt_M) {
|
||||
open $dep, '>', $opt_M or
|
||||
die "Can't create $opt_M: $!\n";
|
||||
print STDERR "opened dependency file $opt_M for output\n" if $opt_d;
|
||||
print $dep basename($opt_o), ":";
|
||||
}
|
||||
|
||||
if ($opt_i) {
|
||||
$ipattern = qr($opt_i);
|
||||
}
|
||||
|
||||
# %snippets is a hash {rank}
|
||||
# of hashes {name-after-rank}
|
||||
# of arrays[] [files...]
|
||||
# of arrays[2] [filename, command]
|
||||
print STDERR "reading input files\n" if $opt_d;
|
||||
foreach (@ARGV) {
|
||||
my $name = basename($_);
|
||||
if ($opt_i and not $name =~ /$ipattern/) {
|
||||
print STDERR " snippet $_ does not match input pattern $opt_i - ignoring\n" if $opt_d;
|
||||
next;
|
||||
}
|
||||
if ($name =~ /\A([ARD]?)([0-9]+)(.*[^~])\z/) {
|
||||
print STDERR " considering snippet $_\n" if $opt_d;
|
||||
if (exists $snippets{$2}) {
|
||||
my %rank = %{$snippets{$2}};
|
||||
my @files = @{ $rank{(keys %rank)[0]} };
|
||||
my $existcmd = $files[0]->[1];
|
||||
if ($1 eq "D" and $existcmd ne "D") {
|
||||
print STDERR " ignoring 'D' default for existing rank $2\n" if $opt_d;
|
||||
next;
|
||||
} elsif ($1 eq "R") {
|
||||
print STDERR " 'R' command - deleting existing rank $2 snippets\n" if $opt_d;
|
||||
$snippets{$2} = {};
|
||||
} elsif ($existcmd eq "D") {
|
||||
print STDERR " deleting existing rank $2 default snippet\n" if $opt_d;
|
||||
$snippets{$2} = {};
|
||||
}
|
||||
}
|
||||
if ($opt_d) {
|
||||
print STDERR " adding snippet ";
|
||||
print STDERR "marked as default " if $1 eq "D";
|
||||
print STDERR "to rank $2\n";
|
||||
}
|
||||
$snippets{$2}{$3} = () if (not exists $snippets{$2}{$3});
|
||||
push @{$snippets{$2}{$3}}, [ $_, $1 ];
|
||||
}
|
||||
}
|
||||
|
||||
if ($opt_d) {
|
||||
print STDERR "finished reading input files\n";
|
||||
print STDERR "dumping the final snippet structure\n";
|
||||
print STDERR Dumper(\%snippets);
|
||||
print STDERR "dumping the macro replacements\n";
|
||||
print STDERR Dumper(\%replacements);
|
||||
print STDERR "creating output\n";
|
||||
}
|
||||
|
||||
foreach my $r (sort {$a<=>$b} keys %snippets) {
|
||||
print STDERR " working on rank $r\n" if $opt_d;
|
||||
foreach my $n (sort keys %{$snippets{$r}}) {
|
||||
foreach my $s (@{$snippets{$r}{$n}}) {
|
||||
my $in;
|
||||
my $f = $s->[0];
|
||||
print STDERR " snippet $n from file $f\n" if $opt_d;
|
||||
open $in, '<', $f or die "Can't open $f: $!\n";
|
||||
$replacements{_SNIPPETFILE_} = $f;
|
||||
print $dep " \\\n $f" if $opt_M;
|
||||
while (<$in>) {
|
||||
chomp;
|
||||
foreach my $k (keys %replacements) {
|
||||
s/$k/$replacements{$k}/g;
|
||||
}
|
||||
print $out $_, "\n";
|
||||
}
|
||||
close $in;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print STDERR "finished creating output, closing\n" if $opt_d;
|
||||
if ($opt_M) {
|
||||
print $dep "\n";
|
||||
close $dep;
|
||||
}
|
||||
close $out;
|
||||
|
||||
sub HELP_MESSAGE {
|
||||
print STDERR "Usage: assembleSnippets.pl [options] snippets ...\n";
|
||||
print STDERR "Options:\n";
|
||||
print STDERR " -o file output file [STDOUT]\n";
|
||||
print STDERR " -d debug mode [no]\n";
|
||||
print STDERR " -m macros list of macro replacements as \"key=val,key=val\"\n";
|
||||
print STDERR " -i pattern pattern for input files to match\n";
|
||||
print STDERR " -M file write file with dependency rule suitable for make\n";
|
||||
exit 2;
|
||||
}
|
||||
@@ -18,6 +18,7 @@ TESTS += Menu
|
||||
TESTS += Recfield
|
||||
TESTS += Recordtype
|
||||
TESTS += Registrar
|
||||
TESTS += Snippets
|
||||
TESTS += Variable
|
||||
|
||||
TESTSCRIPTS_HOST += $(TESTS:%=%.t)
|
||||
|
||||
110
src/tools/test/Snippets.plt
Normal file
110
src/tools/test/Snippets.plt
Normal file
@@ -0,0 +1,110 @@
|
||||
#!/usr/bin/env perl
|
||||
|
||||
use File::Path;
|
||||
use Sys::Hostname;
|
||||
|
||||
use Test::More tests => 29;
|
||||
|
||||
my $user = $ENV{LOGNAME} || $ENV{USER} || $ENV{USERNAME};
|
||||
my $host = hostname;
|
||||
|
||||
mkdir "a$$";
|
||||
mkdir "b$$";
|
||||
|
||||
sub mksnip {
|
||||
my ($dir, $file, $line) = @_;
|
||||
open(my $fh, '>', "$dir$$/$file") or die "can't open $dir$$/$file : $!";
|
||||
print $fh $line;
|
||||
close $fh;
|
||||
}
|
||||
|
||||
sub assemble {
|
||||
my @cmd = ( 'perl', '../../assembleSnippets.pl', '-o', "out$$" );
|
||||
push @cmd, @_;
|
||||
system(@cmd);
|
||||
open(my $fh, '<', "out$$") or die "can't open out$$ : $!";
|
||||
chomp(my @result = <$fh>);
|
||||
close $fh;
|
||||
return join (' ', @result);
|
||||
}
|
||||
|
||||
# Adding two snippets of same rank, sorting alphabetically
|
||||
mksnip('a', '10_a', '10');
|
||||
mksnip('b', '10_c', '12');
|
||||
is assemble("a$$/10_a", "b$$/10_c"), '10 12', "adding same rank; ordered";
|
||||
is assemble("b$$/10_c", "a$$/10_a"), '10 12', "adding same rank; reverse order";
|
||||
|
||||
# Same, with 'A' cmd
|
||||
mksnip('a', 'A10_a', '10');
|
||||
mksnip('b', 'A10_c', '12');
|
||||
is assemble("a$$/10_a", "b$$/A10_c"), '10 12', "'A' add same rank; ordered";
|
||||
is assemble("b$$/10_c", "a$$/A10_a"), '10 12', "'A' add same rank; reverse order";
|
||||
|
||||
# Same name does not create issues
|
||||
mksnip('b', '10_a', '10x');
|
||||
is assemble("a$$/10_a", "b$$/10_a"), '10 10x', "adding same name twice; order a-b";
|
||||
is assemble("b$$/10_a", "a$$/10_a"), '10x 10', "adding same name twice; order b-a";
|
||||
|
||||
# Backup files (trailing ~) and hidden files (leading '.') get ignored
|
||||
mksnip('b', '10_c~', '12~');
|
||||
mksnip('b', '.10_c', '.12');
|
||||
is assemble("b$$/10_c", "b$$/10_c~"), '12', "backup file (trailing ~) gets ignored";
|
||||
is assemble("b$$/10_c", "b$$/.10_c"), '12', "hidden file (leading .) gets ignored";
|
||||
|
||||
# Non-numeric filenames get ignored
|
||||
mksnip('a', 'foo10_a', 'foo10');
|
||||
is assemble("b$$/10_c", "a$$/foo10_a"), '12', "file starting with [^ADR0-9] gets ignored";
|
||||
|
||||
# 'R' command replaces existing snippets of same rank
|
||||
mksnip('a', 'R10_b', '11');
|
||||
is assemble("a$$/10_a", "b$$/10_c", "a$$/R10_b"), '11', "'R' cmd; replace all";
|
||||
is assemble("a$$/10_a", "a$$/R10_b", "b$$/10_c"), '11 12', "'R' cmd; replace one (ordered)";
|
||||
is assemble("b$$/10_c", "a$$/R10_b", "a$$/10_a"), '10 11', "'R' cmd; replace one (reverse order)";
|
||||
|
||||
# 'D' command establishes default that gets overwritten or ignored
|
||||
mksnip('a', 'D10_a', 'D10');
|
||||
mksnip('b', 'D10_c', 'D12');
|
||||
is assemble("a$$/D10_a", "b$$/10_c"), '12', "'D' default; replaced by regular";
|
||||
is assemble("a$$/D10_a", "b$$/D10_c"), 'D12', "'D' default; replaced by new default (ordered)";
|
||||
is assemble("b$$/D10_c", "a$$/D10_a"), 'D10', "'D' default; replaced by new default (reverse order)";
|
||||
is assemble("a$$/D10_a", "a$$/R10_b"), '11', "'D' default; replaced by 'R' cmd";
|
||||
is assemble("b$$/10_c", "a$$/D10_a"), '12', "'D' default; ignored when regular exists";
|
||||
|
||||
# Ranks are sorted numerically
|
||||
mksnip('b', '09_a', '09');
|
||||
mksnip('a', '15_a', '15');
|
||||
mksnip('b', '2_a', '2');
|
||||
is assemble("a$$/10_a", "b$$/2_a", "a$$/15_a", "b$$/09_a"), '2 09 10 15', "ranks are sorted numerically";
|
||||
|
||||
# Builtin macros
|
||||
mksnip('a', '30_a', '_USER_');
|
||||
mksnip('a', '30_b', '_OUTPUTFILE_');
|
||||
mksnip('a', '30_c', '_SNIPPETFILE_');
|
||||
mksnip('a', '30_d', '_HOST_');
|
||||
is assemble("a$$/30_a"), "$user", "builtin macro _USER_";
|
||||
is assemble("a$$/30_b"), "out$$", "builtin macro _OUTPUTFILE_";
|
||||
is assemble("a$$/30_c"), "a$$/30_c", "builtin macro _SNIPPETFILE_";
|
||||
is assemble("a$$/30_d"), "$host", "builtin macro _HOST_";
|
||||
|
||||
# User macros
|
||||
mksnip('b', '35_a', 'Line _M1_');
|
||||
mksnip('b', '35_b', 'Line _M1_ with _M2_');
|
||||
mksnip('b', '35_c', 'Line _M2_ with _M2_');
|
||||
is assemble("-m", "_M1_=REP1", "b$$/35_a"), "Line REP1", "single user macro; single occurrence";
|
||||
is assemble("-m", "_M1_=REP1,_M2_=REP2", "b$$/35_b"), "Line REP1 with REP2", "multiple user macros";
|
||||
is assemble("-m", "_M2_=REP2", "b$$/35_c"), "Line REP2 with REP2", "single user macro; multiple occurrences";
|
||||
|
||||
# Input pattern
|
||||
mksnip('a', '10_a.cmd', '10cmd');
|
||||
is assemble("-i", "\.cmd", "a$$/10_a", "b$$/10_c", "a$$/R10_b", "a$$/10_a.cmd"), '10cmd', "input pattern";
|
||||
|
||||
# Dependency file generation
|
||||
assemble("-M", "./dep$$", "a$$/10_a", "b$$/10_c");
|
||||
open(my $fh, '<', "dep$$") or die "can't open dep$$ : $!";
|
||||
chomp(my @result = <$fh>);
|
||||
close $fh;
|
||||
is "$result[0]", "out$$: \\", "dependency file (line 1)";
|
||||
is "$result[1]", " a$$/10_a \\", "dependency file (line 2)";
|
||||
is "$result[2]", " b$$/10_c", "dependency file (line 3)";
|
||||
|
||||
rmtree([ "a$$", "b$$", "out$$", "dep$$" ]);
|
||||
Reference in New Issue
Block a user