2010-04-07 4 views
5

Ich habe ein Modul, das auf verschiedene Betriebssysteme und Konfigurationen zielt. Manchmal kann ein C-Code die Aufgabe dieses Moduls ein wenig einfacher machen, also habe ich einige C-Funktionen, die ich den Code binden möchte. Ich habe keine haben binden die C-Funktionen - Ich kann nicht garantieren , dass der Endbenutzer sogar einen C-Compiler zum Beispiel hat, und es ist in der Regel kein Problem, Failover anmutig zu einem reinen Perl Weg Vollendung das gleiche - aber es wäre schön, wenn ich die C-Funktionen aus dem Perl-Skript aufrufen könnte.Wie kompiliere ich C-Code-Snippets zu meinem Perl-Modul bedingt?

Immer noch mit mir? Hier ist ein weiterer kniffliger Teil. Fast alle der C-Code ist systemspezifisch - eine für Windows geschriebene Funktion kompiliert nicht auf Linux und umgekehrt, und die Funktion, die eine ähnliche Sache auf Solaris tut, wird völlig anders aussehen.

#include <some/Windows/headerfile.h> 
int foo_for_Windows_c(int a,double b) 
{ 
    do_windows_stuff(); 
    return 42; 
} 

#include <path/to/linux/headerfile.h> 
int foo_for_linux_c(int a,double b) 
{ 
    do_linux_stuff(7); 
    return 42; 
} 

Darüber hinaus auch für native Code, der das gleiche System zielt, ist es möglich, dass nur einige von ihnen können auf eine bestimmte Konfiguration zusammengestellt werden.

#include <some/headerfile/that/might/not/even/exist.h> 
int bar_for_solaris_c(int a,double b) 
{ 
    call_solaris_library_that_might_be_installed_here(11); 
    return 19; 
} 

Aber im Idealfall könnten wir noch die C-Funktionen verwenden, die mit dieser Konfiguration zusammenstellen würde. Also meine Fragen sind:

  • wie kann ich kompilieren C-Funktionen bedingt (kompilieren nur den Code, dass für den aktuellen Wert von $^O geeignet ist)?

  • Wie kann ich C-Funktionen einzeln kompilieren (einige Funktionen könnten nicht kompilieren, aber wir wollen immer noch diejenigen verwenden, die)?

  • kann ich dies während der Bauzeit (während der Endbenutzer das Modul installiert) oder zur Laufzeit (z. B. mit Inline::C) tun? Welcher Weg ist besser?

  • Wie würde ich sagen, welche Funktionen erfolgreich kompiliert wurden und sind zur Verwendung von Perl zur Verfügung?

Alle Gedanken geschätzt!


Update: Vielen Dank an alle, die geantwortet haben.Also hier ist, was ich tat:

ich ein System von Laufzeit mit Inline::C innerhalb von eval Aussagen bindend betrachtet, aber letztlich setzte sich auf Subklassen Module::Build und das Anpassen der ACTION_build Methode:

