RBM.pm 43 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172
  1. package RBM;
  2. use warnings;
  3. use strict;
  4. use Path::Tiny;
  5. use Encode qw(encode);
  6. use Cwd qw(getcwd);
  7. use YAML::XS qw(LoadFile);
  8. use Template;
  9. use File::Basename;
  10. use IO::Handle;
  11. use IO::CaptureOutput qw(capture_exec);
  12. use File::Temp;
  13. use File::Copy;
  14. use File::Copy::Recursive qw(fcopy);
  15. use File::Path qw(make_path);
  16. use File::Basename;
  17. use String::ShellQuote;
  18. use Sort::Versions;
  19. use RBM::DefaultConfig;
  20. use Digest::SHA qw(sha256_hex);
  21. use Data::UUID;
  22. use Data::Dump qw(dd pp);
  23. use feature "state";
  24. our $config;
  25. sub load_config_file {
  26. my $res = {};
  27. my @conf;
  28. eval {
  29. @conf = LoadFile($_[0]);
  30. } or do {
  31. exit_error("Error reading file $_[0] :\n" . $@);
  32. };
  33. foreach my $c (@conf) {
  34. local $@ = '';
  35. $res = { %$res, %$c } if ref $c eq 'HASH';
  36. $res = { %$res, eval $c } if !ref $c;
  37. exit_error("Error executing perl config from $_[0] :\n" . $@) if $@;
  38. }
  39. return $res;
  40. }
  41. sub load_config {
  42. my $config_file = shift // find_config_file();
  43. $config = load_config_file($config_file);
  44. $config->{default} = \%default_config;
  45. $config->{basedir} = dirname($config_file);
  46. $config->{step} = 'rbm_init';
  47. $config->{opt} = {};
  48. my $pdir = $config->{projects_dir} || $config->{default}{projects_dir};
  49. foreach my $p (glob rbm_path($pdir) . '/*') {
  50. next unless -f "$p/config";
  51. $config->{projects}{basename($p)} = load_config_file("$p/config");
  52. }
  53. }
  54. sub load_system_config {
  55. my ($project) = @_;
  56. my $cfile = project_config($project ? $project : 'undef', 'sysconf_file');
  57. $config->{system} = -f $cfile ? load_config_file($cfile) : {};
  58. }
  59. sub load_local_config {
  60. my ($project) = @_;
  61. my $cfile = project_config($project ? $project : 'undef', 'localconf_file');
  62. $cfile = rbm_path($cfile);
  63. $config->{local} = -f $cfile ? load_config_file($cfile) : {};
  64. }
  65. sub find_config_file {
  66. for (my $dir = getcwd; $dir ne '/'; $dir = dirname($dir)) {
  67. return "$dir/rbm.conf" if -f "$dir/rbm.conf";
  68. }
  69. exit_error("Can't find config file");
  70. }
  71. sub set_default_env {
  72. %ENV = (%ENV, %{$config->{ENV}}) if ref $config->{ENV} eq 'HASH';
  73. }
  74. sub rbm_path {
  75. my ($path, $basedir) = @_;
  76. $basedir //= $config->{basedir};
  77. return ( $path =~ m|^/| ) ? $path : "$basedir/$path";
  78. }
  79. sub config_p {
  80. my ($c, $project, $options, @q) = @_;
  81. foreach my $p (@q) {
  82. return undef unless ref $c eq 'HASH';
  83. return undef unless defined $c->{$p};
  84. $c = ref $c->{$p} eq 'CODE' ? $c->{$p}->($project, $options, @_) : $c->{$p};
  85. }
  86. return $c;
  87. }
  88. sub as_array {
  89. ref $_[0] eq 'ARRAY' ? $_[0] : [ $_[0] ];
  90. }
  91. sub get_target {
  92. my ($project, $options, $paths, $target) = @_;
  93. my @res;
  94. foreach my $path (@$paths) {
  95. foreach my $step ([ 'steps', $config->{step} ], []) {
  96. my $z = config_p($config, $project, $options, @$path, @$step,
  97. 'targets', $target);
  98. next unless $z;
  99. if (ref $z eq 'HASH') {
  100. push @res, $target unless grep { $_ eq $target } @res;
  101. next;
  102. }
  103. my @z = ref $z eq 'ARRAY' ? (@{$z}) : ($z);
  104. push @res, map { @{get_target($project, $options, $paths, $_)} } @z;
  105. }
  106. }
  107. return \@res;
  108. }
  109. sub get_targets {
  110. my ($project, $options, $paths) = @_;
  111. my $tmp = $config->{run}{target} ? as_array($config->{run}{target}) : [ 'notarget' ];
  112. $tmp = [ map { m/^$project:(.+)$/ ? $1 : $_ } @$tmp ];
  113. return [ map { @{get_target($project, $options, $paths, $_)} } @$tmp ];
  114. }
  115. sub get_step {
  116. my ($project, $options, $step, $paths) = @_;
  117. foreach my $path (@$paths) {
  118. my $z = config_p($config, $project, $options, @$path, 'steps', $step);
  119. next unless $z;
  120. return $step if ref $z;
  121. return get_step($project, $options, $z, $paths);
  122. }
  123. return $step;
  124. }
  125. sub config {
  126. my $project = shift;
  127. my $name = shift;
  128. my $options = shift;
  129. my $res;
  130. my @targets = @{get_targets($project, $options, \@_)};
  131. my @step = ('steps', get_step($project, $options, $config->{step}, \@_));
  132. my $as_array = $options->{as_array};
  133. foreach my $path (@_) {
  134. my @l;
  135. push @l, config_p($config, $project, $options, @$path, "override.$name->[0]")
  136. if @$name == 1;
  137. if (!$as_array) {
  138. @l = grep { defined $_ } @l;
  139. return $l[0] if @l;
  140. }
  141. # 1st priority: targets + step matching
  142. foreach my $t (@targets) {
  143. push @l, config_p($config, $project, $options, @$path, @step, 'targets', $t, @$name);
  144. if (!$as_array) {
  145. @l = grep { defined $_ } @l;
  146. return $l[0] if @l;
  147. }
  148. }
  149. # 2nd priority: step maching
  150. push @l, config_p($config, $project, $options, @$path, @step, @$name);
  151. if (!$as_array) {
  152. @l = grep { defined $_ } @l;
  153. return $l[0] if @l;
  154. }
  155. # 3rd priority: target matching
  156. foreach my $t (@targets) {
  157. push @l, config_p($config, $project, $options, @$path, 'targets', $t, @$name);
  158. if (!$as_array) {
  159. @l = grep { defined $_ } @l;
  160. return $l[0] if @l;
  161. }
  162. }
  163. # last priority: no target and no step matching
  164. push @l, config_p($config, $project, $options, @$path, @$name);
  165. if (!$as_array) {
  166. @l = grep { defined $_ } @l;
  167. return $l[0] if @l;
  168. }
  169. @l = grep { defined $_ } @l;
  170. push @$res, @l if @l;
  171. }
  172. return $as_array ? $res : undef;
  173. }
  174. sub notmpl {
  175. my ($name, $project) = @_;
  176. return 1 if $name eq 'notmpl';
  177. my @n = (@{$config->{default}{notmpl}},
  178. @{project_config($project, 'notmpl')});
  179. return grep { $name eq $_ } @n;
  180. }
  181. sub confkey_str {
  182. ref $_[0] eq 'ARRAY' ? join '/', @{$_[0]} : $_[0];
  183. }
  184. sub project_config {
  185. my ($project, $name, $options) = @_;
  186. CORE::state $cache;
  187. my $res;
  188. my $error_if_undef = $options->{error_if_undef};
  189. my $cache_save = $cache;
  190. if ($options) {
  191. $options = {%$options, error_if_undef => 0};
  192. my %ignore_options = map { $_ => 1 } qw(error_if_undef step);
  193. $cache = {} if grep { !$ignore_options{$_} } keys %$options;
  194. }
  195. my $name_str = ref $name eq 'ARRAY' ? join '/', @$name : $name;
  196. my $step = $config->{step};
  197. if (exists $cache->{$project}{$step}{$name_str}) {
  198. $res = $cache->{$project}{$step}{$name_str};
  199. goto FINISH;
  200. }
  201. $name = [ split '/', $name ] unless ref $name eq 'ARRAY';
  202. goto FINISH unless @$name;
  203. my $opt_save = $config->{opt};
  204. $config->{opt} = { %{$config->{opt}}, %$options } if $options;
  205. $res = config($project, $name, $options, ['opt'], ['run'],
  206. ['projects', $project], ['local'], [],
  207. ['system'], ['default']);
  208. if (!$options->{no_tmpl} && defined($res) && !ref $res
  209. && !notmpl(confkey_str($name), $project)) {
  210. $res = process_template($project, $res,
  211. confkey_str($name) eq 'output_dir' ? '.' : undef);
  212. }
  213. $cache->{$project}{$step}{$name_str} = $res;
  214. $config->{opt} = $opt_save;
  215. FINISH:
  216. $cache = $cache_save;
  217. if (!defined($res) && $error_if_undef) {
  218. my $msg = $error_if_undef eq '1' ?
  219. "Option " . confkey_str($name) . " is undefined"
  220. : $error_if_undef;
  221. exit_error($msg);
  222. }
  223. return $res;
  224. }
  225. sub project_step_config {
  226. my $run_save = $config->{run};
  227. my $step_save = $config->{step};
  228. if ($_[2] && $_[2]->{step}) {
  229. $config->{step} = $_[2]->{step};
  230. }
  231. $config->{run} = { target => $_[2]->{target} };
  232. $config->{run}{target} //= $run_save->{target};
  233. my $res = project_config(@_);
  234. $config->{run} = $run_save;
  235. $config->{step} = $step_save;
  236. return $res;
  237. }
  238. sub exit_error {
  239. print STDERR "Error: ", $_[0], "\n";
  240. exit (exists $_[1] ? $_[1] : 1);
  241. }
  242. sub get_tmp_dir {
  243. my ($project, $options) = @_;
  244. my $tmp_dir = project_config($project, 'tmp_dir', $options);
  245. make_path($tmp_dir);
  246. return $tmp_dir;
  247. }
  248. sub set_git_gpg_wrapper {
  249. my ($project) = @_;
  250. my $w = project_config($project, 'gpg_wrapper');
  251. my (undef, $tmp) = File::Temp::tempfile(DIR => get_tmp_dir($project));
  252. path($tmp)->spew_utf8($w);
  253. chmod 0700, $tmp;
  254. system('git', 'config', 'gpg.program', $tmp) == 0
  255. || exit_error 'Error setting gpg.program';
  256. return $tmp;
  257. }
  258. sub unset_git_gpg_wrapper {
  259. unlink $_[0];
  260. system('git', 'config', '--unset', 'gpg.program') == 0
  261. || exit_error 'Error unsetting gpg.program';
  262. }
  263. sub gpg_get_fingerprint {
  264. foreach my $l (@_) {
  265. if ($l =~ m/^Primary key fingerprint:(.+)$/) {
  266. my $fp = $1;
  267. $fp =~ s/\s//g;
  268. return $fp;
  269. }
  270. }
  271. return undef;
  272. }
  273. sub git_commit_sign_id {
  274. my ($project, $chash) = @_;
  275. my $w = set_git_gpg_wrapper($project);
  276. my ($stdout, $stderr, $success, $exit_code) =
  277. capture_exec('git', 'log', "--format=format:%G?\n%GG", -1, $chash);
  278. unset_git_gpg_wrapper($w);
  279. return undef unless $success;
  280. my @l = split /\n/, $stdout;
  281. return undef unless @l >= 2;
  282. return undef unless $l[0] =~ m/^[GU]$/;
  283. return gpg_get_fingerprint(@l);
  284. }
  285. sub git_get_signed_tagname {
  286. foreach my $l (split(/\n/, $_[0])) {
  287. # the tag message is separated from headers by an empty line, so we
  288. # ignore anything after the first empty line
  289. return '' unless $l;
  290. return $1 if $l =~ m/^tag (.*)$/;
  291. }
  292. return '';
  293. }
  294. sub git_tag_sign_id {
  295. my ($project, $tag) = @_;
  296. my $w = set_git_gpg_wrapper($project);
  297. my ($stdout, $stderr, $success, $exit_code)
  298. = capture_exec('git', 'tag', '-v', $tag);
  299. unset_git_gpg_wrapper($w);
  300. return undef unless $success;
  301. return undef unless git_get_signed_tagname($stdout) eq $tag;
  302. return gpg_get_fingerprint(split /\n/, $stderr);
  303. }
  304. sub file_sign_id {
  305. my ($project, $options) = @_;
  306. my (undef, $gpg_wrapper) = File::Temp::tempfile(DIR =>
  307. get_tmp_dir($project, $options));
  308. path($gpg_wrapper)->spew_utf8(project_config($project, 'gpg_wrapper', $options));
  309. chmod 0700, $gpg_wrapper;
  310. my ($stdout, $stderr, $success, $exit_code) =
  311. capture_exec($gpg_wrapper, '--verify',
  312. project_config($project, 'filename_sig', $options),
  313. project_config($project, 'filename_data', $options));
  314. return undef unless $success;
  315. return gpg_get_fingerprint(split /\n/, $stderr);
  316. }
  317. sub valid_id {
  318. my ($fp, $valid_id) = @_;
  319. if ($valid_id eq '1' || (ref $valid_id eq 'ARRAY' && @$valid_id == 1
  320. && $valid_id->[0] eq '1')) {
  321. return 1;
  322. }
  323. if (ref $valid_id eq 'ARRAY') {
  324. foreach my $v (@$valid_id) {
  325. return 1 if $fp =~ m/$v$/;
  326. }
  327. return undef;
  328. }
  329. return $fp =~ m/$valid_id$/;
  330. }
  331. sub valid_project {
  332. my ($project) = @_;
  333. exists $config->{projects}{$project}
  334. || exit_error "Unknown project $project";
  335. }
  336. sub create_dir {
  337. my ($directory) = @_;
  338. return $directory if -d $directory;
  339. my @res = make_path($directory);
  340. exit_error "Error creating $directory" unless @res;
  341. return $directory;
  342. }
  343. sub git_need_fetch {
  344. my ($project, $options) = @_;
  345. return 0 if $config->{projects}{$project}{fetched};
  346. my $fetch = project_config($project, 'fetch', $options);
  347. if ($fetch eq 'if_needed') {
  348. my $git_hash = project_config($project, 'git_hash', $options)
  349. || exit_error "No git_hash specified for project $project";
  350. my (undef, undef, $success) = capture_exec('git', 'rev-parse',
  351. '--verify', "$git_hash^{commit}");
  352. return !$success;
  353. }
  354. return $fetch;
  355. }
  356. sub git_clone_fetch_chdir {
  357. my ($project, $options) = @_;
  358. my $clonedir = create_dir(rbm_path(project_config($project,
  359. 'git_clone_dir', $options)));
  360. my $git_url = project_config($project, 'git_url', $options)
  361. || exit_error "git_url is undefined";
  362. my @clone_submod = ();
  363. my @fetch_submod = ();
  364. if (project_config($project, 'git_submodule', $options)) {
  365. @clone_submod = ('--recurse-submodules');
  366. @fetch_submod = ('--recurse-submodules=on-demand');
  367. }
  368. if (!chdir rbm_path("$clonedir/$project")) {
  369. chdir $clonedir || exit_error "Can't enter directory $clonedir: $!";
  370. if (system('git', 'clone', @clone_submod, $git_url, $project) != 0) {
  371. exit_error "Error cloning $git_url";
  372. }
  373. chdir($project) || exit_error "Error entering $project directory";
  374. }
  375. if (git_need_fetch($project, $options)) {
  376. system('git', 'remote', 'set-url', 'origin', $git_url) == 0
  377. || exit_error "Error setting git remote";
  378. my (undef, undef, $success) = capture_exec('git', 'rev-parse', '--verify', 'HEAD');
  379. if ($success) {
  380. system('git', 'checkout', '-q', '--detach') == 0
  381. || exit_error "Error running git checkout --detach";
  382. }
  383. system('git', 'fetch', @fetch_submod, 'origin',
  384. '+refs/heads/*:refs/heads/*') == 0
  385. || exit_error "Error fetching git repository";
  386. system('git', 'fetch', @fetch_submod, 'origin',
  387. '+refs/tags/*:refs/tags/*') == 0
  388. || exit_error "Error fetching git repository";
  389. $config->{projects}{$project}{fetched} = 1;
  390. }
  391. }
  392. sub hg_need_fetch {
  393. my ($project, $options) = @_;
  394. return 0 if $config->{projects}{$project}{fetched};
  395. my $fetch = project_config($project, 'fetch', $options);
  396. if ($fetch eq 'if_needed') {
  397. my $hg_hash = project_config($project, 'hg_hash', $options)
  398. || exit_error "No hg_hash specified for project $project";
  399. my (undef, undef, $success) = capture_exec('hg', 'export', $hg_hash);
  400. return !$success;
  401. }
  402. return $fetch;
  403. }
  404. sub hg_clone_fetch_chdir {
  405. my ($project, $options) = @_;
  406. my $hg = project_config($project, 'hg', $options);
  407. my $clonedir = create_dir(rbm_path(project_config($project,
  408. 'hg_clone_dir', $options)));
  409. my $hg_url = shell_quote(project_config($project, 'hg_url', $options))
  410. || exit_error "hg_url is undefined";
  411. my $sq_project = shell_quote($project);
  412. if (!chdir rbm_path("$clonedir/$project")) {
  413. chdir $clonedir || exit_error "Can't enter directory $clonedir: $!";
  414. if (system("$hg clone -q $hg_url $sq_project") != 0) {
  415. exit_error "Error cloning $hg_url";
  416. }
  417. chdir($project) || exit_error "Error entering $project directory";
  418. }
  419. if (hg_need_fetch($project, $options)) {
  420. system("$hg pull -q $hg_url") == 0
  421. || exit_error "Error pulling changes from $hg_url";
  422. }
  423. }
  424. sub run_script {
  425. my ($project, $cmd, $f) = @_;
  426. $f //= \&capture_exec;
  427. my @res;
  428. if ($cmd =~ m/^#/) {
  429. my (undef, $tmp) = File::Temp::tempfile(DIR => get_tmp_dir($project));
  430. path($tmp)->spew_utf8($cmd);
  431. chmod 0700, $tmp;
  432. @res = $f->($tmp);
  433. unlink $tmp;
  434. } else {
  435. @res = $f->($cmd);
  436. }
  437. return @res == 1 ? $res[0] : @res;
  438. }
  439. sub execute {
  440. my ($project, $cmd, $options) = @_;
  441. my $old_cwd = getcwd;
  442. if (project_config($project, 'git_url', $options)) {
  443. my $git_hash = project_config($project, 'git_hash', $options)
  444. || exit_error "No git_hash specified for project $project";
  445. git_clone_fetch_chdir($project, $options);
  446. my ($stdout, $stderr, $success, $exit_code)
  447. = capture_exec('git', 'checkout', $git_hash);
  448. exit_error "Cannot checkout $git_hash:\n$stderr" unless $success;
  449. if (project_config($project, 'git_submodule', $options)) {
  450. foreach my $action (qw(init sync update)) {
  451. ($stdout, $stderr, $success, $exit_code)
  452. = capture_exec('git', 'submodule', $action);
  453. exit_error "Error running git submodule $action\n$stderr"
  454. unless $success;
  455. }
  456. }
  457. } elsif (project_config($project, 'hg_url', $options)) {
  458. my $hg_hash = project_config($project, 'hg_hash', $options)
  459. || exit_error "No hg_hash specified for project $project";
  460. hg_clone_fetch_chdir($project, $options);
  461. my ($stdout, $stderr, $success, $exit_code)
  462. = capture_exec('hg', 'update', '-C', $hg_hash);
  463. exit_error "Cannot checkout $hg_hash:\n$stderr" unless $success;
  464. }
  465. my ($stdout, $stderr, $success, $exit_code)
  466. = run_script($project, $cmd, \&capture_exec);
  467. chdir($old_cwd);
  468. chomp $stdout;
  469. return $success ? $stdout : undef;
  470. }
  471. sub gpg_id {
  472. my ($id) = @_;
  473. return $id unless $id;
  474. if (ref $id eq 'ARRAY' && @$id == 1 && !$id->[0]) {
  475. return 0;
  476. }
  477. return $id;
  478. }
  479. sub maketar {
  480. my ($project, $options, $dest_dir) = @_;
  481. $dest_dir //= create_dir(rbm_path(project_config($project, 'output_dir')));
  482. valid_project($project);
  483. my $old_cwd = getcwd;
  484. my $commit_hash;
  485. if (project_config($project, 'git_url', $options)) {
  486. $commit_hash = project_config($project, 'git_hash', $options)
  487. || exit_error "No git_hash specified for project $project";
  488. git_clone_fetch_chdir($project);
  489. } elsif (project_config($project, 'hg_url', $options)) {
  490. $commit_hash = project_config($project, 'hg_hash', $options)
  491. || exit_error "No hg_hash specified for project $project";
  492. hg_clone_fetch_chdir($project);
  493. } else {
  494. return undef;
  495. }
  496. my $version = project_config($project, 'version', $options);
  497. if (my $tag_gpg_id = gpg_id(project_config($project, 'tag_gpg_id', $options))) {
  498. my $id = git_tag_sign_id($project, $commit_hash) ||
  499. exit_error "$commit_hash is not a signed tag";
  500. if (!valid_id($id, $tag_gpg_id)) {
  501. exit_error "Tag $commit_hash is not signed with a valid key";
  502. }
  503. print "Tag $commit_hash is signed with key $id\n";
  504. }
  505. if (my $commit_gpg_id = gpg_id(project_config($project, 'commit_gpg_id',
  506. $options))) {
  507. my $id = git_commit_sign_id($project, $commit_hash) ||
  508. exit_error "$commit_hash is not a signed commit";
  509. if (!valid_id($id, $commit_gpg_id)) {
  510. exit_error "Commit $commit_hash is not signed with a valid key";
  511. }
  512. print "Commit $commit_hash is signed with key $id\n";
  513. }
  514. my $tar_file = "$project-$version.tar";
  515. if (project_config($project, 'git_url', $options)) {
  516. system('git', 'archive', "--prefix=$project-$version/",
  517. "--output=$dest_dir/$tar_file", $commit_hash) == 0
  518. || exit_error 'Error running git archive.';
  519. if (project_config($project, 'git_submodule', $options)) {
  520. my $tmpdir = File::Temp->newdir(
  521. get_tmp_dir($project, $options) . '/rbm-XXXXX');
  522. my ($stdout, $stderr, $success, $exit_code)
  523. = capture_exec('git', 'checkout', $commit_hash);
  524. exit_error "Cannot checkout $commit_hash: $stderr" unless $success;
  525. foreach my $action (qw(init sync update)) {
  526. ($stdout, $stderr, $success, $exit_code)
  527. = capture_exec('git', 'submodule', $action);
  528. exit_error "Error running git submodule $action\n$stderr"
  529. unless $success;
  530. }
  531. ($stdout, $stderr, $success, $exit_code)
  532. = capture_exec('git', 'submodule', 'foreach',
  533. "git archive --prefix=$project-$version/\$path/"
  534. . " --output=$tmpdir/submodule.tar \$sha1;"
  535. . "tar -Af \"$dest_dir/$tar_file\" $tmpdir/submodule.tar");
  536. exit_error 'Error running git archive on submodules.' unless $success;
  537. }
  538. } else {
  539. system('hg', 'archive', '-r', $commit_hash, '-t', 'tar',
  540. '--prefix', "$project-$version", "$dest_dir/$tar_file") == 0
  541. || exit_error 'Error running hg archive.';
  542. }
  543. my %compress = (
  544. xz => ['xz', '-f'],
  545. gz => ['gzip', '--no-name', '-f'],
  546. bz2 => ['bzip2', '-f'],
  547. );
  548. if (my $c = project_config($project, 'compress_tar', $options)) {
  549. if (!defined $compress{$c}) {
  550. exit_error "Unknow compression $c";
  551. }
  552. system(@{$compress{$c}}, "$dest_dir/$tar_file") == 0
  553. || exit_error "Error compressing $tar_file with $compress{$c}->[0]";
  554. $tar_file .= ".$c";
  555. }
  556. my $timestamp = project_config($project, 'timestamp', $options);
  557. utime $timestamp, $timestamp, "$dest_dir/$tar_file" if $timestamp;
  558. print "Created $dest_dir/$tar_file\n";
  559. chdir($old_cwd);
  560. return $tar_file;
  561. }
  562. sub sha256file {
  563. CORE::state %res;
  564. my $f = rbm_path(shift);
  565. return $res{$f} if exists $res{$f};
  566. return $res{$f} = -f $f ? sha256_hex(path($f)->slurp_raw) : '';
  567. }
  568. sub process_template_opt {
  569. my ($project, $tmpl, $opt, $dest_dir) = @_;
  570. my $save_opt = $config->{opt};
  571. $config->{opt} = {%{$config->{opt}}, %$opt} if $opt;
  572. my $res = process_template($project, $tmpl, $dest_dir);
  573. $config->{opt} = $save_opt;
  574. return $res;
  575. }
  576. sub process_template {
  577. my ($project, $tmpl, $dest_dir) = @_;
  578. return undef unless defined $tmpl;
  579. exit_error "Can't process template on a hash" if ref $tmpl eq 'HASH';
  580. if (ref $tmpl eq 'ARRAY') {
  581. my $res = [];
  582. foreach my $t (@$tmpl) {
  583. push @$res, process_template($project, $t, $dest_dir);
  584. }
  585. return $res;
  586. }
  587. $dest_dir //= rbm_path(project_config($project, 'output_dir'));
  588. my $projects_dir = rbm_path(project_config($project, 'projects_dir'));
  589. my $template = Template->new(
  590. ENCODING => 'utf8',
  591. INCLUDE_PATH => "$projects_dir/$project:$projects_dir/common",
  592. );
  593. my $vars = {
  594. config => $config,
  595. project => $project,
  596. p => $config->{projects}{$project},
  597. c => sub { project_config($project, @_) },
  598. pc => sub {
  599. my @args = @_;
  600. $args[2] = { $_[2] ? %{$_[2]} : (), origin_project => $project };
  601. project_step_config(@args);
  602. },
  603. dest_dir => $dest_dir,
  604. exit_error => \&exit_error,
  605. exec => sub { execute($project, @_) },
  606. path => \&rbm_path,
  607. tmpl => sub { process_template($project, $_[0], $dest_dir) },
  608. shell_quote => \&shell_quote,
  609. versioncmp => \&versioncmp,
  610. sha256 => sub {
  611. return sha256_hex(encode("utf8", $_[0]));
  612. },
  613. sha256file => \&sha256file,
  614. fileparse => \&fileparse,
  615. ENV => \%ENV,
  616. };
  617. my $output;
  618. $template->process(\$tmpl, $vars, \$output, binmode => ':utf8')
  619. || exit_error "Template Error:\n" . $template->error;
  620. return $output;
  621. }
  622. sub rpmspec {
  623. my ($project, $dest_dir) = @_;
  624. $dest_dir //= create_dir(rbm_path(project_config($project, 'output_dir')));
  625. valid_project($project);
  626. my $timestamp = project_config($project, 'timestamp');
  627. my $rpmspec = project_config($project, 'rpmspec')
  628. || exit_error "Undefined config for rpmspec";
  629. path("$dest_dir/$project.spec")->spew_utf8($rpmspec);
  630. utime $timestamp, $timestamp, "$dest_dir/$project.spec" if $timestamp;
  631. }
  632. sub projectslist {
  633. keys %{$config->{projects}};
  634. }
  635. sub copy_files {
  636. my ($project, $dest_dir) = @_;
  637. my @r;
  638. my $copy_files = project_config($project, 'copy_files');
  639. return unless $copy_files;
  640. my $proj_dir = rbm_path(project_config($project, 'projects_dir'));
  641. my $src_dir = "$proj_dir/$project";
  642. foreach my $file (@$copy_files) {
  643. copy("$src_dir/$file", "$dest_dir/$file");
  644. push @r, $file;
  645. }
  646. return @r;
  647. }
  648. sub urlget {
  649. my ($project, $input_file, $exit_on_error) = @_;
  650. my $cmd = project_config($project, 'urlget', $input_file);
  651. my $success = run_script($project, $cmd, sub { system(@_) }) == 0;
  652. if (!$success) {
  653. unlink project_config($project, 'filename', $input_file);
  654. exit_error "Error downloading file" if $exit_on_error;
  655. }
  656. return $success;
  657. }
  658. sub is_url {
  659. $_[0] =~ m/^https?:\/\/.*/;
  660. }
  661. sub file_in_dir {
  662. my ($filename, @dir) = @_;
  663. return map { -e "$_/$filename" ? "$_/$filename" : () } @dir;
  664. }
  665. sub input_file_need_dl {
  666. my ($input_file, $t, $fname, $action) = @_;
  667. return undef if $action eq 'getfpaths';
  668. if ($fname
  669. && $input_file->{sha256sum}
  670. && $t->('sha256sum') ne sha256_hex(path($fname)->slurp_raw)) {
  671. $fname = undef;
  672. }
  673. if ($action eq 'input_files_id') {
  674. return undef if $input_file->{input_file_id};
  675. return undef if $input_file->{sha256sum};
  676. return undef if $input_file->{exec};
  677. return undef if $fname;
  678. return 1 if $input_file->{URL};
  679. return 1 if $input_file->{content};
  680. return undef;
  681. }
  682. return $t->('refresh_input') if $fname;
  683. return 1;
  684. }
  685. sub input_file_id_hash {
  686. my ($fname, $filename) = @_;
  687. return $filename . ':' . sha256file($fname) if -f $fname;
  688. return $filename . ':' . sha256file(readlink $fname) if -l $fname;
  689. my @subdirs = sort(map { $_->basename } path($fname)->children);
  690. my @hashes = map { input_file_id_hash("$fname/$_", "$filename/$_") } @subdirs;
  691. return join("\n", @hashes);
  692. }
  693. sub input_file_id {
  694. my ($input_file, $t, $fname, $filename) = @_;
  695. return $t->('input_file_id') if $input_file->{input_file_id};
  696. return $input_file->{project} . ':' . $filename if $input_file->{project};
  697. return $filename . ':' . $t->('sha256sum') if $input_file->{sha256sum};
  698. return $filename . ':' . sha256_hex($t->('exec', { output_dir => '/out' }))
  699. if $input_file->{exec};
  700. return input_file_id_hash($fname, $filename);
  701. }
  702. sub recursive_copy {
  703. my ($fname, $name, $dest_dir) = @_;
  704. if (-f $fname || -l $fname) {
  705. fcopy($fname, "$dest_dir/$name");
  706. return ($name);
  707. }
  708. my @copied;
  709. mkdir "$dest_dir/$name";
  710. foreach my $f (map { $_->basename } path($fname)->children) {
  711. push @copied, recursive_copy("$fname/$f", "$name/$f", $dest_dir);
  712. }
  713. return @copied;
  714. }
  715. sub input_files {
  716. my ($action, $project, $options, $dest_dir) = @_;
  717. my @res_copy;
  718. my %res_getfnames;
  719. my @res_getfpaths;
  720. my $getfnames_noname = 0;
  721. my $input_files_id = '';
  722. $options = {$options ? %$options : ()};
  723. my $input_files = project_config($project, 'input_files', $options);
  724. goto RETURN_RES unless $input_files;
  725. my $proj_dir = rbm_path(project_config($project, 'projects_dir', $options));
  726. my $src_dir = "$proj_dir/$project";
  727. my $old_cwd = getcwd;
  728. chdir $src_dir || exit_error "cannot chdir to $src_dir";
  729. foreach my $input_file_alias (@$input_files) {
  730. my $input_file = $input_file_alias;
  731. if (!ref $input_file) {
  732. $input_file = project_config($project,
  733. process_template_opt($project, $input_file, $options), $options);
  734. }
  735. next unless $input_file;
  736. my $t = sub {
  737. project_config($project, $_[0], {$options ? %$options : (),
  738. %$input_file, output_dir => $src_dir,
  739. $_[1] ? %{$_[1]} : ()});
  740. };
  741. if ($input_file->{enable} && !$t->('enable')) {
  742. next;
  743. }
  744. if ($input_file->{target} || $input_file->{target_append}
  745. || $input_file->{target_prepend}) {
  746. $input_file = { %$input_file };
  747. if (ref $input_file->{target} eq 'ARRAY') {
  748. $input_file->{target} = process_template_opt($project,
  749. $input_file->{target}, $options);
  750. } else {
  751. $input_file->{target} = $config->{run}{target};
  752. }
  753. if (ref $input_file->{target_prepend} eq 'ARRAY') {
  754. $input_file->{target} = [ @{ process_template_opt($project,
  755. $input_file->{target_prepend},
  756. $options) },
  757. @{$input_file->{target}} ];
  758. }
  759. if (ref $input_file->{target_append} eq 'ARRAY') {
  760. $input_file->{target} = [ @{$input_file->{target}},
  761. @{ process_template_opt($project,
  762. $input_file->{target_append},
  763. $options) } ];
  764. }
  765. }
  766. if ($action eq 'getfnames') {
  767. my $getfnames_name;
  768. if ($input_file->{name}) {
  769. $getfnames_name = $t->('name');
  770. } else {
  771. $getfnames_name = "noname_$getfnames_noname";
  772. $getfnames_noname++;
  773. }
  774. $res_getfnames{$getfnames_name} = sub {
  775. my ($project, $options) = @_;
  776. $options //= {};
  777. if ($input_file->{project}) {
  778. $options = {%$options};
  779. $options->{origin_project} = $project;
  780. }
  781. my $t = sub {
  782. RBM::project_config($project, $_[0], { %$options, %$input_file })
  783. };
  784. return $t->('filename') if $input_file->{filename};
  785. my $url = $t->('URL');
  786. return basename($url) if $url;
  787. return RBM::project_step_config($t->('project'), 'filename',
  788. {%$options, step => $t->('pkg_type'), %$input_file})
  789. if $input_file->{project};
  790. return undef;
  791. };
  792. next;
  793. }
  794. my $proj_out_dir;
  795. if ($input_file->{project}) {
  796. $proj_out_dir = rbm_path(project_step_config($t->('project'), 'output_dir',
  797. { %$options, step => $t->('pkg_type'),
  798. origin_project => $project,
  799. output_dir => undef, %$input_file }));
  800. } else {
  801. $proj_out_dir = rbm_path(project_config($project, 'output_dir',
  802. { %$input_file, output_dir => undef }));
  803. }
  804. create_dir($proj_out_dir);
  805. my $url = $t->('URL');
  806. my $name = $input_file->{filename} ? $t->('filename') :
  807. $url ? basename($url) :
  808. undef;
  809. $name //= project_step_config($t->('project'), 'filename',
  810. {%$options, step => $t->('pkg_type'),
  811. origin_project => $project, %$input_file})
  812. if $input_file->{project};
  813. exit_error("Missing filename:\n" . pp($input_file)) unless $name;
  814. my ($fname) = file_in_dir($name, $src_dir, $proj_out_dir);
  815. my $file_gpg_id = gpg_id($t->('file_gpg_id'));
  816. if (input_file_need_dl($input_file, $t, $fname, $action)) {
  817. if ($t->('content')) {
  818. path("$proj_out_dir/$name")->spew_utf8($t->('content'));
  819. } elsif ($t->('URL')) {
  820. urlget($project, {%$input_file, filename => $name}, 1);
  821. } elsif ($t->('exec')) {
  822. my $exec_script = project_config($project, 'exec',
  823. { $options ? %$options : (), %$input_file });
  824. if (run_script($project, $exec_script,
  825. sub { system(@_) }) != 0) {
  826. exit_error "Error creating $name";
  827. }
  828. } elsif ($input_file->{project} && $t->('project')) {
  829. my $p = $t->('project');
  830. print "Building project $p - $name\n";
  831. my $run_save = $config->{run};
  832. $config->{run} = { target => $input_file->{target} };
  833. $config->{run}{target} //= $run_save->{target};
  834. build_pkg($p, {%$options, origin_project => $project, %$input_file,
  835. output_dir => $proj_out_dir});
  836. $config->{run} = $run_save;
  837. print "Finished build of project $p - $name\n";
  838. } else {
  839. dd $input_file;
  840. exit_error "Missing file $name";
  841. }
  842. }
  843. ($fname) = file_in_dir($name, $src_dir, $proj_out_dir);
  844. if ($action eq 'input_files_id') {
  845. $input_files_id .= input_file_id($input_file, $t, $fname, $name);
  846. $input_files_id .= "\n";
  847. next;
  848. }
  849. if ($action eq 'getfpaths') {
  850. push @res_getfpaths, $fname if $fname;
  851. if ($file_gpg_id && $fname) {
  852. my $sig_ext = $t->('sig_ext');
  853. $sig_ext = ref $sig_ext eq 'ARRAY' ? $sig_ext : [ $sig_ext ];
  854. foreach my $s (@$sig_ext) {
  855. if (-f "$fname.$s") {
  856. push @res_getfpaths, "$fname.$s";
  857. last;
  858. }
  859. }
  860. }
  861. if ($input_file->{project} && $t->('project')) {
  862. my $r = RBM::project_step_config($t->('project'), 'input_files_paths',
  863. {%$options, step => $t->('pkg_type'), %$input_file});
  864. push @res_getfpaths, @$r if @$r;
  865. }
  866. next;
  867. }
  868. exit_error "Missing file $name" unless $fname;
  869. if ($t->('sha256sum')
  870. && $t->('sha256sum') ne sha256_hex(path($fname)->slurp_raw)) {
  871. exit_error "Can't have sha256sum on directory: $fname" if -d $fname;
  872. exit_error "Wrong sha256sum for $fname.\n" .
  873. "Expected sha256sum: " . $t->('sha256sum');
  874. }
  875. if ($file_gpg_id) {
  876. exit_error "Can't have gpg sig on directory: $fname" if -d $fname;
  877. my $sig_ext = $t->('sig_ext');
  878. $sig_ext = ref $sig_ext eq 'ARRAY' ? $sig_ext : [ $sig_ext ];
  879. my $sig_file;
  880. foreach my $s (@$sig_ext) {
  881. if (-f "$fname.$s" && !$t->('refresh_input')) {
  882. $sig_file = "$fname.$s";
  883. last;
  884. }
  885. }
  886. foreach my $s ($sig_file ? () : @$sig_ext) {
  887. if ($url) {
  888. my $f = { %$input_file, 'override.URL' => "$url.$s",
  889. filename => "$name.$s" };
  890. if (urlget($project, $f, 0)) {
  891. $sig_file = "$fname.$s";
  892. last;
  893. }
  894. }
  895. }
  896. exit_error "No signature file for $name" unless $sig_file;
  897. my $id = file_sign_id($project, { %$input_file,
  898. filename_data => $fname, filename_sig => $sig_file });
  899. print "File $name is signed with id $id\n" if $id;
  900. if (!$id || !valid_id($id, $file_gpg_id)) {
  901. exit_error "File $name is not signed with a valid key";
  902. }
  903. }
  904. my $file_type = -d $fname ? 'directory' : 'file';
  905. print "Using $file_type $fname\n";
  906. mkdir dirname("$dest_dir/$name");
  907. push @res_copy, recursive_copy($fname, $name, $dest_dir);
  908. }
  909. chdir $old_cwd;
  910. RETURN_RES:
  911. return sha256_hex($input_files_id) if $action eq 'input_files_id';
  912. return @res_copy if $action eq 'copy';
  913. return \%res_getfnames if $action eq 'getfnames';
  914. return \@res_getfpaths if $action eq 'getfpaths';
  915. }
  916. sub system_log {
  917. my ($log_file, @args) = @_;
  918. return system(@args) if $log_file eq '-';
  919. if (my $pid = fork) {
  920. waitpid($pid, 0);
  921. return ${^CHILD_ERROR_NATIVE};
  922. }
  923. exit_error "Could not open $log_file" unless open(STDOUT, '>>', $log_file);
  924. open(STDERR, '>&', *STDOUT);
  925. exec(@args);
  926. }
  927. sub build_run {
  928. my ($project, $script_name, $options) = @_;
  929. my $old_step = $config->{step};
  930. $config->{step} = $script_name;
  931. $options //= {};
  932. my $error;
  933. my $dest_dir = create_dir(rbm_path(project_config($project, 'output_dir', $options)));
  934. valid_project($project);
  935. $options = { %$options, build_id => Data::UUID->new->create_str };
  936. my $old_cwd = getcwd;
  937. my $srcdir = project_config($project, 'build_srcdir', $options);
  938. my $use_srcdir = $srcdir;
  939. my $tmpdir = File::Temp->newdir(get_tmp_dir($project, $options)
  940. . '/rbm-XXXXX');
  941. my @cfiles;
  942. if ($use_srcdir) {
  943. @cfiles = ($srcdir);
  944. } else {
  945. $srcdir = $tmpdir->dirname;
  946. my $tarfile = maketar($project, $options, $srcdir);
  947. push @cfiles, $tarfile if $tarfile;
  948. push @cfiles, copy_files($project, $srcdir);
  949. push @cfiles, input_files('copy', $project, $options, $srcdir);
  950. }
  951. my ($remote_tmp_src, $remote_tmp_dst, %build_script);
  952. my @scripts = ('pre', $script_name, 'post');
  953. my %scripts_root = ( pre => 1, post => 1);
  954. if (project_config($project, "remote_exec", $options)) {
  955. my $cmd = project_config($project, "remote_start", {
  956. %$options,
  957. remote_srcdir => $srcdir,
  958. });
  959. if ($cmd) {
  960. my ($stdout, $stderr, $success, $exit_code)
  961. = run_script($project, $cmd, \&capture_exec);
  962. if (!$success) {
  963. $error = "Error starting remote:\n$stdout\n$stderr";
  964. goto EXIT;
  965. }
  966. }
  967. foreach my $remote_tmp ($remote_tmp_src, $remote_tmp_dst) {
  968. $cmd = project_config($project, "remote_exec", {
  969. %$options,
  970. exec_cmd => project_config($project,
  971. "remote_mktemp", $options) || 'mktemp -d -p /var/tmp',
  972. exec_name => 'mktemp',
  973. exec_as_root => 0,
  974. });
  975. my ($stdout, $stderr, $success, $exit_code)
  976. = run_script($project, $cmd, \&capture_exec);
  977. if (!$success) {
  978. $error = "Error connecting to remote:\n$stdout\n$stderr";
  979. goto EXIT;
  980. }
  981. $remote_tmp = (split(/\r?\n/, $stdout))[0];
  982. }
  983. my $o = {
  984. %$options,
  985. output_dir => $remote_tmp_dst,
  986. };
  987. foreach my $s (@scripts) {
  988. $build_script{$s} = project_config($project, $s, $o);
  989. }
  990. } else {
  991. foreach my $s (@scripts) {
  992. $build_script{$s} = project_config($project, $s, $options);
  993. }
  994. }
  995. if (!$build_script{$script_name}) {
  996. $error = "Missing $script_name config";
  997. goto EXIT;
  998. }
  999. @scripts = grep { $build_script{$_} } @scripts;
  1000. push @cfiles, @scripts unless $use_srcdir;
  1001. foreach my $s (@scripts) {
  1002. path("$srcdir/$s")->spew_utf8($build_script{$s});
  1003. chmod 0700, "$srcdir/$s";
  1004. }
  1005. my $build_log = project_config($project, "build_log", $options);
  1006. if ($build_log ne '-') {
  1007. my $append = project_config($project, "build_log_append", $options);
  1008. $build_log = rbm_path($build_log);
  1009. unlink $build_log unless $append;
  1010. make_path(dirname($build_log));
  1011. my $now = localtime;
  1012. path($build_log)->append_utf8("Starting build: $now\n");
  1013. print "Build log: $build_log\n";
  1014. }
  1015. chdir $srcdir;
  1016. my $res;
  1017. if ($remote_tmp_src && $remote_tmp_dst) {
  1018. foreach my $file (@cfiles) {
  1019. my $cmd = project_config($project, "remote_put", {
  1020. %$options,
  1021. put_src => "$srcdir/$file",
  1022. put_dst => $remote_tmp_src . '/' . dirname($file),
  1023. exec_name => 'put',
  1024. exec_as_root => 0,
  1025. });
  1026. if (run_script($project, $cmd, sub { system(@_) }) != 0) {
  1027. $error = "Error uploading $file";
  1028. goto EXIT;
  1029. }
  1030. }
  1031. foreach my $s (@scripts) {
  1032. my $cmd = project_config($project, "remote_exec", {
  1033. %$options,
  1034. exec_cmd => "cd $remote_tmp_src; ./$s",
  1035. exec_name => $s,
  1036. exec_as_root => $scripts_root{$s},
  1037. });
  1038. if (run_script($project, $cmd, sub { system_log($build_log, @_) }) != 0) {
  1039. $error = "Error running $script_name";
  1040. if (project_config($project, 'debug', $options)) {
  1041. print STDERR $error, "\nOpening debug shell\n";
  1042. print STDERR "Warning: build files will be removed when you exit this shell.\n";
  1043. my $cmd = project_config($project, "remote_exec", {
  1044. %$options,
  1045. exec_cmd => "cd $remote_tmp_src; PS1='debug-$project\$ ' \${SHELL-/bin/bash}",
  1046. exec_name => "debug-$s",
  1047. exec_as_root => $scripts_root{$s},
  1048. interactive => 1,
  1049. });
  1050. run_script($project, $cmd, sub { system(@_) });
  1051. }
  1052. goto EXIT;
  1053. }
  1054. }
  1055. my $cmd = project_config($project, "remote_get", {
  1056. %$options,
  1057. get_src => $remote_tmp_dst,
  1058. get_dst => $dest_dir,
  1059. exec_name => 'get',
  1060. exec_as_root => 0,
  1061. });
  1062. if (run_script($project, $cmd, sub { system(@_) }) != 0) {
  1063. $error = "Error downloading build result";
  1064. }
  1065. run_script($project, project_config($project, "remote_exec", {
  1066. %$options,
  1067. exec_cmd => "rm -Rf $remote_tmp_src $remote_tmp_dst",
  1068. exec_name => 'clean',
  1069. exec_as_root => 0,
  1070. }), \&capture_exec);
  1071. } else {
  1072. foreach my $s (@scripts) {
  1073. my $cmd = $scripts_root{$s} ? project_config($project, 'suexec',
  1074. { suexec_cmd => "$srcdir/$s" }) : "$srcdir/$s";
  1075. if (system_log($build_log, $cmd) != 0) {
  1076. $error = "Error running $script_name";
  1077. if (project_config($project, 'debug', $options)) {
  1078. print STDERR $error, "\nOpening debug shell\n";
  1079. print STDERR "Warning: build files will be removed when you exit this shell.\n";
  1080. run_script($project, "PS1='debug-$project\$ ' \$SHELL", sub { system(@_) });
  1081. }
  1082. }
  1083. }
  1084. }
  1085. EXIT:
  1086. if (project_config($project, "remote_exec", $options)) {
  1087. my $cmd = project_config($project, "remote_finish", $options);
  1088. if ($cmd && (run_script($project, $cmd, sub { system(@_) }) != 0)) {
  1089. $error ||= "Error finishing remote";
  1090. }
  1091. }
  1092. $config->{step} = $old_step;
  1093. chdir $old_cwd;
  1094. exit_error $error if $error;
  1095. }
  1096. sub build_pkg {
  1097. my ($project, $options) = @_;
  1098. build_run($project, project_config($project, 'pkg_type', $options), $options);
  1099. }
  1100. sub publish {
  1101. my ($project) = @_;
  1102. project_config($project, 'publish', { error_if_undef => 1 });
  1103. my $publish_src_dir = project_config($project, 'publish_src_dir');
  1104. if (!$publish_src_dir) {
  1105. $publish_src_dir = File::Temp->newdir(get_tmp_dir($project)
  1106. . '/rbm-XXXXXX');
  1107. build_pkg($project, {output_dir => $publish_src_dir});
  1108. }
  1109. build_run($project, 'publish', { build_srcdir => $publish_src_dir });
  1110. }
  1111. 1;
  1112. # vim: expandtab sw=4