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/; use Peace::Model::Build; { 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 Peace::Model::Build->new( arch => $arch, log => $merged_output, success => $success, release => $self, ); } } { 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 $build = $release->generate_build( arch => $arch ); my $success = $build->success; my $log = $build->log; Attempts to build a flatpak for the release and unconditionally returns a L object with the results of the build. =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. =head2 application my $application = $release->application; $release->application($application); Allows to retrieve and set the release application as a L. =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, L =cut