#!/usr/bin/perl use strict; use warnings; use Carp qw(croak); use Cwd qw(abs_path); use English qw(-no_match_vars); use File::Basename qw(basename dirname); use File::Copy qw(copy); use File::Path qw(make_path); use File::Temp qw(tempdir); use FindBin; use Getopt::Long qw(GetOptions); use POSIX qw(strftime); use lib "$FindBin::Bin/../lib"; use XCAT::BuildUtils qw( capture_command digest_manifest read_first_line relative_files run_command write_binary ); use XCAT::GenesisRelease qw( architectures deb_package_name rpm_package_name validate_architecture validate_release ); my $repo_root = abs_path("$FindBin::Bin/.."); my $xcat_source = "$repo_root/../xcat-core"; my $xcat_ref = ''; my $output_dir = ''; my $work_dir = ''; my $format = 'all'; my $all = 0; my @requested_architectures; GetOptions( 'xcat-source=s' => \$xcat_source, 'xcat-ref=s' => \$xcat_ref, 'output-dir=s' => \$output_dir, 'work-dir=s' => \$work_dir, 'format=s' => \$format, 'architecture=s@' => \@requested_architectures, 'all!' => \$all, ) or die usage(); die "Unsupported package format: $format\n" unless $format eq 'all' || $format eq 'rpm' || $format eq 'deb'; die "Use either --all or --architecture\n" if $all && @requested_architectures; @requested_architectures = architectures() if $all; @requested_architectures = ('x86_64') unless @requested_architectures; my %seen; @requested_architectures = grep { !$seen{$_}++ } @requested_architectures; validate_architecture($_) for @requested_architectures; my %requested = map { $_ => 1 } @requested_architectures; @requested_architectures = grep { $requested{$_} } architectures(); umask(0022); $xcat_source = abs_path($xcat_source) or die "Cannot resolve xcat-core source\n"; for my $path (qw(Version xCAT-genesis-builder/oe/build xCAT-genesis-builder/oe/export)) { die "xcat-core source is missing $path\n" unless -f "$xcat_source/$path"; } die "xcat-core checkout is not clean\n" if capture_command('git', '-C', $xcat_source, 'status', '--porcelain') ne ''; if ($requested{s390x}) { my $architecture_reporter = "$xcat_source/xCAT-genesis-builder/oe/build"; my $probe_work = tempdir('xcat-genesis-capabilities.XXXXXX', TMPDIR => 1, CLEANUP => 1); my $supported_output; { local $ENV{XCAT_GENESIS_WORK_DIR} = $probe_work; local $ENV{TMPDIR} = $probe_work; $supported_output = eval { capture_command($architecture_reporter, '--list-architectures'); }; } my $report_error = $EVAL_ERROR; if ($report_error) { croak "xcat-core source does not report supported Genesis architectures: $report_error"; } if (!defined($supported_output) || !length $supported_output) { die "xcat-core source reported no supported Genesis architectures\n"; } my %supported_architecture = map { $_ => 1 } split m{\s+}xms, $supported_output; if (!$supported_architecture{s390x}) { die "xcat-core source does not support Genesis architecture s390x\n"; } } my $revision = capture_command('git', '-C', $xcat_source, 'rev-parse', 'HEAD'); die "Invalid xcat-core revision: $revision\n" unless $revision =~ /^[0-9a-f]{40}$/; if ($xcat_ref ne '') { die "Invalid xcat-core ref: $xcat_ref\n" unless $xcat_ref =~ /^[A-Za-z0-9][A-Za-z0-9._\/-]*$/; my $expected = capture_command( 'git', '-C', $xcat_source, 'rev-parse', '--verify', "$xcat_ref^{commit}", ); die "xcat-core HEAD $revision does not match $xcat_ref ($expected)\n" unless $revision eq $expected; } my $version = read_first_line("$xcat_source/Version"); die "Invalid xCAT version: $version\n" unless $version =~ /^\d+(?:\.\d+){1,3}$/; my $source_date_epoch = capture_command( 'git', '-C', $xcat_source, 'show', '-s', '--format=%ct', 'HEAD', ); die "Invalid xcat-core commit time\n" unless $source_date_epoch =~ /^\d+$/; my $release = strftime('snap%Y%m%d%H%M', gmtime($source_date_epoch)); if ($output_dir eq '') { $output_dir = "$repo_root/build-output/genesis-openembedded/$version-$release"; } my $output_parent = dirname($output_dir); make_path($output_parent) unless -d $output_parent; $output_parent = abs_path($output_parent) or die "Cannot resolve output parent\n"; $output_dir = "$output_parent/" . basename($output_dir); die "Output already exists: $output_dir\n" if -e $output_dir || -l $output_dir; my $work = tempdir('xcat-genesis-release.XXXXXX', TMPDIR => 1, CLEANUP => 1); my $staging = tempdir('.xcat-genesis-release.XXXXXX', DIR => $output_parent, CLEANUP => 1); my $oe_work; if ($work_dir ne '') { make_path($work_dir) unless -d $work_dir; $work_dir = abs_path($work_dir) or die "Cannot resolve work directory\n"; $oe_work = "$work_dir/openembedded"; } else { $oe_work = "$work/openembedded"; } my $oe_tmp = "$oe_work/build/tmp"; local $ENV{XCAT_GENESIS_WORK_DIR} = $oe_work; { local $ENV{TMPDIR} = $oe_tmp; make_path($oe_tmp); run_command("$xcat_source/xCAT-genesis-builder/oe/build", @requested_architectures); } my $effective_deploy = "$oe_tmp/deploy"; die "Invalid OpenEmbedded deploy directory: $effective_deploy\n" unless -d $effective_deploy && !-l $effective_deploy; for my $architecture (@requested_architectures) { my $export = "$work/exports/$architecture"; make_path(dirname($export)); run_command( "$xcat_source/xCAT-genesis-builder/oe/export", $architecture, $effective_deploy, $export, ); my $packages = "$work/packages/$architecture"; run_command( "$FindBin::Bin/package", '--architecture', $architecture, '--export-dir', $export, '--output-dir', $packages, '--version', $version, '--release', $release, '--revision', $revision, '--source-date-epoch', $source_date_epoch, '--format', $format, ); _collect_packages($packages, $staging, $architecture); } my @formats = $format eq 'all' ? qw(deb rpm) : ($format); my $manifest_version = $requested{s390x} ? 2 : 1; write_binary( "$staging/release.manifest", "format=xcat-genesis-packages\n" . "version=$manifest_version\n" . "xcat_version=$version\n" . "xcat_release=$release\n" . "xcat_revision=$revision\n" . "source_date_epoch=$source_date_epoch\n" . "architectures=" . join(',', @requested_architectures) . "\n" . "formats=" . join(',', @formats) . "\n", ); my @release_files = grep { $_ ne 'SHA256SUMS' } relative_files($staging); write_binary( "$staging/SHA256SUMS", digest_manifest($staging, 'sha256', @release_files), ); validate_release($staging); my @verify_args = ('--format', $format); push(@verify_args, '--complete') if $all; run_command("$FindBin::Bin/verify-release", @verify_args, $staging); chmod(0755, $staging) or die "Cannot make release directory readable: $!\n"; rename($staging, $output_dir) or die "Cannot publish $output_dir: $!\n"; print "Built Genesis package release in $output_dir\n"; sub _collect_packages { my ($source, $destination, $architecture) = @_; if ($format eq 'all' || $format eq 'rpm') { my $name = rpm_package_name($architecture); _collect_one( "$source/rpm/$name-$version-$release.noarch.rpm", "$destination/rpm", ); _collect_one( "$source/srpm/$name-$version-$release.src.rpm", "$destination/srpm", ); } if ($format eq 'all' || $format eq 'deb') { my $name = deb_package_name($architecture); _collect_one( "$source/deb/${name}_${version}-${release}_all.deb", "$destination/deb", ); } } sub _collect_one { my ($source, $directory) = @_; die "Missing package artifact: $source\n" unless -f $source && !-l $source; make_path($directory) unless -d $directory; my $destination = "$directory/" . basename($source); die "Duplicate package artifact: $destination\n" if -e $destination || -l $destination; copy($source, $destination) or die "Cannot collect $source: $!\n"; } sub usage { return <<'USAGE'; Usage: build [--xcat-source DIR] [--xcat-ref REF] [--output-dir DIR] [--work-dir DIR] [--format all|rpm|deb] [--architecture ARCH ... | --all] USAGE }