| Shipwright documentation | Contained in the Shipwright distribution. |
Shipwright::Backend::Base - Base Backend Class
Base Backend Class
the constructor
initialize a project you should subclass this method, and call this to get the dir with content initialized
import a dist.
A wrapper around svn's commit command.
regenerate the dependency order.
return a dependency graph in graphviz format
get or set the dependency order.
get or set the map.
get or set the sources map.
get or set flags.
get or set version.
get or set branches.
get or set known failure conditions.
get or set refs
return the hashref of require.yml for a dist.
Check if the given repository is valid.
you should subclass this method, and run this to get the file path with latest version
get or set test_script for a project, i.e. /t/test
trim dists
update refs.
we need update this after import and trim
return true if has branch support
for vcs backend, we made a local checkout/clone version, which will live here
sunnavy <sunnavy@bestpractical.com>
Shipwright is Copyright 2007-2011 Best Practical Solutions, LLC.
This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
| Shipwright documentation | Contained in the Shipwright distribution. |
package Shipwright::Backend::Base; use warnings; use strict; use File::Spec::Functions qw/catfile catdir splitpath/; use Shipwright::Util; use File::Temp qw/tempdir/; use File::Copy 'copy'; use File::Copy::Recursive qw/rcopy/; use File::Path qw/make_path remove_tree/; use List::MoreUtils qw/uniq firstidx/; use Module::Info; our %REQUIRE_OPTIONS = ( import => [qw/source/] ); use base qw/Shipwright::Base/; __PACKAGE__->mk_accessors(qw/repository log/);
sub new { my $proto = shift; my $self = bless {@_}, ref $proto || $proto; return $self->build(@_); }
sub build { my $self = shift; $self->log( Log::Log4perl->get_logger( ref $self ) ); return $self; } sub _subclass_method { my $method = ( caller(0) )[3]; confess_or_die "your should subclass $method\n"; }
sub initialize { my $self = shift; my $dir = tempdir( 'shipwright_backend_base_XXXXXX', CLEANUP => 1, TMPDIR => 1 ); rcopy( share_root(), $dir ) or confess_or_die "copy share_root failed: $!"; $self->_install_yaml_tiny($dir); $self->_install_clean_inc($dir); $self->_install_module_build($dir); $self->_install_file_compare($dir); $self->_install_file_copy_recursive($dir); $self->_install_file_path($dir); # set proper permissions for yml under /shipwright/ my $sw_dir = catdir( $dir, 'shipwright' ); my $sw_dh; opendir $sw_dh, $sw_dir or die "can't opendir $sw_dir: $!"; for my $yml ( grep { /.yml$/ } readdir $sw_dh ) { chmod 0644, catfile( $dir, 'shipwright', $yml ); ## no critic } closedir $sw_dh; chmod 0644, catfile( $dir, 't', 'test' ); return $dir; } sub _install_module_build { my $self = shift; my $dir = shift; my $module_build_path = catdir( $dir, 'inc', 'Module', ); make_path( catdir( $module_build_path, 'Build' ) ); copy( Module::Info->new_from_module('Module::Build')->file, $module_build_path ) or confess_or_die "copy Module/Build.pm failed: $!"; rcopy( catdir( Module::Info->new_from_module('Module::Build')->inc_dir, 'Module', 'Build' ), catdir( $module_build_path, 'Build' ) ) or confess_or_die "copy Module/Build failed: $!"; # Module::Build needs Module::Metadata, Perl::OSType if ( Module::Info->new_from_module('Perl::OSType') ) { make_path( catdir( $dir, 'inc', 'Perl' ) ); copy( Module::Info->new_from_module('Perl::OSType')->file, catdir( $dir, 'inc', 'Perl' ) ) or confess_or_die "copy Perl/OSType.pm failed: $!"; } # Module::Metadata 1.02 requires version 0.87+, which isn't in perl core yet # we can't simply copy version.pm to inc because it's not plain perl. # so here we do a maybe dangerous thing, hack Module::Metadata to not require # version 0.87+ # so is Module::Build my @files = catfile( $dir, 'inc', 'Module', 'Build', 'Version.pm' ); if ( Module::Info->new_from_module('Module::Metadata') ) { copy( Module::Info->new_from_module('Module::Metadata')->file, catdir( $dir, 'inc', 'Module' ) ) or confess_or_die "copy Module/Metadata.pm failed: $!"; push @files, catfile( $dir, 'inc', 'Module', 'Metadata.pm' ); } for my $file ( @files ) { open my $fh, '<', $file or die $!; local $/; my $content = <$fh>; if ( $content =~ s!use version[^'"]+?(['"]?\s*;)!use version $1;! ) { chmod 0755, $file unless -w $file; open $fh, '>', $file or die $!; print $fh $content; close $fh; } } } sub _install_yaml_tiny { my $self = shift; my $dir = shift; my $yaml_tiny_path = catdir( $dir, 'inc', 'YAML' ); make_path( $yaml_tiny_path ); rcopy( Module::Info->new_from_module('YAML::Tiny')->file, $yaml_tiny_path ) or confess_or_die "copy YAML/Tiny.pm failed: $!"; } sub _install_clean_inc { my $self = shift; my $dir = shift; my $util_inc_path = catdir( $dir, 'inc', 'Shipwright', 'Util' ); make_path( $util_inc_path ); for my $mod ( qw/CleanINC PatchModuleBuild/ ) { rcopy( Module::Info->new_from_module("Shipwright::Util::$mod")->file, $util_inc_path ) or confess_or_die "copy $mod failed: $!"; } } sub _install_file_compare { my $self = shift; my $dir = shift; my $path = catdir( $dir, 'inc', 'File' ); make_path( $path ); rcopy( Module::Info->new_from_module('File::Compare')->file, $path ) or confess_or_die "copy File/Compare.pm failed: $!"; } sub _install_file_copy_recursive { my $self = shift; my $dir = shift; my $path = catdir( $dir, 'inc', 'File', 'Copy' ); make_path( $path ); rcopy( Module::Info->new_from_module('File::Copy::Recursive')->file, $path ) or confess_or_die "copy File/Copy/Recursive.pm failed: $!"; } sub _install_file_path { my $self = shift; my $dir = shift; my $path = catdir( $dir, 'inc', 'File' ); rcopy( Module::Info->new_from_module('File::Path')->file, $path ) or confess_or_die "copy File/Path.pm failed: $!"; }
sub import { my $self = shift; return unless ref $self; # get rid of class->import my %args = @_; my $name = ( splitpath( $args{source} ) )[-1]; if ( $self->has_branch_support ) { if ( $args{branches} ) { $args{as} = ''; } else { $args{as} ||= 'vendor'; } } unless ( $args{_initialize} || $args{_extra_tests} ) { if ( $args{_extra_tests} ) { $self->delete( path => "/t/extra" ) if $args{delete}; $self->log->info( "importing extra tests to " . $self->repository ); for my $cmd ( $self->_cmd( import => %args, name => $name ) ) { run_cmd($cmd); } } elsif ( $args{build_script} ) { if ( $self->info( path => "/scripts/$name" ) && not $args{overwrite} ) { $self->log->warn("/scripts/$name exists already"); } else { $self->delete( path => "/scripts/$name" ) if $args{delete}; $self->log->info( "importing $args{source}'s scripts to " . $self->repository ); for my $cmd ( $self->_cmd( import => %args, name => $name ) ) { run_cmd($cmd); } $self->update_refs; } } else { if ( $self->has_branch_support ) { if ( $self->info( path => "/sources/$name/$args{as}" ) && not $args{overwrite} ) { $self->log->warn( "sources/$name/$args{as} exists already" ); } else { $self->delete( path => "/sources/$name/$args{as}" ) if $args{delete}; $self->log->info( "importing $args{source} to " . $self->repository ); $self->_add_to_order($name); my $version = $self->version; if ( $args{as} ) { $version->{$name}{$args{as}} = $args{version}; } else { $version->{$name} = $args{version}; } $self->version($version); my $branches = $self->branches; if ( $args{branches} ) { # mostly this happens when import from another shipwright repo if ( @{ $args{branches} } ) { $branches->{$name} = $args{branches}; $self->branches($branches); } } elsif ( $name !~ /^cpan-/ && !( $branches->{$name} && grep { $args{as} eq $_ } @{ $branches->{$name} } ) ) { $branches->{$name} = [ @{ $branches->{$name} || [] }, $args{as} ]; $self->branches($branches); } for my $cmd ( $self->_cmd( import => %args, name => $name ) ) { run_cmd($cmd); } } } else { if ( $self->info( path => "/dists/$name" ) && not $args{overwrite} ) { $self->log->warn( "dists/$name exists already" ); } else { $self->delete( path => "/dists/$name" ) if $args{delete}; $self->log->info( "importing $args{source} to " . $self->repository ); $self->_add_to_order($name); my $version = $self->version; $version->{$name} = $args{version}; $self->version($version); for my $cmd ( $self->_cmd( import => %args, name => $name ) ) { run_cmd($cmd); } } } } } else { for my $cmd ( $self->_cmd( import => %args, name => $name ) ) { run_cmd($cmd); } } }
sub export { my $self = shift; my %args = @_; my $path = $args{path} || ''; $self->log->info( 'exporting ' . $self->repository . "/$path to $args{target}" ); for my $cmd ( $self->_cmd( export => %args ) ) { run_cmd($cmd); } }
sub checkout { my $self = shift; my %args = @_; my $path = $args{path} || ''; $self->log->info( 'exporting ' . $self->repository . "/$path to $args{target}" ); for my $cmd ( $self->_cmd( checkout => %args ) ) { run_cmd($cmd); } }
sub commit { my $self = shift; my %args = @_; $self->log->info( 'committing ' . $args{path} ); for my $cmd ( $self->_cmd( commit => @_ ) ) { run_cmd( $cmd, 1 ); } } sub _add_to_order { my $self = shift; my $name = shift; my $order = $self->order; unless ( grep { $name eq $_ } @$order ) { $self->log->info( "adding $name to order for " . $self->repository ); push @$order, $name; $self->order($order); } }
sub update_order { my $self = shift; my %args = @_; $self->log->info( "updating order for " . $self->repository ); my @dists = @{ $args{for_dists} || [] }; unless (@dists) { @dists = $self->dists; } s{/$}{} for @dists; my $require = {}; for (@dists) { $self->_fill_deps( %args, require => $require, name => $_ ); } require Algorithm::Dependency::Ordered; require Algorithm::Dependency::Source::HoA; my $source = Algorithm::Dependency::Source::HoA->new($require); $source->load(); my $dep = Algorithm::Dependency::Ordered->new( source => $source, ) or confess_or_die $@; my $order = $dep->schedule_all(); $self->order($order); }
sub graph_deps { my $self = shift; my %args = @_; $self->log->info( "outputting a graphviz order for " . $self->repository ); my @dists = @{ $args{for_dists} || [] }; unless (@dists) { @dists = $self->dists; } s{/$}{} for @dists; my $require = {}; for my $distname (@dists) { $self->_fill_deps( %args, require => $require, name => $distname ); } my $out = 'digraph g { graph [ overlap = scale, rankdir= LR ]; node [ fontsize = "18", shape = record, fontsize = 18 ]; '; for my $dist (@dists) { $out .= qq{ "$dist" [shape = record, fontsize = 18, label = "$dist" ];\n}; for my $dep ( @{ $require->{$dist} } ) { $out .= qq{"$dist" -> "$dep";\n}; } } $out .= "\n};"; return $out; } sub _fill_deps { my $self = shift; my %args = @_; my $require = $args{require}; my $name = $args{name}; return if $require->{$name}; my $req = $self->requires( name => $name ) || {}; if ( $req->{requires} ) { for (qw/requires recommends build_requires test_requires/) { push @{ $require->{$name} }, keys %{ $req->{$_} } unless $args{"skip_$_"}; } } else { #for back compatbility push @{ $require->{$name} }, keys %$req; } @{ $require->{$name} } = uniq @{ $require->{$name} }; for my $dep ( @{ $require->{$name} } ) { next if $require->{$dep}; $self->_fill_deps( %args, name => $dep ); } } sub _yml { my $self = shift; my $path = shift; my $yml = shift; my $file = catfile( $self->repository, $path ); if ($yml) { dump_yaml_file( $file, $yml ); } else { load_yaml_file($file); } }
sub order { my $self = shift; my $order = shift; my $path = '/shipwright/order.yml'; return $self->_yml( $path, $order ); }
sub map { my $self = shift; my $map = shift; my $path = '/shipwright/map.yml'; return $self->_yml( $path, $map ); }
sub source { my $self = shift; my $source = shift; my $path = '/shipwright/source.yml'; return $self->_yml( $path, $source ); }
sub flags { my $self = shift; my $flags = shift; my $path = '/shipwright/flags.yml'; return $self->_yml( $path, $flags ); }
sub version { my $self = shift; my $version = shift; my $path = '/shipwright/version.yml'; return $self->_yml( $path, $version ); }
sub branches { my $self = shift; my $branches = shift; if ( $self->has_branch_support ) { my $path = '/shipwright/branches.yml'; return $self->_yml( $path, $branches ); } # no branches support in 1.x return; }
sub ktf { my $self = shift; my $ktf = shift; my $path = '/shipwright/known_test_failures.yml'; return $self->_yml( $path, $ktf ); }
sub refs { my $self = shift; my $refs = shift; my $path = '/shipwright/refs.yml'; return $self->_yml( $path, $refs ); }
sub delete { my $self = shift; my %args = @_; my $path = $args{path} || ''; if ( $self->info( path => $path ) ) { $self->log->info( "deleting " . $self->repository . $path ); for my $cmd ( $self->_cmd( delete => path => $path ) ) { run_cmd( $cmd, 1 ); } } }
sub list { my $self = shift; my %args = @_; my $path = $args{path} || ''; if ( $self->info( path => $path ) ) { my $out = run_cmd( $self->_cmd( list => path => $path ) ); return $out; } }
sub dists { my $self = shift; my %args = @_; my $out = $self->list( path => '/scripts' ); return split /\s+/, $out; }
sub move { my $self = shift; my %args = @_; my $path = $args{path} || ''; my $new_path = $args{new_path} || ''; if ( $self->info( path => $path ) ) { $self->log->info( "moving " . $self->repository . "/$path to /$new_path" ); for my $cmd ( $self->_cmd( move => path => $path, new_path => $new_path, ) ) { run_cmd($cmd); } } }
sub info { my $self = shift; my %args = @_; my $path = $args{path} || ''; my ( $info, $err ) = run_cmd( $self->_cmd( info => path => $path ), 1 ); $self->log->warn($err) if $err; if (wantarray) { return $info, $err; } else { return $info; } }
sub requires { my $self = shift; my %args = @_; my $name = $args{name}; return $self->_yml( "/scripts/$name/require.yml" ); }
sub check_repository { my $self = shift; my %args = @_; if ( $args{action} eq 'create' ) { return 1; } else { # every valid shipwright repo has '/shipwright' subdir; my $info = $self->info( path => '/shipwright' ); return 1 if $info; } return; }
sub update { my $self = shift; my %args = @_; confess_or_die "need path option" unless $args{path}; if ( $args{path} =~ m{/$} ) { # it's a directory if ( $args{path} eq '/inc/' && ! $args{source} ) { my $dir = tempdir( 'shipwright_backend_base_XXXXXX', CLEANUP => 1, TMPDIR => 1, ); $self->_install_yaml_tiny($dir); $self->_install_clean_inc($dir); $self->_install_module_build($dir); $self->_update_dir( '/inc/', catdir($dir, 'inc') ); } elsif ( $args{source} ) { $self->_update_dir( $args{path}, $args{source} ); } } else { confess_or_die "$args{path} seems not shipwright's own file" unless -e catfile( share_root(), $args{path} ); return $self->_update_file( $args{path}, catfile( share_root(), $args{path} ) ); } }
sub test_script { my $self = shift; my %args = @_; if ( $args{source} ) { $self->_update_file( '/t/test', $args{source} ); } else { return $self->cat( path => '/t/test' ); } }
sub trim { my $self = shift; my %args = @_; my @names_to_trim; if ( ref $args{name} ) { @names_to_trim = @{ $args{name} }; } else { @names_to_trim = $args{name}; } my $order = $self->order; my $map = $self->map; my $version = $self->version || {}; my $source = $self->source || {}; my $flags = $self->flags || {}; for my $name (@names_to_trim) { if ( $self->has_branch_support ) { $self->delete( path => "/sources/$name" ); } else { $self->delete( path => "/sources/$name" ); } $self->delete( path => "/scripts/$name" ); # clean order.yml @$order = grep { $_ ne $name } @$order; # clean map.yml for ( keys %$map ) { delete $map->{$_} if $map->{$_} eq $name; } # clean version.yml, source.yml and flags.yml for my $hashref ( $source, $flags, $version ) { for ( keys %$hashref ) { if ( $_ eq $name ) { delete $hashref->{$_}; last; } } } } $self->version($version); $self->map($map); $self->source($source); $self->flags($flags); $self->order($order); $self->update_refs; }
sub update_refs { my $self = shift; my $order = $self->order; my $refs = {}; for my $name (@$order) { # initialize here, in case we don't have $name entry in $refs $refs->{$name} ||= 0; my $req = $self->requires( name => $name ) || {}; my @deps; if ( $req->{requires} ) { @deps = ( keys %{ $req->{requires} }, keys %{ $req->{recommends} }, keys %{ $req->{build_requires} }, keys %{ $req->{test_requires} } ); } else { #for back compatbility @deps = keys %$req; } @deps = uniq @deps; for (@deps) { $refs->{$_}++; } } $self->refs($refs); }
sub has_branch_support { my $self = shift; return 1 if $self->info( path => '/shipwright/branches.yml' ); return; } *_initialize_local_dir = *_cmd = *_update_file = *_update_dir = *_subclass_method;
sub local_dir { my $self = shift; my $need_init = shift; my $base_dir = catdir( shipwright_user_root(), 'backends' ); make_path( $base_dir ) unless -e $base_dir; my $repo = $self->repository; $repo =~ s/:/-/g; $repo =~ s![/\\]!_!g; my $target = catdir( $base_dir, $repo ); return $target; }
sub strip_repository { my $self = shift; my $repo = $self->repository; $repo =~ s/^[a-z+]+://; $self->repository($repo); return; }
1; __END__