autoupdate.pl 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925
  1. #! /usr/bin/perl -w
  2. # $Id: autoupdate.pl,v 1.36 2014/09/07 07:35:06 markus Exp $
  3. # Copyright (c) 2007,2008,2009
  4. # Markus Hennecke <markus-hennecke@markus-hennecke.de>
  5. #
  6. # Permission to use, copy, modify, and/or distribute this software for any
  7. # purpose with or without fee is hereby granted, provided that the above
  8. # copyright notice and this permission notice appear in all copies.
  9. #
  10. # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  11. # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  12. # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  13. # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  14. # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  15. # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  16. # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  17. use strict;
  18. use warnings;
  19. use Cwd;
  20. use File::Temp qw/tempfile tempdir/;
  21. use List::Util qw/reduce/;
  22. use OpenBSD::PackageName;
  23. use Getopt::Long;
  24. use FindBin;
  25. use File::Spec;
  26. use POSIX qw/uname :sys_wait_h/;
  27. # Silence the warning that is issued because List::Util won't register
  28. # $a and $b in a correct way for us
  29. $a = $a;
  30. $b = $b;
  31. # Defaults to 1, can be set via the --verbose switch
  32. my $verbose = 1;
  33. # If set logs of the build are written under this directory
  34. my $logdir = undef;
  35. # If set it will show the usage information and exit afterwards
  36. my $show_help = 0;
  37. # If set to more than one it will try to build ports in parallel
  38. my $num_jobs = 1;
  39. # If set to 1 update will take those ports into account flagged for always
  40. # update
  41. my $always_update = 0;
  42. # If set to 1 the make clean part of the ports build will use sudo
  43. my $sudo_make_clean = 0;
  44. my $sudo = $ENV{SUDO};
  45. $sudo = `make -f /etc/mk.conf -V SUDO` if (not defined $sudo);
  46. $sudo = '' if (not defined $sudo);
  47. chomp $sudo;
  48. # If defined via command line the script will use that file as input for
  49. # out of date ports. If the filename is '-' it will use stdin.
  50. my $out_of_date = undef;
  51. # The default location of the ports tree and the path where we should look
  52. # for ports. TODO: Read these variables from /etc/mk.conf and fall back
  53. # to the default only if we do not find it either there or in the environment.
  54. my $portsdir = $ENV{PORTSDIR} || '/usr/ports';
  55. my $portsdir_path = $ENV{PORTSDIR_PATH} || "$portsdir:$portsdir/mystuff";
  56. # Number of jobs currently active
  57. my $jobs = 0;
  58. # Number of concurrent jobs in a build
  59. my $make_jobs = 1;
  60. # This hash will hold the PIDs of the forked build processes
  61. my %forked_builds = ();
  62. # Regex used to match a flavor
  63. my $regex_flavor = '((,[a-z][a-z0-9_]*)*)';
  64. # Regex used to match a subpackage
  65. my $regex_subpkg = '(,-[a-z][a-z0-9_+-]*)?';
  66. # List of pseudo flavors we apply if the port supports them
  67. my %pseudo_flavors = ();
  68. # Global abort flag, only set from the reaper func
  69. my $abort = 0;
  70. # Ports that failed to build
  71. my @aborted = ();
  72. # Hash of reaped child processes
  73. my %reaped_pids = ();
  74. # This function will remove the finished forked build from the list of
  75. # currently build ports.
  76. sub REAPER {
  77. while ((my $wpid = waitpid(-1, &WNOHANG)) > 0) {
  78. my $status = $?;
  79. $abort = 1 if ($status != 0);
  80. $reaped_pids{$wpid} = $status;
  81. }
  82. $SIG{CHLD} = \&REAPER;
  83. }
  84. # Remove the status codes/PIDs from the reaped_pids hash periodically
  85. sub reap {
  86. my @pids = keys %reaped_pids;
  87. while (scalar @pids) {
  88. my $wpid = shift @pids;
  89. if (exists $forked_builds{$wpid}) {
  90. my $portname = $forked_builds{$wpid};
  91. $jobs--;
  92. my $status = $reaped_pids{$wpid};
  93. if ($status == 0) {
  94. print STDOUT 'Finished build of ';
  95. }
  96. else {
  97. print STDOUT 'Build aborted of ';
  98. push @aborted, ($portname);
  99. }
  100. delete $forked_builds{$wpid};
  101. print STDOUT $portname . "\n";
  102. }
  103. else {
  104. print STDOUT 'No forked process recorded for '
  105. . $wpid . "\n";
  106. }
  107. delete $reaped_pids{$wpid};
  108. }
  109. }
  110. # Reads a config file and parses the options set in the config file.
  111. # A list of valid options must be passed to the function.
  112. sub read_rc_file {
  113. my $valid_vars = shift;
  114. my ($fh, @lines, %config);
  115. open($fh, '<', "$ENV{HOME}/.autoupdaterc")
  116. or return \%config;
  117. while (<$fh>) {
  118. chomp;
  119. s/\t/ /g;
  120. push @lines, ($_);
  121. }
  122. close($fh);
  123. my $temp_line = '';
  124. my $append = 0;
  125. my $real_lineno = 0;
  126. my $lineno = 0;
  127. foreach (@lines) {
  128. $lineno++;
  129. # Check if we have a multiline statement
  130. if (m/\\$/) {
  131. $append++;
  132. s/\\$//g;
  133. $temp_line .= $_;
  134. next;
  135. }
  136. elsif ($append != 0) {
  137. $_ = $temp_line . $_;
  138. $real_lineno = $lineno - $append;
  139. $append = 0;
  140. $temp_line = '';
  141. }
  142. # Remove comments and unnecessary whitespace
  143. s/\#.*$//g;
  144. s/^[[:space:]]//g;
  145. s/ *= */=/g;
  146. s/ +/ /g;
  147. next if (m/^$/);
  148. # Check if the line has a = character, if not issue an error
  149. if (! m/=/) {
  150. print STDERR 'Not a valid config in line '
  151. . $real_lineno . "\n";
  152. return undef;
  153. }
  154. # Split the line at the first '='
  155. my $split_index = index($_, '=');
  156. my $var = substr($_, 0, $split_index);
  157. my $val = substr($_, $split_index + 1);
  158. # Check if the varible is valid
  159. if (defined $valid_vars && defined $valid_vars->{$var}) {
  160. $config{$var} = $val;
  161. }
  162. else {
  163. print STDERR 'Warning: Unknown variable "' . $var
  164. . '" in config file' . "\n";
  165. }
  166. }
  167. return \%config;
  168. }
  169. # Fill a lookup hash from config
  170. sub setup_lookup_hash {
  171. my $config_line = shift;
  172. my $hash = shift;
  173. my $info = shift;
  174. if (defined $config_line) {
  175. $config_line =~ tr/,/ /;
  176. foreach (split / /, $config_line) {
  177. print STDOUT "Adding $_ to $info\n"
  178. unless ($verbose < 2);
  179. $hash->{$_} = 1;
  180. }
  181. }
  182. }
  183. # Sets up the log directory
  184. sub setup_logging {
  185. my $config = shift;
  186. if (defined $config->{logging} && $config->{logging} == 1) {
  187. my $tmpdir = $ENV{TMPDIR} ? $ENV{TMPDIR} : '/tmp';
  188. $logdir = tempdir("$tmpdir/autoupdate.XXXXXXXXXX");
  189. my $logfile = "$logdir/autoupdate.pl.log";
  190. open TEE, "| tee $logfile"
  191. or die "Unable to open log file \"$logfile\"\n";
  192. open STDOUT, ">&TEE"
  193. or die "Unable to redirect STDOUT to log file.\n";
  194. open STDERR, ">&TEE"
  195. or die "Unable to redirect STDERR to log file.\n";
  196. print STDOUT 'Logging builds in "' . $logdir . '"' . "\n";
  197. print STDOUT 'Using "' . $logfile . '" as mainlog' . "\n";
  198. }
  199. }
  200. # Returns the ports version or undef if no such version could be deduced
  201. sub get_ports_version {
  202. my $port = shift;
  203. chdir "$port" || return undef;
  204. my $show = 'show';
  205. my @uname = POSIX::uname();
  206. if ($uname[2] > 6.4) {
  207. $show .= '-indexed';
  208. }
  209. my $cmd = "make $show=FULLPKGNAME";
  210. my $pkgname = '';
  211. open(my $in, "$cmd 2>&1 |")
  212. or die "Unable to get version for \"$port\"\n";
  213. while (<$in>) {
  214. chomp;
  215. $pkgname .= $_;
  216. }
  217. close($in);
  218. my ($stem, $version, @flavors)
  219. = OpenBSD::PackageName::splitname($pkgname);
  220. return $version;
  221. }
  222. # Returns an integer according to the version numbers supplied.
  223. # -1 if the first argument is the higher version number
  224. # 0 if both version numbers are equal
  225. # +1 if the second argument is the higher version number
  226. sub get_higher_version {
  227. my ($ver1, $ver2) = @_;
  228. if (! defined($ver1)) {
  229. return 0 if (! defined($ver2));
  230. return 1;
  231. }
  232. elsif (! defined($ver2)) {
  233. return -1;
  234. }
  235. my @vers1 = split /[\.pv]/,$ver1;
  236. my @vers2 = split /[\.pv]/,$ver2;
  237. my $max_len = scalar @vers1;
  238. $max_len = scalar @vers2 if (scalar @vers2 > scalar @vers1);
  239. my $index;
  240. for ($index = 0; $index < $max_len; $index++) {
  241. return -1 if ($index >= scalar @vers2);
  242. return 1 if ($index >= scalar @vers1);
  243. my $vers1 = $vers1[$index];
  244. my $vers2 = $vers2[$index];
  245. my $cmp;
  246. if (($vers1 =~ /[a-z]/) || ($vers2 =~ /[a-z]/)) {
  247. $cmp = ($vers2 cmp $vers1);
  248. }
  249. else {
  250. $cmp = ($vers2 <=> $vers1);
  251. }
  252. return $cmp if ($cmp);
  253. }
  254. return 0;
  255. }
  256. # Creates an array with the name of all packages that need updates
  257. sub read_update_package_list {
  258. my $input = shift;
  259. my ($in, @package_list, $out);
  260. # Save the output from pkg_outdated in a log file
  261. if (defined $logdir && not defined $input) {
  262. open($out, '>', "$logdir/pkg_outdated");
  263. print STDERR "Warning: Unable to open log for pkg_outdated\n"
  264. if (not defined $out);
  265. }
  266. if (not defined $input) {
  267. my $ood_path = 'infrastructure/bin';
  268. my $cmd = "env PORTSDIR=\"$portsdir\" "
  269. . "\"$portsdir/$ood_path/pkg_outdated\" ";
  270. unless (open($in, $cmd . '2>/dev/null |')) {
  271. print STDERR "Unable to execute $cmd\n";
  272. exit 1;
  273. }
  274. }
  275. elsif ($input eq '-') {
  276. unless (open($in, '<&=STDIN')) {
  277. print STDERR "Unable to open stdin\n";
  278. exit 1;
  279. }
  280. }
  281. else {
  282. unless (open($in, '<', $input)) {
  283. print STDERR "Unable to open file: " . $input
  284. . " for reading\n";
  285. exit 1;
  286. }
  287. }
  288. while (<$in>) {
  289. print $out $_ if (defined $out);
  290. chomp;
  291. my $pkg = $_;
  292. $pkg =~ s/ +#.*$//g;
  293. if ($_ =~ m/\#\s+->/) {
  294. # Ignore ports that show up every time in pkg_outdated
  295. print STDERR 'Ignoring ' . $pkg . "\n";
  296. next;
  297. }
  298. if ($_ =~ m/\#\s+always-update\s+->\s+[0-9],/x &&
  299. !$always_update) {
  300. print STDERR "Ignoring $pkg (always update)\n";
  301. next;
  302. }
  303. push @package_list, ($pkg);
  304. }
  305. close($in);
  306. close($out) if (defined $out);
  307. return \@package_list;
  308. }
  309. # Creates an array with a hash for each port that needs an update
  310. sub create_package_information {
  311. my $pkg_list = shift;
  312. # Create an array of hashes that has the following data:
  313. # category => name of the ports category
  314. # port => name of the port (the directory in the ports tree)
  315. # subdir => version of the port (for ports with more than one
  316. # version, e.q. python)
  317. # flavor => list of flavors as an array reference
  318. # subpackage => name of the subpackage
  319. my @pkg_information = ();
  320. foreach my $pkg (@$pkg_list) {
  321. # Get all the variables from the port identification string
  322. my ($category, $port, $subdir, $flavor, $subpackage);
  323. # Each group in the regex has the following meaning:
  324. # $1 : category main which may consist of the alphabet
  325. # $2 : port name, which must start with at least one alpha
  326. # char and may have a number appended.
  327. # Some examples may be gtk+2, libIDL etc.
  328. # $3 : optional subdirs for the port, $4 will hold the last
  329. # $5 : an optional subpackage name, this will start with a
  330. # comma followed by an hyphen and have an alphanumeric
  331. # identifier
  332. # $6 : a list with optional flavors. Each flavor starts with
  333. # a comma and may be an alphanumeric identifier
  334. if ($pkg =~ m/^
  335. ([a-z][a-z0-9]*)\/
  336. ([A-Za-z][\.A-Za-z0-9-+_]*)
  337. ((\/[a-z0-9._-]*)*)?
  338. $regex_subpkg
  339. $regex_flavor/x) {
  340. $category = $1;
  341. $port = $2;
  342. $subdir = $3;
  343. $subpackage = $5;
  344. $flavor = $6;
  345. $subdir = '' if (not defined $subdir);
  346. $subdir =~ s/(^\/|\/$)//g;
  347. $subpackage = '' if (not defined $subpackage);
  348. $subpackage =~ s/^,//g;
  349. $flavor = '' if (not defined $flavor);
  350. $flavor =~ s/,/ /g;
  351. $flavor =~ s/^ //g;
  352. my %p = (
  353. category => $category,
  354. port => $port,
  355. subdir => $subdir,
  356. subpkg => $subpackage,
  357. flavor => $flavor,
  358. pkg => $pkg,
  359. );
  360. $p{dependencies} = create_dependencies_list(\%p);
  361. $p{jobs} = set_parallel_build(\%p);
  362. $p{deppkgs}
  363. = create_dependencies_hash($p{dependencies});
  364. $p{pseudo_flavors} = create_pseudo_flavors_list(\%p);
  365. add_pseudo_flavors(\%p);
  366. push @pkg_information, (\%p);
  367. }
  368. else {
  369. next if ((not defined $pkg) || ($pkg eq ''));
  370. print STDERR "Unknown port name: $pkg\n";
  371. exit 2;
  372. }
  373. print STDOUT "Category:\t$category\n"
  374. . "Port:\t\t$port\n"
  375. . "Subdir:\t\t$subdir\n"
  376. . "Subpackage:\t$subpackage\n"
  377. . "Flavor:\t\t$flavor\n\n" unless ($verbose < 5);
  378. }
  379. return \@pkg_information;
  380. }
  381. # Adds pseudo flavors to the flavors list if they appear both in the
  382. # pseudo_flavors array from the ports info hash and the %pseudo_flavors
  383. # global hash set via command line.
  384. sub add_pseudo_flavors {
  385. my $info = shift;
  386. my $flavors = $info->{flavor};
  387. foreach my $pseudo_flavor (@{$info->{pseudo_flavors}}) {
  388. if (exists $pseudo_flavors{$pseudo_flavor}) {
  389. $flavors .= ' ';
  390. $flavors .= $pseudo_flavor;
  391. }
  392. }
  393. $info->{flavor} = $flavors;
  394. }
  395. # Creates an array with pseudo flavors. We can check this array against the
  396. # %pseudo_flavors hash to add flavors on demand.
  397. sub create_pseudo_flavors_list {
  398. my $info = shift;
  399. my $cur_dir = getcwd;
  400. my $cmd = 'make show=PSEUDO_FLAVORS';
  401. # chdir into the ports directory
  402. my ($port, $port_dir) = find_newer_ports_dir($info);
  403. chdir $port_dir or die "Unable to change to \"$port_dir\"";
  404. open (my $in, "$cmd |")
  405. or die "Unable to get pseudo flavors for \"$port\"";
  406. my $output;
  407. while (<$in>) {
  408. chomp;
  409. $output .= $_ . ' ';
  410. }
  411. chop $output;
  412. close($in);
  413. if ($? != 0) {
  414. die "Unable to get pseudo flavors for \"$port\"";
  415. }
  416. my @pseudo_flavors = split / /,$output;
  417. return \@pseudo_flavors;
  418. }
  419. # Returns the reference of the package information belonging to a package
  420. # name from the package info array. The sub will die if there is no such
  421. # package listed.
  422. # A package listed more than one time, e.g. several subpackages, will
  423. # always return the first subpackage. It is taken care of the update of the
  424. # other subpackages via the ports Makefile, so we should only need this first
  425. # one.
  426. sub get_pkg_info {
  427. my ($info_array, $pkg_name) = @_;
  428. foreach my $info (@$info_array) {
  429. my $info_pkg_name = $info->{pkg};
  430. $info_pkg_name =~ s/$regex_subpkg$regex_flavor//g;
  431. return $info if ($info_pkg_name eq $pkg_name);
  432. }
  433. die "ERROR: Internal error, package information not consistent.\n";
  434. }
  435. # Tries to find the newest port from directories listed in $PORTSDIR_PATH
  436. # by comparing the output from make show=FULLPKGNAME.
  437. sub find_newer_ports_dir {
  438. my $info = shift;
  439. my $port = $info->{category} . '/' . $info->{port};
  440. $port .= '/' . $info->{subdir} if ($info->{subdir} ne '');
  441. return ($port, $info->{portdir})
  442. if ((defined $info->{portdir}) && ($info->{portdir} ne ''));
  443. my @port_locations = split /:/,$portsdir_path;
  444. # Take a shortcut here if we got only one location
  445. if (scalar @port_locations == 1) {
  446. $info->{portdir} = "$portsdir_path/$port";
  447. return ($port, $info->{portdir});
  448. }
  449. my @versions = ();
  450. foreach my $dir (@port_locations) {
  451. my $port_dir = "$dir/$port";
  452. my $version = get_ports_version($port_dir);
  453. push @versions, $version if $version;
  454. }
  455. my $highest
  456. = reduce { get_higher_version($a, $b) <= 0 ? $a : $b } @versions;
  457. my $idx = 1;
  458. $idx = grep
  459. { defined($versions[$_])
  460. && ($versions[$_] eq $highest) ? $_ : -1 } 0..$#versions
  461. if (scalar @versions > 1);
  462. $info->{portdir} = "$port_locations[$idx - 1]/$port";
  463. return ($port, $info->{portdir});
  464. }
  465. # (Re)builds a package and updates it.
  466. # The following targets for make are given: repackage, update, clean
  467. # All output from the build process is logged and printed if the package did
  468. # not build.
  469. sub build_pkg {
  470. my $info = shift;
  471. my $cur_dir = getcwd();
  472. my ($port, $port_dir) = find_newer_ports_dir($info);
  473. my $pid = fork();
  474. if (! defined $pid) {
  475. die "Cannot fork to build $port\n";
  476. }
  477. elsif ($pid) {
  478. $forked_builds{$pid} = $port;
  479. $jobs++;
  480. return;
  481. }
  482. # The child must use the default sig handler for SIGCHLD
  483. $SIG{CHLD} = 'DEFAULT';
  484. # Give the parent time to update the forked_builds hash
  485. sleep 1;
  486. # Create the command that will build the package
  487. my $cmd = 'env ';
  488. $cmd .= 'MAKE_JOBS=' . $info->{jobs} . ' ';
  489. $cmd .= "FLAVOR=\"$info->{flavor}\" " if ($info->{flavor} ne '');
  490. $cmd .= "SUBPACKAGE=$info->{subpkg} " if ($info->{subpkg} ne '');
  491. $cmd .= 'make build';
  492. # chdir into the ports directory
  493. chdir $port_dir or die "Unable to change to \"$port_dir\"\n";
  494. print STDOUT "Building $info->{pkg}\n";
  495. my (@log, $logfile, $logfilename);
  496. open(my $in, "$cmd 2>&1 |")
  497. or die "Unable to make \"$port\"\n";
  498. if (defined $logdir) {
  499. my $tmp = $info->{port};
  500. $tmp .= "_$info->{subdir}" if ($info->{subdir} ne '');
  501. $tmp =~ s/\//_/g;
  502. $logfilename = "$logdir/$tmp.log";
  503. open($logfile, '>>', $logfilename)
  504. or print STDERR 'Warning: Unable to create log file'
  505. . " for $info->{pkg}\n";
  506. }
  507. while (<$in>) {
  508. exit 1 if (/^Detected loop/);
  509. print STDOUT $_ unless (($verbose < 2) || ($num_jobs > 1));
  510. print $logfile $_ if (defined $logfile);
  511. push @log, ($_);
  512. }
  513. close($in);
  514. my $build_result = $?;
  515. my $update_result = 1;
  516. if ($build_result == 0) {
  517. $cmd = '';
  518. $cmd .= $sudo if ($sudo_make_clean);
  519. $cmd .= ' env FORCE_UPDATE=Yes CLEANDEPENDS=No ';
  520. $cmd .= "FLAVOR=\"$info->{flavor}\" "
  521. if ($info->{flavor} ne '');
  522. $cmd .= "SUBPACKAGE=$info->{subpkg} "
  523. if ($info->{subpkg} ne '');
  524. $cmd .= ' make repackage update clean';
  525. open($in, "$cmd 2>&1 |")
  526. or die "Unable to update \"$port\"\n";
  527. while (<$in>) {
  528. print STDOUT $_
  529. unless (($verbose < 2) || ($num_jobs > 1));
  530. print $logfile $_ if (defined $logfile);
  531. push @log, ($_);
  532. }
  533. close($in);
  534. $update_result = $?;
  535. }
  536. close($logfile) if (defined $logfile);
  537. if ($build_result != 0 || $update_result != 0) {
  538. # Something went wrong. We list the output of the make process
  539. # and kill the update process
  540. if (!defined $logdir) {
  541. foreach my $line (@log) {
  542. print STDOUT $line;
  543. }
  544. }
  545. else {
  546. print STDERR "Failed: See log in '$logfilename'\n";
  547. }
  548. print STDERR "Aborting build process...\n";
  549. exit 3;
  550. }
  551. chdir $cur_dir;
  552. exit 0;
  553. }
  554. sub set_parallel_build {
  555. my $info = shift;
  556. my $cur_dir = getcwd;
  557. my $cmd = 'make show=PARALLEL_BUILD';
  558. my ($port, $port_dir) = find_newer_ports_dir($info);
  559. chdir $port_dir or die "Unable to change to '$port_dir'\n";
  560. my $parallel = 1;
  561. open(my $in, "$cmd |")
  562. or die "Unable to determine parallel build info for \"$port\"\n";
  563. while (<$in>) {
  564. chomp;
  565. $parallel &= ($_ eq 'Yes');
  566. }
  567. close($in);
  568. chdir $cur_dir;
  569. $info->{jobs} = ($parallel) ? $make_jobs : 1;
  570. return $info->{jobs};
  571. }
  572. # Creates the dependencies list for a given port from the port information
  573. # hash
  574. sub create_dependencies_list {
  575. my $info = shift;
  576. my $cur_dir = getcwd;
  577. my @dep_list = ();
  578. # Create the command that will give us the dependencies list
  579. my $cmd = 'env ';
  580. $cmd .= "FLAVOR=\"$info->{flavor}\" " if ($info->{flavor} ne '');
  581. $cmd .= "SUBPACKAGE=$info->{subpkg} " if ($info->{subpkg} ne '');
  582. $cmd .= 'make build-dir-depends';
  583. # chdir into the ports directory
  584. my ($port, $port_dir) = find_newer_ports_dir($info);
  585. chdir $port_dir or die "Unable to change to \"$port_dir\"\n";
  586. open(my $in, "$cmd |")
  587. or die "Unable to get dependencies for \"$port\"\n";
  588. while (<$in>) {
  589. chomp;
  590. push @dep_list, ($_);
  591. }
  592. close($in);
  593. if ($? != 0) {
  594. die "Unable to gather information for $port";
  595. }
  596. # Add the port itself to the list
  597. $port = "$info->{category}/$info->{port}";
  598. $port .= "/$info->{subdir}" if ($info->{subdir} ne '');
  599. $port .= ",$info->{subpkg}" if ($info->{subpkg} ne '');
  600. # Add the flavors in raw form
  601. if ($info->{flavor} ne '') {
  602. $port .= ',';
  603. my $flavs = $info->{flavor};
  604. $flavs =~ s/ /,/g;
  605. $port .= $flavs;
  606. }
  607. #push @dep_list, ("$port $port");
  608. chdir $cur_dir;
  609. return \@dep_list;
  610. }
  611. # Creates a single hash with all dependant packages from the array created
  612. # via create_dependencies_list
  613. sub create_dependencies_hash {
  614. my $dep_list = shift;
  615. my %dep_hash;
  616. foreach my $pkg (@$dep_list) {
  617. my $key = $pkg;
  618. $key =~ s/^.* //g;
  619. $key =~ s/$regex_subpkg$regex_flavor//g;
  620. $dep_hash{$key} = 1;
  621. }
  622. return \%dep_hash;
  623. }
  624. # Returns true if a package with a given name is in the package list array
  625. # The function will ignore flavors and subpackages.
  626. sub is_pkg_in_list {
  627. my ($pkg_name, $pkgs) = @_;
  628. foreach my $p (@$pkgs) {
  629. $p =~ s/$regex_subpkg$regex_flavor//g;
  630. return 1 if ($p eq $pkg_name);
  631. }
  632. return 0;
  633. }
  634. # Returns 1 if one of the dependencies from the arguments pkg info is currently
  635. # build by a child
  636. sub can_pkg_be_build {
  637. my $pkg = shift;
  638. my $pkgs = $pkg->{deppkgs};
  639. my @pkg_list = keys %$pkgs;
  640. foreach my $pid (keys %forked_builds) {
  641. my $build_pkg = $forked_builds{$pid};
  642. return 1 if (is_pkg_in_list($build_pkg, \@pkg_list));
  643. }
  644. return 0;
  645. }
  646. # Prints out the usage information on STDERR
  647. sub usage {
  648. print STDERR "USAGE: autoupdate.pl [options]\n";
  649. print STDERR "OPTIONS:\n"
  650. . " -v|--verbose\t\tSet verbosity level (1..5)\n"
  651. . " -f|--outdated\t\tUse file as output from the "
  652. . "pkg_outdated script\n"
  653. . " -j|--jobs\t\tTry to build packages in parallel\n"
  654. . " \t\t\tIf the filename is '-' the input will be\n"
  655. . " \t\t\tread from stdin\n"
  656. . " -a|--alwaysupdate\tBuild all packages, even those\n"
  657. . "\t\t\tflagged as always update\n"
  658. . " -h|--help\t\tShow this help\n";
  659. exit ($show_help ? 0 : 1);
  660. }
  661. ##############################################################################
  662. $SIG{CHLD} = 'DEFAULT';
  663. # Read the command line params
  664. my $result = GetOptions("v|verbose=i" => \$verbose,
  665. "f|outdated=s" => \$out_of_date,
  666. "j|jobs=i" => \$num_jobs,
  667. "a|alwaysupdate" => \$always_update,
  668. "h|help" => \$show_help);
  669. usage() if ($show_help != 0 || ! $result || $num_jobs < 1);
  670. $out_of_date = File::Spec->rel2abs($out_of_date) if (defined($out_of_date));
  671. my $basedir = $FindBin::Bin;
  672. chdir($basedir);
  673. # Fill the hash with the allowed variables
  674. my %valid_vars = ( 'logging', 1,
  675. 'make_jobs', 1,
  676. 'sudo_make_clean', 1,
  677. 'pseudo_flavors', 1 );
  678. # Read the config and set everything up
  679. my $config = read_rc_file(\%valid_vars);
  680. setup_logging($config);
  681. $make_jobs = $config->{make_jobs} if $config->{make_jobs};
  682. setup_lookup_hash($config->{pseudo_flavors}, \%pseudo_flavors,
  683. 'pseudo flavors');
  684. $sudo_make_clean = $config->{sudo_make_clean}
  685. if (defined $config->{sudo_make_clean});
  686. print STDOUT "Reading package list scheduled for update...\n";
  687. my $package_list = read_update_package_list($out_of_date);
  688. print STDOUT "Got " . @$package_list . " packages for update\n";
  689. print STDOUT "Gathering package information...\n";
  690. my $port_info = create_package_information($package_list);
  691. print STDOUT "Merging dependencies...\n";
  692. my @dep_list = ();
  693. my @package_dep_list = ();
  694. # Get the dep list of each port
  695. foreach my $info (@$port_info) {
  696. my $deps = $info->{dependencies};
  697. push @dep_list, @$deps
  698. }
  699. print STDOUT "Create dependencies...\n";
  700. my ($fh, $file, $pipe);
  701. ($fh, $file) = tempfile(UNLINK => 1);
  702. open($pipe, "| tsort -r >$file") or die "ERROR: Unable to spawn tsort\n";
  703. foreach my $dep_entry (@dep_list) {
  704. # Zap the flavors and the subpackages for our dep list
  705. $dep_entry =~ s/$regex_subpkg$regex_flavor//g;
  706. print "$dep_entry\n" unless ($verbose < 5);
  707. print $pipe "$dep_entry\n";
  708. }
  709. close $pipe;
  710. die "ERROR: Internal error" if ($? != 0);
  711. # Read the result
  712. while (<$fh>) {
  713. my $pkg = $_;
  714. chomp $pkg;
  715. push @package_dep_list, ($pkg);
  716. }
  717. close $fh;
  718. print STDOUT "Got " . @package_dep_list . " packages as dependencies\n"
  719. unless ($verbose < 1);
  720. print STDOUT "Removing unneeded packages...\n";
  721. # Remove all packages from @package_dep_list that are not in the @package_list
  722. # array, so that only those packages may be build that are in need.
  723. my @pkg_list = ();
  724. foreach my $pkg_name (@package_dep_list) {
  725. if (is_pkg_in_list($pkg_name, $package_list) == 1) {
  726. push @pkg_list, ($pkg_name);
  727. }
  728. }
  729. if ($verbose > 1) {
  730. print STDOUT "Got " . @pkg_list . " packages for building:\n";
  731. foreach my $pkg_name (@pkg_list) {
  732. print STDOUT $pkg_name . "\n";
  733. }
  734. }
  735. $SIG{CHLD} = \&REAPER;
  736. # Build all the packages. Packages that are still unknown to us are ignored.
  737. foreach my $pkg_name (@pkg_list) {
  738. reap();
  739. # Get the package information from the info array
  740. my $info = get_pkg_info($port_info, $pkg_name);
  741. if (not defined $info) {
  742. print STDERR "Unable to retrieve package information for "
  743. . $pkg_name . "\n";
  744. next;
  745. }
  746. my $wait_for_dep_notice = 0;
  747. # XXX Here we must check if the package depends on a currently build
  748. # package too.
  749. while (($jobs >= $num_jobs || can_pkg_be_build($info)) && !$abort) {
  750. if ($wait_for_dep_notice == 0 && !can_pkg_be_build($info)) {
  751. print STDOUT "Waiting for dependant package to finish "
  752. . "building ($pkg_name)\n"
  753. unless ($verbose < 2);
  754. $wait_for_dep_notice++;
  755. }
  756. reap();
  757. sleep 1;
  758. }
  759. last if ($abort != 0);
  760. build_pkg($info);
  761. }
  762. # Wait for all childs to finish
  763. while (scalar (keys %forked_builds)) {
  764. sleep 1;
  765. reap();
  766. }
  767. $SIG{CHLD} = 'DEFAULT';
  768. # Close the pipe to our log file if we were logging
  769. if (defined $config->{logging} && $config->{logging} != 0) {
  770. close STDOUT;
  771. close STDERR;
  772. close TEE;
  773. }
  774. die 'Abort requested by child' if ($abort != 0);
  775. print STDOUT "Done.\n";
  776. exit 0;