autoupdate.pl 26 KB

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