my $builderclass = Module::Build->subclass(
class => 'My::Custom::Builder', 
code => <<'__CUSTOM_BUILD_CODE__,', 
sub ACTION_build { 
    use File::Copy; 
    my $self = shift; 

    ### STEP 1: Compile all .xs files, remove the ones that fail ###  
    if (! -f "./lib/xs/step1") { 
    unlink <lib/xs/*>; 
    foreach my $contrib_file (glob("contrib/*.xs")) { 
     File::Copy::copy($contrib_file, "lib/xs/"); 
    } 
    open my $failed_units_fh, '>', 'lib/xs/step1'; 
    local [email protected] = undef; 
    do { 
     my $r = eval { $self->ACTION_code() }; 
     if ([email protected] =~ /error building (\S+\.o) from/i 
      || [email protected] =~ /error building dll file from '(\S+\.c)'/i) { 
     my $bad_file = $1; 
     $bad_file =~ s!\\!/!g; 
     my $bad_xs = $bad_file; 
     $bad_xs =~ s/.[oc]$/.xs/; 

     print STDERR "ERROR COMPILING UNIT $bad_xs ... removing\n\n"; 
     unlink $bad_xs; 
     print $failed_units_fh "$bad_xs\n"; 
     } elsif ([email protected]) { 
     print STDERR "Compile error not handled in $^O: [email protected]\n"; 
     } 
    } while [email protected]; 
    print "Removed all uncompilable units from lib/xs/\n"; 
    close $failed_units_fh; 
    } 

    ### STEP 2: Combine valid .xs files into a single .xs file ### 
    if (! -f "./lib/xs/step2") { 
    open my $valid_units_fh, '>', "lib/xs/step2"; 
    my (@INCLUDE,%INCLUDE,$MODULE,@PREMOD,@POSTMOD); 
    foreach my $xs (glob("lib/xs/*.xs")) { 
     open my $xs_fh, '<', $xs; 
     while (<$xs_fh>) { 
     if (m/#include/) { 
      next if $INCLUDE{$_}++; 
      push @INCLUDE, $_; 
     } elsif (/^MODULE/) { 
      $MODULE = $_; 
      push @POSTMOD, <$xs_fh>; 
     } else { 
      push @PREMOD, $_; 
     } 
     } 
     close $xs_fh; 
     print $valid_units_fh "$xs\n"; 
    } 
    close $valid_units_fh; 
    unlink <lib/xs/*>, <blib/arch/auto/xs/*/*>; 
    unlink 'lib/My/Module.xs'; 
    open my $xs_fh, '>', 'lib/My/Module.xs' or croak $!; 
    print $xs_fh @INCLUDE, @PREMOD, $MODULE, @POSTMOD; 
    close $xs_fh; 
    print "Assembled remaining XS files into lib/My/Module.xs\n"; 
    } 

    ### STEP 3: Clean all .xs stuff and compile My/Module.xs ### 
    unlink <lib/xs/*>; 
    $self->ACTION_code(); 
    return $self->SUPER::ACTION_build(@_); 
    } 
} 

Die Überprüfung [email protected] ist wahrscheinlich ziemlich zerbrechlich. Es funktioniert auf den Systemen Ich habe versucht (alle mit gcc), aber es wird wahrscheinlich nicht funktionieren, wie es überall geschrieben wird.

Antwort

5

Idealerweise verwenden Module::Build. Zur Konfigurationszeit (perl Build.PL), erkennen Sie die Plattform und Header-Position (aber auch Benutzer Kommandozeilenoptionen festlegen, um die Erkennung zu überschreiben), setzen Sie die entsprechenden extra_compiler_flags und extra_linker_flags im Konstruktor und kopieren Sie dann die relevanten Dateien aus z. contrib bis lib (wo sie automatisch von ExtUtils::CBuilder abgeholt werden). Jetzt ist die Distribution an die Plattform angepasst - die nächsten Schritte (./Build ; …) funktionieren wie gewohnt.

+0

Intriguing ... Ich werde das 'Module :: Build' Kugel beißen und versuchen dies. Sollten die Dateien in 'contrib /' C oder XS sein? XS wäre eine weitere Kugel zu beißen. – mob

+0

Muss XS sein. – daxim

2

In einem meiner Module Ich habe das folgende Stück Code:

my $C_support = Module::Build::ConfigData->feature("C_support") 

my $builder = Module::Build->new(
    ... 
    config_data => { 
     C_support => $C_support 
    } 
); 
$builder->xs_files({}) if not $C_support; 

Dann im Code ich es erkennen, indem module_name Laden :: ConfigData und die Config-Methode aufrufen.

if (Module_name::ConfigData->config("C_support")) { 
    XSLoader::load(__PACKAGE__, $VERSION); 
} 
if (not defined &somefunction) { 
    #define it 
} 

Einzelheiten sehen Sie sich meine Build.PL und Module.pm

1

Ich habe Techniken wie folgt aus:

sub slow_function { 
    # slow fallback perl code if possible 
} 
BEGIN { 
    eval { 
     require Inline; 

     if ($condition) { 
      Inline->import(C => q { 
       int slow_function (...) { 
        // c function to conditionally compile 
       } 
      }) 
     } else { 
      Inline->import(C => q { 
       int slow_function (...) { 
        // c function with something different 
       } 
      }) 
     } 
     1; 
    } or print STDERR "Inline::C error: [email protected] perl fallback used instead\n"; 
} 
+0

Dies ist ähnlich dem Ansatz, den ich im Sinn hatte, aber das Kompilieren zur Laufzeit (wobei der Treffer beim ersten Mal genommen wurde, "slow_function" wurde verwendet und nicht beim ersten Aufruf des Skripts). – mob

+0

Das funktioniert sicherlich auch. In meinem Fall wusste ich, dass ich die Funktion im Voraus brauchen würde. Während der Entwicklung ist eines der schönen Dinge bei der Verwendung von Inline :: C, dass das Ändern des C-Codes eine erneute Kompilierung bei der nächsten Ausführung auslöst, so dass man nicht vergessen muss, das Build-Skript erneut auszuführen. –