package Enbld::Target;
use strict;
use warnings;
use Carp;
use version;
use File::Spec;
use File::Path qw/make_path remove_tree/;
use File::Find;
use File::Copy::Recursive qw/rcopy/;
use autodie;
use List::Util qw/first/;
require Enbld::Definition;
require Enbld::Feature;
require Enbld::Condition;
require Enbld::Message;
require Enbld::Home;
require Enbld::HTTP;
require Enbld::Target::Symlink;
require Enbld::Error;
require Enbld::Deployed;
sub new {
my ( $class, $name, $config ) = @_;
my $self = {
name => $name,
config => $config,
attributes => undef,
build => undef,
install => undef,
PATH => undef,
conditions => undef,
};
bless $self, $class;
$self->_set_config;
$self->_set_attributes;
$self->_set_PATH;
return $self;
}
sub install {
my $self = shift;
my $version = shift;
if ( ! $self->_is_install_ok ) {
Enbld::Error->throw( "'$self->{name}' is already installed." );
}
my $condition = $version ? Enbld::Condition->new( version => $version ) :
Enbld::Condition->new;
$self->{attributes}->add( 'VersionCondition', $condition->version );
$self->_build( $condition );
return $self->{config};
}
sub _is_install_ok {
my $self = shift;
return 1 if Enbld::Feature->is_force_install;
return if $self->is_installed;
return 1;
}
sub deploy {
my $self = shift;
my $condition = Enbld::Condition->new;
$self->{attributes}->add( 'VersionCondition', $condition->version );
$self->_build_to_deploy( $condition );
return $self->{config};
}
sub install_declared {
my ( $self, $declared_conditions ) = @_;
$self->{attributes}->add(
'VersionCondition',
$declared_conditions->{$self->{name}}{version}
);
return unless $self->_is_install_declared_ok(
$declared_conditions->{$self->{name}}
);
$self->{conditions} = $declared_conditions;
$self->_build( $declared_conditions->{$self->{name}} );
return $self->{config};
}
sub deploy_declared {
my ( $self, $declared_conditions ) = @_;
$self->{attributes}->add(
'VersionCondition',
$declared_conditions->{$self->{name}}{version}
);
$self->{conditions} = $declared_conditions;
$self->_build_to_deploy( $declared_conditions->{$self->{name}} );
return $self->{config};
}
sub _is_install_declared_ok {
my ( $self, $condition ) = @_;
return 1 if Enbld::Feature->is_force_install;
return 1 unless $self->{config}->enabled;
return 1 unless $condition->is_equal_to( $self->{config}->condition );
return if $self->{config}->enabled eq $self->{attributes}->Version;
return 1;
}
sub upgrade {
my $self = shift;
if ( ! $self->is_installed ) {
Enbld::Error->throw( "'$self->{name}' is not installed yet." );
}
$self->{attributes}->add(
'VersionCondition',
$self->{config}->condition->version
);
my $current = $self->{attributes}->Version;
my $enabled = $self->{config}->enabled;
my $VersionList = $self->{attributes}->SortedVersionList;
my $index_current =
first { ${ $VersionList }[$_] eq $current } 0..$#{ $VersionList };
my $index_enabled =
first { ${ $VersionList }[$_] eq $enabled } 0..$#{ $VersionList };
if ( $index_current <= $index_enabled ) {
Enbld::Error->throw( "'$self->{name}' is up to date." );
}
$self->_build( $self->{config}->condition );
return $self->{config};
}
sub off {
my $self = shift;
if ( ! $self->is_installed ) {
Enbld::Error->throw( "'$self->{name}' is not installed yet." );
}
$self->_drop;
$self->{config}->drop_enabled;
return $self->{config};
}
sub rehash {
my $self = shift;
if ( ! $self->is_installed ) {
Enbld::Error->throw( "'$self->{name}' isn't installed yet." );
}
$self->_switch( $self->{config}->enabled );
return $self->{config};
}
sub use {
my ( $self, $version ) = @_;
if ( ! $self->{config}->installed ) {
Enbld::Error->throw( "'$self->{name}' isn't installed yet." );
}
my $form = $self->{attributes}->VersionForm;
if ( $version !~ /^$form$/ ) {
Enbld::Error->throw( "'$version' is not valid version form." );
}
if ( $self->{config}->enabled && $self->{config}->enabled eq $version ) {
Enbld::Error->throw( "'$version' is current enabled version." );
}
if ( ! $self->{config}->is_installed_version( $version ) ) {
Enbld::Error->throw( "'$version' isn't installed yet" );
}
$self->_switch( $version );
return $self->{config};
}
sub is_installed {
my $self = shift;
return $self->{config}->enabled;
}
sub is_outdated {
my $self = shift;
return unless ( $self->{config}->enabled );
$self->{attributes}->add(
'VersionCondition', $self->{config}->condition->version
);
my $current = $self->{attributes}->Version;
my $enabled = $self->{config}->enabled;
my $VersionList = $self->{attributes}->SortedVersionList;
my $index_current =
first { ${ $VersionList }[$_] eq $current } 0..$#{ $VersionList };
my $index_enabled =
first { ${ $VersionList }[$_] eq $enabled } 0..$#{ $VersionList };
if ( $index_current > $index_enabled ) {
return $current;
}
return;
}
sub _set_config {
my $self = shift;
if ( ! $self->{config} ) {
require Enbld::Config;
$self->{config} = Enbld::Config->new( name => $self->{name} );
}
}
sub _set_attributes {
my $self = shift;
$self->{attributes} = Enbld::Definition->new( $self->{name} )->parse;
}
sub _set_PATH {
my $self = shift;
my $path = File::Spec->catdir( Enbld::Home->install_path, 'bin' );
$self->{PATH} = $path . ':' . $ENV{PATH};
}
sub _switch {
my ( $self, $version ) = @_;
my $path = File::Spec->catdir(
Enbld::Home->depository,
$self->{attributes}->DistName
);
my $new = File::Spec->catdir( $path, $version );
Enbld::Target::Symlink->delete_symlink( $path );
Enbld::Target::Symlink->create_symlink( $new );
$self->{config}->set_enabled(
$version,
$self->{config}->condition( $version ),
);
}
sub _drop {
my $self = shift;
my $path = File::Spec->catdir(
Enbld::Home->depository,
$self->{attributes}->DistName,
);
Enbld::Target::Symlink->delete_symlink( $path );
$self->{config}->drop_enabled;
}
sub _build {
my ( $self, $condition ) = @_;
Enbld::Message->notify( "=====> Start building target '$self->{name}'." );
local $ENV{PATH} = $self->{PATH};
$self->_solve_dependencies;
$self->_setup_install_directory;
$self->_exec_build_command( $condition );
if ( $condition->module_file ) {
$self->_install_module( $condition );
}
$self->_postbuild;
my $finish_msg = "=====> Finish building target '$self->{name}'.";
Enbld::Message->notify( $finish_msg );
$self->{config}->set_enabled( $self->{attributes}->Version, $condition );
}
sub _build_to_deploy {
my ( $self, $condition ) = @_;
Enbld::Message->notify( "=====> Start building target '$self->{name}'." );
local $ENV{PATH} = $self->{PATH};
$self->_solve_dependencies_to_deploy;
$self->{install} = Enbld::Home->deploy_path;
$self->_exec_build_command( $condition );
if ( $condition->module_file ) {
$self->_install_module( $condition );
}
my $finish_msg = "=====> Finish building target '$self->{name}'.";
Enbld::Message->notify( $finish_msg );
$self->{config}->set_enabled( $self->{attributes}->Version, $condition );
}
sub _exec_build_command {
my $self = shift;
my $condition = shift;
$self->_setup_build_directory;
chdir $self->{build};
$self->_prebuild;
$self->_configure( $condition ) if $self->{attributes}->CommandConfigure;
$self->_make if $self->{attributes}->CommandMake;
if ( $condition->make_test or Enbld::Feature->is_make_test_all ) {
$self->_test;
}
$self->_install if $self->{attributes}->CommandInstall;
}
sub _solve_dependencies {
my $self = shift;
return if ( ! @{ $self->{attributes}->Dependencies } );
Enbld::Message->notify( "=====> Found dependencies." );
require Enbld::App::Configuration;
foreach my $dependency ( @{ $self->{attributes}->Dependencies } ) {
Enbld::Message->notify( "--> Dependency '$dependency'." );
my $config = Enbld::App::Configuration->search_config( $dependency );
my $target = Enbld::Target->new( $dependency, $config );
if ( $target->is_installed ) {
my $installed_msg = "--> $dependency is already installed.";
Enbld::Message->notify( $installed_msg );
next;
}
Enbld::Message->notify( "--> $dependency is not installed yet." );
my $condition = $self->{conditions}{$dependency} ?
$self->{conditions}{$dependency} : undef;
my $installed;
if ( $condition ) {
$installed = $target->install_declared( $self->{conditions} );
} else {
$installed = $target->install;
}
Enbld::App::Configuration->set_config( $installed );
}
}
sub _solve_dependencies_to_deploy {
my $self = shift;
return if ( ! @{ $self->{attributes}->Dependencies } );
Enbld::Message->notify( "=====> Found dependencies." );
foreach my $dependency ( @{ $self->{attributes}->Dependencies } ) {
next if ( Enbld::Deployed->is_deployed( $dependency ));
Enbld::Message->notify( "--> Dependency '$dependency'." );
Enbld::Message->notify( "--> $dependency is not installed yet." );
my $target = Enbld::Target->new( $dependency );
my $condition = $self->{conditions}{$dependency} ?
$self->{conditions}{$dependency} : undef;
my $installed;
if ( $condition ) {
$installed = $target->deploy_declared( $self->{conditions} );
} else {
$installed = $target->deploy;
}
Enbld::Deployed->add( $installed );
}
}
sub _prebuild {
my $self = shift;
$self->_apply_patchfiles if $self->{attributes}->PatchFiles;
}
sub _configure {
my $self = shift;
my $condition = shift;
return $self unless $self->{attributes}->CommandConfigure;
my $configure;
$configure = $self->{attributes}->CommandConfigure . ' ';
$configure .= $self->{attributes}->Prefix . $self->{install};
if( $self->{attributes}->AdditionalArgument ) {
$configure .= ' ' . $self->{attributes}->AdditionalArgument;
}
if ( $condition->arguments ) {
$configure .= ' ' . $condition->arguments;
}
$self->_exec( $configure );
}
sub _make {
my $self = shift;
if ( $self->{attributes}->CommandConfigure ) {
$self->_exec( $self->{attributes}->CommandMake );
return $self;
}
# this code for tree command...tree don't has configure
my $args = $self->{attributes}->Prefix . $self->{install};
if ( $self->{attributes}->AdditionalArgument ) {
$args .= ' ' . $self->{attributes}->AdditionalArgument;
}
$self->_exec( $self->{attributes}->CommandMake . ' ' . $args );
return $self;
}
sub _test {
my $self = shift;
return $self unless $self->{attributes}->CommandTest;
$self->_exec( $self->{attributes}->CommandTest );
}
sub _install {
my $self = shift;
if ( $self->{attributes}->CommandConfigure ) {
$self->_exec( $self->{attributes}->CommandInstall );
return $self;
}
my $args = $self->{attributes}->Prefix . $self->{install};
if ( $self->{attributes}->AdditionalArgument ) {
$args .= ' ' . $self->{attributes}->AdditionalArgument;
}
$self->_exec( $self->{attributes}->CommandInstall . ' ' . $args );
return $self;
}
sub _install_module {
my ( $self, $condition ) = @_;
require Enbld::Module;
my $module = Enbld::Module->new(
name => $self->{name},
path => $self->{install},
module_file => $condition->module_file,
);
$module->install;
}
sub _postbuild {
my $self = shift;
$self->_copy_files;
my $path = File::Spec->catdir(
Enbld::Home->depository,
$self->{attributes}->DistName,
);
Enbld::Target::Symlink->delete_symlink( $path );
Enbld::Target::Symlink->create_symlink( $self->{install} );
}
sub _copy_files {
my $self = shift;
return $self unless ( my $dirs = $self->{attributes}->CopyFiles );
for my $dir ( @{ $dirs } ) {
rcopy(
File::Spec->catdir( $self->{build}, $dir ),
File::Spec->catdir( $self->{install}, $dir )
);
}
}
sub _exec {
my ( $self, $cmd ) = @_;
require Enbld::Logger;
my $logfile = Enbld::Logger->logfile;
Enbld::Message->notify( "--> $cmd" );
system( "LANG=C;$cmd >> $logfile 2>&1" );
return $self unless $?;
if ( $? == -1 ) {
Enbld::Error->throw( "Failed to execute:$cmd" );
} elsif ( $? & 127 ) {
Enbld::Error->new( "Child died with signal:$cmd" );
} else {
Enbld::Error->throw(
"Build fail.Command:$cmd return code:" . ( $? >> 8 )
);
}
}
sub _apply_patchfiles {
my $self = shift;
my $patchfiles = $self->{attributes}->PatchFiles;
require Enbld::HTTP;
require Enbld::Message;
require Enbld::Logger;
my $logfile = Enbld::Logger->logfile;
foreach my $patchfile ( @{ $patchfiles } ) {
my @parse = split( /\//, $patchfile );
my $path = File::Spec->catfile( $self->{build}, $parse[-1] );
Enbld::HTTP->download( $patchfile, $path );
Enbld::Message->notify( "--> Apply patch $parse[-1]." );
system( "patch -p0 < $path >> $logfile 2>&1" );
}
}
sub _setup_build_directory {
my $self = shift;
my $path = File::Spec->catfile(
Enbld::Home->dists,
$self->{attributes}->Filename
);
my $archivefile =
Enbld::HTTP->download_archivefile( $self->{attributes}->URL, $path );
my $build = $archivefile->extract( Enbld::Home->build );
return ( $self->{build} = $build );
}
sub _setup_install_directory {
my $self = shift;
my $depository = File::Spec->catdir(
Enbld::Home->depository,
$self->{attributes}->DistName,
$self->{attributes}->Version,
);
remove_tree( $depository ) if ( -d $depository );
return ( $self->{install} = $depository );
}
1;