Annotation of loncom/build/make_rpm.pl, revision 1.13
1.1 harris41 1: #!/usr/bin/perl
2:
1.12 harris41 3: # The LearningOnline Network with CAPA
1.13 ! harris41 4: # make_rpm.pl - make RedHat package manager file (A CLEAN AND CONFIGURABLE WAY)
1.12 harris41 5: #
1.13 ! harris41 6: # $Id: make_rpm.pl,v 1.12 2002/01/05 00:48:05 harris41 Exp $
1.12 harris41 7: #
8: # Written by Scott Harrison, harris41@msu.edu
9: #
10: # Copyright Michigan State University Board of Trustees
11: #
12: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
13: #
14: # LON-CAPA is free software; you can redistribute it and/or modify
15: # it under the terms of the GNU General Public License as published by
16: # the Free Software Foundation; either version 2 of the License, or
17: # (at your option) any later version.
18: #
19: # LON-CAPA is distributed in the hope that it will be useful,
20: # but WITHOUT ANY WARRANTY; without even the implied warranty of
21: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22: # GNU General Public License for more details.
23: #
24: # You should have received a copy of the GNU General Public License
25: # along with LON-CAPA; if not, write to the Free Software
26: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
27: #
28: # http://www.lon-capa.org/
29: #
30: # YEAR=2000
31: # 9/30,10/2,12/11,12/12,12/21 - Scott Harrison
32: # YEAR=2001
33: # 1/8,1/10,1/13,1/23,5/16 - Scott Harrison
34: # YEAR=2002
1.13 ! harris41 35: # 1/4,1/8 - Scott Harrison
1.12 harris41 36: #
37: ###
38:
39: # Automatically generate RPM files
1.1 harris41 40: # from file listing.
41:
1.13 ! harris41 42: # This script builds the RPM.
1.1 harris41 43:
1.2 harris41 44: # This script also generates and then deletes temporary
1.1 harris41 45: # files (and binary root directory tree) to build an RPM with.
1.13 ! harris41 46: # It is designed to work cleanly and independently from pre-existing
! 47: # directory trees such as /usr/src/redhat/*.
1.1 harris41 48:
49: # I still need to implement the CONFIGURATION_FILES and
50: # DOCUMENTATION_FILES portion of the command line interface to this
51: # script.
52:
53: # Take in a file list (from standard input),
54: # a description tag and version tag from command line argument
1.2 harris41 55: # and temporarily generate a:
1.1 harris41 56: # RPM .spec file
57: # RPM Makefile
58: # SourceRoot
1.2 harris41 59:
60: # A resulting .rpm file is generated.
1.1 harris41 61:
1.13 ! harris41 62: ###############################################################################
! 63: ## ##
! 64: ## ORGANIZATION OF THIS PERL SCRIPT ##
! 65: ## ##
! 66: ## 1. Check to see if RPM builder application is available ##
! 67: ## 2. Read in arguments ##
! 68: ## 3. Generate temporary directories ##
! 69: ## 4. Initialize some variables ##
! 70: ## 5. Create a standalone rpm building environment ##
! 71: ## 6. Perform variable initializations and customizations ##
! 72: ## 7. Print header information for .spec file ##
! 73: ## 8. Process file list and generate information ##
! 74: ## 9. Generate SRPM and BinaryRoot Makefiles ##
! 75: ## 10. mirror copy (BinaryRoot) files under a temporary directory ##
! 76: ## 11. roll everything into an rpm ##
! 77: ## 12. clean everything up ##
! 78: ## 13. find_info - recursively gather information from a directory ##
! 79: ## 14. Plain Old Documentation ##
! 80: ## ##
! 81: ###############################################################################
! 82:
! 83: use strict;
! 84:
! 85: # ------------------------ Check to see if RPM builder application is available
! 86:
! 87: unless (-e '/usr/lib/rpm/rpmrc') {
1.1 harris41 88: print <<END;
1.12 harris41 89: ERROR: This script only works with a properly installed RPM builder
90: application.
1.1 harris41 91: Cannot find /usr/lib/rpm/rpmrc, so cannot generate customized rpmrc file.
92: Script aborting.
93: END
94: }
95:
1.13 ! harris41 96: # ----------------------------------------------------------- Read in arguments
! 97:
! 98: my ($tag,$version,$configuration_files,$documentation_files,
! 99: $pathprefix,$customize)=@ARGV;
1.1 harris41 100: @ARGV=();
1.3 harris41 101:
1.1 harris41 102: if (!$version) {
1.13 ! harris41 103: print <<END;
! 104: See "perldoc make_rpm.pl" for more information.
! 105:
! 106: Usage: <TAG> <VERSION> [CONFIGURATION_FILES]
! 107: [DOCUMENTATION_FILES] [PATHPREFIX] [CUSTOMIZATION_XML]
! 108: Standard input provides the list of files to work with.
! 109: TAG, required descriptive tag. For example, a kerberos software
! 110: package might be tagged as "krb4".
! 111: VERSION, required version. Needed to generate version information
! 112: for the RPM. This should be in the format N.M where N and M are
! 113: integers.
! 114: CONFIGURATION_FILES, optional comma-separated listing of files to
! 115: be treated as configuration files by RPM (and thus subject to saving
! 116: during RPM upgrades).
! 117: DOCUMENTATION_FILES, optional comma-separated listing of files to be
! 118: treated as documentation files by RPM (and thus subject to being
! 119: placed in the /usr/doc/RPM-NAME directory during RPM installation).
! 120: PATHPREFIX, optional path to be removed from file listing. This
! 121: is in case you are building an RPM from files elsewhere than
! 122: root-level. Note, this still depends on a root directory hierarchy
! 123: after PATHPREFIX.
! 124: CUSTOMIZATION_XML, allows for customizing various pieces of information such
! 125: as vendor, summary, name, copyright, group, autoreqprov, requires, prereq,
! 126: description, and pre-installation scripts (see more in the POD;
! 127: perldoc make_rpml.pl).
! 128: END
1.1 harris41 129: exit;
130: }
131:
132: mkdir $tag,0755;
133: mkdir "$tag/BuildRoot",0755;
134: mkdir "$tag/SOURCES",0755;
135: mkdir "$tag/SPECS",0755;
136: mkdir "$tag/BUILD",0755;
137: mkdir "$tag/SRPMS",0755;
138: mkdir "$tag/RPMS",0755;
139: mkdir "$tag/RPMS/i386",0755;
140:
1.13 ! harris41 141: # -------------------------------------------------------- Initialize variables
! 142:
1.1 harris41 143: my $file;
144: my $binaryroot="$tag/BinaryRoot";
145: my ($type,$size,$octalmode,$user,$group);
146:
1.13 ! harris41 147: my $currentdir=`pwd`; chop $currentdir; my $invokingdir=$currentdir;
1.12 harris41 148: $currentdir.="/$tag";
1.1 harris41 149:
1.13 ! harris41 150: # -------------------------------- Create a standalone rpm building environment
! 151:
! 152: open (IN,'</usr/lib/rpm/rpmrc') or die('Cannot open /usr/lib/rpm/rpmrc');
! 153: my @lines=<IN>;
1.1 harris41 154: close IN;
155:
156: open (RPMRC,">$tag/SPECS/rpmrc");
1.13 ! harris41 157: foreach my $line (@lines) {
1.1 harris41 158: if ($line=~/^macrofiles/) {
159: chop $line;
1.11 harris41 160: $line.=":$currentdir/SPECS/rpmmacros\n";
1.1 harris41 161: }
162: print RPMRC $line;
163: }
164: close RPMRC;
165:
166: open (RPMMACROS,">$tag/SPECS/rpmmacros");
167: print RPMMACROS <<END;
168: \%_topdir $currentdir
169: \%__spec_install_post \\
170: /usr/lib/rpm/brp-strip \\
171: /usr/lib/rpm/brp-strip-comment-note \\
172: \%{nil}
173: END
174: close RPMMACROS;
175:
1.13 ! harris41 176: # ------------------------- Perform variable initializations and customizations
! 177:
! 178: my $cu;
! 179: if ($customize) {
! 180: open (IN,"<$customize") or die("Cannot open $customize");
! 181: my @clines=(<IN>);
! 182: $cu=join('',@clines);
! 183: close IN;
! 184: }
! 185: my $tv; # tag value variable for storing retrievals from $cu
! 186:
! 187: # (Sure. We could use HTML::TokeParser here.. but that wouldn't be fun now,
! 188: # would it?)
! 189: my $name=$tag;
! 190: # read in name from customization if available
! 191: $tv=grabtag('name',$cu,1); $name=$tv if $tv;
! 192: $name=~s/\<tag \/\>/$tag/g;
! 193:
! 194: # okay.. now we can generate this needed directory
! 195: mkdir "$tag/SOURCES/$name-$version",0755;
! 196:
1.7 harris41 197: my $requires="";
1.13 ! harris41 198: # read in relevant requires info from customization file (if applicable)
! 199: # note that "PreReq: item" controls order of CD-ROM installation (if you
! 200: # are making a customized CD-ROM)
! 201: # "Requires: item" just enforces dependencies from the command-line invocation
! 202: $tv=grabtag('requires',$cu,1); $requires=$tv if $tv;
! 203: # do more require processing here
! 204: $requires=~s/\s*\<\/item\>\s*//g;
! 205: $requires=~s/\s*\<item\>\s*/\n/g;
! 206: $requires=~s/^\s+//s;
! 207:
! 208: my $summary="Files for the $name software package.";
! 209: # read in summary from customization if available
! 210: $tv=grabtag('summary',$cu,1); $summary=$tv if $tv;
! 211: $summary=~s/\<tag \/\>/$tag/g;
! 212:
! 213: my $copyright="not specified here";
! 214: # read in copyright from customization if available
! 215: $tv=grabtag('copyright',$cu,1); $copyright=$tv if $tv;
! 216: $copyright=~s/\<tag \/\>/$tag/g;
! 217:
! 218: open (SPEC,">$tag/SPECS/$name-$version.spec");
! 219:
! 220: my $vendor='Me';
! 221: # read in vendor from customization if available
! 222: $tv=grabtag('vendor',$cu,1); $vendor=$tv if $tv;
! 223: $vendor=~s/\<tag \/\>/$tag/g;
! 224:
! 225: my $description="$name software package";
! 226: # read in description from customization if available
! 227: $tv=grabtag('description',$cu,0); $description=$tv if $tv;
! 228: $description=~s/\<tag \/\>/$tag/g;
! 229:
! 230: my $pre="";
! 231: # read in pre-installation script if available
! 232: $tv=grabtag('pre',$cu,0); $pre=$tv if $tv;
! 233: $pre=~s/\<tag \/\>/$tag/g;
1.1 harris41 234:
1.13 ! harris41 235: # ------------------------------------- Print header information for .spec file
1.12 harris41 236:
1.1 harris41 237: print SPEC <<END;
1.13 ! harris41 238: Summary: $summary
! 239: Name: $name
1.1 harris41 240: Version: $version
241: Release: 1
1.12 harris41 242: Vendor: $vendor
1.1 harris41 243: BuildRoot: $currentdir/BuildRoot
1.13 ! harris41 244: Copyright: $copyright
1.1 harris41 245: Group: Utilities/System
1.13 ! harris41 246: Source: $name-$version.tar.gz
1.1 harris41 247: AutoReqProv: no
1.7 harris41 248: $requires
1.1 harris41 249: # requires: filesystem
250: \%description
1.13 ! harris41 251: $description
1.1 harris41 252:
253: \%prep
254: \%setup
255:
256: \%build
257: rm -Rf "$currentdir/BuildRoot"
258:
259: \%install
260: make ROOT="\$RPM_BUILD_ROOT" SOURCE="$currentdir/BinaryRoot" directories
261: make ROOT="\$RPM_BUILD_ROOT" SOURCE="$currentdir/BinaryRoot" files
262: make ROOT="\$RPM_BUILD_ROOT" SOURCE="$currentdir/BinaryRoot" links
263:
264: \%pre
1.13 ! harris41 265: $pre
1.1 harris41 266:
267: \%post
268: \%postun
269:
270: \%files
271: END
272:
1.13 ! harris41 273: # ------------------------------------ Process file list and gather information
! 274:
! 275: my %BinaryRootMakefile;
! 276: my %Makefile;
! 277: my %dotspecfile;
! 278:
1.1 harris41 279: foreach $file (<>) {
280: chop $file;
1.4 harris41 281: my $comment="";
282: if ($file=~/\s+\#(.*)$/) {
283: $file=~s/\s+\#(.*)$//;
284: $comment=$1;
285: }
1.13 ! harris41 286: my $directive="";
! 287: if ($comment=~/config\(noreplace\)/) {
! 288: $directive="\%config(noreplace) ";
! 289: }
! 290: elsif ($comment=~/config/) {
! 291: $directive="\%config ";
! 292: }
! 293: elsif ($comment=~/doc/) {
! 294: $directive="\%doc";
1.4 harris41 295: }
1.1 harris41 296: if (($type,$size,$octalmode,$user,$group)=find_info($file)) {
297: $octalmode="0" . $octalmode if length($octalmode)<4;
298: if ($pathprefix) {
299: $file=~s/^$pathprefix//;
300: }
301: if ($type eq "files") {
1.12 harris41 302: push @{$BinaryRootMakefile{$type}},"\tinstall -D -m $octalmode ".
303: "$pathprefix$file $binaryroot$file\n";
304: push @{$Makefile{$type}},"\tinstall -D -m $octalmode ".
305: "\$(SOURCE)$file \$(ROOT)$file\n";
1.13 ! harris41 306: push @{$dotspecfile{$type}},"$directive\%attr($octalmode,$user,".
1.12 harris41 307: "$group) $file\n";
1.1 harris41 308: }
309: elsif ($type eq "directories") {
1.12 harris41 310: push @{$BinaryRootMakefile{$type}},"\tinstall -m $octalmode -d ".
311: "$binaryroot$file\n";
312: push @{$Makefile{$type}},"\tinstall -m $octalmode -d ".
313: "\$(SOURCE)$file \$(ROOT)$file\n";
314: push @{$dotspecfile{$type}},"\%dir \%attr($octalmode,$user,".
315: "$group) $file\n";
1.1 harris41 316: }
317: elsif ($type eq "links") {
1.12 harris41 318: my $link=$size; # I use the size variable to pass the link value
319: # from the subroutine find_info
1.1 harris41 320: $link=~s/^$pathprefix//;
1.12 harris41 321: push @{$BinaryRootMakefile{$type}},
322: "\tln -s $link $binaryroot$file\n";
1.1 harris41 323: push @{$Makefile{$type}},"\tln -s $link \$(ROOT)$file\n";
324: push @{$dotspecfile{$type}},"\%attr(-,$user,$group) $file\n";
325: }
326: }
327: }
328:
1.13 ! harris41 329: # -------------------------------------- Generate SRPM and BinaryRoot Makefiles
! 330:
! 331: open OUTS, ">$tag/SOURCES/$name-$version/Makefile";
! 332: open OUTB, ">$tag/BinaryRootMakefile";
1.1 harris41 333: foreach $type ("directories","files","links") {
1.13 ! harris41 334: print OUTS "$type\:\n";
! 335: print OUTS join("",@{$Makefile{$type}}) if $Makefile{$type};
! 336: print OUTS "\n";
! 337: print OUTB "$type\:\n";
! 338: print OUTB join("",@{$BinaryRootMakefile{$type}})
! 339: if $BinaryRootMakefile{$type};
! 340: print OUTB "\n";
! 341: print SPEC join("",@{$dotspecfile{$type}}) if $dotspecfile{$type};
1.1 harris41 342: }
1.13 ! harris41 343: close OUTB;
! 344: close OUTS;
1.1 harris41 345:
1.13 ! harris41 346: close SPEC;
1.1 harris41 347:
1.13 ! harris41 348: # ------------------ mirror copy (BinaryRoot) files under a temporary directory
1.1 harris41 349:
350: `make -f $tag/BinaryRootMakefile directories`;
351: `make -f $tag/BinaryRootMakefile files`;
352: `make -f $tag/BinaryRootMakefile links`;
353:
1.13 ! harris41 354: # ------------------------------------------------- roll everything into an RPM
! 355:
! 356: my $command="cd $currentdir/SOURCES; tar czvf $name-$version.tar.gz ".
! 357: "$name-$version";
1.12 harris41 358: print `$command`;
359: $command="cd $currentdir/SPECS; rpm --rcfile=./rpmrc -ba ".
1.13 ! harris41 360: "$name-$version.spec; cd ../RPMS/i386; cp ".
! 361: "$name-$version-1.i386.rpm $invokingdir/.";
1.12 harris41 362: print `$command`;
1.13 ! harris41 363:
! 364: # --------------------------------------------------------- clean everything up
! 365:
1.5 harris41 366: print `cd $invokingdir; rm -Rf $tag`;
1.1 harris41 367:
1.13 ! harris41 368: # ----- Subroutine: find_info - recursively gather information from a directory
1.1 harris41 369: sub find_info {
370: # only look for
371: my ($file)=@_;
372: my $line;
373: if (($line=`find $file -type f -prune`)=~/^$file\n/) {
374: $line=`find $file -type f -prune -printf "\%s\t\%m\t\%u\t\%g"`;
375: return ("files",split(/\t/,$line));
376: }
377: elsif (($line=`find $file -type d -prune`)=~/^$file\n/) {
378: $line=`find $file -type d -prune -printf "\%s\t\%m\t\%u\t\%g"`;
379: return ("directories",split(/\t/,$line));
380: }
381: elsif (($line=`find $file -type l -prune`)=~/^$file\n/) {
1.6 harris41 382: $line=`find $file -type l -prune -printf "\%l\t\%m\t\%u\t\%g"`;
1.1 harris41 383: return ("links",split(/\t/,$line));
384: }
385:
386: }
1.13 ! harris41 387:
! 388: # ------------------------- Subroutine: grabtag - grab a tag from an xml string
! 389: sub grabtag {
! 390: my ($tag,$text,$clean)=@_;
! 391: # meant to be quick and dirty as opposed to a formal state machine parser
! 392: my $value;
! 393: $cu=~/\<$tag\>(.*?)\<\/$tag\>/s;
! 394: $value=$1; $value=~s/^\s+//;
! 395: if ($clean) {
! 396: $value=~s/\n\s/ /g;
! 397: $value=~s/\s\n/ /g;
! 398: $value=~s/\n/ /g;
! 399: $value=~s/\s+$//;
! 400: }
! 401: return $value;
! 402: }
! 403:
! 404: # ----------------------------------------------------- Plain Old Documentation
! 405:
! 406: =head1 NAME
! 407:
! 408: make_rpm.pl - automatically generate an RPM software package
! 409:
! 410: =head1 SYNOPSIS
! 411:
! 412: Usage: <TAG> <VERSION> [CONFIGURATION_FILES]
! 413: [DOCUMENTATION_FILES] [PATHPREFIX] [CUSTOMIZATION_XML]
! 414:
! 415: Standard input provides the list of files to work with.
! 416:
! 417: TAG, required descriptive tag. For example, a kerberos software
! 418: package might be tagged as "krb4".
! 419:
! 420: VERSION, required version. Needed to generate version information
! 421: for the RPM. This should be in the format N.M where N and M are
! 422: integers.
! 423:
! 424: CONFIGURATION_FILES, optional comma-separated listing of files to
! 425: be treated as configuration files by RPM (and thus subject to saving
! 426: during RPM upgrades).
! 427:
! 428: DOCUMENTATION_FILES, optional comma-separated listing of files to be
! 429: treated as documentation files by RPM (and thus subject to being
! 430: placed in the /usr/doc/RPM-NAME directory during RPM installation).
! 431:
! 432: PATHPREFIX, optional path to be removed from file listing. This
! 433: is in case you are building an RPM from files elsewhere than
! 434: root-level. Note, this still depends on a root directory hierarchy
! 435: after PATHPREFIX.
! 436:
! 437: CUSTOMIZATION_XML, allows for customizing various pieces of information such
! 438: as vendor, summary, name, copyright, group, autoreqprov, requires, prereq,
! 439: description, and pre-installation scripts (see more in the POD;
! 440: perldoc make_rpml.pl).
! 441:
! 442: Examples:
! 443:
! 444: [prompt] find notreallyrootdir | perl make_rpm.pl makemoney 3.1 '' \
! 445: '/usr/doc/man/man3/makemoney.3' notreallyrootdir
! 446: would generate makemoney-3.1-1.i386.rpm
! 447:
! 448: [prompt] find /usr/local/bin | perl make_rpm.pl mybinfiles 1.0
! 449: would generate mybinfiles-1.0-1.i386.rpm
! 450:
! 451: [prompt] find romeo | perl make_rpm.pl romeo 1.0 '' '' '' customize.xml
! 452: would generate romeo with customizations from customize.xml.
! 453:
! 454: The CUSTOMIZATION_XML argument represents a way to customize the
! 455: numerous variables associated with RPMs. This argument represents
! 456: a file name. (Parsing is done in an unsophisticated fashion using
! 457: regular expressions.) Here are example contents of such a file:
! 458:
! 459: <vendor>
! 460: Laboratory for Instructional Technology Education, Division of
! 461: Science and Mathematics Education, Michigan State University.
! 462: </vendor>
! 463: <summary>Files for the <tag /> component of LON-CAPA</summary>
! 464: <name>LON-CAPA-<tag /></name>
! 465: <copyright>Michigan State University patents may apply.</copyright>
! 466: <group>Utilities/System</group>
! 467: <AutoReqProv>no</AutoReqProv>
! 468: <requires tag='setup'>
! 469: <item>PreReq: setup</item>
! 470: <item>PreReq: passwd</item>
! 471: <item>PreReq: util-linux</item>
! 472: </requires>
! 473: <requires tag='base'>
! 474: <item>PreReq: LON-CAPA-setup</item>
! 475: <item>PreReq: apache</item>
! 476: <item>PreReq: /etc/httpd/conf/access.conf</item>
! 477: </requires>
! 478: <requires>
! 479: <item>Requires: LON-CAPA-base</item>
! 480: </requires>
! 481: <description>
! 482: This package is automatically generated by the make_rpm.pl perl
! 483: script (written by the LON-CAPA development team, www.lon-capa.org,
! 484: Scott Harrison). This implements the <tag /> component for LON-CAPA.
! 485: For more on the LON-CAPA project, visit http://www.lon-capa.org/.
! 486: </description>
! 487: <pre>
! 488: echo "***********************************************************************"
! 489: echo "LON-CAPA LearningOnline with CAPA"
! 490: echo "http://www.lon-capa.org/"
! 491: echo " "
! 492: echo "Laboratory for Instructional Technology Education"
! 493: echo "Michigan State University"
! 494: echo " "
! 495: echo "** Michigan State University patents may apply **"
! 496: echo " "
! 497: echo "This installation assumes an installation of Redhat 6.2"
! 498: echo " "
! 499: echo "The server computer should be currently connected to the ethernet"
! 500: echo " "
! 501: echo "The files in this package are only those for the <tag /> component."
! 502: echo "Configuration files are sometimes part of the LON-CAPA-base RPM."
! 503: echo "***********************************************************************"
! 504: </pre>
! 505:
! 506: =head1 DESCRIPTION
! 507:
! 508: Automatically generate an RPM software package from a list of files.
! 509:
! 510: This script builds the RPM in a very clean and configurable fashion.
! 511: (Finally! Making RPMs the simple way!)
! 512:
! 513: This script generates and then deletes temporary
! 514: files (and binary root directory tree) to build an RPM with.
! 515: It is designed to work cleanly and independently from pre-existing
! 516: directory trees such as /usr/src/redhat/*.
! 517:
! 518: Take in a file list (from standard input),
! 519: a description tag and version tag from command line argument
! 520: and temporarily generate a:
! 521: RPM .spec file
! 522: RPM Makefile
! 523: SourceRoot
! 524:
! 525: A resulting .rpm file is generated.
! 526:
! 527: =head1 README
! 528:
! 529: Automatically generate an RPM software package from a list of files.
! 530:
! 531: This script builds the RPM in a very clean and configurable fashion.
! 532: (Finally! Making RPMs the simple way!)
! 533:
! 534: This script generates and then deletes temporary
! 535: files (and binary root directory tree) to build an RPM with.
! 536: It is designed to work cleanly and independently from pre-existing
! 537: directory trees such as /usr/src/redhat/*.
! 538:
! 539: =head1 PREREQUISITES
! 540:
! 541: This script requires the C<strict> module.
! 542:
! 543: =pod OSNAMES
! 544:
! 545: any
! 546:
! 547: =pod SCRIPT CATEGORIES
! 548:
! 549: UNIX/System Administration
! 550:
! 551: =cut
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>