source: trunk/debbuild@ 10

Last change on this file since 10 was 10, checked in by kdeugau, 19 years ago

/trunk

Checkpoint
%configure works

  • Property svn:executable set to *
  • Property svn:keywords set to Date Rev Author
File size: 17.5 KB
Line 
1#!/usr/bin/perl
2# debbuild script
3# Shamlessly steals intreface from rpm's "rpmbuild" to create
4# Debian packages. Please note that such packages are highly
5# unlikely to conform to "Debian Policy".
6###
7# SVN revision info
8# $Date: 2005-11-03 23:10:26 +0000 (Thu, 03 Nov 2005) $
9# SVN revision $Rev: 10 $
10# Last update by $Author: kdeugau $
11###
12
13use strict;
14use warnings;
15use Fcntl; # for sysopen flags
16
17# regex debugger
18#use re "debug";
19
20# Program flow:
21# -> Parse/execute "system" config/macros (if any - should be rare)
22# -> Parse/execute "user" config/macros (if any - *my* requirement is %_topdir)
23# -> Parse command line for options, spec file/tarball/.src.deb (NB - also accept .src.rpm)
24
25# User's prefs for dirs, environment, etc,etc,etc.
26# config file ~/.debmacros
27# Default ordered search paths for config/macros:
28# /usr/lib/rpm/rpmrc /usr/lib/rpm/redhat/rpmrc /etc/rpmrc ~/.rpmrc
29# /usr/lib/rpm/macros /usr/lib/rpm/redhat/macros /etc/rpm/macros ~/.rpmmacros
30# **NOTE: May be possible to (ab)use bits of debhelper
31
32# Build tree
33# default is /usr/src/debian/{BUILD,SOURCES,SPECS,DEBS,SDEBS}
34
35# Globals
36my $specfile;
37my $tarball;
38my $srcpkg;
39my $cmdbuildroot;
40my $tarballdir; # This should really be initialized, but the coding makes it, um, ugly.
41
42# Initialized globals
43my $verbosity = 0;
44my %cmdopts = (type => '',
45 stage => 'a',
46 short => 'n'
47 );
48my $topdir = "/usr/src/debian";
49my $buildroot = "/var/tmp/%{name}-%{version}-%{release}.root".int(rand(99998)+1);
50
51# "Constants"
52my %targets = ('p' => 'Prep',
53 'c' => 'Compile',
54 'i' => 'Install',
55 'l' => 'Verify %files',
56 'a' => 'Build binary and source',
57 'b' => 'Build binary',
58 's' => 'Build source'
59 );
60my $scriptletbase =
61q(#!/bin/sh
62
63 RPM_SOURCE_DIR="%{_topdir}/SOURCES"
64 RPM_BUILD_DIR="%{_topdir}/BUILD"
65 RPM_OPT_FLAGS="-O2 -g -march=i386 -mcpu=i686"
66 RPM_ARCH="i386"
67 RPM_OS="linux"
68 export RPM_SOURCE_DIR RPM_BUILD_DIR RPM_OPT_FLAGS RPM_ARCH RPM_OS
69 RPM_DOC_DIR="/usr/share/doc"
70 export RPM_DOC_DIR
71 RPM_PACKAGE_NAME="%{name}"
72 RPM_PACKAGE_VERSION="%{version}"
73 RPM_PACKAGE_RELEASE="%{release}"
74 export RPM_PACKAGE_NAME RPM_PACKAGE_VERSION RPM_PACKAGE_RELEASE
75 RPM_BUILD_ROOT="%{buildroot}"
76 export RPM_BUILD_ROOT
77);
78#my $tmp = `dpkg-architecture`; # Debian buildarch goop.
79#$tmp =~ s/^(.*)$/ $1/g;
80#print $tmp;
81foreach (`dpkg-architecture`) {
82 s/=(.+)/="$1"/;
83 $scriptletbase .= " $_";
84}
85$scriptletbase .=
86q(
87 set -x
88 umask 022
89 cd %{_topdir}/BUILD
90);
91
92# Package data
93# This is the form of $pkgdata{pkgname}{meta}
94# meta includes Summary, Name, Version, Release, Group, Copyright,
95# Source, URL, Packager, BuildRoot, Description
96# 10/31/2005 Maybe this should be flatter? -kgd
97my %pkgdata;
98
99# Scriptlets
100my $prepscript;
101my $buildscript;
102my $installscript;
103my $cleanscript;
104
105# Functions
106sub prep;
107sub parse_spec;
108
109die "Not enough arguments\n" if #$argv == 0;
110
111##main
112
113load_userconfig();
114parse_cmd();
115
116if ($cmdopts{type} eq 'b') {
117 # Need to read the spec file to find the tarball. Note that
118 # this also generates most of the shell script required.
119 parse_spec();
120}
121
122# Hokay. Need to:
123# Execute prep if *not* --short-circuit
124if ($cmdopts{stage} eq 'p' || ($cmdopts{stage} =~ /[cilabs]/ && $cmdopts{short} ne 'y')) {
125 prep();
126}
127if ($cmdopts{stage} eq 'c' || ($cmdopts{stage} =~ /[ilabs]/ && $cmdopts{short} ne 'y')) {
128 build();
129}
130if ($cmdopts{stage} =~ /[ilabs]/) {
131 install();
132}
133if ($cmdopts{stage} eq 'a') {
134 binpackage();
135 srcpackage();
136}
137if ($cmdopts{stage} eq 'b') {
138 binpackage();
139}
140if ($cmdopts{stage} eq 's') {
141 srcpackage();
142}
143
144# Just in case.
145exit 0;
146
147
148## load_userconfig()
149# Loads user configuration (if any)
150# Currently only handles .debmacros
151# Needs to handle "other files"
152sub load_userconfig {
153 my (undef,undef,undef,undef,undef,undef,undef,$homedir,undef) = getpwuid($<);
154 if (-e "$homedir/.debmacros") {
155 open USERMACROS,"<$homedir/.debmacros";
156 while (<USERMACROS>) {
157 # And we also only handle a few macros at the moment.
158 if (/^\%_topdir/) {
159 my (undef,$tmp) = split /\s+/, $_;
160 $topdir = $tmp;
161 }
162 }
163 }
164} # end load_userconfig()
165
166
167## parse_cmd()
168# Parses command line into global hash %cmdopts, other globals
169# Options based on rpmbuild's options
170sub parse_cmd {
171 # Don't feel like coding my own option parser...
172 #use Getopt::Long;
173 # ... but I may have to: (OTOH, rpm uses popt, so maybe we can too.)
174 #use Getopt::Popt qw(:all);
175 # Or not. >:( Stupid Debian lack of findable Perl module names in packages.
176
177 # Stuff it.
178 my $prevopt = '';
179 foreach (@ARGV) {
180 chomp;
181
182 # Is it an option?
183 if (/^-/) {
184
185 # Is it a long option?
186 if (/^--/) {
187 if (/^--short-circuit/) {
188 $cmdopts{short} = 'y';
189 } elsif (/^--rebuild/) {
190 $cmdopts{type} = 's';
191 } else {
192 print "Long opt $_\n";
193 }
194 } else {
195 if (/^-[bt]/) {
196 if ($cmdopts{stage} eq 's') {
197 # Mutually exclusive options.
198 die "Can't use $_ with --rebuild\n";
199 } else {
200 ($cmdopts{stage}) = (/^-[bt]([pcilabs])/);
201 ($cmdopts{type}) = (/^-([bt])[pcilabs]/);
202 }
203 } elsif (/^-v/) {
204 # bump verbosity. Not sure what I'll actually do here...
205 } else {
206 die "Bad option $_\n";
207 }
208 }
209 } else {
210 # --buildroot is the only option that takes an argument
211 # Therefore, any *other* bare arguments are the spec file,
212 # tarball, or source package we're operating on - depending
213 # on which one we meet.
214 if ($prevopt eq '--buildroot') {
215 $cmdbuildroot = $_;
216 } else {
217 if ($cmdopts{type} eq 's') {
218 # Source package
219 if (!/\.src\.(deb|rpm)$/) {
220 die "Can't --rebuild with $_\n";
221 }
222 } elsif ($cmdopts{type} eq 'b') {
223 $specfile = $_;
224 # Spec file
225 } else {
226 # Tarball
227 }
228 }
229 }
230 $prevopt = $_;
231 } # foreach @ARGV
232
233 # Some cross-checks. rpmbuild limits --short-circuit to just
234 # the "compile" and "install" targets - with good reason IMO.
235 # NB - this is NOT fatal, just ignored!
236 if ($cmdopts{short} eq 'y' && $cmdopts{stage} =~ /[labs]/) {
237 warn "Can't use --short-circuit for $targets{$cmdopts{stage}} stage. Ignoring.\n";
238 $cmdopts{short} = 'n';
239 }
240 # Valid options, with example arguments (if any):
241# Build from .spec file; mutually exclusive:
242 # -bp
243 # -bc
244 # -bi
245 # -bl
246 # -ba
247 # -bb
248 # -bs
249# Build from tarball; mutually exclusive:
250 # -tp
251 # -tc
252 # -ti
253 # -ta
254 # -tb
255 # -ts
256# Build from .src.(deb|rpm)
257 # --rebuild
258 # --recompile
259
260# General options
261 # --buildroot=DIRECTORY
262 # --clean
263 # --nobuild
264 # --nodeps
265 # --nodirtokens
266 # --rmsource
267 # --rmspec
268 # --short-circuit
269 # --target=CPU-VENDOR-OS
270
271 #my $popt = new Getopt::Popt(argv => \@ARGV, options => \@optionsTable);
272
273} # end parse_cmd()
274
275
276## parse_spec()
277# Parse the .spec file once we've.... uh....
278sub parse_spec {
279 open SPECFILE,"<$specfile";
280
281LINE: while (<SPECFILE>) {
282 next if /^#/;
283 next if /^\s+$/;
284 if (/^\%/) {
285 # A macro that needs further processing.
286 if (/^\%description/) {
287 $pkgdata{main}{desc} .= $_;
288 while (<SPECFILE>) {
289 redo LINE if /^\%/;
290 $pkgdata{main}{desc} .= " $_";
291 }
292
293 }
294 if (/^\%prep/) {
295 # %prep section. May have %setup macro; may include %patch tags,
296 # may be just a bare shell script.
297
298 # This really should be local-ish, but we need just the filename for the source
299 $pkgdata{main}{source} =~ s|.+/([^/]+)$|$1|;
300
301 # Replace some core macros
302 $pkgdata{main}{source} =~ s/\%\{name\}/$pkgdata{main}{name}/;
303 $pkgdata{main}{source} =~ s/\%\{version\}/$pkgdata{main}{version}/;
304
305PREPSCRIPT: while (<SPECFILE>) {
306 if (/^\%setup/) {
307 # Parse out the %setup macro. Note that we aren't supporting
308 # most of RPM's %setup features.
309 $prepscript .= "cd $topdir/BUILD\n";
310 if ( /\s+-n\s+([^\s]+)\s+/ ) {
311 $tarballdir = $1;
312 } else {
313 $tarballdir = "$pkgdata{main}{name}-$pkgdata{main}{version}";
314 }
315 $prepscript .= "rm -rf $tarballdir\ntar -".
316 ( $pkgdata{main}{source} =~ /\.tar\.gz$/ ? "z" : "" ).
317 ( $pkgdata{main}{source} =~ /\.tar\.bz2$/ ? "j" : "" ).
318 "x".( /\s+-q\s+/ ? '' : 'vv' )."f ".
319 "$topdir/SOURCES/$pkgdata{main}{source}\n".
320 qq(STATUS=\$?\nif [ \$STATUS -ne 0 ]; then\n exit \$STATUS\nfi\n).
321 ( /\s+-n\s+([^\s]+)\s+/ ?
322 "cd $1\n" : "cd $pkgdata{main}{name}-$pkgdata{main}{version}\n" ).
323 qq([ `/usr/bin/id -u` = '0' ] && /bin/chown -Rhf root .\n).
324 qq([ `/usr/bin/id -u` = '0' ] && /bin/chgrp -Rhf root .\n).
325 qq(/bin/chmod -Rf a+rX,g-w,o-w .\n);
326 } elsif (/^\%patch([^:]+)\s+(.+)$/) {
327 $prepscript .= "patch $2 <$topdir/SOURCES/".$pkgdata{main}{"patch$1"}."\n";
328 } else {
329 last PREPSCRIPT if /^\%/;
330 $prepscript .= $_;
331 }
332 }
333 redo LINE;
334 }
335 if (/^\%build/) {
336 # %build. This is pretty much just a shell script. There
337 # *are* a few macros, but we're not going to deal with them yet.
338 $buildscript .= "cd $tarballdir\n";
339BUILDSCRIPT: while (<SPECFILE>) {
340 if (/^\%configure/) {
341 my $tmp = expandmacros($_,'configure');
342 $buildscript .= $tmp;
343 } else {
344 # Need to handle %make
345 last BUILDSCRIPT if /^\%/;
346 $buildscript .= $_;
347 }
348 }
349 redo LINE;
350 }
351 if (/^\%install/) {
352 $installscript .= "cd $tarballdir\n";
353 while (<SPECFILE>) {
354 # Need to handle %makeinstall
355 redo LINE if /^\%/;
356 $installscript .= $_;
357 }
358 }
359 if (/^\%clean/) {
360 while (<SPECFILE>) {
361 redo LINE if /^\%/;
362 $cleanscript .= $_;
363 }
364 }
365 if (/^\%post/) {
366 }
367 if (/^\%preun/) {
368 }
369 if (/^\%files/) {
370 # Danger! Danger!
371 }
372 } else {
373 if (/^summary:\s+(.+)/i) {
374 $pkgdata{main}{summary} = $1;
375 } elsif (/^name:\s+(.+)/i) {
376 $pkgdata{main}{name} = $1;
377 } elsif (/^version:\s+(.+)/i) {
378 $pkgdata{main}{version} = $1;
379 } elsif (/^release:\s+(.+)/i) {
380 $pkgdata{main}{release} = $1;
381 } elsif (/^group:\s+(.+)/i) {
382 $pkgdata{main}{group} = $1;
383 } elsif (/^copyright:\s+(.+)/i) {
384 $pkgdata{main}{copyright} = $1;
385 } elsif (/^url:\s+(.+)/i) {
386 $pkgdata{main}{url} = $1;
387 } elsif (/^packager:\s+(.+)/i) {
388 $pkgdata{main}{packager} = $1;
389 } elsif (/^buildroot:\s+(.+)/i) {
390 $buildroot = $1;
391 } elsif (/^source:\s+(.+)/i) {
392 $pkgdata{main}{source} = $1;
393 die "Unknown tarball format $1\n" if $1 !~ /\.tar\.(?:gz|bz2)$/;
394 } elsif (/^patch([^:]+):\s+(.+)$/i) {
395 $pkgdata{main}{"patch$1"} = $2;
396 }
397#Name: suwrap
398#Version: 0.04
399#Release: 3
400#Group: Applications/System
401#Copyright: WebHart internal ONLY. :(
402#BuildArchitectures: i386
403#BuildRoot: /tmp/%{name}-%{version}
404#Url: http://virtual.webhart.net
405#Packager: Kris Deugau <kdeugau@deepnet.cx>
406#Source: ftp://virtual.webhart.net/%{name}-%{version}.tar.gz
407
408 }
409 }
410 # Parse and replace macros
411 # Start with the ones I use
412# $pkgdata{main}{source} =~ s/\%\{name\}/$pkgdata{main}{name}/;
413# $pkgdata{main}{source} =~ s/\%\{version\}/$pkgdata{main}{version}/;
414
415 # Expand macros as necessary.
416 $scriptletbase = expandmacros($scriptletbase,'globals');
417
418 $buildroot = $cmdbuildroot if $cmdbuildroot;
419 $buildroot = expandmacros($buildroot,'pkgdata');
420
421} # end parse_spec()
422
423
424## prep()
425# Unpacks the tarball from the SOURCES directory to the BUILD directory.
426# Speaks gzip and bzip2.
427# Finishes by applying patches in %prep section of spec file
428sub prep {
429 # Replace some things here just to make sure.
430 $prepscript =~ s/\%\{name\}/$pkgdata{main}{name}/g;
431
432 # create script filename
433 my $prepscriptfile = "/var/tmp/deb-tmp.prep.".int(rand(99998)+1);
434 sysopen(PREPSCRIPT, $prepscriptfile, O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW)
435 or die $!;
436 print PREPSCRIPT $scriptletbase;
437 print PREPSCRIPT $prepscript;
438 close PREPSCRIPT;
439
440 # execute
441 print "Calling \%prep script $prepscriptfile...\n";
442 system("/bin/sh -e $prepscriptfile") == 0
443 or die "Can't exec: $!\nalso: $?";
444
445 # and clean up
446 unlink $prepscriptfile;
447} # end prep()
448
449
450## build()
451# Execute commands provided as a shell script. It may prove simpler
452# to do as rpm does and actually create a little shell script.
453sub build {
454 # create script filename
455 my $buildscriptfile = "/var/tmp/deb-tmp.build.".int(rand(99998)+1);
456 sysopen(BUILDSCRIPT, $buildscriptfile, O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW)
457 or die $!;
458 print BUILDSCRIPT $scriptletbase;
459
460 $buildscript = expandmacros($buildscript,'configure');
461 print BUILDSCRIPT $buildscript;
462 close BUILDSCRIPT;
463
464#print "'$scriptletbase$buildscript'";
465#exit 0;
466 # execute
467 print "Calling \%build script $buildscriptfile...\n";
468 system("/bin/sh -e $buildscriptfile") == 0
469 or die "Can't exec: $!\nalso: $?";
470
471 # and clean up
472 unlink $buildscriptfile;
473} # end build()
474
475
476## install()
477# Creates and executes %install script(let)
478sub install {
479 # create script filename
480 my $installscriptfile = "/var/tmp/deb-tmp.inst.".int(rand(99998)+1);
481 sysopen(INSTSCRIPT, $installscriptfile, O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW)
482 or die $!;
483 print INSTSCRIPT $scriptletbase;
484 print INSTSCRIPT $cleanscript; # Clean up our install target before installing into it.
485 print INSTSCRIPT $installscript;
486 close INSTSCRIPT;
487
488 # execute
489 print "Calling \%install script $installscriptfile...\n";
490 system("/bin/sh -e $installscriptfile") == 0
491 or die "Can't exec: $!\nalso: $?";
492
493 # and clean up
494 unlink $installscriptfile;
495} # end install()
496
497
498## binpackage()
499# Creates the binary .deb package from the installed tree in $buildroot.
500sub binpackage {
501 # Some checks on the .deb file location
502 if (!-e "$topdir/DEBS/i386") {
503# "if [ -e $topdir/DEBS/i386 ]; then\n\tmkdir $topdir/DEBS/i386\nfi\n".
504 mkdir "$topdir/DEBS/i386";
505 }
506 # Gotta do this first, otherwise the control file has nowhere to go. >:(
507 mkdir "$buildroot/DEBIAN";
508
509 # create script filename
510 my $debscriptfile = "/var/tmp/deb-tmp.pkg.".int(rand(99998)+1);
511 sysopen(DEBSCRIPT, $debscriptfile, O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW)
512 or die $!;
513 print DEBSCRIPT $scriptletbase;
514 print DEBSCRIPT "tree $buildroot\n".
515 "fakeroot dpkg-deb -b $buildroot $topdir/DEBS/i386/$pkgdata{main}{name}_$pkgdata{main}{version}-$pkgdata{main}{release}_i386.deb\n";
516
517 close DEBSCRIPT;
518
519 my $control = "Package: $pkgdata{main}{name}\n".
520 "Version: $pkgdata{main}{version}-$pkgdata{main}{release}\n".
521 "Section: unknown\n".
522 "Priority: optional\n".
523 "Architecture: i386\n".
524 "Maintainer: $pkgdata{main}{packager}\n".
525 "Description: $pkgdata{main}{desc}\n";
526
527eval {
528 open CONTROL, ">$buildroot/DEBIAN/control";
529 print CONTROL $control;
530 close CONTROL;
531};
532if ($@) {
533 print $@;
534}
535
536#Package: httpd
537#Version: 2.0.54-7via
538#Section: unknown
539#Priority: optional
540#Architecture: i386
541#Depends: libc6 (>= 2.3.2.ds1-21), libdb4.2, libexpat1 (>= 1.95.8), libssl0.9.7, libapr0
542#Replaces: apache2
543#Installed-Size: 3076
544#Maintainer: Kris Deugau <kdeugau@vianet.ca>
545#Description: apache2 for ViaNet
546# apache2 for ViaNet. Includes per-vhost setuid patches from
547# http://home.samfundet.no/~sesse/mpm-itk/.
548
549
550 # execute
551 print "Creating .deb for $pkgdata{main}{name}...\n";
552 system("/bin/sh -e $debscriptfile") == 0
553 or die "Can't exec: $!\nalso: $?";
554
555 # and clean up
556 unlink $debscriptfile;
557
558} # end binpackage()
559
560
561sub srcpackage {}
562
563
564## expandmacros()
565# Expands all %{blah} macros in the passed string
566sub expandmacros {
567 my $macrostring = shift;
568 my $section = shift; # We'll want to split things up a bit eventually.
569
570 if ($section eq 'configure') {
571 # %configure-ish macros
572 # Note that these are processed in reverse order to get the substitution order right
573 # Foistly, %configure itself:
574# Don't know what it's for, don't have a useful default replacement
575# --program-prefix=%{_program_prefix} \
576# Coding Q: Why does this not work if I move the subst string into its own isolated quotes?
577 $macrostring =~ s'%configure'./configure --host=$DEB_HOST_GNU_TYPE \
578 --build=$DEB_BUILD_GNU_TYPE \
579 --prefix=%{_prefix} \
580 --exec-prefix=%{_exec_prefix} \
581 --bindir=%{_bindir} \
582 --sbindir=%{_sbindir} \
583 --sysconfdir=%{_sysconfdir} \
584 --datadir=%{_datadir} \
585 --includedir=%{_includedir} \
586 --libdir=%{_libdir} \
587 --libexecdir=%{_libexecdir} \
588 --localstatedir=%{_localstatedir} \
589 --sharedstatedir=%{_sharedstatedir} \
590 --mandir=%{_mandir} \
591 --infodir=%{_infodir} ';
592
593 # Note that the above regex terminates with the extra space
594 # "Just In Case" of user additions to the %configure line,
595 # which will then get neatly tagged on the end where they
596 # take precedence (supposedly) over the "default" ones.
597
598 $macrostring =~ s'%{_mandir}'%{_prefix}/man'g;
599 $macrostring =~ s'%{_infodir}'%{_prefix}/info'g;
600 $macrostring =~ s'%{_oldincludedir}'/usr/include'g;
601 $macrostring =~ s'%{_includedir}'%{_prefix}/include'g;
602 $macrostring =~ s'%{_libdir}'%{_exec_prefix}/%{_lib}'g;
603 $macrostring =~ s'%{_lib}'lib'g;
604 $macrostring =~ s'%{_localstatedir}'%{_prefix}/var'g;
605 $macrostring =~ s'%{_sharedstatedir}'%{_prefix}/com'g;
606 $macrostring =~ s'%{_sysconfdir}'%{_prefix}/etc'g;
607 $macrostring =~ s'%{_datadir}'%{_prefix}/share'g;
608 $macrostring =~ s'%{_libexecdir}'%{_exec_prefix}/libexec'g;
609 $macrostring =~ s'%{_sbindir}'%{_exec_prefix}/sbin'g;
610 $macrostring =~ s'%{_bindir}'%{_exec_prefix}/bin'g;
611 $macrostring =~ s'%{_exec_prefix}'%{_prefix}'g;
612 $macrostring =~ s'%{_prefix}'/usr'g;
613 } # done with config section
614
615 if ($section eq 'globals') {
616 $macrostring =~ s/\%\{_topdir\}/$topdir/g;
617
618 # "fake" globals (may be tweaked by package)
619 $macrostring =~ s/\%\{buildroot\}/$buildroot/g;
620
621 # sub-package(compatible) stuff
622 $macrostring =~ s/\%\{name\}/$pkgdata{main}{name}/g;
623 $macrostring =~ s/\%\{version\}/$pkgdata{main}{version}/g;
624 $macrostring =~ s/\%\{release\}/$pkgdata{main}{release}/g;
625 } # done with globals section
626
627 if ($section eq 'pkgdata') {
628 $macrostring =~ s/\%\{name\}/$pkgdata{main}{name}/g;
629 $macrostring =~ s/\%\{version\}/$pkgdata{main}{version}/g;
630 $macrostring =~ s/\%\{release\}/$pkgdata{main}{release}/g;
631 }
632
633 return $macrostring;
634} # end expandmacros()
Note: See TracBrowser for help on using the repository browser.