1package Alien::SDL;
2use strict;
3use warnings;
4use Alien::SDL::ConfigData;
5use File::ShareDir qw(dist_dir);
6use File::Spec;
7use File::Find;
8use File::Spec::Functions qw(catdir catfile rel2abs);
9use File::Temp;
10use Capture::Tiny;
11use Config;
12
13=head1 NAME
14
15Alien::SDL - building, finding and using SDL binaries
16
17=head1 VERSION
18
19Version 1.446
20
21=cut
22
23our $VERSION = '1.446';
24$VERSION = eval $VERSION;
25
26=head1 SYNOPSIS
27
28Alien::SDL tries (in given order) during its installation:
29
30=over
31
32=item * When given C<--with-sdl-config> option use specified sdl-config
33script to locate SDL libs.
34
35 perl Build.PL --with-sdl-config=/opt/sdl/bin/sdl-config
36
37or using default script name 'sdl-config' by running:
38
39 perl Build.PL --with-sdl-config
40
41B<IMPORTANT NOTE:> Using --with-sdl-config avoids considering any other
42build methods; no prompt with other available build options.
43
44=item * Locate an already installed SDL via 'sdl-config' script.
45
46=item * Check for SDL libs in directory specified by SDL_INST_DIR variable.
47In this case the module performs SDL library detection via
48'$SDL_INST_DIR/bin/sdl-config' script.
49
50 SDL_INST_DIR=/opt/sdl perl ./Build.PL
51
52=item * Download prebuilt SDL binaries (if available for your platform).
53
54=item * Build SDL binaries from source codes (if possible on your system).
55
56=back
57
58Later you can use Alien::SDL in your module that needs to link agains SDL
59and/or related libraries like this:
60
61    # Sample Makefile.pl
62    use ExtUtils::MakeMaker;
63    use Alien::SDL;
64
65    WriteMakefile(
66      NAME         => 'Any::SDL::Module',
67      VERSION_FROM => 'lib/Any/SDL/Module.pm',
68      LIBS         => Alien::SDL->config('libs', [-lAdd_Lib]),
69      INC          => Alien::SDL->config('cflags'),
70      # + additional params
71    );
72
73=head1 DESCRIPTION
74
75Please see L<Alien> for the manifesto of the Alien namespace.
76
77In short C<Alien::SDL> can be used to detect and get
78configuration settings from an installed SDL and related libraries.
79Based on your platform it offers the possibility to download and
80install prebuilt binaries or to build SDL & co. from source codes.
81
82The important facts:
83
84=over
85
86=item * The module does not modify in any way the already existing SDL
87installation on your system.
88
89=item * If you reinstall SDL libs on your system you do not need to
90reinstall Alien::SDL (providing that you use the same directory for
91the new installation).
92
93=item * The prebuild binaries and/or binaries built from sources are always
94installed into perl module's 'share' directory.
95
96=item * If you use prebuild binaries and/or binaries built from sources
97it happens that some of the dynamic libraries (*.so, *.dll) will not
98automaticly loadable as they will be stored somewhere under perl module's
99'share' directory. To handle this scenario Alien::SDL offers some special
100functionality (see below).
101
102=back
103
104=head1 METHODS
105
106=head2 config()
107
108This function is the main public interface to this module. Basic
109functionality works in a very similar maner to 'sdl-config' script:
110
111    Alien::SDL->config('prefix');   # gives the same string as 'sdl-config --prefix'
112    Alien::SDL->config('version');  # gives the same string as 'sdl-config --version'
113    Alien::SDL->config('libs');     # gives the same string as 'sdl-config --libs'
114    Alien::SDL->config('cflags');   # gives the same string as 'sdl-config --cflags'
115
116On top of that this function supports special parameters:
117
118    Alien::SDL->config('ld_shared_libs');
119
120Returns a list of full paths to shared libraries (*.so, *.dll) that will be
121required for running the resulting binaries you have linked with SDL libs.
122
123    Alien::SDL->config('ld_paths');
124
125Returns a list of full paths to directories with shared libraries (*.so, *.dll)
126that will be required for running the resulting binaries you have linked with
127SDL libs.
128
129    Alien::SDL->config('ld_shlib_map');
130
131Returns a reference to hash of value pairs '<libnick>' => '<full_path_to_shlib'>,
132where '<libnick>' is shortname for SDL related library like: SDL, SDL_gfx, SDL_net,
133SDL_sound ... + some non-SDL shortnames e.g. smpeg, jpeg, png.
134
135NOTE: config('ld_<something>') return an empty list/hash if you have decided to
136use SDL libraries already installed on your system. This concerns 'sdl-config'
137detection and detection via '$SDL_INST_DIR/bin/sdl-config'.
138
139=head2 check_header()
140
141This function checks the availability of given header(s) when using compiler
142options provided by "Alien::SDL->config('cflags')".
143
144    Alien::SDL->check_header('SDL.h');
145    Alien::SDL->check_header('SDL.h', 'SDL_net.h');
146
147Returns 1 if all given headers are available, 0 otherwise.
148
149=head2 get_header_version()
150
151Tries to find a header file specified as a param in SDL prefix direcotry and
152based on "#define" macros inside this header file tries to get a version triplet.
153
154    Alien::SDL->get_header_version('SDL_mixer.h');
155    Alien::SDL->get_header_version('SDL_version.h');
156    Alien::SDL->get_header_version('SDL_gfxPrimitives.h');
157    Alien::SDL->get_header_version('SDL_image.h');
158    Alien::SDL->get_header_version('SDL_mixer.h');
159    Alien::SDL->get_header_version('SDL_net.h');
160    Alien::SDL->get_header_version('SDL_ttf.h');
161    Alien::SDL->get_header_version('smpeg.h');
162
163Returns string like '1.2.3' or undef if not able to find and parse version info.
164
165=head1 BUGS
166
167Please post issues and bugs at L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Alien-SDL>
168
169=head1 AUTHOR
170
171    Kartik Thakore
172    CPAN ID: KTHAKORE
173    Thakore.Kartik@gmail.com
174    http://yapgh.blogspot.com
175
176=head1 ACKNOWLEDGEMENTS
177
178    kmx - complete redesign between versions 0.7.x and 0.8.x
179
180=head1 COPYRIGHT
181
182This program is free software; you can redistribute
183it and/or modify it under the same terms as Perl itself.
184
185The full text of the license can be found in the
186LICENSE file included with this module.
187
188=cut
189
190### get config params
191sub config
192{
193  my $package = shift;
194  my @params  = @_;
195  return _sdl_config_via_script(@params)      if(Alien::SDL::ConfigData->config('script'));
196  return _sdl_config_via_config_data(@params) if(Alien::SDL::ConfigData->config('config'));
197}
198
199### get version info from given header file
200sub get_header_version {
201  my ($package, $header) = @_;
202  return unless $header;
203
204  # try to find header
205  my $root = Alien::SDL->config('prefix');
206  #warn 'Finding in '.$root.'/include';
207  my $include = File::Spec->catfile($root, 'include');
208  my @files;
209  find({ wanted => sub { push @files, rel2abs($_) if /\Q$header\E$/ }, follow => 1, no_chdir => 1, follow_skip => 2 }, $include);
210  return unless @files;
211
212  # get version info
213  open(DAT, $files[0]) || return;
214  my @raw=<DAT>;
215  close(DAT);
216
217  # generic magic how to get version major/minor/patchlevel
218  my ($v_maj) = grep(/^#define[ \t]+[A-Z_]+?MAJOR[A-Z_]*[ \t]+[0-9]+/, @raw);
219  $v_maj =~ s/^#define[ \t]+[A-Z_]+[ \t]+([0-9]+)[.\r\n]*$/$1/;
220  my ($v_min) = grep(/^#define[ \t]+[A-Z_]+MINOR[A-Z_]*[ \t]+[0-9]+/, @raw);
221  $v_min =~ s/^#define[ \t]+[A-Z_]+[ \t]+([0-9]+)[.\r\n]*$/$1/;
222  my ($v_pat) = grep(/^#define[ \t]+[A-Z_]+(PATCHLEVEL|MICRO|RELEASE)[A-Z_]*[ \t]+[0-9]+/, @raw);
223  $v_pat =~ s/^#define[ \t]+[A-Z_]+[ \t]+([0-9]+)[.\r\n]*$/$1/;
224  return if (($v_maj eq '')||($v_min eq '')||($v_pat eq ''));
225  return "$v_maj.$v_min.$v_pat";
226}
227
228### check presence of header(s) specified as params
229sub check_header {
230  my ($package, @header) = @_;
231  print STDERR "[$package] Testing header(s): " . join(', ', @header);
232
233  require ExtUtils::CBuilder; # PAR packer workaround
234
235  my $config = {};
236  if($^O eq 'cygwin') {
237    my $ccflags = $Config{ccflags};
238    $ccflags    =~ s/-fstack-protector//;
239    $config     = { ld => 'gcc', cc => 'gcc', ccflags => $ccflags };
240  }
241
242  my $cb = ExtUtils::CBuilder->new( quiet => 1, config => $config );
243  my ($fs, $src) = File::Temp::tempfile('aaXXXX', SUFFIX => '.c', UNLINK => 1);
244  my $inc = '';
245  my $i = 0;
246  foreach (@header) {
247    @header = (splice(@header, 0, $i) , 'stdio.h', splice(@header, $i)) if $_ eq 'jpeglib.h';
248    $i++;
249  }
250  $inc .= "#include <$_>\n" for @header;
251  syswrite($fs, <<MARKER); # write test source code
252#if defined(_WIN32) && !defined(__CYGWIN__)
253/* GL/gl.h on Win32 requires windows.h being included before */
254#include <windows.h>
255#endif
256$inc
257int demofunc(void) { return 0; }
258
259MARKER
260  close($fs);
261  my $obj;
262  my $stdout = '';
263  my $stderr = '';
264  ($stdout, $stderr) = Capture::Tiny::capture {
265    $obj = eval { $cb->compile( source => $src, extra_compiler_flags => Alien::SDL->config('cflags')); };
266  };
267
268  if($obj) {
269    print STDERR "\n";
270    unlink $obj;
271    return 1;
272  }
273  else {
274    if( $stderr ) {
275      $stderr =~ s/[\r\n]$//;
276      $stderr =~ s/^\Q$src\E[\d\s:]*//;
277      print STDERR " NOK: ($stderr)\n";
278    }
279    # on Windows (MSVC) stdout is set, but not stderr
280    else {
281      $stdout =~ s/[\r\n]$//;
282      $stdout =~ s/.+[\r\n]//;
283      $stdout =~ s/^\Q$src\E[\(\)\d\s:]*//;
284      print STDERR " NOK: ($stdout)\n";
285    }
286
287    return 0;
288  }
289}
290
291### internal functions
292sub _sdl_config_via_script
293{
294  my $param    = shift;
295  my @add_libs = @_;
296  my $devnull = File::Spec->devnull();
297  my $script = Alien::SDL::ConfigData->config('script');
298  return unless ($script && ($param =~ /[a-z0-9_]*/i));
299  my $val = `$script --$param 2>$devnull`;
300  $val =~ s/[\r\n]*$//;
301  if($param eq 'cflags') {
302    $val .= ' ' . Alien::SDL::ConfigData->config('additional_cflags');
303  }
304  elsif($param eq 'libs') {
305    $val .= ' ' . join(' ', @add_libs) if scalar @add_libs;
306    $val .= ' ' . Alien::SDL::ConfigData->config('additional_libs');
307  }
308  elsif($param =~ /^(ld_shlib_map|ld_shared_libs|ld_paths)$/) {
309    $val = Alien::SDL::ConfigData->config('config')->{$param};
310  }
311  return $val;
312}
313
314sub _sdl_config_via_config_data
315{
316  my $param    = shift;
317  my @add_libs = @_;
318  my $share_dir = dist_dir('Alien-SDL');
319  my $subdir = Alien::SDL::ConfigData->config('share_subdir');
320  return unless $subdir;
321  my $real_prefix = catdir($share_dir, $subdir);
322  return unless ($param =~ /[a-z0-9_]*/i);
323  my $val = Alien::SDL::ConfigData->config('config')->{$param};
324  return unless $val;
325  # handle additional flags
326  if($param eq 'cflags') {
327    $val .= ' ' . Alien::SDL::ConfigData->config('additional_cflags');
328  }
329  elsif($param eq 'libs') {
330    $val .= ' ' . join(' ', @add_libs) if scalar @add_libs;
331    $val .= ' ' . Alien::SDL::ConfigData->config('additional_libs');
332  }
333  # handle @PrEfIx@ replacement
334  if ($param =~ /^(ld_shared_libs|ld_paths)$/) {
335    s/\@PrEfIx\@/$real_prefix/g foreach (@{$val});
336  }
337  elsif ($param =~ /^(ld_shlib_map)$/) {
338    while (my ($k, $v) = each %$val ) {
339      $val->{$k} =~ s/\@PrEfIx\@/$real_prefix/g;
340    }
341  }
342  else {
343    $val =~ s/\@PrEfIx\@/$real_prefix/g;
344  }
345  return $val;
346}
347
3481;
349