Group
Extension

SDL2-FFI/builder/SDL2.pm

package builder::SDL2 {
    use strict;
    use warnings;
    use experimental 'signatures';
    use HTTP::Tiny;
    use Path::Tiny qw[path];
    use Archive::Extract;
    #
    use ExtUtils::CBuilder;
    #
    use Config;
    use Module::Build::Tiny;
    #
    use Carp::Always;
    use Alien::gmake;
    #
    $|++;
    #
    #$ENV{TSDL2} = './temp/';
    #
    my $basedir  = Path::Tiny->cwd;    #->child('sdl_libs');
    my $tempdir  = $ENV{TSDL2} ? $basedir->child( $ENV{TSDL2} ) : Path::Tiny->tempdir();
    my $sharedir = $basedir->child('share');

    #`rm -rf $sharedir`;
    #
    my $quiet = $ENV{QSDL2} // 0;
    #
    #die $sharedir;
    #
    my @libraries   = qw[SDL2 SDL2_image SDL2_mixer SDL2_ttf  SDL2_gfx];
    my %libversions = (                                                  # Allow custom lib versions
        SDL2       => $ENV{VSDL2}       // '2.0.16',
        SDL2_mixer => $ENV{VSDL2_mixer} // '2.0.4',
        SDL2_ttf   => $ENV{VSDL2_ttf}   // '2.0.15',
        SDL2_image => $ENV{VSDL2_image} // '2.0.5',
        SDL2_gfx   => $ENV{VSDL2_gfx}   // '1.0.4'
    );
    my %sdl2_urls = (
        SDL2       => 'https://www.libsdl.org/release/SDL2-%s%s',
        SDL2_mixer => 'https://www.libsdl.org/projects/SDL_mixer/release/SDL2_mixer-%s%s',
        SDL2_ttf   => 'https://www.libsdl.org/projects/SDL_ttf/release/SDL2_ttf-%s%s',
        SDL2_image => 'https://www.libsdl.org/projects/SDL_image/release/SDL2_image-%s%s',
        SDL2_gfx   => 'https://github.com/a-hurst/sdl2gfx-builds/releases/download/%s/SDL2_gfx-%s%s'
    );
    my %override_urls = (    # Allow custom download URLs for libs (github releases, tags, etc.)
        ( defined $ENV{DSDL2}       ? ( SDL2       => $ENV{DSDL2} )       : () ),
        ( defined $ENV{DSDL2_mixer} ? ( SDL2_mixer => $ENV{DSDL2_mixer} ) : () ),
        ( defined $ENV{DSDL2_ttf}   ? ( SDL2_ttf   => $ENV{DSDL2_ttf} )   : () ),
        ( defined $ENV{DSDL2_image} ? ( SDL2_image => $ENV{DSDL2_image} ) : () ),
        ( defined $ENV{DSDL2_gfx}   ? ( SDL2_gfx   => $ENV{DSDL2_gfx} )   : () ),
        libwebp => 'http://storage.googleapis.com/downloads.webmproject.org/releases/webp/%s.tar.gz'
    );
    #
    my ( $cflags, $lflags )
        = $sharedir->child('config.ini')->is_file ?
        $sharedir->child('config.ini')->lines( { chomp => 1, count => 2 } ) :
        ();
    #
    sub SDL_Build () {
        my $action = @ARGV && $ARGV[0] =~ /\A\w+\z/ ? shift @ARGV : 'build';
        if ( $action eq 'build' ) {
            my $x64 = $Config{ptrsize} == 8;
            if (
                !$sharedir->is_dir

                #|| $sharedir->child('lib')->children < 4
            ) {
                buildDLLs( $^O, $x64 );
                build_api_wrapper( $^O, $x64 );
            }
            build_api_wrapper( $^O, $x64 ) if -d '.git';    # Always build during dev...
        }
        Module::Build::Tiny::Build();
    }

    sub SDL_Build_PL {
        my $meta = Module::Build::Tiny::get_meta();
        printf "Creating new 'Build' script for '%s' version '%s'\n", $meta->name, $meta->version;

        #my $dir = $meta->name eq 'Module-Build-Tiny' ? "use lib 'lib';" : '"./";';
        Module::Build::Tiny::write_file( 'Build',
            "#!perl\nuse lib '.';\nuse builder::SDL2;\nbuilder::SDL2::SDL_Build();\n" );
        Module::Build::Tiny::make_executable('Build');
        my @env
            = defined $ENV{PERL_MB_OPT} ?
            Module::Build::Tiny::split_like_shell( $ENV{PERL_MB_OPT} ) :
            ();
        Module::Build::Tiny::write_file( '_build_params',
            Module::Build::Tiny::encode_json( [ \@env, \@ARGV ] ) );
        $meta->save(@$_) for ['MYMETA.json'], [ 'MYMETA.yml' => { version => 1.4 } ];
    }

    sub buildDLLs ( $platform_name, $x64 ) {
        for my $d ( $tempdir, $sharedir ) {

            #$d->remove_tree( { safe => 0, verbose => !$quiet } );
            $d->mkpath( { verbose => !$quiet } );
        }
        $sharedir->child('lib')->mkpath( { verbose => !$quiet } );
        $sharedir->child('bin')->mkpath( { verbose => !$quiet } );
        if ( 'MSWin32' eq $platform_name ) {
            my $http = HTTP::Tiny->new;
            for my $lib (@libraries) {

                # Download zip archive containing library
                my $libversion = $libversions{$lib};

                # https://www.libsdl.org/projects/SDL_image/release/SDL2_image-2.0.5-win32-x86.zip
                # https://www.libsdl.org/projects/SDL_image/release/SDL2_image-2.0.52.0.5
                #my $liburl = sprintf $sdl2_urls{$lib},
                #    $lib eq 'SDL2_gfx' ?
                #    ( $libversion, $libversion, $x64 ? '-win32-x64.zip' : '-win32-x86.zip' ) :
                #    $lib ne 'SDL2' ? ( $libversion, $x64 ? '-win32-x64.zip' : '-win32-x86.zip' ) :
                #    ( 'devel-' . $libversion, '-mingw.tar.gz' );
                my $liburl = sprintf $sdl2_urls{$lib},
                    $lib eq 'SDL2_gfx' ?
                    ( $libversion, $libversion, $x64 ? '-win32-x64.zip' : '-win32-x86.zip' ) :
                    ( 'devel-' . $libversion, '-mingw.tar.gz' );
                printf 'Downloading %s %s... ', $lib, $libversion;
                my $sourcepath = fetch_source( $liburl,
                    $tempdir->child( Path::Tiny->new($liburl)->basename ) );
                if ($sourcepath) {
                    if ( $lib eq 'SDL2_gfx' ) {
                        warn 'HERE!!!!!!!!!!!!!!!!!!!!!';
                        warn $sourcepath->child('SDL2_gfx.dll')->absolute->stringify;
                        warn $sharedir->child( 'bin', 'SDL2_gfx.dll' )->absolute->stringify;

                        #sleep 120;
                        $sourcepath->child('SDL2_gfx.dll')
                            ->move( $sharedir->child( 'bin', 'SDL2_gfx.dll' ) );
                        $sharedir->child( 'bin', 'SDL2_gfx.dll' )->chmod('0755');

                        #sleep 120;
                    }
                    elsif ( $sourcepath->child('Makefile')->is_file ) {
                        my $orig_path = Path::Tiny->cwd->absolute;
                        chdir $sourcepath;

                        #system Alien::gmake->exe, 'install-package',
                        #    'arch=' . ( $x64 ? 'i686-w64-mingw32' : 'x86_64-w64-mingw32' ),
                        #    'prefix=' . $sharedir->absolute->stringify;
                        # collect files sizes
                        my $sizes
                            = $sourcepath->child( $x64 ? 'x86_64-w64-mingw32' : 'i686-w64-mingw32' )
                            ->visit(
                            sub {
                                my ( $path, $state ) = @_;
                                my $rel = $path->relative(
                                    $sourcepath->child(
                                        $x64 ? 'x86_64-w64-mingw32' : 'i686-w64-mingw32'
                                    )
                                );
                                my $dest = $sharedir->child($rel);
                                if ( $path->is_dir ) {
                                    warn $dest;

                                    #use Data::Dump;
                                    #ddx $dest->mkpath({ verbose => !$quiet, chmod => '0755' });
                                    return;
                                }
                                $dest->touchpath;
                                $path->copy( $sharedir->child($rel) );
                                $state->{$rel} = -s $path;
                            },
                            { recurse => 1 }
                            );
                        my $prefix = $sharedir->absolute->stringify;
                        $sharedir->child( 'bin', 'sdl2-config' )
                            ->edit_raw( sub {s[^prefix=.*][prefix=${prefix}]smg} );
                        $sharedir->child( 'bin', 'sdl2-config' )->chmod('0755');
                        $sharedir->child( 'lib', 'libSDL2.la' )
                            ->edit_raw( sub {s[^libdir=.*][prefix=${prefix}/lib]smg} );
                        $sharedir->child( 'lib', 'libSDL2main.la' )
                            ->edit_raw( sub {s[^libdir=.*][libdir=${prefix}/lib]smg} );
                        $sharedir->child( 'lib', 'pkgconfig', 'sdl2.pc' )
                            ->edit_raw( sub {s[^prefix=.*][prefix=${prefix}]smg} );
                        chdir $orig_path;
                    }
                    else {
                        #$sourcepath->child('Makefile')->is_file
                        #$sourcepath->child('SDL2_gfx.dll')->absolute->stringify;
                        #$sharedir->child( 'bin', 'SDL2_gfx.dll' )->absolute->stringify;
                        #sleep 120;
                        for my $kid ( $sourcepath->children(qr/.*\.dll/) ) {
                            warn $kid;
                            my $dest = $sharedir->child( 'bin', $kid->basename );
                            warn $dest;
                            $dest->touchpath;
                            $kid->copy($dest);
                            $dest->chmod('0755');
                        }
                    }
                }

                #else {
                #    die 'oops!';
                #}
            }
            $cflags
                = ( $x64 ? '-m64' : '-m32' ) .
                ' -Dmain=SDL_main -I' . $sharedir->child('include')->absolute .
                ' -I' . $sharedir->child( 'include', 'SDL2' )->absolute;
            $lflags = ( $x64 ? '-m64' : '-m32' ) . ' -L' .
                $sharedir->child('lib')->absolute . ' -lSDL2main -lSDL2 -lSDL2_mixer -mwindows ';

#' -Wl,--dynamicbase -Wl,--nxcompat -lm -ldinput8 -ldxguid -ldxerr8 -luser32 -lgdi32 -lwinmm -limm32 -lole32 -loleaut32 -lshell32 -lsetupapi -lversion -luuid ';
# TODO: store in config file:
# cflags = '-I'
#ld flags = '-lmingw32 -lSDL2main -lSDL2 -ggdb3 -O0 --std=c99 -lSDL2_image -lm  -Wall'
        }
        else {
            my $suffix = '.tar.gz';    # source code

            # Set required environment variables for custom prefix
            my %buildenv      = %ENV;
            my $pkgconfig_dir = $sharedir->child( 'lib', 'pkgconfig' );
            my $builtlib_dir  = $sharedir->child('lib');
            my $include_dir   = $sharedir->child('include');
            #
            $buildenv{PKG_CONFIG_PATH} .= $pkgconfig_dir->absolute;
            $buildenv{LD_LIBRARY_PATH} .= $builtlib_dir->absolute;
            $buildenv{LDFLAGS}         .= '-L' . $builtlib_dir->absolute;
            $buildenv{CPPFLAGS}
                .= '-I' . $include_dir->absolute . ' -I' . $include_dir->parent->absolute;
            #
            my $outdir = $sharedir->child('download');    #Path::Tiny->tempdir;
            $sdl2_urls{SDL2_gfx} = 'http://www.ferzkopp.net/Software/SDL2_gfx/SDL2_gfx-%s%s';
            for my $lib (@libraries) {
                my $libversion = $libversions{$lib};
                printf 'Downloading %s %s... ', $lib, $libversion;
                my $liburl = $override_urls{$lib} // sprintf $sdl2_urls{$lib}, $libversion, $suffix;
                my $libfolder  = $lib . '-' . $libversion;
                my $sourcepath = fetch_source( $liburl,
                    $tempdir->child( Path::Tiny->new($liburl)->basename ) );
                if ( !$sourcepath ) {
                    die 'something went wrong!';
                }

                # Check for any external dependencies and set correct build order
                my @dependencies;
                my @ignore = (
                    'libvorbisidec'    # only needed for special non-standard builds
                );
                my @build_first = qw[zlib harfbuzz];
                my @build_last  = qw[libvorbis opusfile flac];
                my $ext_dir     = $sourcepath->child('external');
                if ( $ext_dir->is_dir ) {
                    my @dep_dirs = $ext_dir->children();
                    my ( @deps_first, @deps, @deps_last );
                    for my $dep ( grep { $_->is_dir } @dep_dirs ) {
                        my $dep_path = $ext_dir->child($dep);
                        next if !$dep_path->is_dir;
                        my ( $depname, $depversion ) = split '-', $dep->basename;
                        next if grep { $_ eq $depname } @ignore;
                        if ( grep { $_ eq $depname } @build_first ) {
                            push @deps_first, $dep;
                        }
                        elsif ( grep { $_ eq $depname } @build_last ) {
                            push @deps_last, $dep;
                        }
                        else { push @deps, $dep }
                    }
                    @dependencies = ( @deps_first, @deps, @deps_last );
                }

                # Build any external dependencies
                my %extra_args
                    = ( opusfile => ['--disable-http'], freetype => ['--enable-freetype-config'] );
                for my $dep (@dependencies) {
                    my ( $depname, $depversion ) = split '-', $dep;
                    my $dep_path = $ext_dir->child($dep);
                    if ( defined $override_urls{$depname} ) {
                        printf "======= Downloading alternate source for %s =======\n", $dep;
                        my $liburl = sprintf $override_urls{$depname}, $dep;
                        path($dep_path)->move( $dep_path . '_bad' );
                        $dep_path
                            = fetch_source( $liburl,
                            $ext_dir->child( Path::Tiny->new($liburl)->basename ),
                            );
                    }
                    printf "======= Compiling %s dependency %s =======\n", $lib, $dep;
                    my $xtra_args;
                    if ( grep { $_ eq $depname } keys %extra_args ) {
                        $xtra_args = $extra_args{$depname};
                    }
                    die 'Error building ' . $dep
                        unless make_install_lib( $dep_path, $sharedir, \%buildenv, $xtra_args );
                    printf "\n======= %s built sucessfully =======\n", $dep;
                }

                # Build the library
                printf "======= Compiling %s %s =======\n", $lib, $libversion;
                my $xtra_args = ();
                $xtra_args = [ '--with-ft-prefix=' . $sharedir->absolute ] if $lib eq 'SDL2_ttf';
                die 'Error building ' . $lib
                    unless make_install_lib( $sourcepath, $sharedir, \%buildenv, $xtra_args );
                printf "\n======= %s %s built sucessfully =======\n", $lib, $libversion;
                chdir $basedir->absolute;
            }

            # TODO: store in config file
            #chdir $basedir->child('share', 'bin');
            #warn `./sdl2_config --prefix=%s --cflags`;
            #warn `./sdl2_config --prefix=%s --libs`;
            chdir $sharedir->child( 'lib', 'pkgconfig' );
            $ENV{PKG_CONFIG_PATH} .= $sharedir->child( 'lib', 'pkgconfig' )->absolute;
            $cflags = $sharedir->child('include')->absolute . ' ' .
                `pkg-config sdl2.pc SDL2_gfx.pc SDL2_image.pc SDL2_mixer.pc SDL2_ttf.pc --cflags`;
            chomp $cflags;
            $lflags
                = `pkg-config sdl2.pc SDL2_gfx.pc SDL2_image.pc SDL2_mixer.pc SDL2_ttf.pc --libs`;
            chomp $lflags;
            chdir $basedir->absolute;
        }
        $sharedir->child('config.ini')->spew_raw("$cflags\n$lflags");
    }

    sub make_install_lib ( $src_path, $prefix, $buildenv, $extra_args = () ) {
        my $orig_path = Path::Tiny->cwd->absolute;
        local %ENV = %$buildenv;
        chdir $src_path;
        my $success = 0;
        for my $cmd (
            [ './configure', ( $quiet ? '--silent' : () ), '--prefix=' . $prefix ],
            [ Alien::gmake->exe, ( $quiet ? '--silent' : () ), '-j10' ],
            [ Alien::gmake->exe, ( $quiet ? '--silent' : () ), 'install' ]
        ) {
            if ( $cmd->[0] eq './configure' && $extra_args ) {
                push @$cmd, @$extra_args;
            }
            $success = 1 if system(@$cmd) == 0;
            if ( $? == -1 ) {
                print "failed to execute: $!\n";
                last;
            }
            elsif ( $? & 127 ) {
                printf "child died with signal %d, %s coredump\n", ( $? & 127 ),
                    ( $? & 128 ) ? 'with' : 'without';
                last;
            }
            else {
                printf "child exited with value %d\n", $? >> 8;
            }
        }
        chdir $orig_path;
        return $success;
    }

    sub fetch_source ( $liburl, $outfile ) {
        CORE::state $http //= HTTP::Tiny->new();

        #printf '%s => %s ... ', $liburl, $outfile;
        $outfile->parent->mkpath;
        my $response = $http->mirror( $liburl, $outfile, {} );
        if ( $response->{success} ) {    #ddx $response;
            CORE::say 'okay';
            my $outdir = $outfile->parent

                #->child(
                #		$outfile->basename('.tar.gz', '.zip'))
                ;
            printf 'Extracting to %s... ', $outdir;
            my $ae = Archive::Extract->new( archive => $outfile );
            if ( $ae->extract( to => $outdir ) ) {
                CORE::say 'okay';
                return Path::Tiny->new( $ae->extract_path );
            }
            else {
                CORE::say 'oops!';
            }
        }
        else {
            CORE::say 'oops!';

            #warn $liburl;
            #use Data::Dump;
            #ddx($response);
        }
    }

    sub build_api_wrapper ( $platform_name, $x64 ) {
        my $c = $basedir->child( 'src', 'api_wrapper.cpp' );
        use FFI::Build;
        my $libperl = `perl -MExtUtils::Embed -e ldopts`;
        chomp $libperl;
        my $idk = `perl -MExtUtils::Embed -e ccopts`;
        chomp $idk;
        my $build = FFI::Build->new(
            'api_wrapper',
            cflags  => $idk . $cflags . ' -fPIC -I' . path( $Config{archlibexp}, 'CORE' ),
            dir     => $sharedir->child('lib')->absolute->stringify,
            libs    => $lflags . ( $platform_name eq 'MSWin32' ? $libperl : '' ),
            source  => [ $c->absolute->stringify ],
            verbose => $quiet ? 0 : 2
        );

        # $lib is an instance of FFI::Build::File::Library
        my $lib = $build->build;

        #my $ffi = FFI::Platypus->new( api => 1 );
        # The filename will be platform dependant, but something like libfrooble.so or frooble.dll
        #$ffi->lib( $lib->path );
        warn $lib;
        return $lib;
    }
}
1;


Powered by Groonga
Maintained by Kenichi Ishigaki <ishigaki@cpan.org>. If you find anything, submit it on GitHub.