Peace/lib/Peace/Model/Release.pm

393 lines
10 KiB
Perl

package Peace::Model::Release;
use v5.30.0;
use strict;
use warnings;
use Params::ValidationCompiler qw/validation_for/;
use Types::Standard qw/InstanceOf Str HasMethods HashRef/;
use Path::Tiny;
use File::pushd;
use YAML;
use JSON;
use Capture::Tiny qw/tee_merged/;
use File::Spec;
use List::AllUtils qw/any/;
{
my $validator = validation_for(
params => {
uuid => { type => Str, optional => 1 },
date_creation => { type => InstanceOf ['DateTime'], optional => 1 },
application => {
type => InstanceOf ['Peace::Model::Application'],
optional => 1
},
application_uuid => { type => Str, optional => 1 },
dbh => { type => HasMethods ['selectrow_hashref'], optional => 1 },
tag => { type => Str },
name => { type => Str },
}
);
sub new {
my $class = shift;
my %params = $validator->(@_);
die
'application or application_uuid with dbh should be passed on construct'
unless exists $params{application}
|| ( exists $params{application_uuid} && exists $params{dbh} );
return bless {%params}, $class;
}
}
{
my $validator = validation_for(
params => {
arch => { type => Str },
}
);
sub generate_build {
my $self = shift;
my %params = $validator->(@_);
my $arch = $params{arch};
my $application = $self->application;
say "Building @{[$application->name]} for $arch...";
my ( $merged_output, $exit ) = tee_merged {
my $flatpak_builder_file = $application->flatpak_builder_file;
say
"$flatpak_builder_file directory portions should not be .., . or empty string",
return 1
if any { $_ eq '..' or $_ eq '.' or $_ eq '' }
File::Spec->splitdir($flatpak_builder_file);
my $dir = Path::Tiny->tempdir;
my $clone_dir = Path::Tiny->tempdir;
my $output_dir =
Path::Tiny->new( $ENV{HOME} )->child('.peace-apps');
local $ENV{HOME} = $dir;
say "TEMP_HOME: $dir";
system 'git', 'clone', $application->git_repo, $clone_dir
and return 1;
say "CLONE_DIR: $clone_dir";
my $current_dir = pushd $clone_dir;
my $flatpak_data = $self->_parse_flatpak_builder_file;
if ( !defined $flatpak_data ) {
return 1;
}
my $app_id = $application->app_id;
my ( $sdk, $runtime ) = $flatpak_data->@{qw/sdk runtime/};
say "APP_ID: $app_id";
say "SDK: $sdk";
say "RUNTIME: $runtime";
return 1
if $self->_install_dependencies(
arch => $arch,
runtime => $runtime,
sdk => $sdk
);
system 'flatpak-builder', '--arch', $arch, '--install', '--user',
'build', $application->flatpak_builder_file, $app_id
and return 1;
$output_dir = $output_dir->child( $self->uuid );
$output_dir->mkpath;
return system 'flatpak', 'build-bundle', '--arch', $arch,
$dir->child('.local/share/flatpak/repo/'),
$output_dir->child("$arch.flatpak"), $app_id;
};
my $success = 1;
if ($exit) {
say "BUILD FAILED:\n$merged_output";
$success = 0;
}
return {
output => $merged_output,
success => $success,
};
}
}
{
my $validator = validation_for(
params => {
arch => { type => Str },
runtime => { type => Str },
sdk => { type => Str },
}
);
sub _install_dependencies {
my $self = shift;
my %params = $validator->(@_);
my $application = $self->application;
my ( $arch, $runtime, $sdk ) = @params{qw/arch runtime sdk/};
return 1
if system 'flatpak', '--user', 'remote-add', '--if-not-exists',
'remote', $application->flatpak_repo;
my $installed_apps = $self->_get_installed_flatpak_packages;
return 1
unless $self->_install_flatpak_package_if_not_exists(
arch => $arch,
package => $sdk,
installed_apps => $installed_apps,
);
return 1
unless $self->_install_flatpak_package_if_not_exists(
arch => $arch,
package => $runtime,
installed_apps => $installed_apps,
);
return;
}
}
sub _get_installed_flatpak_packages {
return {
map { ( join( ',', [ split "\t" ]->@[ 1, 4 ] ) => 1 ) }
split "\n",
`flatpak list`
};
}
{
my $validator = validation_for(
params => {
arch => { type => Str },
package => { type => Str },
installed_apps => { type => HashRef },
}
);
sub _install_flatpak_package_if_not_exists {
my $self = shift;
my %params = $validator->(@_);
my ( $arch, $package, $installed_apps ) =
@params{ 'arch', 'package', 'installed_apps' };
return 1 if exists $installed_apps->{"$package,$arch"};
return !system qw/flatpak --user install --arch/, $arch, '-y',
"$package";
}
}
sub _parse_flatpak_builder_file {
my $self = shift;
my $application = $self->application;
my $flatpak_builder_file = $application->flatpak_builder_file;
my $path = Path::Tiny->new($flatpak_builder_file);
my $payload = $path->slurp_utf8;
my $structure;
eval { $structure = decode_json($payload); };
if ( !defined $structure ) {
eval { ($structure) = Load($payload); };
}
if ( !defined $structure ) {
say "Unable to parse flatpak builder file.";
return undef;
}
my $app_id = $structure->{"app-id"};
if ( $app_id ne $application->app_id ) {
say "$app_id is not the provided app_id ($application->app_id)";
return undef;
}
return $structure;
}
sub _dbh {
my $self = shift;
return $self->{dbh};
}
{
my $validator =
validation_for( params => [ { type => Str, optional => 1 } ] );
sub uuid {
my $self = shift;
if (@_) {
my ($new_uuid) = $validator->(@_);
$self->{uuid} = $new_uuid;
}
return $self->{uuid};
}
}
{
my $validator =
validation_for(
params => [ { type => InstanceOf ['DateTime'], optional => 1 } ] );
sub date_creation {
my $self = shift;
if (@_) {
my ($new_date_creation) = $validator->(@_);
$self->{date_creation} = $new_date_creation;
}
return $self->{date_creation};
}
}
{
my $validator = validation_for(
params => [
{ type => InstanceOf ['Peace::Model::Application'], optional => 1 }
]
);
sub application {
my $self = shift;
if (@_) {
my ($new_application) = $validator->(@_);
$self->{application} = $new_application;
}
unless ( exists $self->{application} ) {
my $application_uuid = $self->{application_uuid};
my $application_dao =
Peace::DAO::Application->new( dbh => $self->_dbh );
$self->{application} =
$application_dao->recover_by_uuid( uuid => $application_uuid );
}
return $self->{application};
}
}
{
my $validator = validation_for(
params => [
{
type => Str,
optional => 1,
}
]
);
sub tag {
my $self = shift;
if (@_) {
my $new_tag = $validator->(@_);
$self->{tag} = $new_tag;
}
return $self->{tag};
}
}
{
my $validator =
validation_for( params => [ { type => Str, optional => 1 }, ] );
sub name {
my $self = shift;
if (@_) {
my ($new_name) = $validator->(@_);
$self->{name} = $new_name;
}
return $self->{name};
}
}
1;
=encoding utf8
=head1 NAME
Peace::Model::Release - The release object representation.
=head1 SYNOPSIS
my $release = Peace::Model::Release->new(
application => $application,
tag => $tag,
name => $name,
);
=head1 DESCRIPTION
Describes a release from an application from Peace.
=head1 INSTANCE METHODS
Peace::Model::Release implements the following instance
methods:
=head2 new
my $release = Peace::Model::Release->new(
uuid => $uuid, # optional
date_creation => $date_creation, # optional
application => $application, # required or application_uuid should be passed.
application_uuid => $application_uuid, # required or application should be passed
dbh => $dbh, # needed if application_uuid is passed
tag => $tag,
name => $name,
);
=head1 METHODS
Peace::Model::Release implements the following methods:
=head2 generate_build
my $result = $release->generate_build( arch => $arch );
my ($success, $output) = @result{'success', 'output'};
Generates a build for a given arch of the release and
returns the success status, any false value failed,
any true value succeded and the output which combines
stdout and stderr from the ran commands and some
application specific data about whats being done.
The output log is thought to be human consumed, not
automatically parsed.
=head2 uuid
my $uuid = $release->uuid;
$release->uuid($uuid);
Allows to retrieve and set the release uuid.
=head2 date_creation
my $date_creation = $release->date_creation;
$release->date_creation($date_creation);
Allows to retrieve and set the release date creation as a L<DateTime>.
=head2 application
my $application = $release->application;
$release->application($application);
Allows to retrieve and set the release application as a L<Peace::Model::Application>.
=head2 tag
my $tag = $release->tag;
$release->tag($tag);
Allows to retrieve and set the release tag.
=head2 name
my $name = $release->name
$release->name($name);
Allows to retrieve and set the release name.
=head1 SEE ALSO
L<Peace::Model::Application>, L<Peace::DAO::Release>
=cut