From Code to Community: Sponsoring The Perl and Raku Conference 2025 Learn more

use utf8;
# Created by DBIx::Class::Schema::Loader
# DO NOT MODIFY THE FIRST PART OF THIS FILE
use strict;
use Moose;
use MooseX::MarkAsMethods autoclean => 1;
__PACKAGE__->table("stack");
__PACKAGE__->add_columns(
"id", { data_type => "integer", is_auto_increment => 1, is_nullable => 0 },
"name", { data_type => "text", is_nullable => 0 },
"is_default", { data_type => "boolean", is_nullable => 0 },
"is_locked", { data_type => "boolean", is_nullable => 0 },
"properties", { data_type => "text", is_nullable => 0 },
"head", { data_type => "integer", is_foreign_key => 1, is_nullable => 0 },
);
__PACKAGE__->set_primary_key("id");
__PACKAGE__->add_unique_constraint( "name_unique", ["name"] );
__PACKAGE__->belongs_to(
"head",
"Pinto::Schema::Result::Revision",
{ id => "head" },
{ is_deferrable => 0, on_delete => "RESTRICT", on_update => "NO ACTION" },
);
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-03-04 12:39:54
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:+O/IwTdVRx98MHUkJ281lg
#-------------------------------------------------------------------------------
# ABSTRACT: Represents a named set of Packages
#-------------------------------------------------------------------------------
our $VERSION = '0.0994_03'; # VERSION
#-------------------------------------------------------------------------------
use MooseX::Types::Moose qw(Bool Str Undef);
use File::Copy ();
use JSON qw(encode_json decode_json);
use Pinto::Util qw(:all);
use Pinto::Types qw(Dir File Version);
use version;
use overload (
'""' => 'to_string',
'<=>' => 'numeric_compare',
'cmp' => 'string_compare'
);
#------------------------------------------------------------------------------
__PACKAGE__->inflate_column(
'properties' => {
inflate => sub { decode_json( $_[0] || '{}' ) },
deflate => sub { encode_json( $_[0] || {} ) }
}
);
#------------------------------------------------------------------------------
has stack_dir => (
is => 'ro',
isa => Dir,
lazy => 1,
default => sub { $_[0]->repo->config->stacks_dir->subdir( $_[0]->name ) },
);
has modules_dir => (
is => 'ro',
isa => Dir,
lazy => 1,
default => sub { $_[0]->stack_dir->subdir('modules') },
);
has authors_dir => (
is => 'ro',
isa => Dir,
lazy => 1,
default => sub { $_[0]->stack_dir->subdir('authors') },
);
has description => (
is => 'ro',
isa => Str | Undef,
lazy => 1,
default => sub { $_[0]->get_property('description') },
init_arg => undef,
);
has target_perl_version => (
is => 'ro',
isa => Version,
lazy => 1,
default => sub {
$_[0]->get_property('target_perl_version')
or $_[0]->repo->config->target_perl_version;
},
init_arg => undef,
coerce => 1,
);
#------------------------------------------------------------------------------
sub FOREIGNBUILDARGS {
my ( $class, $args ) = @_;
$args ||= {};
$args->{is_default} ||= 0;
$args->{is_locked} ||= 0;
$args->{properties} ||= '{}';
return $args;
}
#------------------------------------------------------------------------------
before is_default => sub {
my ( $self, @args ) = @_;
throw "Cannot directly set is_default. Use mark_as_default instead" if @args;
};
#------------------------------------------------------------------------------
# TODO: All methods below that operate on the head should be moved into the
# Revision class, since that is where the data actually is. For convenience,
# the Stack class can have the same methods, but they should just delegate to
# the Revision class.
#------------------------------------------------------------------------------
sub get_distribution {
my ( $self, %args ) = @_;
my $cache = $args{cache};
my $target = $args{target} or throw 'Invalid arguments';
return $cache->{$target} if $cache && exists $cache->{$target};
my $dist;
if ( itis( $target, 'Pinto::Target::Distribution' ) ) {
my $attrs = { prefetch => 'distribution'};
my $where = {'distribution.author' => $target->author, 'distribution.archive' => $target->archive};
return unless my $reg = $self->head->search_related( registrations => $where, $attrs )->first;
$dist = $reg->distribution;
}
elsif ( itis( $target, 'Pinto::Target::Package' ) ) {
my $attrs = { prefetch => 'distribution' };
my $where = { package_name => $target->name };
return unless my $reg = $self->head->find_related( registrations => $where, $attrs );
return unless $target->is_satisfied_by($reg->package->version);
$dist = $reg->distribution;
}
$cache->{$target} = $dist if $cache;
return $dist;
}
#------------------------------------------------------------------------------
sub make_filesystem {
my ($self) = @_;
my $stack_dir = $self->stack_dir;
debug "Making stack directory at $stack_dir";
$stack_dir->mkpath;
my $stack_modules_dir = $self->modules_dir;
debug "Making modules directory at $stack_modules_dir";
$stack_modules_dir->mkpath;
my $stack_authors_dir = $self->authors_dir;
my $shared_authors_dir = $self->repo->config->authors_dir->relative($stack_dir);
mksymlink( $stack_authors_dir => $shared_authors_dir );
$self->write_modlist;
return $self;
}
#------------------------------------------------------------------------------
sub rename_filesystem {
my ( $self, %args ) = @_;
my $new_name = $args{to};
$self->assert_not_locked;
my $orig_dir = $self->stack_dir;
throw "Directory $orig_dir does not exist"
if not -e $orig_dir;
my $new_dir = $self->repo->config->stacks_dir->subdir($new_name);
throw "Directory $new_dir already exists"
if -e $new_dir && (lc $new_dir ne lc $orig_dir);
debug "Renaming directory $orig_dir to $new_dir";
File::Copy::move( $orig_dir, $new_dir ) or throw "Rename failed: $!";
return $self;
}
#------------------------------------------------------------------------------
sub kill_filesystem {
my ($self) = @_;
$self->assert_not_locked;
my $stack_dir = $self->stack_dir;
$stack_dir->rmtree or throw "Failed to remove $stack_dir: $!";
return $self;
}
#------------------------------------------------------------------------------
sub duplicate {
my ( $self, %changes ) = @_;
$changes{is_default} = 0; # Never duplicate the default flag
return $self->copy( \%changes );
}
#------------------------------------------------------------------------------
sub duplicate_registrations {
my ( $self, %args ) = @_;
my $new_rev = $args{to};
my $old_rev = $args{from} || $self->head;
my $new_rev_id = $new_rev->id;
my $old_rev_id = $old_rev->id;
debug "Copying registrations for stack $self to $new_rev";
# This raw SQL is an optimization. I was using DBIC's HashReinflator
# to fetch all the registrations, change the revision, and then reinsert
# them as new records using populate(). But that was too slow if there
# are lots of registrations.
my $sql = qq{
INSERT INTO registration(revision, package, package_name, distribution, is_pinned)
SELECT '$new_rev_id', package, package_name, distribution, is_pinned
FROM registration WHERE revision = '$old_rev_id';
};
$self->result_source->storage->dbh->do($sql);
return $self;
}
#------------------------------------------------------------------------------
sub rename {
my ( $self, %args ) = @_;
my $new_name = $args{to};
$self->assert_not_locked;
$self->update( { name => $new_name } );
$self->refresh; # Causes moose attributes to be reinitialized
$self->repo->link_modules_dir( to => $self->modules_dir ) if $self->is_default;
return $self;
}
#------------------------------------------------------------------------------
sub kill {
my ($self) = @_;
$self->assert_not_locked;
throw "Cannot kill the default stack" if $self->is_default;
$self->delete;
return $self;
}
#------------------------------------------------------------------------------
sub lock {
my ($self) = @_;
return $self if $self->is_locked;
debug "Locking stack $self";
$self->update( { is_locked => 1 } );
return $self;
}
#------------------------------------------------------------------------------
sub unlock {
my ($self) = @_;
return $self if not $self->is_locked;
debug "Unlocking stack $self";
$self->update( { is_locked => 0 } );
return $self;
}
#------------------------------------------------------------------------------
sub set_head {
my ( $self, $revision ) = @_;
debug sub {"Setting head of stack $self to revision $revision"};
$self->update( { head => $revision } );
return $self;
}
#------------------------------------------------------------------------------
sub start_revision {
my ($self) = @_;
debug "Starting revision on stack $self";
$self->assert_is_committed;
my $old_head = $self->head;
my $new_head = $self->result_source->schema->create_revision( {} );
$self->duplicate_registrations( to => $new_head );
$new_head->add_parent($old_head);
$self->set_head($new_head);
$self->assert_is_open;
return $self;
}
#------------------------------------------------------------------------------
sub commit_revision {
my ( $self, %args ) = @_;
throw "Must specify a message to commit"
if not( $args{message} or $self->head->message );
$self->assert_is_open;
$self->assert_has_changed;
$self->head->commit(%args);
$self->write_index;
$self->assert_is_committed;
return $self;
}
#-------------------------------------------------------------------------------
sub should_keep_history {
my ($self) = @_;
# Is this revision referenced by other stacks?
return 1 if $self->head->stacks->count > 1;
# Then do not keep history
return 0;
}
#-------------------------------------------------------------------------------
sub package_count {
my ($self) = @_;
return $self->head->registrations->count;
}
#-------------------------------------------------------------------------------
sub distribution_count {
my ($self) = @_;
my $attrs = { select => 'distribution', distinct => 1 };
return $self->head->registrations( {}, $attrs )->count;
}
#------------------------------------------------------------------------------
sub assert_is_open {
my ($self) = @_;
return $self->head->assert_is_open;
}
#------------------------------------------------------------------------------
sub assert_is_committed {
my ($self) = @_;
return $self->head->assert_is_committed;
}
#------------------------------------------------------------------------------
sub assert_has_changed {
my ($self) = @_;
return $self->head->assert_has_changed;
}
#------------------------------------------------------------------------------
sub assert_not_locked {
my ($self) = @_;
throw "Stack $self is locked and cannot be modified or deleted"
if $self->is_locked;
return $self;
}
#------------------------------------------------------------------------------
sub set_description {
my ( $self, $description ) = @_;
$self->set_property( description => $description );
return $self;
}
#------------------------------------------------------------------------------
sub diff {
my ( $self, $other ) = @_;
my $left = $other || ( $self->head->parents )[0];
my $right = $self;
return Pinto::Difference->new( left => $left, right => $right );
}
#------------------------------------------------------------------------------
sub mark_as_default {
my ($self) = @_;
return $self if $self->is_default;
debug 'Marking all stacks as non-default';
my $rs = $self->result_source->resultset->search;
$rs->update_all( { is_default => 0 } );
debug "Marking stack $self as default";
$self->update( { is_default => 1 } );
$self->repo->link_modules_dir( to => $self->modules_dir );
return 1;
}
#------------------------------------------------------------------------------
sub unmark_as_default {
my ($self) = @_;
return $self if not $self->is_default;
debug "Unmarking stack $self as default";
$self->update( { is_default => 0 } );
$self->repo->unlink_modules_dir;
return 1;
}
#------------------------------------------------------------------------------
sub mark_as_changed {
my ($self) = @_;
debug "Marking stack $self as changed";
$self->head->update( { has_changes => 1 } );
return $self;
}
#------------------------------------------------------------------------------
sub has_changed {
my ($self) = @_;
return $self->head->refresh->has_changes;
}
#------------------------------------------------------------------------------
sub has_not_changed {
my ($self) = @_;
return !$self->has_changed;
}
#------------------------------------------------------------------------------
sub write_index {
my ($self) = @_;
my $writer = Pinto::IndexWriter->new( stack => $self );
$writer->write_index;
return $self;
}
#------------------------------------------------------------------------------
sub write_modlist {
my ($self) = @_;
my $writer = Pinto::ModlistWriter->new( stack => $self );
$writer->write_modlist;
return $self;
}
#------------------------------------------------------------------------------
sub get_property {
my ( $self, @prop_keys ) = @_;
my %props = %{ $self->get_properties };
return @props{ map {lc} @prop_keys };
}
#-------------------------------------------------------------------------------
sub get_properties {
my ($self) = @_;
my %props = %{ $self->properties }; # Making a copy!
return \%props;
}
#-------------------------------------------------------------------------------
sub set_property {
my ( $self, $key, $value ) = @_;
$self->set_properties( { $key => "$value" } );
return $self;
}
#-------------------------------------------------------------------------------
sub set_properties {
my ( $self, $new_props ) = @_;
my $props = $self->properties;
while ( my ( $key, $value ) = each %{$new_props} ) {
Pinto::Util::validate_property_name($key);
if ( defined $value && length "$value" ) {
$props->{ lc $key } = "$value";
}
else {
delete $props->{ lc $key };
}
}
$self->update( { properties => $props } );
return $self;
}
#-------------------------------------------------------------------------------
sub delete_property {
my ( $self, @prop_keys ) = @_;
my $props = $self->properties;
delete $props->{ lc $_ } for @prop_keys;
$self->update( { properties => $props } );
return $self;
}
#-------------------------------------------------------------------------------
sub delete_properties {
my ($self) = @_;
self->update( { properties => {} } );
return $self;
}
#-------------------------------------------------------------------------------
sub default_properties {
my ($self) = @_;
my $desc = sprintf( 'The %s stack', $self->name );
my $tpv = $self->repo->config->target_perl_version->stringify;
return {
description => $desc,
target_perl_version => $tpv
};
}
#-------------------------------------------------------------------------------
sub roots {
my ($self) = @_;
my @dists = $self->head->distributions->all;
my $tpv = $self->target_perl_version;
my %is_prereq_dist;
my %cache;
# Algorithm: Visit each distribution and resolve each of its
# dependencies to the prerequisite distribution (if it exists).
# Any distribution that is a prerequisite cannot be a root.
for my $dist ( @dists ) {
for my $prereq ($dist->prerequisites) {
# TODO: Decide what to do about development prereqs
next if $prereq->is_core(in => $tpv) or $prereq->is_perl;
my %args = (target => $prereq->as_target, cache => \%cache);
next unless my $prereq_dist = $self->get_distribution(%args);
$is_prereq_dist{$prereq_dist} = 1;
}
}
return grep { not $is_prereq_dist{$_} } @dists;
}
#-------------------------------------------------------------------------------
sub numeric_compare {
my ( $stack_a, $stack_b ) = @_;
my $pkg = __PACKAGE__;
throw "Can only compare $pkg objects"
if not( itis( $stack_a, $pkg ) && itis( $stack_b, $pkg ) );
return 0 if $stack_a->id == $stack_b->id;
my $r = ( $stack_a->head <=> $stack_b->head );
return $r;
}
#------------------------------------------------------------------------------
sub string_compare {
my ( $stack_a, $stack_b ) = @_;
my $pkg = __PACKAGE__;
throw "Can only compare $pkg objects"
if not( itis( $stack_a, $pkg ) && itis( $stack_b, $pkg ) );
return 0 if $stack_a->id == $stack_b->id;
my $r = ( $stack_a->name cmp $stack_b->name );
return $r;
}
#------------------------------------------------------------------------------
sub to_string {
my ( $self, $format ) = @_;
my %fspec = (
k => sub { $self->name },
M => sub { $self->is_default ? '*' : ' ' },
L => sub { $self->is_locked ? '!' : ' ' },
I => sub { $self->head->uuid },
i => sub { $self->head->uuid_prefix },
g => sub { $self->head->message },
G => sub { indent_text( trim_text( $self->head->message ), $_[0] ) },
t => sub { $self->head->message_title },
T => sub { truncate_text( $self->head->message_title, $_[0] ) },
b => sub { $self->head->message_body },
j => sub { $self->head->username },
u => sub { $self->head->datetime->strftime( $_[0] || '%c' ) },
);
$format ||= $self->default_format();
return String::Format::stringf( $format, %fspec );
}
#-------------------------------------------------------------------------------
sub default_format {
my ($self) = @_;
return '%k';
}
#-------------------------------------------------------------------------------
__PACKAGE__->meta->make_immutable;
#-------------------------------------------------------------------------------
1;
__END__
=pod
=encoding UTF-8
=for :stopwords Jeffrey Ryan Thalhammer BenRifkah Fowler Jakob Voss Karen Etheridge Michael
G. Bergsten-Buret Schwern Oleg Gashev Steffen Schwigon Tommy Stanton
Wolfgang Kinkeldei Yanick Boris Champoux brian d foy hesco popl Däppen Cory
G Watson David Steinbrunner Glenn
=head1 NAME
Pinto::Schema::Result::Stack - Represents a named set of Packages
=head1 VERSION
version 0.0994_03
=head1 METHODS
=head2 get_distribution( target => $target )
Given a L<Pinto::Target::Package>, returns the L<Pinto::Schema::Result::Distribution>
which contains the package with the same name as the target B<and the same or higher
version as the target>. Returns nothing if no such distribution is found in
this stack.
Given a L<Pinto::Target::Distribution>, returns the L<Pinto::Schema::Result::Distribution>
from this stack with the same author id and archive attributes as the target.
Returns nothing if no such distribution is found in this stack.
You can also pass a C<cache> argument that must be a reference to a hash. It will
be used to cache results so that repeated calls to C<get_distribution> require
fewer trips to the database. It is up to you to decide when to expire the cache.
=head1 NAME
Pinto::Schema::Result::Stack
=head1 TABLE: C<stack>
=head1 ACCESSORS
=head2 id
data_type: 'integer'
is_auto_increment: 1
is_nullable: 0
=head2 name
data_type: 'text'
is_nullable: 0
=head2 is_default
data_type: 'boolean'
is_nullable: 0
=head2 is_locked
data_type: 'boolean'
is_nullable: 0
=head2 properties
data_type: 'text'
is_nullable: 0
=head2 head
data_type: 'integer'
is_foreign_key: 1
is_nullable: 0
=head1 PRIMARY KEY
=over 4
=item * L</id>
=back
=head1 UNIQUE CONSTRAINTS
=head2 C<name_unique>
=over 4
=item * L</name>
=back
=head1 RELATIONS
=head2 head
Type: belongs_to
Related object: L<Pinto::Schema::Result::Revision>
=head1 L<Moose> ROLES APPLIED
=over 4
=item * L<Pinto::Role::Schema::Result>
=back
=head1 AUTHOR
Jeffrey Ryan Thalhammer <jeff@stratopan.com>
=head1 COPYRIGHT AND LICENSE
This software is copyright (c) 2013 by Jeffrey Ryan Thalhammer.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
=cut