3 # addport - perl script that adds new ports to the
4 # FreeBSD Ports Collection. Replaces easy-import.
6 # Copyright (c) 2000 Will Andrews and Michael Haro
9 # Redistribution and use in source and binary forms, with or without
10 # modification, are permitted provided that the following conditions
12 # 1. Redistributions of source code must retain the above copyright
13 # notice, this list of conditions and the following disclaimer.
14 # 2. Redistributions in binary form must reproduce the above copyright
15 # notice, this list of conditions and the following disclaimer in the
16 # documentation and/or other materials provided with the distribution.
18 # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22 # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 # Original shell script & idea: Will Andrews <will@FreeBSD.org>
31 # Original conversion to Perl : Michael Haro <mharo@FreeBSD.org>
32 # Maintainer alumni : Renato Botelho <garga@FreeBSD.org>
34 # Id: addport,v 1.2 2000/04/02 06:21:13 will Exp (original shell script)
35 # Id: addport,v 1.5 2000/04/22 22:19:43 mharo Exp (perl conversion)
38 # MAINTAINER= crees@FreeBSD.org
41 # Smartmatch operator (~~) used
50 # Subroutine prototypes
63 getopts('ac:d:fh:il:L:mns:tu:y', \%opts);
65 my $autofill_l = $opts{'l'};
66 my $autofill_L = $opts{'L'};
67 my $autofill = ($autofill_l ? $autofill_l : $autofill_L);
68 my $c = $opts{'c'} if ($opts{'c'} ne "");
69 my $distdir = $opts{'s'} if ($opts{'s'} ne "");
71 my $h = "repo.FreeBSD.org";
72 $h = $opts{'h'} if ($opts{'h'} ne "");
74 $u = $opts{'u'} if ($opts{'u'} ne "");
75 my $more_testing = $opts{'t'};
76 my $interactive = $opts{'i'};
77 my $nomkdir = $opts{'m'};
78 my $addlchk = $opts{'a'};
79 my $nofetch = $opts{'f'};
80 my $checkexist = $opts{'y'};
81 my $currentdir = abs_path(".");
90 'portuguese' => 'pt-',
93 'vietnamese' => 'vi-',
98 my $portsdir = $ENV{PORTSDIR} ? $ENV{PORTSDIR} : '/usr/ports';
99 my $repo = $ENV{ADDPSVNROOT}? $ENV{ADDPSVNROOT} : "svn+ssh://$u\@$h/ports/head";
101 my $portlint = `which portlint`; chomp $portlint;
102 my $plint_args = "-N -a -c -t";
107 chomp(my $svnlite = `which svnlite`);
108 my $svn = ($svnlite ? $svnlite : "svn");
109 my $keyword = '\$FreeBSD\\\$';
110 # vars required for commitfile
111 my $descr; my $portversion; my $pkgcomment;
112 my $tmp; my $pkgcommentlen; my $comment; my $maintainer = "";
113 my $maintaineraddr; my $tmp2; my $offset; my $commitfile = "";
115 $tmp = $tmp2 = $offset = 0;
118 my $edit = "/usr/bin/vi";
119 $edit = $ENV{EDITOR} if ($ENV{EDITOR} ne "");
122 my $svnversion = `$svn --version --quiet 2>/dev/null`;
124 if ($svnversion eq "") {
125 errx(1, "Subversion binary not found in \$PATH, aborting.");
127 my @svnversion = split(/\./, $svnversion);
128 if ($svnversion[0] == 1 && $svnversion[1] < 7) {
129 errx(1, "minimum Subversion version of 1.7 not met, aborting.");
132 # stuff that always happens when we start
134 $tmpdir=`mktemp -d -t ap`;
137 errx(1,"making random tmpdir didn't work, aborting.");
141 # stuff that always happens when we exit
143 # only remove $tmpdir if it points to something in /tmp
144 # this is a silly little security thing
145 if (defined($rm) && defined($tmpdir)) {
146 system("$rm -rf $tmpdir") if ($tmpdir =~ m,/tmp/,);
150 # setup the list of commands to run on the new port(s).
153 if ($addlchk && -f $portlint) {
154 $passenv = "DISTDIR=\"$distdir\"" if -d $distdir;
155 $passenv = $passenv . " PORTSDIR=\"$portsdir\"" if !$nomkdir;
156 push(@commands, "$make $passenv clean check-categories");
157 push(@commands, "$portlint $plint_args");
158 push(@commands, "$make $passenv FETCH_BEFORE_ARGS='-A' checksum") if !$nofetch;
160 push(@commands, "$make $passenv distclean");
161 push(@commands, "$make $passenv build");
162 push(@commands, "$make $passenv distclean");
166 print "Checking out Mk directory to ensure portlint correctness.\n";
167 system("$svn co $repo/Mk Mk") && errx(1, "Could not checkout Mk directory");
168 system("$svn co $repo/Templates Templates") && errx(1, "Could not checkout Templates directory");
174 warnx("Need to specify a directory with -d argument!");
179 # check the port doesn't exist already
183 print ">> Fetching INDEX to scan for duplicates...\n";
184 my $indexfile = "$tmpdir/" . `$make -C $portsdir -V INDEXFILE`;
185 system("$make -C /usr/ports INDEXDIR=$tmpdir fetchindex") && errx(1, "Could not fetch INDEX file.");
187 foreach (split(/\,/, $dir)) {
189 foreach (split(/[-_]/)) {
190 next if length () <=2 or /^rubygem$/;
194 open(INDEXFILE, "< $indexfile") or errx(1, "$indexfile unreadable.");
195 while (my $line = <INDEXFILE>) {
196 $line =~ m,^[^|]*\|/usr/ports/[^/]*/([^|]*)\|,;
198 foreach my $dpart (@namepart) {
199 if ($line =~ /^[^ ]*\b$dpart\b/i) {
201 print "$dpart matches $line\n";
207 prompt ("Possible duplicates found -- still OK?")
208 and errx(1, "Investigating duplicates");
212 # make sure we're in the right place.
214 my @dirs = split(/\,/, $dir);
215 foreach my $i (@dirs) { $i = abs_path($i); }
216 my $portname; my $wrapat;
217 foreach my $thisdir (@dirs) {
218 # make double sure where we are..
220 # do some dir sanity checking first
221 errx(1, "Please specify valid directories to import new ports from.") if $thisdir eq "";
222 errx(1, "$thisdir is either not a directory or does not exist.") if (! -d $thisdir);
224 print "Working with port directory $thisdir.\n";
226 $portname = `basename $thisdir`; # avoid problems with dirs containing `/' in cvs
228 warnx("Port directory contains upper-case character! Please try using an all lower-case name to make everybody's life a bit easier.") if ($portname =~ /[A-Z]/);
230 if (prompt("Port directory name will be $portname in SVN Repo. OK? ")) {
232 $portname = query("Preferred name for port directory? ");
233 } while (prompt("Is the new name $portname OK? "));
237 chdir $thisdir or err(1, "$thisdir");
239 # now run the tests on this baby.
241 system("$_") && errx(1, "'$_' had problems. aborting.");
244 # Get the category name and make it suitable for use with svn
245 my @categories = split(/ /, `$make -VCATEGORIES`);
246 my $category = $categories[0];
248 if (prompt("Port $portname will be put in category $category. OK? " )) {
250 $category = query("Preferred category for $portname? ");
251 } while (prompt("Is the new category $category OK? "));
256 # Do commitfile checking but only if the user did not request automatic filling.
259 system("$mv $c $tmpdir/commitfile") && errx(1, "Oops, can't move commitfile!");
260 print "\nRemember, you asked to use a commit file to read for the commit log.\n";
261 print "This means you'll get a message saying the log message was unchanged or\n";
262 print "not specified. Just tell it to continue and it will be committed.\n\n";
263 $commitfile = "--file $tmpdir/commitfile";
266 ## Set up the autofill file.
267 # Read COMMENT for part of the commit message.
269 chomp($pkgcomment = `$make $passenv -V COMMENT`);
270 # Change the first character to lowercase to make it fit with the
271 # rest of the commit message, only if the second is not upper case.
272 $pkgcomment =~ s/(^.)(?![A-Z])/\l$1/;
274 $pkgcomment .= "\n\n" if ($autofill != -1);
276 $pkgcomment = `cat pkg-descr`;
277 $pkgcomment .= "\n" if ($autofill != -1);
279 chomp($maintaineraddr = `$make $passenv -V MAINTAINER`);
280 chomp($portversion = `$make $passenv -V PORTVERSION`);
281 # Read Makefile to find necessary variables.
282 open(MAKEFILE, "Makefile") or die("Can't open Makefile for reading: $!");
285 die ("Old style Makefile headers detected") if (/^# (?:[Nn]ew )?[Pp]orts collection [Mm]akefile/);
286 ($maintainer) = (m/^# Created by:\s+(\w.*)$/) if (/^# Created by/);
289 $maintainer = $maintaineraddr unless ($maintainer);
290 # Write out the data to the comment file.
291 open(AUTOFILL, "> $tmpdir/commitfile") or die("Can't open $tmpdir/commitfile for writing: $!");
293 # pretty print; wrap @ 72 characters
294 $tmp = "Add $portname $portversion, $pkgcomment";
298 $tmp =~ s/(.{$wrapat}([^ ]+)?) /$1\n/g;
299 last unless $tmp =~ /^[^\n]{73}/;
307 print AUTOFILL "PR: $autofill\n" if ($autofill != -1);
308 print AUTOFILL "Submitted by: $maintainer" if ($maintainer && $autofill != -1);
310 print "Okay, a commit log message was automatically generated for you.\n";
311 print "Now you will have a chance to edit it to make sure it's OK to use.\n";
312 print "Here's the contents of the file:\n--start--\n";
313 open(AUTOFILL, "$tmpdir/commitfile") or die("Can't open $tmpdir/commitfile for reading: $!");
314 print while (<AUTOFILL>);
316 $tmp = prompt("\n--end--\nDo you wish to edit the file before continuing? ");
317 system("$edit $tmpdir/commitfile") if ($tmp == 0);
318 print "\nRemember, you asked to use a commit file to read for the commit log.\n";
319 print "This means you'll get a message saying the log message was unchanged or\n";
320 print "not specified. Just tell it to continue and it will be committed.\n\n";
321 $commitfile = "--file $tmpdir/commitfile";
324 print "We're ready to commit.\n";
325 print "Source directory: $thisdir\n";
326 print "Target SVN Repo directory: ports/$category/$portname\n";
327 prompt("Adding port $portname to $category OK? ") && errx(1, "user abort requested");
329 chdir $tmpdir or err(1, "$tmpdir");
331 # let's get our hands dirty.
333 system("$svn co --depth empty $repo ports") && errx(1, "can't get ports root, aborting.");
334 chdir "ports" or err(1,"ports");
335 system("$svn up --depth files $category") && errx(1, "can't get temporary category directory, aborting.");
337 chdir $category or err(1,"$category");
339 # check for previous existence of port -- on the way use filthy
340 # home-made XML parser.
341 # Until svn revs are in the database, we'll use dates.
342 print "Checking for previous versions of $category/$portname... \n";
343 my $previous_incarnation = "bogus";
344 my $oldportlist = `/usr/bin/fetch -qo - http://people.FreeBSD.org/~crees/removed_ports/index.xml`;
345 if ($oldportlist !=~ /^fetch:[^:]+: Not Found/) {
346 foreach (split("\n", $oldportlist)) {
347 if (/^ +\<port\>$category\/$portname(?:\<removed_revision\>r([0-9]*)\<\/removed_revision\>)?\<removed_date\>([^<]*)/) {
348 print "Found one!\n";
350 $previous_incarnation = $2;
351 $previous_incarnation =~ s,/,-,g;
352 print "This port was last alive on $previous_incarnation.\n";
353 $previous_incarnation = "\{$previous_incarnation\}";
355 $previous_incarnation = $1 - 1;
356 print "The last living revision of this port was r$previous_incarnation.\n";
362 print "Could not fetch list-- perhaps the site is down.";
364 if ($previous_incarnation ne "bogus") {
365 print "Fetching older version... ";
366 system("$svn cp -q '$repo/$category/$portname\@$previous_incarnation' .");
368 print "Removing irrelevant files and directories... ";
369 my @oldfiles = split("\0", `cd $portname && find . -type f -print0`);
370 my @olddirs = split("\0", `cd $portname && find . -type d -print0 | sort -r`);
371 my @newfiles = split("\0", `cd $thisdir && find . -type f -print0`);
372 my @newdirs = split("\0", `cd $thisdir && find . -type d -print0| sort -r`);
374 foreach my $file (@oldfiles) {
375 system("cd $portname && $svn rm $file")
376 if !($file ~~ @newfiles)
379 foreach my $directory (@olddirs) {
380 system("cd $portname && $svn rm $directory")
381 if !($directory ~~ @newdirs);
386 # Remove cvs2svn props if present
387 print "Removing cvs2svn props...\n";
388 system("$svn propdel -qR cvs2svn:cvs-rev $portname");
391 print "Removing port's entry from $moved...\n";
392 system("cd .. && $svn up -q $moved && sed -i '' -e '\\,^$category/$portname\|\|,d' $moved");
394 # Add note to log about readdition
395 system("echo '(Readdition of $category/$portname which was removed on $previous_incarnation)\n' > $tmpdir/commitfile.tmp && cat $tmpdir/commitfile >> $tmpdir/commitfile.tmp && mv $tmpdir/commitfile.tmp $tmpdir/commitfile") unless ($commitfile eq "");
398 print "[none found]\n";
400 system("$cp -PRp $thisdir .");
401 system("$svn add --force --depth empty `find $portname -type d | grep -v '^$portname/work'`") && errx(1, "svn add for dirs failed, aborting.");
403 system("$svn add --force `find $portname -type f | grep -v '^$portname/work'`") && errx(1, "svn add for files failed, aborting.");
405 # Find keywords in old files and strip
406 print "Stripping any keywords...\n";
407 system("sed -i '' -e 's,\\\$Free" . "BSD:[^\$]*\\\$,\$Free" . "BSD\$,' \$(find $portname -type f)");
409 # find files with keywords in and propset
411 print "Setting correct properties on files...\n";
413 my @portfiles = split("\0", `find $portname -type f -print0`);
414 my $portfiles = join(" ", @portfiles);
415 my @keywordfiles = split("\n", `grep -l $keyword $portfiles`);
416 foreach (@portfiles) {
417 if ($_ ~~ @keywordfiles) {
418 system("$svn -q propset svn:keywords FreeBSD=%H $_");
419 system("$svn -q propdel fbsd:nokeywords $_");
421 system("$svn -q propset fbsd:nokeywords on $_");
422 system("$svn -q propdel svn:keywords $_");
426 # strip svn:executable if added-- not allowed
428 system("cd $portname && $svn -qR propdel svn:executable");
430 # figure out where the port name belongs in category Makefile
431 my ($spaces, @ports) = &lsports;
432 errx(1, "Error: $portname already exists in $category\'s Makefile") if (&contains($portname, @ports));
434 foreach my $tmp (sort(@ports)) {
435 if ($tmp gt $portname) {
441 # now let's insert it
443 if (scalar @ports == 0) {
444 # there were no previous SUBDIR += lines, so we're going to
445 # put ourselves after the last comment (we can't be after a
446 # .include <bsd.port.subdir.mk> for example).
447 my $lastcommentnum = &lastcomment;
448 $cmd = "$lastcommentnum\n+\ni\n";
451 # there were previous SUBDIR lines, but none was greater than we are,
452 # means, we're the last port, so, add ourselves after the last port
453 $port = $ports[$#ports];
454 $cmd = "/^" . $spaces . "SUBDIR += $port/\na\n";
456 # OK, append ourselves in the right place, so things *stay* sorted.
457 $cmd = "/^" . $spaces . "SUBDIR += $port/\ni\n";
460 print "Inserting new port into $category/Makefile...\n";
461 open(ED, "|ed Makefile") || die "Cannot start ed to actually insert module\n";
462 print ED "$cmd" . $spaces . "SUBDIR += $portname\n.\nw\nq\n";
465 # commit the actual port.
466 chdir "$tmpdir/ports" or err(1, "$tmpdir/ports");
468 print "Faking commit....\n";
470 system("$svn ci $commitfile $moved $category/Makefile $category/$portname") && errx(1, "svn commit failed, aborting.");
475 You're done! The new port $portname has been completely imported in
476 the tree. Don't forget to add the creator's name and email address to
477 the Contributors' List if they are not already there. To do this, edit
478 doc/head/en_US.ISO8859-1/articles/contributors/contrib.additional.xml.
483 print STDERR $0 . ": " . $msg . "\n";
489 warnx("WARNING: err called incorrectly") if (($ex !~ m/^\d+/) || ($msg eq ""));
490 print STDERR $0 . ": " . $msg . ": $!\n";
497 warnx("WARNING: errx called incorrectly") if (($ex !~ m/^\d+/) || ($msg eq ""));
498 print STDERR $0 . ": " . $msg . "\n";
504 my $reply = query($msg);
505 return 0 if ($reply =~ m/^[Yy]/);
506 return 1 if ($reply =~ m/^[Nn]/);
519 #addport,v \$Revision: 1.21 $
521 authors: <will\@FreeBSD.org>, <mharo\@FreeBSD.org>
524 $0 [-c commitfile] [-h host] [-l PR number | -L PR number]
525 [-s distdir] [-u user] [-afimnty] -d directory
527 Where "directory" contains the comma-delimited list
528 of root directories of new ports that you wish to
529 add to the Ports Collection. The name of this directory
530 *WILL* matter in regards to the repository!
533 -a Perform checks on the port to make sure
534 there are no problems. Recommended.
535 -c file Use file in place of normal log message.
536 -f Do not fetch the distfile.
537 -h host Use a svnhost besides repo.FreeBSD.org.
538 -i Interactive mode; allow more control over
539 where things are placed.
540 -l PR# Attempts to autogenerate a commit message by
541 reading the Makefile file.
542 The PR number must be passed to -l. If there is
543 no PR (i.e., self-created or submitted in
544 private email), use PR# -1.
545 -L PR# Like -l but it'll generate commit message based
547 -m Do not checkout ports/Mk (needed for support
549 -n Do not actually commit anything.
550 -s distdir Use a different directory besides the default,
551 for downloading distfiles.
552 -t Do more port testing. Requires -a.
553 -u user Use a different username (default: $u).
554 -y Check for similarly named ports.
556 ENVIRONMENT VARIABLES
557 $0 supports the following environment variables:
559 ADDPSVNROOT - Location of SVN repository.
560 USER - Username of user invoking $0.
563 % addport -n -d greatgame,helpfuldev,shoot
564 Will show what happens but not actually commit ports
565 named "greatgame", "helpfuldev", and "shoot".
568 Displays this message. :-)
574 # look if the first parameter is contained in the list following it
575 my ($item, @list) = @_;
577 foreach my $i (@list) {
578 return 1 if $i eq $item;
587 open(F, "Makefile") || die "can't open Makefile: $!";
591 next if $_ !~ m/SUBDIR/;
593 if ( !defined($spaces) ) {
594 m/^([\s\t]+)[^\s\t]/;
598 s/^[ \t]+SUBDIR[ \t]+\+?=[\ \t]+//;
603 return ($spaces, @rv);
606 # this finds the last comment in the Makefile