/lib/Module/Build/SDL.pm

http://github.com/PerlGameDev/SDL · Perl · 351 lines · 251 code · 89 blank · 11 comment · 31 complexity · a73bb814e70498e5b07172e79462fa40 MD5 · raw file

  1. package Module::Build::SDL;
  2. use strict;
  3. use warnings;
  4. use base 'Module::Build';
  5. __PACKAGE__->add_property(parinput => '');
  6. __PACKAGE__->add_property(paroutput => '');
  7. __PACKAGE__->add_property(parlibs => [ qw/SDL SDL-1.2 SDLmain/ ]);
  8. __PACKAGE__->add_property(parmods => []);
  9. use File::Spec;
  10. use File::Find qw[finddepth];
  11. use Archive::Zip qw( :ERROR_CODES :CONSTANTS );
  12. use Alien::SDL;
  13. sub new {
  14. my $self = shift;
  15. my %args = @_;
  16. $args{share_dir} ||= 'data'; #set default sharedir name 'data' instead of 'share'
  17. $self->SUPER::new(%args);
  18. }
  19. sub ACTION_par {
  20. my ($self) = @_;
  21. $self->depends_on('code');
  22. $self->depends_on('installdeps');
  23. #checking if we have the pp installed
  24. die 'Need PAR::Packer' if !( eval ' use PAR::Packer; 1' );
  25. #here comes the code from https://github.com/PerlGameDev/SDL-Par-Packager/blob/master/SDLpp.pl
  26. my $output = $self->paroutput || (($^O eq 'MSWin32') ? 'a.exe' : 'a');
  27. my $input = $self->parinput;
  28. my @sdl_libs = @{$self->parlibs};
  29. my $extra = join ' ', (map {"-M$_"} @{$self->parmods}); # = '-MModname::A -MModname::B ...'
  30. my $Include = './lib';
  31. die 'parinput needs to be specified' unless $input;
  32. print "BUILDING PAR \n";
  33. my $exclude_modules = '-X Alien::SDL::ConfigData -X SDL::ConfigData';
  34. my $include_modules = '-M ExtUtils::CBuilder::Base -M Data::Dumper -M SDL -M Alien::SDL';
  35. $include_modules .= " $extra" if $extra;
  36. my $out_par = $output . '.par';
  37. my $par_cmd = "pp -B $exclude_modules $include_modules";
  38. $par_cmd .= " -I $Include" if $Include;
  39. $par_cmd .= " -p -o $out_par $input";
  40. print "\t $par_cmd \n";
  41. `$par_cmd` if !-e $out_par;
  42. print "PAR: $out_par\n" if -e $out_par;
  43. print "SEARCHING FOR ConfigData files \n";
  44. my $lib;
  45. my $AS_path;
  46. my $SD_path;
  47. finddepth( sub {
  48. my ($f, $d) = ($File::Find::name, $File::Find::dir);
  49. if ( $_ =~ /ConfigData/ ) {
  50. $AS_path = $f if $f =~ 'Alien/SDL/ConfigData.pm';
  51. $SD_path = $f if $f =~ 'SDL/ConfigData.pm' && $f !~ 'Alien/SDL/ConfigData.pm';
  52. $lib = $d if ( $AS_path && $SD_path );
  53. }
  54. }, @INC );
  55. die "Cannot find lib/SDL/ConfigData.pm or lib/Alien/SDL/ConfigData.pm \n" if ( !$AS_path || !$SD_path );
  56. print "Found ConfigData files in $lib \n";
  57. print "READING PAR FILE \n";
  58. my $par_file = Archive::Zip->new();
  59. unless ( $par_file->read($out_par) == AZ_OK ) {
  60. die 'read error on ' . $out_par;
  61. }
  62. $par_file->addFile( $AS_path, 'lib/Alien/SDL/ConfigData.pm' );
  63. $par_file->addFile( $SD_path, 'lib/SDL/ConfigData.pm' );
  64. my $share = Alien::SDL::ConfigData->config('share_subdir');
  65. my @shares = $par_file->membersMatching($share);
  66. my $alien_sdl_auto = $shares[0]->fileName;
  67. $alien_sdl_auto =~ s/$share(\S+)// if $alien_sdl_auto;
  68. my @auto_folder = $par_file->membersMatching("$alien_sdl_auto(?!$share)");
  69. my @sdl_not_runtime = $par_file->membersMatching( $share . '/include' ); #TODO remove extra fluff in share_dri
  70. push @sdl_not_runtime, @auto_folder; #remove non share dir stuff
  71. push @sdl_not_runtime, $par_file->membersMatching( $share . '/etc' );
  72. push @sdl_not_runtime, $par_file->membersMatching( $share . '/share' );
  73. push @sdl_not_runtime, $par_file->membersMatching( $share . '/lib' ) if $^O eq 'MSWin32';
  74. my @non = ();
  75. my @sdl_libs_to_keep = ();
  76. foreach (@sdl_libs) {
  77. if ( $^O eq 'MSWin32' ) {
  78. @non = $par_file->membersMatching( $share . "/bin(\\S+)" );
  79. #push @sdl_not_runtime ,$par_file->membersMatching( $share."/bin(\\S+)(?!$_)" )
  80. }
  81. else {
  82. @non = $par_file->membersMatching( $share . "/lib(\\S+)" );
  83. }
  84. print "Removing non $_ shared objs \n";
  85. my $lib_look = 'lib' . $_;
  86. map {
  87. my $n = $_->fileName;
  88. if ( $n =~ /$lib_look\.(so|a|dll|dylib)/ ) {
  89. push( @sdl_libs_to_keep, $_ );
  90. }
  91. } @non;
  92. }
  93. print "found $#sdl_libs_to_keep sdl libs to keep \n";
  94. my $regex_search = ']';
  95. map {
  96. print "\t " . $_->fileName . "\n";
  97. $regex_search .= ']' . $_->fileName
  98. } @sdl_libs_to_keep;
  99. $regex_search =~ s/\]\]//g;
  100. $regex_search =~ s/\]/\|/g;
  101. $regex_search = '(' . $regex_search . ')';
  102. map {
  103. my $n = $_->fileName;
  104. my $star = ' ';
  105. if ( $n !~ $regex_search ) {
  106. push @sdl_not_runtime, $_;
  107. }
  108. } @non;
  109. push @sdl_not_runtime, $par_file->membersMatching( $share . '/bin' )
  110. unless $^O eq 'MSWin32';
  111. print "REMOVING NON RUNTIME $#sdl_not_runtime files from \n";
  112. open( my $FH, '>', 'DeleteRecords.txt' ) or die $!;
  113. foreach (@sdl_not_runtime) {
  114. if ( $_->fileName eq $alien_sdl_auto . $share ) {
  115. print $FH "Not deleting " . $_->fileName . " \n";
  116. }
  117. else {
  118. $par_file->removeMember($_);
  119. print $FH $_->fileName . "\n";
  120. }
  121. }
  122. close $FH;
  123. my @config_members = $par_file->membersMatching('ConfigData.pm');
  124. foreach (@config_members) {
  125. $_->desiredCompressionLevel(1);
  126. $_->unixFileAttributes(0644);
  127. }
  128. unlink $out_par . '2';
  129. unless ( $par_file->writeToFileNamed( $out_par . '2' ) == AZ_OK ) {
  130. die 'write error';
  131. }
  132. $par_cmd = "pp -o $output " . $out_par . "2";
  133. `$par_cmd`;
  134. print "MADE $output \n" if -e $output;
  135. unlink $out_par . '2';
  136. unlink $out_par;
  137. }
  138. sub ACTION_run {
  139. my ($self) = @_;
  140. $self->depends_on('code');
  141. $self->depends_on('installdeps');
  142. my $bd = $self->{properties}->{base_dir};
  143. # prepare INC
  144. local @INC = @INC;
  145. local @ARGV = @{$self->args->{ARGV}};
  146. my $script = shift @ARGV;
  147. unshift @INC, (File::Spec->catdir($bd, $self->blib, 'lib'), File::Spec->catdir($bd, $self->blib, 'arch'));
  148. if ($script) {
  149. # scenario: ./Build run bin/scriptname param1 param2
  150. do($script);
  151. }
  152. else {
  153. # scenario: ./Build run
  154. my ($first_script) = ( glob('bin/*') ); # take the first script in bin subdir
  155. print STDERR "No params given to run action - gonna start: '$first_script'\n";
  156. do($first_script);
  157. }
  158. }
  159. # TODO: later move app skeleton generation into SDL::Devel (or something like this)
  160. sub generate_sdl_module {
  161. my ($path, $name) = @_;
  162. #Make the path and directory stuff
  163. mkdir $path or
  164. Carp::croak "Cannot make a SDL based module at $path : $!";
  165. mkdir "$path/lib";
  166. mkdir "$path/bin";
  167. mkdir "$path/data";
  168. open my $FH, ">>$path/bin/sdl_app.pl";
  169. print $FH "use string;\nuse warnings;\nuse SDL;\n";
  170. close $FH;
  171. open $FH, ">>$path/Build.PL";
  172. print $FH
  173. "use strict;\nuse warnings;\nuse Module::Build::SDL;
  174. my \$builder = Module::Build::SDL->new(
  175. module_name => '$name',
  176. dist_version => '1.01',
  177. dist_abstract => 'Put something in here',
  178. dist_author => 'developer <developer\@example.com>',
  179. license => 'perl',
  180. )->create_build_script();
  181. ";
  182. }
  183. 1;
  184. __END__
  185. =head1 NAME
  186. Module::Build::SDL - Module::Build subclass for building SDL apps/games [not stable yet]
  187. =head1 SYNOPSIS
  188. When creating a new SDL application/game you can create Build.PL like this:
  189. use Module::Build::SDL;
  190. my $builder = Module::Build::SDL->new(
  191. module_name => 'Games::Demo',
  192. dist_version => '1.00',
  193. dist_abstract => 'Demo game based on Module::Build::SDL',
  194. dist_author => 'coder@cpan.org',
  195. license => 'perl',
  196. requires => {
  197. 'SDL' => 0,
  198. },
  199. #+ others Module::Build options
  200. )->create_build_script();
  201. Once you have created a SDL application/game via Module::Build::SDL as described
  202. above you can use some extra build targets/actions:
  203. =over
  204. =item * you can create a PAR distribution like:
  205. $ perl ./Build.PL
  206. $ ./Build
  207. $ ./Build par
  208. There are some extra parameters related to 'par' action you can pass to Module::Build::SDL->new():
  209. parinput => 'bin/scriptname.pl'
  210. paroutput => 'filename.par.exe',
  211. parlibs => [ qw/SDL SDL_main SDL_gfx/ ], #external libraries (.so/.dll) to be included into PAR
  212. parmods => [ qw/Module::A Module::B/ ], #extra modules to be included into PAR
  213. =item * to run the game from distribution directory you can use:
  214. $ perl ./Build.PL
  215. $ ./Build
  216. $ ./Build run
  217. =item * TODO: maybe some additional actions: parexe, parmsi, deb, rpm
  218. =back
  219. =head1 DESCRIPTION
  220. Module::Build::SDL is a subclass of L<Module::Build|Module::Build> created
  221. to make easy some tasks specific to SDL applications - e.g. packaging SDL
  222. application/game into PAR archive.
  223. =head1 APPLICATION/GAME LAYOUT
  224. Module::Build::SDL expects the following layout in project directory:
  225. #example: game with the main *.pl script + data files + modules (*.pm)
  226. Build.PL
  227. lib/
  228. Games/
  229. Demo.pm
  230. bin/
  231. game-script.pl
  232. data/
  233. whatever_data_files_you_need.jpg
  234. the most simple game should look like:
  235. #example: simple one-script apllication/game
  236. Build.PL
  237. bin/
  238. game-script.pl
  239. In short - there are 3 expected subdirectories:
  240. =over
  241. =item * B<bin> - one or more perl scripts (*.pl) to start the actual
  242. application/game
  243. =item * B<lib> - application/game specific modules (*.pm) organized
  244. in dir structure in "usual perl manners"
  245. =item * B<data> - directory for storing application data (pictures,
  246. sounds etc.). This subdirectory is handled as a "ShareDir"
  247. (see L<File::ShareDir|File::ShareDir> for more details)
  248. =item * As the project is (or could be) composed as a standard perl
  249. distribution it also support standard subdirectory B<'t'> (with tests).
  250. =back
  251. =head1 RULES TO FOLLOW
  252. When creating a SDL application/game based on Module::Build::SDL it is
  253. recommended to follow these rules:
  254. =over
  255. =item * Use the name for your game from I<Games::*> namespace; it will make
  256. the later release to CPAN much easier.
  257. =item * Put all data files into B<data> subdirectory and access the B<data>
  258. subdir only via L<File::ShareDir|File::ShareDir>
  259. (namely by calling L<distdir()|File::ShareDir/dist_dir> function)
  260. =item * TODO: maybe add more
  261. =back
  262. =cut